diff --git a/man/rules/meson.build b/man/rules/meson.build index bb5830eaf6..76aa77ff87 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -1050,6 +1050,10 @@ manpages = [ ['systemd-modules-load.service', '8', ['systemd-modules-load'], 'HAVE_KMOD'], ['systemd-mount', '1', ['systemd-umount'], ''], ['systemd-mountfsd.service', '8', ['systemd-mountfsd'], 'ENABLE_MOUNTFSD'], + ['systemd-mute-console', + '1', + ['systemd-mute-console.socket', 'systemd-mute-console@.service'], + ''], ['systemd-network-generator.service', '8', ['systemd-network-generator'], ''], ['systemd-networkd-wait-online.service', '8', diff --git a/man/systemd-firstboot.xml b/man/systemd-firstboot.xml index fe50022d73..cc4ddc0cf6 100644 --- a/man/systemd-firstboot.xml +++ b/man/systemd-firstboot.xml @@ -354,6 +354,16 @@ + + + + Takes a boolean argument. If true kernel log output and service manager status output + to the system console is temporarily disabled while systemd-firstboot is running, + so that its own output is not interrupted. Defaults to false. + + + + diff --git a/man/systemd-mute-console.xml b/man/systemd-mute-console.xml new file mode 100644 index 0000000000..cfab5e9165 --- /dev/null +++ b/man/systemd-mute-console.xml @@ -0,0 +1,79 @@ + + + + + + + + systemd-mute-console + systemd + + + + systemd-mute-console + 1 + + + + systemd-mute-console + systemd-mute-console@.service + systemd-mute-console.socket + Temporarily mute kernel log output and service manager status output to the system console + + + + + systemd-mute-console + OPTIONS + + + systemd-mute-console@.service + systemd-mute-console.socket + + + + Description + + The systemd-mute-console tool and service may be used to + temporarily mute the log output of the kernel as well as the status output of the service manager to + the system console. It may be used by tools running on the console to ensure their terminal output is not + interrupted by unrelated messages. + + The tool can be invoked directly in which case it will mute the two outputs and then issue an + sd_notify3 + READY=1 notification once that is completed. On SIGINT or + SIGTERM output is unmuted again. Alternatively it can be invoked via Varlink + IPC. + + + + Options + + The following options are understood: + + + + + + Individually controls which output to mute. If true is specified the respective + output is muted, if false the output is left as is. Defaults to true. + + + + + + + + + + + See Also + + systemd1 + systemd-firstboot1 + + + + diff --git a/meson.build b/meson.build index 1acf9728d4..bf58d536f4 100644 --- a/meson.build +++ b/meson.build @@ -2384,6 +2384,7 @@ subdir('src/measure') subdir('src/modules-load') subdir('src/mount') subdir('src/mountfsd') +subdir('src/mute-console') subdir('src/network') subdir('src/notify') subdir('src/nspawn') diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index f92011b614..6a2e8ee284 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -6,6 +6,7 @@ #include "sd-bus.h" #include "sd-id128.h" +#include "sd-varlink.h" #include "alloc-util.h" #include "ask-password-api.h" @@ -86,6 +87,7 @@ static bool arg_welcome = true; static bool arg_reset = false; static ImagePolicy *arg_image_policy = NULL; static bool arg_chrome = true; +static bool arg_mute_console = false; STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); @@ -99,13 +101,17 @@ STATIC_DESTRUCTOR_REGISTER(arg_root_shell, freep); STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); -static void print_welcome(int rfd) { +static void print_welcome(int rfd, sd_varlink **mute_console_link) { _cleanup_free_ char *pretty_name = NULL, *os_name = NULL, *ansi_color = NULL; static bool done = false; const char *pn, *ac; int r; assert(rfd >= 0); + assert(mute_console_link); + + if (!*mute_console_link && arg_mute_console) + (void) mute_console(mute_console_link); if (!arg_welcome) return; @@ -227,7 +233,7 @@ static int locale_is_ok(const char *name, void *userdata) { return r != 0 ? locale_is_installed(name) > 0 : locale_is_valid(name); } -static int prompt_locale(int rfd) { +static int prompt_locale(int rfd, sd_varlink **mute_console_link) { _cleanup_strv_free_ char **locales = NULL; bool acquired_from_creds = false; int r; @@ -279,7 +285,7 @@ static int prompt_locale(int rfd) { /* Not setting arg_locale_message here, since it defaults to LANG anyway */ } } else { - print_welcome(rfd); + print_welcome(rfd, mute_console_link); r = prompt_loop("Please enter the new system locale name or number", GLYPH_WORLD, @@ -321,7 +327,7 @@ static int prompt_locale(int rfd) { return 0; } -static int process_locale(int rfd) { +static int process_locale(int rfd, sd_varlink **mute_console_link) { _cleanup_close_ int pfd = -EBADF; _cleanup_free_ char *f = NULL; char* locales[3]; @@ -357,7 +363,7 @@ static int process_locale(int rfd) { } } - r = prompt_locale(rfd); + r = prompt_locale(rfd, mute_console_link); if (r < 0) return r; @@ -394,7 +400,7 @@ static int keymap_is_ok(const char* name, void *userdata) { return r != 0 ? keymap_exists(name) > 0 : keymap_is_valid(name); } -static int prompt_keymap(int rfd) { +static int prompt_keymap(int rfd, sd_varlink **mute_console_link) { _cleanup_strv_free_ char **kmaps = NULL; int r; @@ -422,7 +428,7 @@ static int prompt_keymap(int rfd) { if (r < 0) return log_error_errno(r, "Failed to read keymaps: %m"); - print_welcome(rfd); + print_welcome(rfd, mute_console_link); return prompt_loop( "Please enter the new keymap name or number", @@ -439,7 +445,7 @@ static int prompt_keymap(int rfd) { &arg_keymap); } -static int process_keymap(int rfd) { +static int process_keymap(int rfd, sd_varlink **mute_console_link) { _cleanup_close_ int pfd = -EBADF; _cleanup_free_ char *f = NULL; _cleanup_strv_free_ char **keymap = NULL; @@ -474,7 +480,7 @@ static int process_keymap(int rfd) { } } - r = prompt_keymap(rfd); + r = prompt_keymap(rfd, mute_console_link); if (r == -ENOENT) return 0; /* don't fail if no keymaps are installed */ if (r < 0) @@ -508,7 +514,7 @@ static int timezone_is_ok(const char *name, void *userdata) { return timezone_is_valid(name, LOG_DEBUG); } -static int prompt_timezone(int rfd) { +static int prompt_timezone(int rfd, sd_varlink **mute_console_link) { _cleanup_strv_free_ char **zones = NULL; int r; @@ -534,7 +540,7 @@ static int prompt_timezone(int rfd) { if (r < 0) return log_error_errno(r, "Cannot query timezone list: %m"); - print_welcome(rfd); + print_welcome(rfd, mute_console_link); return prompt_loop( "Please enter the new timezone name or number", @@ -551,7 +557,7 @@ static int prompt_timezone(int rfd) { &arg_timezone); } -static int process_timezone(int rfd) { +static int process_timezone(int rfd, sd_varlink **mute_console_link) { _cleanup_close_ int pfd = -EBADF; _cleanup_free_ char *f = NULL, *relpath = NULL; const char *e; @@ -592,7 +598,7 @@ static int process_timezone(int rfd) { } } - r = prompt_timezone(rfd); + r = prompt_timezone(rfd, mute_console_link); if (r < 0) return r; @@ -616,7 +622,7 @@ static int hostname_is_ok(const char *name, void *userdata) { return hostname_is_valid(name, VALID_HOSTNAME_TRAILING_DOT); } -static int prompt_hostname(int rfd) { +static int prompt_hostname(int rfd, sd_varlink **mute_console_link) { int r; assert(rfd >= 0); @@ -629,7 +635,7 @@ static int prompt_hostname(int rfd) { return 0; } - print_welcome(rfd); + print_welcome(rfd, mute_console_link); r = prompt_loop("Please enter the new hostname", GLYPH_LABEL, @@ -652,7 +658,7 @@ static int prompt_hostname(int rfd) { return 0; } -static int process_hostname(int rfd) { +static int process_hostname(int rfd, sd_varlink **mute_console_link) { _cleanup_close_ int pfd = -EBADF; _cleanup_free_ char *f = NULL; int r; @@ -671,7 +677,7 @@ static int process_hostname(int rfd) { if (r <= 0) return r; - r = prompt_hostname(rfd); + r = prompt_hostname(rfd, mute_console_link); if (r < 0) return r; @@ -720,7 +726,7 @@ static int process_machine_id(int rfd) { return 0; } -static int prompt_root_password(int rfd) { +static int prompt_root_password(int rfd, sd_varlink **mute_console_link) { const char *msg1, *msg2; int r; @@ -737,10 +743,10 @@ static int prompt_root_password(int rfd) { return 0; } - print_welcome(rfd); + print_welcome(rfd, mute_console_link); - msg1 = strjoina("Please enter the new root password (empty to skip):"); - msg2 = strjoina("Please enter the new root password again:"); + msg1 = "Please enter the new root password (empty to skip):"; + msg2 = "Please enter the new root password again:"; suggest_passwords(); @@ -817,7 +823,7 @@ static int shell_is_ok(const char *path, void *userdata) { return find_shell(rfd, path) >= 0; } -static int prompt_root_shell(int rfd) { +static int prompt_root_shell(int rfd, sd_varlink **mute_console_link) { int r; assert(rfd >= 0); @@ -838,7 +844,7 @@ static int prompt_root_shell(int rfd) { return 0; } - print_welcome(rfd); + print_welcome(rfd, mute_console_link); return prompt_loop( "Please enter the new root shell", @@ -1005,7 +1011,7 @@ static int write_root_shadow(int etc_fd, const char *hashed_password) { return 0; } -static int process_root_account(int rfd) { +static int process_root_account(int rfd, sd_varlink **mute_console_link) { _cleanup_close_ int pfd = -EBADF; _cleanup_(release_lock_file) LockFile lock = LOCK_FILE_INIT; _cleanup_(erase_and_freep) char *_hashed_password = NULL; @@ -1059,7 +1065,7 @@ static int process_root_account(int rfd) { return log_oom(); } - r = prompt_root_shell(rfd); + r = prompt_root_shell(rfd, mute_console_link); if (r < 0) return r; @@ -1078,7 +1084,7 @@ static int process_root_account(int rfd) { arg_root_password_is_hashed = true; } - r = prompt_root_password(rfd); + r = prompt_root_password(rfd, mute_console_link); if (r < 0) return r; @@ -1246,6 +1252,8 @@ static int help(void) { " --welcome=no Disable the welcome text\n" " --chrome=no Don't show color bar at top and bottom of\n" " terminal\n" + " --mute-console=yes Tell kernel/PID 1 to not write to the console\n" + " while running\n" " --reset Remove existing files\n" "\nSee the %2$s for details.\n", program_invocation_short_name, @@ -1293,6 +1301,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_WELCOME, ARG_CHROME, ARG_RESET, + ARG_MUTE_CONSOLE, }; static const struct option options[] = { @@ -1331,6 +1340,7 @@ static int parse_argv(int argc, char *argv[]) { { "welcome", required_argument, NULL, ARG_WELCOME }, { "chrome", required_argument, NULL, ARG_CHROME }, { "reset", no_argument, NULL, ARG_RESET }, + { "mute-console", required_argument, NULL, ARG_MUTE_CONSOLE }, {} }; @@ -1550,6 +1560,13 @@ static int parse_argv(int argc, char *argv[]) { arg_reset = true; break; + case ARG_MUTE_CONSOLE: + r = parse_boolean_argument("--mute-console=", optarg, &arg_mute_console); + if (r < 0) + return r; + + break; + case '?': return -EINVAL; @@ -1712,27 +1729,28 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - r = process_locale(rfd); + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *mute_console_link = NULL; + r = process_locale(rfd, &mute_console_link); if (r < 0) return r; if (r > 0 && !offline) (void) reload_system_manager(&bus); - r = process_keymap(rfd); + r = process_keymap(rfd, &mute_console_link); if (r < 0) return r; if (r > 0 && !offline) (void) reload_vconsole(&bus); - r = process_timezone(rfd); + r = process_timezone(rfd, &mute_console_link); if (r < 0) return r; - r = process_hostname(rfd); + r = process_hostname(rfd, &mute_console_link); if (r < 0) return r; - r = process_root_account(rfd); + r = process_root_account(rfd, &mute_console_link); if (r < 0) return r; diff --git a/src/mute-console/meson.build b/src/mute-console/meson.build new file mode 100644 index 0000000000..f0179da21f --- /dev/null +++ b/src/mute-console/meson.build @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +executables += [ + executable_template + { + 'name' : 'systemd-mute-console', + 'public' : true, + 'sources' : files('mute-console.c'), + }, +] diff --git a/src/mute-console/mute-console.c b/src/mute-console/mute-console.c new file mode 100644 index 0000000000..7f0b211d3f --- /dev/null +++ b/src/mute-console/mute-console.c @@ -0,0 +1,419 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-bus.h" +#include "sd-event.h" +#include "sd-varlink.h" + +#include "alloc-util.h" +#include "ansi-color.h" +#include "build.h" +#include "bus-error.h" +#include "bus-locator.h" +#include "bus-util.h" +#include "daemon-util.h" +#include "errno-util.h" +#include "log.h" +#include "main-func.h" +#include "parse-argument.h" +#include "pretty-print.h" +#include "printk-util.h" +#include "varlink-io.systemd.MuteConsole.h" +#include "varlink-util.h" +#include "virt.h" + +static bool arg_mute_pid1 = true; +static bool arg_mute_kernel = true; +static bool arg_varlink = false; + +static int help(void) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("systemd-mute-console", "1", &link); + if (r < 0) + return log_oom(); + + printf("%s [OPTIONS...]\n" + "\n%sMute status output to the console.%s\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --kernel=BOOL Mute kernel log output\n" + " --pid1=BOOL Mute PID 1 status output\n" + "\nSee the %s for details.\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal(), + link); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_KERNEL, + ARG_PID1, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "kernel", required_argument, NULL, ARG_KERNEL }, + { "pid1", required_argument, NULL, ARG_PID1 }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hq", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + return help(); + + case ARG_VERSION: + return version(); + + case ARG_PID1: + r = parse_boolean_argument("--pid1=", optarg, &arg_mute_pid1); + if (r < 0) + return r; + + break; + + case ARG_KERNEL: + r = parse_boolean_argument("--kernel=", optarg, &arg_mute_kernel); + if (r < 0) + return r; + + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + } + + r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT); + if (r < 0) + return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m"); + if (r > 0) + arg_varlink = true; + + return 1; +} + +static int set_show_status(const char *value) { + int r; + assert(value); + + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + r = bus_connect_system_systemd(&bus); + if (r < 0) + return log_error_errno(r, "Failed to connect to systemd: %m"); + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = bus_call_method(bus, bus_systemd_mgr, "SetShowStatus", &error, /* ret_reply= */ NULL, "s", value); + if (r < 0) + return log_error_errno(r, "Failed to issue SetShowStatus() method call: %s", bus_error_message(&error, r)); + + return 0; +} + +typedef struct Context { + bool mute_pid1; + bool mute_kernel; + + bool muted_pid1; + int saved_kernel; + + sd_varlink *link; +} Context; + +static int mute_pid1(Context *c) { + int r; + + assert(c); + + if (!c->mute_pid1) { + log_debug("Muting of PID 1 status console output disabled."); + c->muted_pid1 = false; + return 0; + } + + r = set_show_status("no"); + if (r < 0) + return r; + + log_debug("Successfully muted PID 1 status console output."); + + c->muted_pid1 = true; + return 0; +} + +static int unmute_pid1(Context *c) { + int r; + + assert(c); + + if (!c->muted_pid1) { + log_debug("Not restoring PID 1 status console output level."); + return 0; + } + + r = set_show_status(""); + if (r < 0) + return r; + + log_debug("Successfully unmuted PID 1 status console output."); + c->muted_pid1 = false; + return 0; +} + +static int mute_kernel(Context *c) { + int r; + + assert(c); + + if (!arg_mute_kernel) { + log_debug("Muting of kernel printk() console output disabled."); + c->saved_kernel = -1; + return 0; + } + + if (detect_container() > 0) { + log_debug("Skipping muting of print() console output, because running in a container."); + c->saved_kernel = -1; + return 0; + } + + int level = sysctl_printk_read(); + if (level < 0) + return log_error_errno(level, "Failed to read kernel printk() console output level: %m"); + + if (level == 0) { + log_info("Not muting kernel printk() console output, since it is already disabled."); + c->saved_kernel = -1; /* don't bother with restoring */ + } else { + r = sysctl_printk_write(0); + if (r < 0) + return log_error_errno(r, "Failed to change kernel printk() console output level: %m"); + + log_debug("Successfully muted kernel printk() console output."); + c->saved_kernel = level; + } + + return 0; +} + +static int unmute_kernel(Context *c) { + int r; + + assert(c); + + if (c->saved_kernel < 0) { + log_debug("Not restoring kernel printk() console output level."); + return 0; + } + + int level = sysctl_printk_read(); + if (level < 0) + return log_error_errno(level, "Failed to read kernel printk() console output level: %m"); + + if (level != 0) { + log_info("Not unmuting kernel printk() console output, since it has been changed externally in the meantime."); + return 0; + } + + r = sysctl_printk_write(c->saved_kernel); + if (r < 0) + return log_error_errno(r, "Failed to unmute kernel printk() console output level: %m"); + + log_debug("Successfully unmuted kernel printk() console output."); + c->saved_kernel = -1; + return 0; +} + +static void context_done(Context *c) { + assert(c); + + (void) unmute_pid1(c); + (void) unmute_kernel(c); + + if (c->link) { + (void) sd_varlink_set_userdata(c->link, NULL); + c->link = sd_varlink_flush_close_unref(c->link); + } +} + +static Context* context_free(Context *c) { + if (!c) + return NULL; + + context_done(c); + return mfree(c); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(Context*, context_free); + +static void vl_on_disconnect(sd_varlink_server *server, sd_varlink *link, void *userdata) { + assert(link); + + Context *c = sd_varlink_get_userdata(link); + if (!c) + return; + + context_free(c); +} + +static int vl_method_mute( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + int r; + + assert(link); + + _cleanup_(context_freep) Context *nc = new(Context, 1); + if (!nc) + return -ENOMEM; + + *nc = (Context) { + .mute_pid1 = true, + .mute_kernel = true, + .saved_kernel = -1, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "kernel", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(Context, mute_kernel), 0 }, + { "pid1", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(Context, mute_pid1), 0 }, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, nc); + if (r != 0) + return r; + + if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)) + return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, NULL); + + r = sd_varlink_server_bind_disconnect(sd_varlink_get_server(link), vl_on_disconnect); + if (r < 0) + return r; + + (void) sd_varlink_set_userdata(link, nc); + nc->link = sd_varlink_ref(link); + Context *c = TAKE_PTR(nc); /* the Context object is now managed by the disconnect handler, not us anymore */ + + r = 0; + RET_GATHER(r, mute_pid1(c)); + RET_GATHER(r, mute_kernel(c)); + if (r < 0) + return r; + + /* Let client know we are muted now. We use sd_varlink_notify() here (rather than sd_varlink_reply()) + * because we want to keep the method call open, as we want that the lifetime of the + * connection/method call to determine how long we keep the console muted. */ + r = sd_varlink_notify(link, /* parameters= */ NULL); + if (r < 0) + return r; + + return 0; +} + +static int vl_server(void) { + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; + int r; + + /* Invocation as Varlink service */ + + r = varlink_server_new( + &varlink_server, + SD_VARLINK_SERVER_ROOT_ONLY| + SD_VARLINK_SERVER_HANDLE_SIGINT| + SD_VARLINK_SERVER_HANDLE_SIGTERM, + /* userdata= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_MuteConsole); + if (r < 0) + return log_error_errno(r, "Failed to add Varlink interface: %m"); + + r = sd_varlink_server_bind_method_many( + varlink_server, + "io.systemd.MuteConsole.Mute", vl_method_mute); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink methods: %m"); + + r = sd_varlink_server_loop_auto(varlink_server); + if (r < 0) + return log_error_errno(r, "Failed to run Varlink event loop: %m"); + + return 0; +} + +static int run(int argc, char* argv[]) { + int r; + + log_setup(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + if (arg_varlink) + return vl_server(); + + if (!arg_mute_pid1 && !arg_mute_kernel) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not asked to mute anything, refusing."); + + _cleanup_(context_done) Context c = { + .mute_pid1 = arg_mute_pid1, + .mute_kernel = arg_mute_kernel, + .saved_kernel = -1, + }; + + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + r = sd_event_new(&event); + if (r < 0) + return log_error_errno(r, "Failed to get default event source: %m"); + + (void) sd_event_set_watchdog(event, true); + (void) sd_event_set_signal_exit(event, true); + + int ret = 0; + RET_GATHER(ret, mute_pid1(&c)); + RET_GATHER(ret, mute_kernel(&c)); + + /* Now tell service manager we area ready to go */ + _unused_ _cleanup_(notify_on_cleanup) const char *notify_message = + notify_start("READY=1\n" + "STATUS=Console status output muted temporarily.", + "STOPPING=1\n" + "STATUS=Console status output unmuted."); + + /* Now wait for SIGINT/SIGTERM */ + r = sd_event_loop(event); + if (r < 0) + RET_GATHER(ret, log_error_errno(r, "Failed to run event loop: %m")); + + RET_GATHER(ret, unmute_pid1(&c)); + RET_GATHER(ret, unmute_kernel(&c)); + + return ret; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/shared/meson.build b/src/shared/meson.build index f3906305c6..6e3e79eb2e 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -154,6 +154,7 @@ shared_sources = files( 'polkit-agent.c', 'portable-util.c', 'pretty-print.c', + 'printk-util.c', 'prompt-util.c', 'ptyfwd.c', 'qrcode-util.c', @@ -201,6 +202,7 @@ shared_sources = files( 'varlink-io.systemd.ManagedOOM.c', 'varlink-io.systemd.Manager.c', 'varlink-io.systemd.MountFileSystem.c', + 'varlink-io.systemd.MuteConsole.c', 'varlink-io.systemd.NamespaceResource.c', 'varlink-io.systemd.Network.c', 'varlink-io.systemd.PCRExtend.c', diff --git a/src/shared/printk-util.c b/src/shared/printk-util.c new file mode 100644 index 0000000000..0f2221b3b2 --- /dev/null +++ b/src/shared/printk-util.c @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "extract-word.h" +#include "log.h" +#include "parse-util.h" +#include "printk-util.h" +#include "sysctl-util.h" + +int sysctl_printk_read(void) { + int r; + + _cleanup_free_ char *sysctl_printk_vals = NULL; + r = sysctl_read("kernel/printk", &sysctl_printk_vals); + if (r < 0) + return log_debug_errno(r, "Cannot read sysctl kernel.printk: %m"); + + _cleanup_free_ char *sysctl_printk_curr = NULL; + const char *p = sysctl_printk_vals; + r = extract_first_word(&p, &sysctl_printk_curr, NULL, 0); + if (r < 0) + return log_debug_errno(r, "Failed to split out kernel printk priority: %m"); + if (r == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Short read while reading kernel.printk sysctl"); + + int current_lvl; + r = safe_atoi(sysctl_printk_curr, ¤t_lvl); + if (r < 0) + return log_debug_errno(r, "Failed to parse kernel.printk sysctl: %s", sysctl_printk_vals); + + return current_lvl; +} + +int sysctl_printk_write(int l) { + int r; + + r = sysctl_writef("kernel/printk", "%i", l); + if (r < 0) + return log_debug_errno(r, "Failed to set kernel.printk to %i: %m", l); + + return 0; +} diff --git a/src/shared/printk-util.h b/src/shared/printk-util.h new file mode 100644 index 0000000000..e2bd0db46a --- /dev/null +++ b/src/shared/printk-util.h @@ -0,0 +1,5 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +int sysctl_printk_read(void); +int sysctl_printk_write(int l); diff --git a/src/shared/prompt-util.c b/src/shared/prompt-util.c index 927ef0770e..28435b2da1 100644 --- a/src/shared/prompt-util.c +++ b/src/shared/prompt-util.c @@ -2,6 +2,8 @@ #include +#include + #include "alloc-util.h" #include "glyph-util.h" #include "log.h" @@ -330,3 +332,60 @@ void chrome_hide(void) { fflush(stdout); } + +static int vl_on_reply(sd_varlink *link, sd_json_variant *parameters, const char *error_id, sd_varlink_reply_flags_t flags, void *userdata) { + assert(link); + + /* We want to keep the link around (since its lifetime defines the lifetime of the console muting), + * hence let's detach it from the event loop now, and then exit the event loop. */ + + _cleanup_(sd_event_unrefp) sd_event *e = sd_event_ref(ASSERT_PTR(sd_varlink_get_event(link))); + sd_varlink_detach_event(link); + (void) sd_event_exit(e, (error_id || !FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES)) ? -EBADR : 0); + + return 0; +} + +int mute_console(sd_varlink **ret_link) { + int r; + + assert(ret_link); + + /* Talks to the MuteConsole service, and asks for output to the console to be muted, as long as the + * connection is retained */ + + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *link = NULL; + r = sd_varlink_connect_address(&link, "/run/systemd/io.systemd.MuteConsole"); + if (r < 0) + return log_debug_errno(r, "Failed to connect to console muting service: %m"); + + _cleanup_(sd_event_unrefp) sd_event* event = NULL; + r = sd_event_new(&event); + if (r < 0) + return r; + + r = sd_varlink_attach_event(link, event, /* priority= */ 0); + if (r < 0) + return r; + + r = sd_varlink_bind_reply(link, vl_on_reply); + if (r < 0) + return r; + + r = sd_varlink_set_relative_timeout(link, UINT64_MAX); + if (r < 0) + return log_debug_errno(r, "Failed to disable method call time-out: %m"); + + r = sd_varlink_observe(link, "io.systemd.MuteConsole.Mute", /* parameters= */ NULL); + if (r < 0) + return log_debug_errno(r, "Failed to issue Mute() call to io.systemd.MuteConsole: %m"); + + /* Now run the event loop, it will exit on the first reply, which is when we know the console output + * is now muted. */ + r = sd_event_loop(event); + if (r < 0) + return r; + + *ret_link = TAKE_PTR(link); + return 0; +} diff --git a/src/shared/prompt-util.h b/src/shared/prompt-util.h index 06dd2c7f0c..70f8f0215d 100644 --- a/src/shared/prompt-util.h +++ b/src/shared/prompt-util.h @@ -29,3 +29,5 @@ int prompt_loop(const char *text, int chrome_show(const char *top, const char *bottom); void chrome_hide(void); + +int mute_console(sd_varlink **ret_link); diff --git a/src/shared/varlink-io.systemd.MuteConsole.c b/src/shared/varlink-io.systemd.MuteConsole.c new file mode 100644 index 0000000000..0cea5b8554 --- /dev/null +++ b/src/shared/varlink-io.systemd.MuteConsole.c @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-varlink-idl.h" + +#include "varlink-io.systemd.MuteConsole.h" + +static SD_VARLINK_DEFINE_METHOD( + Mute, + SD_VARLINK_FIELD_COMMENT("Whether to mute the kernel's output to the console (defaults to true)."), + SD_VARLINK_DEFINE_INPUT(kernel, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Whether to mute PID1's output to the console (defaults to true)."), + SD_VARLINK_DEFINE_INPUT(pid1, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_MuteConsole, + "io.systemd.MuteConsole", + SD_VARLINK_INTERFACE_COMMENT("API for temporarily muting noisy output to the main kernel console"), + SD_VARLINK_SYMBOL_COMMENT("Mute kernel and PID 1 output to the main kernel console"), + &vl_method_Mute); diff --git a/src/shared/varlink-io.systemd.MuteConsole.h b/src/shared/varlink-io.systemd.MuteConsole.h new file mode 100644 index 0000000000..9957ed1a5f --- /dev/null +++ b/src/shared/varlink-io.systemd.MuteConsole.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-varlink-idl.h" + +extern const sd_varlink_interface vl_interface_io_systemd_MuteConsole; diff --git a/src/shutdown/shutdown.c b/src/shutdown/shutdown.c index 9a114d9be5..97ae7d119c 100644 --- a/src/shutdown/shutdown.c +++ b/src/shutdown/shutdown.c @@ -36,6 +36,7 @@ #include "log.h" #include "parse-util.h" #include "pidref.h" +#include "printk-util.h" #include "process-util.h" #include "reboot-util.h" #include "rlimit-util.h" @@ -272,42 +273,14 @@ int sync_with_progress(int fd) { return r; } -static int read_current_sysctl_printk_log_level(void) { - _cleanup_free_ char *sysctl_printk_vals = NULL, *sysctl_printk_curr = NULL; - int current_lvl; - const char *p; - int r; - - r = sysctl_read("kernel/printk", &sysctl_printk_vals); - if (r < 0) - return log_debug_errno(r, "Cannot read sysctl kernel.printk: %m"); - - p = sysctl_printk_vals; - r = extract_first_word(&p, &sysctl_printk_curr, NULL, 0); - if (r < 0) - return log_debug_errno(r, "Failed to split out kernel printk priority: %m"); - if (r == 0) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Short read while reading kernel.printk sysctl"); - - r = safe_atoi(sysctl_printk_curr, ¤t_lvl); - if (r < 0) - return log_debug_errno(r, "Failed to parse kernel.printk sysctl: %s", sysctl_printk_vals); - - return current_lvl; -} - static void bump_sysctl_printk_log_level(int min_level) { - int current_lvl, r; - /* Set the logging level to be able to see messages with log level smaller or equal to min_level */ - current_lvl = read_current_sysctl_printk_log_level(); + int current_lvl = sysctl_printk_read(); if (current_lvl < 0 || current_lvl >= min_level + 1) return; - r = sysctl_writef("kernel/printk", "%i", min_level + 1); - if (r < 0) - log_debug_errno(r, "Failed to bump kernel.printk to %i: %m", min_level + 1); + (void) sysctl_printk_write(min_level + 1); } static void init_watchdog(void) { diff --git a/src/test/test-varlink-idl.c b/src/test/test-varlink-idl.c index 6b3449f777..1d328bd3e2 100644 --- a/src/test/test-varlink-idl.c +++ b/src/test/test-varlink-idl.c @@ -24,6 +24,7 @@ #include "varlink-io.systemd.Manager.h" #include "varlink-io.systemd.ManagedOOM.h" #include "varlink-io.systemd.MountFileSystem.h" +#include "varlink-io.systemd.MuteConsole.h" #include "varlink-io.systemd.NamespaceResource.h" #include "varlink-io.systemd.Network.h" #include "varlink-io.systemd.PCRExtend.h" @@ -166,6 +167,8 @@ TEST(parse_format) { print_separator(); test_parse_format_one(&vl_interface_io_systemd_UserDatabase); print_separator(); + test_parse_format_one(&vl_interface_io_systemd_MuteConsole); + print_separator(); test_parse_format_one(&vl_interface_io_systemd_NamespaceResource); print_separator(); test_parse_format_one(&vl_interface_io_systemd_Journal); diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index 9e32156239..808ae95348 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -587,8 +587,13 @@ static int reply_callback( else r = *ret = log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call failed: %s", error); } - } else + } else { + /* Let the caller know we have received at least one reply now. This is useful for + * subscription style interfaces where the first reply indicates the subscription being + * successfully enabled. */ + (void) sd_notify(/* unset_environment= */ false, "READY=1"); r = 0; + } if (!arg_quiet) sd_json_variant_dump(parameters, arg_json_format_flags, stdout, NULL); diff --git a/test/units/TEST-74-AUX-UTILS.mute-console.sh b/test/units/TEST-74-AUX-UTILS.mute-console.sh new file mode 100755 index 0000000000..e15be1ef32 --- /dev/null +++ b/test/units/TEST-74-AUX-UTILS.mute-console.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +if ! command -v systemd-mute-console >/dev/null; then + echo "systemd-mute-console is not installed, skipping the test" + exit 0 +fi + +PID="$(systemd-notify --fork -- systemd-mute-console)" +sleep .5 +kill "$PID" +unset PID + +(! systemd-mute-console --kernel=no --pid1=no) + +PID="$(systemd-notify --fork -- systemd-mute-console --kernel=yes --pid1=yes)" +sleep .5 +kill "$PID" +unset PID + +varlinkctl introspect "$(which systemd-mute-console)" + +PID="$(systemd-notify --fork -- varlinkctl call -E "$(which systemd-mute-console)" io.systemd.MuteConsole.Mute '{}')" +sleep .5 +kill "$PID" +unset PID + +PID="$(systemd-notify --fork -- varlinkctl call -E "$(which systemd-mute-console)" io.systemd.MuteConsole.Mute '{"pid1":true, "kernel":true}')" +sleep .5 +kill "$PID" +unset PID + +varlinkctl introspect /run/systemd/io.systemd.MuteConsole + +PID="$(systemd-notify --fork -- varlinkctl call -E /run/systemd/io.systemd.MuteConsole io.systemd.MuteConsole.Mute '{}')" +sleep .5 +kill "$PID" +unset PID diff --git a/units/meson.build b/units/meson.build index c5b99e4e04..ba2dfcab06 100644 --- a/units/meson.build +++ b/units/meson.build @@ -143,6 +143,12 @@ units = [ }, { 'file' : 'modprobe@.service' }, { 'file' : 'multi-user.target' }, + { + 'file' : 'systemd-mute-console.socket', + 'symlinks' : ['sockets.target.wants/'] + }, + { 'file' : 'systemd-mute-console@.service' }, + { 'file' : 'system-systemd\\x2dmute\\x2dconsole.slice' }, { 'file' : 'network-online.target' }, { 'file' : 'network-pre.target' }, { 'file' : 'network.target' }, diff --git "a/units/system-systemd\\x2dmute\\x2dconsole.slice" "b/units/system-systemd\\x2dmute\\x2dconsole.slice" new file mode 100644 index 0000000000..7819eb91a3 --- /dev/null +++ "b/units/system-systemd\\x2dmute\\x2dconsole.slice" @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd 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. + +[Unit] +Description=Console Output Muting Service Slice +Documentation=man:systemd-mute-console(8) +DefaultDependencies=no +Conflicts=shutdown.target +Before=shutdown.target + +[Slice] +# Serialize requests to mute the console. +ConcurrencySoftMax=1 diff --git a/units/systemd-firstboot.service b/units/systemd-firstboot.service index ce6f984f93..5ba9c8ba03 100644 --- a/units/systemd-firstboot.service +++ b/units/systemd-firstboot.service @@ -22,6 +22,7 @@ After=systemd-remount-fs.service After=systemd-sysusers.service systemd-tmpfiles-setup.service # Let vconsole-setup do its setup before starting user interaction: After=systemd-vconsole-setup.service +After=systemd-mute-console.socket Wants=first-boot-complete.target Before=first-boot-complete.target sysinit.target @@ -31,7 +32,7 @@ Before=shutdown.target [Service] Type=oneshot RemainAfterExit=yes -ExecStart=systemd-firstboot --prompt-locale --prompt-keymap --prompt-timezone --prompt-root-password +ExecStart=systemd-firstboot --prompt-locale --prompt-keymap --prompt-timezone --prompt-root-password --mute-console=yes StandardOutput=tty StandardInput=tty StandardError=tty diff --git a/units/systemd-mute-console.socket b/units/systemd-mute-console.socket new file mode 100644 index 0000000000..6223dc033c --- /dev/null +++ b/units/systemd-mute-console.socket @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd 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. + +[Unit] +Description=Console Output Muting Service Socket +Documentation=man:systemd-mute-console(8) +DefaultDependencies=no +Before=sockets.target +Conflicts=shutdown.target +Before=shutdown.target + +[Socket] +ListenStream=/run/systemd/io.systemd.MuteConsole +FileDescriptorName=varlink +SocketMode=0600 +Accept=yes diff --git a/units/systemd-mute-console@.service b/units/systemd-mute-console@.service new file mode 100644 index 0000000000..d43766c70b --- /dev/null +++ b/units/systemd-mute-console@.service @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd 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. + +[Unit] +Description=Console Output Muting Service +Documentation=man:systemd-mute-console(8) +DefaultDependencies=no +Conflicts=shutdown.target +Before=shutdown.target + +[Service] +ExecStart=systemd-mute-console