From 5b7641d5c772004efa3e11207e6f55885c874642 Mon Sep 17 00:00:00 2001 From: Balazs Scheidler Date: Sat, 5 Oct 2024 14:38:21 +0200 Subject: [PATCH] console: new module to track the interactive connection to a terminal Signed-off-by: Balazs Scheidler --- lib/CMakeLists.txt | 2 + lib/Makefile.am | 2 + lib/console.c | 168 +++++++++++++++++++++++++++++++++++++++++++++ lib/console.h | 41 +++++++++++ lib/gprocess.c | 144 +++++++++++++------------------------- lib/gprocess.h | 2 - syslog-ng/main.c | 6 +- 7 files changed, 266 insertions(+), 99 deletions(-) create mode 100644 lib/console.c create mode 100644 lib/console.h diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 0d5d983658..050d0a2f23 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 @@ -179,6 +180,7 @@ set(LIB_SOURCES apphook.c block-ref-parser.c cache.c + console.c cfg.c cfg-args.c cfg-block.c diff --git a/lib/Makefile.am b/lib/Makefile.am index 4a552ba080..995360761b 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 \ @@ -195,6 +196,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 \ diff --git a/lib/console.c b/lib/console.c new file mode 100644 index 0000000000..c72717dab7 --- /dev/null +++ b/lib/console.c @@ -0,0 +1,168 @@ +/* + * 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 = TRUE; +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()) + 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(void) +{ + gboolean result; + + /* the lock only serves a memory barrier but is not a real synchronization */ + g_mutex_lock(&console_lock); + result = console_present; + g_mutex_unlock(&console_lock); + return result; +} + +gboolean +console_is_attached(void) +{ + gboolean result; + /* the lock only serves a memory barrier but is not a real synchronization */ + g_mutex_lock(&console_lock); + if (using_initial_console) + result = FALSE; + else + result = console_present; + g_mutex_unlock(&console_lock); + return result; +} + +/* 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_attached()); + + 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..932f9c673c --- /dev/null +++ b/lib/console.h @@ -0,0 +1,41 @@ +/* + * 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(void); +gboolean console_is_attached(void); +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/gprocess.c b/lib/gprocess.c index 4ed3edec62..2e7c99353e 100644 --- a/lib/gprocess.c +++ b/lib/gprocess.c @@ -26,6 +26,7 @@ #include "userdb.h" #include "messages.h" #include "reloc.h" +#include "console.h" #include #include @@ -99,7 +100,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 +615,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 - * - * 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: + * g_process_setup_console: * - * 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) { @@ -697,8 +662,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 +675,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 +692,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 +714,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 +772,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 +792,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 +812,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 +852,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 +869,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 +899,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 +907,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 +933,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 +968,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 +981,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 +1122,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 +1166,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 +1189,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 +1200,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 +1220,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 +1255,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 +1269,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; } } @@ -1361,7 +1313,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 +1325,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 +1362,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) 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/syslog-ng/main.c b/syslog-ng/main.c index f0f8d2425f..62ef4f24a0 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,8 @@ 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 +351,7 @@ main(int argc, char *argv[]) app_shutdown(); z_mem_trace_dump(); g_process_finish(); + console_global_deinit(); reloc_deinit(); return rc; }