X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=blobdiff_plain;f=afs.c;h=75b82c21317a718fbf42ee4c5abaaed61192fbf7;hp=4bc7871cac868a45a2d487cdf5761d9509f19bfa;hb=a85b3b947174c64ce06b4d6e438677055bf3f1ae;hpb=586e2d24be69bc0aca8ad6c2033a4f4d193f5372 diff --git a/afs.c b/afs.c index 4bc7871c..75b82c21 100644 --- a/afs.c +++ b/afs.c @@ -12,11 +12,14 @@ #include #include #include +#include #include #include #include +#include -#include "server.cmdline.h" +#include "server.lsg.h" +#include "server_cmd.lsg.h" #include "para.h" #include "error.h" #include "crypt.h" @@ -97,10 +100,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 +134,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; }; @@ -201,7 +205,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) { @@ -234,7 +238,7 @@ int send_callback_request(callback_function *f, struct osl_object *query, *(uint32_t *)buf = afs_socket_cookie; *(int *)(buf + sizeof(afs_socket_cookie)) = query_shmid; - ret = connect_local_socket(conf.afs_socket_arg); + ret = connect_local_socket(OPT_STRING_VAL(AFS_SOCKET)); if (ret < 0) goto out; fd = ret; @@ -279,73 +283,33 @@ out: } /** - * Send a callback request passing an options structure and an argument vector. + * Wrapper for send_callback_request() which passes a lopsub parse result. * - * \param options pointer to an arbitrary data structure. - * \param argc Argument count. - * \param argv Standard argument vector. * \param f The callback function. - * \param result_handler See \ref send_callback_request. - * \param private_result_data See \ref send_callback_request. + * \param cmd Needed for (de-)serialization. + * \param lpr Must match cmd. + * \param private_result_data Passed to send_callback_request(). * - * 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. + * This function serializes the parse result given by the lpr pointer into a + * buffer. The buffer is sent as the query to the afs process with the callback + * mechanism. * - * \return The return value of the underlying call to \ref - * send_callback_request(). - * - * \sa send_standard_callback_request(), send_callback_request(). + * \return The return value of the underlying call to send_callback_request(). */ -int send_option_arg_callback_request(struct osl_object *options, - int argc, char * const * const argv, callback_function *f, - callback_result_handler *result_handler, - void *private_result_data) +int send_lls_callback_request(afs_callback *f, + const struct lls_command * const cmd, + struct lls_parse_result *lpr, void *private_result_data) { - char *p; - int i, ret; - struct osl_object query = {.size = options? options->size : 0}; - - for (i = 0; i < argc; i++) - query.size += strlen(argv[i]) + 1; - query.data = para_malloc(query.size); - p = query.data; - if (options) { - memcpy(query.data, options->data, options->size); - p += options->size; - } - for (i = 0; i < argc; i++) { - strcpy(p, argv[i]); /* OK */ - p += strlen(argv[i]) + 1; - } - ret = send_callback_request(f, &query, result_handler, - private_result_data); - free(query.data); - return ret; -} + struct osl_object query; + char *buf = NULL; + int ret = lls_serialize_parse_result(lpr, cmd, &buf, &query.size); -/** - * Send a callback request with an argument vector only. - * - * \param argc The same meaning as in send_option_arg_callback_request(). - * \param argv The same meaning as in send_option_arg_callback_request(). - * \param f The same meaning as in send_option_arg_callback_request(). - * \param result_handler See \ref send_callback_request. - * \param private_result_data See \ref send_callback_request. - * - * This is similar to send_option_arg_callback_request(), but no options buffer - * is passed to the parent process. - * - * \return The return value of the underlying call to - * send_option_arg_callback_request(). - */ -int send_standard_callback_request(int argc, char * const * const argv, - callback_function *f, callback_result_handler *result_handler, - void *private_result_data) -{ - return send_option_arg_callback_request(NULL, argc, argv, f, result_handler, + assert(ret >= 0); + query.data = buf; + ret = send_callback_request(f, &query, afs_cb_result_handler, private_result_data); + free(buf); + return ret; } static int action_if_pattern_matches(struct osl_row *row, void *data) @@ -353,30 +317,37 @@ static int action_if_pattern_matches(struct osl_row *row, void *data) struct pattern_match_data *pmd = data; struct osl_object name_obj; const char *p, *name; - int ret = osl(osl_get_object(pmd->table, row, pmd->match_col_num, &name_obj)); - const char *pattern_txt = (const char *)pmd->patterns.data; + int i, ret; + ret = osl(osl_get_object(pmd->table, row, pmd->match_col_num, + &name_obj)); if (ret < 0) return ret; name = (char *)name_obj.data; if ((!name || !*name) && (pmd->pm_flags & PM_SKIP_EMPTY_NAME)) return 1; - 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); + if (lls_num_inputs(pmd->lpr) == 0) { + if (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) { + i = pmd->input_skip; + for (;;) { + if (i >= lls_num_inputs(pmd->lpr)) + break; + p = lls_input(i, pmd->lpr); ret = fnmatch(p, name, pmd->fnmatch_flags); - if (ret == FNM_NOMATCH) - continue; - if (ret) - return -E_FNMATCH; - ret = pmd->action(pmd->table, row, name, pmd->data); - if (ret >= 0) - pmd->num_matches++; - return ret; + if (ret != FNM_NOMATCH) { + if (ret != 0) + return -E_FNMATCH; + ret = pmd->action(pmd->table, row, name, pmd->data); + if (ret >= 0) + pmd->num_matches++; + return ret; + + } + i++; } return 1; } @@ -423,7 +394,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; @@ -496,7 +467,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; @@ -573,7 +544,7 @@ int afs_cb_result_handler(struct osl_object *result, uint8_t band, } } -void flush_and_free_pb(struct para_buffer *pb) +static void flush_and_free_pb(struct para_buffer *pb) { int ret; struct afs_max_size_handler_data *amshd = pb->private_data; @@ -587,61 +558,63 @@ void flush_and_free_pb(struct para_buffer *pb) free(pb->buf); } -static int com_select_callback(int fd, const struct osl_object *query) +static int com_select_callback(struct afs_callback_arg *aca) { - 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; + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT); + const char *arg; int num_admissible, 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(&pb, "%s\n", para_strerror(-ret)); - goto out; + 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); - if (ret < 0) { - 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) { - para_printf(&pb, "failed (%s), switching to dummy\n", - para_strerror(-ret)); - activate_mood_or_playlist(NULL, &num_admissible); - } - } else - 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\n", arg); + if (current_mop) { + int ret2; + para_printf(&aca->pbout, "switching back to %s\n", current_mop); + ret2 = activate_mood_or_playlist(current_mop, &num_admissible); + if (ret2 >= 0) + goto out; + 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: - flush_and_free_pb(&pb); - return 0; + para_printf(&aca->pbout, "activated %s (%d admissible files)\n", + current_mop? current_mop : "dummy mood", num_admissible); +free_lpr: + lls_free_parse_result(aca->lpr, cmd); + return ret; } -int com_select(struct command_context *cc) +static int com_select(struct command_context *cc, struct lls_parse_result *lpr) { - struct osl_object query; + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT); + char *errctx; + int ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx)); - if (cc->argc != 2) - return -E_AFS_SYNTAX; - query.data = cc->argv[1]; - query.size = strlen(cc->argv[1]) + 1; - return send_callback_request(com_select_callback, &query, - &afs_cb_result_handler, cc); + 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(char *arg) +static void init_admissible_files(const char *arg) { if (activate_mood_or_playlist(arg, NULL) < 0) activate_mood_or_playlist(NULL, NULL); /* always successful */ @@ -650,18 +623,13 @@ static void init_admissible_files(char *arg) static int setup_command_socket_or_die(void) { int ret, socket_fd; - char *socket_name = conf.afs_socket_arg; + const char *socket_name = OPT_STRING_VAL(AFS_SOCKET); unlink(socket_name); - ret = create_local_socket(socket_name, 0); + ret = create_local_socket(socket_name); if (ret < 0) { - ret = create_local_socket(socket_name, - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IWOTH); - if (ret < 0) { - PARA_EMERG_LOG("%s: %s\n", para_strerror(-ret), - socket_name); - exit(EXIT_FAILURE); - } + PARA_EMERG_LOG("%s: %s\n", para_strerror(-ret), socket_name); + exit(EXIT_FAILURE); } socket_fd = ret; PARA_INFO_LOG("listening on socket %s (fd %d)\n", socket_name, @@ -682,8 +650,8 @@ static char *database_dir; static void get_database_dir(void) { if (!database_dir) { - if (conf.afs_database_dir_given) - database_dir = para_strdup(conf.afs_database_dir_arg); + if (OPT_GIVEN(AFS_DATABASE_DIR)) + database_dir = para_strdup(OPT_STRING_VAL(AFS_DATABASE_DIR)); else { char *home = para_homedir(); database_dir = make_message( @@ -700,7 +668,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; } @@ -710,7 +678,7 @@ static int open_afs_tables(void) int i, ret; get_database_dir(); - PARA_NOTICE_LOG("opening %u osl tables in %s\n", NUM_AFS_TABLES, + PARA_NOTICE_LOG("opening %d 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); @@ -847,16 +815,22 @@ static int call_callback(int fd, int query_shmid) { void *query_shm; struct callback_query *cq; - struct osl_object query; 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; - ret = cq->handler(fd, &query); + 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 */ @@ -865,6 +839,7 @@ static int call_callback(int fd, int query_shmid) 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)); @@ -885,7 +860,7 @@ static int execute_server_command(fd_set *rfds) return ret; buf[n] = '\0'; if (strcmp(buf, "new")) - return -E_BAD_CMD; + return -ERRNO_TO_PARA_ERROR(EINVAL); return open_next_audio_file(); } @@ -1017,10 +992,17 @@ __noreturn void afs_init(uint32_t cookie, int socket_fd) goto out_close; PARA_INFO_LOG("server_socket: %d, afs_socket_cookie: %u\n", server_socket, (unsigned) cookie); - init_admissible_files(conf.afs_initial_mode_arg); + 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; + ret = write(socket_fd, "\0", 1); + if (ret != 1) { + if (ret == 0) + errno = EINVAL; + ret = -ERRNO_TO_PARA_ERROR(errno); + goto out_close; + } ret = schedule(&s); sched_shutdown(&s); out_close: @@ -1031,17 +1013,10 @@ out: exit(EXIT_FAILURE); } -static int 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 = { - .max_size = shm_get_shmmax(), - .private_data = &(struct afs_max_size_handler_data) { - .fd = fd, - .band = SBD_OUTPUT - } - }; close_afs_tables(); for (i = 0; i < NUM_AFS_TABLES; i++) { @@ -1052,35 +1027,39 @@ static int 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)); - flush_and_free_pb(&pb); - return 0; + para_printf(&aca->pbout, "cannot open afs tables\n"); +out: + return ret; } -int com_init(struct command_context *cc) +static int com_init(struct command_context *cc, struct lls_parse_result *lpr) { int i, j, ret; uint32_t table_mask = (1 << (NUM_AFS_TABLES + 1)) - 1; struct osl_object query = {.data = &table_mask, .size = sizeof(table_mask)}; + unsigned num_inputs = lls_num_inputs(lpr); ret = make_database_dir(); if (ret < 0) return ret; - if (cc->argc != 1) { + if (num_inputs > 0) { table_mask = 0; - for (i = 1; i < cc->argc; i++) { + for (i = 0; i < num_inputs; i++) { for (j = 0; j < NUM_AFS_TABLES; j++) { struct afs_table *t = &afs_tables[j]; - if (strcmp(cc->argv[i], t->name)) + if (strcmp(lls_input(i, lpr), t->name)) continue; table_mask |= (1 << j); break; @@ -1089,68 +1068,40 @@ int com_init(struct command_context *cc) return -E_BAD_TABLE_NAME; } } - return send_callback_request(create_tables_callback, &query, + return send_callback_request(com_init_callback, &query, afs_cb_result_handler, cc); } +EXPORT_SERVER_CMD_HANDLER(init); -/** - * Flags for the check command. - * - * \sa com_check(). - */ -enum com_check_flags { - /** Check the audio file table. */ - CHECK_AFT = 1, - /** Check the mood table. */ - CHECK_MOODS = 2, - /** Check the playlist table. */ - CHECK_PLAYLISTS = 4 -}; - -int com_check(struct command_context *cc) +static int com_check(struct command_context *cc, struct lls_parse_result *lpr) { - unsigned flags = 0; - int i, ret; + const struct lls_opt_result *r_a = SERVER_CMD_OPT_RESULT(CHECK, AFT, lpr); + const struct lls_opt_result *r_A = SERVER_CMD_OPT_RESULT(CHECK, ATTRIBUTE, lpr); + const struct lls_opt_result *r_m = SERVER_CMD_OPT_RESULT(CHECK, MOOD, lpr); + const struct lls_opt_result *r_p = SERVER_CMD_OPT_RESULT(CHECK, PLAYLIST, lpr); + bool noopt = !lls_opt_given(r_a) && !lls_opt_given(r_A) + && !lls_opt_given(r_m) && !lls_opt_given(r_p); + int ret; - for (i = 1; i < cc->argc; i++) { - const char *arg = cc->argv[i]; - if (arg[0] != '-') - break; - if (!strcmp(arg, "--")) { - i++; - break; - } - if (!strcmp(arg, "-a")) { - flags |= CHECK_AFT; - continue; - } - if (!strcmp(arg, "-p")) { - flags |= CHECK_PLAYLISTS; - continue; - } - if (!strcmp(arg, "-m")) { - flags |= CHECK_MOODS; - continue; - } - return -E_AFS_SYNTAX; - } - if (i < cc->argc) - return -E_AFS_SYNTAX; - if (!flags) - flags = ~0U; - if (flags & CHECK_AFT) { + if (noopt || lls_opt_given(r_a)) { ret = send_callback_request(aft_check_callback, NULL, afs_cb_result_handler, cc); if (ret < 0) return ret; } - if (flags & CHECK_PLAYLISTS) { + if (noopt || lls_opt_given(r_A)) { + ret = send_callback_request(attribute_check_callback, NULL, + afs_cb_result_handler, cc); + if (ret < 0) + return ret; + } + if (noopt || lls_opt_given(r_p)) { ret = send_callback_request(playlist_check_callback, NULL, afs_cb_result_handler, cc); if (ret < 0) return ret; } - if (flags & CHECK_MOODS) { + if (noopt || lls_opt_given(r_m)) { ret = send_callback_request(mood_check_callback, NULL, afs_cb_result_handler, cc); if (ret < 0) @@ -1158,6 +1109,7 @@ int com_check(struct command_context *cc) } return 1; } +EXPORT_SERVER_CMD_HANDLER(check); /** * The afs event dispatcher. @@ -1166,10 +1118,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; @@ -1179,10 +1135,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) - PARA_CRIT_LOG("table %s, event %d: %s\n", t->name, + if (ret < 0) { + PARA_CRIT_LOG("table %s, event %u: %s\n", t->name, event, para_strerror(-ret)); + return ret; + } } + return 1; } /**