diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 0d5d983658..ba23d749a5 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -80,6 +80,7 @@ set (LIB_HEADERS atomic-gssize.h block-ref-parser.h cache.h + console.h cfg.h cfg-lexer.h cfg-lexer-subst.h @@ -152,6 +153,7 @@ set (LIB_HEADERS serialize.h service-management.h seqnum.h + stackdump.h str-format.h str-utils.h syslog-names.h @@ -179,6 +181,7 @@ set(LIB_SOURCES apphook.c block-ref-parser.c cache.c + console.c cfg.c cfg-args.c cfg-block.c @@ -247,6 +250,7 @@ set(LIB_SOURCES scratch-buffers.c serialize.c service-management.c + stackdump.c str-format.c str-utils.c syslog-names.c diff --git a/lib/Makefile.am b/lib/Makefile.am index 4a552ba080..2e65e2562f 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -97,6 +97,7 @@ pkginclude_HEADERS += \ lib/atomic-gssize.h \ lib/block-ref-parser.h \ lib/cache.h \ + lib/console.h \ lib/cfg.h \ lib/cfg-grammar.h \ lib/cfg-grammar-internal.h \ @@ -171,6 +172,7 @@ pkginclude_HEADERS += \ lib/service-management.h \ lib/seqnum.h \ lib/signal-handler.h \ + lib/stackdump.h \ lib/str-format.h \ lib/str-utils.h \ lib/syslog-names.h \ @@ -195,6 +197,7 @@ lib_libsyslog_ng_la_SOURCES = \ lib/apphook.c \ lib/block-ref-parser.c \ lib/cache.c \ + lib/console.c \ lib/cfg.c \ lib/cfg-args.c \ lib/cfg-block.c \ @@ -262,6 +265,7 @@ lib_libsyslog_ng_la_SOURCES = \ lib/scratch-buffers.c \ lib/serialize.c \ lib/service-management.c \ + lib/stackdump.c \ lib/str-format.c \ lib/str-utils.c \ lib/syslog-names.c \ diff --git a/lib/cfg-source.c b/lib/cfg-source.c index 4695c40225..50fec12965 100644 --- a/lib/cfg-source.c +++ b/lib/cfg-source.c @@ -55,25 +55,24 @@ _print_underline(const gchar *line, gint whitespace_before, gint number_of_caret } static void -_print_underlined_source_block(const CFG_LTYPE *yylloc, gchar **lines, gint error_index) +_print_underlined_source_block(const CFG_LTYPE *yylloc, gchar **lines, gsize num_lines, gint start_line) { gint line_ndx; gchar line_prefix[12]; - gint error_length = yylloc->last_line - yylloc->first_line + 1; - for (line_ndx = 0; lines[line_ndx]; line_ndx++) + for (line_ndx = 0; line_ndx < num_lines; line_ndx++) { - gint lineno = yylloc->first_line + line_ndx - error_index; + gint lineno = start_line + line_ndx; const gchar *line = lines[line_ndx]; gint line_len = strlen(line); gboolean line_ends_with_newline = line_len > 0 && line[line_len - 1] == '\n'; _format_source_prefix(line_prefix, sizeof(line_prefix), lineno, - line_ndx >= error_index && line_ndx < error_index + error_length); + lineno >= yylloc->first_line && lineno <= yylloc->last_line); fprintf(stderr, "%-8s%s%s", line_prefix, line, line_ends_with_newline ? "" : "\n"); - if (line_ndx == error_index) + if (lineno == yylloc->first_line) { /* print the underline right below the source line we just printed */ fprintf(stderr, "%-8s", line_prefix); @@ -84,20 +83,24 @@ _print_underlined_source_block(const CFG_LTYPE *yylloc, gchar **lines, gint erro multi_line ? strlen(&line[yylloc->first_column]) + 1 : yylloc->last_column - yylloc->first_column); } - else if (line_ndx >= error_index + CONTEXT) - break; } } static void -_report_file_location(const gchar *filename, const CFG_LTYPE *yylloc) +_report_file_location(const gchar *filename, const CFG_LTYPE *yylloc, gint start_line) { FILE *f; gint lineno = 0; gsize buflen = 65520; gchar *buf = g_malloc(buflen); GPtrArray *context = g_ptr_array_new(); - gint error_index = 0; + gint end_line = start_line + 2*CONTEXT; + + if (start_line <= 0) + { + start_line = yylloc->first_line > CONTEXT ? yylloc->first_line - CONTEXT : 1; + end_line = yylloc->first_line + CONTEXT; + } f = fopen(filename, "r"); if (f) @@ -105,49 +108,64 @@ _report_file_location(const gchar *filename, const CFG_LTYPE *yylloc) while (fgets(buf, buflen, f)) { lineno++; - if (lineno > (gint) yylloc->first_line + CONTEXT) + if (lineno > end_line) break; - else if (lineno < (gint) yylloc->first_line - CONTEXT) + else if (lineno < start_line) continue; - else if (lineno == yylloc->first_line) - error_index = context->len; g_ptr_array_add(context, g_strdup(buf)); } - /* NOTE: do we have the appropriate number of lines? */ - if (lineno <= yylloc->first_line) - goto exit; - g_ptr_array_add(context, NULL); fclose(f); } if (context->len > 0) - _print_underlined_source_block(yylloc, (gchar **) context->pdata, error_index); + _print_underlined_source_block(yylloc, (gchar **) context->pdata, context->len, start_line); -exit: g_free(buf); g_ptr_array_foreach(context, (GFunc) g_free, NULL); g_ptr_array_free(context, TRUE); } +/* this will report source content from the buffer, but use the line numbers + * of the file where the block was defined. + * + * buffer_* => tracks buffer related information + * file_* => tracks file related information + */ static void _report_buffer_location(const gchar *buffer_content, const CFG_LTYPE *file_lloc, const CFG_LTYPE *buf_lloc) { - gchar **lines = g_strsplit(buffer_content, "\n", buf_lloc->first_line + CONTEXT + 1); - gint num_lines = g_strv_length(lines); + gchar **buffer_lines = g_strsplit(buffer_content, "\n", buf_lloc->first_line + CONTEXT + 1); + gint buffer_num_lines = g_strv_length(buffer_lines); - if (num_lines <= buf_lloc->first_line) + if (buffer_num_lines <= buf_lloc->first_line) goto exit; - gint start = buf_lloc->first_line - 1 - CONTEXT; - gint error_index = CONTEXT; - if (start < 0) - { - error_index += start; - start = 0; - } - _print_underlined_source_block(file_lloc, &lines[start], error_index); + /* the line number in the file, which we report in the source dump, 1 based */ + gint range_backwards = CONTEXT; + if (file_lloc->first_line <= range_backwards) + range_backwards = file_lloc->first_line - 1; + + /* the index of the line in the buffer where we start printing 0-based */ + gint buffer_start_index = buf_lloc->first_line - 1 - range_backwards; + if (buffer_start_index < 0) + buffer_start_index = 0; + + _print_underlined_source_block(file_lloc, &buffer_lines[buffer_start_index], buffer_num_lines - buffer_start_index, + file_lloc->first_line - range_backwards); exit: - g_strfreev(lines); + g_strfreev(buffer_lines); +} + +gboolean +cfg_source_print_source_text(const gchar *filename, gint line, gint column, gint start_line) +{ + CFG_LTYPE yylloc = {0}; + + yylloc.name = filename; + yylloc.first_line = yylloc.last_line = line; + yylloc.first_column = yylloc.last_column = column; + _report_file_location(yylloc.name, &yylloc, start_line); + return TRUE; } gboolean @@ -155,7 +173,7 @@ cfg_source_print_source_context(CfgLexer *lexer, CfgIncludeLevel *level, const C { if (level->include_type == CFGI_FILE) { - _report_file_location(yylloc->name, yylloc); + _report_file_location(yylloc->name, yylloc, -1); } else if (level->include_type == CFGI_BUFFER) { diff --git a/lib/cfg-source.h b/lib/cfg-source.h index 51e81fbd7e..55313b3939 100644 --- a/lib/cfg-source.h +++ b/lib/cfg-source.h @@ -26,6 +26,8 @@ #include "cfg-lexer.h" +gboolean cfg_source_print_source_text(const gchar *filename, gint line, gint column, gint offset); + /* These functions are only available during parsing */ gboolean cfg_source_print_source_context(CfgLexer *lexer, CfgIncludeLevel *level, const CFG_LTYPE *yylloc); gboolean cfg_source_extract_source_text(CfgLexer *lexer, const CFG_LTYPE *yylloc, GString *result); diff --git a/lib/console.c b/lib/console.c new file mode 100644 index 0000000000..9a3ecb8b2e --- /dev/null +++ b/lib/console.c @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2002-2012 Balabit + * Copyright (c) 1998-2012 Balázs Scheidler + * Copyright (c) 2024 Balázs Scheidler + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + * + */ +#include "console.h" +#include +#include +#include +#include + +GMutex console_lock; +gboolean console_present = FALSE; +gboolean using_initial_console = TRUE; +const gchar *console_prefix; + +/** + * console_printf: + * @fmt: format string + * @...: arguments to @fmt + * + * This function sends a message to the client preferring to use the stderr + * channel as long as it is available and switching to using syslog() if it + * isn't. Generally the stderr channell will be available in the startup + * process and in the beginning of the first startup in the + * supervisor/daemon processes. Later on the stderr fd will be closed and we + * have to fall back to using the system log. + **/ +void +console_printf(const gchar *fmt, ...) +{ + gchar buf[2048]; + va_list ap; + + va_start(ap, fmt); + g_vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + if (console_is_present(FALSE)) + fprintf(stderr, "%s: %s\n", console_prefix, buf); + else + { + openlog(console_prefix, LOG_PID, LOG_DAEMON); + syslog(LOG_CRIT, "%s\n", buf); + closelog(); + } +} + + +/* NOTE: this is not synced with any changes and is just an indication whether we have a console */ +gboolean +console_is_present(gboolean exclude_initial) +{ + gboolean result; + /* the lock only serves a memory barrier but is not a real synchronization */ + g_mutex_lock(&console_lock); + if (exclude_initial && using_initial_console) + result = FALSE; + else + result = console_present; + g_mutex_unlock(&console_lock); + return result; +} + +/* set the current console to be our current stdin/out/err after we start up */ +void +console_acquire_from_stdio(void) +{ + g_assert(!console_is_present(FALSE)); + + g_mutex_lock(&console_lock); + console_present = TRUE; + g_mutex_unlock(&console_lock); +} + +/* re-acquire a console after startup using an array of fds */ +void +console_acquire_from_fds(gint fds[3]) +{ + const gchar *takeover_message_on_old_console = "[Console taken over, no further output here]\n"; + g_assert(!console_is_present(TRUE)); + + if (using_initial_console) + { + (void) write(1, takeover_message_on_old_console, strlen(takeover_message_on_old_console)); + } + + g_mutex_lock(&console_lock); + + dup2(fds[0], STDIN_FILENO); + dup2(fds[1], STDOUT_FILENO); + dup2(fds[2], STDERR_FILENO); + + console_present = TRUE; + using_initial_console = FALSE; + g_mutex_unlock(&console_lock); +} + +/** + * console_release: + * + * Use /dev/null as 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) + goto exit; + + devnull_fd = open("/dev/null", O_RDONLY); + if (devnull_fd >= 0) + { + dup2(devnull_fd, STDIN_FILENO); + close(devnull_fd); + } + devnull_fd = open("/dev/null", O_WRONLY); + if (devnull_fd >= 0) + { + dup2(devnull_fd, STDOUT_FILENO); + dup2(devnull_fd, STDERR_FILENO); + close(devnull_fd); + } + clearerr(stdin); + clearerr(stdout); + clearerr(stderr); + console_present = FALSE; + using_initial_console = FALSE; + +exit: + g_mutex_unlock(&console_lock); +} + +void +console_global_init(const gchar *console_prefix_) +{ + g_mutex_init(&console_lock); + console_prefix = console_prefix_; +} + +void +console_global_deinit(void) +{ + g_mutex_clear(&console_lock); +} diff --git a/lib/console.h b/lib/console.h new file mode 100644 index 0000000000..f1c6b719f6 --- /dev/null +++ b/lib/console.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2002-2012 Balabit + * Copyright (c) 1998-2012 Balázs Scheidler + * Copyright (c) 2024 Balázs Scheidler + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + * + */ +#ifndef SYSLOG_NG_CONSOLE_H_INCLUDED +#define SYSLOG_NG_CONSOLE_H_INCLUDED + +#include "syslog-ng.h" + +void console_printf(const gchar *fmt, ...) __attribute__ ((format (printf, 1, 2))); + +gboolean console_is_present(gboolean exclude_initial); +void console_acquire_from_fds(gint fds[3]); +void console_acquire_from_stdio(void); +void console_release(void); + +void console_global_init(const gchar *console_prefix); +void console_global_deinit(void); + +#endif diff --git a/lib/control/control-command-thread.c b/lib/control/control-command-thread.c index 9d5ed8b976..f97605f5f9 100644 --- a/lib/control/control-command-thread.c +++ b/lib/control/control-command-thread.c @@ -27,6 +27,7 @@ #include "messages.h" #include "secret-storage/secret-storage.h" #include "scratch-buffers.h" +#include "apphook.h" #include struct _ControlCommandThread @@ -44,6 +45,12 @@ struct _ControlCommandThread struct iv_event thread_finished; }; +gboolean +control_command_thread_relates_to_connection(ControlCommandThread *self, ControlConnection *cc) +{ + return self->connection == cc; +} + static void _on_thread_finished(gpointer user_data) { @@ -64,7 +71,7 @@ _thread(gpointer user_data) ControlCommandThread *self = (ControlCommandThread *) user_data; iv_init(); - scratch_buffers_allocator_init(); + app_thread_start(); msg_debug("Control command thread has started", evt_tag_str("control_command", self->command->str)); @@ -82,8 +89,8 @@ _thread(gpointer user_data) evt_tag_str("control_command", self->command->str)); scratch_buffers_explicit_gc(); - scratch_buffers_allocator_deinit(); control_command_thread_unref(self); + app_thread_stop(); iv_deinit(); } diff --git a/lib/control/control-command-thread.h b/lib/control/control-command-thread.h index 609247d320..cf8787694e 100644 --- a/lib/control/control-command-thread.h +++ b/lib/control/control-command-thread.h @@ -27,6 +27,8 @@ #include "control.h" +gboolean control_command_thread_relates_to_connection(ControlCommandThread *self, ControlConnection *cc); + void control_command_thread_run(ControlCommandThread *self); void control_command_thread_cancel(ControlCommandThread *self); const gchar *control_command_thread_get_command(ControlCommandThread *self); diff --git a/lib/control/control-connection.c b/lib/control/control-connection.c index 03819103cf..ed58460e81 100644 --- a/lib/control/control-connection.c +++ b/lib/control/control-connection.c @@ -36,6 +36,14 @@ _g_string_destroy(gpointer user_data) g_string_free(str, TRUE); } +gboolean +control_connection_get_attached_fds(ControlConnection *self, gint *fds, gsize *num_fds) +{ + if (self->get_attached_fds) + return self->get_attached_fds(self, fds, num_fds); + return FALSE; +} + static void _control_connection_free(ControlConnection *self) { @@ -215,7 +223,7 @@ control_connection_io_input(void *s) } else if (rc == 0) { - msg_debug("EOF on control channel, closing connection"); + msg_trace("EOF on control channel, closing connection"); goto destroy_connection; } else diff --git a/lib/control/control-connection.h b/lib/control/control-connection.h index 7d61fa8400..5ad25dd75d 100644 --- a/lib/control/control-connection.h +++ b/lib/control/control-connection.h @@ -41,6 +41,7 @@ struct _ControlConnection GString *output_buffer; gsize pos; ControlServer *server; + gboolean (*get_attached_fds)(ControlConnection *self, gint *fds, gsize *num_fds); gboolean (*run_command)(ControlConnection *self, ControlCommand *command_desc, GString *command_string); int (*read)(ControlConnection *self, gpointer buffer, gsize size); int (*write)(ControlConnection *self, gpointer buffer, gsize size); @@ -56,6 +57,7 @@ struct _ControlConnection }; +gboolean control_connection_get_attached_fds(ControlConnection *self, gint *fds, gsize *num_fds); gboolean control_connection_run_command(ControlConnection *self, GString *command_string); void control_connection_send_reply(ControlConnection *self, GString *reply); void control_connection_send_batched_reply(ControlConnection *self, GString *reply); diff --git a/lib/control/control-server-unix.c b/lib/control/control-server-unix.c index 726453f720..4b9db5c1d5 100644 --- a/lib/control/control-server-unix.c +++ b/lib/control/control-server-unix.c @@ -42,8 +42,21 @@ typedef struct _ControlConnectionUnix ControlConnection super; struct iv_fd control_io; gint fd; + /* stdin, stdout, stderr as passed by syslog-ng-ctl */ + gint attached_fds[3]; } ControlConnectionUnix; +static gboolean +control_connection_unix_get_attached_fds(ControlConnection *s, gint *fds, gsize *num_fds) +{ + ControlConnectionUnix *self = (ControlConnectionUnix *)s; + + g_assert(*num_fds >= 3); + memcpy(fds, self->attached_fds, sizeof(self->attached_fds)); + *num_fds = 3; + return TRUE; +} + gint control_connection_unix_write(ControlConnection *s, gpointer buffer, gsize size) { @@ -51,11 +64,58 @@ control_connection_unix_write(ControlConnection *s, gpointer buffer, gsize size) return write(self->control_io.fd, buffer, size); } +static gint +_extract_ancillary_data(ControlConnectionUnix *self, gint rc, struct msghdr *msg) +{ + if (G_UNLIKELY(msg->msg_flags & MSG_CTRUNC)) + { + msg_warning_once("WARNING: recvmsg() on control socket returned truncated control data", + evt_tag_int("control_len", msg->msg_controllen)); + return -1; + } + + for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL; cmsg = CMSG_NXTHDR(msg, cmsg)) + { + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) + { + gint header_len = CMSG_DATA(cmsg) - (unsigned char *) cmsg; + gint fd_array_size = (cmsg->cmsg_len - header_len); + + if (fd_array_size != sizeof(self->attached_fds)) + { + msg_warning_once("WARNING: invalid number of fds received on control socket", + evt_tag_int("fd_array_size", fd_array_size)); + return -1; + } + memcpy(&self->attached_fds, CMSG_DATA(cmsg), sizeof(self->attached_fds)); + break; + } + } + + return rc; +} + gint control_connection_unix_read(ControlConnection *s, gpointer buffer, gsize size) { ControlConnectionUnix *self = (ControlConnectionUnix *)s; - return read(self->control_io.fd, buffer, size); + gchar cmsg_buf[256]; + struct iovec iov[1] = + { + { .iov_base = buffer, .iov_len = size }, + }; + struct msghdr msg = + { + .msg_iov = iov, + .msg_iovlen = G_N_ELEMENTS(iov), + .msg_control = cmsg_buf, + .msg_controllen = sizeof(cmsg_buf), + }; + gint rc = recvmsg(self->control_io.fd, &msg, 0); + if (rc < 0) + return rc; + + return _extract_ancillary_data(self, rc, &msg); } static void @@ -114,6 +174,11 @@ control_connection_unix_free(ControlConnection *s) { ControlConnectionUnix *self = (ControlConnectionUnix *)s; close(self->control_io.fd); + for (gint i = 0; i < G_N_ELEMENTS(self->attached_fds); i++) + { + if (self->attached_fds[i] >= 0) + close(self->attached_fds[i]); + } } ControlConnection * @@ -129,6 +194,12 @@ control_connection_unix_new(ControlServer *server, gint sock) self->super.events.start_watches = control_connection_unix_start_watches; self->super.events.update_watches = control_connection_unix_update_watches; self->super.events.stop_watches = control_connection_unix_stop_watches; + self->super.get_attached_fds = control_connection_unix_get_attached_fds; + + for (gint i = 0; i < G_N_ELEMENTS(self->attached_fds); i++) + { + self->attached_fds[i] = -1; + } return &self->super; } @@ -152,7 +223,6 @@ _control_socket_accept(void *cookie) goto error; } - cc = control_connection_unix_new(&self->super, conn_socket); /* NOTE: with the call below, the reference to the control connection (cc) diff --git a/lib/control/control-server.c b/lib/control/control-server.c index 66a5c57867..058237467d 100644 --- a/lib/control/control-server.c +++ b/lib/control/control-server.c @@ -31,6 +31,16 @@ void _cancel_worker(gpointer data, gpointer user_data) { ControlCommandThread *thread = (ControlCommandThread *) data; + ControlConnection *cc = (ControlConnection *) user_data; + + if (cc && !control_command_thread_relates_to_connection(thread, cc)) + { + /* check if we relate to a specific connection and cancel only those. + * This is only used when a connection closed while the thread is + * still running. + */ + return; + } msg_warning("Requesting the cancellation of control command thread", evt_tag_str("control_command", control_command_thread_get_command(thread))); @@ -52,17 +62,21 @@ _cancel_worker(gpointer data, gpointer user_data) */ } -void -control_server_cancel_workers(ControlServer *self) +static void +control_server_cancel_workers(ControlServer *self, ControlConnection *cc) { if (self->worker_threads) { - msg_debug("Cancelling control server worker threads"); - g_list_foreach(self->worker_threads, _cancel_worker, NULL); - msg_debug("Control server worker threads have been cancelled"); + g_list_foreach(self->worker_threads, _cancel_worker, cc); } } +void +control_server_cancel_all_workers(ControlServer *self) +{ + control_server_cancel_workers(self, NULL); +} + void control_server_worker_started(ControlServer *self, ControlCommandThread *worker) { @@ -80,6 +94,7 @@ control_server_worker_finished(ControlServer *self, ControlCommandThread *worker void control_server_connection_closed(ControlServer *self, ControlConnection *cc) { + control_server_cancel_workers(self, cc); control_connection_stop_watches(cc); control_connection_unref(cc); } diff --git a/lib/control/control-server.h b/lib/control/control-server.h index 5c85e8c7f1..5d77c74279 100644 --- a/lib/control/control-server.h +++ b/lib/control/control-server.h @@ -38,7 +38,7 @@ struct _ControlServer void (*free_fn)(ControlServer *self); }; -void control_server_cancel_workers(ControlServer *self); +void control_server_cancel_all_workers(ControlServer *self); void control_server_connection_closed(ControlServer *self, ControlConnection *cc); void control_server_worker_started(ControlServer *self, ControlCommandThread *worker); void control_server_worker_finished(ControlServer *self, ControlCommandThread *worker); diff --git a/lib/debugger/debugger-main.c b/lib/debugger/debugger-main.c index 66309a5dff..275043f659 100644 --- a/lib/debugger/debugger-main.c +++ b/lib/debugger/debugger-main.c @@ -24,27 +24,75 @@ #include "debugger/debugger.h" #include "logpipe.h" +#include "mainloop-worker.h" +#include "mainloop-call.h" static Debugger *current_debugger; static gboolean _pipe_hook(LogPipe *s, LogMessage *msg, const LogPathOptions *path_options) { - if ((s->flags & PIF_CONFIG_RELATED) == 0) - return TRUE; - - if (msg->flags & LF_STATE_TRACING) - return debugger_perform_tracing(current_debugger, s, msg); - else + if (debugger_is_to_stop(current_debugger, s, msg)) return debugger_stop_at_breakpoint(current_debugger, s, msg); + else if (debugger_is_to_trace(current_debugger, s, msg)) + return debugger_perform_tracing(current_debugger, s, msg); + return TRUE; +} + +gboolean +debugger_is_running(void) +{ + return current_debugger != NULL; +} + +static void +_install_hook(gpointer user_data) +{ + /* NOTE: this is invoked via main_loop_worker_sync_call(), e.g. all workers are stopped */ + + pipe_single_step_hook = _pipe_hook; +} + +static gpointer +_attach_debugger(gpointer user_data) +{ + /* NOTE: this function is always run in the main thread via main_loop_call. */ + main_loop_worker_sync_call(_install_hook, NULL); + + debugger_start_console(current_debugger); + return NULL; +} + +static void +_remove_hook_and_clean_up_the_debugger(gpointer user_data) +{ + /* NOTE: this is invoked via main_loop_worker_sync_call(), e.g. all workers are stopped */ + + pipe_single_step_hook = NULL; + + Debugger *d = current_debugger; + current_debugger = NULL; + + debugger_exit(d); + debugger_free(d); +} + +static gpointer +_detach_debugger(gpointer user_data) +{ + main_loop_worker_sync_call(_remove_hook_and_clean_up_the_debugger, NULL); + return NULL; } void debugger_start(MainLoop *main_loop, GlobalConfig *cfg) { - /* we don't support threaded mode (yet), force it to non-threaded */ - cfg->threaded = FALSE; current_debugger = debugger_new(main_loop, cfg); - pipe_single_step_hook = _pipe_hook; - debugger_start_console(current_debugger); + main_loop_call(_attach_debugger, current_debugger, FALSE); +} + +void +debugger_stop(void) +{ + main_loop_call(_detach_debugger, NULL, FALSE); } diff --git a/lib/debugger/debugger-main.h b/lib/debugger/debugger-main.h index cd03143213..09862bd970 100644 --- a/lib/debugger/debugger-main.h +++ b/lib/debugger/debugger-main.h @@ -28,6 +28,8 @@ #include "debugger/debugger.h" #include "cfg.h" +gboolean debugger_is_running(void); void debugger_start(MainLoop *main_loop, GlobalConfig *cfg); +void debugger_stop(void); #endif diff --git a/lib/debugger/debugger.c b/lib/debugger/debugger.c index a677bc7839..1f3e404059 100644 --- a/lib/debugger/debugger.c +++ b/lib/debugger/debugger.c @@ -29,23 +29,47 @@ #include "mainloop.h" #include "timeutils/misc.h" #include "compat/time.h" +#include "scratch-buffers.h" +#include "cfg-source.h" +#include #include #include +#include struct _Debugger { + /* debugger_get_mode() assumes this comes as the first field */ + DebuggerMode mode; Tracer *tracer; + struct iv_signal sigint; MainLoop *main_loop; GlobalConfig *cfg; + GThread *debugger_thread; + BreakpointSite *breakpoint_site; + struct timespec last_trace_event; + gboolean starting_up; + + /* user interface related state */ gchar *command_buffer; + struct + { + gchar *filename; + gint line; + gint column; + gint list_start; + } current_location; LogTemplate *display_template; - LogMessage *current_msg; - LogPipe *current_pipe; - gboolean drop_current_message; - struct timespec last_trace_event; }; +static void +_set_command(Debugger *self, gchar *new_command) +{ + if (self->command_buffer) + g_free(self->command_buffer); + self->command_buffer = g_strdup(new_command); +} + static gboolean _format_nvpair(NVHandle handle, const gchar *name, @@ -106,43 +130,43 @@ _display_msg_with_template_string(Debugger *self, LogMessage *msg, const gchar * } static void -_display_source_line(LogExprNode *expr_node) +_set_current_location(Debugger *self, LogExprNode *expr_node) { - FILE *f; - gint lineno = 1; - gchar buf[1024]; - - if (!expr_node || !expr_node->filename) - return; - - f = fopen(expr_node->filename, "r"); - if (f) + g_free(self->current_location.filename); + if (expr_node) { - while (fgets(buf, sizeof(buf), f) && lineno < expr_node->line) - lineno++; - if (lineno != expr_node->line) - buf[0] = 0; - fclose(f); + self->current_location.filename = g_strdup(expr_node->filename); + self->current_location.line = expr_node->line; + self->current_location.column = expr_node->column; + self->current_location.list_start = expr_node->line - 5; } else { - buf[0] = 0; + memset(&self->current_location, 0, sizeof(self->current_location)); } - printf("%-8d %s", expr_node->line, buf); - if (buf[0] == 0 || buf[strlen(buf) - 1] != '\n') - putc('\n', stdout); - fflush(stdout); } +static void +_display_source_line(Debugger *self) +{ + if (self->current_location.filename) + cfg_source_print_source_text(self->current_location.filename, self->current_location.line, + self->current_location.column, self->current_location.list_start); + else + puts("Unable to list source, no current location set"); +} static gboolean _cmd_help(Debugger *self, gint argc, gchar *argv[]) { printf("syslog-ng interactive console, the following commands are available\n\n" " help, h, or ? Display this help\n" - " info Display information about the current execution state\n" " continue or c Continue until the next breakpoint\n" - " trace Display timing information as the message traverses the config\n" + " step or s Single step\n" + " follow or f Follow this message, ignoring any other breakpoints\n" + " trace or t Trace this message along the configuration\n" + " info Display information about the current execution state\n" + " list or l Display source code at the current location\n" " print, p Print the current log message\n" " drop, d Drop the current message\n" " quit, q Tell syslog-ng to exit\n" @@ -150,21 +174,16 @@ _cmd_help(Debugger *self, gint argc, gchar *argv[]) return TRUE; } -static gboolean -_cmd_continue(Debugger *self, gint argc, gchar *argv[]) -{ - return FALSE; -} static gboolean _cmd_print(Debugger *self, gint argc, gchar *argv[]) { if (argc == 1) - _display_msg_details(self, self->current_msg); + _display_msg_details(self, self->breakpoint_site->msg); else if (argc == 2) { GError *error = NULL; - if (!_display_msg_with_template_string(self, self->current_msg, argv[1], &error)) + if (!_display_msg_with_template_string(self, self->breakpoint_site->msg, argv[1], &error)) { printf("print: %s\n", error->message); g_clear_error(&error); @@ -195,24 +214,10 @@ _cmd_display(Debugger *self, gint argc, gchar *argv[]) static gboolean _cmd_drop(Debugger *self, gint argc, gchar *argv[]) { - self->drop_current_message = TRUE; + self->breakpoint_site->drop = TRUE; return FALSE; } -static gboolean -_cmd_trace(Debugger *self, gint argc, gchar *argv[]) -{ - self->current_msg->flags |= LF_STATE_TRACING; - return FALSE; -} - -static gboolean -_cmd_quit(Debugger *self, gint argc, gchar *argv[]) -{ - main_loop_exit(self->main_loop); - self->drop_current_message = TRUE; - return FALSE; -} static gboolean _cmd_info_pipe(Debugger *self, LogPipe *pipe) @@ -220,7 +225,7 @@ _cmd_info_pipe(Debugger *self, LogPipe *pipe) gchar buf[1024]; printf("LogPipe %p at %s\n", pipe, log_expr_node_format_location(pipe->expr_node, buf, sizeof(buf))); - _display_source_line(pipe->expr_node); + _display_source_line(self); return TRUE; } @@ -231,7 +236,7 @@ _cmd_info(Debugger *self, gint argc, gchar *argv[]) if (argc >= 2) { if (strcmp(argv[1], "pipe") == 0) - return _cmd_info_pipe(self, self->current_pipe); + return _cmd_info_pipe(self, self->breakpoint_site->pipe); } printf("info: List of info subcommands\n" @@ -239,12 +244,97 @@ _cmd_info(Debugger *self, gint argc, gchar *argv[]) return TRUE; } +static gboolean +_cmd_list(Debugger *self, gint argc, gchar *argv[]) +{ + gint shift = 11; + if (argc >= 2) + { + if (strcmp(argv[1], "+") == 0) + shift = 11; + else if (strcmp(argv[1], "-") == 0) + shift = -11; + else if (strcmp(argv[1], ".") == 0) + { + shift = 0; + if (self->breakpoint_site) + _set_current_location(self, self->breakpoint_site->pipe->expr_node); + } + else if (isdigit(argv[1][0])) + { + gint target_lineno = atoi(argv[1]); + if (target_lineno <= 0) + target_lineno = 1; + self->current_location.list_start = target_lineno; + } + /* drop any arguments for repeated execution */ + _set_command(self, "l"); + } + _display_source_line(self); + if (shift) + self->current_location.list_start += shift; + return TRUE; +} + +static inline void +_set_mode(Debugger *self, DebuggerMode new_mode, gboolean trace_message) +{ + self->mode = new_mode; + if (self->breakpoint_site) + { + if (trace_message) + self->breakpoint_site->msg->flags |= LF_STATE_TRACING; + else + self->breakpoint_site->msg->flags &= ~LF_STATE_TRACING; + } +} + +static gboolean +_cmd_continue(Debugger *self, gint argc, gchar *argv[]) +{ + _set_mode(self, DBG_WAITING_FOR_BREAKPOINT, FALSE); + return FALSE; +} + +static gboolean +_cmd_step(Debugger *self, gint argc, gchar *argv[]) +{ + _set_mode(self, DBG_WAITING_FOR_STEP, FALSE); + return FALSE; +} + +static gboolean +_cmd_trace(Debugger *self, gint argc, gchar *argv[]) +{ + clock_gettime(CLOCK_MONOTONIC, &self->last_trace_event); + _set_mode(self, DBG_FOLLOW_AND_TRACE, TRUE); + return FALSE; +} + +static gboolean +_cmd_follow(Debugger *self, gint argc, gchar *argv[]) +{ + _set_mode(self, DBG_FOLLOW_AND_BREAK, TRUE); + return FALSE; +} + +static gboolean +_cmd_quit(Debugger *self, gint argc, gchar *argv[]) +{ + _set_mode(self, DBG_QUIT, FALSE); + if (self->breakpoint_site) + self->breakpoint_site->drop = TRUE; + main_loop_exit(self->main_loop); + return FALSE; +} + typedef gboolean (*DebuggerCommandFunc)(Debugger *self, gint argc, gchar *argv[]); struct { const gchar *name; DebuggerCommandFunc command; + gboolean requires_breakpoint_site; } command_table[] = { { "help", _cmd_help }, @@ -252,15 +342,22 @@ struct { "?", _cmd_help }, { "continue", _cmd_continue }, { "c", _cmd_continue }, - { "print", _cmd_print }, - { "p", _cmd_print }, + { "step", _cmd_step }, + { "s", _cmd_step }, + { "follow", _cmd_follow, .requires_breakpoint_site = TRUE }, + { "f", _cmd_follow, .requires_breakpoint_site = TRUE }, + { "print", _cmd_print, .requires_breakpoint_site = TRUE }, + { "p", _cmd_print, .requires_breakpoint_site = TRUE }, + { "list", _cmd_list, }, + { "l", _cmd_list, }, { "display", _cmd_display }, - { "drop", _cmd_drop }, + { "drop", _cmd_drop, .requires_breakpoint_site = TRUE }, { "quit", _cmd_quit }, { "q", _cmd_quit }, - { "trace", _cmd_trace }, - { "info", _cmd_info }, - { "i", _cmd_info }, + { "trace", _cmd_trace, .requires_breakpoint_site = TRUE }, + { "t", _cmd_trace, .requires_breakpoint_site = TRUE }, + { "info", _cmd_info, .requires_breakpoint_site = TRUE }, + { "i", _cmd_info, .requires_breakpoint_site = TRUE }, { NULL, NULL } }; @@ -272,6 +369,7 @@ debugger_builtin_fetch_command(void) printf("(syslog-ng) "); fflush(stdout); + clearerr(stdin); if (!fgets(buf, sizeof(buf), stdin)) return NULL; @@ -293,6 +391,7 @@ debugger_register_command_fetcher(FetchCommandFunc fetcher) fetch_command_func = fetcher; } + static void _fetch_command(Debugger *self) { @@ -300,16 +399,8 @@ _fetch_command(Debugger *self) command = fetch_command_func(); if (command && strlen(command) > 0) - { - if (self->command_buffer) - g_free(self->command_buffer); - self->command_buffer = command; - } - else - { - if (command) - g_free(command); - } + _set_command(self, command); + g_free(command); } static gboolean @@ -318,6 +409,7 @@ _handle_command(Debugger *self) gint argc; gchar **argv; GError *error = NULL; + gboolean requires_breakpoint_site = TRUE; DebuggerCommandFunc command = NULL; if (!g_shell_parse_argv(self->command_buffer ? : "", &argc, &argv, &error)) @@ -332,6 +424,7 @@ _handle_command(Debugger *self) if (strcmp(command_table[i].name, argv[0]) == 0) { command = command_table[i].command; + requires_breakpoint_site = command_table[i].requires_breakpoint_site; break; } } @@ -340,6 +433,11 @@ _handle_command(Debugger *self) printf("Undefined command %s, try \"help\"\n", argv[0]); return TRUE; } + else if (requires_breakpoint_site && self->breakpoint_site == NULL) + { + printf("Running in interrupt context, command %s requires pipeline context\n", argv[0]); + return TRUE; + } gboolean result = command(self, argc, argv); g_strfreev(argv); return result; @@ -349,11 +447,21 @@ static void _handle_interactive_prompt(Debugger *self) { gchar buf[1024]; - LogPipe *current_pipe = self->current_pipe; - printf("Breakpoint hit %s\n", log_expr_node_format_location(current_pipe->expr_node, buf, sizeof(buf))); - _display_source_line(current_pipe->expr_node); - _display_msg_with_template(self, self->current_msg, self->display_template); + if (self->breakpoint_site) + { + LogPipe *current_pipe = self->breakpoint_site->pipe; + + _set_current_location(self, current_pipe->expr_node); + printf("Breakpoint hit %s\n", log_expr_node_format_location(current_pipe->expr_node, buf, sizeof(buf))); + _display_source_line(self); + _display_msg_with_template(self, self->breakpoint_site->msg, self->display_template); + } + else if (!self->starting_up) + { + _set_current_location(self, NULL); + printf(" Stopping on Interrupt...\n"); + } while (1) { _fetch_command(self); @@ -365,40 +473,99 @@ _handle_interactive_prompt(Debugger *self) printf("(continuing)\n"); } +static gboolean +_debugger_wait_for_event(Debugger *self) +{ + while (1) + { + if (!tracer_wait_for_event(self->tracer, &self->breakpoint_site)) + return FALSE; + + /* this is an interrupt, let's handle it now */ + if (!self->breakpoint_site) + return TRUE; + + /* is this an event we are still interested in? */ + if (debugger_is_to_stop(self, self->breakpoint_site->pipe, self->breakpoint_site->msg)) + return TRUE; + + /* not interesting now, let's resume and wait for another */ + tracer_resume_after_event(self->tracer, self->breakpoint_site); + } + return TRUE; +} + +static void +_debugger_ack_event(Debugger *self) +{ + tracer_resume_after_event(self->tracer, self->breakpoint_site); +} + static gpointer -_interactive_console_thread_func(Debugger *self) +_debugger_thread_func(Debugger *self) { app_thread_start(); - printf("Waiting for breakpoint...\n"); + self->breakpoint_site = NULL; + + printf("axosyslog interactive debugger\n" + "Copyright (c) 2024 Axoflow and contributors\n" + "License LGPLV2.1+ and GPLv2+\n\n" + "For help, type \"help\".\n"); + + self->starting_up = TRUE; + _handle_interactive_prompt(self); + self->starting_up = FALSE; while (1) { - tracer_wait_for_breakpoint(self->tracer); + if (!_debugger_wait_for_event(self)) + break; _handle_interactive_prompt(self); - tracer_resume_after_breakpoint(self->tracer); + + _debugger_ack_event(self); } + + scratch_buffers_explicit_gc(); app_thread_stop(); return NULL; } +static void +_interrupt(gpointer user_data) +{ + Debugger *self = (Debugger *) user_data; + + tracer_stop_on_interrupt(self->tracer); +} + void debugger_start_console(Debugger *self) { - g_thread_new(NULL, (GThreadFunc) _interactive_console_thread_func, self); + main_loop_assert_main_thread(); + + IV_SIGNAL_INIT(&self->sigint); + self->sigint.signum = SIGINT; + self->sigint.flags = IV_SIGNAL_FLAG_EXCLUSIVE; + self->sigint.cookie = self; + self->sigint.handler = _interrupt; + iv_signal_register(&self->sigint); + + self->debugger_thread = g_thread_new(NULL, (GThreadFunc) _debugger_thread_func, self); } gboolean debugger_stop_at_breakpoint(Debugger *self, LogPipe *pipe_, LogMessage *msg) { - self->drop_current_message = FALSE; - self->current_msg = log_msg_ref(msg); - self->current_pipe = log_pipe_ref(pipe_); - tracer_stop_on_breakpoint(self->tracer); - log_msg_unref(self->current_msg); - log_pipe_unref(self->current_pipe); - self->current_msg = NULL; - self->current_pipe = NULL; - return !self->drop_current_message; + BreakpointSite breakpoint_site = {0}; + msg_trace("Debugger: stopping at breakpoint", + log_pipe_location_tag(pipe_)); + + breakpoint_site.msg = log_msg_ref(msg); + breakpoint_site.pipe = log_pipe_ref(pipe_); + tracer_stop_on_breakpoint(self->tracer, &breakpoint_site); + log_msg_unref(breakpoint_site.msg); + log_pipe_unref(breakpoint_site.pipe); + return !breakpoint_site.drop; } gboolean @@ -417,6 +584,16 @@ debugger_perform_tracing(Debugger *self, LogPipe *pipe_, LogMessage *msg) return TRUE; } +void +debugger_exit(Debugger *self) +{ + main_loop_assert_main_thread(); + + iv_signal_unregister(&self->sigint); + tracer_cancel(self->tracer); + g_thread_join(self->debugger_thread); +} + Debugger * debugger_new(MainLoop *main_loop, GlobalConfig *cfg) { @@ -426,7 +603,7 @@ debugger_new(MainLoop *main_loop, GlobalConfig *cfg) self->tracer = tracer_new(cfg); self->cfg = cfg; self->display_template = log_template_new(cfg, NULL); - self->command_buffer = g_strdup("help"); + _set_command(self, "help"); log_template_compile(self->display_template, "$DATE $HOST $MSGHDR$MSG", NULL); return self; } @@ -434,6 +611,7 @@ debugger_new(MainLoop *main_loop, GlobalConfig *cfg) void debugger_free(Debugger *self) { + g_free(self->current_location.filename); log_template_unref(self->display_template); tracer_free(self->tracer); g_free(self->command_buffer); diff --git a/lib/debugger/debugger.h b/lib/debugger/debugger.h index 8742651d7f..12506fc8aa 100644 --- a/lib/debugger/debugger.h +++ b/lib/debugger/debugger.h @@ -27,18 +27,73 @@ #include "syslog-ng.h" #include "cfg.h" #include "mainloop.h" +#include "logpipe.h" + +typedef enum +{ + DBG_WAITING_FOR_STEP, + DBG_WAITING_FOR_BREAKPOINT, + DBG_FOLLOW_AND_BREAK, + DBG_FOLLOW_AND_TRACE, + DBG_QUIT, +} DebuggerMode; typedef struct _Debugger Debugger; -typedef gchar *(*FetchCommandFunc)(void); -Debugger *debugger_new(MainLoop *main_loop, GlobalConfig *cfg); -void debugger_free(Debugger *self); +static inline DebuggerMode +debugger_get_mode(Debugger *self) +{ + return *(DebuggerMode *) self; +} + +typedef gchar *(*FetchCommandFunc)(void); gchar *debugger_builtin_fetch_command(void); void debugger_register_command_fetcher(FetchCommandFunc fetcher); +void debugger_exit(Debugger *self); void debugger_start_console(Debugger *self); gboolean debugger_perform_tracing(Debugger *self, LogPipe *pipe, LogMessage *msg); gboolean debugger_stop_at_breakpoint(Debugger *self, LogPipe *pipe, LogMessage *msg); +Debugger *debugger_new(MainLoop *main_loop, GlobalConfig *cfg); +void debugger_free(Debugger *self); + +static inline gboolean +debugger_is_to_stop(Debugger *self, LogPipe *pipe, LogMessage *msg) +{ + DebuggerMode mode = debugger_get_mode(self); + + switch (mode) + { + case DBG_WAITING_FOR_BREAKPOINT: + return (pipe->flags & PIF_BREAKPOINT); + + case DBG_WAITING_FOR_STEP: + return TRUE; + + case DBG_FOLLOW_AND_BREAK: + return (msg->flags & LF_STATE_TRACING); + + case DBG_FOLLOW_AND_TRACE: + return FALSE; + + case DBG_QUIT: + return FALSE; + + default: + g_assert_not_reached(); + } + return FALSE; +} + +static inline gboolean +debugger_is_to_trace(Debugger *self, LogPipe *pipe, LogMessage *msg) +{ + DebuggerMode mode = debugger_get_mode(self); + + return (mode == DBG_FOLLOW_AND_TRACE) && (msg->flags & LF_STATE_TRACING); + +} + #endif diff --git a/lib/debugger/tracer.c b/lib/debugger/tracer.c index 39b6d8b11e..915e5e5f40 100644 --- a/lib/debugger/tracer.c +++ b/lib/debugger/tracer.c @@ -23,38 +23,109 @@ */ #include "debugger/tracer.h" +struct _Tracer +{ + GQueue *waiting_breakpoints; + GMutex breakpoint_mutex; + GCond breakpoint_cond; + + /* this condition variable is shared between all breakpoints, but each of + * them has its own "resume_requested" variable, this means that all + * resumes will wake up all waiting breakpoints, but only one of them will + * actually resume, it's not the most efficient implementation, but avoids + * us having to free the GCond instance as a thread is terminated. */ + + GCond resume_cond; + BreakpointSite *pending_breakpoint; + gboolean cancel_requested; +}; + +/* NOTE: called by workers to stop on a breakpoint, wait for the debugger to + * do its stuff and return to continue */ void -tracer_stop_on_breakpoint(Tracer *self) +tracer_stop_on_breakpoint(Tracer *self, BreakpointSite *breakpoint_site) { g_mutex_lock(&self->breakpoint_mutex); + if (self->cancel_requested) + goto exit; + breakpoint_site->resume_requested = FALSE; /* send break point */ - self->breakpoint_hit = TRUE; + g_queue_push_tail(self->waiting_breakpoints, breakpoint_site); g_cond_signal(&self->breakpoint_cond); - /* wait for resume */ - while (!self->resume_requested) + /* wait for resume or cancel */ + while (!(breakpoint_site->resume_requested || self->cancel_requested)) g_cond_wait(&self->resume_cond, &self->breakpoint_mutex); - self->resume_requested = FALSE; + +exit: g_mutex_unlock(&self->breakpoint_mutex); } void -tracer_wait_for_breakpoint(Tracer *self) +tracer_stop_on_interrupt(Tracer *self) +{ + g_mutex_lock(&self->breakpoint_mutex); + /* send interrupt signal as a NULL */ + g_queue_push_tail(self->waiting_breakpoints, NULL); + g_cond_signal(&self->breakpoint_cond); + g_mutex_unlock(&self->breakpoint_mutex); +} + +/* NOTE: called by the interactive debugger to wait for a breakpoint to + * trigger, a return of FALSE indicates that the tracing was cancelled */ +gboolean +tracer_wait_for_event(Tracer *self, BreakpointSite **breakpoint_site) { + gboolean cancelled = FALSE; g_mutex_lock(&self->breakpoint_mutex); - while (!self->breakpoint_hit) + while (g_queue_is_empty(self->waiting_breakpoints) && !self->cancel_requested) g_cond_wait(&self->breakpoint_cond, &self->breakpoint_mutex); - self->breakpoint_hit = FALSE; + + g_assert(self->pending_breakpoint == NULL); + *breakpoint_site = NULL; + if (!self->cancel_requested) + { + *breakpoint_site = self->pending_breakpoint = g_queue_pop_head(self->waiting_breakpoints); + } + else + { + cancelled = TRUE; + + /* cancel out threads waiting on breakpoint, e.g. in the cancelled + * case no need to call tracer_resume_after_breakpoint() */ + g_cond_broadcast(&self->resume_cond); + } g_mutex_unlock(&self->breakpoint_mutex); + return !cancelled; } +/* NOTE: called by the interactive debugger to resume the worker after a breakpoint */ void -tracer_resume_after_breakpoint(Tracer *self) +tracer_resume_after_event(Tracer *self, BreakpointSite *breakpoint_site) { g_mutex_lock(&self->breakpoint_mutex); - self->resume_requested = TRUE; - g_cond_signal(&self->resume_cond); + g_assert(self->pending_breakpoint == breakpoint_site); + if (self->pending_breakpoint) + { + /* we might be returning from an interrupt in which case + * pending_breakpoint is NULL, nothing to resume */ + + self->pending_breakpoint->resume_requested = TRUE; + self->pending_breakpoint = NULL; + g_cond_broadcast(&self->resume_cond); + } + g_mutex_unlock(&self->breakpoint_mutex); +} + +/* NOTE: called by any thread, not necessarily the debugger thread or worker + * threads. It cancels out the tracer_wait_for_breakpoint() calls */ +void +tracer_cancel(Tracer *self) +{ + g_mutex_lock(&self->breakpoint_mutex); + self->cancel_requested = TRUE; + g_cond_signal(&self->breakpoint_cond); g_mutex_unlock(&self->breakpoint_mutex); } @@ -66,7 +137,7 @@ tracer_new(GlobalConfig *cfg) g_mutex_init(&self->breakpoint_mutex); g_cond_init(&self->breakpoint_cond); g_cond_init(&self->resume_cond); - + self->waiting_breakpoints = g_queue_new(); return self; } @@ -76,5 +147,6 @@ tracer_free(Tracer *self) g_mutex_clear(&self->breakpoint_mutex); g_cond_clear(&self->breakpoint_cond); g_cond_clear(&self->resume_cond); + g_queue_free(self->waiting_breakpoints); g_free(self); } diff --git a/lib/debugger/tracer.h b/lib/debugger/tracer.h index 2893e277ce..b0b46f8bba 100644 --- a/lib/debugger/tracer.h +++ b/lib/debugger/tracer.h @@ -26,18 +26,23 @@ #include "syslog-ng.h" -typedef struct _Tracer +typedef struct _Tracer Tracer; + +/* struct to track the invocation of a breakpoint, we have an instance for each thread */ +typedef struct _BreakpointSite { - GMutex breakpoint_mutex; - GCond breakpoint_cond; - GCond resume_cond; - gboolean breakpoint_hit; gboolean resume_requested; -} Tracer; + LogMessage *msg; + LogPipe *pipe; + gboolean drop; +} BreakpointSite; + -void tracer_stop_on_breakpoint(Tracer *self); -void tracer_wait_for_breakpoint(Tracer *self); -void tracer_resume_after_breakpoint(Tracer *self); +void tracer_stop_on_interrupt(Tracer *self); +void tracer_stop_on_breakpoint(Tracer *self, BreakpointSite *breakpoint_site); +gboolean tracer_wait_for_event(Tracer *self, BreakpointSite **breakpoint_site); +void tracer_resume_after_event(Tracer *self, BreakpointSite *breakpoint_site); +void tracer_cancel(Tracer *self); Tracer *tracer_new(GlobalConfig *cfg); void tracer_free(Tracer *self); diff --git a/lib/gprocess.c b/lib/gprocess.c index 4ed3edec62..0e69c2c9ed 100644 --- a/lib/gprocess.c +++ b/lib/gprocess.c @@ -26,6 +26,8 @@ #include "userdb.h" #include "messages.h" #include "reloc.h" +#include "console.h" +#include "stackdump.h" #include #include @@ -99,7 +101,6 @@ static gint startup_result_pipe[2] = { -1, -1 }; /* pipe used to deliver initialization result to the supervisor */ static gint init_result_pipe[2] = { -1, -1 }; static GProcessKind process_kind = G_PK_STARTUP; -static gboolean stderr_present = TRUE; #if SYSLOG_NG_ENABLE_LINUX_CAPS static int have_capsyslog = FALSE; static cap_value_t cap_syslog; @@ -615,49 +616,14 @@ g_process_set_check(gint check_period, gboolean (*check_fn)(void)) process_opts.check_fn = check_fn; } - /** - * g_process_message: - * @fmt: format string - * @...: arguments to @fmt + * g_process_setup_console: * - * This function sends a message to the client preferring to use the stderr - * channel as long as it is available and switching to using syslog() if it - * isn't. Generally the stderr channell will be available in the startup - * process and in the beginning of the first startup in the - * supervisor/daemon processes. Later on the stderr fd will be closed and we - * have to fall back to using the system log. - **/ -void -g_process_message(const gchar *fmt, ...) -{ - gchar buf[2048]; - va_list ap; - - va_start(ap, fmt); - g_vsnprintf(buf, sizeof(buf), fmt, ap); - va_end(ap); - if (stderr_present) - fprintf(stderr, "%s: %s\n", process_opts.name, buf); - else - { - gchar name[32]; - - g_snprintf(name, sizeof(name), "%s/%s", process_kind == G_PK_SUPERVISOR ? "supervise" : "daemon", process_opts.name); - openlog(name, LOG_PID, LOG_DAEMON); - syslog(LOG_CRIT, "%s\n", buf); - closelog(); - } -} - -/** - * g_process_detach_tty: - * - * This function is called from g_process_start() to detach from the - * controlling tty. + * This function is called from g_process_start() to acquire the terminal as + * console or to detach from it in case we are in the background. **/ static void -g_process_detach_tty(void) +g_process_setup_console(void) { if (process_opts.mode != G_PM_FOREGROUND) { @@ -670,6 +636,10 @@ g_process_detach_tty(void) } setsid(); } + else + { + console_acquire_from_stdio(); + } } /** @@ -697,8 +667,8 @@ g_process_change_limits(void) } if (setrlimit(RLIMIT_NOFILE, &limit) < 0) - g_process_message("Error setting file number limit; limit='%d'; error='%s'", process_opts.fd_limit_min, - g_strerror(errno)); + console_printf("Error setting file number limit; limit='%d'; error='%s'", process_opts.fd_limit_min, + g_strerror(errno)); } /** @@ -710,24 +680,9 @@ g_process_change_limits(void) static void g_process_detach_stdio(void) { - gint devnull_fd; - - if (process_opts.mode != G_PM_FOREGROUND && stderr_present) + if (process_opts.mode != G_PM_FOREGROUND) { - devnull_fd = open("/dev/null", O_RDONLY); - if (devnull_fd >= 0) - { - dup2(devnull_fd, STDIN_FILENO); - close(devnull_fd); - } - devnull_fd = open("/dev/null", O_WRONLY); - if (devnull_fd >= 0) - { - dup2(devnull_fd, STDOUT_FILENO); - dup2(devnull_fd, STDERR_FILENO); - close(devnull_fd); - } - stderr_present = FALSE; + console_release(); } } @@ -742,7 +697,7 @@ g_process_set_dumpable(void) rc = prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); if (rc < 0) - g_process_message("Cannot set process to be dumpable; error='%s'", g_strerror(errno)); + console_printf("Cannot set process to be dumpable; error='%s'", g_strerror(errno)); } #endif } @@ -764,7 +719,7 @@ g_process_enable_core(void) limit.rlim_cur = limit.rlim_max = RLIM_INFINITY; if (setrlimit(RLIMIT_CORE, &limit) < 0) - g_process_message("Error setting core limit to infinity; error='%s'", g_strerror(errno)); + console_printf("Error setting core limit to infinity; error='%s'", g_strerror(errno)); } } @@ -822,7 +777,7 @@ g_process_write_pidfile(pid_t pid) } else { - g_process_message("Error creating pid file; file='%s', error='%s'", pidfile, g_strerror(errno)); + console_printf("Error creating pid file; file='%s', error='%s'", pidfile, g_strerror(errno)); } } @@ -842,7 +797,7 @@ g_process_remove_pidfile(void) if (unlink(pidfile) < 0) { - g_process_message("Error removing pid file; file='%s', error='%s'", pidfile, g_strerror(errno)); + console_printf("Error removing pid file; file='%s', error='%s'", pidfile, g_strerror(errno)); } } @@ -862,13 +817,13 @@ g_process_change_root(void) { if (chroot(process_opts.chroot_dir) < 0) { - g_process_message("Error in chroot(); chroot='%s', error='%s'\n", process_opts.chroot_dir, g_strerror(errno)); + console_printf("Error in chroot(); chroot='%s', error='%s'\n", process_opts.chroot_dir, g_strerror(errno)); return FALSE; } if (chdir("/") < 0) { - g_process_message("Error in chdir() after chroot; chroot='%s', error='%s'\n", process_opts.chroot_dir, - g_strerror(errno)); + console_printf("Error in chdir() after chroot; chroot='%s', error='%s'\n", process_opts.chroot_dir, + g_strerror(errno)); return FALSE; } } @@ -902,14 +857,14 @@ g_process_change_user(void) { if (setgid((gid_t) process_opts.gid) < 0) { - g_process_message("Error in setgid(); group='%s', gid='%d', error='%s'", process_opts.group, process_opts.gid, - g_strerror(errno)); + console_printf("Error in setgid(); group='%s', gid='%d', error='%s'", process_opts.group, process_opts.gid, + g_strerror(errno)); if (getuid() == 0) return FALSE; } if (process_opts.user && initgroups(process_opts.user, (gid_t) process_opts.gid) < 0) { - g_process_message("Error in initgroups(); user='%s', error='%s'", process_opts.user, g_strerror(errno)); + console_printf("Error in initgroups(); user='%s', error='%s'", process_opts.user, g_strerror(errno)); if (getuid() == 0) return FALSE; } @@ -919,8 +874,8 @@ g_process_change_user(void) { if (setuid((uid_t) process_opts.uid) < 0) { - g_process_message("Error in setuid(); user='%s', uid='%d', error='%s'", process_opts.user, process_opts.uid, - g_strerror(errno)); + console_printf("Error in setuid(); user='%s', uid='%d', error='%s'", process_opts.user, process_opts.uid, + g_strerror(errno)); if (getuid() == 0) return FALSE; } @@ -949,7 +904,7 @@ g_process_change_caps(void) if (cap == NULL) { - g_process_message("Error parsing capabilities: %s", process_opts.caps); + console_printf("Error parsing capabilities: %s", process_opts.caps); g_process_disable_caps(); return FALSE; } @@ -957,7 +912,7 @@ g_process_change_caps(void) { if (cap_set_proc(cap) == -1) { - g_process_message("Error setting capabilities, capability management disabled; error='%s'", g_strerror(errno)); + console_printf("Error setting capabilities, capability management disabled; error='%s'", g_strerror(errno)); g_process_disable_caps(); } @@ -983,13 +938,13 @@ g_process_resolve_names(void) gboolean result = TRUE; if (process_opts.user && !resolve_user(process_opts.user, &process_opts.uid)) { - g_process_message("Error resolving user; user='%s'", process_opts.user); + console_printf("Error resolving user; user='%s'", process_opts.user); process_opts.uid = -1; result = FALSE; } if (process_opts.group && !resolve_group(process_opts.group, &process_opts.gid)) { - g_process_message("Error resolving group; group='%s'", process_opts.group); + console_printf("Error resolving group; group='%s'", process_opts.group); process_opts.gid = -1; result = FALSE; } @@ -1018,8 +973,10 @@ g_process_change_dir(void) cwd = get_installation_path_for(SYSLOG_NG_PATH_PIDFILEDIR); if (cwd) - if (chdir(cwd)) - g_process_message("Error changing to directory=%s, errcode=%d", cwd, errno); + { + if (chdir(cwd)) + console_printf("Error changing to directory=%s, errcode=%d", cwd, errno); + } } /* this check is here to avoid having to change directory early in the startup process */ @@ -1029,8 +986,8 @@ g_process_change_dir(void) if (!getcwd(buf, sizeof(buf))) strncpy(buf, "unable-to-query", sizeof(buf)); - g_process_message("Unable to write to current directory, core dumps will not be generated; dir='%s', error='%s'", buf, - g_strerror(errno)); + console_printf("Unable to write to current directory, core dumps will not be generated; dir='%s', error='%s'", buf, + g_strerror(errno)); } } @@ -1170,14 +1127,14 @@ g_process_perform_supervise(void) { if (pipe(init_result_pipe) != 0) { - g_process_message("Error daemonizing process, cannot open pipe; error='%s'", g_strerror(errno)); + console_printf("Error daemonizing process, cannot open pipe; error='%s'", g_strerror(errno)); g_process_startup_failed(1, TRUE); } /* fork off a child process */ if ((pid = fork()) < 0) { - g_process_message("Error forking child process; error='%s'", g_strerror(errno)); + console_printf("Error forking child process; error='%s'", g_strerror(errno)); g_process_startup_failed(1, TRUE); } else if (pid != 0) @@ -1214,8 +1171,8 @@ g_process_perform_supervise(void) i++; } if (i == 6) - g_process_message("Initialization failed but the daemon did not exit, even when forced to, trying to recover; pid='%d'", - pid); + console_printf("Initialization failed but the daemon did not exit, even when forced to, trying to recover; pid='%d'", + pid); continue; } @@ -1237,7 +1194,7 @@ g_process_perform_supervise(void) if (!exited) { gint j = 0; - g_process_message("Daemon deadlock detected, killing process;"); + console_printf("Daemon deadlock detected, killing process;"); deadlock = TRUE; while (j < 6 && waitpid(pid, &rc, WNOHANG) == 0) @@ -1248,7 +1205,7 @@ g_process_perform_supervise(void) j++; } if (j == 6) - g_process_message("The daemon did not exit after deadlock, even when forced to, trying to recover; pid='%d'", pid); + console_printf("The daemon did not exit after deadlock, even when forced to, trying to recover; pid='%d'", pid); } } else @@ -1268,14 +1225,14 @@ g_process_perform_supervise(void) switch (npid) { case -1: - g_process_message("Could not fork for external notification; reason='%s'", strerror(errno)); + console_printf("Could not fork for external notification; reason='%s'", strerror(errno)); break; case 0: switch(fork()) { case -1: - g_process_message("Could not fork for external notification; reason='%s'", strerror(errno)); + console_printf("Could not fork for external notification; reason='%s'", strerror(errno)); exit(1); break; case 0: @@ -1303,7 +1260,7 @@ g_process_perform_supervise(void) argbuf, (deadlock || !WIFSIGNALED(rc) || WTERMSIG(rc) != SIGKILL) ? "restarting" : "not-restarting", (gchar *) NULL); - g_process_message("Could not execute external notification; reason='%s'", strerror(errno)); + console_printf("Could not execute external notification; reason='%s'", strerror(errno)); break; default: @@ -1317,18 +1274,18 @@ g_process_perform_supervise(void) } if (deadlock || !WIFSIGNALED(rc) || WTERMSIG(rc) != SIGKILL) { - g_process_message("Daemon exited due to a deadlock/signal/failure, restarting; exitcode='%d'", rc); + console_printf("Daemon exited due to a deadlock/signal/failure, restarting; exitcode='%d'", rc); sleep(1); } else { - g_process_message("Daemon was killed, not restarting; exitcode='%d'", rc); + console_printf("Daemon was killed, not restarting; exitcode='%d'", rc); break; } } else { - g_process_message("Daemon exited gracefully, not restarting; exitcode='%d'", rc); + console_printf("Daemon exited gracefully, not restarting; exitcode='%d'", rc); break; } } @@ -1350,6 +1307,14 @@ g_process_perform_supervise(void) exit(0); } + +static void +g_process_setup_fatal_signal_handler(void) +{ + stackdump_setup_signal(SIGSEGV); + stackdump_setup_signal(SIGABRT); +} + /** * g_process_start: * @@ -1361,7 +1326,7 @@ g_process_start(void) { pid_t pid; - g_process_detach_tty(); + g_process_setup_console(); g_process_change_limits(); if (!g_process_resolve_names()) { @@ -1373,13 +1338,13 @@ g_process_start(void) /* no supervisor, sends result to startup process directly */ if (pipe(init_result_pipe) != 0) { - g_process_message("Error daemonizing process, cannot open pipe; error='%s'", g_strerror(errno)); + console_printf("Error daemonizing process, cannot open pipe; error='%s'", g_strerror(errno)); exit(1); } if ((pid = fork()) < 0) { - g_process_message("Error forking child process; error='%s'", g_strerror(errno)); + console_printf("Error forking child process; error='%s'", g_strerror(errno)); exit(1); } else if (pid != 0) @@ -1410,13 +1375,13 @@ g_process_start(void) /* full blown startup/supervisor/daemon */ if (pipe(startup_result_pipe) != 0) { - g_process_message("Error daemonizing process, cannot open pipe; error='%s'", g_strerror(errno)); + console_printf("Error daemonizing process, cannot open pipe; error='%s'", g_strerror(errno)); exit(1); } /* first fork off supervisor process */ if ((pid = fork()) < 0) { - g_process_message("Error forking child process; error='%s'", g_strerror(errno)); + console_printf("Error forking child process; error='%s'", g_strerror(errno)); exit(1); } else if (pid != 0) @@ -1453,6 +1418,8 @@ g_process_start(void) g_assert_not_reached(); } + g_process_setup_fatal_signal_handler(); + /* daemon process, we should return to the caller to perform work */ /* Only call setsid() for backgrounded processes. */ if (process_opts.mode != G_PM_FOREGROUND) diff --git a/lib/gprocess.h b/lib/gprocess.h index 534be0bba2..e699782e7a 100644 --- a/lib/gprocess.h +++ b/lib/gprocess.h @@ -58,8 +58,6 @@ typedef gpointer cap_t; #endif -void g_process_message(const gchar *fmt, ...) G_GNUC_PRINTF(1, 2); - void g_process_set_mode(GProcessMode mode); GProcessMode g_process_get_mode(void); void g_process_set_name(const gchar *name); diff --git a/lib/logpipe.c b/lib/logpipe.c index 202d39f048..7e844fecc2 100644 --- a/lib/logpipe.c +++ b/lib/logpipe.c @@ -28,6 +28,59 @@ gboolean (*pipe_single_step_hook)(LogPipe *pipe, LogMessage *msg, const LogPathOptions *path_options); +void +log_pipe_forward_msg(LogPipe *self, LogMessage *msg, const LogPathOptions *path_options) +{ + if (self->pipe_next) + { + log_pipe_queue(self->pipe_next, msg, path_options); + } + else + { + log_msg_drop(msg, path_options, AT_PROCESSED); + } +} + +void +log_pipe_queue(LogPipe *s, LogMessage *msg, const LogPathOptions *path_options) +{ + LogPathOptions local_path_options; + g_assert((s->flags & PIF_INITIALIZED) != 0); + + if (G_UNLIKELY((s->flags & PIF_CONFIG_RELATED) != 0 && pipe_single_step_hook)) + { + if (!pipe_single_step_hook(s, msg, path_options)) + { + log_msg_drop(msg, path_options, AT_PROCESSED); + return; + } + } + + if ((s->flags & PIF_SYNC_FILTERX)) + filterx_eval_sync_message(path_options->filterx_context, &msg, path_options); + + if (G_UNLIKELY(s->flags & (PIF_HARD_FLOW_CONTROL | PIF_JUNCTION_END | PIF_CONDITIONAL_MIDPOINT))) + { + path_options = log_path_options_chain(&local_path_options, path_options); + if (s->flags & PIF_HARD_FLOW_CONTROL) + { + local_path_options.flow_control_requested = 1; + msg_trace("Requesting flow control", log_pipe_location_tag(s)); + } + if (s->flags & PIF_JUNCTION_END) + { + log_path_options_pop_junction(&local_path_options); + } + if (s->flags & PIF_CONDITIONAL_MIDPOINT) + { + log_path_options_pop_conditional(&local_path_options); + } + } + + s->queue(s, msg, path_options); +} + + EVTTAG * log_pipe_location_tag(LogPipe *pipe) { @@ -40,7 +93,8 @@ log_pipe_attach_expr_node(LogPipe *self, LogExprNode *expr_node) self->expr_node = log_expr_node_ref(expr_node); } -void log_pipe_detach_expr_node(LogPipe *self) +void +log_pipe_detach_expr_node(LogPipe *self) { if (!self->expr_node) return; @@ -79,7 +133,7 @@ log_pipe_init_instance(LogPipe *self, GlobalConfig *cfg) * log_msg_forward_msg. Since this is a common case, it is better * inlined (than to use an indirect call) for performance. */ - self->queue = NULL; + self->queue = log_pipe_forward_msg; self->free_fn = log_pipe_free_method; self->arcs = _arcs; } diff --git a/lib/logpipe.h b/lib/logpipe.h index b955be567c..8d7bcd03ae 100644 --- a/lib/logpipe.h +++ b/lib/logpipe.h @@ -75,6 +75,8 @@ /* sync filterx state and message in right before calling queue() */ #define PIF_SYNC_FILTERX 0x0200 +#define PIF_BREAKPOINT 0x0400 + /* private flags range, to be used by other LogPipe instances for their own purposes */ #define PIF_PRIVATE(x) ((x) << 16) @@ -428,68 +430,7 @@ log_pipe_post_config_init(LogPipe *s) return TRUE; } -static inline void -log_pipe_queue(LogPipe *s, LogMessage *msg, const LogPathOptions *path_options); - -static inline void -log_pipe_forward_msg(LogPipe *self, LogMessage *msg, const LogPathOptions *path_options) -{ - if (self->pipe_next) - { - log_pipe_queue(self->pipe_next, msg, path_options); - } - else - { - log_msg_drop(msg, path_options, AT_PROCESSED); - } -} - -static inline void -log_pipe_queue(LogPipe *s, LogMessage *msg, const LogPathOptions *path_options) -{ - LogPathOptions local_path_options; - g_assert((s->flags & PIF_INITIALIZED) != 0); - - if (G_UNLIKELY(pipe_single_step_hook)) - { - if (!pipe_single_step_hook(s, msg, path_options)) - { - log_msg_drop(msg, path_options, AT_PROCESSED); - return; - } - } - - if ((s->flags & PIF_SYNC_FILTERX)) - filterx_eval_sync_message(path_options->filterx_context, &msg, path_options); - if (G_UNLIKELY(s->flags & (PIF_HARD_FLOW_CONTROL | PIF_JUNCTION_END | PIF_CONDITIONAL_MIDPOINT))) - { - path_options = log_path_options_chain(&local_path_options, path_options); - if (s->flags & PIF_HARD_FLOW_CONTROL) - { - local_path_options.flow_control_requested = 1; - msg_trace("Requesting flow control", log_pipe_location_tag(s)); - } - if (s->flags & PIF_JUNCTION_END) - { - log_path_options_pop_junction(&local_path_options); - } - if (s->flags & PIF_CONDITIONAL_MIDPOINT) - { - log_path_options_pop_conditional(&local_path_options); - } - } - - if (s->queue) - { - s->queue(s, msg, path_options); - } - else - { - log_pipe_forward_msg(s, msg, path_options); - } - -} static inline LogPipe * log_pipe_clone(LogPipe *self) @@ -511,11 +452,11 @@ log_pipe_append(LogPipe *s, LogPipe *next) s->pipe_next = next; } -void -log_pipe_set_persist_name(LogPipe *self, const gchar *persist_name); +void log_pipe_set_persist_name(LogPipe *self, const gchar *persist_name); +const gchar *log_pipe_get_persist_name(const LogPipe *self); -const gchar * -log_pipe_get_persist_name(const LogPipe *self); +void log_pipe_queue(LogPipe *s, LogMessage *msg, const LogPathOptions *path_options); +void log_pipe_forward_msg(LogPipe *self, LogMessage *msg, const LogPathOptions *path_options); void log_pipe_set_options(LogPipe *self, const LogPipeOptions *options); void log_pipe_set_internal(LogPipe *self, gboolean internal); diff --git a/lib/logreader.c b/lib/logreader.c index c8747e4635..0f43d34752 100644 --- a/lib/logreader.c +++ b/lib/logreader.c @@ -389,6 +389,11 @@ log_reader_work_finished(void *s, gpointer arg) self->notify_code = 0; log_pipe_notify(self->control, notify_code, self); + + if (notify_code == NC_CLOSE && (self->options->flags & LR_EXIT_ON_EOF)) + { + cfg_shutdown(log_pipe_get_config(s)); + } } if ((self->super.super.flags & PIF_INITIALIZED) && self->proto) { @@ -573,7 +578,11 @@ log_reader_io_handle_in(gpointer s) log_reader_disable_watches(self); if ((self->options->flags & LR_THREADED)) { - main_loop_io_worker_job_submit(&self->io_job, NULL); + if (!main_loop_io_worker_job_submit(&self->io_job, NULL)) + { + log_reader_set_immediate_check(self); + log_reader_update_watches(self); + } } else { @@ -868,6 +877,7 @@ CfgFlagHandler log_reader_flag_handlers[] = { "empty-lines", CFH_SET, offsetof(LogReaderOptions, flags), LR_EMPTY_LINES }, { "threaded", CFH_SET, offsetof(LogReaderOptions, flags), LR_THREADED }, { "ignore-aux-data", CFH_SET, offsetof(LogReaderOptions, flags), LR_IGNORE_AUX_DATA }, + { "exit-on-eof", CFH_SET, offsetof(LogReaderOptions, flags), LR_EXIT_ON_EOF }, { NULL }, }; diff --git a/lib/logreader.h b/lib/logreader.h index 6c633314c8..2d3b51f75e 100644 --- a/lib/logreader.h +++ b/lib/logreader.h @@ -39,6 +39,7 @@ #define LR_EMPTY_LINES 0x0004 #define LR_IGNORE_AUX_DATA 0x0008 #define LR_THREADED 0x0040 +#define LR_EXIT_ON_EOF 0x0080 /* options */ diff --git a/lib/mainloop-control.c b/lib/mainloop-control.c index 8b94f9db4b..b0404e4d70 100644 --- a/lib/mainloop-control.c +++ b/lib/mainloop-control.c @@ -31,6 +31,8 @@ #include "secret-storage/secret-storage.h" #include "cfg-walker.h" #include "logpipe.h" +#include "console.h" +#include "debugger/debugger-main.h" #include @@ -104,6 +106,101 @@ control_connection_message_log(ControlConnection *cc, GString *command, gpointer control_connection_send_reply(cc, result); } +void +_wait_until_console_is_released_or_peer_disappears(ControlConnection *cc, gint max_seconds, gboolean *cancelled) +{ + while (console_is_present(FALSE) && max_seconds != 0 && !(*cancelled)) + { + sleep(1); + if (max_seconds > 0) + max_seconds--; + control_connection_send_batched_reply(cc, g_string_new("ALIVE\n")); + } + console_release(); +} + +static void +control_connection_attach(ControlConnection *cc, GString *command, gpointer user_data, gboolean *cancelled) +{ + MainLoop *main_loop = (MainLoop *) user_data; + gchar **cmds = g_strsplit(command->str, " ", 4); + + GString *result = g_string_sized_new(128); + gint n_seconds = -1; + gboolean start_debugger = FALSE; + struct + { + gboolean log_stderr; + gint log_level; + } old_values; + + old_values.log_stderr = log_stderr; + old_values.log_level = msg_get_log_level(); + + if (!cmds[1]) + { + g_string_assign(result, "FAIL Invalid arguments received"); + goto exit; + } + + if (g_str_equal(cmds[1], "STDIO")) + { + ; + } + else if (g_str_equal(cmds[1], "LOGS")) + { + log_stderr = TRUE; + if (cmds[3] && !_control_process_log_level(cmds[3], result)) + goto exit; + } + else if (g_str_equal(cmds[1], "DEBUGGER")) + { + start_debugger = TRUE; + } + else + { + g_string_assign(result, "FAIL This version of syslog-ng only supports attaching to STDIO"); + goto exit; + } + + if (cmds[2]) + n_seconds = atoi(cmds[2]); + + if (console_is_present(TRUE)) + { + g_string_assign(result, "FAIL A console is already attached to syslog-ng, only one console is allowed"); + goto exit; + } + + gint fds[3]; + gsize num_fds = G_N_ELEMENTS(fds); + if (!control_connection_get_attached_fds(cc, fds, &num_fds) || num_fds != 3) + { + g_string_assign(result, + "FAIL The underlying transport for syslog-ng-ctl does not support fd passing or incorrect number of fds received"); + goto exit; + } + console_acquire_from_fds(fds); + + if (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_console_is_released_or_peer_disappears(cc, n_seconds, cancelled); + if (start_debugger && debugger_is_running()) + { + debugger_stop(); + } + g_string_assign(result, "OK [console output ends here]"); + log_stderr = old_values.log_stderr; + msg_set_log_level(old_values.log_level); +exit: + control_connection_send_batched_reply(cc, result); + control_connection_send_close_batch(cc); + g_strfreev(cmds); +} + static void control_connection_stop_process(ControlConnection *cc, GString *command, gpointer user_data, gboolean *cancelled) { @@ -452,6 +549,7 @@ export_config_graph(ControlConnection *cc, GString *command, gpointer user_data, ControlCommand default_commands[] = { + { "ATTACH", control_connection_attach, .threaded = TRUE }, { "LOG", control_connection_message_log }, { "STOP", control_connection_stop_process }, { "RELOAD", control_connection_reload }, diff --git a/lib/mainloop.c b/lib/mainloop.c index 405625ff39..f03490277b 100644 --- a/lib/mainloop.c +++ b/lib/mainloop.c @@ -463,7 +463,7 @@ main_loop_exit_initiate(gpointer user_data) if (main_loop_is_terminating(self)) return; - control_server_cancel_workers(self->control_server); + control_server_cancel_all_workers(self->control_server); app_pre_shutdown(); @@ -550,7 +550,6 @@ _register_signal_handler(struct iv_signal *signal_poll, gint signum, void (*hand { IV_SIGNAL_INIT(signal_poll); signal_poll->signum = signum; - signal_poll->flags = IV_SIGNAL_FLAG_EXCLUSIVE; signal_poll->cookie = user_data; signal_poll->handler = handler; iv_signal_register(signal_poll); diff --git a/lib/scratch-buffers.c b/lib/scratch-buffers.c index 494d2a73ce..6c5b6fa520 100644 --- a/lib/scratch-buffers.c +++ b/lib/scratch-buffers.c @@ -245,10 +245,17 @@ _thread_maintenance_update_time(void) void scratch_buffers_lazy_update_stats(void) { - if (_thread_maintenance_period_elapsed()) + if (iv_inited()) + { + if (_thread_maintenance_period_elapsed()) + { + scratch_buffers_update_stats(); + _thread_maintenance_update_time(); + } + } + else { scratch_buffers_update_stats(); - _thread_maintenance_update_time(); } } diff --git a/lib/stackdump.c b/lib/stackdump.c new file mode 100644 index 0000000000..2e7f323168 --- /dev/null +++ b/lib/stackdump.c @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2024 Balázs Scheidler + * Copyright (c) 2024 Axoflow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + * + */ +#include "stackdump.h" +#include "console.h" + +#include +#include +#include +#include + +#ifdef __linux__ + +/* this is Linux only for now */ + +static void +_stackdump_print_stack(gpointer stack_pointer) +{ + guint8 *p = stack_pointer; + + for (gint i = 0; i < 16; i++) + { + gchar line[51] = {0}; + for (gint j = 0; j < 8; j++) + { + sprintf(&line[j*3], "%02x ", (guint) *(p+j)); + } + line[8*3] = ' '; + for (gint j = 8; j < 16; j++) + { + sprintf(&line[j*3 + 1], "%02x ", (guint) *(p+j)); + } + + console_printf("Stack %p: %s", p, line); + p += 16; + } +} + +/* should not do any allocation to allow this to work even if our heap is corrupted */ +static void +_stackdump_print_maps(void) +{ + int fd; + + console_printf("Maps file follows"); + fd = open("/proc/self/maps", O_RDONLY); + if (fd < 0) + { + console_printf("Error opening /proc/self/maps"); + return; + } + + gchar buf[1024]; + int rc; + gchar *p, *eol; + gint avail, end = 0; + + while (1) + { + avail = sizeof(buf) - end; + rc = read(fd, buf + end, avail); + + if (rc < 0) + break; + + end += rc; + + if (rc == 0) + break; + + p = buf; + while (*p && p < (buf + end)) + { + eol = memchr(p, '\n', buf + end - p); + if (eol) + { + *eol = 0; + console_printf("%s", p); + p = eol + 1; + } + else + { + end = end - (p - buf); + memmove(buf, p, end); + break; + } + } + } + if (end) + console_printf("%.*s", end, buf); + close(fd); +} + +static inline void +_stackdump_print_backtrace(void) +{ + void *bt[256]; + gint count; + + count = backtrace(bt, 256); + console_printf("Raw backtrace dump, count=%d", count); + for (gint i = 0; i < count; i++) + { + console_printf("[%d]: %p", i, bt[i]); + } + if (count) + { + gchar **symbols; + + console_printf("Symbol backtrace dump, count=%d", count); + symbols = backtrace_symbols(bt, count); + for (gint i = 0; i < count; i++) + { + console_printf("[%d]: %s", i, symbols[i]); + } + } +} + + +#ifdef __x86_64__ +/**************************************************************************** + * + * + * x86_64 support + * + * + ****************************************************************************/ + +void +_stackdump_print_registers(struct sigcontext *p) +{ + console_printf( + "Registers: " + "rax=%016lx rbx=%016lx rcx=%016lx rdx=%016lx rsi=%016lx rdi=%016lx " + "rbp=%016lx rsp=%016lx r8=%016lx r9=%016lx r10=%016lx r11=%016lx " + "r12=%016lx r13=%016lx r14=%016lx r15=%016lx rip=%016lx", + p->rax, p->rbx, p->rcx, p->rdx, p->rsi, p->rdi, p->rbp, p->rsp, p->r8, p->r9, p->r10, p->r11, p->r12, p->r13, p->r14, p->r15, p->rip); + _stackdump_print_stack((gpointer) p->rsp); +} + +#elif __x86__ +/**************************************************************************** + * + * + * i386 support + * + * + ****************************************************************************/ + +void +_stackdump_print_registers(struct sigcontext *p) +{ + console_printf( + "Registers: eax=%08lx ebx=%08lx ecx=%08lx edx=%08lx esi=%08lx edi=%08lx ebp=%08lx esp=%08lx eip=%08lx", + p->eax, p->ebx, p->ecx, p->edx, p->esi, p->edi, p->ebp, p->esp, p->eip); + _stackdump_print_stack((gpointer) p->esp); +} + +#else +/**************************************************************************** + * + * + * unsupported platform + * + * + ****************************************************************************/ + +static void +_stackdump_print_registers(struct sigcontext *p) +{ +} + +#endif + + +static inline void +_stackdump_log(struct sigcontext *p) +{ + /* the order is important here, even if it might be illogical. The + * backtrace function is the most fragile (as backtrace_symbols() may + * allocate memory). Let's log everything else first, and then we can + * produce the backtrace, which is potentially causing another crash. */ + + _stackdump_print_registers(p); + _stackdump_print_maps(); + _stackdump_print_backtrace(); +} + +static void +_fatal_signal_handler(int signo, siginfo_t *info, void *uc) +{ + struct ucontext_t *ucontext = (struct ucontext_t *) uc; + struct sigcontext *p = (struct sigcontext *) &ucontext->uc_mcontext; + struct sigaction act; + + memset(&act, 0, sizeof(act)); + act.sa_handler = SIG_DFL; + sigaction(signo, &act, NULL); + + console_printf("Fatal signal received, stackdump follows, signal=%d", signo); + _stackdump_log(p); + /* let's get a stacktrace as well */ + kill(getpid(), signo); +} + +void +stackdump_setup_signal(gint signal_number) +{ + struct sigaction act; + + memset(&act, 0, sizeof(act)); + act.sa_flags = SA_SIGINFO; + act.sa_sigaction = _fatal_signal_handler; + + sigaction(signal_number, &act, NULL); +} + +#else + +void +stackdump_setup_signal(gint signal_number) +{ +} + +#endif diff --git a/lib/stackdump.h b/lib/stackdump.h new file mode 100644 index 0000000000..7942f651c9 --- /dev/null +++ b/lib/stackdump.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 Balázs Scheidler + * Copyright (c) 2024 Axoflow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + * + */ +#ifndef STACKDUMP_H_INCLUDED +#define STACKDUMP_H_INCLUDED + +#include "syslog-ng.h" + +void stackdump_setup_signal(gint signal_number); + +#endif diff --git a/modules/affile/affile-source.c b/modules/affile/affile-source.c index 6d84d33a06..db8d605875 100644 --- a/modules/affile/affile-source.c +++ b/modules/affile/affile-source.c @@ -91,7 +91,7 @@ affile_sd_queue(LogPipe *s, LogMessage *msg, const LogPathOptions *path_options) log_src_driver_queue_method(s, msg, path_options); } -static gboolean +gboolean affile_sd_init(LogPipe *s) { AFFileSourceDriver *self = (AFFileSourceDriver *) s; diff --git a/modules/affile/affile-source.h b/modules/affile/affile-source.h index 50e9e1257e..1e673239e8 100644 --- a/modules/affile/affile-source.h +++ b/modules/affile/affile-source.h @@ -41,6 +41,7 @@ typedef struct _AFFileSourceDriver gsize transport_name_len; } AFFileSourceDriver; +gboolean affile_sd_init(LogPipe *s); void affile_sd_set_transport_name(AFFileSourceDriver *s, const gchar *transport_name); AFFileSourceDriver *affile_sd_new_instance(gchar *filename, GlobalConfig *cfg); LogDriver *affile_sd_new(gchar *filename, GlobalConfig *cfg); diff --git a/modules/affile/file-reader.c b/modules/affile/file-reader.c index 4c6af0ae29..7439d6dd53 100644 --- a/modules/affile/file-reader.c +++ b/modules/affile/file-reader.c @@ -267,8 +267,6 @@ file_reader_notify_method(LogPipe *s, gint notify_code, gpointer user_data) switch (notify_code) { case NC_CLOSE: - if (self->options->exit_on_eof) - cfg_shutdown(log_pipe_get_config(s)); break; case NC_FILE_MOVED: diff --git a/modules/affile/file-reader.h b/modules/affile/file-reader.h index 0091908242..05a8795c05 100644 --- a/modules/affile/file-reader.h +++ b/modules/affile/file-reader.h @@ -32,7 +32,6 @@ typedef struct _FileReaderOptions gint multi_line_timeout; gboolean restore_state; LogReaderOptions reader_options; - gboolean exit_on_eof; } FileReaderOptions; typedef struct _FileReader diff --git a/modules/affile/named-pipe.c b/modules/affile/named-pipe.c index 9f203c8721..86b2248d49 100644 --- a/modules/affile/named-pipe.c +++ b/modules/affile/named-pipe.c @@ -35,6 +35,12 @@ #include #include +typedef struct _FileOpenerNamedPipe +{ + FileOpener super; + gboolean suppress_eof; +} FileOpenerNamedPipe; + static gboolean _prepare_open(FileOpener *self, const gchar *name) { @@ -64,11 +70,19 @@ _prepare_open(FileOpener *self, const gchar *name) } static gint -_get_open_flags(FileOpener *self, FileDirection dir) +_get_open_flags(FileOpener *s, FileDirection dir) { + FileOpenerNamedPipe *self = (FileOpenerNamedPipe *) s; switch (dir) { case AFFILE_DIR_READ: + /* if a named pipe is opened for read write, we won't get an EOF, as + * there's always a writer (us, having opened in RW mode). EOF is only + * indicated if no writers remain */ + + if (self->suppress_eof) + return (O_RDWR | O_NOCTTY | O_NONBLOCK | O_LARGEFILE); + return (O_RDONLY | O_NOCTTY | O_NONBLOCK | O_LARGEFILE); case AFFILE_DIR_WRITE: return (O_RDWR | O_NOCTTY | O_NONBLOCK | O_LARGEFILE); default: @@ -77,11 +91,13 @@ _get_open_flags(FileOpener *self, FileDirection dir) } static LogTransport * -_construct_transport(FileOpener *self, gint fd) +_construct_transport(FileOpener *s, gint fd) { + FileOpenerNamedPipe *self = (FileOpenerNamedPipe *) s; LogTransport *transport = log_transport_pipe_new(fd); - transport->read = log_transport_file_read_and_ignore_eof_method; + if (self->suppress_eof) + transport->read = log_transport_file_read_and_ignore_eof_method; return transport; } @@ -106,16 +122,29 @@ pipe_sd_set_create_dirs(LogDriver *s, gboolean create_dirs) } FileOpener * -file_opener_for_named_pipes_new(void) +file_opener_for_named_pipes_new(gboolean suppress_eof) { - FileOpener *self = file_opener_new(); - - self->prepare_open = _prepare_open; - self->get_open_flags = _get_open_flags; - self->construct_transport = _construct_transport; - self->construct_src_proto = _construct_src_proto; - self->construct_dst_proto = _construct_dst_proto; - return self; + FileOpenerNamedPipe *self = g_new0(FileOpenerNamedPipe, 1); + + file_opener_init_instance(&self->super); + + self->super.prepare_open = _prepare_open; + self->super.get_open_flags = _get_open_flags; + self->super.construct_transport = _construct_transport; + self->super.construct_src_proto = _construct_src_proto; + self->super.construct_dst_proto = _construct_dst_proto; + + self->suppress_eof = suppress_eof; + return &self->super; +} + +static gboolean +_init(LogPipe *s) +{ + AFFileSourceDriver *self = (AFFileSourceDriver *) s; + if (!self->file_opener) + self->file_opener = file_opener_for_named_pipes_new((self->file_reader_options.reader_options.flags & LR_EXIT_ON_EOF) == 0); + return affile_sd_init(s); } LogDriver * @@ -123,6 +152,7 @@ pipe_sd_new(gchar *filename, GlobalConfig *cfg) { AFFileSourceDriver *self = affile_sd_new_instance(filename, cfg); + self->super.super.super.init = _init; self->file_reader_options.reader_options.super.stats_source = stats_register_type("pipe"); if (cfg_is_config_version_older(cfg, VERSION_VALUE_3_2)) @@ -137,8 +167,6 @@ pipe_sd_new(gchar *filename, GlobalConfig *cfg) self->file_reader_options.reader_options.parse_options.flags &= ~LP_EXPECT_HOSTNAME; } - self->file_opener = file_opener_for_named_pipes_new(); - affile_sd_set_transport_name(self, "local+pipe"); return &self->super.super; } @@ -149,6 +177,6 @@ pipe_dd_new(LogTemplate *filename_template, GlobalConfig *cfg) AFFileDestDriver *self = affile_dd_new_instance(filename_template, cfg); self->writer_options.stats_source = stats_register_type("pipe"); - self->file_opener = file_opener_for_named_pipes_new(); + self->file_opener = file_opener_for_named_pipes_new(FALSE); return &self->super.super; } diff --git a/modules/affile/named-pipe.h b/modules/affile/named-pipe.h index f988f649f0..65fddbb8a2 100644 --- a/modules/affile/named-pipe.h +++ b/modules/affile/named-pipe.h @@ -30,7 +30,7 @@ void pipe_sd_set_create_dirs(LogDriver *s, gboolean create_dirs); -FileOpener *file_opener_for_named_pipes_new(void); +FileOpener *file_opener_for_named_pipes_new(gboolean open_for_readonly); LogDriver *pipe_sd_new(gchar *filename, GlobalConfig *cfg); LogDriver *pipe_dd_new(LogTemplate *filename_template, GlobalConfig *cfg); diff --git a/modules/affile/stdin.c b/modules/affile/stdin.c index 9dcc07d24b..b2be7e1be9 100644 --- a/modules/affile/stdin.c +++ b/modules/affile/stdin.c @@ -60,7 +60,7 @@ stdin_sd_new(GlobalConfig *cfg) { AFFileSourceDriver *self = affile_sd_new_instance("-", cfg); - self->file_reader_options.exit_on_eof = TRUE; + self->file_reader_options.reader_options.flags |= LR_EXIT_ON_EOF; self->file_reader_options.reader_options.super.stats_source = stats_register_type("stdin"); self->file_opener = file_opener_for_stdin_new(); affile_sd_set_transport_name(self, "local+stdin"); diff --git a/syslog-ng-ctl/CMakeLists.txt b/syslog-ng-ctl/CMakeLists.txt index 0deab8846f..162364f0cf 100644 --- a/syslog-ng-ctl/CMakeLists.txt +++ b/syslog-ng-ctl/CMakeLists.txt @@ -1,6 +1,8 @@ set(SYSLOG_NG_CTL_SOURCES syslog-ng-ctl.c control-client.h + commands/attach.h + commands/attach.c commands/commands.h commands/commands.c commands/credentials.h diff --git a/syslog-ng-ctl/Makefile.am b/syslog-ng-ctl/Makefile.am index 601143fb4e..88c393e11c 100644 --- a/syslog-ng-ctl/Makefile.am +++ b/syslog-ng-ctl/Makefile.am @@ -6,6 +6,8 @@ syslog_ng_ctl_syslog_ng_ctl_SOURCES = \ syslog-ng-ctl/syslog-ng-ctl.c \ syslog-ng-ctl/commands/commands.h \ syslog-ng-ctl/commands/commands.c \ + syslog-ng-ctl/commands/attach.h \ + syslog-ng-ctl/commands/attach.c \ syslog-ng-ctl/commands/config.h \ syslog-ng-ctl/commands/config.c \ syslog-ng-ctl/commands/credentials.h \ diff --git a/syslog-ng-ctl/commands/attach.c b/syslog-ng-ctl/commands/attach.c new file mode 100644 index 0000000000..2bd18ac246 --- /dev/null +++ b/syslog-ng-ctl/commands/attach.c @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024 Balazs Scheidler + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + * + */ + +#include "ctl-stats.h" +#include "syslog-ng.h" + +static gint attach_options_seconds; +static gchar *attach_options_log_level = NULL; +static gchar **attach_commands = NULL; + +static gboolean +_store_log_level(const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + if (!attach_options_log_level) + { + attach_options_log_level = g_strdup(value); + return TRUE; + } + g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "You can't specify multiple log-levels at a time."); + 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) +{ + GString *command = g_string_new("ATTACH"); + const gchar *attach_mode; + + if (attach_commands) + { + if (attach_commands[1]) + { + fprintf(stderr, "Too many arguments"); + return 1; + } + attach_mode = attach_commands[0]; + } + else + attach_mode = "stdio"; + + if (g_str_equal(attach_mode, "stdio")) + g_string_append(command, " STDIO"); + else if (g_str_equal(attach_mode, "logs")) + 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); + 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; +} diff --git a/syslog-ng-ctl/commands/attach.h b/syslog-ng-ctl/commands/attach.h new file mode 100644 index 0000000000..b544924ff4 --- /dev/null +++ b/syslog-ng-ctl/commands/attach.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 Balazs Scheidler + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + * + */ + +#ifndef SYSLOG_NG_CTL_ATTACH_H_INCLUDED +#define SYSLOG_NG_CTL_ATTACH_H_INCLUDED 1 + +#include "commands.h" + +extern GOptionEntry attach_options[]; +gint slng_attach(int argc, char *argv[], const gchar *mode, GOptionContext *ctx); + +#endif diff --git a/syslog-ng-ctl/commands/commands.c b/syslog-ng-ctl/commands/commands.c index 2ff1add1e3..ec15828a70 100644 --- a/syslog-ng-ctl/commands/commands.c +++ b/syslog-ng-ctl/commands/commands.c @@ -57,14 +57,14 @@ process_response_status(GString *response) } static gboolean -slng_send_cmd(const gchar *cmd) +slng_send_cmd(const gchar *cmd, gboolean attach) { if (!control_client_connect(control_client)) { return FALSE; } - if (control_client_send_command(control_client, cmd) < 0) + if (control_client_send_command(control_client, cmd, attach) < 0) { return FALSE; } @@ -75,7 +75,16 @@ slng_send_cmd(const gchar *cmd) gint slng_run_command(const gchar *command, CommandResponseHandlerFunc cb, gpointer user_data) { - if (!slng_send_cmd(command)) + if (!slng_send_cmd(command, FALSE)) + return 1; + + return control_client_read_reply(control_client, cb, user_data); +} + +gint +slng_attach_command(const gchar *command, CommandResponseHandlerFunc cb, gpointer user_data) +{ + if (!slng_send_cmd(command, TRUE)) return 1; return control_client_read_reply(control_client, cb, user_data); @@ -117,6 +126,26 @@ dispatch_command(const gchar *cmd) return retval; } +static gint +_ignore_alive_messages(GString *reply, gpointer user_data) +{ + return 0; +} + +gint +attach_command(const gchar *cmd) +{ + gboolean first_response = TRUE; + gint retval = 0; + gchar *dispatchable_command = g_strdup_printf("%s\n", cmd); + retval = slng_attach_command(dispatchable_command, _ignore_alive_messages, &first_response); + + secret_storage_wipe(dispatchable_command, strlen(dispatchable_command)); + g_free(dispatchable_command); + + return retval; +} + gint run(const gchar *control_name, gint argc, gchar **argv, CommandDescriptor *mode, GOptionContext *ctx) { diff --git a/syslog-ng-ctl/commands/commands.h b/syslog-ng-ctl/commands/commands.h index 0072f5568e..f24c4f586a 100644 --- a/syslog-ng-ctl/commands/commands.h +++ b/syslog-ng-ctl/commands/commands.h @@ -43,7 +43,7 @@ typedef struct _CommandDescriptor typedef gint (*CommandResponseHandlerFunc)(GString *response, gpointer user_data); gint dispatch_command(const gchar *cmd); -gint slng_run_command(const gchar *command, CommandResponseHandlerFunc cb, gpointer user_data); +gint attach_command(const gchar *cmd); gint process_response_status(GString *response); gboolean is_syslog_ng_running(void); diff --git a/syslog-ng-ctl/control-client-unix.c b/syslog-ng-ctl/control-client-unix.c index 0bcd8bc99e..f960dab67c 100644 --- a/syslog-ng-ctl/control-client-unix.c +++ b/syslog-ng-ctl/control-client-unix.c @@ -79,9 +79,38 @@ control_client_connect(ControlClient *self) } gint -control_client_send_command(ControlClient *self, const gchar *cmd) +control_client_send_command(ControlClient *self, const gchar *cmd, gboolean attach) { - return fwrite(cmd, strlen(cmd), 1, self->control_socket); + struct iovec iov[1] = + { + { .iov_base = (gchar *) cmd, .iov_len = strlen(cmd) }, + }; + gint fds[3] = { 0, 1, 2 }; + union + { + char buf[CMSG_SPACE(sizeof(fds))]; + struct cmsghdr align; + } u; + struct msghdr msg = + { + .msg_iov = iov, + .msg_iovlen = G_N_ELEMENTS(iov), + 0 + }; + if (attach) + { + msg.msg_control = u.buf; + msg.msg_controllen = sizeof(u.buf); + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(fds)); + memcpy(CMSG_DATA(cmsg), fds, sizeof(fds)); + } + + return sendmsg(self->control_fd, &msg, 0); +// return fwrite(cmd, strlen(cmd), 1, self->control_socket); } #define BUFF_LEN 8192 diff --git a/syslog-ng-ctl/control-client.h b/syslog-ng-ctl/control-client.h index c3efbf15a5..081e1ca988 100644 --- a/syslog-ng-ctl/control-client.h +++ b/syslog-ng-ctl/control-client.h @@ -32,7 +32,7 @@ typedef struct _ControlClient ControlClient; ControlClient *control_client_new(const gchar *path); gboolean control_client_connect(ControlClient *self); -gint control_client_send_command(ControlClient *self, const gchar *cmd); +gint control_client_send_command(ControlClient *self, const gchar *cmd, gboolean attach); gint control_client_read_reply(ControlClient *self, CommandResponseHandlerFunc cb, gpointer user_data); void control_client_free(ControlClient *self); diff --git a/syslog-ng-ctl/syslog-ng-ctl.c b/syslog-ng-ctl/syslog-ng-ctl.c index d029a1a591..0e2fd45aa6 100644 --- a/syslog-ng-ctl/syslog-ng-ctl.c +++ b/syslog-ng-ctl/syslog-ng-ctl.c @@ -37,6 +37,7 @@ #include "commands/query.h" #include "commands/license.h" #include "commands/healthcheck.h" +#include "commands/attach.h" #include #include @@ -111,6 +112,7 @@ 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 }, { "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 }, diff --git a/syslog-ng/main.c b/syslog-ng/main.c index f0f8d2425f..e446a0aa04 100644 --- a/syslog-ng/main.c +++ b/syslog-ng/main.c @@ -37,6 +37,7 @@ #include "mainloop.h" #include "plugin.h" #include "reloc.h" +#include "console.h" #include "resolved-configurable-paths.h" #include @@ -235,6 +236,7 @@ main(int argc, char *argv[]) GOptionContext *ctx; GError *error = NULL; + console_global_init("syslog-ng"); MainLoop *main_loop = main_loop_get_instance(); z_mem_trace_init("syslog-ng.trace"); @@ -293,7 +295,7 @@ main(int argc, char *argv[]) if (debug_flag && !log_stderr) { - g_process_message("The -d/--debug option no longer implies -e/--stderr, if you want to redirect internal() source to stderr please also include -e/--stderr option"); + fprintf(stderr, "The -d/--debug option no longer implies -e/--stderr, if you want to redirect internal() source to stderr please also include -e/--stderr option"); } gboolean exit_before_main_loop_run = main_loop_options.syntax_only @@ -348,6 +350,7 @@ main(int argc, char *argv[]) app_shutdown(); z_mem_trace_dump(); g_process_finish(); + console_global_deinit(); reloc_deinit(); return rc; } diff --git a/tests/copyright/policy b/tests/copyright/policy index 57b05879a4..59778b5321 100644 --- a/tests/copyright/policy +++ b/tests/copyright/policy @@ -121,6 +121,7 @@ lib/tests/test_generic_number\.c lib/severity-aliases\.table lib/transport/transport-adapter\.[ch] syslog-ng-ctl/commands/log-level.[ch] +syslog-ng-ctl/commands/attach.[ch] modules/afsocket/afsocket-signals.h syslog-ng-ctl/commands/healthcheck.[ch] modules/python-modules/syslogng/confgen\.py