1 /*  $Id: hisv6.c 10305 2018-12-02 14:21:56Z iulius $
2 **
3 **  History v6 implementation against the history API.
4 **
5 **  Copyright (c) 2001, Thus plc
6 **
7 **  Redistribution and use of the source code in source and binary
8 **  forms, with or without modification, are permitted provided that
9 **  the following 3 conditions are met:
10 **
11 **  1. Redistributions of the source code must retain the above
12 **  copyright notice, this list of conditions and the disclaimer
13 **  set out below.
14 **
15 **  2. Redistributions of the source code in binary form must
16 **  reproduce the above copyright notice, this list of conditions
17 **  and the disclaimer set out below in the documentation and/or
18 **  other materials provided with the distribution.
19 **
20 **  3. Neither the name of the Thus plc nor the names of its
21 **  contributors may be used to endorse or promote products
22 **  derived from this software without specific prior written
23 **  permission from Thus plc.
24 **
25 **  Disclaimer:
26 **
27 **  "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28 **  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29 **  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30 **  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE DIRECTORS
31 **  OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32 **  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33 **  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34 **  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35 **  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36 **  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37 **  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
38 */
39 
40 #include "config.h"
41 #include "clibrary.h"
42 #include <fcntl.h>
43 #include <limits.h>
44 #include <errno.h>
45 #include "hisinterface.h"
46 #include "hisv6.h"
47 #include "hisv6-private.h"
48 #include "inn/dbz.h"
49 #include "inn/fdflag.h"
50 #include "inn/innconf.h"
51 #include "inn/timer.h"
52 #include "inn/qio.h"
53 #include "inn/sequence.h"
54 #include "inn/inndcomm.h"
55 
56 /*
57 **  because we can only have one open dbz per process, we keep a
58 **  pointer to which of the current history structures owns it
59 */
60 static struct hisv6 *hisv6_dbzowner;
61 
62 
63 /*
64 **  set error status to that indicated by s; doesn't copy the string,
65 **  assumes the caller did that for us
66 */
67 static void
hisv6_seterror(struct hisv6 * h,const char * s)68 hisv6_seterror(struct hisv6 *h, const char *s)
69 {
70     his_seterror(h->history, s);
71 }
72 
73 
74 /*
75 **  format line or offset into a string for error reporting
76 */
77 static void
hisv6_errloc(char * s,size_t line,off_t offset)78 hisv6_errloc(char *s, size_t line, off_t offset)
79 {
80     if (offset != -1) {
81 	/* really we want an autoconf test for %ll/%L/%I64, sigh */
82         snprintf(s, HISV6_MAX_LOCATION, "@%lu", (unsigned long)offset);
83     } else {
84         snprintf(s, HISV6_MAX_LOCATION, ":%lu", (unsigned long)line);
85     }
86 }
87 
88 
89 /*
90 **  split a history line into its constituent components; return a
91 **  bitmap indicating which components we're returning are valid (or
92 **  would be valid if a NULL pointer is passed for that component) or
93 **  -1 for error.  *error is set to a string which describes the
94 **  failure.
95 */
96 static int
hisv6_splitline(const char * line,const char ** error,HASH * hash,time_t * arrived,time_t * posted,time_t * expires,TOKEN * token)97 hisv6_splitline(const char *line, const char **error, HASH *hash,
98 		 time_t *arrived, time_t *posted, time_t *expires,
99 		 TOKEN *token)
100 {
101     const char *p = line;
102     char *end;
103     unsigned long l;
104     int r = 0;
105 
106     /* parse the [...] hash field */
107     if (*p != '[') {
108 	*error = "`[' missing from history line";
109 	return -1;
110     }
111     ++p;
112     if (hash)
113 	*hash = TextToHash(p);
114     p += 32;
115     if (*p != ']') {
116 	*error = "`]' missing from history line";
117 	return -1;
118     }
119     ++p;
120     r |= HISV6_HAVE_HASH;
121     if (*p != HISV6_FIELDSEP) {
122 	*error = "field separator missing from history line";
123 	return -1;
124     }
125 
126     /* parse the arrived field */
127     l = strtoul(p + 1, &end, 10);
128     p = end;
129     if (l == ULONG_MAX) {
130 	*error = "arrived timestamp out of range";
131 	return -1;
132     }
133     r |= HISV6_HAVE_ARRIVED;
134     if (arrived)
135 	*arrived = (time_t)l;
136     if (*p != HISV6_SUBFIELDSEP) {
137 	/* no expires or posted time */
138 	if (posted)
139 	    *posted = 0;
140 	if (expires)
141 	    *expires = 0;
142     } else {
143 	/* parse out the expires field */
144 	++p;
145 	if (*p == HISV6_NOEXP) {
146 	    ++p;
147 	    if (expires)
148 		*expires = 0;
149 	} else {
150 	    l = strtoul(p, &end, 10);
151             p = end;
152 	    if (l == ULONG_MAX) {
153 		*error = "expires timestamp out of range";
154 		return -1;
155 	    }
156 	    r |= HISV6_HAVE_EXPIRES;
157 	    if (expires)
158 		*expires = (time_t)l;
159 	}
160 	/* parse out the posted field */
161 	if (*p != HISV6_SUBFIELDSEP) {
162 	    /* no posted time */
163 	    if (posted)
164 		*posted = 0;
165 	} else {
166 	    ++p;
167 	    l = strtoul(p, &end, 10);
168             p = end;
169 	    if (l == ULONG_MAX) {
170 		*error = "posted timestamp out of range";
171 		return -1;
172 	    }
173 	    r |= HISV6_HAVE_POSTED;
174 	    if (posted)
175 		*posted = (time_t)l;
176 	}
177     }
178 
179     /* parse the token */
180     if (*p == HISV6_FIELDSEP)
181 	++p;
182     else if (*p != '\0') {
183 	*error = "field separator missing from history line";
184 	return -1;
185     }
186     /* IsToken false would imply a remembered line, or where someone's
187      * used prunehistory */
188     if (IsToken(p)) {
189 	r |= HISV6_HAVE_TOKEN;
190 	if (token)
191 	    *token = TextToToken(p);
192     }
193     return r;
194 }
195 
196 
197 /*
198 **  Given the time, now, return the time at which we should next check
199 **  the history file
200 */
201 static unsigned long
hisv6_nextcheck(struct hisv6 * h,unsigned long now)202 hisv6_nextcheck(struct hisv6 *h, unsigned long now)
203 {
204     return now + h->statinterval;
205 }
206 
207 
208 /*
209 **  close any dbz structures associated with h; we also manage the
210 **  single dbz instance voodoo
211 */
212 static bool
hisv6_dbzclose(struct hisv6 * h)213 hisv6_dbzclose(struct hisv6 *h)
214 {
215     bool r = true;
216 
217     if (h == hisv6_dbzowner) {
218 	if (!hisv6_sync(h))
219 	    r = false;
220 	if (!dbzclose()) {
221 	    hisv6_seterror(h, concat("can't dbzclose ",
222 				      h->histpath, " ",
223 				      strerror(errno), NULL));
224 	    r = false;
225 	}
226 	hisv6_dbzowner = NULL;
227     }
228     return r;
229 }
230 
231 
232 /*
233 **  close an existing history structure, cleaning it to the point
234 **  where we can reopon without leaking resources
235 */
236 static bool
hisv6_closefiles(struct hisv6 * h)237 hisv6_closefiles(struct hisv6 *h)
238 {
239     bool r = true;
240 
241     if (!hisv6_dbzclose(h))
242 	r = false;
243 
244     if (h->readfd != -1) {
245 	if (close(h->readfd) != 0 && errno != EINTR) {
246 	    hisv6_seterror(h, concat("can't close history ",
247 				      h->histpath, " ",
248 				      strerror(errno),NULL));
249 	    r = false;
250 	}
251 	h->readfd = -1;
252     }
253 
254     if (h->writefp != NULL) {
255 	if (ferror(h->writefp) || fflush(h->writefp) == EOF) {
256 	    hisv6_seterror(h, concat("error on history ",
257 				      h->histpath, " ",
258 				      strerror(errno), NULL));
259 	    r = false;
260 	}
261 	if (Fclose(h->writefp) == EOF) {
262 	    hisv6_seterror(h, concat("can't fclose history ",
263 				      h->histpath, " ",
264 				      strerror(errno), NULL));
265 	    r = false;
266 	}
267 	h->writefp = NULL;
268         h->offset = 0;
269     }
270 
271     h->nextcheck = 0;
272     h->st.st_ino = (ino_t)-1;
273     h->st.st_dev = (dev_t)-1;
274     return r;
275 }
276 
277 
278 /*
279 **  Reopen (or open from fresh) a history structure; assumes the flags
280 **  & path are all set up, ready to roll. If we don't own the dbz, we
281 **  suppress the dbz code; this is needed during expiry (since the dbz
282 **  code doesn't yet understand multiple open contexts... yes its a
283 **  hack)
284 */
285 static bool
hisv6_reopen(struct hisv6 * h)286 hisv6_reopen(struct hisv6 *h)
287 {
288     bool r = false;
289 
290     if (h->flags & HIS_RDWR) {
291 	const char *mode;
292 
293 	if (h->flags & HIS_CREAT)
294 	    mode = "w";
295 	else
296 	    mode = "r+";
297 	if ((h->writefp = Fopen(h->histpath, mode, INND_HISTORY)) == NULL) {
298 	    hisv6_seterror(h, concat("can't fopen history ",
299 				      h->histpath, " ",
300 				      strerror(errno), NULL));
301 	    hisv6_closefiles(h);
302 	    goto fail;
303 	}
304 	if (fseeko(h->writefp, 0, SEEK_END) == -1) {
305 	    hisv6_seterror(h, concat("can't fseek to end of ",
306 				      h->histpath, " ",
307 				      strerror(errno), NULL));
308 	    hisv6_closefiles(h);
309 	    goto fail;
310 	}
311         h->offset = ftello(h->writefp);
312 	if (h->offset == -1) {
313 	    hisv6_seterror(h, concat("can't ftello ", h->histpath, " ",
314 				      strerror(errno), NULL));
315 	    hisv6_closefiles(h);
316 	    goto fail;
317 	}
318 	fdflag_close_exec(fileno(h->writefp), true);
319     }
320 
321     /* Open the history file for reading. */
322     if ((h->readfd = open(h->histpath, O_RDONLY)) < 0) {
323 	hisv6_seterror(h, concat("can't open ", h->histpath, " ",
324 				  strerror(errno), NULL));
325 	hisv6_closefiles(h);
326 	goto fail;
327     }
328     fdflag_close_exec(h->readfd, true);
329 
330     /* if there's no current dbz owner, claim it here */
331     if (hisv6_dbzowner == NULL) {
332 	hisv6_dbzowner = h;
333     }
334 
335     /* During expiry we need two history structures in place, so we
336        have to select which one gets the dbz file */
337     if (h == hisv6_dbzowner) {
338 	dbzoptions opt;
339 
340 	/* Open the DBZ file. */
341 	dbzgetoptions(&opt);
342 
343 	/* HIS_INCORE usually means we're rebuilding from scratch, so
344 	   keep the whole lot in core until we flush */
345 	if (h->flags & HIS_INCORE) {
346 	    opt.writethrough = false;
347 	    opt.pag_incore = INCORE_MEM;
348 #ifndef	DO_TAGGED_HASH
349 	    opt.exists_incore = INCORE_MEM;
350 #endif
351 	} else {
352 	    opt.writethrough = true;
353 #ifdef	DO_TAGGED_HASH
354 	    opt.pag_incore = INCORE_MMAP;
355 #else
356 	    /*opt.pag_incore = INCORE_NO;*/
357 	    opt.pag_incore = (h->flags & HIS_MMAP) ? INCORE_MMAP : INCORE_NO;
358 	    opt.exists_incore = (h->flags & HIS_MMAP) ? INCORE_MMAP : INCORE_NO;
359 
360 # if defined(MMAP_NEEDS_MSYNC) && INND_DBZINCORE == 1
361 	    /* Systems that have MMAP_NEEDS_MSYNC defined will have their
362 	       on-disk copies out of sync with the mmap'ed copies most of
363 	       the time.  So if innd is using INCORE_MMAP, then we force
364 	       everything else to use it, too (unless we're on NFS) */
365 	    if(!innconf->nfsreader) {
366 		opt.pag_incore = INCORE_MMAP;
367 		opt.exists_incore = INCORE_MMAP;
368 	    }
369 # endif
370 #endif
371 	}
372 	dbzsetoptions(opt);
373 	if (h->flags & HIS_CREAT) {
374 	    size_t npairs;
375 
376 	    /* must only do this once! */
377 	    h->flags &= ~HIS_CREAT;
378 	    npairs = (h->npairs == -1) ? 0 : h->npairs;
379 	    if (!dbzfresh(h->histpath, dbzsize(npairs))) {
380 		hisv6_seterror(h, concat("can't dbzfresh ", h->histpath, " ",
381 					  strerror(errno), NULL));
382 		hisv6_closefiles(h);
383 		goto fail;
384 	    }
385 	} else if (!dbzinit(h->histpath)) {
386 	    hisv6_seterror(h, concat("can't dbzinit ", h->histpath, " ",
387 				      strerror(errno), NULL));
388 	    hisv6_closefiles(h);
389 	    goto fail;
390 	}
391     }
392     h->nextcheck = hisv6_nextcheck(h, TMRnow());
393     r = true;
394  fail:
395     return r;
396 }
397 
398 
399 /*
400 ** check if the history file has changed, if so rotate to the new
401 ** history file. Returns false on failure (which is probably fatal as
402 ** we'll have closed the files)
403 */
404 static bool
hisv6_checkfiles(struct hisv6 * h)405 hisv6_checkfiles(struct hisv6 *h)
406 {
407     unsigned long t = TMRnow();
408 
409     if (h->statinterval == 0)
410 	return true;
411 
412     if (h->readfd == -1) {
413 	/* this can happen if a previous checkfiles() has failed to
414 	 * reopen the handles, but our caller hasn't realised... */
415 	hisv6_closefiles(h);
416 	if (!hisv6_reopen(h)) {
417 	    hisv6_closefiles(h);
418 	    return false;
419 	}
420     }
421     if (seq_lcompare(t, h->nextcheck) == 1) {
422 	struct stat st;
423 
424 	if (stat(h->histpath, &st) == 0 &&
425 	    (st.st_ino != h->st.st_ino ||
426 	     st.st_dev != h->st.st_dev)) {
427 	    /* there's a possible race on the history file here... */
428 	    hisv6_closefiles(h);
429 	    if (!hisv6_reopen(h)) {
430 		hisv6_closefiles(h);
431 		return false;
432 	    }
433 	    h->st = st;
434 	}
435 	h->nextcheck = hisv6_nextcheck(h, t);
436     }
437     return true;
438 }
439 
440 
441 /*
442 **  dispose (and clean up) an existing history structure
443 */
444 static bool
hisv6_dispose(struct hisv6 * h)445 hisv6_dispose(struct hisv6 *h)
446 {
447     bool r;
448 
449     r = hisv6_closefiles(h);
450     if (h->histpath) {
451 	free(h->histpath);
452 	h->histpath = NULL;
453     }
454 
455     free(h);
456     return r;
457 }
458 
459 
460 /*
461 **  return a newly constructed, but empty, history structure
462 */
463 static struct hisv6 *
hisv6_new(const char * path,int flags,struct history * history)464 hisv6_new(const char *path, int flags, struct history *history)
465 {
466     struct hisv6 *h;
467 
468     h = xmalloc(sizeof *h);
469     h->histpath = path ? xstrdup(path) : NULL;
470     h->flags = flags;
471     h->writefp = NULL;
472     h->offset = 0;
473     h->history = history;
474     h->readfd = -1;
475     h->nextcheck = 0;
476     h->statinterval = 0;
477     h->npairs = 0;
478     h->dirty = 0;
479     h->synccount = 0;
480     h->st.st_ino = (ino_t)-1;
481     h->st.st_dev = (dev_t)-1;
482     return h;
483 }
484 
485 
486 /*
487 **  open the history database identified by path in mode flags
488 */
489 void *
hisv6_open(const char * path,int flags,struct history * history)490 hisv6_open(const char *path, int flags, struct history *history)
491 {
492     struct hisv6 *h;
493 
494     his_logger("HISsetup begin", S_HISsetup);
495 
496     h = hisv6_new(path, flags, history);
497     if (path) {
498 	if (!hisv6_reopen(h)) {
499 	    hisv6_dispose(h);
500 	    h = NULL;
501 	}
502     }
503     his_logger("HISsetup end", S_HISsetup);
504     return h;
505 }
506 
507 
508 /*
509 **  close and free a history handle
510 */
511 bool
hisv6_close(void * history)512 hisv6_close(void *history)
513 {
514     struct hisv6 *h = history;
515     bool r;
516 
517     his_logger("HISclose begin", S_HISclose);
518     r = hisv6_dispose(h);
519     his_logger("HISclose end", S_HISclose);
520     return r;
521 }
522 
523 
524 /*
525 **  synchronise any outstanding history changes to disk
526 */
527 bool
hisv6_sync(void * history)528 hisv6_sync(void *history)
529 {
530     struct hisv6 *h = history;
531     bool r = true;
532 
533     if (h->writefp != NULL) {
534 	his_logger("HISsync begin", S_HISsync);
535 	if (fflush(h->writefp) == EOF) {
536 	    hisv6_seterror(h, concat("error on history ",
537 				      h->histpath, " ",
538 				      strerror(errno), NULL));
539 	    r = false;
540 	}
541 	if (h->dirty && h == hisv6_dbzowner) {
542 	    if (!dbzsync()) {
543 		hisv6_seterror(h, concat("can't dbzsync ", h->histpath,
544 					  " ", strerror(errno), NULL));
545 		r = false;
546 	    } else {
547 		h->dirty = 0;
548 	    }
549 	}
550 	his_logger("HISsync end", S_HISsync);
551     }
552     return r;
553 }
554 
555 
556 /*
557 **  fetch the line associated with `hash' in the history database into
558 **  buf; buf must be at least HISV6_MAXLINE+1 bytes. `poff' is filled
559 **  with the offset of the line in the history file.
560 */
561 static bool
hisv6_fetchline(struct hisv6 * h,const HASH * hash,char * buf,off_t * poff)562 hisv6_fetchline(struct hisv6 *h, const HASH *hash, char *buf, off_t *poff)
563 {
564     off_t offset;
565     bool r;
566 
567     if (h != hisv6_dbzowner) {
568 	hisv6_seterror(h, concat("dbz not open for this history file ",
569 				  h->histpath, NULL));
570 	return false;
571     }
572     if ((h->flags & (HIS_RDWR | HIS_INCORE)) == (HIS_RDWR | HIS_INCORE)) {
573 	/* need to fflush as we may be reading uncommitted data
574 	   written via writefp */
575 	if (fflush(h->writefp) == EOF) {
576 	    hisv6_seterror(h, concat("error on history ",
577 				      h->histpath, " ",
578 				      strerror(errno), NULL));
579 	    r = false;
580 	    goto fail;
581 	}
582     }
583 
584     /* Get the seek value into the history file. */
585     errno = 0;
586     r = dbzfetch(*hash, &offset);
587 #ifdef ESTALE
588     /* If your history is on NFS need to deal with stale NFS
589      * handles */
590     if (!r && errno == ESTALE) {
591 	hisv6_closefiles(h);
592 	if (!hisv6_reopen(h)) {
593 	    hisv6_closefiles(h);
594 	    r = false;
595 	    goto fail;
596 	}
597     }
598 #endif
599     if (r) {
600 	ssize_t n;
601 
602 	do {
603 	    n = pread(h->readfd, buf, HISV6_MAXLINE, offset);
604 #ifdef ESTALE
605 	    if (n == -1 && errno == ESTALE) {
606 		hisv6_closefiles(h);
607 		if (!hisv6_reopen(h)) {
608 		    hisv6_closefiles(h);
609 		    r = false;
610 		    goto fail;
611 		}
612 	    }
613 #endif
614 	} while (n == -1 && errno == EINTR);
615 	if (n >= HISV6_MINLINE) {
616 	    char *p;
617 
618 	    buf[n] = '\0';
619 	    p = strchr(buf, '\n');
620 	    if (!p) {
621 		char location[HISV6_MAX_LOCATION];
622 
623 		hisv6_errloc(location, (size_t)-1, offset);
624 		hisv6_seterror(h,
625 				concat("can't locate end of line in history ",
626 				       h->histpath, location,
627 				       NULL));
628 		r = false;
629 	    } else {
630 		*p = '\0';
631 		*poff = offset;
632 		r = true;
633 	    }
634 	} else {
635 	    char location[HISV6_MAX_LOCATION];
636 
637 	    hisv6_errloc(location, (size_t)-1, offset);
638 	    hisv6_seterror(h, concat("line too short in history ",
639 				      h->histpath, location,
640 				      NULL));
641 	    r = false;
642 
643 	}
644     } else {
645 	/* not found */
646 	r = false;
647     }
648  fail:
649     return r;
650 }
651 
652 
653 /*
654 **  lookup up the entry `key' in the history database, returning
655 **  arrived, posted and expires (for those which aren't NULL
656 **  pointers), and any storage token associated with the entry.
657 **
658 **  If any of arrived, posted or expires aren't available, return zero
659 **  for that component.
660 */
661 bool
hisv6_lookup(void * history,const char * key,time_t * arrived,time_t * posted,time_t * expires,TOKEN * token)662 hisv6_lookup(void *history, const char *key, time_t *arrived,
663 	     time_t *posted, time_t *expires, TOKEN *token)
664 {
665     struct hisv6 *h = history;
666     HASH messageid;
667     bool r;
668     off_t offset;
669     char buf[HISV6_MAXLINE + 1];
670 
671     his_logger("HISfilesfor begin", S_HISfilesfor);
672     hisv6_checkfiles(h);
673 
674     messageid = HashMessageID(key);
675     r = hisv6_fetchline(h, &messageid, buf, &offset);
676     if (r == true) {
677 	int status;
678 	const char *error;
679 
680 	status = hisv6_splitline(buf, &error, NULL,
681 				  arrived, posted, expires, token);
682 	if (status < 0) {
683 	    char location[HISV6_MAX_LOCATION];
684 
685 	    hisv6_errloc(location, (size_t)-1, offset);
686 	    hisv6_seterror(h, concat(error, " ",
687 				      h->histpath, location,
688 				      NULL));
689 	    r = false;
690 	} else {
691 	    /* if we have a token then we have the article */
692 	    r = !!(status & HISV6_HAVE_TOKEN);
693 	}
694     }
695     his_logger("HISfilesfor end", S_HISfilesfor);
696     return r;
697 }
698 
699 
700 /*
701 **  check `key' has been seen in this history database
702 */
703 bool
hisv6_check(void * history,const char * key)704 hisv6_check(void *history, const char *key)
705 {
706     struct hisv6 *h = history;
707     bool r;
708     HASH hash;
709 
710     if (h != hisv6_dbzowner) {
711 	hisv6_seterror(h, concat("dbz not open for this history file ",
712 				  h->histpath, NULL));
713 	return false;
714     }
715 
716     his_logger("HIShavearticle begin", S_HIShavearticle);
717     hisv6_checkfiles(h);
718     hash = HashMessageID(key);
719     r = dbzexists(hash);
720     his_logger("HIShavearticle end", S_HIShavearticle);
721     return r;
722 }
723 
724 
725 /*
726 **  Format a history line.  s should hold at least HISV6_MAXLINE + 1
727 **  characters (to allow for the nul).  Returns the length of the data
728 **  written, 0 if there was some error or if the data was too long to write.
729 */
730 static int
hisv6_formatline(char * s,const HASH * hash,time_t arrived,time_t posted,time_t expires,const TOKEN * token)731 hisv6_formatline(char *s, const HASH *hash, time_t arrived,
732 		  time_t posted, time_t expires, const TOKEN *token)
733 {
734     int i;
735     const char *hashtext = HashToText(*hash);
736 
737     if (token == NULL) {
738         /* Only a line to remember an article.  We keep its arrival
739          * and posting time. */
740         if (posted <= 0) {
741             i = snprintf(s, HISV6_MAXLINE, "[%s]%c%lu%c%c\n",
742                          hashtext, HISV6_FIELDSEP,
743                          (unsigned long)arrived, HISV6_SUBFIELDSEP,
744                          HISV6_NOEXP);
745         } else {
746             i = snprintf(s, HISV6_MAXLINE, "[%s]%c%lu%c%c%c%lu\n",
747                          hashtext, HISV6_FIELDSEP,
748                          (unsigned long)arrived, HISV6_SUBFIELDSEP,
749                          HISV6_NOEXP, HISV6_SUBFIELDSEP,
750                          (unsigned long)posted);
751         }
752     } else {
753 	const char *texttok;
754 
755 	texttok = TokenToText(*token);
756 	if (expires <= 0) {
757 	    i = snprintf(s, HISV6_MAXLINE, "[%s]%c%lu%c%c%c%lu%c%s\n",
758 			 hashtext, HISV6_FIELDSEP,
759 			 (unsigned long)arrived, HISV6_SUBFIELDSEP,
760 			 HISV6_NOEXP, HISV6_SUBFIELDSEP,
761 			 (unsigned long)posted, HISV6_FIELDSEP,
762 			 texttok);
763 	} else {
764 	    i = snprintf(s, HISV6_MAXLINE, "[%s]%c%lu%c%lu%c%lu%c%s\n",
765 			 hashtext, HISV6_FIELDSEP,
766 			 (unsigned long)arrived, HISV6_SUBFIELDSEP,
767 			 (unsigned long)expires, HISV6_SUBFIELDSEP,
768 			 (unsigned long)posted, HISV6_FIELDSEP,
769 			 texttok);
770 	}
771     }
772     if (i < 0 || i >= HISV6_MAXLINE)
773 	return 0;
774     return i;
775 }
776 
777 
778 /*
779 **  write the hash and offset to the dbz
780 */
781 static bool
hisv6_writedbz(struct hisv6 * h,const HASH * hash,off_t offset)782 hisv6_writedbz(struct hisv6 *h, const HASH *hash, off_t offset)
783 {
784     bool r;
785     char location[HISV6_MAX_LOCATION];
786     const char *error;
787 
788     /* store the offset in the database */
789     switch (dbzstore(*hash, offset)) {
790     case DBZSTORE_EXISTS:
791 	error = "dbzstore duplicate message-id ";
792 	/* not `false' so that we duplicate the pre-existing
793 	   behaviour */
794 	r = true;
795 	break;
796 
797     case DBZSTORE_ERROR:
798 	error = "dbzstore error ";
799 	r = false;
800 	break;
801 
802     default:
803 	error = NULL;
804 	r = true;
805 	break;
806     }
807     if (error) {
808 	hisv6_errloc(location, (size_t)-1, offset);
809 	hisv6_seterror(h, concat(error, h->histpath,
810 				  ":[", HashToText(*hash), "]",
811 				  location, " ", strerror(errno), NULL));
812     }
813     if (r && h->synccount != 0 && ++h->dirty >= h->synccount)
814 	r = hisv6_sync(h);
815 
816     return r;
817 }
818 
819 
820 /*
821 **  write a history entry, hash, with times arrived, posted and
822 **  expires, and storage token.
823 */
824 static bool
hisv6_writeline(struct hisv6 * h,const HASH * hash,time_t arrived,time_t posted,time_t expires,const TOKEN * token)825 hisv6_writeline(struct hisv6 *h, const HASH *hash, time_t arrived,
826 		time_t posted, time_t expires, const TOKEN *token)
827 {
828     bool r;
829     size_t i, length;
830     char hisline[HISV6_MAXLINE + 1];
831     char location[HISV6_MAX_LOCATION];
832 
833     if (h != hisv6_dbzowner) {
834 	hisv6_seterror(h, concat("dbz not open for this history file ",
835 				  h->histpath, NULL));
836 	return false;
837     }
838 
839     if (!(h->flags & HIS_RDWR)) {
840 	hisv6_seterror(h, concat("history not open for writing ",
841 				  h->histpath, NULL));
842 	return false;
843     }
844 
845     length = hisv6_formatline(hisline, hash, arrived, posted, expires, token);
846     if (length == 0) {
847 	hisv6_seterror(h, concat("error formatting history line ",
848 				  h->histpath, NULL));
849 	return false;
850     }
851 
852     i = fwrite(hisline, 1, length, h->writefp);
853 
854     /* If the write failed, the history line is now an orphan.  Attempt to
855        rewind the write pointer to our offset to avoid leaving behind a
856        partial write and desyncing the offset from our file position. */
857     if (i < length ||
858         (!(h->flags & HIS_INCORE) && fflush(h->writefp) == EOF)) {
859 	hisv6_errloc(location, (size_t)-1, h->offset);
860 	hisv6_seterror(h, concat("can't write history ", h->histpath,
861 				  location, " ", strerror(errno), NULL));
862         if (fseeko(h->writefp, h->offset, SEEK_SET) == -1)
863             h->offset += i;
864 	r = false;
865 	goto fail;
866     }
867 
868     r = hisv6_writedbz(h, hash, h->offset);
869     h->offset += length;     /* increment regardless of error from writedbz */
870  fail:
871     return r;
872 }
873 
874 
875 /*
876 **  write a history entry, key, with times arrived, posted and
877 **  expires, and storage token.
878 */
879 bool
hisv6_write(void * history,const char * key,time_t arrived,time_t posted,time_t expires,const TOKEN * token)880 hisv6_write(void *history, const char *key, time_t arrived,
881 	    time_t posted, time_t expires, const TOKEN *token)
882 {
883     struct hisv6 *h = history;
884     HASH hash;
885     bool r;
886 
887     his_logger("HISwrite begin", S_HISwrite);
888     hash = HashMessageID(key);
889     r = hisv6_writeline(h, &hash, arrived, posted, expires, token);
890     his_logger("HISwrite end", S_HISwrite);
891     return r;
892 }
893 
894 
895 /*
896 **  Remember a history entry, key, with arrival time, and also
897 **  posting time if known.
898 */
899 bool
hisv6_remember(void * history,const char * key,time_t arrived,time_t posted)900 hisv6_remember(void *history, const char *key, time_t arrived, time_t posted)
901 {
902     struct hisv6 *h = history;
903     HASH hash;
904     bool r;
905 
906     his_logger("HISwrite begin", S_HISwrite);
907     hash = HashMessageID(key);
908     r = hisv6_writeline(h, &hash, arrived, posted, 0, NULL);
909     his_logger("HISwrite end", S_HISwrite);
910     return r;
911 }
912 
913 
914 /*
915 **  replace an existing history entry, `key', with times arrived,
916 **  posted and expires, and (optionally) storage token `token'. The
917 **  new history line must fit in the space allocated for the old one -
918 **  if it had previously just been HISremember()ed you'll almost
919 **  certainly lose.
920 */
921 bool
hisv6_replace(void * history,const char * key,time_t arrived,time_t posted,time_t expires,const TOKEN * token)922 hisv6_replace(void *history, const char *key, time_t arrived,
923 	      time_t posted, time_t expires, const TOKEN *token)
924 {
925     struct hisv6 *h = history;
926     HASH hash;
927     bool r;
928     off_t offset;
929     char old[HISV6_MAXLINE + 1];
930 
931     if (!(h->flags & HIS_RDWR)) {
932 	hisv6_seterror(h, concat("history not open for writing ",
933 				  h->histpath, NULL));
934 	return false;
935     }
936 
937     hash = HashMessageID(key);
938     r = hisv6_fetchline(h, &hash, old, &offset);
939     if (r == true) {
940 	char new[HISV6_MAXLINE + 1];
941 
942 	if (hisv6_formatline(new, &hash, arrived, posted, expires,
943                              token) == 0) {
944  	    hisv6_seterror(h, concat("error formatting history line ",
945 				      h->histpath, NULL));
946 	    r = false;
947 	} else {
948 	    size_t oldlen, newlen;
949 
950 	    oldlen = strlen(old);
951 	    newlen = strlen(new);
952 	    if (new[newlen - 1] == '\n')
953                 newlen--;
954 	    if (newlen > oldlen) {
955 		hisv6_seterror(h, concat("new history line too long ",
956 					  h->histpath, NULL));
957 		r = false;
958 	    } else {
959 		ssize_t n;
960 
961 		/* space fill any excess in the tail of new */
962 		memset(new + newlen, ' ', oldlen - newlen);
963 
964 		do {
965 		    n = pwrite(fileno(h->writefp), new, oldlen, offset);
966 		} while (n == -1 && errno == EINTR);
967 		if ((size_t) n != oldlen) {
968 		    char location[HISV6_MAX_LOCATION];
969 
970 		    hisv6_errloc(location, (size_t)-1, offset);
971 		    hisv6_seterror(h, concat("can't write history ",
972 					      h->histpath, location, " ",
973 					      strerror(errno), NULL));
974 		    r = false;
975 		}
976 	    }
977 	}
978     }
979     return r;
980 }
981 
982 
983 /*
984 **  traverse a history database, passing the pieces through a
985 **  callback; note that we have more parameters in the callback than
986 **  the public interface, we add the internal history struct and the
987 **  message hash so we can use those if we need them. If the callback
988 **  returns false we abort the traversal.
989 **/
990 static bool
hisv6_traverse(struct hisv6 * h,struct hisv6_walkstate * cookie,const char * reason,bool (* callback)(struct hisv6 *,void *,const HASH * hash,time_t,time_t,time_t,const TOKEN *))991 hisv6_traverse(struct hisv6 *h, struct hisv6_walkstate *cookie,
992 	       const char *reason,
993 	       bool (*callback)(struct hisv6 *, void *, const HASH *hash,
994 				time_t, time_t, time_t,
995 				const TOKEN *))
996 {
997     bool r = false;
998     QIOSTATE *qp;
999     void *p;
1000     size_t line;
1001     char location[HISV6_MAX_LOCATION];
1002 
1003     if ((qp = QIOopen(h->histpath)) == NULL) {
1004 	hisv6_seterror(h, concat("can't QIOopen history file ",
1005 				  h->histpath, strerror(errno), NULL));
1006 	return false;
1007     }
1008 
1009     line = 1;
1010     /* we come back to again after we hit EOF for the first time, when
1011        we pause the server & clean up any lines which sneak through in
1012        the interim */
1013  again:
1014     while ((p = QIOread(qp)) != NULL) {
1015 	time_t arrived, posted, expires;
1016 	int status;
1017 	TOKEN token;
1018 	HASH hash;
1019 	const char *error;
1020 
1021 	status = hisv6_splitline(p, &error, &hash,
1022 				  &arrived, &posted, &expires, &token);
1023 	if (status > 0) {
1024 	    r = (*callback)(h, cookie, &hash, arrived, posted, expires,
1025 			    (status & HISV6_HAVE_TOKEN) ? &token : NULL);
1026 	    if (r == false)
1027 		hisv6_seterror(h, concat("callback failed ",
1028 					  h->histpath, NULL));
1029 	} else {
1030 	    hisv6_errloc(location, line, (off_t)-1);
1031 	    hisv6_seterror(h, concat(error, " ", h->histpath, location,
1032 				      NULL));
1033 	    /* if we're not ignoring errors set the status */
1034 	    if (!cookie->ignore)
1035 		r = false;
1036 	}
1037 	if (r == false)
1038 	    goto fail;
1039 	++line;
1040     }
1041 
1042     if (p == NULL) {
1043 	/* read or line-format error? */
1044 	if (QIOerror(qp) || QIOtoolong(qp)) {
1045 	    hisv6_errloc(location, line, (off_t)-1);
1046 	    if (QIOtoolong(qp)) {
1047 		hisv6_seterror(h, concat("line too long ",
1048 					 h->histpath, location, NULL));
1049 		/* if we're not ignoring errors set the status */
1050 		if (!cookie->ignore)
1051 		    r = false;
1052 	    } else {
1053 		hisv6_seterror(h, concat("can't read line ",
1054 					 h->histpath, location, " ",
1055 					 strerror(errno), NULL));
1056 		r = false;
1057 	    }
1058 	    if (r == false)
1059 		goto fail;
1060 	}
1061 
1062 	/* must have been EOF, pause the server & clean up any
1063 	 * stragglers */
1064 	if (reason && !cookie->paused) {
1065 	    if (ICCpause(reason) != 0) {
1066 		hisv6_seterror(h, concat("can't pause server ",
1067 					  h->histpath, strerror(errno), NULL));
1068 		r = false;
1069 		goto fail;
1070 	    }
1071 	    cookie->paused = true;
1072 	    goto again;
1073 	}
1074     }
1075  fail:
1076     QIOclose(qp);
1077     return r;
1078 }
1079 
1080 
1081 /*
1082 **  internal callback used during hisv6_traverse; we just pass on the
1083 **  parameters the user callback expects
1084 **/
1085 static bool
hisv6_traversecb(struct hisv6 * h UNUSED,void * cookie,const HASH * hash UNUSED,time_t arrived,time_t posted,time_t expires,const TOKEN * token)1086 hisv6_traversecb(struct hisv6 *h UNUSED, void *cookie, const HASH *hash UNUSED,
1087 		 time_t arrived, time_t posted, time_t expires,
1088 		 const TOKEN *token)
1089 {
1090     struct hisv6_walkstate *hiscookie = cookie;
1091 
1092     return (*hiscookie->cb.walk)(hiscookie->cookie,
1093 				 arrived, posted, expires,
1094 				 token);
1095 }
1096 
1097 
1098 /*
1099 **  history API interface to the database traversal routine
1100 */
1101 bool
hisv6_walk(void * history,const char * reason,void * cookie,bool (* callback)(void *,time_t,time_t,time_t,const TOKEN *))1102 hisv6_walk(void *history, const char *reason, void *cookie,
1103 	   bool (*callback)(void *, time_t, time_t, time_t,
1104 			    const TOKEN *))
1105 {
1106     struct hisv6 *h = history;
1107     struct hisv6_walkstate hiscookie;
1108     bool r;
1109 
1110     /* our internal walk routine passes too many parameters, so add a
1111        wrapper */
1112     hiscookie.cb.walk = callback;
1113     hiscookie.cookie = cookie;
1114     hiscookie.new = NULL;
1115     hiscookie.paused = false;
1116     hiscookie.ignore = false;
1117 
1118     r = hisv6_traverse(h, &hiscookie, reason, hisv6_traversecb);
1119 
1120     return r;
1121 }
1122 
1123 
1124 /*
1125 **  internal callback used during expire
1126 **/
1127 static bool
hisv6_expirecb(struct hisv6 * h,void * cookie,const HASH * hash,time_t arrived,time_t posted,time_t expires,const TOKEN * token)1128 hisv6_expirecb(struct hisv6 *h, void *cookie, const HASH *hash,
1129 		time_t arrived, time_t posted, time_t expires,
1130 		const TOKEN *token)
1131 {
1132     struct hisv6_walkstate *hiscookie = cookie;
1133     bool r = true;
1134 
1135     /* check if we've seen this message id already */
1136     if (hiscookie->new && dbzexists(*hash)) {
1137 	/* continue after duplicates, it's serious, but not fatal */
1138 	hisv6_seterror(h, concat("duplicate message-id [",
1139 				 HashToText(*hash), "] in history ",
1140 				 hiscookie->new->histpath, NULL));
1141     } else {
1142 	struct hisv6_walkstate *hiscookie = cookie;
1143 	TOKEN ltoken, *t;
1144 
1145 	/* if we have a token pass it to the discrimination function */
1146 	if (token) {
1147 	    bool keep;
1148 
1149 	    /* make a local copy of the token so the callback can
1150 	     * modify it */
1151 	    ltoken = *token;
1152 	    t = &ltoken;
1153 	    keep = (*hiscookie->cb.expire)(hiscookie->cookie,
1154 					   arrived, posted, expires,
1155 					   t);
1156 	    /* If the callback returns true, we should keep the
1157 	     * token for the time being, else we just remember
1158 	     * it. */
1159 	    if (keep == false) {
1160 		t = NULL;
1161 		expires = 0;
1162 	    }
1163 	} else {
1164 	    t = NULL;
1165 	}
1166         /* When t is NULL (no token), the message-ID is removed from
1167          * history when the posting time of the article is older than
1168          * threshold, as set by the /remember/ line in expire.ctl.
1169          * We keep the check for the arrival time because some entries
1170          * might not have one. */
1171 	if (hiscookie->new &&
1172 	    (t != NULL || posted >= hiscookie->threshold
1173              || (posted <= 0 && arrived >= hiscookie->threshold))) {
1174 	    r = hisv6_writeline(hiscookie->new, hash,
1175 				 arrived, posted, expires, t);
1176 	}
1177     }
1178     return r;
1179 }
1180 
1181 
1182 /*
1183 **  unlink files associated with the history structure h
1184 */
1185 static bool
hisv6_unlink(struct hisv6 * h)1186 hisv6_unlink(struct hisv6 *h)
1187 {
1188     bool r = true;
1189     char *p;
1190 
1191 #ifdef DO_TAGGED_HASH
1192     p = concat(h->histpath, ".pag", NULL);
1193     r = (unlink(p) == 0) && r;
1194     free(p);
1195 #else
1196     p = concat(h->histpath, ".index", NULL);
1197     r = (unlink(p) == 0) && r;
1198     free(p);
1199 
1200     p = concat(h->histpath, ".hash", NULL);
1201     r = (unlink(p) == 0) && r;
1202     free(p);
1203 #endif
1204 
1205     p = concat(h->histpath, ".dir", NULL);
1206     r = (unlink(p) == 0) && r;
1207     free(p);
1208 
1209     r = (unlink(h->histpath) == 0) && r;
1210     return r;
1211 }
1212 
1213 
1214 /*
1215 **  rename files associated with hold to hnew
1216 */
1217 static bool
hisv6_rename(struct hisv6 * hold,struct hisv6 * hnew)1218 hisv6_rename(struct hisv6 *hold, struct hisv6 *hnew)
1219 {
1220     bool r = true;
1221     char *old, *new;
1222 
1223 #ifdef DO_TAGGED_HASH
1224     old = concat(hold->histpath, ".pag", NULL);
1225     new = concat(hnew->histpath, ".pag", NULL);
1226     r = (rename(old, new) == 0) && r;
1227     free(old);
1228     free(new);
1229 #else
1230     old = concat(hold->histpath, ".index", NULL);
1231     new = concat(hnew->histpath, ".index", NULL);
1232     r = (rename(old, new) == 0) && r;
1233     free(old);
1234     free(new);
1235 
1236     old = concat(hold->histpath, ".hash", NULL);
1237     new = concat(hnew->histpath, ".hash", NULL);
1238     r = (rename(old, new) == 0) && r;
1239     free(old);
1240     free(new);
1241 #endif
1242 
1243     old = concat(hold->histpath, ".dir", NULL);
1244     new = concat(hnew->histpath, ".dir", NULL);
1245     r = (rename(old, new) == 0) && r;
1246     free(old);
1247     free(new);
1248 
1249     r = (rename(hold->histpath, hnew->histpath) == 0) && r;
1250     return r;
1251 }
1252 
1253 
1254 /*
1255 **  expire the history database, history.
1256 */
1257 bool
hisv6_expire(void * history,const char * path,const char * reason,bool writing,void * cookie,time_t threshold,bool (* exists)(void *,time_t,time_t,time_t,TOKEN *))1258 hisv6_expire(void *history, const char *path, const char *reason,
1259 	     bool writing, void *cookie, time_t threshold,
1260 	     bool (*exists)(void *, time_t, time_t, time_t, TOKEN *))
1261 {
1262     struct hisv6 *h = history, *hnew = NULL;
1263     char *nhistory = NULL;
1264     dbzoptions opt;
1265     bool r;
1266     struct hisv6_walkstate hiscookie;
1267 
1268     /* this flag is always tested in the fail clause, so initialise it
1269        now */
1270     hiscookie.paused = false;
1271 
1272     /* during expire we ignore errors whilst reading the history file
1273      * so any errors in it get fixed automagically */
1274     hiscookie.ignore = true;
1275 
1276     if (writing && (h->flags & HIS_RDWR)) {
1277 	hisv6_seterror(h, concat("can't expire from read/write history ",
1278 				 h->histpath, NULL));
1279 	r = false;
1280 	goto fail;
1281     }
1282 
1283     if (writing) {
1284 	/* form base name for new history file */
1285 	if (path != NULL) {
1286 	    nhistory = concat(path, ".n", NULL);
1287 	} else {
1288 	    nhistory = concat(h->histpath, ".n", NULL);
1289 	}
1290 
1291 	hnew = hisv6_new(nhistory, HIS_CREAT | HIS_RDWR | HIS_INCORE,
1292                          h->history);
1293 	if (!hisv6_reopen(hnew)) {
1294 	    hisv6_dispose(hnew);
1295 	    hnew = NULL;
1296 	    r = false;
1297 	    goto fail;
1298 	}
1299 
1300 	/* this is icky... we can only have one dbz open at a time; we
1301 	   really want to make dbz take a state structure. For now we'll
1302 	   just close the existing one and create our new one they way we
1303 	   need it */
1304 	if (!hisv6_dbzclose(h)) {
1305 	    r = false;
1306 	    goto fail;
1307 	}
1308 
1309 	dbzgetoptions(&opt);
1310 	opt.writethrough = false;
1311 	opt.pag_incore = INCORE_MEM;
1312 #ifndef	DO_TAGGED_HASH
1313 	opt.exists_incore = INCORE_MEM;
1314 #endif
1315 	dbzsetoptions(opt);
1316 
1317 	if (h->npairs == 0) {
1318 	    if (!dbzagain(hnew->histpath, h->histpath)) {
1319 		hisv6_seterror(h, concat("can't dbzagain ",
1320 					 hnew->histpath, ":", h->histpath,
1321 					 strerror(errno), NULL));
1322 		r = false;
1323 		goto fail;
1324 	    }
1325 	} else {
1326 	    size_t npairs;
1327 
1328 	    npairs = (h->npairs == -1) ? 0 : h->npairs;
1329 	    if (!dbzfresh(hnew->histpath, dbzsize(npairs))) {
1330 		hisv6_seterror(h, concat("can't dbzfresh ",
1331 					 hnew->histpath, ":", h->histpath,
1332 					 strerror(errno), NULL));
1333 		r = false;
1334 		goto fail;
1335 	    }
1336 	}
1337 	hisv6_dbzowner = hnew;
1338     }
1339 
1340     /* set up the callback handler */
1341     hiscookie.cb.expire = exists;
1342     hiscookie.cookie = cookie;
1343     hiscookie.new = hnew;
1344     hiscookie.threshold = threshold;
1345     r = hisv6_traverse(h, &hiscookie, reason, hisv6_expirecb);
1346 
1347  fail:
1348     if (writing) {
1349 	if (hnew && !hisv6_closefiles(hnew)) {
1350 	    /* error will already have been set */
1351 	    r = false;
1352 	}
1353 
1354 	/* reopen will synchronise the dbz stuff for us */
1355 	if (!hisv6_closefiles(h)) {
1356 	    /* error will already have been set */
1357 	    r = false;
1358 	}
1359 
1360 	if (r) {
1361 	    /* if the new path was explicitly specified don't move the
1362 	       files around, our caller is planning to do it out of
1363 	       band */
1364 	    if (path == NULL) {
1365 		/* unlink the old files */
1366 		r = hisv6_unlink(h);
1367 
1368 		if (r) {
1369 		    r = hisv6_rename(hnew, h);
1370 		}
1371 	    }
1372 	} else if (hnew) {
1373 	    /* something went pear shaped, unlink the new files */
1374 	    hisv6_unlink(hnew);
1375 	}
1376 
1377 	/* re-enable dbz on the old history file */
1378 	if (!hisv6_reopen(h)) {
1379 	    hisv6_closefiles(h);
1380 	}
1381     }
1382 
1383     if (hnew && !hisv6_dispose(hnew))
1384 	r = false;
1385     if (nhistory && nhistory != path)
1386 	free(nhistory);
1387     if (r == false && hiscookie.paused)
1388 	ICCgo(reason);
1389     return r;
1390 }
1391 
1392 
1393 /*
1394 **  control interface
1395 */
1396 bool
hisv6_ctl(void * history,int selector,void * val)1397 hisv6_ctl(void *history, int selector, void *val)
1398 {
1399     struct hisv6 *h = history;
1400     bool r = true;
1401 
1402     switch (selector) {
1403     case HISCTLG_PATH:
1404 	*(char **)val = h->histpath;
1405 	break;
1406 
1407     case HISCTLS_PATH:
1408 	if (h->histpath) {
1409 	    hisv6_seterror(h, concat("path already set in handle", NULL));
1410 	    r = false;
1411 	} else {
1412 	    h->histpath = xstrdup((char *)val);
1413 	    if (!hisv6_reopen(h)) {
1414 		free(h->histpath);
1415 		h->histpath = NULL;
1416 		r = false;
1417 	    }
1418 	}
1419 	break;
1420 
1421     case HISCTLS_STATINTERVAL:
1422 	h->statinterval = *(time_t *)val * 1000;
1423 	break;
1424 
1425     case HISCTLS_SYNCCOUNT:
1426 	h->synccount = *(size_t *)val;
1427 	break;
1428 
1429     case HISCTLS_NPAIRS:
1430 	h->npairs = (ssize_t)*(size_t *)val;
1431 	break;
1432 
1433     case HISCTLS_IGNOREOLD:
1434 	if (h->npairs == 0 && *(bool *)val) {
1435 	    h->npairs = -1;
1436 	} else if (h->npairs == -1 && !*(bool *)val) {
1437 	    h->npairs = 0;
1438 	}
1439 	break;
1440 
1441     default:
1442 	/* deliberately doesn't call hisv6_seterror as we don't want
1443 	 * to spam the error log if someone's passing in stuff which
1444 	 * would be relevant to a different history manager */
1445 	r = false;
1446 	break;
1447     }
1448     return r;
1449 }
1450