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