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