chiark / gitweb /
relax config file checking for non-server programs
[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
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   if(value) {
964     /* TODO: if value matches default then set value=0 */
965   }
966   
967   for(;;) {
968     tid = trackdb_begin_transaction();
969     if((err = gettrackdata(track, &t, &p, 0,
970                            0, tid)) == DB_LOCK_DEADLOCK)
971       goto fail;
972     if(err == DB_NOTFOUND) break;
973     if(name[0] == '_') {
974       if(kvp_set(&t, name, value))
975         if(trackdb_putdata(trackdb_tracksdb, track, t, tid, 0))
976           goto fail;
977     } else {
978       /* get the old alias name */
979       if(compute_alias(&oldalias, track, p, tid)) goto fail;
980       /* get the old tags */
981       if(!strcmp(name, "tags"))
982         oldtags = parsetags(kvp_get(p, "tags"));
983       /* set the value */
984       if(kvp_set(&p, name, value))
985         if(trackdb_putdata(trackdb_prefsdb, track, p, tid, 0))
986           goto fail;
987       /* compute the new alias name */
988       if((err = compute_alias(&newalias, track, p, tid))) goto fail;
989       /* check whether alias has changed */
990       if(!(oldalias == newalias
991            || (oldalias && newalias && !strcmp(oldalias, newalias)))) {
992         /* adjust alias records to fit change */
993         if(oldalias
994            && trackdb_delkey(trackdb_tracksdb, oldalias, tid)) goto fail;
995         if(newalias) {
996           a = 0;
997           kvp_set(&a, "_alias_for", track);
998           if(trackdb_putdata(trackdb_tracksdb, newalias, a, tid, 0)) goto fail;
999         }
1000       }
1001       /* check whether tags have changed */
1002       if(!strcmp(name, "tags")) {
1003         newtags = parsetags(value);
1004         while(*oldtags || *newtags) {
1005           if(*oldtags && *newtags) {
1006             cmp = strcmp(*oldtags, *newtags);
1007             if(!cmp) {
1008               /* keeping this tag */
1009               ++oldtags;
1010               ++newtags;
1011             } else if(cmp < 0)
1012               /* old tag fits into a gap in the new list, so delete old */
1013               goto delete_old;
1014             else
1015               /* new tag fits into a gap in the old list, so insert new */
1016               goto insert_new;
1017           } else if(*oldtags) {
1018             /* we've run out of new tags, so remaining old ones are to be
1019              * deleted */
1020           delete_old:
1021             if(trackdb_delkeydata(trackdb_tagsdb,
1022                                   *oldtags, track, tid) == DB_LOCK_DEADLOCK)
1023               goto fail;
1024             ++oldtags;
1025           } else {
1026             /* we've run out of old tags, so remainig new ones are to be
1027              * inserted */
1028           insert_new:
1029             if(register_tag(track, *newtags, tid)) goto fail;
1030             ++newtags;
1031           }
1032         }
1033         reqtracks = 0;
1034       }
1035     }
1036     err = 0;
1037     break;
1038 fail:
1039     trackdb_abort_transaction(tid);
1040   }
1041   trackdb_commit_transaction(tid);
1042   return err == 0 ? 0 : -1;
1043 }
1044
1045 /* get a pref */
1046 const char *trackdb_get(const char *track,
1047                         const char *name) {
1048   return kvp_get(trackdb_get_all(track), name);
1049 }
1050
1051 /* get all prefs as a 0-terminated array */
1052 struct kvp *trackdb_get_all(const char *track) {
1053   struct kvp *t, *p, **pp;
1054   DB_TXN *tid;
1055
1056   for(;;) {
1057     tid = trackdb_begin_transaction();
1058     if(gettrackdata(track, &t, &p, 0, 0, tid) == DB_LOCK_DEADLOCK)
1059       goto fail;
1060     break;
1061 fail:
1062     trackdb_abort_transaction(tid);
1063   }
1064   trackdb_commit_transaction(tid);
1065   for(pp = &p; *pp; pp = &(*pp)->next)
1066     ;
1067   *pp = t;
1068   return p;
1069 }
1070
1071 /* resolve alias */
1072 const char *trackdb_resolve(const char *track) {
1073   DB_TXN *tid;
1074   const char *actual;
1075   
1076   for(;;) {
1077     tid = trackdb_begin_transaction();
1078     if(gettrackdata(track, 0, 0, &actual, 0, tid) == DB_LOCK_DEADLOCK)
1079       goto fail;
1080     break;
1081 fail:
1082     trackdb_abort_transaction(tid);
1083   }
1084   trackdb_commit_transaction(tid);
1085   return actual;
1086 }
1087
1088 int trackdb_isalias(const char *track) {
1089   const char *actual = trackdb_resolve(track);
1090
1091   return strcmp(actual, track);
1092 }
1093
1094 /* test whether a track exists (perhaps an alias) */
1095 int trackdb_exists(const char *track) {
1096   DB_TXN *tid;
1097   int err;
1098
1099   for(;;) {
1100     tid = trackdb_begin_transaction();
1101     /* unusually, here we want the return value */
1102     if((err = gettrackdata(track, 0, 0, 0, 0, tid)) == DB_LOCK_DEADLOCK)
1103       goto fail;
1104     break;
1105 fail:
1106     trackdb_abort_transaction(tid);
1107   }
1108   trackdb_commit_transaction(tid);
1109   return (err == 0);
1110 }
1111
1112 /* return the list of tags */
1113 char **trackdb_alltags(void) {
1114   DB_TXN *tid;
1115   int err;
1116   char **taglist;
1117
1118   for(;;) {
1119     tid = trackdb_begin_transaction();
1120     err = trackdb_alltags_tid(tid, &taglist);
1121     if(!err) break;
1122     trackdb_abort_transaction(tid);
1123   }
1124   trackdb_commit_transaction(tid);
1125   return taglist;
1126 }
1127
1128 static int trackdb_alltags_tid(DB_TXN *tid, char ***taglistp) {
1129   struct vector v;
1130   DBC *c;
1131   DBT k, d;
1132   int err;
1133
1134   vector_init(&v);
1135   c = trackdb_opencursor(trackdb_tagsdb, tid);
1136   memset(&k, 0, sizeof k);
1137   while(!(err = c->c_get(c, &k, prepare_data(&d), DB_NEXT_NODUP)))
1138     vector_append(&v, xstrndup(k.data, k.size));
1139   switch(err) {
1140   case DB_NOTFOUND:
1141     break;
1142   case DB_LOCK_DEADLOCK:
1143       return err;
1144   default:
1145     fatal(0, "c->c_get: %s", db_strerror(err));
1146   }
1147   if((err = trackdb_closecursor(c))) return err;
1148   vector_terminate(&v);
1149   *taglistp = v.vec;
1150   return 0;
1151 }
1152
1153 /* return 1 iff sorted tag lists A and B have at least one member in common */
1154 static int tag_intersection(char **a, char **b) {
1155   int cmp;
1156
1157   /* Same sort of logic as trackdb_set() above */
1158   while(*a && *b) {
1159     if(!(cmp = strcmp(*a, *b))) return 1;
1160     else if(cmp < 0) ++a;
1161     else ++b;
1162   }
1163   return 0;
1164 }
1165
1166 /* Check whether a track is suitable for random play.  Returns 0 if it is,
1167  * DB_NOTFOUND if it is not or DB_LOCK_DEADLOCK if the database gave us
1168  * that. */
1169 static int check_suitable(const char *track,
1170                           DB_TXN *tid,
1171                           char **required_tags,
1172                           char **prohibited_tags) {
1173   char **track_tags;
1174   time_t last, now;
1175   struct kvp *p, *t;
1176   const char *pick_at_random, *played_time;
1177
1178   /* don't pick tracks that aren't in any surviving collection (for instance
1179    * you've edited the config but the rescan hasn't done its job yet) */
1180   if(!find_track_root(track)) {
1181     info("found track not in any collection: %s", track);
1182     return DB_NOTFOUND;
1183   }
1184   /* don't pick aliases - only pick the canonical form */
1185   if(gettrackdata(track, &t, &p, 0, 0, tid) == DB_LOCK_DEADLOCK)
1186     return DB_LOCK_DEADLOCK;
1187   if(kvp_get(t, "_alias_for"))
1188     return DB_NOTFOUND;
1189   /* check that random play is not suppressed for this track */
1190   if((pick_at_random = kvp_get(p, "pick_at_random"))
1191      && !strcmp(pick_at_random, "0"))
1192     return DB_NOTFOUND;
1193   /* don't pick a track that's been played in the last 8 hours */
1194   if((played_time = kvp_get(p, "played_time"))) {
1195     last = atoll(played_time);
1196     now = time(0);
1197     if(now < last + 8 * 3600)       /* TODO configurable */
1198       return DB_NOTFOUND;
1199   }
1200   track_tags = parsetags(kvp_get(p, "tags"));
1201   /* check that no prohibited tag is present for this track */
1202   if(prohibited_tags && tag_intersection(track_tags, prohibited_tags))
1203     return DB_NOTFOUND;
1204   /* check that at least one required tags is present for this track */
1205   if(*required_tags && !tag_intersection(track_tags, required_tags))
1206     return DB_NOTFOUND;
1207   return 0;
1208 }
1209
1210 /* attempt to pick a random non-alias track */
1211 const char *trackdb_random(int tries) {
1212   DBT key, data;
1213   DB_BTREE_STAT *sp;
1214   int err, n;
1215   DB_TXN *tid;
1216   const char *track, *candidate;
1217   db_recno_t r;
1218   const char *tags;
1219   char **required_tags, **prohibited_tags, **tp;
1220   hash *h;
1221   DBC *c = 0;
1222
1223   for(;;) {
1224     tid = trackdb_begin_transaction();
1225     if((err = trackdb_get_global_tid("required-tags", tid, &tags)))
1226       goto fail;
1227     required_tags = parsetags(tags);
1228     if((err = trackdb_get_global_tid("prohibited-tags", tid, &tags)))
1229       goto fail;
1230     prohibited_tags = parsetags(tags);
1231     track = 0;
1232     if(*required_tags) {
1233       /* Bung all the suitable tracks into a hash and convert to a list of keys
1234        * (to eliminate duplicates).  We cache this list since it is possible
1235        * that it will be very large. */
1236       if(!reqtracks) {
1237         h = hash_new(0);
1238         for(tp = required_tags; *tp; ++tp) {
1239           c = trackdb_opencursor(trackdb_tagsdb, tid);
1240           memset(&key, 0, sizeof key);
1241           key.data = *tp;
1242           key.size = strlen(*tp);
1243           n = 0;
1244           err = c->c_get(c, &key, prepare_data(&data), DB_SET);
1245           while(err == 0) {
1246             hash_add(h, xstrndup(data.data, data.size), 0,
1247                      HASH_INSERT_OR_REPLACE);
1248             ++n;
1249             err = c->c_get(c, &key, prepare_data(&data), DB_NEXT_DUP);
1250           }
1251           switch(err) {
1252           case 0:
1253           case DB_NOTFOUND:
1254             break;
1255           case DB_LOCK_DEADLOCK:
1256             goto fail;
1257           default:
1258             fatal(0, "error querying tags.db: %s", db_strerror(err));
1259           }
1260           trackdb_closecursor(c);
1261           c = 0;
1262           if(!n)
1263             error(0, "required tag %s does not match any tracks", *tp);
1264         }
1265         nreqtracks = hash_count(h);
1266         reqtracks = hash_keys(h);
1267       }
1268       while(nreqtracks && !track && tries-- > 0) {
1269         r = (rand() * (double)nreqtracks / (RAND_MAX + 1.0));
1270         candidate = reqtracks[r];
1271         switch(check_suitable(candidate, tid,
1272                               required_tags, prohibited_tags)) {
1273         case 0:
1274           track = candidate;
1275           break;
1276         case DB_NOTFOUND:
1277           break;
1278         case DB_LOCK_DEADLOCK:
1279           goto fail;
1280         }
1281       }
1282     } else {
1283       /* No required tags.  We pick random record numbers in the database
1284        * instead. */
1285       switch(err = trackdb_tracksdb->stat(trackdb_tracksdb, tid, &sp, 0)) {
1286       case 0:
1287         break;
1288       case DB_LOCK_DEADLOCK:
1289         error(0, "error querying tracks.db: %s", db_strerror(err));
1290         goto fail;
1291       default:
1292         fatal(0, "error querying tracks.db: %s", db_strerror(err));
1293       }
1294       if(!sp->bt_nkeys)
1295         error(0, "cannot pick tracks at random from an empty database");
1296       while(sp->bt_nkeys && !track && tries-- > 0) {
1297         /* record numbers count from 1 upwards */
1298         r = 1 + (rand() * (double)sp->bt_nkeys / (RAND_MAX + 1.0));
1299         memset(&key, sizeof key, 0);
1300         key.flags = DB_DBT_MALLOC;
1301         key.size = sizeof r;
1302         key.data = &r;
1303         switch(err = trackdb_tracksdb->get(trackdb_tracksdb, tid, &key, prepare_data(&data),
1304                                            DB_SET_RECNO)) {
1305         case 0:
1306           break;
1307         case DB_LOCK_DEADLOCK:
1308           error(0, "error querying tracks.db: %s", db_strerror(err));
1309           goto fail;
1310         default:
1311           fatal(0, "error querying tracks.db: %s", db_strerror(err));
1312         }
1313         candidate = xstrndup(key.data, key.size);
1314         switch(check_suitable(candidate, tid,
1315                               required_tags, prohibited_tags)) {
1316         case 0:
1317           track = candidate;
1318           break;
1319         case DB_NOTFOUND:
1320           break;
1321         case DB_LOCK_DEADLOCK:
1322           goto fail;
1323         }
1324       }
1325     }
1326     break;
1327 fail:
1328     trackdb_closecursor(c);
1329     c = 0;
1330     trackdb_abort_transaction(tid);
1331   }
1332   trackdb_commit_transaction(tid);
1333   if(!track)
1334     error(0, "could not pick a random track");
1335   return track;
1336 }
1337
1338 /* get a track name given the prefs.  Set *used_db to 1 if we got the answer
1339  * from the prefs. */
1340 static const char *getpart(const char *track,
1341                            const char *context,
1342                            const char *part,
1343                            const struct kvp *p,
1344                            int *used_db) {
1345   const char *result;
1346   char *pref;
1347
1348   byte_xasprintf(&pref, "trackname_%s_%s", context, part);
1349   if((result = kvp_get(p, pref)))
1350     *used_db = 1;
1351   else
1352     result = trackname_part(track, context, part);
1353   assert(result != 0);
1354   return result;
1355 }
1356
1357 /* get a track name part, like trackname_part(), but taking the database into
1358  * account. */
1359 const char *trackdb_getpart(const char *track,
1360                             const char *context,
1361                             const char *part) {
1362   struct kvp *p;
1363   DB_TXN *tid;
1364   char *pref;
1365   const char *actual;
1366   int used_db, err;
1367
1368   /* construct the full pref */
1369   byte_xasprintf(&pref, "trackname_%s_%s", context, part);
1370   for(;;) {
1371     tid = trackdb_begin_transaction();
1372     if((err = gettrackdata(track, 0, &p, &actual, 0, tid)) == DB_LOCK_DEADLOCK)
1373       goto fail;
1374     break;
1375 fail:
1376     trackdb_abort_transaction(tid);
1377   }
1378   trackdb_commit_transaction(tid);
1379   return getpart(actual, context, part, p, &used_db);
1380 }
1381
1382 /* get the raw path name for @track@ (might be an alias) */
1383 const char *trackdb_rawpath(const char *track) {
1384   DB_TXN *tid;
1385   struct kvp *t;
1386   const char *path;
1387
1388   for(;;) {
1389     tid = trackdb_begin_transaction();
1390     if(gettrackdata(track, &t, 0, 0, 0, tid) == DB_LOCK_DEADLOCK)
1391       goto fail;
1392     break;
1393 fail:
1394     trackdb_abort_transaction(tid);
1395   }
1396   trackdb_commit_transaction(tid);
1397   if(!(path = kvp_get(t, "_path"))) path = track;
1398   return path;
1399 }
1400
1401 /* trackdb_list **************************************************************/
1402
1403 /* this is incredibly ugly, sorry, perhaps it will be rewritten to be actually
1404  * readable at some point */
1405
1406 /* return true if the basename of TRACK[0..TL-1], as defined by DL, matches RE.
1407  * If RE is a null pointer then it matches everything. */
1408 static int track_matches(size_t dl, const char *track, size_t tl,
1409                          const pcre *re) {
1410   int ovec[3], rc;
1411
1412   if(!re)
1413     return 1;
1414   track += dl + 1;
1415   tl -= (dl + 1);
1416   switch(rc = pcre_exec(re, 0, track, tl, 0, 0, ovec, 3)) {
1417   case PCRE_ERROR_NOMATCH: return 0;
1418   default:
1419     if(rc < 0) {
1420       error(0, "pcre_exec returned %d, subject '%s'", rc, track);
1421       return 0;
1422     }
1423     return 1;
1424   }
1425 }
1426
1427 static int do_list(struct vector *v, const char *dir,
1428                    enum trackdb_listable what, const pcre *re, DB_TXN *tid) {
1429   DBC *cursor;
1430   DBT k, d;
1431   size_t dl;
1432   char *ptr;
1433   int err;
1434   size_t l, last_dir_len = 0;
1435   char *last_dir = 0, *track, *alias;
1436   struct kvp *p;
1437   
1438   dl = strlen(dir);
1439   cursor = trackdb_opencursor(trackdb_tracksdb, tid);
1440   make_key(&k, dir);
1441   prepare_data(&d);
1442   /* find the first key >= dir */
1443   err = cursor->c_get(cursor, &k, &d, DB_SET_RANGE);
1444   /* keep going while we're dealing with <dir/anything> */
1445   while(err == 0
1446         && k.size > dl
1447         && ((char *)k.data)[dl] == '/'
1448         && !memcmp(k.data, dir, dl)) {
1449     ptr = memchr((char *)k.data + dl + 1, '/', k.size - (dl + 1));
1450     if(ptr) {
1451       /* we have <dir/component/anything>, so <dir/component> is a directory */
1452       l = ptr - (char *)k.data;
1453       if(what & trackdb_directories)
1454         if(!(last_dir
1455              && l == last_dir_len
1456              && !memcmp(last_dir, k.data, l))) {
1457           last_dir = xstrndup(k.data, last_dir_len = l);
1458           if(track_matches(dl, k.data, l, re))
1459             vector_append(v, last_dir);
1460         }
1461     } else {
1462       /* found a plain file */
1463       if((what & trackdb_files)) {
1464         track = xstrndup(k.data, k.size);
1465         if((err = trackdb_getdata(trackdb_prefsdb,
1466                                   track, &p, tid)) == DB_LOCK_DEADLOCK)
1467           goto deadlocked;
1468         /* if this file has an alias in the same directory then we skip it */
1469         if((err = compute_alias(&alias, track, p, tid)))
1470           goto deadlocked;
1471         if(!(alias && !strcmp(d_dirname(alias), d_dirname(track))))
1472           if(track_matches(dl, k.data, k.size, re))
1473             vector_append(v, track);
1474       }
1475     }
1476     err = cursor->c_get(cursor, &k, &d, DB_NEXT);
1477   }
1478   switch(err) {
1479   case 0:
1480     break;
1481   case DB_NOTFOUND:
1482     err = 0;
1483     break;
1484   case DB_LOCK_DEADLOCK:
1485     error(0, "error querying database: %s", db_strerror(err));
1486     break;
1487   default:
1488     fatal(0, "error querying database: %s", db_strerror(err));
1489   }
1490 deadlocked:
1491   if(trackdb_closecursor(cursor)) err = DB_LOCK_DEADLOCK;
1492   return err;
1493 }
1494
1495 /* return the directories or files below @dir@ */
1496 char **trackdb_list(const char *dir, int *np, enum trackdb_listable what,
1497                     const pcre *re) {
1498   DB_TXN *tid;
1499   int n;
1500   struct vector v;
1501
1502   vector_init(&v);
1503   for(;;) {
1504     tid = trackdb_begin_transaction();
1505     v.nvec = 0;
1506     if(dir) {
1507       if(do_list(&v, dir, what, re, tid))
1508         goto fail;
1509     } else {
1510       for(n = 0; n < config->collection.n; ++n)
1511         if(do_list(&v, config->collection.s[n].root, what, re, tid))
1512           goto fail;
1513     }
1514     break;
1515 fail:
1516     trackdb_abort_transaction(tid);
1517   }
1518   trackdb_commit_transaction(tid);
1519   vector_terminate(&v);
1520   if(np)
1521     *np = v.nvec;
1522   return v.vec;
1523 }  
1524
1525 /* If S is tag:something, return something.  Else return 0. */
1526 static const char *checktag(const char *s) {
1527   if(!strncmp(s, "tag:", 4))
1528     return s + 4;
1529   else
1530     return 0;
1531 }
1532
1533 /* return a list of tracks containing all of the words given.  If you
1534  * ask for only stopwords you get no tracks. */
1535 char **trackdb_search(char **wordlist, int nwordlist, int *ntracks) {
1536   const char **w, *best = 0, *tag;
1537   char **twords, **tags;
1538   int i, j, n, err, what;
1539   DBC *cursor = 0;
1540   DBT k, d;
1541   struct vector u, v;
1542   DB_TXN *tid;
1543   struct kvp *p;
1544   int ntags = 0;
1545   DB *db;
1546   const char *dbname;
1547
1548   *ntracks = 0;                         /* for early returns */
1549   /* casefold all the words */
1550   w = xmalloc(nwordlist * sizeof (char *));
1551   for(n = 0; n < nwordlist; ++n) {
1552     w[n] = casefold(wordlist[n]);
1553     if(checktag(w[n])) ++ntags;         /* count up tags */
1554   }
1555   /* find the longest non-stopword */
1556   for(n = 0; n < nwordlist; ++n)
1557     if(!stopword(w[n]) && !checktag(w[n]))
1558       if(!best || strlen(w[n]) > strlen(best))
1559         best = w[n];
1560   /* TODO: we should at least in principal be able to identify the word or tag
1561    * with the least matches in log time, and choose that as our primary search
1562    * term. */
1563   if(ntags && !best) {
1564     /* Only tags are listed.  We limit to the first and narrow down with the
1565      * rest. */
1566     best = checktag(w[0]);
1567     db = trackdb_tagsdb;
1568     dbname = "tags";
1569   } else if(best) {
1570     /* We can limit to some word. */
1571     db = trackdb_searchdb;
1572     dbname = "search";
1573   } else {
1574     /* Only stopwords */
1575     return 0;
1576   }
1577   vector_init(&u);
1578   vector_init(&v);
1579   for(;;) {
1580     tid = trackdb_begin_transaction();
1581     /* find all the tracks that have that word */
1582     make_key(&k, best);
1583     prepare_data(&d);
1584     what = DB_SET;
1585     v.nvec = 0;
1586     cursor = trackdb_opencursor(db, tid);
1587     while(!(err = cursor->c_get(cursor, &k, &d, what))) {
1588       vector_append(&v, xstrndup(d.data, d.size));
1589       what = DB_NEXT_DUP;
1590     }
1591     switch(err) {
1592     case DB_NOTFOUND:
1593       err = 0;
1594       break;
1595     case DB_LOCK_DEADLOCK:
1596       error(0, "error querying %s database: %s", dbname, db_strerror(err));
1597       break;
1598     default:
1599       fatal(0, "error querying %s database: %s", dbname, db_strerror(err));
1600     }
1601     if(trackdb_closecursor(cursor)) err = DB_LOCK_DEADLOCK;
1602     cursor = 0;
1603     /* do a naive search over that (hopefuly fairly small) list of tracks */
1604     u.nvec = 0;
1605     for(n = 0; n < v.nvec; ++n) {
1606       if((err = gettrackdata(v.vec[n], 0, &p, 0, 0, tid) == DB_LOCK_DEADLOCK))
1607         goto fail;
1608       else if(err) {
1609         error(0, "track %s unexpected error: %s", v.vec[n], db_strerror(err));
1610         continue;
1611       }
1612       twords = track_to_words(v.vec[n], p);
1613       tags = parsetags(kvp_get(p, "tags"));
1614       for(i = 0; i < nwordlist; ++i) {
1615         if((tag = checktag(w[i]))) {
1616           /* Track must have this tag */
1617           for(j = 0; tags[j]; ++j)
1618             if(!strcmp(tag, tags[j])) break; /* tag found */
1619           if(!tags[j]) break;           /* tag not found */
1620         } else {
1621           /* Track must contain this word */
1622           for(j = 0; twords[j]; ++j)
1623             if(!strcmp(w[i], twords[j])) break; /* word found */
1624           if(!twords[j]) break;         /* word not found */
1625         }
1626       }
1627       if(i >= nwordlist)                /* all words found */
1628         vector_append(&u, v.vec[n]);
1629     }
1630     break;
1631   fail:
1632     trackdb_closecursor(cursor);
1633     cursor = 0;
1634     trackdb_abort_transaction(tid);
1635     info("retrying search");
1636   }
1637   trackdb_commit_transaction(tid);
1638   vector_terminate(&u);
1639   if(ntracks)
1640     *ntracks = u.nvec;
1641   return u.vec;
1642 }
1643
1644 /* trackdb_scan **************************************************************/
1645
1646 int trackdb_scan(const char *root,
1647                  int (*callback)(const char *track,
1648                                  struct kvp *data,
1649                                  void *u,
1650                                  DB_TXN *tid),
1651                  void *u,
1652                  DB_TXN *tid) {
1653   DBC *cursor;
1654   DBT k, d;
1655   const size_t root_len = root ? strlen(root) : 0;
1656   int err, cberr;
1657   struct kvp *data;
1658   const char *track;
1659
1660   cursor = trackdb_opencursor(trackdb_tracksdb, tid);
1661   if(root)
1662     err = cursor->c_get(cursor, make_key(&k, root), prepare_data(&d),
1663                         DB_SET_RANGE);
1664   else {
1665     memset(&k, 0, sizeof k);
1666     err = cursor->c_get(cursor, &k, prepare_data(&d),
1667                         DB_FIRST);
1668   }
1669   while(!err) {
1670     if(!root
1671        || (k.size > root_len
1672            && !strncmp(k.data, root, root_len)
1673            && ((char *)k.data)[root_len] == '/')) {
1674       data = kvp_urldecode(d.data, d.size);
1675       if(kvp_get(data, "_path")) {
1676         track = xstrndup(k.data, k.size);
1677         /* Advance to the next track before the callback so that the callback
1678          * may safely delete the track */
1679         err = cursor->c_get(cursor, &k, &d, DB_NEXT);
1680         if((cberr = callback(track, data, u, tid))) {
1681           err = cberr;
1682           break;
1683         }
1684       } else
1685         err = cursor->c_get(cursor, &k, &d, DB_NEXT);
1686     } else
1687       break;
1688   }
1689   trackdb_closecursor(cursor);
1690   switch(err) {
1691   case EINTR:
1692     return err;
1693   case 0:
1694   case DB_NOTFOUND:
1695     return 0;
1696   case DB_LOCK_DEADLOCK:
1697     error(0, "c->c_get: %s", db_strerror(err));
1698     return err;
1699   default:
1700     fatal(0, "c->c_get: %s", db_strerror(err));
1701   }
1702 }
1703
1704 /* trackdb_rescan ************************************************************/
1705
1706 /* called when the rescanner terminates */
1707 static int reap_rescan(ev_source attribute((unused)) *ev,
1708                        pid_t pid,
1709                        int status,
1710                        const struct rusage attribute((unused)) *rusage,
1711                        void attribute((unused)) *u) {
1712   if(pid == rescan_pid) rescan_pid = -1;
1713   if(status)
1714     error(0, "disorderd-rescan: %s", wstat(status));
1715   else
1716     D(("disorderd-rescan terminate: %s", wstat(status)));
1717   /* Our cache of file lookups is out of date now */
1718   cache_clean(&cache_files_type);
1719   return 0;
1720 }
1721
1722 void trackdb_rescan(ev_source *ev) {
1723   if(rescan_pid != -1) {
1724     error(0, "rescan already underway");
1725     return;
1726   }
1727   rescan_pid = subprogram(ev, RESCAN);
1728   ev_child(ev, rescan_pid, 0, reap_rescan, 0);
1729   D(("started rescanner"));
1730   
1731 }
1732
1733 int trackdb_rescan_cancel(void) {
1734   if(rescan_pid == -1) return 0;
1735   if(kill(rescan_pid, SIGTERM) < 0)
1736     fatal(errno, "error killing rescanner");
1737   rescan_pid = -1;
1738   return 1;
1739 }
1740
1741 /* global prefs **************************************************************/
1742
1743 void trackdb_set_global(const char *name,
1744                         const char *value,
1745                         const char *who) {
1746   DB_TXN *tid;
1747   DBT k, d;
1748   int err;
1749   int state;
1750
1751   memset(&k, 0, sizeof k);
1752   memset(&d, 0, sizeof d);
1753   k.data = (void *)name;
1754   k.size = strlen(name);
1755   if(value) {
1756     d.data = (void *)value;
1757     d.size = strlen(value);
1758   }
1759   for(;;) {
1760     tid = trackdb_begin_transaction();
1761     if(value)
1762       err = trackdb_globaldb->put(trackdb_globaldb, tid, &k, &d, 0);
1763     else
1764       err = trackdb_globaldb->del(trackdb_globaldb, tid, &k, 0);
1765     if(!err || err == DB_NOTFOUND) break;
1766     if(err != DB_LOCK_DEADLOCK)
1767       fatal(0, "error updating database: %s", db_strerror(err));
1768     trackdb_abort_transaction(tid);
1769   }
1770   trackdb_commit_transaction(tid);
1771   /* log important state changes */
1772   if(!strcmp(name, "playing")) {
1773     state = !value || !strcmp(value, "yes");
1774     info("playing %s by %s",
1775          state ? "enabled" : "disabled",
1776          who ? who : "-");
1777     eventlog("state", state ? "enable_play" : "disable_play", (char *)0);
1778   }
1779   if(!strcmp(name, "random-play")) {
1780     state = !value || !strcmp(value, "yes");
1781     info("random play %s by %s",
1782          state ? "enabled" : "disabled",
1783          who ? who : "-");
1784     eventlog("state", state ? "enable_random" : "disable_random", (char *)0);
1785   }
1786   if(!strcmp(name, "required-tags"))
1787     reqtracks = 0;
1788 }
1789
1790 const char *trackdb_get_global(const char *name) {
1791   DB_TXN *tid;
1792   int err;
1793   const char *r;
1794
1795   for(;;) {
1796     tid = trackdb_begin_transaction();
1797     if(!(err = trackdb_get_global_tid(name, tid, &r)))
1798       break;
1799     trackdb_abort_transaction(tid);
1800   }
1801   trackdb_commit_transaction(tid);
1802   return r;
1803 }
1804
1805 static int trackdb_get_global_tid(const char *name,
1806                                   DB_TXN *tid,
1807                                   const char **rp) {
1808   DBT k, d;
1809   int err;
1810
1811   memset(&k, 0, sizeof k);
1812   k.data = (void *)name;
1813   k.size = strlen(name);
1814   switch(err = trackdb_globaldb->get(trackdb_globaldb, tid, &k,
1815                                      prepare_data(&d), 0)) {
1816   case 0:
1817     *rp = xstrndup(d.data, d.size);
1818     return 0;
1819   case DB_NOTFOUND:
1820     *rp = 0;
1821     return 0;
1822   case DB_LOCK_DEADLOCK:
1823     return err;
1824   default:
1825     fatal(0, "error updating database: %s", db_strerror(err));
1826   }
1827 }
1828
1829 /* tidying up ****************************************************************/
1830
1831 void trackdb_gc(void) {
1832   int err;
1833   char **logfiles;
1834
1835   if((err = trackdb_env->txn_checkpoint(trackdb_env,
1836                                         config->checkpoint_kbyte,
1837                                         config->checkpoint_min,
1838                                         0)))
1839     fatal(0, "trackdb_env->txn_checkpoint: %s", db_strerror(err));
1840   if((err = trackdb_env->log_archive(trackdb_env, &logfiles, DB_ARCH_REMOVE)))
1841     fatal(0, "trackdb_env->log_archive: %s", db_strerror(err));
1842   /* This makes catastrophic recovery impossible.  However, the user can still
1843    * preserve the important data by using disorder-dump to snapshot their
1844    * prefs, and later to restore it.  This is likely to have much small
1845    * long-term storage requirements than record the db logfiles. */
1846 }
1847
1848 /*
1849 Local Variables:
1850 c-basic-offset:2
1851 comment-column:40
1852 fill-column:79
1853 indent-tabs-mode:nil
1854 End:
1855 */