#include "afs.h"
#include "net.h"
#include "server.h"
+#include "daemon.h"
#include "ipc.h"
#include "list.h"
#include "sched.h"
#include "fd.h"
#include "signal.h"
-#include "mood.h"
#include "sideband.h"
#include "command.h"
-/** The osl tables used by afs. \sa \ref blob.c. */
-enum afs_table_num {
- /** Contains audio file information. See \ref aft.c. */
- TBLNUM_AUDIO_FILES,
- /** The table for the paraslash attributes. See \ref attribute.c. */
- TBLNUM_ATTRIBUTES,
- /**
- * Paraslash's scoring system is based on Gaussian normal
- * distributions, and the relevant data is stored in the rbtrees of an
- * osl table containing only volatile columns. See \ref score.c for
- * details.
- */
- TBLNUM_SCORES,
- /**
- * A standard blob table containing the mood definitions. For details
- * see \ref mood.c.
- */
- TBLNUM_MOODS,
- /** A blob table containing lyrics on a per-song basis. */
- TBLNUM_LYRICS,
- /** Another blob table for images (for example album cover art). */
- TBLNUM_IMAGES,
- /** Yet another blob table for storing standard playlists. */
- TBLNUM_PLAYLIST,
- /** How many tables are in use? */
- NUM_AFS_TABLES
-};
-
-static struct afs_table afs_tables[NUM_AFS_TABLES] = {
- [TBLNUM_AUDIO_FILES] = {.init = aft_init, .name = "audio_files"},
- [TBLNUM_ATTRIBUTES] = {.init = attribute_init, .name = "attributes"},
- [TBLNUM_SCORES] = {.init = score_init, .name = "scores"},
- [TBLNUM_MOODS] = {.init = moods_init, .name = "moods"},
- [TBLNUM_LYRICS] = {.init = lyrics_init, .name = "lyrics"},
- [TBLNUM_IMAGES] = {.init = images_init, .name = "images"},
- [TBLNUM_PLAYLIST] = {.init = playlists_init, .name = "playlists"},
+/**
+ * The array of tables of the audio file selector.
+ *
+ * We organize them in an array to be able to loop over all tables.
+ */
+static const struct afs_table {
+ /** The name is no table operation, so define it here. */
+ const char * const name;
+ /** The only way to invoke the ops is via this pointer. */
+ const struct afs_table_operations *ops;
+} afs_tables[] = {
+ {.name = "audio_files", .ops = &aft_ops},
+ {.name = "attributes", .ops = &attr_ops},
+ {.name = "scores", .ops = &score_ops},
+ {.name = "moods", .ops = &moods_ops},
+ {.name = "lyrics", .ops = &lyrics_ops},
+ {.name = "images", .ops = &images_ops},
+ {.name = "playlists", .ops = &playlists_ops},
};
+/** Used to loop over the afs tables. */
+#define NUM_AFS_TABLES ARRAY_SIZE(afs_tables)
struct command_task {
/** The file descriptor for the local socket. */
extern uint32_t afs_socket_cookie;
/**
- * Struct to let command handlers execute a callback in afs context.
- *
- * Commands that need to change the state of afs can't change the relevant data
- * structures directly because commands are executed in a child process, i.e.
- * they get their own virtual address space.
+ * Passed from command handlers to afs.
*
- * This structure is used by \p send_callback_request() (executed from handler
- * context) in order to let the afs process call the specified function. An
- * instance of that structure is written to a shared memory area together with
- * the arguments to the callback function. The identifier of the shared memory
- * area is written to the command socket.
+ * Command handlers cannot change the afs database directly because they run in
+ * a separate process. The callback query structure circumvents this
+ * restriction as follows. To instruct the afs process to execute a particular
+ * function, the command hander writes an instance of this structure to a
+ * shared memory area, along with the arguments to the callback function. The
+ * identifier of the shared memory area is transferred to the afs process via
+ * the command socket.
*
- * The afs process accepts connections on the command socket and reads the
- * shared memory id, attaches the corresponding area, calls the given handler to
- * perform the desired action and to optionally compute a result.
+ * The afs process reads the shared memory id from the command socket, attaches
+ * the corresponding area, and calls the callback function whose address is
+ * stored in the area.
*
- * The result and a \p callback_result structure is then written to another
- * shared memory area. The identifier for that area is written to the handler's
- * command socket, so that the handler process can read the id, attach the
- * shared memory area and use the result.
+ * The command output, if any, is transferred back to the command handler in
+ * the same way: The afs process writes the output to a second shared memory
+ * area together with a fixed size metadata header whose format corresponds to
+ * the \ref callback_result structure. The identifier of this area is sent back
+ * to the command handler which attaches the area and forwards the output to
+ * the remote client.
*
* \sa \ref struct callback_result.
*/
struct callback_query {
/** The function to be called. */
- afs_callback *handler;
+ afs_callback *cb;
/** The number of bytes of the query */
size_t query_size;
};
if (ret < 0)
goto out;
cq = query_shm;
- cq->handler = f;
+ cq->cb = f;
cq->query_size = query_shm_size - sizeof(*cq);
if (query)
return write_all(server_socket, buf, 8);
}
-/* Never fails if arg == NULL */
-static int activate_mood_or_playlist(const char *arg, int *num_admissible,
- char **errmsg)
+static int activate_mood_or_playlist(const char *arg, struct para_buffer *pb)
{
enum play_mode mode;
int ret;
+ char *msg;
- if (!arg) {
- ret = change_current_mood(NULL, NULL); /* always successful */
+ if (!arg) { /* load dummy mood */
+ ret = mood_load(NULL, NULL, &msg);
+ mode = PLAY_MODE_MOOD;
+ } else if (!strncmp(arg, "p/", 2)) {
+ ret = playlist_load(arg + 2, NULL, &msg);
+ mode = PLAY_MODE_PLAYLIST;
+ } else if (!strncmp(arg, "m/", 2)) {
+ ret = mood_load(arg + 2, NULL, &msg);
mode = PLAY_MODE_MOOD;
} else {
- if (!strncmp(arg, "p/", 2)) {
- ret = playlist_open(arg + 2);
- if (ret < 0 && errmsg)
- *errmsg = make_message( "could not open %s",
- arg);
- mode = PLAY_MODE_PLAYLIST;
- } else if (!strncmp(arg, "m/", 2)) {
- ret = change_current_mood(arg + 2, errmsg);
- mode = PLAY_MODE_MOOD;
- } else {
- if (errmsg)
- *errmsg = make_message("%s: parse error", arg);
- return -ERRNO_TO_PARA_ERROR(EINVAL);
- }
- if (ret < 0)
- return ret;
+ ret = -ERRNO_TO_PARA_ERROR(EINVAL);
+ msg = make_message("%s: parse error", arg);
}
- if (num_admissible)
- *num_admissible = ret;
+ if (pb)
+ para_printf(pb, "%s", msg);
+ free(msg);
+ if (ret < 0)
+ return ret;
current_play_mode = mode;
+ /*
+ * We get called with arg == current_mop from the signal dispatcher
+ * after SIGHUP and from the error path of the select command to
+ * re-select the current mood or playlist. In this case the assignment
+ * to current_mop below would result in a use-after-free condition.
+ */
if (arg != current_mop) {
free(current_mop);
- if (arg) {
- current_mop = para_strdup(arg);
- mutex_lock(mmd_mutex);
- strncpy(mmd->afs_mode_string, arg,
- sizeof(mmd->afs_mode_string));
- mmd->afs_mode_string[sizeof(mmd->afs_mode_string) - 1] = '\0';
- mmd->events++;
- mutex_unlock(mmd_mutex);
- } else {
- mutex_lock(mmd_mutex);
- strcpy(mmd->afs_mode_string, "dummy");
- mmd->events++;
- mutex_unlock(mmd_mutex);
- current_mop = NULL;
- }
+ current_mop = arg? para_strdup(arg) : NULL;
}
+ /* Notify the server about the mood/playlist change. */
+ mutex_lock(mmd_mutex);
+ strncpy(mmd->afs_mode_string, arg? arg: "dummy",
+ sizeof(mmd->afs_mode_string));
+ mmd->afs_mode_string[sizeof(mmd->afs_mode_string) - 1] = '\0';
+ mmd->events++;
+ mutex_unlock(mmd_mutex);
return 1;
}
free(pb->buf);
}
-static int com_select_callback(struct afs_callback_arg *aca)
-{
- const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT);
- const char *arg;
- int num_admissible, ret;
- char *errmsg;
-
- ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr);
- assert(ret >= 0);
- arg = lls_input(0, aca->lpr);
- ret = clear_score_table();
- if (ret < 0) {
- para_printf(&aca->pbout, "could not clear score table\n");
- goto free_lpr;
- }
- if (current_play_mode == PLAY_MODE_MOOD)
- close_current_mood();
- else
- playlist_close();
- ret = activate_mood_or_playlist(arg, &num_admissible, &errmsg);
- if (ret >= 0)
- goto out;
- /* ignore subsequent errors (but log them) */
- para_printf(&aca->pbout, "%s\n", errmsg);
- free(errmsg);
- para_printf(&aca->pbout, "could not activate %s\n", arg);
- if (current_mop && strcmp(current_mop, arg) != 0) {
- int ret2;
- para_printf(&aca->pbout, "switching back to %s\n", current_mop);
- ret2 = activate_mood_or_playlist(current_mop, &num_admissible,
- &errmsg);
- if (ret2 >= 0)
- goto out;
- para_printf(&aca->pbout, "%s\n", errmsg);
- free(errmsg);
- para_printf(&aca->pbout, "could not reactivate %s: %s\n",
- current_mop, para_strerror(-ret2));
- }
- para_printf(&aca->pbout, "activating dummy mood\n");
- activate_mood_or_playlist(NULL, &num_admissible, NULL);
-out:
- para_printf(&aca->pbout, "activated %s (%d admissible file%s)\n",
- current_mop? current_mop : "dummy mood", num_admissible,
- num_admissible == 1? "" : "s");
-free_lpr:
- lls_free_parse_result(aca->lpr, cmd);
- return ret;
-}
-
-static int com_select(struct command_context *cc, struct lls_parse_result *lpr)
-{
- const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT);
- char *errctx;
- int ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx));
-
- if (ret < 0) {
- send_errctx(cc, errctx);
- return ret;
- }
- return send_lls_callback_request(com_select_callback, cmd, lpr, cc);
-}
-EXPORT_SERVER_CMD_HANDLER(select);
-
static void init_admissible_files(const char *arg)
{
- int ret = activate_mood_or_playlist(arg, NULL, NULL);
+ int ret = activate_mood_or_playlist(arg, NULL);
if (ret < 0) {
- assert(arg);
- PARA_WARNING_LOG("could not activate %s: %s\n", arg,
- para_strerror(-ret));
- activate_mood_or_playlist(NULL, NULL, NULL);
+ PARA_WARNING_LOG("could not activate %s: %s\n", arg?
+ arg : "dummy", para_strerror(-ret));
+ if (arg)
+ activate_mood_or_playlist(NULL, NULL);
}
}
static void close_afs_tables(void)
{
int i;
- PARA_NOTICE_LOG("closing afs_tables\n");
+ PARA_NOTICE_LOG("closing afs tables\n");
for (i = 0; i < NUM_AFS_TABLES; i++)
- afs_tables[i].close();
+ afs_tables[i].ops->close();
free(database_dir);
database_dir = NULL;
}
else {
char *home = para_homedir();
database_dir = make_message(
- "%s/.paraslash/afs_database-0.4", home);
+ "%s/.paraslash/afs_database-0.7", home);
free(home);
}
}
int i, ret;
get_database_dir();
- PARA_NOTICE_LOG("opening %d osl tables in %s\n", NUM_AFS_TABLES,
+ PARA_NOTICE_LOG("opening %zu osl tables in %s\n", NUM_AFS_TABLES,
database_dir);
for (i = 0; i < NUM_AFS_TABLES; i++) {
- ret = afs_tables[i].open(database_dir);
+ ret = afs_tables[i].ops->open(database_dir);
if (ret >= 0)
continue;
PARA_ERROR_LOG("could not open %s\n", afs_tables[i].name);
if (ret >= 0)
return ret;
while (i)
- afs_tables[--i].close();
+ afs_tables[--i].ops->close();
return ret;
}
-static int afs_signal_post_select(struct sched *s, __a_unused void *context)
+static int afs_signal_post_monitor(struct sched *s, __a_unused void *context)
{
int signum, ret;
PARA_EMERG_LOG("para_server died\n");
goto shutdown;
}
- signum = para_next_signal(&s->rfds);
+ signum = para_next_signal();
if (signum == 0)
return 0;
if (signum == SIGHUP) {
signal_task->task = task_register(&(struct task_info) {
.name = "signal",
- .pre_select = signal_pre_select,
- .post_select = afs_signal_post_select,
+ .pre_monitor = signal_pre_monitor,
+ .post_monitor = afs_signal_post_monitor,
.context = signal_task,
}, s);
struct timeval connect_time;
};
-static void command_pre_select(struct sched *s, void *context)
+static void command_pre_monitor(struct sched *s, void *context)
{
struct command_task *ct = context;
struct afs_client *client;
- para_fd_set(server_socket, &s->rfds, &s->max_fileno);
- para_fd_set(ct->fd, &s->rfds, &s->max_fileno);
+ sched_monitor_readfd(server_socket, s);
+ sched_monitor_readfd(ct->fd, s);
list_for_each_entry(client, &afs_client_list, node)
- para_fd_set(client->fd, &s->rfds, &s->max_fileno);
+ sched_monitor_readfd(client->fd, s);
}
/**
return ret;
}
+/**
+ * Format and send an error message to the command handler.
+ *
+ * To pass an error message from the callback of an afs command to the client,
+ * this function should be called. It formats the message into a buffer which
+ * is passed as a shared memory area to the command handler from where it
+ * propagates to the client.
+ *
+ * The message will be tagged with the ERROR_LOG sideband designator so that
+ * the client writes it to its stderr stream rather than to stdout as with
+ * aca->pbout. In analogy to the default Unix semantics of stderr, the message
+ * is sent without buffering.
+ *
+ * If sending the error message fails, an error is logged on the server side,
+ * but no other action is taken.
+ *
+ * \param aca Used to obtain the fd to send the shmid to.
+ * \param fmt Usual format string.
+ */
+__printf_2_3 void afs_error(const struct afs_callback_arg *aca,
+ const char *fmt,...)
+{
+ va_list argp;
+ char *msg;
+ unsigned n;
+ int ret;
+
+ va_start(argp, fmt);
+ n = xvasprintf(&msg, fmt, argp);
+ va_end(argp);
+ ret = pass_buffer_as_shm(aca->fd, SBD_ERROR_LOG, msg, n + 1);
+ if (ret < 0)
+ PARA_ERROR_LOG("Could not send %s: %s\n", msg,
+ para_strerror(-ret));
+ free(msg);
+}
+
static int call_callback(int fd, int query_shmid)
{
void *query_shm;
.fd = fd,
.band = SBD_OUTPUT
};
- ret = cq->handler(&aca);
+ ret = cq->cb(&aca);
ret2 = shm_detach(query_shm);
if (ret2 < 0) {
if (ret < 0) /* ignore (but log) detach error */
return ret;
}
-static int execute_server_command(fd_set *rfds)
+static int execute_server_command(void)
{
char buf[8];
size_t n;
- int ret = read_nonblock(server_socket, buf, sizeof(buf) - 1, rfds, &n);
+ int ret = read_nonblock(server_socket, buf, sizeof(buf) - 1, &n);
if (ret < 0 || n == 0)
return ret;
}
/* returns 0 if no data available, 1 else */
-static int execute_afs_command(int fd, fd_set *rfds)
+static int execute_afs_command(int fd)
{
uint32_t cookie;
int query_shmid;
char buf[sizeof(cookie) + sizeof(query_shmid)];
size_t n;
- int ret = read_nonblock(fd, buf, sizeof(buf), rfds, &n);
+ int ret = read_nonblock(fd, buf, sizeof(buf), &n);
if (ret < 0)
goto err;
/** Shutdown connection if query has not arrived until this many seconds. */
#define AFS_CLIENT_TIMEOUT 3
-static int command_post_select(struct sched *s, void *context)
+static int command_post_monitor(struct sched *s, void *context)
{
struct command_task *ct = context;
struct sockaddr_un unix_addr;
ret = task_get_notification(ct->task);
if (ret < 0)
return ret;
- ret = execute_server_command(&s->rfds);
+ ret = execute_server_command();
if (ret < 0) {
PARA_EMERG_LOG("%s\n", para_strerror(-ret));
task_notify_all(s, -ret);
}
/* Check the list of connected clients. */
list_for_each_entry_safe(client, tmp, &afs_client_list, node) {
- ret = execute_afs_command(client->fd, &s->rfds);
+ ret = execute_afs_command(client->fd);
if (ret == 0) { /* prevent bogus connection flooding */
struct timeval diff;
tv_diff(now, &client->connect_time, &diff);
free(client);
}
/* Accept connections on the local socket. */
- ret = para_accept(ct->fd, &s->rfds, &unix_addr, sizeof(unix_addr), &fd);
+ ret = para_accept(ct->fd, &unix_addr, sizeof(unix_addr), &fd);
if (ret < 0)
PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
if (ret <= 0)
close(fd);
return 0;
}
- client = para_malloc(sizeof(*client));
+ client = alloc(sizeof(*client));
client->fd = fd;
client->connect_time = *now;
para_list_add(&client->node, &afs_client_list);
ct->task = task_register(&(struct task_info) {
.name = "afs command",
- .pre_select = command_pre_select,
- .post_select = command_post_select,
+ .pre_monitor = command_pre_monitor,
+ .post_monitor = command_post_monitor,
.context = ct,
}, s);
}
+static int afs_poll(struct pollfd *fds, nfds_t nfds, int timeout)
+{
+ mutex_lock(mmd_mutex);
+ daemon_set_loglevel(mmd->loglevel);
+ mutex_unlock(mmd_mutex);
+ return xpoll(fds, nfds, timeout);
+}
+
/**
* Initialize the audio file selector process.
*
__noreturn void afs_init(int socket_fd)
{
static struct sched s;
- int i, ret;
+ int ret;
register_signal_task(&s);
init_list_head(&afs_client_list);
- for (i = 0; i < NUM_AFS_TABLES; i++)
- afs_tables[i].init(&afs_tables[i]);
ret = open_afs_tables();
if (ret < 0)
goto out;
PARA_INFO_LOG("server_socket: %d\n", server_socket);
init_admissible_files(OPT_STRING_VAL(AFS_INITIAL_MODE));
register_command_task(&s);
- s.default_timeout.tv_sec = 0;
- s.default_timeout.tv_usec = 999 * 1000;
+ s.default_timeout = 1000;
+ s.poll_function = afs_poll;
ret = write(socket_fd, "\0", 1);
if (ret != 1) {
if (ret == 0)
}
ret = schedule(&s);
sched_shutdown(&s);
- close_current_mood();
+ mood_unload(NULL);
out_close:
close_afs_tables();
out:
exit(EXIT_FAILURE);
}
+static int com_select_callback(struct afs_callback_arg *aca)
+{
+ const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT);
+ const char *arg;
+ int ret;
+
+ ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr);
+ assert(ret >= 0);
+ arg = lls_input(0, aca->lpr);
+ score_clear();
+ if (current_play_mode == PLAY_MODE_MOOD)
+ mood_unload(NULL);
+ else
+ playlist_unload(NULL);
+ ret = activate_mood_or_playlist(arg, &aca->pbout);
+ if (ret >= 0)
+ goto free_lpr;
+ /* ignore subsequent errors (but log them) */
+ if (current_mop && strcmp(current_mop, arg) != 0) {
+ int ret2;
+ afs_error(aca, "switching back to %s\n", current_mop);
+ ret2 = activate_mood_or_playlist(current_mop, &aca->pbout);
+ if (ret2 >= 0)
+ goto free_lpr;
+ afs_error(aca, "could not reactivate %s: %s\n", current_mop,
+ para_strerror(-ret2));
+ }
+ activate_mood_or_playlist(NULL, &aca->pbout);
+free_lpr:
+ lls_free_parse_result(aca->lpr, cmd);
+ return ret;
+}
+
+static int com_select(struct command_context *cc, struct lls_parse_result *lpr)
+{
+ const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT);
+ char *errctx;
+ int ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx));
+
+ if (ret < 0) {
+ send_errctx(cc, errctx);
+ return ret;
+ }
+ ret = send_lls_callback_request(com_select_callback, cmd, lpr, cc);
+ return ret == osl(-E_OSL_RB_KEY_NOT_FOUND)? -E_BAD_MOP : ret;
+}
+EXPORT_SERVER_CMD_HANDLER(select);
+
static int com_init_callback(struct afs_callback_arg *aca)
{
uint32_t table_mask = *(uint32_t *)aca->query.data;
close_afs_tables();
get_database_dir();
for (i = 0; i < NUM_AFS_TABLES; i++) {
- struct afs_table *t = &afs_tables[i];
+ const struct afs_table *t = afs_tables + i;
if (!(table_mask & (1 << i)))
continue;
- if (!t->create)
+ if (!t->ops->create)
continue;
- ret = t->create(database_dir);
+ ret = t->ops->create(database_dir);
if (ret < 0) {
- para_printf(&aca->pbout, "cannot create table %s\n",
- t->name);
+ afs_error(aca, "cannot create table %s\n", t->name);
goto out;
}
para_printf(&aca->pbout, "successfully created %s table\n",
}
ret = open_afs_tables();
if (ret < 0)
- para_printf(&aca->pbout, "cannot open afs tables: %s\n",
+ afs_error(aca, "cannot open afs tables: %s\n",
para_strerror(-ret));
out:
return ret;
table_mask = 0;
for (i = 0; i < num_inputs; i++) {
for (j = 0; j < NUM_AFS_TABLES; j++) {
- struct afs_table *t = &afs_tables[j];
+ const struct afs_table *t = afs_tables + j;
if (strcmp(lls_input(i, lpr), t->name))
continue;
int i, ret;
for (i = 0; i < NUM_AFS_TABLES; i++) {
- struct afs_table *t = &afs_tables[i];
- if (!t->event_handler)
+ const struct afs_table *t = afs_tables + i;
+ if (!t->ops->event_handler)
continue;
- ret = t->event_handler(event, pb, data);
+ ret = t->ops->event_handler(event, pb, data);
if (ret < 0) {
PARA_CRIT_LOG("table %s, event %u: %s\n", t->name,
event, para_strerror(-ret));