1 /* $Id: hisv6.c 7339 2005-06-20 03:16:41Z eagle $
3 ** History v6 implementation against the history API.
5 ** Copyright (c) 2001, Thus plc
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:
11 ** 1. Redistributions of the source code must retain the above
12 ** copyright notice, this list of conditions and the disclaimer
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.
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.
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."
45 #include "hisinterface.h"
47 #include "hisv6-private.h"
49 #include "inn/innconf.h"
50 #include "inn/timer.h"
52 #include "inn/sequence.h"
56 ** because we can only have one open dbz per process, we keep a
57 ** pointer to which of the current history structures owns it
59 static struct hisv6 *hisv6_dbzowner;
63 ** set error status to that indicated by s; doesn't copy the string,
64 ** assumes the caller did that for us
67 hisv6_seterror(struct hisv6 *h, const char *s)
69 his_seterror(h->history, s);
74 ** format line or offset into a string for error reporting
77 hisv6_errloc(char *s, size_t line, off_t offset)
80 /* really we want an autoconf test for %ll/%L/%I64, sigh */
81 sprintf(s, "@%.0f", (double)offset);
83 sprintf(s, ":%lu", (unsigned long)line);
89 ** split a history line into its constituent components; return a
90 ** bitmap indicating which components we're returning are valid (or
91 ** would be valid if a NULL pointer is passed for that component) or
92 ** -1 for error. *error is set to a string which describes the
96 hisv6_splitline(const char *line, const char **error, HASH *hash,
97 time_t *arrived, time_t *posted, time_t *expires,
100 char *p = (char *)line;
104 /* parse the [...] hash field */
106 *error = "`[' missing from history line";
111 *hash = TextToHash(p);
114 *error = "`]' missing from history line";
118 r |= HISV6_HAVE_HASH;
119 if (*p != HISV6_FIELDSEP) {
120 *error = "field separator missing from history line";
124 /* parse the arrived field */
125 l = strtoul(p + 1, &p, 10);
126 if (l == ULONG_MAX) {
127 *error = "arrived timestamp out of range";
130 r |= HISV6_HAVE_ARRIVED;
132 *arrived = (time_t)l;
133 if (*p != HISV6_SUBFIELDSEP) {
134 /* no expires or posted time */
140 /* parse out the expires field */
142 if (*p == HISV6_NOEXP) {
147 l = strtoul(p, &p, 10);
148 if (l == ULONG_MAX) {
149 *error = "expires timestamp out of range";
152 r |= HISV6_HAVE_EXPIRES;
154 *expires = (time_t)l;
156 /* parse out the posted field */
157 if (*p != HISV6_SUBFIELDSEP) {
163 l = strtoul(p, &p, 10);
164 if (l == ULONG_MAX) {
165 *error = "posted timestamp out of range";
168 r |= HISV6_HAVE_POSTED;
174 /* parse the token */
175 if (*p == HISV6_FIELDSEP)
177 else if (*p != '\0') {
178 *error = "field separator missing from history line";
181 /* IsToken false would imply a remembered line, or where someone's
182 * used prunehistory */
184 r |= HISV6_HAVE_TOKEN;
186 *token = TextToToken(p);
193 ** Given the time, now, return the time at which we should next check
197 hisv6_nextcheck(struct hisv6 *h, unsigned long now)
199 return now + h->statinterval;
204 ** close any dbz structures associated with h; we also manage the
205 ** single dbz instance voodoo
208 hisv6_dbzclose(struct hisv6 *h)
212 if (h == hisv6_dbzowner) {
216 hisv6_seterror(h, concat("can't dbzclose ",
218 strerror(errno), NULL));
221 hisv6_dbzowner = NULL;
228 ** close an existing history structure, cleaning it to the point
229 ** where we can reopon without leaking resources
232 hisv6_closefiles(struct hisv6 *h)
236 if (!hisv6_dbzclose(h))
239 if (h->readfd != -1) {
240 if (close(h->readfd) != 0 && errno != EINTR) {
241 hisv6_seterror(h, concat("can't close history ",
243 strerror(errno),NULL));
249 if (h->writefp != NULL) {
250 if (ferror(h->writefp) || fflush(h->writefp) == EOF) {
251 hisv6_seterror(h, concat("error on history ",
253 strerror(errno), NULL));
256 if (Fclose(h->writefp) == EOF) {
257 hisv6_seterror(h, concat("can't fclose history ",
259 strerror(errno), NULL));
267 h->st.st_ino = (ino_t)-1;
268 h->st.st_dev = (dev_t)-1;
274 ** Reopen (or open from fresh) a history structure; assumes the flags
275 ** & path are all set up, ready to roll. If we don't own the dbz, we
276 ** suppress the dbz code; this is needed during expiry (since the dbz
277 ** code doesn't yet understand multiple open contexts... yes its a
281 hisv6_reopen(struct hisv6 *h)
285 if (h->flags & HIS_RDWR) {
288 if (h->flags & HIS_CREAT)
292 if ((h->writefp = Fopen(h->histpath, mode, INND_HISTORY)) == NULL) {
293 hisv6_seterror(h, concat("can't fopen history ",
295 strerror(errno), NULL));
299 if (fseeko(h->writefp, 0, SEEK_END) == -1) {
300 hisv6_seterror(h, concat("can't fseek to end of ",
302 strerror(errno), NULL));
306 h->offset = ftello(h->writefp);
307 if (h->offset == -1) {
308 hisv6_seterror(h, concat("can't ftello ", h->histpath, " ",
309 strerror(errno), NULL));
313 close_on_exec(fileno(h->writefp), true);
316 /* Open the history file for reading. */
317 if ((h->readfd = open(h->histpath, O_RDONLY)) < 0) {
318 hisv6_seterror(h, concat("can't open ", h->histpath, " ",
319 strerror(errno), NULL));
323 close_on_exec(h->readfd, true);
325 /* if there's no current dbz owner, claim it here */
326 if (hisv6_dbzowner == NULL) {
330 /* During expiry we need two history structures in place, so we
331 have to select which one gets the dbz file */
332 if (h == hisv6_dbzowner) {
335 /* Open the DBZ file. */
338 /* HIS_INCORE usually means we're rebuilding from scratch, so
339 keep the whole lot in core until we flush */
340 if (h->flags & HIS_INCORE) {
341 opt.writethrough = false;
342 opt.pag_incore = INCORE_MEM;
343 #ifndef DO_TAGGED_HASH
344 opt.exists_incore = INCORE_MEM;
347 opt.writethrough = true;
348 #ifdef DO_TAGGED_HASH
349 opt.pag_incore = INCORE_MMAP;
351 /*opt.pag_incore = INCORE_NO;*/
352 opt.pag_incore = (h->flags & HIS_MMAP) ? INCORE_MMAP : INCORE_NO;
353 opt.exists_incore = (h->flags & HIS_MMAP) ? INCORE_MMAP : INCORE_NO;
355 # if defined(MMAP_NEEDS_MSYNC) && INND_DBZINCORE == 1
356 /* Systems that have MMAP_NEEDS_MSYNC defined will have their
357 on-disk copies out of sync with the mmap'ed copies most of
358 the time. So if innd is using INCORE_MMAP, then we force
359 everything else to use it, too (unless we're on NFS) */
360 if(!innconf->nfsreader) {
361 opt.pag_incore = INCORE_MMAP;
362 opt.exists_incore = INCORE_MMAP;
368 if (h->flags & HIS_CREAT) {
371 /* must only do this once! */
372 h->flags &= ~HIS_CREAT;
373 npairs = (h->npairs == -1) ? 0 : h->npairs;
374 if (!dbzfresh(h->histpath, dbzsize(npairs))) {
375 hisv6_seterror(h, concat("can't dbzfresh ", h->histpath, " ",
376 strerror(errno), NULL));
380 } else if (!dbzinit(h->histpath)) {
381 hisv6_seterror(h, concat("can't dbzinit ", h->histpath, " ",
382 strerror(errno), NULL));
387 h->nextcheck = hisv6_nextcheck(h, TMRnow());
395 ** check if the history file has changed, if so rotate to the new
396 ** history file. Returns false on failure (which is probably fatal as
397 ** we'll have closed the files)
400 hisv6_checkfiles(struct hisv6 *h)
402 unsigned long t = TMRnow();
404 if (h->statinterval == 0)
407 if (h->readfd == -1) {
408 /* this can happen if a previous checkfiles() has failed to
409 * reopen the handles, but our caller hasn't realised... */
411 if (!hisv6_reopen(h)) {
416 if (seq_lcompare(t, h->nextcheck) == 1) {
419 if (stat(h->histpath, &st) == 0 &&
420 (st.st_ino != h->st.st_ino ||
421 st.st_dev != h->st.st_dev)) {
422 /* there's a possible race on the history file here... */
424 if (!hisv6_reopen(h)) {
430 h->nextcheck = hisv6_nextcheck(h, t);
437 ** dispose (and clean up) an existing history structure
440 hisv6_dispose(struct hisv6 *h)
444 r = hisv6_closefiles(h);
456 ** return a newly constructed, but empty, history structure
458 static struct hisv6 *
459 hisv6_new(const char *path, int flags, struct history *history)
463 h = xmalloc(sizeof *h);
464 h->histpath = path ? xstrdup(path) : NULL;
468 h->history = history;
475 h->st.st_ino = (ino_t)-1;
476 h->st.st_dev = (dev_t)-1;
482 ** open the history database identified by path in mode flags
485 hisv6_open(const char *path, int flags, struct history *history)
489 his_logger("HISsetup begin", S_HISsetup);
491 h = hisv6_new(path, flags, history);
493 if (!hisv6_reopen(h)) {
498 his_logger("HISsetup end", S_HISsetup);
504 ** close and free a history handle
507 hisv6_close(void *history)
509 struct hisv6 *h = history;
512 his_logger("HISclose begin", S_HISclose);
513 r = hisv6_dispose(h);
514 his_logger("HISclose end", S_HISclose);
520 ** synchronise any outstanding history changes to disk
523 hisv6_sync(void *history)
525 struct hisv6 *h = history;
528 if (h->writefp != NULL) {
529 his_logger("HISsync begin", S_HISsync);
530 if (fflush(h->writefp) == EOF) {
531 hisv6_seterror(h, concat("error on history ",
533 strerror(errno), NULL));
536 if (h->dirty && h == hisv6_dbzowner) {
538 hisv6_seterror(h, concat("can't dbzsync ", h->histpath,
539 " ", strerror(errno), NULL));
545 his_logger("HISsync end", S_HISsync);
552 ** fetch the line associated with `hash' in the history database into
553 ** buf; buf must be at least HISV6_MAXLINE+1 bytes. `poff' is filled
554 ** with the offset of the line in the history file.
557 hisv6_fetchline(struct hisv6 *h, const HASH *hash, char *buf, off_t *poff)
562 if (h != hisv6_dbzowner) {
563 hisv6_seterror(h, concat("dbz not open for this history file ",
567 if ((h->flags & (HIS_RDWR | HIS_INCORE)) == (HIS_RDWR | HIS_INCORE)) {
568 /* need to fflush as we may be reading uncommitted data
569 written via writefp */
570 if (fflush(h->writefp) == EOF) {
571 hisv6_seterror(h, concat("error on history ",
573 strerror(errno), NULL));
579 /* Get the seek value into the history file. */
581 r = dbzfetch(*hash, &offset);
583 /* If your history is on NFS need to deal with stale NFS
585 if (!r && errno == ESTALE) {
587 if (!hisv6_reopen(h)) {
598 n = pread(h->readfd, buf, HISV6_MAXLINE, offset);
600 if (n == -1 && errno == ESTALE) {
602 if (!hisv6_reopen(h)) {
609 } while (n == -1 && errno == EINTR);
610 if (n >= HISV6_MINLINE) {
614 p = strchr(buf, '\n');
616 char location[HISV6_MAX_LOCATION];
618 hisv6_errloc(location, (size_t)-1, offset);
620 concat("can't locate end of line in history ",
621 h->histpath, location,
630 char location[HISV6_MAX_LOCATION];
632 hisv6_errloc(location, (size_t)-1, offset);
633 hisv6_seterror(h, concat("line too short in history ",
634 h->histpath, location,
649 ** lookup up the entry `key' in the history database, returning
650 ** arrived, posted and expires (for those which aren't NULL
651 ** pointers), and any storage token associated with the entry.
653 ** If any of arrived, posted or expires aren't available, return zero
654 ** for that component.
657 hisv6_lookup(void *history, const char *key, time_t *arrived,
658 time_t *posted, time_t *expires, TOKEN *token)
660 struct hisv6 *h = history;
664 char buf[HISV6_MAXLINE + 1];
666 his_logger("HISfilesfor begin", S_HISfilesfor);
669 messageid = HashMessageID(key);
670 r = hisv6_fetchline(h, &messageid, buf, &offset);
675 status = hisv6_splitline(buf, &error, NULL,
676 arrived, posted, expires, token);
678 char location[HISV6_MAX_LOCATION];
680 hisv6_errloc(location, (size_t)-1, offset);
681 hisv6_seterror(h, concat(error, " ",
682 h->histpath, location,
686 /* if we have a token then we have the article */
687 r = !!(status & HISV6_HAVE_TOKEN);
690 his_logger("HISfilesfor end", S_HISfilesfor);
696 ** check `key' has been seen in this history database
699 hisv6_check(void *history, const char *key)
701 struct hisv6 *h = history;
705 if (h != hisv6_dbzowner) {
706 hisv6_seterror(h, concat("dbz not open for this history file ",
711 his_logger("HIShavearticle begin", S_HIShavearticle);
713 hash = HashMessageID(key);
715 his_logger("HIShavearticle end", S_HIShavearticle);
721 ** Format a history line. s should hold at least HISV6_MAXLINE + 1
722 ** characters (to allow for the nul). Returns the length of the data
723 ** written, 0 if there was some error or if the data was too long to write.
726 hisv6_formatline(char *s, const HASH *hash, time_t arrived,
727 time_t posted, time_t expires, const TOKEN *token)
730 const char *hashtext = HashToText(*hash);
733 i = snprintf(s, HISV6_MAXLINE, "[%s]%c%lu%c%c\n",
734 hashtext, HISV6_FIELDSEP,
735 (unsigned long)arrived, HISV6_SUBFIELDSEP, HISV6_NOEXP);
739 texttok = TokenToText(*token);
741 i = snprintf(s, HISV6_MAXLINE, "[%s]%c%lu%c%c%c%lu%c%s\n",
742 hashtext, HISV6_FIELDSEP,
743 (unsigned long)arrived, HISV6_SUBFIELDSEP,
744 HISV6_NOEXP, HISV6_SUBFIELDSEP,
745 (unsigned long)posted, HISV6_FIELDSEP,
748 i = snprintf(s, HISV6_MAXLINE, "[%s]%c%lu%c%lu%c%lu%c%s\n",
749 hashtext, HISV6_FIELDSEP,
750 (unsigned long)arrived, HISV6_SUBFIELDSEP,
751 (unsigned long)expires, HISV6_SUBFIELDSEP,
752 (unsigned long)posted, HISV6_FIELDSEP,
756 if (i < 0 || i >= HISV6_MAXLINE)
763 ** write the hash and offset to the dbz
766 hisv6_writedbz(struct hisv6 *h, const HASH *hash, off_t offset)
769 char location[HISV6_MAX_LOCATION];
772 /* store the offset in the database */
773 switch (dbzstore(*hash, offset)) {
774 case DBZSTORE_EXISTS:
775 error = "dbzstore duplicate message-id ";
776 /* not `false' so that we duplicate the pre-existing
782 error = "dbzstore error ";
792 hisv6_errloc(location, (size_t)-1, offset);
793 hisv6_seterror(h, concat(error, h->histpath,
794 ":[", HashToText(*hash), "]",
795 location, " ", strerror(errno), NULL));
797 if (r && h->synccount != 0 && ++h->dirty >= h->synccount)
805 ** write a history entry, hash, with times arrived, posted and
806 ** expires, and storage token.
809 hisv6_writeline(struct hisv6 *h, const HASH *hash, time_t arrived,
810 time_t posted, time_t expires, const TOKEN *token)
814 char hisline[HISV6_MAXLINE + 1];
815 char location[HISV6_MAX_LOCATION];
817 if (h != hisv6_dbzowner) {
818 hisv6_seterror(h, concat("dbz not open for this history file ",
823 if (!(h->flags & HIS_RDWR)) {
824 hisv6_seterror(h, concat("history not open for writing ",
829 length = hisv6_formatline(hisline, hash, arrived, posted, expires, token);
831 hisv6_seterror(h, concat("error formatting history line ",
836 i = fwrite(hisline, 1, length, h->writefp);
838 /* If the write failed, the history line is now an orphan. Attempt to
839 rewind the write pointer to our offset to avoid leaving behind a
840 partial write and desyncing the offset from our file position. */
842 (!(h->flags & HIS_INCORE) && fflush(h->writefp) == EOF)) {
843 hisv6_errloc(location, (size_t)-1, h->offset);
844 hisv6_seterror(h, concat("can't write history ", h->histpath,
845 location, " ", strerror(errno), NULL));
846 if (fseeko(h->writefp, h->offset, SEEK_SET) == -1)
852 r = hisv6_writedbz(h, hash, h->offset);
853 h->offset += length; /* increment regardless of error from writedbz */
860 ** write a history entry, key, with times arrived, posted and
861 ** expires, and storage token.
864 hisv6_write(void *history, const char *key, time_t arrived,
865 time_t posted, time_t expires, const TOKEN *token)
867 struct hisv6 *h = history;
871 his_logger("HISwrite begin", S_HISwrite);
872 hash = HashMessageID(key);
873 r = hisv6_writeline(h, &hash, arrived, posted, expires, token);
874 his_logger("HISwrite end", S_HISwrite);
880 ** remember a history entry, key, with arrival time arrived.
883 hisv6_remember(void *history, const char *key, time_t arrived)
885 struct hisv6 *h = history;
889 his_logger("HISwrite begin", S_HISwrite);
890 hash = HashMessageID(key);
891 r = hisv6_writeline(h, &hash, arrived, 0, 0, NULL);
892 his_logger("HISwrite end", S_HISwrite);
898 ** replace an existing history entry, `key', with times arrived,
899 ** posted and expires, and (optionally) storage token `token'. The
900 ** new history line must fit in the space allocated for the old one -
901 ** if it had previously just been HISremember()ed you'll almost
905 hisv6_replace(void *history, const char *key, time_t arrived,
906 time_t posted, time_t expires, const TOKEN *token)
908 struct hisv6 *h = history;
912 char old[HISV6_MAXLINE + 1];
914 if (!(h->flags & HIS_RDWR)) {
915 hisv6_seterror(h, concat("history not open for writing ",
920 hash = HashMessageID(key);
921 r = hisv6_fetchline(h, &hash, old, &offset);
923 char new[HISV6_MAXLINE + 1];
925 if (hisv6_formatline(new, &hash, arrived, posted, expires,
927 hisv6_seterror(h, concat("error formatting history line ",
931 size_t oldlen, newlen;
933 oldlen = strlen(old);
934 newlen = strlen(new);
935 if (new[newlen - 1] == '\n')
937 if (newlen > oldlen) {
938 hisv6_seterror(h, concat("new history line too long ",
944 /* space fill any excess in the tail of new */
945 memset(new + newlen, ' ', oldlen - newlen);
948 n = pwrite(fileno(h->writefp), new, oldlen, offset);
949 } while (n == -1 && errno == EINTR);
951 char location[HISV6_MAX_LOCATION];
953 hisv6_errloc(location, (size_t)-1, offset);
954 hisv6_seterror(h, concat("can't write history ",
955 h->histpath, location, " ",
956 strerror(errno), NULL));
967 ** traverse a history database, passing the pieces through a
968 ** callback; note that we have more parameters in the callback than
969 ** the public interface, we add the internal history struct and the
970 ** message hash so we can use those if we need them. If the callback
971 ** returns false we abort the traversal.
974 hisv6_traverse(struct hisv6 *h, struct hisv6_walkstate *cookie,
976 bool (*callback)(struct hisv6 *, void *, const HASH *hash,
977 time_t, time_t, time_t,
984 char location[HISV6_MAX_LOCATION];
986 if ((qp = QIOopen(h->histpath)) == NULL) {
987 hisv6_seterror(h, concat("can't QIOopen history file ",
988 h->histpath, strerror(errno), NULL));
993 /* we come back to again after we hit EOF for the first time, when
994 we pause the server & clean up any lines which sneak through in
997 while ((p = QIOread(qp)) != NULL) {
998 time_t arrived, posted, expires;
1004 status = hisv6_splitline(p, &error, &hash,
1005 &arrived, &posted, &expires, &token);
1007 r = (*callback)(h, cookie, &hash, arrived, posted, expires,
1008 (status & HISV6_HAVE_TOKEN) ? &token : NULL);
1010 hisv6_seterror(h, concat("callback failed ",
1011 h->histpath, NULL));
1013 hisv6_errloc(location, line, (off_t)-1);
1014 hisv6_seterror(h, concat(error, " ", h->histpath, location,
1016 /* if we're not ignoring errors set the status */
1017 if (!cookie->ignore)
1026 /* read or line-format error? */
1027 if (QIOerror(qp) || QIOtoolong(qp)) {
1028 hisv6_errloc(location, line, (off_t)-1);
1029 if (QIOtoolong(qp)) {
1030 hisv6_seterror(h, concat("line too long ",
1031 h->histpath, location, NULL));
1032 /* if we're not ignoring errors set the status */
1033 if (!cookie->ignore)
1036 hisv6_seterror(h, concat("can't read line ",
1037 h->histpath, location, " ",
1038 strerror(errno), NULL));
1045 /* must have been EOF, pause the server & clean up any
1047 if (reason && !cookie->paused) {
1048 if (ICCpause(reason) != 0) {
1049 hisv6_seterror(h, concat("can't pause server ",
1050 h->histpath, strerror(errno), NULL));
1054 cookie->paused = true;
1065 ** internal callback used during hisv6_traverse; we just pass on the
1066 ** parameters the user callback expects
1069 hisv6_traversecb(struct hisv6 *h UNUSED, void *cookie, const HASH *hash UNUSED,
1070 time_t arrived, time_t posted, time_t expires,
1073 struct hisv6_walkstate *hiscookie = cookie;
1075 return (*hiscookie->cb.walk)(hiscookie->cookie,
1076 arrived, posted, expires,
1082 ** history API interface to the database traversal routine
1085 hisv6_walk(void *history, const char *reason, void *cookie,
1086 bool (*callback)(void *, time_t, time_t, time_t,
1089 struct hisv6 *h = history;
1090 struct hisv6_walkstate hiscookie;
1093 /* our internal walk routine passes too many parameters, so add a
1095 hiscookie.cb.walk = callback;
1096 hiscookie.cookie = cookie;
1097 hiscookie.new = NULL;
1098 hiscookie.paused = false;
1099 hiscookie.ignore = false;
1101 r = hisv6_traverse(h, &hiscookie, reason, hisv6_traversecb);
1108 ** internal callback used during expire
1111 hisv6_expirecb(struct hisv6 *h, void *cookie, const HASH *hash,
1112 time_t arrived, time_t posted, time_t expires,
1115 struct hisv6_walkstate *hiscookie = cookie;
1118 /* check if we've seen this message id already */
1119 if (hiscookie->new && dbzexists(*hash)) {
1120 /* continue after duplicates, it serious, but not fatal */
1121 hisv6_seterror(h, concat("duplicate message-id [",
1122 HashToText(*hash), "] in history ",
1123 hiscookie->new->histpath, NULL));
1125 struct hisv6_walkstate *hiscookie = cookie;
1128 /* if we have a token pass it to the discrimination function */
1132 /* make a local copy of the token so the callback can
1136 keep = (*hiscookie->cb.expire)(hiscookie->cookie,
1137 arrived, posted, expires,
1139 /* if the callback returns true, we should keep the
1140 token for the time being, else we just remember
1142 if (keep == false) {
1144 posted = expires = 0;
1149 if (hiscookie->new &&
1150 (t != NULL || arrived >= hiscookie->threshold)) {
1151 r = hisv6_writeline(hiscookie->new, hash,
1152 arrived, posted, expires, t);
1160 ** unlink files associated with the history structure h
1163 hisv6_unlink(struct hisv6 *h)
1168 #ifdef DO_TAGGED_HASH
1169 p = concat(h->histpath, ".pag", NULL);
1170 r = (unlink(p) == 0) && r;
1173 p = concat(h->histpath, ".index", NULL);
1174 r = (unlink(p) == 0) && r;
1177 p = concat(h->histpath, ".hash", NULL);
1178 r = (unlink(p) == 0) && r;
1182 p = concat(h->histpath, ".dir", NULL);
1183 r = (unlink(p) == 0) && r;
1186 r = (unlink(h->histpath) == 0) && r;
1192 ** rename files associated with hold to hnew
1195 hisv6_rename(struct hisv6 *hold, struct hisv6 *hnew)
1200 #ifdef DO_TAGGED_HASH
1201 old = concat(hold->histpath, ".pag", NULL);
1202 new = concat(hnew->histpath, ".pag", NULL);
1203 r = (rename(old, new) == 0) && r;
1207 old = concat(hold->histpath, ".index", NULL);
1208 new = concat(hnew->histpath, ".index", NULL);
1209 r = (rename(old, new) == 0) && r;
1213 old = concat(hold->histpath, ".hash", NULL);
1214 new = concat(hnew->histpath, ".hash", NULL);
1215 r = (rename(old, new) == 0) && r;
1220 old = concat(hold->histpath, ".dir", NULL);
1221 new = concat(hnew->histpath, ".dir", NULL);
1222 r = (rename(old, new) == 0) && r;
1226 r = (rename(hold->histpath, hnew->histpath) == 0) && r;
1232 ** expire the history database, history.
1235 hisv6_expire(void *history, const char *path, const char *reason,
1236 bool writing, void *cookie, time_t threshold,
1237 bool (*exists)(void *, time_t, time_t, time_t, TOKEN *))
1239 struct hisv6 *h = history, *hnew = NULL;
1240 const char *nhistory = NULL;
1243 struct hisv6_walkstate hiscookie;
1245 /* this flag is always tested in the fail clause, so initialise it
1247 hiscookie.paused = false;
1249 /* during expire we ignore errors whilst reading the history file
1250 * so any errors in it get fixed automagically */
1251 hiscookie.ignore = true;
1253 if (writing && (h->flags & HIS_RDWR)) {
1254 hisv6_seterror(h, concat("can't expire from read/write history ",
1255 h->histpath, NULL));
1261 /* form base name for new history file */
1263 nhistory = concat(path, ".n", NULL);
1265 nhistory = concat(h->histpath, ".n", NULL);
1268 hnew = hisv6_new(nhistory, HIS_CREAT | HIS_RDWR | HIS_INCORE,
1270 if (!hisv6_reopen(hnew)) {
1271 hisv6_dispose(hnew);
1277 /* this is icky... we can only have one dbz open at a time; we
1278 really want to make dbz take a state structure. For now we'll
1279 just close the existing one and create our new one they way we
1281 if (!hisv6_dbzclose(h)) {
1286 dbzgetoptions(&opt);
1287 opt.writethrough = false;
1288 opt.pag_incore = INCORE_MEM;
1289 #ifndef DO_TAGGED_HASH
1290 opt.exists_incore = INCORE_MEM;
1294 if (h->npairs == 0) {
1295 if (!dbzagain(hnew->histpath, h->histpath)) {
1296 hisv6_seterror(h, concat("can't dbzagain ",
1297 hnew->histpath, ":", h->histpath,
1298 strerror(errno), NULL));
1305 npairs = (h->npairs == -1) ? 0 : h->npairs;
1306 if (!dbzfresh(hnew->histpath, dbzsize(npairs))) {
1307 hisv6_seterror(h, concat("can't dbzfresh ",
1308 hnew->histpath, ":", h->histpath,
1309 strerror(errno), NULL));
1314 hisv6_dbzowner = hnew;
1317 /* set up the callback handler */
1318 hiscookie.cb.expire = exists;
1319 hiscookie.cookie = cookie;
1320 hiscookie.new = hnew;
1321 hiscookie.threshold = threshold;
1322 r = hisv6_traverse(h, &hiscookie, reason, hisv6_expirecb);
1326 if (hnew && !hisv6_closefiles(hnew)) {
1327 /* error will already have been set */
1331 /* reopen will synchronise the dbz stuff for us */
1332 if (!hisv6_closefiles(h)) {
1333 /* error will already have been set */
1338 /* if the new path was explicitly specified don't move the
1339 files around, our caller is planning to do it out of
1342 /* unlink the old files */
1343 r = hisv6_unlink(h);
1346 r = hisv6_rename(hnew, h);
1350 /* something went pear shaped, unlink the new files */
1354 /* re-enable dbz on the old history file */
1355 if (!hisv6_reopen(h)) {
1356 hisv6_closefiles(h);
1360 if (hnew && !hisv6_dispose(hnew))
1362 if (nhistory && nhistory != path)
1363 free((char *)nhistory);
1364 if (r == false && hiscookie.paused)
1371 ** control interface
1374 hisv6_ctl(void *history, int selector, void *val)
1376 struct hisv6 *h = history;
1381 *(char **)val = h->histpath;
1386 hisv6_seterror(h, concat("path already set in handle", NULL));
1389 h->histpath = xstrdup((char *)val);
1390 if (!hisv6_reopen(h)) {
1398 case HISCTLS_STATINTERVAL:
1399 h->statinterval = *(time_t *)val * 1000;
1402 case HISCTLS_SYNCCOUNT:
1403 h->synccount = *(size_t *)val;
1406 case HISCTLS_NPAIRS:
1407 h->npairs = (ssize_t)*(size_t *)val;
1410 case HISCTLS_IGNOREOLD:
1411 if (h->npairs == 0 && *(bool *)val) {
1413 } else if (h->npairs == -1 && !*(bool *)val) {
1419 /* deliberately doesn't call hisv6_seterror as we don't want
1420 * to spam the error log if someone's passing in stuff which
1421 * would be relevant to a different history manager */