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