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