diff --git a/lib/console.c b/lib/console.c index da5a919d18..dc6b0f2f76 100644 --- a/lib/console.c +++ b/lib/console.c @@ -29,9 +29,10 @@ #include GMutex console_lock; -gboolean console_present = TRUE; gboolean using_initial_console = TRUE; const gchar *console_prefix; +gint initial_console_fds[3]; +gint stolen_fds; /** * console_printf: @@ -54,7 +55,7 @@ console_printf(const gchar *fmt, ...) va_start(ap, fmt); g_vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); - if (console_is_present()) + if (console_is_initial()) fprintf(stderr, "%s: %s\n", console_prefix, buf); else { @@ -65,39 +66,75 @@ console_printf(const gchar *fmt, ...) } -/* NOTE: this is not synced with any changes and is just an indication whether we have a console */ +/* NOTE: this is not synced with any changes and is just an indication whether we already acquired the console */ gboolean -console_is_present(void) +console_is_initial(void) { gboolean result; /* the lock only serves a memory barrier but is not a real synchronization */ g_mutex_lock(&console_lock); - result = console_present; + result = using_initial_console; g_mutex_unlock(&console_lock); return result; } +GString * +_get_fn_names(gint fns) +{ + GString *result = g_string_new(NULL); + + if (fns & (1 << STDIN_FILENO)) + g_string_append(result, "stdin"); + if (fns & (1 << STDOUT_FILENO)) + { + if (result->len > 0) + g_string_append_c(result, ','); + g_string_append(result, "stdout"); + } + if (fns & (1 << STDERR_FILENO)) + { + if (result->len > 0) + g_string_append_c(result, ','); + g_string_append(result, "stderr"); + } + + return result; +} + /* re-acquire a console after startup using an array of fds */ gboolean -console_acquire_from_fds(gint fds[3]) +console_acquire_from_fds(gint fds[3], gint fds_to_steal) { - const gchar *takeover_message_on_old_console = "[Console taken over, no further output here]\n"; gboolean result = FALSE; g_mutex_lock(&console_lock); - if (console_present) - { - if (!using_initial_console) - goto exit; - (void) write(1, takeover_message_on_old_console, strlen(takeover_message_on_old_console)); - } + if (!using_initial_console) + goto exit; - dup2(fds[0], STDIN_FILENO); - dup2(fds[1], STDOUT_FILENO); - dup2(fds[2], STDERR_FILENO); + GString *stolen_fn_names = _get_fn_names(fds_to_steal); + gchar *takeover_message_on_old_console = g_strdup_printf("[Console(%s) taken over, no further output here]\n", + stolen_fn_names->str); + (void) write(STDOUT_FILENO, takeover_message_on_old_console, strlen(takeover_message_on_old_console)); + g_free(takeover_message_on_old_console); + g_string_free(stolen_fn_names, TRUE); + + stolen_fds = fds_to_steal; + + if (stolen_fds & (1 << STDIN_FILENO)) + initial_console_fds[0] = dup(STDIN_FILENO); + if (stolen_fds & (1 << STDOUT_FILENO)) + initial_console_fds[1] = dup(STDOUT_FILENO); + if (stolen_fds & (1 << STDERR_FILENO)) + initial_console_fds[2] = dup(STDERR_FILENO); + + if (stolen_fds & (1 << STDIN_FILENO)) + dup2(fds[0], STDIN_FILENO); + if (stolen_fds & (1 << STDOUT_FILENO)) + dup2(fds[1], STDOUT_FILENO); + if (stolen_fds & (1 << STDERR_FILENO)) + dup2(fds[2], STDERR_FILENO); - console_present = TRUE; using_initial_console = FALSE; result = TRUE; exit: @@ -108,38 +145,36 @@ console_acquire_from_fds(gint fds[3]) /** * console_release: * - * Use /dev/null as input/output/error. This function is idempotent, can be + * Restore input/output/error. This function is idempotent, can be * called any number of times without harm. **/ void console_release(void) { - gint devnull_fd; - g_mutex_lock(&console_lock); - if (!console_present) + if (using_initial_console) goto exit; - devnull_fd = open("/dev/null", O_RDONLY); - if (devnull_fd >= 0) + if (initial_console_fds[0] > 0) { - dup2(devnull_fd, STDIN_FILENO); - close(devnull_fd); + dup2(initial_console_fds[0], STDIN_FILENO); + close(initial_console_fds[0]); + initial_console_fds[0] = -1; } - devnull_fd = open("/dev/null", O_WRONLY); - if (devnull_fd >= 0) + if (initial_console_fds[1] > 0) { - dup2(devnull_fd, STDOUT_FILENO); - dup2(devnull_fd, STDERR_FILENO); - close(devnull_fd); + dup2(initial_console_fds[1], STDOUT_FILENO); + close(initial_console_fds[1]); + initial_console_fds[1] = -1; } - clearerr(stdin); - clearerr(stdout); - clearerr(stderr); - console_present = FALSE; - using_initial_console = FALSE; - + if (initial_console_fds[2] > 0) + { + dup2(initial_console_fds[2], STDERR_FILENO); + close(initial_console_fds[2]); + initial_console_fds[2] = -1; + } + using_initial_console = TRUE; exit: g_mutex_unlock(&console_lock); } @@ -149,6 +184,9 @@ console_global_init(const gchar *console_prefix_) { g_mutex_init(&console_lock); console_prefix = console_prefix_; + initial_console_fds[0] = -1; + initial_console_fds[1] = -1; + initial_console_fds[2] = -1; } void diff --git a/lib/console.h b/lib/console.h index c65347357b..7613eee498 100644 --- a/lib/console.h +++ b/lib/console.h @@ -29,8 +29,8 @@ void console_printf(const gchar *fmt, ...) __attribute__ ((format (printf, 1, 2))); -gboolean console_is_present(void); -gboolean console_acquire_from_fds(gint fds[3]); +gboolean console_is_initial(void); +gboolean console_acquire_from_fds(gint fds[3], gint fds_to_steal); void console_release(void); void console_global_init(const gchar *console_prefix); diff --git a/lib/mainloop-control.c b/lib/mainloop-control.c index 6f33f81bb7..56231d463f 100644 --- a/lib/mainloop-control.c +++ b/lib/mainloop-control.c @@ -35,6 +35,7 @@ #include "debugger/debugger-main.h" #include +#include static gboolean _control_process_log_level(const gchar *level, GString *result) @@ -119,26 +120,22 @@ _wait_until_peer_disappears(ControlConnection *cc, gint max_seconds, gboolean *c console_release(); } -static void -control_connection_attach(ControlConnection *cc, GString *command, gpointer user_data, gboolean *cancelled) +typedef struct _AttachCommandArgs { - MainLoop *main_loop = (MainLoop *) user_data; - gchar **cmds = g_strsplit(command->str, " ", 4); + gboolean start_debugger; + gint fds_to_steal; + gint n_seconds; + gboolean log_stderr; + gint log_level; +} AttachCommandArgs; - GString *result = g_string_sized_new(128); - gint n_seconds = -1; - gboolean start_debugger = FALSE; - struct - { - gboolean log_stderr; - gint log_level; - } old_values, new_values; - - old_values.log_stderr = log_stderr; - old_values.log_level = msg_get_log_level(); - new_values = old_values; +static gboolean +_parse_attach_command_args(GString *command, AttachCommandArgs *args, GString *result) +{ + gboolean success = FALSE; + gchar **cmds = g_strsplit(command->str, " ", 5); - if (!cmds[1]) + if (cmds[1] == NULL) { g_string_assign(result, "FAIL Invalid arguments received"); goto exit; @@ -146,22 +143,24 @@ control_connection_attach(ControlConnection *cc, GString *command, gpointer user if (g_str_equal(cmds[1], "STDIO")) { - ; + if (cmds[3]) + args->fds_to_steal = atoi(cmds[3]); } else if (g_str_equal(cmds[1], "LOGS")) { - new_values.log_stderr = TRUE; - if (cmds[3]) - new_values.log_level = msg_map_string_to_log_level(cmds[3]); - if (new_values.log_level < 0) - { - g_string_assign(result, "FAIL Invalid log level"); - goto exit; - } + args->log_stderr = TRUE; + /* NOTE: as log_stderr uses stderr (what a surprise) + * - we need to steal only the stderr + * - caller of the `attach logs` will get the stderr output as well, so + * they should redirect stderr to stdout if they want to capture it for + * further processing e.g. + * syslog-ng-ctl attach logs |& grep -i error + */ + args->fds_to_steal = (1 << STDERR_FILENO); } else if (g_str_equal(cmds[1], "DEBUGGER")) { - start_debugger = TRUE; + args->start_debugger = TRUE; } else { @@ -170,7 +169,47 @@ control_connection_attach(ControlConnection *cc, GString *command, gpointer user } if (cmds[2]) - n_seconds = atoi(cmds[2]); + args->n_seconds = atoi(cmds[2]); + + if (cmds[4]) + args->log_level = msg_map_string_to_log_level(cmds[4]); + if (args->log_level < 0) + { + g_string_assign(result, "FAIL Invalid log level"); + goto exit; + } + success = TRUE; + +exit: + g_strfreev(cmds); + return success; +} + +static void +control_connection_attach(ControlConnection *cc, GString *command, gpointer user_data, gboolean *cancelled) +{ + MainLoop *main_loop = (MainLoop *) user_data; + GString *result = g_string_sized_new(128); + struct + { + gboolean log_stderr; + gint log_level; + } old_values = + { + log_stderr, + msg_get_log_level() + }; + AttachCommandArgs cmd_args = + { + .start_debugger = FALSE, + .fds_to_steal = (1 << STDOUT_FILENO) | (1 << STDERR_FILENO), + .n_seconds = -1, + .log_stderr = old_values.log_stderr, + .log_level = old_values.log_level + }; + + if (!_parse_attach_command_args(command, &cmd_args, result)) + goto exit; gint fds[3]; gsize num_fds = G_N_ELEMENTS(fds); @@ -181,38 +220,34 @@ control_connection_attach(ControlConnection *cc, GString *command, gpointer user goto exit; } - if (!console_acquire_from_fds(fds)) + if (!console_acquire_from_fds(fds, cmd_args.fds_to_steal)) { - g_string_assign(result, - "FAIL Error acquiring console"); + g_string_assign(result, "FAIL Error acquiring console"); goto exit; } - log_stderr = new_values.log_stderr; - msg_set_log_level(new_values.log_level); + log_stderr = cmd_args.log_stderr; + msg_set_log_level(cmd_args.log_level); - if (start_debugger && !debugger_is_running()) + if (cmd_args.start_debugger && !debugger_is_running()) { //cfg_load_module(self->current_configuration, "mod-python"); debugger_start(main_loop, main_loop_get_current_config(main_loop)); } - _wait_until_peer_disappears(cc, n_seconds, cancelled); + _wait_until_peer_disappears(cc, cmd_args.n_seconds, cancelled); - if (start_debugger && debugger_is_running()) - { - debugger_stop(); - } + if (cmd_args.start_debugger && debugger_is_running()) + debugger_stop(); log_stderr = old_values.log_stderr; msg_set_log_level(old_values.log_level); - g_string_assign(result, "OK [console output ends here]"); -exit: + g_string_assign(result, "OK [Console output ends here]"); +exit: control_connection_send_batched_reply(cc, result); control_connection_send_close_batch(cc); - g_strfreev(cmds); } static void diff --git a/syslog-ng-ctl/commands/attach.c b/syslog-ng-ctl/commands/attach.c index 2bd18ac246..290b2f9def 100644 --- a/syslog-ng-ctl/commands/attach.c +++ b/syslog-ng-ctl/commands/attach.c @@ -21,12 +21,14 @@ * */ -#include "ctl-stats.h" +#include "commands.h" #include "syslog-ng.h" -static gint attach_options_seconds; +#include + +static gint attach_options_seconds = -1; static gchar *attach_options_log_level = NULL; -static gchar **attach_commands = NULL; +static gint attach_options_fds_to_steal = 0; static gboolean _store_log_level(const gchar *option_name, @@ -43,31 +45,41 @@ _store_log_level(const gchar *option_name, return FALSE; } -GOptionEntry attach_options[] = -{ - { "seconds", 0, 0, G_OPTION_ARG_INT, &attach_options_seconds, "amount of time to attach for", NULL }, - { "log-level", 0, 0, G_OPTION_ARG_CALLBACK, _store_log_level, "change syslog-ng log level", "" }, - { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &attach_commands, "attach mode: logs, debugger, stdio", NULL }, - { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL } -}; - -gint -slng_attach(int argc, char *argv[], const gchar *mode, GOptionContext *ctx) +static gboolean +_parse_fd_names(const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) { - GString *command = g_string_new("ATTACH"); - const gchar *attach_mode; + gboolean result = TRUE; + gchar **fds = g_strsplit(value, ",", 3); - if (attach_commands) + attach_options_fds_to_steal = 0; + for (gchar **fd = fds; *fd; fd++) { - if (attach_commands[1]) + if (g_str_equal(*fd, "stdin")) + attach_options_fds_to_steal |= (1 << STDIN_FILENO); + else if (g_str_equal(*fd, "stdout")) + attach_options_fds_to_steal |= (1 << STDOUT_FILENO); + else if (g_str_equal(*fd, "stderr")) + attach_options_fds_to_steal |= (1 << STDERR_FILENO); + else { - fprintf(stderr, "Too many arguments"); - return 1; + g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Unknown %s option value: %s", option_name, *fd); + result = FALSE; } - attach_mode = attach_commands[0]; } - else - attach_mode = "stdio"; + g_strfreev(fds); + return result; +} + +/* NOTE: this attach command handler uses the normal, automatic GLib Commandline Option Parser + * to parse the sub-command arguments with options, so there is no need to manually validate the sub-command and the options */ +gint +slng_attach(int argc, char *argv[], const gchar *mode, GOptionContext *ctx) +{ + GString *command = g_string_new("ATTACH"); + const gchar *attach_mode = mode ? : "stdio"; if (g_str_equal(attach_mode, "stdio")) g_string_append(command, " STDIO"); @@ -75,16 +87,40 @@ slng_attach(int argc, char *argv[], const gchar *mode, GOptionContext *ctx) g_string_append(command, " LOGS"); else if (g_str_equal(attach_mode, "debugger")) g_string_append(command, " DEBUGGER"); - else - { - fprintf(stderr, "Unknown attach mode\n"); - return 1; - } - g_string_append_printf(command, " %d", attach_options_seconds ? : -1); + g_string_append_printf(command, " %d", attach_options_seconds > 0 ? attach_options_seconds : -1); + g_string_append_printf(command, " %d", + attach_options_fds_to_steal > 0 ? attach_options_fds_to_steal : (1 << STDOUT_FILENO) | (1 << STDERR_FILENO)); if (attach_options_log_level) g_string_append_printf(command, " %s", attach_options_log_level); + gint result = attach_command(command->str); g_string_free(command, TRUE); return result; } + +#define SECONDS_OPTION_ENTRY OPTIONS_ENTRY("seconds", 's', 0, G_OPTION_ARG_INT, &attach_options_seconds, "amount of time to attach for", NULL) +#define LOG_LEVEL_OPTION_ENTRY OPTIONS_ENTRY("log-level", 'l', 0, G_OPTION_ARG_CALLBACK, _store_log_level, "change syslog-ng log level", "") + +const GOptionEntry attach_stdio_options[] = +{ + SECONDS_OPTION_ENTRY, + LOG_LEVEL_OPTION_ENTRY, + { "fds-to-steel", 'f', 0, G_OPTION_ARG_CALLBACK, _parse_fd_names, "which stdio file handlers to attach to, default is ", " in a comma separated list" }, + { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL } +}; + +GOptionEntry attach_logs_and_debugger_options[] = +{ + SECONDS_OPTION_ENTRY, + LOG_LEVEL_OPTION_ENTRY, + { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL } +}; + +CommandDescriptor attach_commands[] = +{ + { "stdio", attach_stdio_options, "Attach to syslog-ng console using the given file handlers", slng_attach }, + { "logs", attach_logs_and_debugger_options, "Attach to syslog-ng internal logs, which are normally redirected via stderr; for further processing, use another redirection, e.g., `syslog-ng-ctl attach logs |& grep -i error.`", slng_attach }, + { "debugger", attach_logs_and_debugger_options, "Start and attach to syslog-ng debugger", slng_attach }, + { NULL, NULL, NULL, NULL } +}; diff --git a/syslog-ng-ctl/commands/attach.h b/syslog-ng-ctl/commands/attach.h index b544924ff4..540c02efa3 100644 --- a/syslog-ng-ctl/commands/attach.h +++ b/syslog-ng-ctl/commands/attach.h @@ -26,7 +26,6 @@ #include "commands.h" -extern GOptionEntry attach_options[]; -gint slng_attach(int argc, char *argv[], const gchar *mode, GOptionContext *ctx); +extern CommandDescriptor attach_commands[]; #endif diff --git a/syslog-ng-ctl/commands/commands.h b/syslog-ng-ctl/commands/commands.h index f24c4f586a..4af1ab616e 100644 --- a/syslog-ng-ctl/commands/commands.h +++ b/syslog-ng-ctl/commands/commands.h @@ -29,6 +29,10 @@ #include +/* Though clang has no issues, but gcc is stricter about requiring initializer elements to be compile-time constants when initializing structures directly + * This helper macro is used to initialize GOptionEntry structures with the same values as the GOptionEntry initializers without having to repeat the initializer values */ +#define OPTIONS_ENTRY(long_name, short_name, flags, arg, arg_data, description, arg_description) { long_name, short_name, flags, arg, arg_data, description, arg_description } + extern GOptionEntry no_options[]; typedef struct _CommandDescriptor diff --git a/syslog-ng-ctl/syslog-ng-ctl.c b/syslog-ng-ctl/syslog-ng-ctl.c index 0e2fd45aa6..f01b028b3d 100644 --- a/syslog-ng-ctl/syslog-ng-ctl.c +++ b/syslog-ng-ctl/syslog-ng-ctl.c @@ -50,7 +50,7 @@ #endif static const gchar *control_name; -static void print_usage(const gchar *bin_name, CommandDescriptor *descriptors); +static void print_usage(const gchar *bin_name, const gchar *command, CommandDescriptor *descriptors); static gint slng_stop(int argc, char *argv[], const gchar *mode, GOptionContext *ctx) @@ -112,7 +112,8 @@ slng_export_config_graph(int argc, char *argv[], const gchar *mode, GOptionConte static CommandDescriptor modes[] = { - { "attach", attach_options, "Attach to a running syslog-ng instance", slng_attach, NULL }, + { "attach", no_options, "Attach to a running syslog-ng instance", NULL, attach_commands }, + // TODO: Add proper details of the sub-commands like attach and credentials have { "stats", stats_options, "Get syslog-ng statistics. Possible commands: csv, prometheus; default: csv", slng_stats, NULL }, { "verbose", verbose_options, "Enable/query verbose messages", slng_verbose, NULL }, { "debug", verbose_options, "Enable/query debug messages", slng_verbose, NULL }, @@ -121,6 +122,7 @@ static CommandDescriptor modes[] = { "stop", no_options, "Stop syslog-ng process", slng_stop, NULL }, { "reload", no_options, "Reload syslog-ng", slng_reload, NULL }, { "reopen", no_options, "Re-open of log destination files", slng_reopen, NULL }, + // TODO: Add proper details of the sub-commands like attach and credentials have { "query", query_options, "Query syslog-ng statistics. Possible commands: list, get, get --sum", slng_query, NULL }, { "show-license-info", license_options, "Show information about the license", slng_license, NULL }, { "credentials", no_options, "Credentials manager", NULL, credentials_commands }, @@ -132,30 +134,34 @@ static CommandDescriptor modes[] = }; static void -print_usage(const gchar *bin_name, CommandDescriptor *descriptors) +print_usage(const gchar *bin_name, const gchar *command, CommandDescriptor *descriptors) { gint mode; - - fprintf(stderr, "Syntax: %s [options]\nPossible commands are:\n", bin_name); + gboolean has_command = command && *command; + + // NOTE: this does not handle more than 2 commands, but currently we do not have such a case + fprintf(stderr, "Usage:\n %s%s%s <%s[command]> [options]\n\nPossible commands are:\n", + bin_name, + has_command ? " " : "", + has_command ? command : "", + has_command ? "" : "command "); for (mode = 0; descriptors[mode].mode; mode++) { fprintf(stderr, " %-20s %s\n", descriptors[mode].mode, descriptors[mode].description); } -} - -gboolean -_is_help(gchar *cmd) -{ - return g_str_equal(cmd, "--help"); + fprintf(stderr, + "\nFor detailed help of a given command use:\n %s --help|-h\n %s --help|-h \n\nFor options of the given command use:\n %s [option] --help|-h\n %s --help|-h [option]\n", + bin_name, bin_name, bin_name, bin_name); } static CommandDescriptor * -find_active_mode(CommandDescriptor descriptors[], gint *argc, char **argv, GString *cmdname_accumulator) +find_active_mode(GString *bin_name, CommandDescriptor descriptors[], gint *argc, char **argv, + GString *cmdname_accumulator) { const gchar *mode_string = get_mode(argc, &argv); if (!mode_string) { - print_usage(cmdname_accumulator->str, descriptors); + print_usage(bin_name->str, cmdname_accumulator->str, descriptors); exit(1); } @@ -163,11 +169,14 @@ find_active_mode(CommandDescriptor descriptors[], gint *argc, char **argv, GStri if (strcmp(descriptors[mode].mode, mode_string) == 0) { if (descriptors[mode].main) - return &descriptors[mode]; + { + g_string_append_printf(cmdname_accumulator, "%s%s", cmdname_accumulator->len > 0 ? " " : "", mode_string); + return &descriptors[mode]; + } g_assert(descriptors[mode].subcommands); - g_string_append_printf(cmdname_accumulator, " %s", mode_string); - return find_active_mode(descriptors[mode].subcommands, argc, argv, cmdname_accumulator); + g_string_append_printf(cmdname_accumulator, "%s%s", cmdname_accumulator->len > 0 ? " " : "", mode_string); + return find_active_mode(bin_name, descriptors[mode].subcommands, argc, argv, cmdname_accumulator); } return NULL; @@ -179,7 +188,7 @@ setup_help_context(const gchar *cmdname, CommandDescriptor *active_mode) if (!active_mode) return NULL; - GOptionContext *ctx = g_option_context_new(cmdname); + GOptionContext *ctx = g_option_context_new(cmdname ? : active_mode->mode); g_option_context_set_summary(ctx, active_mode->description); g_option_context_add_main_entries(ctx, active_mode->options, NULL); g_option_context_add_main_entries(ctx, slng_options, NULL); @@ -197,21 +206,16 @@ main(int argc, char *argv[]) control_name = get_installation_path_for(PATH_CONTROL_SOCKET); - if (argc > 1 && _is_help(argv[1])) - { - print_usage(argv[0], modes); - exit(0); - } - - GString *cmdname_accumulator = g_string_new(argv[0]); - CommandDescriptor *active_mode = find_active_mode(modes, &argc, argv, cmdname_accumulator); - GOptionContext *ctx = setup_help_context(cmdname_accumulator->str, active_mode); + GString *bin_name = g_string_new(g_path_get_basename(argv[0])); + GString *cmdname_accumulator = g_string_new(NULL); + CommandDescriptor *active_mode = find_active_mode(bin_name, modes, &argc, argv, cmdname_accumulator); + GOptionContext *ctx = setup_help_context(cmdname_accumulator->len > 0 ? cmdname_accumulator->str : NULL, active_mode); g_string_free(cmdname_accumulator, TRUE); if (!ctx) { - fprintf(stderr, "Unknown command\n"); - print_usage(argv[0], modes); + fprintf(stderr, "Unknown command\n\n"); + print_usage(bin_name->str, "", modes); exit(1); }