mute console kernel log/pid1 status output while firstboot is running (#39101)

This is also preparation for the installer later, split out of #38764.
It makes the experience a lot nicer if our nice little tools aren't
constantly interrupted by log spew from the kernel.

Fixes: #34448
This commit is contained in:
Lennart Poettering
2025-09-26 11:12:24 +02:00
committed by GitHub
23 changed files with 825 additions and 63 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

@@ -354,6 +354,16 @@
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--mute-console=</option></term>
<listitem><para>Takes a boolean argument. If true kernel log output and service manager status output
to the system console is temporarily disabled while <command>systemd-firstboot</command> is running,
so that its own output is not interrupted. Defaults to false.</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>

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

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

View File

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

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

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

42
src/shared/printk-util.c Normal file
View File

@@ -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, &current_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;
}

5
src/shared/printk-util.h Normal file
View File

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

View File

@@ -2,6 +2,8 @@
#include <unistd.h>
#include <sd-varlink.h>
#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;
}

View File

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

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

@@ -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, &current_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) {

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

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

View File

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

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

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

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