From: Lennart Poettering Date: Mon, 15 Sep 2025 16:19:22 +0000 (+0200) Subject: mute-console: add simple varlink service that can disable log/status spew to kernel... X-Git-Tag: v259-rc1~422^2~5 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=ac63a04bd6211f25b8029b56720228456800fcb6;p=thirdparty%2Fsystemd.git mute-console: add simple varlink service that can disable log/status spew to kernel console For "wizard" style interactive tools it's very annoying if they are interrupted by kernel log output or PID1's status output. let's add some infra to disable this temporarily. I decided to implement this as an IPC service so that we can make this robust: if the client request the muting dies we can automatically unmute again. This is hence a tiny varlink service, but it can also be started directly from the cmdline. --- diff --git a/man/rules/meson.build b/man/rules/meson.build index bb5830eaf6d..76aa77ff872 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-mute-console.xml b/man/systemd-mute-console.xml new file mode 100644 index 00000000000..cfab5e9165f --- /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 8ec025c8d6e..3d728e88a10 100644 --- a/meson.build +++ b/meson.build @@ -2383,6 +2383,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/mute-console/meson.build b/src/mute-console/meson.build new file mode 100644 index 00000000000..f0179da21f9 --- /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 00000000000..7f0b211d3f5 --- /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 71735aaaa24..10fc6742a2e 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -202,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/varlink-io.systemd.MuteConsole.c b/src/shared/varlink-io.systemd.MuteConsole.c new file mode 100644 index 00000000000..0cea5b85548 --- /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 00000000000..9957ed1a5f7 --- /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/test/test-varlink-idl.c b/src/test/test-varlink-idl.c index 6b3449f7772..1d328bd3e28 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/units/meson.build b/units/meson.build index c5b99e4e04c..ba2dfcab065 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 00000000000..7819eb91a3d --- /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-mute-console.socket b/units/systemd-mute-console.socket new file mode 100644 index 00000000000..6223dc033cc --- /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 00000000000..d43766c70bd --- /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