X-Git-Url: http://git.tuebingen.mpg.de/?a=blobdiff_plain;f=afs.c;h=cea8f2707491f00bab0f457a3463fd4762147a21;hb=9055c71be97f1095dcdbd83da305b600f204f763;hp=59595567fcd6c7b4f7c66c49bfb2e3183cd73283;hpb=80541d0f045e1ed57332800eff9832e0a5b72ddf;p=paraslash.git diff --git a/afs.c b/afs.c index 59595567..cea8f270 100644 --- a/afs.c +++ b/afs.c @@ -1,8 +1,4 @@ -/* - * Copyright (C) 2007 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ +/* Copyright (C) 2007 Andre Noll , see file COPYING. */ /** \file afs.c Paraslash's audio file selector. */ @@ -26,63 +22,42 @@ #include "string.h" #include "afh.h" #include "afs.h" -#include "server.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. */ int fd; - /** - * Value sent by the command handlers to identify themselves as - * children of the running para_server. - */ - uint32_t cookie; /** The associated task structure. */ struct task *task; }; @@ -97,44 +72,35 @@ static struct signal_task *signal_task; static enum play_mode current_play_mode; static char *current_mop; /* mode or playlist specifier. NULL means dummy mood */ -/** - * A random number used to "authenticate" the connection. - * - * para_server picks this number by random before it forks the afs process. The - * command handlers know this number as well and write it to the afs socket, - * together with the id of the shared memory area which contains the payload of - * the afs command. A local process has to know this number to abuse the afs - * service provided by 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; }; @@ -225,7 +191,7 @@ int send_callback_request(afs_callback *f, struct osl_object *query, if (ret < 0) goto out; cq = query_shm; - cq->handler = f; + cq->cb = f; cq->query_size = query_shm_size - sizeof(*cq); if (query) @@ -384,8 +350,8 @@ int for_each_matching_row(struct pattern_match_data *pmd) */ int string_compare(const struct osl_object *obj1, const struct osl_object *obj2) { - const char *str1 = (const char *)obj1->data; - const char *str2 = (const char *)obj2->data; + const char *str1 = obj1->data; + const char *str2 = obj2->data; return strncmp(str1, str2, PARA_MIN(obj1->size, obj2->size)); } @@ -436,13 +402,13 @@ static int pass_afd(int fd, char *buf, size_t size) */ static int open_next_audio_file(void) { - struct audio_file_data afd; - int ret, shmid; + int ret, shmid, fd; char buf[8]; - ret = open_and_update_audio_file(&afd); + ret = open_and_update_audio_file(&fd); if (ret < 0) { - PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + if (ret != -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND)) + PARA_ERROR_LOG("%s\n", para_strerror(-ret)); goto no_admissible_files; } shmid = ret; @@ -452,8 +418,8 @@ static int open_next_audio_file(void) } *(uint32_t *)buf = NEXT_AUDIO_FILE; *(uint32_t *)(buf + 4) = (uint32_t)shmid; - ret = pass_afd(afd.fd, buf, 8); - close(afd.fd); + ret = pass_afd(fd, buf, 8); + close(fd); if (ret >= 0) return ret; destroy: @@ -465,46 +431,48 @@ no_admissible_files: return write_all(server_socket, buf, 8); } -/* Never fails if arg == NULL */ -static int activate_mood_or_playlist(const char *arg, int *num_admissible) +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); /* always successful */ + ret = mood_load(NULL, &msg); + mode = PLAY_MODE_MOOD; + } else if (!strncmp(arg, "p/", 2)) { + ret = playlist_load(arg + 2, &msg); + mode = PLAY_MODE_PLAYLIST; + } else if (!strncmp(arg, "m/", 2)) { + ret = mood_load(arg + 2, &msg); mode = PLAY_MODE_MOOD; } else { - if (!strncmp(arg, "p/", 2)) { - ret = playlist_open(arg + 2); - mode = PLAY_MODE_PLAYLIST; - } else if (!strncmp(arg, "m/", 2)) { - ret = change_current_mood(arg + 2); - mode = PLAY_MODE_MOOD; - } else - return -E_AFS_SYNTAX; - 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'; - mutex_unlock(mmd_mutex); - } else { - mutex_lock(mmd_mutex); - strcpy(mmd->afs_mode_string, "dummy"); - 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; } @@ -561,39 +529,30 @@ 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; + int ret; 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; - } + score_clear(); if (current_play_mode == PLAY_MODE_MOOD) - close_current_mood(); + mood_unload(); else - playlist_close(); - ret = activate_mood_or_playlist(arg, &num_admissible); + playlist_unload(); + ret = activate_mood_or_playlist(arg, &aca->pbout); if (ret >= 0) - goto out; + goto free_lpr; /* ignore subsequent errors (but log them) */ - para_printf(&aca->pbout, "could not activate %s\n", arg); - if (current_mop) { + 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); + ret2 = activate_mood_or_playlist(current_mop, &aca->pbout); if (ret2 >= 0) - goto out; + goto free_lpr; 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); -out: - para_printf(&aca->pbout, "activated %s (%d admissible files)\n", - current_mop? current_mop : "dummy mood", num_admissible); + activate_mood_or_playlist(NULL, &aca->pbout); free_lpr: lls_free_parse_result(aca->lpr, cmd); return ret; @@ -615,8 +574,13 @@ EXPORT_SERVER_CMD_HANDLER(select); static void init_admissible_files(const char *arg) { - if (activate_mood_or_playlist(arg, NULL) < 0) - activate_mood_or_playlist(NULL, NULL); /* always successful */ + int ret = activate_mood_or_playlist(arg, NULL); + if (ret < 0) { + PARA_WARNING_LOG("could not activate %s: %s\n", arg, + para_strerror(-ret)); + if (arg) + activate_mood_or_playlist(NULL, NULL); + } } static int setup_command_socket_or_die(void) @@ -636,16 +600,18 @@ static int setup_command_socket_or_die(void) return socket_fd; } +static char *database_dir; + 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; } -static char *database_dir; - static void get_database_dir(void) { if (!database_dir) { @@ -654,7 +620,7 @@ static void get_database_dir(void) 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); } } @@ -677,24 +643,23 @@ static int open_afs_tables(void) 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("%s init: %s\n", afs_tables[i].name, - para_strerror(-ret)); + PARA_ERROR_LOG("could not open %s\n", afs_tables[i].name); break; } 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; @@ -702,7 +667,7 @@ static int afs_signal_post_select(struct sched *s, __a_unused void *context) 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) { @@ -730,8 +695,8 @@ static void register_signal_task(struct sched *s) 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); @@ -749,15 +714,15 @@ struct afs_client { 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); } /** @@ -829,7 +794,7 @@ static int call_callback(int fd, int query_shmid) .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 */ @@ -849,11 +814,11 @@ static int call_callback(int fd, int query_shmid) 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; @@ -864,13 +829,13 @@ static int execute_server_command(fd_set *rfds) } /* returns 0 if no data available, 1 else */ -static int execute_afs_command(int fd, fd_set *rfds, uint32_t expected_cookie) +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; @@ -882,9 +847,9 @@ static int execute_afs_command(int fd, fd_set *rfds, uint32_t expected_cookie) return 1; } cookie = *(uint32_t *)buf; - if (cookie != expected_cookie) { + if (cookie != afs_socket_cookie) { PARA_NOTICE_LOG("received invalid cookie (got %u, expected %u)\n", - (unsigned)cookie, (unsigned)expected_cookie); + (unsigned)cookie, (unsigned)afs_socket_cookie); return 1; } query_shmid = *(int *)(buf + sizeof(cookie)); @@ -904,7 +869,7 @@ 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; @@ -914,7 +879,7 @@ static int command_post_select(struct sched *s, void *context) 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); @@ -922,7 +887,7 @@ static int command_post_select(struct sched *s, void *context) } /* 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, ct->cookie); + ret = execute_afs_command(client->fd); if (ret == 0) { /* prevent bogus connection flooding */ struct timeval diff; tv_diff(now, &client->connect_time, &diff); @@ -935,7 +900,7 @@ static int command_post_select(struct sched *s, void *context) 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) @@ -946,42 +911,46 @@ static int command_post_select(struct sched *s, void *context) 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); return 0; } -static void register_command_task(uint32_t cookie, struct sched *s) +static void register_command_task(struct sched *s) { struct command_task *ct = &command_task_struct; ct->fd = setup_command_socket_or_die(); - ct->cookie = cookie; 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. * - * \param cookie The value used for "authentication". * \param socket_fd File descriptor used for communication with the server. */ -__noreturn void afs_init(uint32_t cookie, int socket_fd) +__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]); + init_list_head(&afs_client_list); ret = open_afs_tables(); if (ret < 0) goto out; @@ -989,12 +958,11 @@ __noreturn void afs_init(uint32_t cookie, int socket_fd) ret = mark_fd_nonblocking(server_socket); if (ret < 0) goto out_close; - PARA_INFO_LOG("server_socket: %d, afs_socket_cookie: %u\n", - server_socket, (unsigned) cookie); + PARA_INFO_LOG("server_socket: %d\n", server_socket); init_admissible_files(OPT_STRING_VAL(AFS_INITIAL_MODE)); - register_command_task(cookie, &s); - s.default_timeout.tv_sec = 0; - s.default_timeout.tv_usec = 999 * 1000; + register_command_task(&s); + s.default_timeout = 1000; + s.poll_function = afs_poll; ret = write(socket_fd, "\0", 1); if (ret != 1) { if (ret == 0) @@ -1004,9 +972,14 @@ __noreturn void afs_init(uint32_t cookie, int socket_fd) } ret = schedule(&s); sched_shutdown(&s); + mood_unload(); out_close: close_afs_tables(); out: + signal_shutdown(signal_task); + free_status_items(); + free(current_mop); + free_lpr(); if (ret < 0) PARA_EMERG_LOG("%s\n", para_strerror(-ret)); exit(EXIT_FAILURE); @@ -1018,14 +991,15 @@ static int com_init_callback(struct afs_callback_arg *aca) int i, ret; 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); @@ -1036,7 +1010,8 @@ static int com_init_callback(struct afs_callback_arg *aca) } ret = open_afs_tables(); if (ret < 0) - para_printf(&aca->pbout, "cannot open afs tables\n"); + para_printf(&aca->pbout, "cannot open afs tables: %s\n", + para_strerror(-ret)); out: return ret; } @@ -1056,7 +1031,7 @@ static int com_init(struct command_context *cc, struct lls_parse_result *lpr) 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; @@ -1130,10 +1105,10 @@ __must_check int afs_event(enum afs_events event, struct para_buffer *pb, 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));