X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/0fc2fcd010ac5cc98aab67222f3c2af2ec2beefe..6ebc4527c6a103d0532c08744fb916f951018413:/lib/trackdb.c?ds=sidebyside diff --git a/lib/trackdb.c b/lib/trackdb.c index 1786239..628498a 100644 --- a/lib/trackdb.c +++ b/lib/trackdb.c @@ -59,6 +59,7 @@ #include "unidata.h" #include "base64.h" #include "sendmail.h" +#include "validity.h" #define RESCAN "disorder-rescan" #define DEADLOCK "disorder-deadlock" @@ -246,7 +247,7 @@ void trackdb_init(int flags) { ++initialized; if(home) { if(strcmp(home, config->home)) - fatal(0, "cannot change db home without server restart"); + disorder_fatal(0, "cannot change db home without server restart"); home = config->home; } @@ -264,14 +265,14 @@ void trackdb_init(int flags) { * The socket, not being a regular file, is excepted. */ if(!(dp = opendir(config->home))) - fatal(errno, "error reading %s", config->home); + disorder_fatal(errno, "error reading %s", config->home); while((de = readdir(dp))) { byte_xasprintf(&p, "%s/%s", config->home, de->d_name); if(lstat(p, &st) == 0 && S_ISREG(st.st_mode) && (st.st_mode & 077)) { if(chmod(p, st.st_mode & 07700) < 0) - fatal(errno, "cannot chmod %s", p); + disorder_fatal(errno, "cannot chmod %s", p); } xfree(p); } @@ -279,15 +280,15 @@ void trackdb_init(int flags) { } /* create environment */ - if((err = db_env_create(&trackdb_env, 0))) fatal(0, "db_env_create: %s", - db_strerror(err)); + if((err = db_env_create(&trackdb_env, 0))) + disorder_fatal(0, "db_env_create: %s", db_strerror(err)); if((err = trackdb_env->set_alloc(trackdb_env, xmalloc_noptr, xrealloc_noptr, xfree))) - fatal(0, "trackdb_env->set_alloc: %s", db_strerror(err)); + disorder_fatal(0, "trackdb_env->set_alloc: %s", db_strerror(err)); if((err = trackdb_env->set_lk_max_locks(trackdb_env, 10000))) - fatal(0, "trackdb_env->set_lk_max_locks: %s", db_strerror(err)); + disorder_fatal(0, "trackdb_env->set_lk_max_locks: %s", db_strerror(err)); if((err = trackdb_env->set_lk_max_objects(trackdb_env, 10000))) - fatal(0, "trackdb_env->set_lk_max_objects: %s", db_strerror(err)); + disorder_fatal(0, "trackdb_env->set_lk_max_objects: %s", db_strerror(err)); if((err = trackdb_env->open(trackdb_env, config->home, DB_INIT_LOG |DB_INIT_LOCK @@ -296,7 +297,8 @@ void trackdb_init(int flags) { |DB_CREATE |recover_type[recover], 0600))) - fatal(0, "trackdb_env->open %s: %s", config->home, db_strerror(err)); + disorder_fatal(0, "trackdb_env->open %s: %s", + config->home, db_strerror(err)); trackdb_env->set_errpfx(trackdb_env, "DB"); trackdb_env->set_errfile(trackdb_env, stderr); trackdb_env->set_verbose(trackdb_env, DB_VERB_DEADLOCK, 1); @@ -313,8 +315,8 @@ static int reap_db_deadlock(ev_source attribute((unused)) *ev, void attribute((unused)) *u) { db_deadlock_pid = -1; if(initialized) - fatal(0, "deadlock manager unexpectedly terminated: %s", - wstat(status)); + disorder_fatal(0, "deadlock manager unexpectedly terminated: %s", + wstat(status)); else D(("deadlock manager terminated: %s", wstat(status))); return 0; @@ -361,12 +363,12 @@ static pid_t subprogram(ev_source *ev, int outputfd, const char *prog, } /* ensure we don't leak privilege anywhere */ if(setuid(geteuid()) < 0) - fatal(errno, "error calling setuid"); + disorder_fatal(errno, "error calling setuid"); /* If we were negatively niced, undo it. We don't bother checking for * error, it's not that important. */ setpriority(PRIO_PROCESS, 0, 0); execvp(prog, (char **)args); - fatal(errno, "error invoking %s", prog); + disorder_fatal(errno, "error invoking %s", prog); } return pid; } @@ -399,7 +401,7 @@ static void terminate_and_wait(ev_source *ev, if(pid == -1) return; if(kill(pid, SIGTERM) < 0) - fatal(errno, "error killing %s", what); + disorder_fatal(errno, "error killing %s", what); /* wait for the rescanner to finish */ while(waitpid(pid, &err, 0) == -1 && errno == EINTR) ; @@ -419,7 +421,7 @@ void trackdb_deinit(ev_source *ev) { /* close the environment */ if((err = trackdb_env->close(trackdb_env, 0))) - fatal(0, "trackdb_env->close: %s", db_strerror(err)); + disorder_fatal(0, "trackdb_env->close: %s", db_strerror(err)); terminate_and_wait(ev, rescan_pid, "disorder-rescan"); rescan_pid = -1; @@ -460,22 +462,22 @@ static DB *open_db(const char *path, D(("open %s", path)); path = config_get_file(path); if((err = db_create(&db, trackdb_env, 0))) - fatal(0, "db_create %s: %s", path, db_strerror(err)); + disorder_fatal(0, "db_create %s: %s", path, db_strerror(err)); if(dbflags) if((err = db->set_flags(db, dbflags))) - fatal(0, "db->set_flags %s: %s", path, db_strerror(err)); + disorder_fatal(0, "db->set_flags %s: %s", path, db_strerror(err)); if(dbtype == DB_BTREE) if((err = db->set_bt_compare(db, compare))) - fatal(0, "db->set_bt_compare %s: %s", path, db_strerror(err)); + disorder_fatal(0, "db->set_bt_compare %s: %s", path, db_strerror(err)); if((err = db->open(db, 0, path, 0, dbtype, openflags | DB_AUTO_COMMIT, mode))) { if((openflags & DB_CREATE) || errno != ENOENT) { if((err2 = db->close(db, 0))) - error(0, "db->close: %s", db_strerror(err2)); + disorder_error(0, "db->close: %s", db_strerror(err2)); trackdb_close(); trackdb_env->close(trackdb_env,0); trackdb_env = 0; - fatal(0, "db->open %s: %s", path, db_strerror(err)); + disorder_fatal(0, "db->open %s: %s", path, db_strerror(err)); } db->close(db, 0); db = 0; @@ -511,32 +513,32 @@ void trackdb_open(int flags) { s = trackdb_get_global("_dbversion"); /* Close the database again, we'll open it property below */ if((err = trackdb_globaldb->close(trackdb_globaldb, 0))) - fatal(0, "error closing global.db: %s", db_strerror(err)); + disorder_fatal(0, "error closing global.db: %s", db_strerror(err)); trackdb_globaldb = 0; /* Convert version string to an integer */ oldversion = s ? atol(s) : 1; if(oldversion > config->dbversion) { /* Database is from the future; we never allow this. */ - fatal(0, "this version of DisOrder is too old for database version %ld", - oldversion); + disorder_fatal(0, "this version of DisOrder is too old for database version %ld", + oldversion); } if(oldversion < config->dbversion) { /* Database version is out of date */ switch(flags & TRACKDB_UPGRADE_MASK) { case TRACKDB_NO_UPGRADE: /* This database needs upgrading but this is not permitted */ - fatal(0, "database needs upgrading from %ld to %ld", - oldversion, config->dbversion); + disorder_fatal(0, "database needs upgrading from %ld to %ld", + oldversion, config->dbversion); case TRACKDB_CAN_UPGRADE: /* This database needs upgrading */ - info("invoking disorder-dbupgrade to upgrade from %ld to %ld", + disorder_info("invoking disorder-dbupgrade to upgrade from %ld to %ld", oldversion, config->dbversion); pid = subprogram(0, -1, "disorder-dbupgrade", (char *)0); while(waitpid(pid, &err, 0) == -1 && errno == EINTR) ; if(err) - fatal(0, "disorder-dbupgrade %s", wstat(err)); - info("disorder-dbupgrade succeeded"); + disorder_fatal(0, "disorder-dbupgrade %s", wstat(err)); + disorder_info("disorder-dbupgrade succeeded"); break; case TRACKDB_OPEN_FOR_UPGRADE: break; @@ -546,13 +548,13 @@ void trackdb_open(int flags) { } if(oldversion == config->dbversion && (flags & TRACKDB_OPEN_FOR_UPGRADE)) { /* This doesn't make any sense */ - fatal(0, "database is already at current version"); + disorder_fatal(0, "database is already at current version"); } trackdb_existing_database = 1; } else { if(flags & TRACKDB_OPEN_FOR_UPGRADE) { /* Cannot upgrade a new database */ - fatal(0, "cannot upgrade a database that does not exist"); + disorder_fatal(0, "cannot upgrade a database that does not exist"); } /* This is a brand new database */ trackdb_existing_database = 0; @@ -560,7 +562,7 @@ void trackdb_open(int flags) { /* open the databases */ if(!(trackdb_usersdb = open_db("users.db", 0, DB_HASH, dbflags, 0600))) - fatal(0, "cannot open users.db"); + disorder_fatal(0, "cannot open users.db"); trackdb_tracksdb = open_db("tracks.db", DB_RECNUM, DB_BTREE, dbflags, 0666); trackdb_searchdb = open_db("search.db", @@ -591,10 +593,10 @@ void trackdb_close(void) { /* sanity checks */ assert(opened == 1); --opened; -#define CLOSE(N, V) do { \ - if(V && (err = V->close(V, 0))) \ - fatal(0, "error closing %s: %s", N, db_strerror(err)); \ - V = 0; \ +#define CLOSE(N, V) do { \ + if(V && (err = V->close(V, 0))) \ + disorder_fatal(0, "error closing %s: %s", N, db_strerror(err)); \ + V = 0; \ } while(0) CLOSE("tracks.db", trackdb_tracksdb); CLOSE("search.db", trackdb_searchdb); @@ -635,10 +637,10 @@ int trackdb_getdata(DB *db, *kp = 0; return err; case DB_LOCK_DEADLOCK: - error(0, "error querying database: %s", db_strerror(err)); + disorder_error(0, "error querying database: %s", db_strerror(err)); return err; default: - fatal(0, "error querying database: %s", db_strerror(err)); + disorder_fatal(0, "error querying database: %s", db_strerror(err)); } } @@ -664,10 +666,10 @@ int trackdb_putdata(DB *db, case DB_KEYEXIST: return err; case DB_LOCK_DEADLOCK: - error(0, "error updating database: %s", db_strerror(err)); + disorder_error(0, "error updating database: %s", db_strerror(err)); return err; default: - fatal(0, "error updating database: %s", db_strerror(err)); + disorder_fatal(0, "error updating database: %s", db_strerror(err)); } } @@ -688,10 +690,10 @@ int trackdb_delkey(DB *db, case DB_NOTFOUND: return 0; case DB_LOCK_DEADLOCK: - error(0, "error updating database: %s", db_strerror(err)); + disorder_error(0, "error updating database: %s", db_strerror(err)); return err; default: - fatal(0, "error updating database: %s", db_strerror(err)); + disorder_fatal(0, "error updating database: %s", db_strerror(err)); } } @@ -706,7 +708,7 @@ DBC *trackdb_opencursor(DB *db, DB_TXN *tid) { switch(err = db->cursor(db, tid, &c, 0)) { case 0: break; - default: fatal(0, "error creating cursor: %s", db_strerror(err)); + default: disorder_fatal(0, "error creating cursor: %s", db_strerror(err)); } return c; } @@ -723,10 +725,10 @@ int trackdb_closecursor(DBC *c) { case 0: return err; case DB_LOCK_DEADLOCK: - error(0, "error closing cursor: %s", db_strerror(err)); + disorder_error(0, "error closing cursor: %s", db_strerror(err)); return err; default: - fatal(0, "error closing cursor: %s", db_strerror(err)); + disorder_fatal(0, "error closing cursor: %s", db_strerror(err)); } } @@ -759,19 +761,19 @@ int trackdb_delkeydata(DB *db, err = 0; break; case DB_LOCK_DEADLOCK: - error(0, "error updating database: %s", db_strerror(err)); + disorder_error(0, "error updating database: %s", db_strerror(err)); break; default: - fatal(0, "c->c_del: %s", db_strerror(err)); + disorder_fatal(0, "c->c_del: %s", db_strerror(err)); } break; case DB_NOTFOUND: break; case DB_LOCK_DEADLOCK: - error(0, "error updating database: %s", db_strerror(err)); + disorder_error(0, "error updating database: %s", db_strerror(err)); break; default: - fatal(0, "c->c_get: %s", db_strerror(err)); + disorder_fatal(0, "c->c_get: %s", db_strerror(err)); } if(trackdb_closecursor(c)) err = DB_LOCK_DEADLOCK; return err; @@ -785,7 +787,7 @@ DB_TXN *trackdb_begin_transaction(void) { int err; if((err = trackdb_env->txn_begin(trackdb_env, 0, &tid, 0))) - fatal(0, "trackdb_env->txn_begin: %s", db_strerror(err)); + disorder_fatal(0, "trackdb_env->txn_begin: %s", db_strerror(err)); return tid; } @@ -799,7 +801,7 @@ void trackdb_abort_transaction(DB_TXN *tid) { if(tid) if((err = tid->abort(tid))) - fatal(0, "tid->abort: %s", db_strerror(err)); + disorder_fatal(0, "tid->abort: %s", db_strerror(err)); } /** @brief Commit transaction @@ -809,7 +811,7 @@ void trackdb_commit_transaction(DB_TXN *tid) { int err; if((err = tid->commit(tid, 0))) - fatal(0, "tid->commit: %s", db_strerror(err)); + disorder_fatal(0, "tid->commit: %s", db_strerror(err)); } /* search/tags shared code ***************************************************/ @@ -872,10 +874,10 @@ static int register_word(DB *db, const char *what, case DB_KEYEXIST: return 0; case DB_LOCK_DEADLOCK: - error(0, "error updating %s.db: %s", what, db_strerror(err)); + disorder_error(0, "error updating %s.db: %s", what, db_strerror(err)); return err; default: - fatal(0, "error updating %s.db: %s", what, db_strerror(err)); + disorder_fatal(0, "error updating %s.db: %s", what, db_strerror(err)); } } @@ -1214,7 +1216,8 @@ static int gettrackdata(const char *track, if((err = trackdb_getdata(trackdb_tracksdb, track, &t, tid))) goto done; if((actual = kvp_get(t, "_alias_for"))) { if(flags & GTD_NOALIAS) { - error(0, "alias passed to gettrackdata where real path required"); + disorder_error(0, + "alias passed to gettrackdata where real path required"); abort(); } if((err = trackdb_getdata(trackdb_tracksdb, actual, &t, tid))) goto done; @@ -1326,7 +1329,8 @@ int trackdb_notice_tid(const char *track, make_key(&data, track), 0)) { case 0: break; case DB_LOCK_DEADLOCK: return err; - default: fatal(0, "error updating noticed.db: %s", db_strerror(err)); + default: + disorder_fatal(0, "error updating noticed.db: %s", db_strerror(err)); } } return ret; @@ -1448,10 +1452,10 @@ static int get_stats(struct vector *v, case 0: break; case DB_LOCK_DEADLOCK: - error(0, "error querying database: %s", db_strerror(err)); + disorder_error(0, "error querying database: %s", db_strerror(err)); return err; default: - fatal(0, "error querying database: %s", db_strerror(err)); + disorder_fatal(0, "error querying database: %s", db_strerror(err)); } for(n = 0; n < nsi; ++n) { byte_xasprintf(&str, "%s=%"PRIuMAX, si[n].name, @@ -1533,10 +1537,10 @@ static int search_league(struct vector *v, int count, DB_TXN *tid) { err = 0; break; case DB_LOCK_DEADLOCK: - error(0, "error querying search database: %s", db_strerror(err)); + disorder_error(0, "error querying search database: %s", db_strerror(err)); break; default: - fatal(0, "error querying search database: %s", db_strerror(err)); + disorder_fatal(0, "error querying search database: %s", db_strerror(err)); } if(trackdb_closecursor(cursor)) err = DB_LOCK_DEADLOCK; if(err) return err; @@ -1637,7 +1641,7 @@ static int stats_finished(ev_source attribute((unused)) *ev, d->exited = 1; if(status) - error(0, "disorder-stats %s", wstat(status)); + disorder_error(0, "disorder-stats %s", wstat(status)); stats_complete(d); char *k; byte_xasprintf(&k, "%lu", (unsigned long)pid); @@ -1681,7 +1685,7 @@ static int stats_error(ev_source attribute((unused)) *ev, void *u) { struct stats_details *const d = u; - error(errno_value, "error reading from pipe to disorder-stats"); + disorder_error(errno_value, "error reading from pipe to disorder-stats"); d->closed = 1; stats_complete(d); return 0; @@ -1711,7 +1715,7 @@ void trackdb_stats_subprocess(ev_source *ev, ev_child(ev, pid, 0, stats_finished, d); if(!ev_reader_new(ev, p[0], stats_read, stats_error, d, "disorder-stats reader")) - fatal(0, "ev_reader_new for disorder-stats reader failed"); + disorder_fatal(0, "ev_reader_new for disorder-stats reader failed"); /* Remember the PID */ if(!stats_pids) stats_pids = hash_new(1); @@ -1910,7 +1914,7 @@ fail: } /** @brief Resolve an alias - * @param Track name (might be an alias) + * @param track Track name (might be an alias) * @return Real track name (definitely not an alias) or NULL if no such track */ const char *trackdb_resolve(const char *track) { @@ -1993,7 +1997,7 @@ int trackdb_listkeys(DB *db, struct vector *v, DB_TXN *tid) { case DB_LOCK_DEADLOCK: return e; default: - fatal(0, "c->c_get: %s", db_strerror(e)); + disorder_fatal(0, "c->c_get: %s", db_strerror(e)); } if((e = trackdb_closecursor(c))) return e; @@ -2002,6 +2006,13 @@ int trackdb_listkeys(DB *db, struct vector *v, DB_TXN *tid) { } /* return 1 iff sorted tag lists A and B have at least one member in common */ +/** @brief Detect intersecting tag lists + * @param a First list of tags (NULL-terminated) + * @param b Second list of tags (NULL-terminated) + * @return 1 if @p a and @p b have at least one member in common + * + * @p a and @p must be sorted. + */ int tag_intersection(char **a, char **b) { int cmp; @@ -2014,6 +2025,13 @@ int tag_intersection(char **a, char **b) { return 0; } +/** @brief Called when disorder-choose might have completed + * @param ev Event loop + * @param which @ref CHOOSE_RUNNING or @ref CHOOSE_READING + * + * Once called with both @p which values, @ref choose_callback is called + * (usually chosen_random_track()). + */ static void choose_finished(ev_source *ev, unsigned which) { choose_complete |= which; if(choose_complete != (CHOOSE_RUNNING|CHOOSE_READING)) @@ -2026,20 +2044,35 @@ static void choose_finished(ev_source *ev, unsigned which) { choose_callback(ev, 0); } -/** @brief Called when @c disorder-choose terminates */ +/** @brief Called when @c disorder-choose terminates + * @param ev Event loop + * @param pid Process ID + * @param status Exit status + * @param rusage Resource usage + * @param u User data + * @return 0 + */ static int choose_exited(ev_source *ev, pid_t attribute((unused)) pid, int status, const struct rusage attribute((unused)) *rusage, void attribute((unused)) *u) { if(status) - error(0, "disorder-choose %s", wstat(status)); + disorder_error(0, "disorder-choose %s", wstat(status)); choose_status = status; choose_finished(ev, CHOOSE_RUNNING); return 0; } -/** @brief Called with data from @c disorder-choose pipe */ +/** @brief Called with data from @c disorder-choose pipe + * @param ev Event loop + * @param reader Reader state + * @param ptr Data read + * @param bytes Number of bytes read + * @param eof Set at end of file + * @param u User data + * @return 0 + */ static int choose_readable(ev_source *ev, ev_reader *reader, void *ptr, @@ -2053,10 +2086,16 @@ static int choose_readable(ev_source *ev, return 0; } +/** @brief Called when @c disorder-choose pipe errors + * @param ev Event loop + * @param errno_value Error code + * @param u User data + * @return 0 + */ static int choose_read_error(ev_source *ev, int errno_value, void attribute((unused)) *u) { - error(errno_value, "error reading disorder-choose pipe"); + disorder_error(errno_value, "error reading disorder-choose pipe"); choose_finished(ev, CHOOSE_READING); return 0; } @@ -2090,13 +2129,21 @@ int trackdb_request_random(ev_source *ev, choose_complete = 0; if(!ev_reader_new(ev, p[0], choose_readable, choose_read_error, 0, "disorder-choose reader")) /* owns p[0] */ - fatal(0, "ev_reader_new for disorder-choose reader failed"); + disorder_fatal(0, "ev_reader_new for disorder-choose reader failed"); ev_child(ev, choose_pid, 0, choose_exited, 0); /* owns the subprocess */ return 0; } -/* get a track name given the prefs. Set *used_db to 1 if we got the answer - * from the prefs. */ +/** @brief Get a track name part, using prefs + * @param track Track name + * @param context Context ("display" etc) + * @param part Part ("album" etc) + * @param p Preference + * @param used_db Set if a preference is used + * @return Name part (never NULL) + * + * Used by compute_alias() and trackdb_getpart(). + */ static const char *getpart(const char *track, const char *context, const char *part, @@ -2114,8 +2161,14 @@ static const char *getpart(const char *track, return result; } -/* get a track name part, like trackname_part(), but taking the database into - * account. */ +/** @brief Get a track name part + * @param track Track name + * @param context Context ("display" etc) + * @param part Part ("album" etc) + * @return Name part (never NULL) + * + * This is interface used by c_part(). + */ const char *trackdb_getpart(const char *track, const char *context, const char *part) { @@ -2139,7 +2192,12 @@ fail: return getpart(actual, context, part, p, &used_db); } -/* get the raw path name for @track@ (might be an alias) */ +/** @brief Get the raw (filesystem) path for @p track + * @param track track Track name (can be an alias) + * @return Raw path (never NULL) + * + * The raw path is the actual bytes that came out of readdir() etc. + */ const char *trackdb_rawpath(const char *track) { DB_TXN *tid; struct kvp *t; @@ -2165,6 +2223,19 @@ fail: /* return true if the basename of TRACK[0..TL-1], as defined by DL, matches RE. * If RE is a null pointer then it matches everything. */ +/** @brief Match a track against a rgeexp + * @param dl Length of directory part of track + * @param track Track name + * @param tl Length of track name + * @param re Regular expression or NULL + * @return Nonzero on match + * + * @p tl is the total length of @p track, @p dl is the length of the directory + * part (the index of the final "/"). The subject of the regexp match is the + * basename, i.e. the part after @p dl. + * + * If @p re is NULL then always matches. + */ static int track_matches(size_t dl, const char *track, size_t tl, const pcre *re) { int ovec[3], rc; @@ -2177,13 +2248,21 @@ static int track_matches(size_t dl, const char *track, size_t tl, case PCRE_ERROR_NOMATCH: return 0; default: if(rc < 0) { - error(0, "pcre_exec returned %d, subject '%s'", rc, track); + disorder_error(0, "pcre_exec returned %d, subject '%s'", rc, track); return 0; } return 1; } } +/** @brief Generate a list of tracks and/or directories in @p dir + * @param v Where to put results + * @param dir Directory to list + * @param what Bitmap of objects to return + * @param re Regexp to filter matches (or NULL to accept all) + * @param tid Owning transaction + * @return 0 or DB_LOCK_DEADLOCK + */ static int do_list(struct vector *v, const char *dir, enum trackdb_listable what, const pcre *re, DB_TXN *tid) { DBC *cursor; @@ -2265,17 +2344,23 @@ static int do_list(struct vector *v, const char *dir, err = 0; break; case DB_LOCK_DEADLOCK: - error(0, "error querying database: %s", db_strerror(err)); + disorder_error(0, "error querying database: %s", db_strerror(err)); break; default: - fatal(0, "error querying database: %s", db_strerror(err)); + disorder_fatal(0, "error querying database: %s", db_strerror(err)); } deadlocked: if(trackdb_closecursor(cursor)) err = DB_LOCK_DEADLOCK; return err; } -/* return the directories or files below @dir@ */ +/** @brief Get the directories or files below @p dir + * @param dir Directory to list + * @param np Where to put number of results (or NULL) + * @param what Bitmap of objects to return + * @param re Regexp to filter matches (or NULL to accept all) + * @return List of tracks + */ char **trackdb_list(const char *dir, int *np, enum trackdb_listable what, const pcre *re) { DB_TXN *tid; @@ -2305,7 +2390,12 @@ fail: return v.vec; } -/* If S is tag:something, return something. Else return 0. */ +/** @brief Detect a tag element in a search string + * @param s Element of search string + * @return Pointer to tag name (in @p s) if this is a tag: search, else NULL + * + * Tag searches take the form "tag:TAG". + */ static const char *checktag(const char *s) { if(!strncmp(s, "tag:", 4)) return s + 4; @@ -2394,10 +2484,12 @@ char **trackdb_search(char **wordlist, int nwordlist, int *ntracks) { err = 0; break; case DB_LOCK_DEADLOCK: - error(0, "error querying %s database: %s", dbname, db_strerror(err)); + disorder_error(0, "error querying %s database: %s", + dbname, db_strerror(err)); break; default: - fatal(0, "error querying %s database: %s", dbname, db_strerror(err)); + disorder_fatal(0, "error querying %s database: %s", + dbname, db_strerror(err)); } if(trackdb_closecursor(cursor)) err = DB_LOCK_DEADLOCK; cursor = 0; @@ -2407,7 +2499,8 @@ char **trackdb_search(char **wordlist, int nwordlist, int *ntracks) { if((err = gettrackdata(v.vec[n], 0, &p, 0, 0, tid) == DB_LOCK_DEADLOCK)) goto fail; else if(err) { - error(0, "track %s unexpected error: %s", v.vec[n], db_strerror(err)); + disorder_error(0, "track %s unexpected error: %s", + v.vec[n], db_strerror(err)); continue; } twords = track_to_words(v.vec[n], p); @@ -2434,7 +2527,7 @@ char **trackdb_search(char **wordlist, int nwordlist, int *ntracks) { trackdb_closecursor(cursor); cursor = 0; trackdb_abort_transaction(tid); - info("retrying search"); + disorder_info("retrying search"); } trackdb_commit_transaction(tid); vector_terminate(&u); @@ -2445,6 +2538,17 @@ char **trackdb_search(char **wordlist, int nwordlist, int *ntracks) { /* trackdb_scan **************************************************************/ +/** @brief Visit every track + * @param root Root to scan or NULL for all + * @param callback Callback for each track + * @param u Passed to @p callback + * @param tid Owning transaction + * @return 0, DB_LOCK_DEADLOCK or EINTR + * + * Visits every track and calls @p callback. @p callback will get the track + * data and preferences and should return 0 to continue scanning or EINTR to + * stop. + */ int trackdb_scan(const char *root, int (*callback)(const char *track, struct kvp *data, @@ -2494,11 +2598,11 @@ int trackdb_scan(const char *root, prefs = 0; break; case DB_LOCK_DEADLOCK: - error(0, "getting prefs: %s", db_strerror(err)); + disorder_error(0, "getting prefs: %s", db_strerror(err)); trackdb_closecursor(cursor); return err; default: - fatal(0, "getting prefs: %s", db_strerror(err)); + disorder_fatal(0, "getting prefs: %s", db_strerror(err)); } /* Advance to the next track before the callback so that the callback * may safely delete the track */ @@ -2520,10 +2624,10 @@ int trackdb_scan(const char *root, case DB_NOTFOUND: return 0; case DB_LOCK_DEADLOCK: - error(0, "c->c_get: %s", db_strerror(err)); + disorder_error(0, "c->c_get: %s", db_strerror(err)); return err; default: - fatal(0, "c->c_get: %s", db_strerror(err)); + disorder_fatal(0, "c->c_get: %s", db_strerror(err)); } } @@ -2559,7 +2663,7 @@ static int reap_rescan(ev_source attribute((unused)) *ev, void attribute((unused)) *u) { if(pid == rescan_pid) rescan_pid = -1; if(status) - error(0, RESCAN": %s", wstat(status)); + disorder_error(0, RESCAN": %s", wstat(status)); else D((RESCAN" terminated: %s", wstat(status))); /* Our cache of file lookups is out of date now */ @@ -2589,7 +2693,7 @@ void trackdb_rescan(ev_source *ev, int recheck, if(rescan_pid != -1) { trackdb_add_rescanned(rescanned, ru); - error(0, "rescan already underway"); + disorder_error(0, "rescan already underway"); return; } rescan_pid = subprogram(ev, -1, RESCAN, @@ -2607,10 +2711,13 @@ void trackdb_rescan(ev_source *ev, int recheck, } } +/** @brief Cancel a rescan + * @return Nonzero if a rescan was cancelled + */ int trackdb_rescan_cancel(void) { if(rescan_pid == -1) return 0; if(kill(rescan_pid, SIGTERM) < 0) - fatal(errno, "error killing rescanner"); + disorder_fatal(errno, "error killing rescanner"); rescan_pid = -1; return 1; } @@ -2622,6 +2729,11 @@ int trackdb_rescan_underway(void) { /* global prefs **************************************************************/ +/** @brief Set a global preference + * @param name Global preference name + * @param value New value + * @param who Who is setting it + */ void trackdb_set_global(const char *name, const char *value, const char *who) { @@ -2639,20 +2751,25 @@ void trackdb_set_global(const char *name, /* log important state changes */ if(!strcmp(name, "playing")) { state = !value || !strcmp(value, "yes"); - info("playing %s by %s", - state ? "enabled" : "disabled", - who ? who : "-"); + disorder_info("playing %s by %s", + state ? "enabled" : "disabled", + who ? who : "-"); eventlog("state", state ? "enable_play" : "disable_play", (char *)0); } if(!strcmp(name, "random-play")) { state = !value || !strcmp(value, "yes"); - info("random play %s by %s", - state ? "enabled" : "disabled", - who ? who : "-"); + disorder_info("random play %s by %s", + state ? "enabled" : "disabled", + who ? who : "-"); eventlog("state", state ? "enable_random" : "disable_random", (char *)0); } } +/** @brief Set a global preference + * @param name Global preference name + * @param value New value + * @param tid Owning transaction + */ int trackdb_set_global_tid(const char *name, const char *value, DB_TXN *tid) { @@ -2673,10 +2790,14 @@ int trackdb_set_global_tid(const char *name, err = trackdb_globaldb->del(trackdb_globaldb, tid, &k, 0); if(err == DB_LOCK_DEADLOCK) return err; if(err) - fatal(0, "error updating database: %s", db_strerror(err)); + disorder_fatal(0, "error updating database: %s", db_strerror(err)); return 0; } +/** @brief Get a global preference + * @param name Global preference name + * @return Value of global preference, or NULL if it's not set + */ const char *trackdb_get_global(const char *name) { DB_TXN *tid; int err; @@ -2692,6 +2813,12 @@ const char *trackdb_get_global(const char *name) { return r; } +/** @brief Get a global preference + * @param name Global preference name + * @param tid Owning transaction + * @param rp Where to store value (will get NULL if preference not set) + * @return 0 or DB_LOCK_DEADLOCK + */ int trackdb_get_global_tid(const char *name, DB_TXN *tid, const char **rp) { @@ -2712,7 +2839,7 @@ int trackdb_get_global_tid(const char *name, case DB_LOCK_DEADLOCK: return err; default: - fatal(0, "error reading database: %s", db_strerror(err)); + disorder_fatal(0, "error reading database: %s", db_strerror(err)); } } @@ -2780,7 +2907,7 @@ static char **trackdb_new_tid(int *ntracksp, trackdb_closecursor(c); return 0; default: - fatal(0, "error reading noticed.db: %s", db_strerror(err)); + disorder_fatal(0, "error reading noticed.db: %s", db_strerror(err)); } if((err = trackdb_closecursor(c))) return 0; /* deadlock */ @@ -2826,8 +2953,8 @@ static int trackdb_expire_noticed_tid(time_t earliest, DB_TXN *tid) { break; if((err = c->c_del(c, 0))) { if(err != DB_LOCK_DEADLOCK) - fatal(0, "error deleting expired noticed.db entry: %s", - db_strerror(err)); + disorder_fatal(0, "error deleting expired noticed.db entry: %s", + db_strerror(err)); break; } ++count; @@ -2835,20 +2962,24 @@ static int trackdb_expire_noticed_tid(time_t earliest, DB_TXN *tid) { if(err == DB_NOTFOUND) err = 0; if(err && err != DB_LOCK_DEADLOCK) - fatal(0, "error expiring noticed.db: %s", db_strerror(err)); + disorder_fatal(0, "error expiring noticed.db: %s", db_strerror(err)); ret = err; if((err = trackdb_closecursor(c))) { if(err != DB_LOCK_DEADLOCK) - fatal(0, "error closing cursor: %s", db_strerror(err)); + disorder_fatal(0, "error closing cursor: %s", db_strerror(err)); ret = err; } if(!ret && count) - info("expired %d tracks from noticed.db", count); + disorder_info("expired %d tracks from noticed.db", count); return ret; } /* tidying up ****************************************************************/ +/** @brief Do database garbage collection + * + * Called form periodic_database_gc(). + */ void trackdb_gc(void) { int err; char **logfiles; @@ -2857,9 +2988,9 @@ void trackdb_gc(void) { config->checkpoint_kbyte, config->checkpoint_min, 0))) - fatal(0, "trackdb_env->txn_checkpoint: %s", db_strerror(err)); + disorder_fatal(0, "trackdb_env->txn_checkpoint: %s", db_strerror(err)); if((err = trackdb_env->log_archive(trackdb_env, &logfiles, DB_ARCH_REMOVE))) - fatal(0, "trackdb_env->log_archive: %s", db_strerror(err)); + disorder_fatal(0, "trackdb_env->log_archive: %s", db_strerror(err)); /* This makes catastrophic recovery impossible. However, the user can still * preserve the important data by using disorder-dump to snapshot their * prefs, and later to restore it. This is likely to have much small @@ -2868,7 +2999,12 @@ void trackdb_gc(void) { /* user database *************************************************************/ -/** @brief Return true if @p user is trusted */ +/** @brief Return true if @p user is trusted + * @param user User to look up + * @return Nonzero if they are in the 'trusted' list + * + * Now used only in upgrade from old versions. + */ static int trusted(const char *user) { int n; @@ -2878,31 +3014,16 @@ static int trusted(const char *user) { return n < config->trust.n; } -/** @brief Return non-zero for a valid username - * - * Currently we only allow the letters and digits in ASCII. We could be more - * liberal than this but it is a nice simple test. It is critical that - * semicolons are never allowed. - * - * NB also used by playlist_parse_name() to validate playlist names! +/** @brief Add a user + * @param user Username + * @param password Initial password or NULL + * @param rights Initial rights + * @param email Email address or NULL + * @param confirmation Confirmation string to require + * @param tid Owning transaction + * @param flags DB flags e.g. DB_NOOVERWRITE + * @return 0, DB_KEYEXIST or DB_LOCK_DEADLOCK */ -int valid_username(const char *user) { - if(!*user) - return 0; - while(*user) { - const uint8_t c = *user++; - /* For now we are very strict */ - if((c >= 'a' && c <= 'z') - || (c >= 'A' && c <= 'Z') - || (c >= '0' && c <= '9')) - /* ok */; - else - return 0; - } - return 1; -} - -/** @brief Add a user */ static int create_user(const char *user, const char *password, const char *rights, @@ -2915,11 +3036,11 @@ static int create_user(const char *user, /* sanity check user */ if(!valid_username(user)) { - error(0, "invalid username '%s'", user); + disorder_error(0, "invalid username '%s'", user); return -1; } if(parse_rights(rights, 0, 1)) { - error(0, "invalid rights string"); + disorder_error(0, "invalid rights string"); return -1; } /* data for this user */ @@ -2935,14 +3056,21 @@ static int create_user(const char *user, return trackdb_putdata(trackdb_usersdb, user, k, tid, flags); } -/** @brief Add one pre-existing user */ +/** @brief Add one pre-existing user + * @param user Username + * @param password password + * @param tid Owning transaction + * @return 0, DB_KEYEXIST or DB_LOCK_DEADLOCK + * + * Used only in upgrade from old versions. + */ static int one_old_user(const char *user, const char *password, DB_TXN *tid) { const char *rights; /* www-data doesn't get added */ if(!strcmp(user, "www-data")) { - info("not adding www-data to user database"); + disorder_info("not adding www-data to user database"); return 0; } /* pick rights */ @@ -2962,6 +3090,10 @@ static int one_old_user(const char *user, const char *password, tid, DB_NOOVERWRITE); } +/** @brief Upgrade old users + * @param tid Owning transaction + * @return 0 or DB_LOCK_DEADLOCK + */ static int trackdb_old_users_tid(DB_TXN *tid) { int n; @@ -2969,10 +3101,11 @@ static int trackdb_old_users_tid(DB_TXN *tid) { switch(one_old_user(config->allow.s[n].s[0], config->allow.s[n].s[1], tid)) { case 0: - info("created user %s from 'allow' directive", config->allow.s[n].s[0]); + disorder_info("created user %s from 'allow' directive", + config->allow.s[n].s[0]); break; case DB_KEYEXIST: - error(0, "user %s already exists, delete 'allow' directive", + disorder_error(0, "user %s already exists, delete 'allow' directive", config->allow.s[n].s[0]); /* This won't ever become fatal - eventually 'allow' will be * disabled. */ @@ -3006,7 +3139,7 @@ void trackdb_create_root(void) { 0/*email*/, 0/*confirmation*/, tid, DB_NOOVERWRITE)); if(e == 0) - info("created root user"); + disorder_info("created root user"); } /** @brief Find a user's password from the database @@ -3047,14 +3180,15 @@ int trackdb_adduser(const char *user, WITH_TRANSACTION(create_user(user, password, rights, email, confirmation, tid, DB_NOOVERWRITE)); if(e) { - error(0, "cannot create user '%s' because they already exist", user); + disorder_error(0, "cannot create user '%s' because they already exist", + user); return -1; } else { if(email) - info("created user '%s' with rights '%s' and email address '%s'", - user, rights, email); + disorder_info("created user '%s' with rights '%s' and email address '%s'", + user, rights, email); else - info("created user '%s' with rights '%s'", user, rights); + disorder_info("created user '%s' with rights '%s'", user, rights); eventlog("user_add", user, (char *)0); return 0; } @@ -3069,10 +3203,11 @@ int trackdb_deluser(const char *user) { WITH_TRANSACTION(trackdb_delkey(trackdb_usersdb, user, tid)); if(e) { - error(0, "cannot delete user '%s' because they do not exist", user); + disorder_error(0, "cannot delete user '%s' because they do not exist", + user); return -1; } - info("deleted user '%s'", user); + disorder_info("deleted user '%s'", user); eventlog("user_delete", user, (char *)0); return 0; } @@ -3126,32 +3261,33 @@ int trackdb_edituserinfo(const char *user, if(!strcmp(key, "rights")) { if(!value) { - error(0, "cannot remove 'rights' key from user '%s'", user); + disorder_error(0, "cannot remove 'rights' key from user '%s'", user); return -1; } if(parse_rights(value, 0, 1)) { - error(0, "invalid rights string"); + disorder_error(0, "invalid rights string"); return -1; } } else if(!strcmp(key, "email")) { if(*value) { if(!email_valid(value)) { - error(0, "invalid email address '%s' for user '%s'", value, user); + disorder_error(0, "invalid email address '%s' for user '%s'", + value, user); return -1; } } else value = 0; /* no email -> remove key */ } else if(!strcmp(key, "created")) { - error(0, "cannot change creation date for user '%s'", user); + disorder_error(0, "cannot change creation date for user '%s'", user); return -1; } else if(strcmp(key, "password") && !strcmp(key, "confirmation")) { - error(0, "unknown user info key '%s' for user '%s'", key, user); + disorder_error(0, "unknown user info key '%s' for user '%s'", key, user); return -1; } WITH_TRANSACTION(trackdb_edituserinfo_tid(user, key, value, tid)); if(e) { - error(0, "unknown user '%s'", user); + disorder_error(0, "unknown user '%s'", user); return -1; } else { eventlog("user_edit", user, key, (char *)0); @@ -3189,18 +3325,18 @@ static int trackdb_confirm_tid(const char *user, const char *confirmation, if((e = trackdb_getdata(trackdb_usersdb, user, &k, tid))) return e; if(!(stored_confirmation = kvp_get(k, "confirmation"))) { - error(0, "already confirmed user '%s'", user); + disorder_error(0, "already confirmed user '%s'", user); /* DB claims -30,800 to -30,999 so -1 should be a safe bet */ return -1; } if(!(rights = kvp_get(k, "rights"))) { - error(0, "no rights for unconfirmed user '%s'", user); + disorder_error(0, "no rights for unconfirmed user '%s'", user); return -1; } if(parse_rights(rights, rightsp, 1)) return -1; if(strcmp(confirmation, stored_confirmation)) { - error(0, "wrong confirmation string for user '%s'", user); + disorder_error(0, "wrong confirmation string for user '%s'", user); return -1; } /* 'sall good */ @@ -3221,11 +3357,11 @@ int trackdb_confirm(const char *user, const char *confirmation, WITH_TRANSACTION(trackdb_confirm_tid(user, confirmation, rightsp, tid)); switch(e) { case 0: - info("registration confirmed for user '%s'", user); + disorder_info("registration confirmed for user '%s'", user); eventlog("user_confirm", user, (char *)0); return 0; case DB_NOTFOUND: - error(0, "confirmation for nonexistent user '%s'", user); + disorder_error(0, "confirmation for nonexistent user '%s'", user); return -1; default: /* already reported */ return -1;