X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=blobdiff_plain;f=afs.c;h=016329841fe87255cdc68c1191e8661124c1c326;hp=135888a7fc632e6760d2aba4e1b4b2b4738af618;hb=c998e827328c7989986e4fb91048e7f427f722a8;hpb=f6a99f9e27cf84d92fc992677dca9578c5a3b585 diff --git a/afs.c b/afs.c index 135888a7..01632984 100644 --- a/afs.c +++ b/afs.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007-2013 Andre Noll + * Copyright (C) 2007 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -28,8 +28,8 @@ #include "ipc.h" #include "list.h" #include "sched.h" -#include "signal.h" #include "fd.h" +#include "signal.h" #include "mood.h" #include "sideband.h" #include "command.h" @@ -81,7 +81,7 @@ struct command_task { */ uint32_t cookie; /** The associated task structure. */ - struct task task; + struct task *task; }; extern int mmd_mutex; @@ -89,7 +89,7 @@ extern struct misc_meta_data *mmd; static int server_socket; static struct command_task command_task_struct; -static struct signal_task signal_task_struct; +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 */ @@ -97,10 +97,11 @@ 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 forking the afs process. The - * command handlers write this number together with the id of the shared memory - * area containing the query. This way, a malicious local user has to know this - * number to be able to cause the afs process to crash by sending fake queries. + * 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; @@ -130,7 +131,7 @@ extern uint32_t afs_socket_cookie; */ struct callback_query { /** The function to be called. */ - callback_function *handler; + afs_callback *handler; /** The number of bytes of the query */ size_t query_size; }; @@ -166,13 +167,8 @@ static int dispatch_result(int result_shmid, callback_result_handler *handler, } result.size = cr->result_size; result.data = result_shm + sizeof(*cr); - if (result.size) { - assert(handler); - ret = handler(&result, cr->band, private_result_data); - if (ret < 0) - PARA_NOTICE_LOG("result handler error: %s\n", - para_strerror(-ret)); - } + assert(handler); + ret = handler(&result, cr->band, private_result_data); ret2 = shm_detach(result_shm); if (ret2 < 0) { PARA_ERROR_LOG("detach failed: %s\n", para_strerror(-ret2)); @@ -195,7 +191,7 @@ static int dispatch_result(int result_shmid, callback_result_handler *handler, * copied. It then notifies the afs process that the callback function \a f * should be executed by sending the shared memory identifier (shmid) to the * socket. - + * * If the callback produces a result, it sends any number of shared memory * identifiers back via the socket. For each such identifier received, \a * result_handler is called. The contents of the sma identified by the received @@ -206,7 +202,7 @@ static int dispatch_result(int result_shmid, callback_result_handler *handler, * * \sa send_option_arg_callback_request(), send_standard_callback_request(). */ -int send_callback_request(callback_function *f, struct osl_object *query, +int send_callback_request(afs_callback *f, struct osl_object *query, callback_result_handler *result_handler, void *private_result_data) { @@ -236,8 +232,8 @@ int send_callback_request(callback_function *f, struct osl_object *query, if (ret < 0) goto out; - *(uint32_t *) buf = afs_socket_cookie; - *(int *) (buf + sizeof(afs_socket_cookie)) = query_shmid; + *(uint32_t *)buf = afs_socket_cookie; + *(int *)(buf + sizeof(afs_socket_cookie)) = query_shmid; ret = connect_local_socket(conf.afs_socket_arg); if (ret < 0) @@ -261,12 +257,10 @@ int send_callback_request(callback_function *f, struct osl_object *query, ret = *(int *) buf; assert(ret > 0); result_shmid = ret; - if (!dispatch_error) { - ret = dispatch_result(result_shmid, result_handler, - private_result_data); - if (ret < 0) - dispatch_error = 1; - } + ret = dispatch_result(result_shmid, result_handler, + private_result_data); + if (ret < 0 && dispatch_error >= 0) + dispatch_error = ret; ret = shm_destroy(result_shmid); if (ret < 0) PARA_CRIT_LOG("destroy result failed: %s\n", @@ -278,8 +272,11 @@ out: PARA_CRIT_LOG("shm destroy error\n"); if (fd >= 0) close(fd); -// PARA_DEBUG_LOG("callback_ret: %d\n", ret); - return ret < 0? ret : num_dispatched; + if (dispatch_error < 0) + return dispatch_error; + if (ret < 0) + return ret; + return num_dispatched; } /** @@ -292,10 +289,10 @@ out: * \param result_handler See \ref send_callback_request. * \param private_result_data See \ref send_callback_request. * - * Some commands have a couple of options that are parsed in child context for - * syntactic correctness and are stored in a special options structure for that - * command. This function allows to pass such a structure together with a list - * of further arguments (often a list of audio files) to the parent process. + * Some command handlers pass command-specific options to a callback, together + * with a list of further arguments (often a list of audio files). This + * function allows to pass an arbitrary structure (given as an osl object) and + * a usual argument vector to the specified callback. * * \return The return value of the underlying call to \ref * send_callback_request(). @@ -303,7 +300,7 @@ out: * \sa send_standard_callback_request(), send_callback_request(). */ int send_option_arg_callback_request(struct osl_object *options, - int argc, char * const * const argv, callback_function *f, + int argc, char * const * const argv, afs_callback *f, callback_result_handler *result_handler, void *private_result_data) { @@ -345,7 +342,7 @@ int send_option_arg_callback_request(struct osl_object *options, * send_option_arg_callback_request(). */ int send_standard_callback_request(int argc, char * const * const argv, - callback_function *f, callback_result_handler *result_handler, + afs_callback *f, callback_result_handler *result_handler, void *private_result_data) { return send_option_arg_callback_request(NULL, argc, argv, f, result_handler, @@ -365,8 +362,11 @@ static int action_if_pattern_matches(struct osl_row *row, void *data) name = (char *)name_obj.data; if ((!name || !*name) && (pmd->pm_flags & PM_SKIP_EMPTY_NAME)) return 1; - if (!pmd->patterns.size && (pmd->pm_flags & PM_NO_PATTERN_MATCHES_EVERYTHING)) + if (pmd->patterns.size == 0 && + (pmd->pm_flags & PM_NO_PATTERN_MATCHES_EVERYTHING)) { + pmd->num_matches++; return pmd->action(pmd->table, row, name, pmd->data); + } for (p = pattern_txt; p < pattern_txt + pmd->patterns.size; p += strlen(p) + 1) { ret = fnmatch(p, name, pmd->fnmatch_flags); @@ -424,7 +424,7 @@ static int pass_afd(int fd, char *buf, size_t size) { struct msghdr msg = {.msg_iov = NULL}; struct cmsghdr *cmsg; - char control[255]; + char control[255] __a_aligned(8); int ret; struct iovec iov; @@ -455,7 +455,7 @@ static int pass_afd(int fd, char *buf, size_t size) } /** - * Open the audio file with highest score. + * Pass the fd of the next audio file to the server process. * * This stores all information for streaming the "best" audio file in a shared * memory area. The id of that area and an open file descriptor for the next @@ -467,27 +467,15 @@ static int pass_afd(int fd, char *buf, size_t size) */ static int open_next_audio_file(void) { - struct osl_row *aft_row; struct audio_file_data afd; int ret, shmid; char buf[8]; - long score; -again: - PARA_NOTICE_LOG("getting next audio file\n"); - ret = score_get_best(&aft_row, &score); + + ret = open_and_update_audio_file(&afd); if (ret < 0) { PARA_ERROR_LOG("%s\n", para_strerror(-ret)); goto no_admissible_files; } - ret = open_and_update_audio_file(aft_row, score, &afd); - if (ret < 0) { - ret = score_delete(aft_row); - if (ret < 0) { - PARA_ERROR_LOG("%s\n", para_strerror(-ret)); - goto no_admissible_files; - } - goto again; - } shmid = ret; if (!write_ok(server_socket)) { ret = -E_AFS_SOCKET; @@ -509,7 +497,7 @@ no_admissible_files: } /* Never fails if arg == NULL */ -static int activate_mood_or_playlist(char *arg, int *num_admissible) +static int activate_mood_or_playlist(const char *arg, int *num_admissible) { enum play_mode mode; int ret; @@ -568,51 +556,70 @@ int afs_cb_result_handler(struct osl_object *result, uint8_t band, struct command_context *cc = private; assert(cc); - if (!result->size) - return 1; - return send_sb(&cc->scc, result->data, result->size, band, true); + switch (band) { + case SBD_OUTPUT: + case SBD_DEBUG_LOG: + case SBD_INFO_LOG: + case SBD_NOTICE_LOG: + case SBD_WARNING_LOG: + case SBD_ERROR_LOG: + case SBD_CRIT_LOG: + case SBD_EMERG_LOG: + assert(result->size > 0); + return send_sb(&cc->scc, result->data, result->size, band, true); + case SBD_AFS_CB_FAILURE: + return *(int *)(result->data); + default: + return -E_BAD_BAND; + } } -static void com_select_callback(int fd, const struct osl_object *query) +static void flush_and_free_pb(struct para_buffer *pb) { - struct para_buffer pb = { - .max_size = shm_get_shmmax(), - .private_data = &(struct afs_max_size_handler_data) { - .fd = fd, - .band = SBD_OUTPUT - }, - .max_size_handler = afs_max_size_handler, - }; - char *arg = query->data; - int num_admissible, ret, ret2; + int ret; + struct afs_max_size_handler_data *amshd = pb->private_data; + + if (pb->buf && pb->size > 0) { + ret = pass_buffer_as_shm(amshd->fd, amshd->band, pb->buf, + pb->offset); + if (ret < 0) + PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + } + free(pb->buf); +} + +static int com_select_callback(struct afs_callback_arg *aca) +{ + const char *arg = aca->query.data; + int num_admissible, ret; ret = clear_score_table(); if (ret < 0) { - ret2 = para_printf(&pb, "%s\n", para_strerror(-ret)); - goto out; + para_printf(&aca->pbout, "could not clear score table: %s\n", + para_strerror(-ret)); + return ret; } if (current_play_mode == PLAY_MODE_MOOD) close_current_mood(); else playlist_close(); ret = activate_mood_or_playlist(arg, &num_admissible); - if (ret < 0) { - ret2 = para_printf(&pb, "%s\nswitching back to %s\n", - para_strerror(-ret), current_mop? - current_mop : "dummy"); - ret = activate_mood_or_playlist(current_mop, &num_admissible); - if (ret < 0) { - if (ret2 >= 0) - ret2 = para_printf(&pb, "failed, switching to dummy\n"); - activate_mood_or_playlist(NULL, &num_admissible); - } - } else - ret2 = para_printf(&pb, "activated %s (%d admissible files)\n", current_mop? - current_mop : "dummy mood", num_admissible); + if (ret >= 0) + goto out; + /* ignore subsequent errors (but log them) */ + para_printf(&aca->pbout, "could not activate %s: %s\n" + "switching back to %s\n", + arg, para_strerror(-ret), current_mop? current_mop : "dummy"); + ret = activate_mood_or_playlist(current_mop, &num_admissible); + if (ret >= 0) + goto out; + para_printf(&aca->pbout, "could not activate %s: %s\nswitching to dummy\n", + current_mop, para_strerror(-ret)); + activate_mood_or_playlist(NULL, &num_admissible); out: - if (ret2 >= 0 && pb.offset) - pass_buffer_as_shm(fd, SBD_OUTPUT, pb.buf, pb.offset); - free(pb.buf); + para_printf(&aca->pbout, "activated %s (%d admissible files)\n", + current_mop? current_mop : "dummy mood", num_admissible); + return ret; } int com_select(struct command_context *cc) @@ -637,25 +644,19 @@ static int setup_command_socket_or_die(void) { int ret, socket_fd; char *socket_name = conf.afs_socket_arg; - struct sockaddr_un unix_addr; unlink(socket_name); - ret = create_local_socket(socket_name, &unix_addr, - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IWOTH); + ret = create_local_socket(socket_name, 0); if (ret < 0) { - PARA_EMERG_LOG("%s: %s\n", para_strerror(-ret), socket_name); - exit(EXIT_FAILURE); + ret = create_local_socket(socket_name, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IROTH); + if (ret < 0) { + PARA_EMERG_LOG("%s: %s\n", para_strerror(-ret), + socket_name); + exit(EXIT_FAILURE); + } } socket_fd = ret; - if (listen(socket_fd , 5) < 0) { - PARA_EMERG_LOG("can not listen on socket\n"); - exit(EXIT_FAILURE); - } - ret = mark_fd_nonblocking(socket_fd); - if (ret < 0) { - close(socket_fd); - return ret; - } PARA_INFO_LOG("listening on socket %s (fd %d)\n", socket_name, socket_fd); return socket_fd; @@ -692,7 +693,7 @@ static int make_database_dir(void) get_database_dir(); ret = para_mkdir(database_dir, 0777); - if (ret >= 0 || is_errno(-ret, EEXIST)) + if (ret >= 0 || ret == -ERRNO_TO_PARA_ERROR(EEXIST)) return 1; return ret; } @@ -719,13 +720,7 @@ static int open_afs_tables(void) return ret; } -static void signal_pre_select(struct sched *s, struct task *t) -{ - struct signal_task *st = container_of(t, struct signal_task, task); - para_fd_set(st->fd, &s->rfds, &s->max_fileno); -} - -static int afs_signal_post_select(struct sched *s, __a_unused struct task *t) +static int afs_signal_post_select(struct sched *s, __a_unused void *context) { int signum, ret; @@ -753,24 +748,24 @@ shutdown: static void register_signal_task(struct sched *s) { - struct signal_task *st = &signal_task_struct; - para_sigaction(SIGPIPE, SIG_IGN); - st->fd = para_signal_init(); - PARA_INFO_LOG("signal pipe: fd %d\n", st->fd); + signal_task = signal_init_or_die(); para_install_sighandler(SIGINT); para_install_sighandler(SIGTERM); para_install_sighandler(SIGHUP); - st->task.pre_select = signal_pre_select; - st->task.post_select = afs_signal_post_select; - sprintf(st->task.status, "signal task"); - register_task(s, &st->task); + signal_task->task = task_register(&(struct task_info) { + .name = "signal", + .pre_select = signal_pre_select, + .post_select = afs_signal_post_select, + .context = signal_task, + + }, s); } static struct list_head afs_client_list; -/** Describes on connected afs client. */ +/** Describes one connected afs client. */ struct afs_client { /** Position in the afs client list. */ struct list_head node; @@ -780,9 +775,9 @@ struct afs_client { struct timeval connect_time; }; -static void command_pre_select(struct sched *s, struct task *t) +static void command_pre_select(struct sched *s, void *context) { - struct command_task *ct = container_of(t, struct command_task, task); + struct command_task *ct = context; struct afs_client *client; para_fd_set(server_socket, &s->rfds, &s->max_fileno); @@ -815,8 +810,8 @@ int pass_buffer_as_shm(int fd, uint8_t band, const char *buf, size_t size) void *shm; struct callback_result *cr; - if (!buf || !size) - return 0; + if (size == 0) + assert(band != SBD_OUTPUT); ret = shm_new(size + sizeof(*cr)); if (ret < 0) return ret; @@ -827,7 +822,8 @@ int pass_buffer_as_shm(int fd, uint8_t band, const char *buf, size_t size) cr = shm; cr->result_size = size; cr->band = band; - memcpy(shm + sizeof(*cr), buf, size); + if (size > 0) + memcpy(shm + sizeof(*cr), buf, size); ret = shm_detach(shm); if (ret < 0) goto err; @@ -840,26 +836,43 @@ err: return ret; } -/* - * On errors, negative value is written to fd. - * On success: If query produced a result, the result_shmid is written to fd. - * Otherwise, zero is written. - */ static int call_callback(int fd, int query_shmid) { void *query_shm; struct callback_query *cq; - struct osl_object query; - int ret; + int ret, ret2; + struct afs_callback_arg aca = {.fd = fd}; ret = shm_attach(query_shmid, ATTACH_RW, &query_shm); if (ret < 0) return ret; cq = query_shm; - query.data = (char *)query_shm + sizeof(*cq); - query.size = cq->query_size; - cq->handler(fd, &query); - return shm_detach(query_shm); + aca.query.data = (char *)query_shm + sizeof(*cq); + aca.query.size = cq->query_size; + aca.pbout.max_size = shm_get_shmmax(); + aca.pbout.max_size_handler = afs_max_size_handler; + aca.pbout.private_data = &(struct afs_max_size_handler_data) { + .fd = fd, + .band = SBD_OUTPUT + }; + ret = cq->handler(&aca); + ret2 = shm_detach(query_shm); + if (ret2 < 0) { + if (ret < 0) /* ignore (but log) detach error */ + PARA_ERROR_LOG("could not detach sma: %s\n", + para_strerror(-ret2)); + else + ret = ret2; + } + flush_and_free_pb(&aca.pbout); + if (ret < 0) { + ret2 = pass_buffer_as_shm(fd, SBD_AFS_CB_FAILURE, + (const char *)&ret, sizeof(ret)); + if (ret2 < 0) + PARA_ERROR_LOG("could not pass cb failure packet: %s\n", + para_strerror(-ret)); + } + return ret; } static int execute_server_command(fd_set *rfds) @@ -917,14 +930,14 @@ 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, struct task *t) +static int command_post_select(struct sched *s, void *context) { - struct command_task *ct = container_of(t, struct command_task, task); + struct command_task *ct = context; struct sockaddr_un unix_addr; struct afs_client *client, *tmp; int fd, ret; - ret = task_get_notification(t); + ret = task_get_notification(ct->task); if (ret < 0) return ret; ret = execute_server_command(&s->rfds); @@ -972,10 +985,12 @@ static void register_command_task(uint32_t cookie, struct sched *s) ct->fd = setup_command_socket_or_die(); ct->cookie = cookie; - ct->task.pre_select = command_pre_select; - ct->task.post_select = command_post_select; - sprintf(ct->task.status, "afs command task"); - register_task(s, &ct->task); + ct->task = task_register(&(struct task_info) { + .name = "afs command", + .pre_select = command_pre_select, + .post_select = command_post_select, + .context = ct, + }, s); } /** @@ -1007,6 +1022,7 @@ __noreturn void afs_init(uint32_t cookie, int socket_fd) s.default_timeout.tv_sec = 0; s.default_timeout.tv_usec = 999 * 1000; ret = schedule(&s); + sched_shutdown(&s); out_close: close_afs_tables(); out: @@ -1015,11 +1031,10 @@ out: exit(EXIT_FAILURE); } -static void create_tables_callback(int fd, const struct osl_object *query) +static int com_init_callback(struct afs_callback_arg *aca) { - uint32_t table_mask = *(uint32_t *)query->data; + uint32_t table_mask = *(uint32_t *)aca->query.data; int i, ret; - struct para_buffer pb = {.buf = NULL}; close_afs_tables(); for (i = 0; i < NUM_AFS_TABLES; i++) { @@ -1030,17 +1045,19 @@ static void create_tables_callback(int fd, const struct osl_object *query) if (!t->create) continue; ret = t->create(database_dir); - if (ret < 0) + if (ret < 0) { + para_printf(&aca->pbout, "cannot create table %s\n", + t->name); goto out; - para_printf(&pb, "successfully created %s table\n", t->name); + } + para_printf(&aca->pbout, "successfully created %s table\n", + t->name); } ret = open_afs_tables(); -out: if (ret < 0) - para_printf(&pb, "%s\n", para_strerror(-ret)); - if (pb.buf) - pass_buffer_as_shm(fd, SBD_OUTPUT, pb.buf, pb.offset); - free(pb.buf); + para_printf(&aca->pbout, "cannot open afs tables\n"); +out: + return ret; } int com_init(struct command_context *cc) @@ -1068,9 +1085,8 @@ int com_init(struct command_context *cc) return -E_BAD_TABLE_NAME; } } - ret = send_callback_request(create_tables_callback, &query, + return send_callback_request(com_init_callback, &query, afs_cb_result_handler, cc); - return ret; } /** @@ -1084,7 +1100,9 @@ enum com_check_flags { /** Check the mood table. */ CHECK_MOODS = 2, /** Check the playlist table. */ - CHECK_PLAYLISTS = 4 + CHECK_PLAYLISTS = 4, + /** Check the attribute table against the audio file table. */ + CHECK_ATTS = 8 }; int com_check(struct command_context *cc) @@ -1104,6 +1122,10 @@ int com_check(struct command_context *cc) flags |= CHECK_AFT; continue; } + if (!strcmp(arg, "-A")) { + flags |= CHECK_ATTS; + continue; + } if (!strcmp(arg, "-p")) { flags |= CHECK_PLAYLISTS; continue; @@ -1124,6 +1146,12 @@ int com_check(struct command_context *cc) if (ret < 0) return ret; } + if (flags & CHECK_ATTS) { + ret = send_callback_request(attribute_check_callback, NULL, + afs_cb_result_handler, cc); + if (ret < 0) + return ret; + } if (flags & CHECK_PLAYLISTS) { ret = send_callback_request(playlist_check_callback, NULL, afs_cb_result_handler, cc); @@ -1146,10 +1174,14 @@ int com_check(struct command_context *cc) * \param pb May be \p NULL. * \param data Type depends on \a event. * - * This function calls the table handlers of all tables and passes \a pb and \a - * data verbatim. It's up to the handlers to interpret the \a data pointer. + * This function calls each table event handler, passing \a pb and \a data + * verbatim. It's up to the handlers to interpret the \a data pointer. If a + * handler returns negative, the loop is aborted. + * + * \return The (negative) error code of the first handler that failed, or non-negative + * if all handlers succeeded. */ -void afs_event(enum afs_events event, struct para_buffer *pb, +__must_check int afs_event(enum afs_events event, struct para_buffer *pb, void *data) { int i, ret; @@ -1159,10 +1191,13 @@ void afs_event(enum afs_events event, struct para_buffer *pb, if (!t->event_handler) continue; ret = t->event_handler(event, pb, data); - if (ret < 0) + if (ret < 0) { PARA_CRIT_LOG("table %s, event %d: %s\n", t->name, event, para_strerror(-ret)); + return ret; + } } + return 1; } /**