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