#include "unidata.h"
#include "base64.h"
#include "sendmail.h"
+#include "validity.h"
#define RESCAN "disorder-rescan"
#define DEADLOCK "disorder-deadlock"
++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;
}
* 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);
}
}
/* 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
|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);
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;
}
/* 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;
}
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)
;
/* 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;
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;
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;
}
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;
/* 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",
/* 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);
*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));
}
}
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));
}
}
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));
}
}
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;
}
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));
}
}
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;
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;
}
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
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 ***************************************************/
int m, n;
qsort(vec, nvec, sizeof (char *), wordcmp);
- m = n = 0;
+ m = 0;
if(nvec) {
vec[m++] = vec[0];
for(n = 1; n < nvec; ++n)
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));
}
}
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;
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;
#define H(name) { #name, offsetof(DB_HASH_STAT, name) }
#define B(name) { #name, offsetof(DB_BTREE_STAT, name) }
+/** @brief Table of libdb stats to return */
static const struct statinfo {
const char *name;
size_t offset;
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,
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;
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);
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;
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);
return 0;
}
-/* set a pref (remove if value=0) */
+/** @brief Set a preference
+ * @param track Track to modify
+ * @param name Preference name
+ * @param value New value, or NULL to erase any existing value
+ * @return 0 on success or non-zero if not allowed to set preference
+ */
int trackdb_set(const char *track,
const char *name,
const char *value) {
if(trackdb_putdata(trackdb_prefsdb, track, p, tid, 0))
goto fail;
/* compute the new alias name */
- if((err = compute_alias(&newalias, track, p, tid))) goto fail;
+ if(compute_alias(&newalias, track, p, tid)) goto fail;
/* check whether alias has changed */
if(!(oldalias == newalias
|| (oldalias && newalias && !strcmp(oldalias, newalias)))) {
return err == 0 ? 0 : -1;
}
-/* get a pref */
+/** @brief Get the value of a preference
+ * @param track Track name
+ * @param name Preference name
+ * @return Preference value or NULL if it's not set
+ */
const char *trackdb_get(const char *track,
const char *name) {
return kvp_get(trackdb_get_all(track), name);
}
-/* get all prefs as a 0-terminated array */
+/** @brief Get all preferences for a track
+ * @param track Track name
+ * @return Linked list of preferences
+ */
struct kvp *trackdb_get_all(const char *track) {
struct kvp *t, *p, **pp;
DB_TXN *tid;
return p;
}
-/* resolve alias */
+/** @brief Resolve 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) {
DB_TXN *tid;
const char *actual;
return actual;
}
+/** @brief Detect an alias
+ * @param track Track name
+ * @return Nonzero if @p track exists and is an alias
+ */
int trackdb_isalias(const char *track) {
const char *actual = trackdb_resolve(track);
return strcmp(actual, track);
}
-/* test whether a track exists (perhaps an alias) */
+/** @brief Detect whether a track exists
+ * @param track Track name (can be an alias)
+ * @return Nonzero if @p track exists (whether or not it's an alias)
+ */
int trackdb_exists(const char *track) {
DB_TXN *tid;
int err;
return (err == 0);
}
-/* return the list of tags */
+/** @brief Return list of all known tags
+ * @return NULL-terminated tag list
+ */
char **trackdb_alltags(void) {
int e;
struct vector v[1];
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;
}
/* 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;
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))
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,
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;
}
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,
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) {
DB_TXN *tid;
char *pref;
const char *actual;
- int used_db, err;
+ int used_db;
/* construct the full pref */
byte_xasprintf(&pref, "trackname_%s_%s", context, part);
for(;;) {
tid = trackdb_begin_transaction();
- if((err = gettrackdata(track, 0, &p, &actual, 0, tid)) == DB_LOCK_DEADLOCK)
+ if(gettrackdata(track, 0, &p, &actual, 0, tid) == DB_LOCK_DEADLOCK)
goto fail;
break;
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;
/* 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;
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;
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;
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;
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;
+ if(err)
+ goto fail;
+ cursor = 0;
/* do a naive search over that (hopefuly fairly small) list of tracks */
u.nvec = 0;
for(n = 0; n < v.nvec; ++n) {
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);
trackdb_closecursor(cursor);
cursor = 0;
trackdb_abort_transaction(tid);
- info("retrying search");
+ disorder_info("retrying search");
}
trackdb_commit_transaction(tid);
vector_terminate(&u);
/* 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,
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 */
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));
}
}
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 */
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,
}
}
+/** @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;
}
/* global prefs **************************************************************/
-void trackdb_set_global(const char *name,
+/** @brief Set a global preference
+ * @param name Global preference name
+ * @param value New value
+ * @param who Who is setting it
+ * @return 0 on success, -1 on error
+ */
+int trackdb_set_global(const char *name,
const char *value,
const char *who) {
DB_TXN *tid;
- int err;
- int state;
+ int state, err;
for(;;) {
tid = trackdb_begin_transaction();
- if(!(err = trackdb_set_global_tid(name, value, tid)))
+ err = trackdb_set_global_tid(name, value, tid);
+ if(err != DB_LOCK_DEADLOCK)
break;
trackdb_abort_transaction(tid);
}
/* 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);
}
+ eventlog("global_pref", name, value, (char *)0);
+ return err == 0 ? 0 : -1;
}
+/** @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) {
err = trackdb_globaldb->put(trackdb_globaldb, tid, &k, &d, 0);
else
err = trackdb_globaldb->del(trackdb_globaldb, tid, &k, 0);
- if(err == DB_LOCK_DEADLOCK) return err;
+ if(err == DB_LOCK_DEADLOCK || err == DB_NOTFOUND) 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;
const char *r;
for(;;) {
tid = trackdb_begin_transaction();
- if(!(err = trackdb_get_global_tid(name, tid, &r)))
+ if(!trackdb_get_global_tid(name, tid, &r))
break;
trackdb_abort_transaction(tid);
}
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) {
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));
}
}
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)))
+ if(trackdb_closecursor(c))
return 0; /* deadlock */
vector_terminate(tracks);
if(ntracksp)
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;
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;
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
/* user database *************************************************************/
-/** @brief Return true if @p user is trusted */
-static int trusted(const char *user) {
- int n;
-
- for(n = 0; (n < config->trust.n
- && strcmp(config->trust.s[n], user)); ++n)
- ;
- 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,
/* 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 */
return trackdb_putdata(trackdb_usersdb, user, k, tid, flags);
}
-/** @brief Add one pre-existing user */
-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");
- return 0;
- }
- /* pick rights */
- if(!strcmp(user, "root"))
- rights = "all";
- else if(trusted(user)) {
- rights_type r;
-
- parse_rights(config->default_rights, &r, 1);
- r &= ~(rights_type)(RIGHT_SCRATCH__MASK|RIGHT_MOVE__MASK|RIGHT_REMOVE__MASK);
- r |= (RIGHT_ADMIN|RIGHT_RESCAN
- |RIGHT_SCRATCH_ANY|RIGHT_MOVE_ANY|RIGHT_REMOVE_ANY);
- rights = rights_string(r);
- } else
- rights = config->default_rights;
- return create_user(user, password, rights, 0/*email*/, 0/*confirmation*/,
- tid, DB_NOOVERWRITE);
-}
-
-static int trackdb_old_users_tid(DB_TXN *tid) {
- int n;
-
- for(n = 0; n < config->allow.n; ++n) {
- 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]);
- break;
- case DB_KEYEXIST:
- 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. */
- break;
- case DB_LOCK_DEADLOCK:
- return DB_LOCK_DEADLOCK;
- }
- }
- return 0;
-}
-
-/** @brief Read old 'allow' directives and copy them to the users database */
-void trackdb_old_users(void) {
- int e;
-
- if(config->allow.n)
- WITH_TRANSACTION(trackdb_old_users_tid(tid));
-}
-
/** @brief Create a root user in the user database if there is none */
void trackdb_create_root(void) {
int e;
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
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;
}
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;
}
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);
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 */
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;