1
0
mirror of https://github.com/systemd/systemd synced 2025-10-06 00:13:24 +02:00

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.
This commit is contained in:
Lennart Poettering
2025-09-15 18:19:22 +02:00
parent 45c04464dc
commit ac63a04bd6
13 changed files with 606 additions and 0 deletions

View File

@@ -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',

View File

@@ -0,0 +1,79 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="systemd-mute-console"
xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
<title>systemd-mute-console</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-mute-console</refentrytitle>
<manvolnum>1</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-mute-console</refname>
<refname>systemd-mute-console@.service</refname>
<refname>systemd-mute-console.socket</refname>
<refpurpose>Temporarily mute kernel log output and service manager status output to the system console</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>systemd-mute-console</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
</cmdsynopsis>
<para><filename>systemd-mute-console@.service</filename></para>
<para><filename>systemd-mute-console.socket</filename></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>The <command>systemd-mute-console</command> 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.</para>
<para>The tool can be invoked directly in which case it will mute the two outputs and then issue an
<citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry>
<literal>READY=1</literal> notification once that is completed. On <constant>SIGINT</constant> or
<constant>SIGTERM</constant> output is unmuted again. Alternatively it can be invoked via Varlink
IPC.</para>
</refsect1>
<refsect1>
<title>Options</title>
<para>The following options are understood:</para>
<variablelist>
<varlistentry>
<term><option>--kernel=<replaceable>bool</replaceable></option></term>
<term><option>--pid1=<replaceable>bool</replaceable></option></term>
<listitem><para>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.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
</refsect1>
<refsect1>
<title>See Also</title>
<para><simplelist type="inline">
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-firstboot</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
</simplelist></para>
</refsect1>
</refentry>

View File

@@ -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')

View File

@@ -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'),
},
]

View File

@@ -0,0 +1,419 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <getopt.h>
#include <stdbool.h>
#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);

View File

@@ -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',

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);

View File

@@ -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' },

View File

@@ -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

View File

@@ -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

View File

@@ -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