chiark / gitweb /
server subprocesses should log to the same place the server does
[disorder] / server / trackdb.c
1 /*
2  * This file is part of DisOrder
3  * Copyright (C) 2005, 2006, 2007 Richard Kettlewell
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18  * USA
19  */
20
21 #include <config.h>
22 #include "types.h"
23
24 #include <string.h>
25 #include <stdio.h>
26 #include <db.h>
27 #include <sys/socket.h>
28 #include <pcre.h>
29 #include <assert.h>
30 #include <unistd.h>
31 #include <errno.h>
32 #include <stddef.h>
33 #include <sys/time.h>
34 #include <sys/resource.h>
35 #include <time.h>
36 #include <arpa/inet.h>
37
38 #include "event.h"
39 #include "mem.h"
40 #include "kvp.h"
41 #include "log.h"
42 #include "vector.h"
43 #include "trackdb.h"
44 #include "configuration.h"
45 #include "syscalls.h"
46 #include "wstat.h"
47 #include "words.h"
48 #include "printf.h"
49 #include "filepart.h"
50 #include "trackname.h"
51 #include "trackdb-int.h"
52 #include "logfd.h"
53 #include "cache.h"
54 #include "eventlog.h"
55 #include "hash.h"
56
57 #define RESCAN "disorder-rescan"
58 #define DEADLOCK "disorder-deadlock"
59
60 static const char *getpart(const char *track,
61                            const char *context,
62                            const char *part,
63                            const struct kvp *p,
64                            int *used_db);
65 static int trackdb_alltags_tid(DB_TXN *tid, char ***taglistp);
66 static int trackdb_get_global_tid(const char *name,
67                                   DB_TXN *tid,
68                                   const char **rp);
69 static char **trackdb_new_tid(int *ntracksp,
70                               int maxtracks,
71                               DB_TXN *tid);
72 static int trackdb_expire_noticed_tid(time_t earliest, DB_TXN *tid);
73
74 const struct cache_type cache_files_type = { 86400 };
75 unsigned long cache_files_hits, cache_files_misses;
76
77 /* setup and teardown ********************************************************/
78
79 static const char *home;                /* home had better not change */
80 DB_ENV *trackdb_env;                    /* db environment */
81 DB *trackdb_tracksdb;                   /* the db itself */
82 DB *trackdb_prefsdb;                    /* preferences */
83 DB *trackdb_searchdb;                   /* the search database */
84 DB *trackdb_tagsdb;                     /* the tags database */
85 DB *trackdb_globaldb;                   /* global preferences */
86 DB *trackdb_noticeddb;                   /* when track noticed */
87 static pid_t db_deadlock_pid = -1;      /* deadlock manager PID */
88 static pid_t rescan_pid = -1;           /* rescanner PID */
89 static int initialized, opened;         /* state */
90
91 /* tracks matched by required_tags */
92 static char **reqtracks;
93 static size_t nreqtracks;
94
95 /* comparison function for keys */
96 static int compare(DB attribute((unused)) *db_,
97                    const DBT *a, const DBT *b) {
98   return compare_path_raw(a->data, a->size, b->data, b->size);
99 }
100
101 /* open environment */
102 void trackdb_init(int recover) {
103   int err;
104   static int recover_type[] = { 0, DB_RECOVER, DB_RECOVER_FATAL };
105
106   /* sanity checks */
107   assert(initialized == 0);
108   ++initialized;
109   if(home) {
110     if(strcmp(home, config->home))
111       fatal(0, "cannot change db home without server restart");
112     home = config->home;
113   }
114
115   /* create environment */
116   if((err = db_env_create(&trackdb_env, 0))) fatal(0, "db_env_create: %s",
117                                                    db_strerror(err));
118   if((err = trackdb_env->set_alloc(trackdb_env,
119                                    xmalloc_noptr, xrealloc_noptr, xfree)))
120     fatal(0, "trackdb_env->set_alloc: %s", db_strerror(err));
121   if((err = trackdb_env->set_lk_max_locks(trackdb_env, 10000)))
122     fatal(0, "trackdb_env->set_lk_max_locks: %s", db_strerror(err));
123   if((err = trackdb_env->set_lk_max_objects(trackdb_env, 10000)))
124     fatal(0, "trackdb_env->set_lk_max_objects: %s", db_strerror(err));
125   if((err = trackdb_env->open(trackdb_env, config->home,
126                               DB_INIT_LOG
127                               |DB_INIT_LOCK
128                               |DB_INIT_MPOOL
129                               |DB_INIT_TXN
130                               |DB_CREATE
131                               |recover_type[recover],
132                               0666)))
133     fatal(0, "trackdb_env->open %s: %s", config->home, db_strerror(err));
134   trackdb_env->set_errpfx(trackdb_env, "DB");
135   trackdb_env->set_errfile(trackdb_env, stderr);
136   trackdb_env->set_verbose(trackdb_env, DB_VERB_DEADLOCK, 1);
137   trackdb_env->set_verbose(trackdb_env, DB_VERB_RECOVERY, 1);
138   trackdb_env->set_verbose(trackdb_env, DB_VERB_REPLICATION, 1);
139   D(("initialized database environment"));
140 }
141
142 /* called when deadlock manager terminates */
143 static int reap_db_deadlock(ev_source attribute((unused)) *ev,
144                             pid_t attribute((unused)) pid,
145                             int status,
146                             const struct rusage attribute((unused)) *rusage,
147                             void attribute((unused)) *u) {
148   db_deadlock_pid = -1;
149   if(initialized)
150     fatal(0, "deadlock manager unexpectedly terminated: %s",
151           wstat(status));
152   else
153     D(("deadlock manager terminated: %s", wstat(status)));
154   return 0;
155 }
156
157 static pid_t subprogram(ev_source *ev, const char *prog,
158                         int outputfd) {
159   pid_t pid;
160   int lfd;
161
162   /* If we're in the background then trap subprocess stdout/stderr */
163   if(!isatty(2))
164     lfd = logfd(ev, prog);
165   else
166     lfd = -1;
167   if(!(pid = xfork())) {
168     exitfn = _exit;
169     ev_signal_atfork(ev);
170     signal(SIGPIPE, SIG_DFL);
171     if(lfd != -1) {
172       xdup2(lfd, 1);
173       xdup2(lfd, 2);
174       xclose(lfd);
175     }
176     if(outputfd != -1) {
177       xdup2(outputfd, 1);
178       xclose(outputfd);
179     }
180     /* If we were negatively niced, undo it.  We don't bother checking for
181      * error, it's not that important. */
182     setpriority(PRIO_PROCESS, 0, 0);
183     execlp(prog, prog, "--config", configfile,
184            debugging ? "--debug" : "--no-debug",
185            log_default == &log_syslog ? "--syslog" : "--no-syslog",
186            (char *)0);
187     fatal(errno, "error invoking %s", prog);
188   }
189   if(lfd != -1) xclose(lfd);
190   return pid;
191 }
192
193 /* start deadlock manager */
194 void trackdb_master(ev_source *ev) {
195   assert(db_deadlock_pid == -1);
196   db_deadlock_pid = subprogram(ev, DEADLOCK, -1);
197   ev_child(ev, db_deadlock_pid, 0, reap_db_deadlock, 0);
198   D(("started deadlock manager"));
199 }
200
201 /* close environment */
202 void trackdb_deinit(void) {
203   int err;
204
205   /* sanity checks */
206   assert(initialized == 1);
207   --initialized;
208
209   /* close the environment */
210   if((err = trackdb_env->close(trackdb_env, 0)))
211     fatal(0, "trackdb_env->close: %s", db_strerror(err));
212
213   if(rescan_pid != -1 && kill(rescan_pid, SIGTERM) < 0)
214     fatal(errno, "error killing rescanner");
215
216   /* terminate the deadlock manager */
217   if(db_deadlock_pid != -1 && kill(db_deadlock_pid, SIGTERM) < 0)
218     fatal(errno, "error killing deadlock manager");
219   db_deadlock_pid = -1;
220
221   D(("deinitialized database environment"));
222 }
223
224 /* open a specific database */
225 static DB *open_db(const char *path,
226                    u_int32_t dbflags,
227                    DBTYPE dbtype,
228                    u_int32_t openflags,
229                    int mode) {
230   int err;
231   DB *db;
232
233   D(("open %s", path));
234   path = config_get_file(path);
235   if((err = db_create(&db, trackdb_env, 0)))
236     fatal(0, "db_create %s: %s", path, db_strerror(err));
237   if(dbflags)
238     if((err = db->set_flags(db, dbflags)))
239       fatal(0, "db->set_flags %s: %s", path, db_strerror(err));
240   if(dbtype == DB_BTREE)
241     if((err = db->set_bt_compare(db, compare)))
242       fatal(0, "db->set_bt_compare %s: %s", path, db_strerror(err));
243   if((err = db->open(db, 0, path, 0, dbtype,
244                      openflags | DB_AUTO_COMMIT, mode)))
245     fatal(0, "db->open %s: %s", path, db_strerror(err));
246   return db;
247 }
248
249 /* open track databases */
250 void trackdb_open(void) {
251   /* sanity checks */
252   assert(opened == 0);
253   ++opened;
254   /* open the databases */
255   trackdb_tracksdb = open_db("tracks.db",
256                              DB_RECNUM, DB_BTREE, DB_CREATE, 0666);
257   trackdb_searchdb = open_db("search.db",
258                              DB_DUP|DB_DUPSORT, DB_HASH, DB_CREATE, 0666);
259   trackdb_tagsdb = open_db("tags.db",
260                            DB_DUP|DB_DUPSORT, DB_HASH, DB_CREATE, 0666);
261   trackdb_prefsdb = open_db("prefs.db", 0, DB_HASH, DB_CREATE, 0666);
262   trackdb_globaldb = open_db("global.db", 0, DB_HASH, DB_CREATE, 0666);
263   trackdb_noticeddb = open_db("noticed.db",
264                              DB_DUPSORT, DB_BTREE, DB_CREATE, 0666);
265   D(("opened databases"));
266 }
267
268 /* close track databases */
269 void trackdb_close(void) {
270   int err;
271   
272   /* sanity checks */
273   assert(opened == 1);
274   --opened;
275   if((err = trackdb_tracksdb->close(trackdb_tracksdb, 0)))
276     fatal(0, "error closing tracks.db: %s", db_strerror(err));
277   if((err = trackdb_searchdb->close(trackdb_searchdb, 0)))
278     fatal(0, "error closing search.db: %s", db_strerror(err));
279   if((err = trackdb_tagsdb->close(trackdb_tagsdb, 0)))
280     fatal(0, "error closing tags.db: %s", db_strerror(err));
281   if((err = trackdb_prefsdb->close(trackdb_prefsdb, 0)))
282     fatal(0, "error closing prefs.db: %s", db_strerror(err));
283   if((err = trackdb_globaldb->close(trackdb_globaldb, 0)))
284     fatal(0, "error closing global.db: %s", db_strerror(err));
285   if((err = trackdb_noticeddb->close(trackdb_noticeddb, 0)))
286     fatal(0, "error closing noticed.db: %s", db_strerror(err));
287   trackdb_tracksdb = trackdb_searchdb = trackdb_prefsdb = 0;
288   trackdb_tagsdb = trackdb_globaldb = 0;
289   D(("closed databases"));
290 }
291
292 /* generic db routines *******************************************************/
293
294 /* fetch and decode a database entry.  Returns 0, DB_NOTFOUND or
295  * DB_LOCK_DEADLOCK. */
296 int trackdb_getdata(DB *db,
297                     const char *track,
298                     struct kvp **kp,
299                     DB_TXN *tid) {
300   int err;
301   DBT key, data;
302
303   switch(err = db->get(db, tid, make_key(&key, track),
304                        prepare_data(&data), 0)) {
305   case 0:
306     *kp = kvp_urldecode(data.data, data.size);
307     return 0;
308   case DB_NOTFOUND:
309     *kp = 0;
310     return err;
311   case DB_LOCK_DEADLOCK:
312     error(0, "error querying database: %s", db_strerror(err));
313     return err;
314   default:
315     fatal(0, "error querying database: %s", db_strerror(err));
316   }
317 }
318
319 /* encode and store a database entry.  Returns 0, DB_KEYEXIST or
320  * DB_LOCK_DEADLOCK. */
321 int trackdb_putdata(DB *db,
322                     const char *track,
323                     const struct kvp *k,
324                     DB_TXN *tid,
325                     u_int32_t flags) {
326   int err;
327   DBT key, data;
328
329   switch(err = db->put(db, tid, make_key(&key, track),
330                        encode_data(&data, k), flags)) {
331   case 0:
332   case DB_KEYEXIST:
333     return err;
334   case DB_LOCK_DEADLOCK:
335     error(0, "error updating database: %s", db_strerror(err));
336     return err;
337   default:
338     fatal(0, "error updating database: %s", db_strerror(err));
339   }
340 }
341
342 /* delete a database entry */
343 int trackdb_delkey(DB *db,
344                    const char *track,
345                    DB_TXN *tid) {
346   int err;
347
348   DBT key;
349   switch(err = db->del(db, tid, make_key(&key, track), 0)) {
350   case 0:
351   case DB_NOTFOUND:
352     return 0;
353   case DB_LOCK_DEADLOCK:
354     error(0, "error updating database: %s", db_strerror(err));
355     return err;
356   default:
357     fatal(0, "error updating database: %s", db_strerror(err));
358   }
359 }
360
361 /* open a database cursor */
362 DBC *trackdb_opencursor(DB *db, DB_TXN *tid) {
363   int err;
364   DBC *c;
365
366   switch(err = db->cursor(db, tid, &c, 0)) {
367   case 0: break;
368   default: fatal(0, "error creating cursor: %s", db_strerror(err));
369   }
370   return c;
371 }
372
373 /* close a database cursor; returns 0 or DB_LOCK_DEADLOCK */
374 int trackdb_closecursor(DBC *c) {
375   int err;
376
377   if(!c) return 0;
378   switch(err = c->c_close(c)) {
379   case 0:
380     return err;
381   case DB_LOCK_DEADLOCK:
382     error(0, "error closing cursor: %s", db_strerror(err));
383     return err;
384   default:
385     fatal(0, "error closing cursor: %s", db_strerror(err));
386   }
387 }
388
389 /* delete a (key,data) pair.  Returns 0, DB_NOTFOUND or DB_LOCK_DEADLOCK. */
390 int trackdb_delkeydata(DB *db,
391                        const char *word,
392                        const char *track,
393                        DB_TXN *tid) {
394   int err;
395   DBC *c;
396   DBT key, data;
397
398   c = trackdb_opencursor(db, tid);
399   switch(err = c->c_get(c, make_key(&key, word),
400                         make_key(&data, track), DB_GET_BOTH)) {
401   case 0:
402     switch(err = c->c_del(c, 0)) {
403     case 0:
404       break;
405     case DB_KEYEMPTY:
406       err = 0;
407       break;
408     case DB_LOCK_DEADLOCK:
409       error(0, "error updating database: %s", db_strerror(err));
410       break;
411     default:
412       fatal(0, "c->c_del: %s", db_strerror(err));
413     }
414     break;
415   case DB_NOTFOUND:
416     break;
417   case DB_LOCK_DEADLOCK:
418     error(0, "error updating database: %s", db_strerror(err));
419     break;
420   default:
421     fatal(0, "c->c_get: %s", db_strerror(err));
422   }
423   if(trackdb_closecursor(c)) err = DB_LOCK_DEADLOCK;
424   return err;
425 }
426
427 /* start a transaction */
428 DB_TXN *trackdb_begin_transaction(void) {
429   DB_TXN *tid;
430   int err;
431
432   if((err = trackdb_env->txn_begin(trackdb_env, 0, &tid, 0)))
433     fatal(0, "trackdb_env->txn_begin: %s", db_strerror(err));
434   return tid;
435 }
436
437 /* abort transaction */
438 void trackdb_abort_transaction(DB_TXN *tid) {
439   int err;
440
441   if(tid)
442     if((err = tid->abort(tid)))
443       fatal(0, "tid->abort: %s", db_strerror(err));
444 }
445
446 /* commit transaction */
447 void trackdb_commit_transaction(DB_TXN *tid) {
448   int err;
449
450   if((err = tid->commit(tid, 0)))
451     fatal(0, "tid->commit: %s", db_strerror(err));
452 }
453
454 /* search/tags shared code ***************************************************/
455
456 /* comparison function used by dedupe() */
457 static int wordcmp(const void *a, const void *b) {
458   return strcmp(*(const char **)a, *(const char **)b);
459 }
460
461 /* sort and de-dupe VEC */
462 static char **dedupe(char **vec, int nvec) {
463   int m, n;
464
465   qsort(vec, nvec, sizeof (char *), wordcmp);
466   m = n = 0;
467   if(nvec) {
468     vec[m++] = vec[0];
469     for(n = 1; n < nvec; ++n)
470       if(strcmp(vec[n], vec[m - 1]))
471         vec[m++] = vec[n];
472   }
473   vec[m] = 0;
474   return vec;
475 }
476
477 /* update a key/track database.  Returns 0 or DB_DEADLOCK. */
478 static int register_word(DB *db, const char *what,
479                          const char *track, const char *word,
480                          DB_TXN *tid) {
481   int err;
482   DBT key, data;
483
484   switch(err = db->put(db, tid, make_key(&key, word),
485                        make_key(&data, track), DB_NODUPDATA)) {
486   case 0:
487   case DB_KEYEXIST:
488     return 0;
489   case DB_LOCK_DEADLOCK:
490     error(0, "error updating %s.db: %s", what, db_strerror(err));
491     return err;
492   default:
493     fatal(0, "error updating %s.db: %s", what,  db_strerror(err));
494   }
495 }
496
497 /* search primitives *********************************************************/
498
499 /* return true iff NAME is a trackname_display_ pref */
500 static int is_display_pref(const char *name) {
501   static const char prefix[] = "trackname_display_";
502   return !strncmp(name, prefix, (sizeof prefix) - 1);
503 }
504
505 /* compute the words of a track name */
506 static char **track_to_words(const char *track,
507                              const struct kvp *p) {
508   struct vector v;
509   char **w;
510   int nw;
511   const char *rootless = track_rootless(track);
512
513   if(!rootless)
514     rootless = track;                   /* bodge */
515   vector_init(&v);
516   if((w = words(casefold(strip_extension(rootless)), &nw)))
517     vector_append_many(&v, w, nw);
518
519   for(; p; p = p->next)
520     if(is_display_pref(p->name))
521       if((w = words(casefold(p->value), &nw)))
522         vector_append_many(&v, w, nw);
523   vector_terminate(&v);
524   return dedupe(v.vec, v.nvec);
525 }
526
527 /* return nonzero iff WORD is a stopword */
528 static int stopword(const char *word) {
529   int n;
530
531   for(n = 0; n < config->stopword.n
532         && strcmp(word, config->stopword.s[n]); ++n)
533     ;
534   return n < config->stopword.n;
535 }
536
537 /* record that WORD appears in TRACK.  Returns 0 or DB_LOCK_DEADLOCK. */
538 static int register_search_word(const char *track, const char *word,
539                                 DB_TXN *tid) {
540   if(stopword(word)) return 0;
541   return register_word(trackdb_searchdb, "search", track, word, tid);
542 }
543
544 /* Tags **********************************************************************/
545
546 /* Return nonzero if C is a valid tag character */
547 static int tagchar(int c) {
548   switch(c) {
549   case ',':
550     return 0;
551   default:
552     return c >= ' ';
553   }
554 }
555
556 /* Parse and de-dupe a tag list.  If S=0 then assumes "". */
557 static char **parsetags(const char *s) {
558   const char *t;
559   struct vector v;
560
561   vector_init(&v);
562   if(s) {
563     /* skip initial separators */
564     while(*s && (!tagchar(*s) || *s == ' '))
565       ++s;
566     while(*s) {
567       /* find the extent of the tag */
568       t = s;
569       while(*s && tagchar(*s))
570         ++s;
571       /* strip trailing spaces */
572       while(s > t && s[-1] == ' ')
573         --s;
574       vector_append(&v, xstrndup(t, s - t));
575       /* skip intermediate and trailing separators */
576       while(*s && (!tagchar(*s) || *s == ' '))
577         ++s;
578     }
579   }
580   vector_terminate(&v);
581   return dedupe(v.vec, v.nvec);
582 }
583
584 /* Record that TRACK has TAG.  Returns 0 or DB_LOCK_DEADLOCK. */
585 static int register_tag(const char *track, const char *tag, DB_TXN *tid) {
586   return register_word(trackdb_tagsdb, "tags", track, tag, tid);
587 }
588
589 /* aliases *******************************************************************/
590
591 /* compute the alias and store at aliasp.  Returns 0 or DB_LOCK_DEADLOCK.  If
592  * there is no alias sets *aliasp to 0. */
593 static int compute_alias(char **aliasp,
594                          const char *track,
595                          const struct kvp *p,
596                          DB_TXN *tid) {
597   struct dynstr d;
598   const char *s = config->alias, *t, *expansion, *part;
599   int c, used_db = 0, slash_prefix, err;
600   struct kvp *at;
601   const char *const root = find_track_root(track);
602
603   if(!root) {
604     /* Bodge for tracks with no root */
605     *aliasp = 0;
606     return 0;
607   }
608   dynstr_init(&d);
609   dynstr_append_string(&d, root);
610   while((c = (unsigned char)*s++)) {
611     if(c != '{') {
612       dynstr_append(&d, c);
613       continue;
614     }
615     if((slash_prefix = (*s == '/')))
616       s++;
617     t = strchr(s, '}');
618     assert(t != 0);                     /* validated at startup */
619     part = xstrndup(s, t - s);
620     expansion = getpart(track, "display", part, p, &used_db);
621     if(*expansion) {
622       if(slash_prefix) dynstr_append(&d, '/');
623       dynstr_append_string(&d, expansion);
624     }
625     s = t + 1;                          /* skip {part} */
626   }
627   /* only admit to the alias if we used the db... */
628   if(!used_db) {
629     *aliasp = 0;
630     return 0;
631   }
632   dynstr_terminate(&d);
633   /* ...and the answer differs from the original... */
634   if(!strcmp(track, d.vec)) {
635     *aliasp = 0;
636     return 0;
637   }
638   /* ...and there isn't already a different track with that name (including as
639    * an alias) */
640   switch(err = trackdb_getdata(trackdb_tracksdb, d.vec, &at, tid)) {
641   case 0:
642     if((s = kvp_get(at, "_alias_for"))
643        && !strcmp(s, track)) {
644     case DB_NOTFOUND:
645       *aliasp = d.vec;
646     } else {
647       *aliasp = 0;
648     }
649     return 0;
650   default:
651     return err;
652   }
653 }
654
655 /* get track and prefs data (if tp/pp not null pointers).  Returns 0 on
656  * success, DB_NOTFOUND if the track does not exist or DB_LOCK_DEADLOCK.
657  * Always sets the return values, even if only to null pointers. */
658 static int gettrackdata(const char *track,
659                         struct kvp **tp,
660                         struct kvp **pp,
661                         const char **actualp,
662                         unsigned flags,
663 #define GTD_NOALIAS 0x0001
664                         DB_TXN *tid) {
665   int err;
666   const char *actual = track;
667   struct kvp *t = 0, *p = 0;
668   
669   if((err = trackdb_getdata(trackdb_tracksdb, track, &t, tid))) goto done;
670   if((actual = kvp_get(t, "_alias_for"))) {
671     if(flags & GTD_NOALIAS) {
672       error(0, "alias passed to gettrackdata where real path required");
673       abort();
674     }
675     if((err = trackdb_getdata(trackdb_tracksdb, actual, &t, tid))) goto done;
676   } else
677     actual = track;
678   assert(actual != 0);
679   if(pp) {
680     if((err = trackdb_getdata(trackdb_prefsdb, actual, &p, tid)) == DB_LOCK_DEADLOCK)
681       goto done;
682   }
683   err = 0;
684 done:
685   if(actualp) *actualp = actual;
686   if(tp) *tp = t;
687   if(pp) *pp = p;
688   return err;
689 }
690
691 /* trackdb_notice() **********************************************************/
692
693 /** @brief notice a possibly new track
694  * @return @c DB_NOTFOUND if new, 0 if already known
695  */
696 int trackdb_notice(const char *track,
697                    const char *path) {
698   int err;
699   DB_TXN *tid;
700   
701   for(;;) {
702     tid = trackdb_begin_transaction();
703     err = trackdb_notice_tid(track, path, tid);
704     if((err == DB_LOCK_DEADLOCK)) goto fail;
705     break;
706   fail:
707     trackdb_abort_transaction(tid);
708   }
709   trackdb_commit_transaction(tid);
710   return err;
711 }
712
713 /** @brief notice a possibly new track
714  * @return @c DB_NOTFOUND if new, 0 if already known, @c DB_LOCK_DEADLOCK also
715  */
716 int trackdb_notice_tid(const char *track,
717                        const char *path,
718                        DB_TXN *tid) {
719   int err, n;
720   struct kvp *t, *a, *p;
721   int t_changed, ret;
722   char *alias, **w;
723
724   /* notice whether the tracks.db entry changes */
725   t_changed = 0;
726   /* get any existing tracks entry */
727   if((err = gettrackdata(track, &t, &p, 0, 0, tid)) == DB_LOCK_DEADLOCK)
728     return err;
729   ret = err;                            /* 0 or DB_NOTFOUND */
730   /* this is a real track */
731   t_changed += kvp_set(&t, "_alias_for", 0);
732   t_changed += kvp_set(&t, "_path", path);
733   /* if we have an alias record it in the database */
734   if((err = compute_alias(&alias, track, p, tid))) return err;
735   if(alias) {
736     /* won't overwrite someone else's alias as compute_alias() checks */
737     D(("%s: alias %s", track, alias));
738     a = 0;
739     kvp_set(&a, "_alias_for", track);
740     if((err = trackdb_putdata(trackdb_tracksdb, alias, a, tid, 0))) return err;
741   }
742   /* update search.db */
743   w = track_to_words(track, p);
744   for(n = 0; w[n]; ++n)
745     if((err = register_search_word(track, w[n], tid)))
746       return err;
747   /* update tags.db */
748   w = parsetags(kvp_get(p, "tags"));
749   for(n = 0; w[n]; ++n)
750     if((err = register_tag(track, w[n], tid)))
751       return err;
752   reqtracks = 0;
753   /* only store the tracks.db entry if it has changed */
754   if(t_changed && (err = trackdb_putdata(trackdb_tracksdb, track, t, tid, 0)))
755     return err;
756   if(ret == DB_NOTFOUND) {
757     uint32_t timestamp[2];
758     time_t now;
759     DBT key, data;
760
761     time(&now);
762     timestamp[0] = htonl((uint64_t)now >> 32);
763     timestamp[1] = htonl((uint32_t)now);
764     memset(&key, 0, sizeof key);
765     key.data = timestamp;
766     key.size = sizeof timestamp;
767     switch(err = trackdb_noticeddb->put(trackdb_noticeddb, tid, &key,
768                                         make_key(&data, track), 0)) {
769     case 0: break;
770     case DB_LOCK_DEADLOCK: return err;
771     default: fatal(0, "error updating noticed.db: %s", db_strerror(err));
772     }
773   }
774   return ret;
775 }
776
777 /* trackdb_obsolete() ********************************************************/
778
779 /* obsolete a track */
780 int trackdb_obsolete(const char *track, DB_TXN *tid) {
781   int err, n;
782   struct kvp *p;
783   char *alias, **w;
784
785   if((err = gettrackdata(track, 0, &p, 0,
786                          GTD_NOALIAS, tid)) == DB_LOCK_DEADLOCK)
787     return err;
788   else if(err == DB_NOTFOUND) return 0;
789   /* compute the alias, if any, and delete it */
790   if(compute_alias(&alias, track, p, tid)) return err;
791   if(alias) {
792     /* if the alias points to some other track then compute_alias won't
793      * return it */
794     if(trackdb_delkey(trackdb_tracksdb, alias, tid))
795       return err;
796   }
797   /* update search.db */
798   w = track_to_words(track, p);
799   for(n = 0; w[n]; ++n)
800     if(trackdb_delkeydata(trackdb_searchdb,
801                           w[n], track, tid) == DB_LOCK_DEADLOCK)
802       return err;
803   /* update tags.db */
804   w = parsetags(kvp_get(p, "tags"));
805   for(n = 0; w[n]; ++n)
806     if(trackdb_delkeydata(trackdb_tagsdb,
807                           w[n], track, tid) == DB_LOCK_DEADLOCK)
808       return err;
809   reqtracks = 0;
810   /* update tracks.db */
811   if(trackdb_delkey(trackdb_tracksdb, track, tid) == DB_LOCK_DEADLOCK)
812     return err;
813   /* We don't delete the prefs, so they survive temporary outages of the
814    * (possibly virtual) track filesystem */
815   return 0;
816 }
817
818 /* trackdb_stats() ***********************************************************/
819
820 #define H(name) { #name, offsetof(DB_HASH_STAT, name) }
821 #define B(name) { #name, offsetof(DB_BTREE_STAT, name) }
822
823 static const struct statinfo {
824   const char *name;
825   size_t offset;
826 } statinfo_hash[] = {
827   H(hash_magic),
828   H(hash_version),
829   H(hash_nkeys),
830   H(hash_ndata),
831   H(hash_pagesize),
832   H(hash_ffactor),
833   H(hash_buckets),
834   H(hash_free),
835   H(hash_bfree),
836   H(hash_bigpages),
837   H(hash_big_bfree),
838   H(hash_overflows),
839   H(hash_ovfl_free),
840   H(hash_dup),
841   H(hash_dup_free),
842 }, statinfo_btree[] = {
843   B(bt_magic),
844   B(bt_version),
845   B(bt_nkeys),
846   B(bt_ndata),
847   B(bt_pagesize),
848   B(bt_minkey),
849   B(bt_re_len),
850   B(bt_re_pad),
851   B(bt_levels),
852   B(bt_int_pg),
853   B(bt_leaf_pg),
854   B(bt_dup_pg),
855   B(bt_over_pg),
856   B(bt_free),
857   B(bt_int_pgfree),
858   B(bt_leaf_pgfree),
859   B(bt_dup_pgfree),
860   B(bt_over_pgfree),
861 };
862
863 /* look up stats for DB */
864 static int get_stats(struct vector *v,
865                      DB *database,
866                      const struct statinfo *si,
867                      size_t nsi,
868                      DB_TXN *tid) {
869   void *sp;
870   size_t n;
871   char *str;
872   int err;
873
874   if(database) {
875     switch(err = database->stat(database, tid, &sp, 0)) {
876     case 0:
877       break;
878     case DB_LOCK_DEADLOCK:
879       error(0, "error querying database: %s", db_strerror(err));
880       return err;
881     default:
882       fatal(0, "error querying database: %s", db_strerror(err));
883     }
884     for(n = 0; n < nsi; ++n) {
885       byte_xasprintf(&str, "%s=%"PRIuMAX, si[n].name,
886                      (uintmax_t)*(u_int32_t *)((char *)sp + si[n].offset));
887       vector_append(v, str);
888     }
889   }
890   return 0;
891 }
892
893 struct search_entry {
894   char *word;
895   int n;
896 };
897
898 /* find the top COUNT words in the search database */
899 static int search_league(struct vector *v, int count, DB_TXN *tid) {
900   struct search_entry *se;
901   DBT k, d;
902   DBC *cursor;
903   int err, n = 0, nse = 0, i;
904   char *word = 0;
905   size_t wl = 0;
906   char *str;
907
908   cursor = trackdb_opencursor(trackdb_searchdb, tid);
909   se = xmalloc(count * sizeof *se);
910   while(!(err = cursor->c_get(cursor, prepare_data(&k), prepare_data(&d),
911                               DB_NEXT))) {
912     if(word && wl == k.size && !strncmp(word, k.data, wl))
913       ++n;
914     else {
915 #define FINALIZE() do {                                         \
916   if(word && (nse < count || n > se[nse - 1].n)) {              \
917     if(nse == count)                                            \
918       i = nse - 1;                                              \
919     else                                                        \
920       i = nse++;                                                \
921     while(i > 0 && n > se[i - 1].n)                             \
922       --i;                                                      \
923     memmove(&se[i + 1], &se[i], (nse - i) * sizeof *se);        \
924     se[i].word = word;                                          \
925     se[i].n = n;                                                \
926   }                                                             \
927 } while(0)
928       FINALIZE();
929       word = xstrndup(k.data, wl = k.size);
930       n = 1;
931     }
932   }
933   switch(err) {
934   case DB_NOTFOUND:
935     err = 0;
936     break;
937   case DB_LOCK_DEADLOCK:
938     error(0, "error querying search database: %s", db_strerror(err));
939     break;
940   default:
941     fatal(0, "error querying search database: %s", db_strerror(err));
942   }
943   if(trackdb_closecursor(cursor)) err = DB_LOCK_DEADLOCK;
944   if(err) return err;
945   FINALIZE();
946   byte_xasprintf(&str, "Top %d search words:", nse);
947   vector_append(v, str);
948   for(i = 0; i < nse; ++i) {
949     byte_xasprintf(&str, "%4d: %5d %s", i + 1, se[i].n, se[i].word);
950     vector_append(v, str);
951   }
952   return 0;
953 }
954
955 #define SI(what) statinfo_##what, \
956                  sizeof statinfo_##what / sizeof (struct statinfo)
957
958 /* return a list of database stats */
959 char **trackdb_stats(int *nstatsp) {
960   DB_TXN *tid;
961   struct vector v;
962   
963   vector_init(&v);
964   for(;;) {
965     tid = trackdb_begin_transaction();
966     v.nvec = 0;
967     vector_append(&v, (char *)"Tracks database stats:");
968     if(get_stats(&v, trackdb_tracksdb, SI(btree), tid)) goto fail;
969     vector_append(&v, (char *)"");
970     vector_append(&v, (char *)"Search database stats:");
971     if(get_stats(&v, trackdb_searchdb, SI(hash), tid)) goto fail;
972     vector_append(&v, (char *)"");
973     vector_append(&v, (char *)"Prefs database stats:");
974     if(get_stats(&v, trackdb_prefsdb, SI(hash), tid)) goto fail;
975     vector_append(&v, (char *)"");
976     if(search_league(&v, 10, tid)) goto fail;
977     vector_terminate(&v);
978     break;
979 fail:
980     trackdb_abort_transaction(tid);
981   }
982   trackdb_commit_transaction(tid);
983   if(nstatsp) *nstatsp = v.nvec;
984   return v.vec;
985 }
986
987 struct stats_details {
988   void (*done)(char *data, void *u);
989   void *u;
990   int exited;                           /* subprocess exited */
991   int closed;                           /* pipe close */
992   int wstat;                            /* wait status from subprocess */
993   struct dynstr data[1];                /* data read from pipe */
994 };
995
996 static void stats_complete(struct stats_details *d) {
997   char *s;
998   
999   if(!(d->exited && d->closed))
1000     return;
1001   byte_xasprintf(&s, "\n"
1002                  "Server stats:\n"
1003                  "track lookup cache hits: %lu\n"
1004                  "track lookup cache misses: %lu\n",
1005                  cache_files_hits,
1006                  cache_files_misses);
1007   dynstr_append_string(d->data, s);
1008   dynstr_terminate(d->data);
1009   d->done(d->data->vec, d->u);
1010 }
1011
1012 static int stats_finished(ev_source attribute((unused)) *ev,
1013                           pid_t attribute((unused)) pid,
1014                           int status,
1015                           const struct rusage attribute((unused)) *rusage,
1016                           void *u) {
1017   struct stats_details *const d = u;
1018
1019   d->exited = 1;
1020   if(status)
1021     error(0, "disorder-stats %s", wstat(status));
1022   stats_complete(d);
1023   return 0;
1024 }
1025
1026 static int stats_read(ev_source attribute((unused)) *ev,
1027                       ev_reader *reader,
1028                       void *ptr,
1029                       size_t bytes,
1030                       int eof,
1031                       void *u) {
1032   struct stats_details *const d = u;
1033
1034   dynstr_append_bytes(d->data, ptr, bytes);
1035   ev_reader_consume(reader, bytes);
1036   if(eof)
1037     d->closed = 1;
1038   stats_complete(d);
1039   return 0;
1040 }
1041
1042 static int stats_error(ev_source attribute((unused)) *ev,
1043                        int errno_value,
1044                        void *u) {
1045   struct stats_details *const d = u;
1046
1047   error(errno_value, "error reading from pipe to disorder-stats");
1048   d->closed = 1;
1049   stats_complete(d);
1050   return 0;
1051 }
1052
1053 void trackdb_stats_subprocess(ev_source *ev,
1054                               void (*done)(char *data, void *u),
1055                               void *u) {
1056   int p[2];
1057   pid_t pid;
1058   struct stats_details *d = xmalloc(sizeof *d);
1059
1060   dynstr_init(d->data);
1061   d->done = done;
1062   d->u = u;
1063   xpipe(p);
1064   pid = subprogram(ev, "disorder-stats", p[1]);
1065   xclose(p[1]);
1066   ev_child(ev, pid, 0, stats_finished, d);
1067   ev_reader_new(ev, p[0], stats_read, stats_error, d, "disorder-stats reader");
1068 }
1069
1070 /* set a pref (remove if value=0) */
1071 int trackdb_set(const char *track,
1072                 const char *name,
1073                 const char *value) {
1074   struct kvp *t, *p, *a;
1075   DB_TXN *tid;
1076   int err, cmp;
1077   char *oldalias, *newalias, **oldtags = 0, **newtags;
1078
1079   if(value) {
1080     /* TODO: if value matches default then set value=0 */
1081   }
1082   
1083   for(;;) {
1084     tid = trackdb_begin_transaction();
1085     if((err = gettrackdata(track, &t, &p, 0,
1086                            0, tid)) == DB_LOCK_DEADLOCK)
1087       goto fail;
1088     if(err == DB_NOTFOUND) break;
1089     if(name[0] == '_') {
1090       if(kvp_set(&t, name, value))
1091         if(trackdb_putdata(trackdb_tracksdb, track, t, tid, 0))
1092           goto fail;
1093     } else {
1094       /* get the old alias name */
1095       if(compute_alias(&oldalias, track, p, tid)) goto fail;
1096       /* get the old tags */
1097       if(!strcmp(name, "tags"))
1098         oldtags = parsetags(kvp_get(p, "tags"));
1099       /* set the value */
1100       if(kvp_set(&p, name, value))
1101         if(trackdb_putdata(trackdb_prefsdb, track, p, tid, 0))
1102           goto fail;
1103       /* compute the new alias name */
1104       if((err = compute_alias(&newalias, track, p, tid))) goto fail;
1105       /* check whether alias has changed */
1106       if(!(oldalias == newalias
1107            || (oldalias && newalias && !strcmp(oldalias, newalias)))) {
1108         /* adjust alias records to fit change */
1109         if(oldalias
1110            && trackdb_delkey(trackdb_tracksdb, oldalias, tid)) goto fail;
1111         if(newalias) {
1112           a = 0;
1113           kvp_set(&a, "_alias_for", track);
1114           if(trackdb_putdata(trackdb_tracksdb, newalias, a, tid, 0)) goto fail;
1115         }
1116       }
1117       /* check whether tags have changed */
1118       if(!strcmp(name, "tags")) {
1119         newtags = parsetags(value);
1120         while(*oldtags || *newtags) {
1121           if(*oldtags && *newtags) {
1122             cmp = strcmp(*oldtags, *newtags);
1123             if(!cmp) {
1124               /* keeping this tag */
1125               ++oldtags;
1126               ++newtags;
1127             } else if(cmp < 0)
1128               /* old tag fits into a gap in the new list, so delete old */
1129               goto delete_old;
1130             else
1131               /* new tag fits into a gap in the old list, so insert new */
1132               goto insert_new;
1133           } else if(*oldtags) {
1134             /* we've run out of new tags, so remaining old ones are to be
1135              * deleted */
1136           delete_old:
1137             if(trackdb_delkeydata(trackdb_tagsdb,
1138                                   *oldtags, track, tid) == DB_LOCK_DEADLOCK)
1139               goto fail;
1140             ++oldtags;
1141           } else {
1142             /* we've run out of old tags, so remainig new ones are to be
1143              * inserted */
1144           insert_new:
1145             if(register_tag(track, *newtags, tid)) goto fail;
1146             ++newtags;
1147           }
1148         }
1149         reqtracks = 0;
1150       }
1151     }
1152     err = 0;
1153     break;
1154 fail:
1155     trackdb_abort_transaction(tid);
1156   }
1157   trackdb_commit_transaction(tid);
1158   return err == 0 ? 0 : -1;
1159 }
1160
1161 /* get a pref */
1162 const char *trackdb_get(const char *track,
1163                         const char *name) {
1164   return kvp_get(trackdb_get_all(track), name);
1165 }
1166
1167 /* get all prefs as a 0-terminated array */
1168 struct kvp *trackdb_get_all(const char *track) {
1169   struct kvp *t, *p, **pp;
1170   DB_TXN *tid;
1171
1172   for(;;) {
1173     tid = trackdb_begin_transaction();
1174     if(gettrackdata(track, &t, &p, 0, 0, tid) == DB_LOCK_DEADLOCK)
1175       goto fail;
1176     break;
1177 fail:
1178     trackdb_abort_transaction(tid);
1179   }
1180   trackdb_commit_transaction(tid);
1181   for(pp = &p; *pp; pp = &(*pp)->next)
1182     ;
1183   *pp = t;
1184   return p;
1185 }
1186
1187 /* resolve alias */
1188 const char *trackdb_resolve(const char *track) {
1189   DB_TXN *tid;
1190   const char *actual;
1191   
1192   for(;;) {
1193     tid = trackdb_begin_transaction();
1194     if(gettrackdata(track, 0, 0, &actual, 0, tid) == DB_LOCK_DEADLOCK)
1195       goto fail;
1196     break;
1197 fail:
1198     trackdb_abort_transaction(tid);
1199   }
1200   trackdb_commit_transaction(tid);
1201   return actual;
1202 }
1203
1204 int trackdb_isalias(const char *track) {
1205   const char *actual = trackdb_resolve(track);
1206
1207   return strcmp(actual, track);
1208 }
1209
1210 /* test whether a track exists (perhaps an alias) */
1211 int trackdb_exists(const char *track) {
1212   DB_TXN *tid;
1213   int err;
1214
1215   for(;;) {
1216     tid = trackdb_begin_transaction();
1217     /* unusually, here we want the return value */
1218     if((err = gettrackdata(track, 0, 0, 0, 0, tid)) == DB_LOCK_DEADLOCK)
1219       goto fail;
1220     break;
1221 fail:
1222     trackdb_abort_transaction(tid);
1223   }
1224   trackdb_commit_transaction(tid);
1225   return (err == 0);
1226 }
1227
1228 /* return the list of tags */
1229 char **trackdb_alltags(void) {
1230   DB_TXN *tid;
1231   int err;
1232   char **taglist;
1233
1234   for(;;) {
1235     tid = trackdb_begin_transaction();
1236     err = trackdb_alltags_tid(tid, &taglist);
1237     if(!err) break;
1238     trackdb_abort_transaction(tid);
1239   }
1240   trackdb_commit_transaction(tid);
1241   return taglist;
1242 }
1243
1244 static int trackdb_alltags_tid(DB_TXN *tid, char ***taglistp) {
1245   struct vector v;
1246   DBC *c;
1247   DBT k, d;
1248   int err;
1249
1250   vector_init(&v);
1251   c = trackdb_opencursor(trackdb_tagsdb, tid);
1252   memset(&k, 0, sizeof k);
1253   while(!(err = c->c_get(c, &k, prepare_data(&d), DB_NEXT_NODUP)))
1254     vector_append(&v, xstrndup(k.data, k.size));
1255   switch(err) {
1256   case DB_NOTFOUND:
1257     break;
1258   case DB_LOCK_DEADLOCK:
1259       return err;
1260   default:
1261     fatal(0, "c->c_get: %s", db_strerror(err));
1262   }
1263   if((err = trackdb_closecursor(c))) return err;
1264   vector_terminate(&v);
1265   *taglistp = v.vec;
1266   return 0;
1267 }
1268
1269 /* return 1 iff sorted tag lists A and B have at least one member in common */
1270 static int tag_intersection(char **a, char **b) {
1271   int cmp;
1272
1273   /* Same sort of logic as trackdb_set() above */
1274   while(*a && *b) {
1275     if(!(cmp = strcmp(*a, *b))) return 1;
1276     else if(cmp < 0) ++a;
1277     else ++b;
1278   }
1279   return 0;
1280 }
1281
1282 /* Check whether a track is suitable for random play.  Returns 0 if it is,
1283  * DB_NOTFOUND if it is not or DB_LOCK_DEADLOCK if the database gave us
1284  * that. */
1285 static int check_suitable(const char *track,
1286                           DB_TXN *tid,
1287                           char **required_tags,
1288                           char **prohibited_tags) {
1289   char **track_tags;
1290   time_t last, now;
1291   struct kvp *p, *t;
1292   const char *pick_at_random, *played_time;
1293
1294   /* don't pick tracks that aren't in any surviving collection (for instance
1295    * you've edited the config but the rescan hasn't done its job yet) */
1296   if(!find_track_root(track)) {
1297     info("found track not in any collection: %s", track);
1298     return DB_NOTFOUND;
1299   }
1300   /* don't pick aliases - only pick the canonical form */
1301   if(gettrackdata(track, &t, &p, 0, 0, tid) == DB_LOCK_DEADLOCK)
1302     return DB_LOCK_DEADLOCK;
1303   if(kvp_get(t, "_alias_for"))
1304     return DB_NOTFOUND;
1305   /* check that random play is not suppressed for this track */
1306   if((pick_at_random = kvp_get(p, "pick_at_random"))
1307      && !strcmp(pick_at_random, "0"))
1308     return DB_NOTFOUND;
1309   /* don't pick a track that's been played in the last 8 hours */
1310   if((played_time = kvp_get(p, "played_time"))) {
1311     last = atoll(played_time);
1312     now = time(0);
1313     if(now < last + 8 * 3600)       /* TODO configurable */
1314       return DB_NOTFOUND;
1315   }
1316   track_tags = parsetags(kvp_get(p, "tags"));
1317   /* check that no prohibited tag is present for this track */
1318   if(prohibited_tags && tag_intersection(track_tags, prohibited_tags))
1319     return DB_NOTFOUND;
1320   /* check that at least one required tags is present for this track */
1321   if(*required_tags && !tag_intersection(track_tags, required_tags))
1322     return DB_NOTFOUND;
1323   return 0;
1324 }
1325
1326 /* attempt to pick a random non-alias track */
1327 const char *trackdb_random(int tries) {
1328   DBT key, data;
1329   DB_BTREE_STAT *sp;
1330   int err, n;
1331   DB_TXN *tid;
1332   const char *track, *candidate;
1333   db_recno_t r;
1334   const char *tags;
1335   char **required_tags, **prohibited_tags, **tp;
1336   hash *h;
1337   DBC *c = 0;
1338
1339   for(;;) {
1340     tid = trackdb_begin_transaction();
1341     if((err = trackdb_get_global_tid("required-tags", tid, &tags)))
1342       goto fail;
1343     required_tags = parsetags(tags);
1344     if((err = trackdb_get_global_tid("prohibited-tags", tid, &tags)))
1345       goto fail;
1346     prohibited_tags = parsetags(tags);
1347     track = 0;
1348     if(*required_tags) {
1349       /* Bung all the suitable tracks into a hash and convert to a list of keys
1350        * (to eliminate duplicates).  We cache this list since it is possible
1351        * that it will be very large. */
1352       if(!reqtracks) {
1353         h = hash_new(0);
1354         for(tp = required_tags; *tp; ++tp) {
1355           c = trackdb_opencursor(trackdb_tagsdb, tid);
1356           memset(&key, 0, sizeof key);
1357           key.data = *tp;
1358           key.size = strlen(*tp);
1359           n = 0;
1360           err = c->c_get(c, &key, prepare_data(&data), DB_SET);
1361           while(err == 0) {
1362             hash_add(h, xstrndup(data.data, data.size), 0,
1363                      HASH_INSERT_OR_REPLACE);
1364             ++n;
1365             err = c->c_get(c, &key, prepare_data(&data), DB_NEXT_DUP);
1366           }
1367           switch(err) {
1368           case 0:
1369           case DB_NOTFOUND:
1370             break;
1371           case DB_LOCK_DEADLOCK:
1372             goto fail;
1373           default:
1374             fatal(0, "error querying tags.db: %s", db_strerror(err));
1375           }
1376           trackdb_closecursor(c);
1377           c = 0;
1378           if(!n)
1379             error(0, "required tag %s does not match any tracks", *tp);
1380         }
1381         nreqtracks = hash_count(h);
1382         reqtracks = hash_keys(h);
1383       }
1384       while(nreqtracks && !track && tries-- > 0) {
1385         r = (rand() * (double)nreqtracks / (RAND_MAX + 1.0));
1386         candidate = reqtracks[r];
1387         switch(check_suitable(candidate, tid,
1388                               required_tags, prohibited_tags)) {
1389         case 0:
1390           track = candidate;
1391           break;
1392         case DB_NOTFOUND:
1393           break;
1394         case DB_LOCK_DEADLOCK:
1395           goto fail;
1396         }
1397       }
1398     } else {
1399       /* No required tags.  We pick random record numbers in the database
1400        * instead. */
1401       switch(err = trackdb_tracksdb->stat(trackdb_tracksdb, tid, &sp, 0)) {
1402       case 0:
1403         break;
1404       case DB_LOCK_DEADLOCK:
1405         error(0, "error querying tracks.db: %s", db_strerror(err));
1406         goto fail;
1407       default:
1408         fatal(0, "error querying tracks.db: %s", db_strerror(err));
1409       }
1410       if(!sp->bt_nkeys)
1411         error(0, "cannot pick tracks at random from an empty database");
1412       while(sp->bt_nkeys && !track && tries-- > 0) {
1413         /* record numbers count from 1 upwards */
1414         r = 1 + (rand() * (double)sp->bt_nkeys / (RAND_MAX + 1.0));
1415         memset(&key, sizeof key, 0);
1416         key.flags = DB_DBT_MALLOC;
1417         key.size = sizeof r;
1418         key.data = &r;
1419         switch(err = trackdb_tracksdb->get(trackdb_tracksdb, tid, &key, prepare_data(&data),
1420                                            DB_SET_RECNO)) {
1421         case 0:
1422           break;
1423         case DB_LOCK_DEADLOCK:
1424           error(0, "error querying tracks.db: %s", db_strerror(err));
1425           goto fail;
1426         default:
1427           fatal(0, "error querying tracks.db: %s", db_strerror(err));
1428         }
1429         candidate = xstrndup(key.data, key.size);
1430         switch(check_suitable(candidate, tid,
1431                               required_tags, prohibited_tags)) {
1432         case 0:
1433           track = candidate;
1434           break;
1435         case DB_NOTFOUND:
1436           break;
1437         case DB_LOCK_DEADLOCK:
1438           goto fail;
1439         }
1440       }
1441     }
1442     break;
1443 fail:
1444     trackdb_closecursor(c);
1445     c = 0;
1446     trackdb_abort_transaction(tid);
1447   }
1448   trackdb_commit_transaction(tid);
1449   if(!track)
1450     error(0, "could not pick a random track");
1451   return track;
1452 }
1453
1454 /* get a track name given the prefs.  Set *used_db to 1 if we got the answer
1455  * from the prefs. */
1456 static const char *getpart(const char *track,
1457                            const char *context,
1458                            const char *part,
1459                            const struct kvp *p,
1460                            int *used_db) {
1461   const char *result;
1462   char *pref;
1463
1464   byte_xasprintf(&pref, "trackname_%s_%s", context, part);
1465   if((result = kvp_get(p, pref)))
1466     *used_db = 1;
1467   else
1468     result = trackname_part(track, context, part);
1469   assert(result != 0);
1470   return result;
1471 }
1472
1473 /* get a track name part, like trackname_part(), but taking the database into
1474  * account. */
1475 const char *trackdb_getpart(const char *track,
1476                             const char *context,
1477                             const char *part) {
1478   struct kvp *p;
1479   DB_TXN *tid;
1480   char *pref;
1481   const char *actual;
1482   int used_db, err;
1483
1484   /* construct the full pref */
1485   byte_xasprintf(&pref, "trackname_%s_%s", context, part);
1486   for(;;) {
1487     tid = trackdb_begin_transaction();
1488     if((err = gettrackdata(track, 0, &p, &actual, 0, tid)) == DB_LOCK_DEADLOCK)
1489       goto fail;
1490     break;
1491 fail:
1492     trackdb_abort_transaction(tid);
1493   }
1494   trackdb_commit_transaction(tid);
1495   return getpart(actual, context, part, p, &used_db);
1496 }
1497
1498 /* get the raw path name for @track@ (might be an alias) */
1499 const char *trackdb_rawpath(const char *track) {
1500   DB_TXN *tid;
1501   struct kvp *t;
1502   const char *path;
1503
1504   for(;;) {
1505     tid = trackdb_begin_transaction();
1506     if(gettrackdata(track, &t, 0, 0, 0, tid) == DB_LOCK_DEADLOCK)
1507       goto fail;
1508     break;
1509 fail:
1510     trackdb_abort_transaction(tid);
1511   }
1512   trackdb_commit_transaction(tid);
1513   if(!(path = kvp_get(t, "_path"))) path = track;
1514   return path;
1515 }
1516
1517 /* trackdb_list **************************************************************/
1518
1519 /* this is incredibly ugly, sorry, perhaps it will be rewritten to be actually
1520  * readable at some point */
1521
1522 /* return true if the basename of TRACK[0..TL-1], as defined by DL, matches RE.
1523  * If RE is a null pointer then it matches everything. */
1524 static int track_matches(size_t dl, const char *track, size_t tl,
1525                          const pcre *re) {
1526   int ovec[3], rc;
1527
1528   if(!re)
1529     return 1;
1530   track += dl + 1;
1531   tl -= (dl + 1);
1532   switch(rc = pcre_exec(re, 0, track, tl, 0, 0, ovec, 3)) {
1533   case PCRE_ERROR_NOMATCH: return 0;
1534   default:
1535     if(rc < 0) {
1536       error(0, "pcre_exec returned %d, subject '%s'", rc, track);
1537       return 0;
1538     }
1539     return 1;
1540   }
1541 }
1542
1543 static int do_list(struct vector *v, const char *dir,
1544                    enum trackdb_listable what, const pcre *re, DB_TXN *tid) {
1545   DBC *cursor;
1546   DBT k, d;
1547   size_t dl;
1548   char *ptr;
1549   int err;
1550   size_t l, last_dir_len = 0;
1551   char *last_dir = 0, *track, *alias;
1552   struct kvp *p;
1553   
1554   dl = strlen(dir);
1555   cursor = trackdb_opencursor(trackdb_tracksdb, tid);
1556   make_key(&k, dir);
1557   prepare_data(&d);
1558   /* find the first key >= dir */
1559   err = cursor->c_get(cursor, &k, &d, DB_SET_RANGE);
1560   /* keep going while we're dealing with <dir/anything> */
1561   while(err == 0
1562         && k.size > dl
1563         && ((char *)k.data)[dl] == '/'
1564         && !memcmp(k.data, dir, dl)) {
1565     ptr = memchr((char *)k.data + dl + 1, '/', k.size - (dl + 1));
1566     if(ptr) {
1567       /* we have <dir/component/anything>, so <dir/component> is a directory */
1568       l = ptr - (char *)k.data;
1569       if(what & trackdb_directories)
1570         if(!(last_dir
1571              && l == last_dir_len
1572              && !memcmp(last_dir, k.data, l))) {
1573           last_dir = xstrndup(k.data, last_dir_len = l);
1574           if(track_matches(dl, k.data, l, re))
1575             vector_append(v, last_dir);
1576         }
1577     } else {
1578       /* found a plain file */
1579       if((what & trackdb_files)) {
1580         track = xstrndup(k.data, k.size);
1581         if((err = trackdb_getdata(trackdb_prefsdb,
1582                                   track, &p, tid)) == DB_LOCK_DEADLOCK)
1583           goto deadlocked;
1584         /* if this file has an alias in the same directory then we skip it */
1585         if((err = compute_alias(&alias, track, p, tid)))
1586           goto deadlocked;
1587         if(!(alias && !strcmp(d_dirname(alias), d_dirname(track))))
1588           if(track_matches(dl, k.data, k.size, re))
1589             vector_append(v, track);
1590       }
1591     }
1592     err = cursor->c_get(cursor, &k, &d, DB_NEXT);
1593   }
1594   switch(err) {
1595   case 0:
1596     break;
1597   case DB_NOTFOUND:
1598     err = 0;
1599     break;
1600   case DB_LOCK_DEADLOCK:
1601     error(0, "error querying database: %s", db_strerror(err));
1602     break;
1603   default:
1604     fatal(0, "error querying database: %s", db_strerror(err));
1605   }
1606 deadlocked:
1607   if(trackdb_closecursor(cursor)) err = DB_LOCK_DEADLOCK;
1608   return err;
1609 }
1610
1611 /* return the directories or files below @dir@ */
1612 char **trackdb_list(const char *dir, int *np, enum trackdb_listable what,
1613                     const pcre *re) {
1614   DB_TXN *tid;
1615   int n;
1616   struct vector v;
1617
1618   vector_init(&v);
1619   for(;;) {
1620     tid = trackdb_begin_transaction();
1621     v.nvec = 0;
1622     if(dir) {
1623       if(do_list(&v, dir, what, re, tid))
1624         goto fail;
1625     } else {
1626       for(n = 0; n < config->collection.n; ++n)
1627         if(do_list(&v, config->collection.s[n].root, what, re, tid))
1628           goto fail;
1629     }
1630     break;
1631 fail:
1632     trackdb_abort_transaction(tid);
1633   }
1634   trackdb_commit_transaction(tid);
1635   vector_terminate(&v);
1636   if(np)
1637     *np = v.nvec;
1638   return v.vec;
1639 }  
1640
1641 /* If S is tag:something, return something.  Else return 0. */
1642 static const char *checktag(const char *s) {
1643   if(!strncmp(s, "tag:", 4))
1644     return s + 4;
1645   else
1646     return 0;
1647 }
1648
1649 /* return a list of tracks containing all of the words given.  If you
1650  * ask for only stopwords you get no tracks. */
1651 char **trackdb_search(char **wordlist, int nwordlist, int *ntracks) {
1652   const char **w, *best = 0, *tag;
1653   char **twords, **tags;
1654   int i, j, n, err, what;
1655   DBC *cursor = 0;
1656   DBT k, d;
1657   struct vector u, v;
1658   DB_TXN *tid;
1659   struct kvp *p;
1660   int ntags = 0;
1661   DB *db;
1662   const char *dbname;
1663
1664   *ntracks = 0;                         /* for early returns */
1665   /* casefold all the words */
1666   w = xmalloc(nwordlist * sizeof (char *));
1667   for(n = 0; n < nwordlist; ++n) {
1668     w[n] = casefold(wordlist[n]);
1669     if(checktag(w[n])) ++ntags;         /* count up tags */
1670   }
1671   /* find the longest non-stopword */
1672   for(n = 0; n < nwordlist; ++n)
1673     if(!stopword(w[n]) && !checktag(w[n]))
1674       if(!best || strlen(w[n]) > strlen(best))
1675         best = w[n];
1676   /* TODO: we should at least in principal be able to identify the word or tag
1677    * with the least matches in log time, and choose that as our primary search
1678    * term. */
1679   if(ntags && !best) {
1680     /* Only tags are listed.  We limit to the first and narrow down with the
1681      * rest. */
1682     best = checktag(w[0]);
1683     db = trackdb_tagsdb;
1684     dbname = "tags";
1685   } else if(best) {
1686     /* We can limit to some word. */
1687     db = trackdb_searchdb;
1688     dbname = "search";
1689   } else {
1690     /* Only stopwords */
1691     return 0;
1692   }
1693   vector_init(&u);
1694   vector_init(&v);
1695   for(;;) {
1696     tid = trackdb_begin_transaction();
1697     /* find all the tracks that have that word */
1698     make_key(&k, best);
1699     prepare_data(&d);
1700     what = DB_SET;
1701     v.nvec = 0;
1702     cursor = trackdb_opencursor(db, tid);
1703     while(!(err = cursor->c_get(cursor, &k, &d, what))) {
1704       vector_append(&v, xstrndup(d.data, d.size));
1705       what = DB_NEXT_DUP;
1706     }
1707     switch(err) {
1708     case DB_NOTFOUND:
1709       err = 0;
1710       break;
1711     case DB_LOCK_DEADLOCK:
1712       error(0, "error querying %s database: %s", dbname, db_strerror(err));
1713       break;
1714     default:
1715       fatal(0, "error querying %s database: %s", dbname, db_strerror(err));
1716     }
1717     if(trackdb_closecursor(cursor)) err = DB_LOCK_DEADLOCK;
1718     cursor = 0;
1719     /* do a naive search over that (hopefuly fairly small) list of tracks */
1720     u.nvec = 0;
1721     for(n = 0; n < v.nvec; ++n) {
1722       if((err = gettrackdata(v.vec[n], 0, &p, 0, 0, tid) == DB_LOCK_DEADLOCK))
1723         goto fail;
1724       else if(err) {
1725         error(0, "track %s unexpected error: %s", v.vec[n], db_strerror(err));
1726         continue;
1727       }
1728       twords = track_to_words(v.vec[n], p);
1729       tags = parsetags(kvp_get(p, "tags"));
1730       for(i = 0; i < nwordlist; ++i) {
1731         if((tag = checktag(w[i]))) {
1732           /* Track must have this tag */
1733           for(j = 0; tags[j]; ++j)
1734             if(!strcmp(tag, tags[j])) break; /* tag found */
1735           if(!tags[j]) break;           /* tag not found */
1736         } else {
1737           /* Track must contain this word */
1738           for(j = 0; twords[j]; ++j)
1739             if(!strcmp(w[i], twords[j])) break; /* word found */
1740           if(!twords[j]) break;         /* word not found */
1741         }
1742       }
1743       if(i >= nwordlist)                /* all words found */
1744         vector_append(&u, v.vec[n]);
1745     }
1746     break;
1747   fail:
1748     trackdb_closecursor(cursor);
1749     cursor = 0;
1750     trackdb_abort_transaction(tid);
1751     info("retrying search");
1752   }
1753   trackdb_commit_transaction(tid);
1754   vector_terminate(&u);
1755   if(ntracks)
1756     *ntracks = u.nvec;
1757   return u.vec;
1758 }
1759
1760 /* trackdb_scan **************************************************************/
1761
1762 int trackdb_scan(const char *root,
1763                  int (*callback)(const char *track,
1764                                  struct kvp *data,
1765                                  void *u,
1766                                  DB_TXN *tid),
1767                  void *u,
1768                  DB_TXN *tid) {
1769   DBC *cursor;
1770   DBT k, d;
1771   const size_t root_len = root ? strlen(root) : 0;
1772   int err, cberr;
1773   struct kvp *data;
1774   const char *track;
1775
1776   cursor = trackdb_opencursor(trackdb_tracksdb, tid);
1777   if(root)
1778     err = cursor->c_get(cursor, make_key(&k, root), prepare_data(&d),
1779                         DB_SET_RANGE);
1780   else {
1781     memset(&k, 0, sizeof k);
1782     err = cursor->c_get(cursor, &k, prepare_data(&d),
1783                         DB_FIRST);
1784   }
1785   while(!err) {
1786     if(!root
1787        || (k.size > root_len
1788            && !strncmp(k.data, root, root_len)
1789            && ((char *)k.data)[root_len] == '/')) {
1790       data = kvp_urldecode(d.data, d.size);
1791       if(kvp_get(data, "_path")) {
1792         track = xstrndup(k.data, k.size);
1793         /* Advance to the next track before the callback so that the callback
1794          * may safely delete the track */
1795         err = cursor->c_get(cursor, &k, &d, DB_NEXT);
1796         if((cberr = callback(track, data, u, tid))) {
1797           err = cberr;
1798           break;
1799         }
1800       } else
1801         err = cursor->c_get(cursor, &k, &d, DB_NEXT);
1802     } else
1803       break;
1804   }
1805   trackdb_closecursor(cursor);
1806   switch(err) {
1807   case EINTR:
1808     return err;
1809   case 0:
1810   case DB_NOTFOUND:
1811     return 0;
1812   case DB_LOCK_DEADLOCK:
1813     error(0, "c->c_get: %s", db_strerror(err));
1814     return err;
1815   default:
1816     fatal(0, "c->c_get: %s", db_strerror(err));
1817   }
1818 }
1819
1820 /* trackdb_rescan ************************************************************/
1821
1822 /* called when the rescanner terminates */
1823 static int reap_rescan(ev_source attribute((unused)) *ev,
1824                        pid_t pid,
1825                        int status,
1826                        const struct rusage attribute((unused)) *rusage,
1827                        void attribute((unused)) *u) {
1828   if(pid == rescan_pid) rescan_pid = -1;
1829   if(status)
1830     error(0, "disorderd-rescan: %s", wstat(status));
1831   else
1832     D(("disorderd-rescan terminate: %s", wstat(status)));
1833   /* Our cache of file lookups is out of date now */
1834   cache_clean(&cache_files_type);
1835   eventlog("rescanned", (char *)0);
1836   return 0;
1837 }
1838
1839 void trackdb_rescan(ev_source *ev) {
1840   if(rescan_pid != -1) {
1841     error(0, "rescan already underway");
1842     return;
1843   }
1844   rescan_pid = subprogram(ev, RESCAN, -1);
1845   ev_child(ev, rescan_pid, 0, reap_rescan, 0);
1846   D(("started rescanner"));
1847   
1848 }
1849
1850 int trackdb_rescan_cancel(void) {
1851   if(rescan_pid == -1) return 0;
1852   if(kill(rescan_pid, SIGTERM) < 0)
1853     fatal(errno, "error killing rescanner");
1854   rescan_pid = -1;
1855   return 1;
1856 }
1857
1858 /* global prefs **************************************************************/
1859
1860 void trackdb_set_global(const char *name,
1861                         const char *value,
1862                         const char *who) {
1863   DB_TXN *tid;
1864   DBT k, d;
1865   int err;
1866   int state;
1867
1868   memset(&k, 0, sizeof k);
1869   memset(&d, 0, sizeof d);
1870   k.data = (void *)name;
1871   k.size = strlen(name);
1872   if(value) {
1873     d.data = (void *)value;
1874     d.size = strlen(value);
1875   }
1876   for(;;) {
1877     tid = trackdb_begin_transaction();
1878     if(value)
1879       err = trackdb_globaldb->put(trackdb_globaldb, tid, &k, &d, 0);
1880     else
1881       err = trackdb_globaldb->del(trackdb_globaldb, tid, &k, 0);
1882     if(!err || err == DB_NOTFOUND) break;
1883     if(err != DB_LOCK_DEADLOCK)
1884       fatal(0, "error updating database: %s", db_strerror(err));
1885     trackdb_abort_transaction(tid);
1886   }
1887   trackdb_commit_transaction(tid);
1888   /* log important state changes */
1889   if(!strcmp(name, "playing")) {
1890     state = !value || !strcmp(value, "yes");
1891     info("playing %s by %s",
1892          state ? "enabled" : "disabled",
1893          who ? who : "-");
1894     eventlog("state", state ? "enable_play" : "disable_play", (char *)0);
1895   }
1896   if(!strcmp(name, "random-play")) {
1897     state = !value || !strcmp(value, "yes");
1898     info("random play %s by %s",
1899          state ? "enabled" : "disabled",
1900          who ? who : "-");
1901     eventlog("state", state ? "enable_random" : "disable_random", (char *)0);
1902   }
1903   if(!strcmp(name, "required-tags"))
1904     reqtracks = 0;
1905 }
1906
1907 const char *trackdb_get_global(const char *name) {
1908   DB_TXN *tid;
1909   int err;
1910   const char *r;
1911
1912   for(;;) {
1913     tid = trackdb_begin_transaction();
1914     if(!(err = trackdb_get_global_tid(name, tid, &r)))
1915       break;
1916     trackdb_abort_transaction(tid);
1917   }
1918   trackdb_commit_transaction(tid);
1919   return r;
1920 }
1921
1922 static int trackdb_get_global_tid(const char *name,
1923                                   DB_TXN *tid,
1924                                   const char **rp) {
1925   DBT k, d;
1926   int err;
1927
1928   memset(&k, 0, sizeof k);
1929   k.data = (void *)name;
1930   k.size = strlen(name);
1931   switch(err = trackdb_globaldb->get(trackdb_globaldb, tid, &k,
1932                                      prepare_data(&d), 0)) {
1933   case 0:
1934     *rp = xstrndup(d.data, d.size);
1935     return 0;
1936   case DB_NOTFOUND:
1937     *rp = 0;
1938     return 0;
1939   case DB_LOCK_DEADLOCK:
1940     return err;
1941   default:
1942     fatal(0, "error updating database: %s", db_strerror(err));
1943   }
1944 }
1945
1946 /** @brief Retrieve the most recently added tracks
1947  * @param ntracksp Where to put count, or 0
1948  * @param maxtracks Maximum number of tracks to retrieve
1949  * @return null-terminated array of track names
1950  *
1951  * The most recently added track is first in the array.
1952  */
1953 char **trackdb_new(int *ntracksp,
1954                    int maxtracks) {
1955   DB_TXN *tid;
1956   char **tracks;
1957
1958   for(;;) {
1959     tid = trackdb_begin_transaction();
1960     tracks = trackdb_new_tid(ntracksp, maxtracks, tid);
1961     if(tracks)
1962       break;
1963     trackdb_abort_transaction(tid);
1964   }
1965   trackdb_commit_transaction(tid);
1966   return tracks;
1967 }
1968
1969 /** @brief Retrieve the most recently added tracks
1970  * @param ntracksp Where to put count, or 0
1971  * @param maxtracks Maximum number of tracks to retrieve, or 0 for all
1972  * @param tid Transaction ID
1973  * @return null-terminated array of track names, or NULL on deadlock
1974  *
1975  * The most recently added track is first in the array.
1976  */
1977 static char **trackdb_new_tid(int *ntracksp,
1978                               int maxtracks,
1979                               DB_TXN *tid) {
1980   DBC *c;
1981   DBT k, d;
1982   int err = 0;
1983   struct vector tracks[1];
1984
1985   vector_init(tracks);
1986   c = trackdb_opencursor(trackdb_noticeddb, tid);
1987   while((maxtracks <= 0 || tracks->nvec < maxtracks)
1988         && !(err = c->c_get(c, prepare_data(&k), prepare_data(&d), DB_PREV)))
1989     vector_append(tracks, xstrndup(d.data, d.size));
1990   switch(err) {
1991   case 0:                               /* hit maxtracks */
1992   case DB_NOTFOUND:                     /* ran out of tracks */
1993     break;
1994   case DB_LOCK_DEADLOCK:
1995     trackdb_closecursor(c);
1996     return 0;
1997   default:
1998     fatal(0, "error reading noticed.db: %s", db_strerror(err));
1999   }
2000   if((err = trackdb_closecursor(c)))
2001     return 0;                           /* deadlock */
2002   vector_terminate(tracks);
2003   if(ntracksp)
2004     *ntracksp = tracks->nvec;
2005   return tracks->vec;
2006 }
2007
2008 /** @brief Expire noticed.db
2009  * @param earliest Earliest timestamp to keep
2010  */
2011 void trackdb_expire_noticed(time_t earliest) {
2012   DB_TXN *tid;
2013
2014   for(;;) {
2015     tid = trackdb_begin_transaction();
2016     if(!trackdb_expire_noticed_tid(earliest, tid))
2017       break;
2018     trackdb_abort_transaction(tid);
2019   }
2020   trackdb_commit_transaction(tid);
2021 }
2022
2023 /** @brief Expire noticed.db
2024  * @param earliest Earliest timestamp to keep
2025  * @param tid Transaction ID
2026  * @return 0 or DB_LOCK_DEADLOCK
2027  */
2028 static int trackdb_expire_noticed_tid(time_t earliest, DB_TXN *tid) {
2029   DBC *c;
2030   DBT k, d;
2031   int err = 0, ret;
2032   time_t when;
2033   uint32_t *kk;
2034   int count = 0;
2035
2036   c = trackdb_opencursor(trackdb_noticeddb, tid);
2037   while(!(err = c->c_get(c, prepare_data(&k), prepare_data(&d), DB_NEXT))) {
2038     kk = k.data;
2039     when = (time_t)(((uint64_t)ntohl(kk[0]) << 32) + ntohl(kk[1]));
2040     if(when >= earliest)
2041       break;
2042     if((err = c->c_del(c, 0))) {
2043       if(err != DB_LOCK_DEADLOCK)
2044         fatal(0, "error deleting expired noticed.db entry: %s",
2045               db_strerror(err));
2046       break;
2047     }
2048     ++count;
2049   }
2050   if(err == DB_NOTFOUND)
2051     err = 0;
2052   if(err && err != DB_LOCK_DEADLOCK)
2053     fatal(0, "error expiring noticed.db: %s", db_strerror(err));
2054   ret = err;
2055   if((err = trackdb_closecursor(c))) {
2056     if(err != DB_LOCK_DEADLOCK)
2057       fatal(0, "error closing cursor: %s", db_strerror(err));
2058     ret = err;
2059   }
2060   if(!ret && count)
2061     info("expired %d tracks from noticed.db", count);
2062   return ret;
2063 }
2064
2065 /* tidying up ****************************************************************/
2066
2067 void trackdb_gc(void) {
2068   int err;
2069   char **logfiles;
2070
2071   if((err = trackdb_env->txn_checkpoint(trackdb_env,
2072                                         config->checkpoint_kbyte,
2073                                         config->checkpoint_min,
2074                                         0)))
2075     fatal(0, "trackdb_env->txn_checkpoint: %s", db_strerror(err));
2076   if((err = trackdb_env->log_archive(trackdb_env, &logfiles, DB_ARCH_REMOVE)))
2077     fatal(0, "trackdb_env->log_archive: %s", db_strerror(err));
2078   /* This makes catastrophic recovery impossible.  However, the user can still
2079    * preserve the important data by using disorder-dump to snapshot their
2080    * prefs, and later to restore it.  This is likely to have much small
2081    * long-term storage requirements than record the db logfiles. */
2082 }
2083
2084 /*
2085 Local Variables:
2086 c-basic-offset:2
2087 comment-column:40
2088 fill-column:79
2089 indent-tabs-mode:nil
2090 End:
2091 */