From 15fde2fd9f9e668b267ab4cfd9fc10db37804dcf Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Tue, 19 Oct 2021 21:41:45 +0200 Subject: [PATCH] New server command: ll to change the log level at runtime. This makes use of the infrastructure introduced in the previous patch. However, the implementation of the ll command for para_server is more involved than its audiod counterpart because in the server case we have to tell two different processes (server and afs) to change their log level while the calling process, the command handler, does not need to set the loglevel because it is about to exit anyway. For the inter-process communication we introduce a new field in the mmd shared memory area so that command handlers can read the current value or set a new value. The log level propagates from there via daemon_set_loglevel() to the server and afs processes during each iteration of the scheduler loop where para_log() will pick it up to set the log level threshold for subsequent log events. The si command handler currently refers to the argument of the --loglevel server option to include the log level in its output. With dynamic log levels this no longer works because it always prints the value from the command line or the config file rather than the run time log level. Since the new ll command also prints the loglevel when it is executed with no arguments, we simply remove this line from the si output and hope that nobody cares. The si command handler was the last user of the ENUM_STRING_VAL macro in command.c. Removing the macro also allows us to make CMD_PTR local to server.c and to remove the lopsub definitions of the server suite from command.c. However, we still include the lopsub definitions of the server *command* suite (server_cmd.lsg.h) of course. We let any authenticated user run the command with no arguments to report the current loglevel but require full privileges to change the loglevel. Thus, the check for sufficient privileges needs to be performed in the command handler. --- afs.c | 11 ++++++++++ client.c | 6 ++++++ command.c | 43 +++++++++++++++++++++++++++++++++++--- interactive.c | 4 ++++ m4/lls/include/com_ll.m4 | 1 + m4/lls/makefile | 1 + m4/lls/server_cmd.suite.m4 | 2 ++ server.c | 5 +++++ server.h | 15 ++----------- 9 files changed, 72 insertions(+), 16 deletions(-) diff --git a/afs.c b/afs.c index 71067025..21ab9ae8 100644 --- a/afs.c +++ b/afs.c @@ -24,6 +24,7 @@ #include "afs.h" #include "net.h" #include "server.h" +#include "daemon.h" #include "ipc.h" #include "list.h" #include "sched.h" @@ -979,6 +980,15 @@ static void register_command_task(struct sched *s) }, s); } +static int afs_select(int max_fileno, fd_set *readfds, fd_set *writefds, + struct timeval *timeout_tv) +{ + mutex_lock(mmd_mutex); + daemon_set_loglevel(mmd->loglevel); + mutex_unlock(mmd_mutex); + return para_select(max_fileno + 1, readfds, writefds, timeout_tv); +} + /** * Initialize the audio file selector process. * @@ -1003,6 +1013,7 @@ __noreturn void afs_init(int socket_fd) PARA_INFO_LOG("server_socket: %d\n", server_socket); init_admissible_files(OPT_STRING_VAL(AFS_INITIAL_MODE)); register_command_task(&s); + s.select_function = afs_select; s.default_timeout.tv_sec = 0; s.default_timeout.tv_usec = 999 * 1000; ret = write(socket_fd, "\0", 1); diff --git a/client.c b/client.c index 8caf4483..63f09659 100644 --- a/client.c +++ b/client.c @@ -246,6 +246,12 @@ I9E_DUMMY_COMPLETER(init); static struct i9e_completer completers[]; +static void ll_completer(struct i9e_completion_info *ci, + struct i9e_completion_result *cr) +{ + i9e_ll_completer(ci, cr); +} + static void help_completer(struct i9e_completion_info *ci, struct i9e_completion_result *cr) { diff --git a/command.c b/command.c index e3f12931..bb276a89 100644 --- a/command.c +++ b/command.c @@ -10,7 +10,6 @@ #include #include -#include "server.lsg.h" #include "para.h" #include "error.h" #include "lsu.h" @@ -391,7 +390,6 @@ static int com_si(struct command_context *cc, "server_pid: %d\n" "afs_pid: %d\n" "connections (active/accepted/total): %u/%u/%u\n" - "current loglevel: %s\n" "supported audio formats: %s\n", ut, mmd->num_played, (int)getppid(), @@ -399,7 +397,6 @@ static int com_si(struct command_context *cc, mmd->active_connections, mmd->num_commands, mmd->num_connects, - ENUM_STRING_VAL(LOGLEVEL), AUDIO_FORMAT_HANDLERS ); mutex_unlock(mmd_mutex); @@ -590,6 +587,46 @@ static int com_hup(__a_unused struct command_context *cc, } EXPORT_SERVER_CMD_HANDLER(hup); +static int com_ll(struct command_context *cc, struct lls_parse_result *lpr) +{ + unsigned ll, perms; + char *errctx; + const char *sev[] = {SEVERITIES}, *arg; + int ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx)); + + if (ret < 0) { + send_errctx(cc, errctx); + return ret; + } + if (lls_num_inputs(lpr) == 0) { /* reporting is an unprivileged op. */ + const char *severity; + mutex_lock(mmd_mutex); + severity = sev[mmd->loglevel]; + mutex_unlock(mmd_mutex); + return send_sb_va(&cc->scc, SBD_OUTPUT, "%s\n", severity); + } + /* + * Changing the loglevel changes the state of both the afs and the vss, + * so we require both AFS_WRITE and VSS_WRITE. + */ + perms = AFS_WRITE | VSS_WRITE; + if ((cc->u->perms & perms) != perms) + return -ERRNO_TO_PARA_ERROR(EPERM); + arg = lls_input(0, lpr); + for (ll = 0; ll < NUM_LOGLEVELS; ll++) + if (!strcmp(arg, sev[ll])) + break; + if (ll >= NUM_LOGLEVELS) + return -ERRNO_TO_PARA_ERROR(EINVAL); + PARA_INFO_LOG("new log level: %s\n", sev[ll]); + /* Ask the server and afs processes to adjust their log level. */ + mutex_lock(mmd_mutex); + mmd->loglevel = ll; + mutex_unlock(mmd_mutex); + return 1; +} +EXPORT_SERVER_CMD_HANDLER(ll); + static int com_term(__a_unused struct command_context *cc, __a_unused struct lls_parse_result *lpr) { diff --git a/interactive.c b/interactive.c index 209c5a36..069c0039 100644 --- a/interactive.c +++ b/interactive.c @@ -811,6 +811,10 @@ create_matches: * * \param ci See struct \ref i9e_completer. * \param cr See struct \ref i9e_completer. + * + * This is used by para_client and para_audioc which need the same completion + * primitive for the ll server/audiod command. Both define their own completer + * which is implemented as a trivial wrapper that calls this function. */ void i9e_ll_completer(struct i9e_completion_info *ci, struct i9e_completion_result *cr) diff --git a/m4/lls/include/com_ll.m4 b/m4/lls/include/com_ll.m4 index d100dfa8..d7576eae 100644 --- a/m4/lls/include/com_ll.m4 +++ b/m4/lls/include/com_ll.m4 @@ -1,5 +1,6 @@ [subcommand ll] purpose = Query or set the log level of the daemon + m4_ifelse(SUITE, `server_cmd', `aux_info = NO_PERMISSION_REQUIRED') non-opts-name = [severity] [description] If no argument is given, the command prints the severity string (one diff --git a/m4/lls/makefile b/m4/lls/makefile index daf6de92..a13f079e 100644 --- a/m4/lls/makefile +++ b/m4/lls/makefile @@ -11,6 +11,7 @@ $(lls_suite_dir)/%.suite: $(lls_m4_dir)/%.suite.m4 | $(lls_suite_dir) $(call SAY, M4 $<) $(M4) -Pg -I $(lls_m4_include_dir) -D GIT_VERSION=$(GIT_VERSION) \ -D COPYRIGHT_YEAR=$(COPYRIGHT_YEAR) -D LOGLEVELS=$(LOGLEVELS) \ + -D SUITE=$(basename $(notdir $<)) \ $< > $@ $(lls_suite_dir)/%.lsg.c: $(lls_suite_dir)/%.suite diff --git a/m4/lls/server_cmd.suite.m4 b/m4/lls/server_cmd.suite.m4 index f73d66b2..8200c624 100644 --- a/m4/lls/server_cmd.suite.m4 +++ b/m4/lls/server_cmd.suite.m4 @@ -169,6 +169,8 @@ aux_info_prefix = Permissions: playlists. Otherwise only the given tables are created. [/description] +m4_include(`com_ll.m4') + [subcommand jmp] purpose = reposition the current stream non-opts-name = n diff --git a/server.c b/server.c index b9be84ce..b9026ad6 100644 --- a/server.c +++ b/server.c @@ -172,6 +172,7 @@ static void init_ipc_or_die(void) mmd->active_connections = 0; mmd->vss_status_flags = VSS_NEXT; mmd->new_vss_status_flags = VSS_NEXT; + mmd->loglevel = OPT_UINT32_VAL(LOGLEVEL); return; destroy_mmd_mutex: mutex_destroy(mmd_mutex); @@ -180,6 +181,9 @@ err_out: exit(EXIT_FAILURE); } +/** Get a reference to the supercommand of para_server. */ +#define CMD_PTR (lls_cmd(0, server_suite)) + /** * (Re-)read the server configuration files. * @@ -622,6 +626,7 @@ static int server_select(int max_fileno, fd_set *readfds, fd_set *writefds, { int ret; + daemon_set_loglevel(mmd->loglevel); status_refresh(); mutex_unlock(mmd_mutex); ret = para_select(max_fileno + 1, readfds, writefds, timeout_tv); diff --git a/server.h b/server.h index da75d86b..10bb6172 100644 --- a/server.h +++ b/server.h @@ -73,6 +73,8 @@ struct misc_meta_data { char afs_mode_string[MAXLINE]; /** Used by the sender command. */ struct sender_command_data sender_cmd_data; + /** Set by the ll command. */ + int loglevel; /** Describes the current audio file. */ struct audio_file_data afd; }; @@ -80,15 +82,6 @@ struct misc_meta_data { extern pid_t afs_pid; extern struct lls_parse_result *server_lpr; -/** - * Get a reference to the supercommand of para_server. - * - * This is needed for parsing the command line and for the ENUM_STRING_VAL() - * macro below. The latter macro is used in command.c, so CMD_PTR() can not - * be made local to server.c. - */ -#define CMD_PTR (lls_cmd(0, server_suite)) - /** Get the parse result of an option to para_server. */ #define OPT_RESULT(_name) (lls_opt_result( \ LSG_SERVER_PARA_SERVER_OPT_ ## _name, server_lpr)) @@ -105,10 +98,6 @@ extern struct lls_parse_result *server_lpr; /** The (first) argument to a server option of type int32. */ #define OPT_INT32_VAL(_name) (lls_int32_val(0, OPT_RESULT(_name))) -/** Get the string which corresponds to an enum constant. */ -#define ENUM_STRING_VAL(_name) (lls_enum_string_val(OPT_UINT32_VAL(_name), \ - lls_opt(LSG_SERVER_PARA_SERVER_OPT_ ## _name, CMD_PTR))) - int handle_connect(int fd); void parse_config_or_die(bool reload); char *server_get_tasks(void); -- 2.39.2