getty-generator: fine-grained execution control (#38258)

Closes #37928.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2025-07-23 09:58:27 +02:00
committed by GitHub
4 changed files with 361 additions and 103 deletions
+45 -9
View File
@@ -64,11 +64,35 @@
<varlistentry>
<term><varname>systemd.getty_auto=</varname></term>
<listitem><para>this options take an optional boolean argument, and default to yes.
The generator is enabled by default, and a false value may be used to disable it.
</para>
<listitem>
<para>This kernel command line option may be used to control the execution mode of the generator.
Takes an optional boolean argument. Since v258, this also takes comma-separated list of special
values: <literal>credential</literal>, <literal>container</literal>, <literal>console</literal>,
and <literal>builtin</literal>.</para>
<xi:include href="version-info.xml" xpointer="v250"/></listitem>
<para>When <literal>credential</literal> is specified, the two credentials
<varname>getty.ttys.serial</varname> and <varname>getty.ttys.container</varname> will be parsed.
See System Credentials section below for more details.</para>
<para>When <literal>container</literal> is specified, <filename>console-getty.service</filename>
and <filename>container-getty@.service</filename> will be enabled when the system is running in a
container. This option will be ignored when the system is not in a container.</para>
<para>When <literal>console</literal> is specified, <filename>serial-getty@.service</filename> for
active kernel consoles will be enabled. This option will be ignored when the system is running in a
container.</para>
<para>When <literal>builtins</literal> is specified, <filename>serial-getty@.service</filename> for
available virtualizer consoles will be enabled. This option will be ignored when the system is
running in a container.</para>
<para>When yes, the above four options will be enabled. When no, all options are disabled and no
service will be enabled. When the kernel command line option is specified without an argument,
defaults to yes. The generator is enabled by default, and a false value may be used to disable it.
</para>
<xi:include href="version-info.xml" xpointer="v250"/>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
@@ -80,11 +104,12 @@
<varlistentry>
<term><varname>$SYSTEMD_GETTY_AUTO</varname></term>
<listitem><para>This variable takes an optional boolean argument, and default to yes.
The generator is enabled by default, and a false value may be used to disable it.
</para>
<listitem>
<para>This environment variable may be used to control the execution mode of the generator.
Takes the same value as <varname>systemd.getty_auto=</varname> kernel command line option.</para>
<xi:include href="version-info.xml" xpointer="v250"/></listitem>
<xi:include href="version-info.xml" xpointer="v250"/>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
@@ -93,6 +118,17 @@
<title>System Credentials</title>
<variablelist class='system-credentials'>
<varlistentry>
<term><varname>getty.auto</varname></term>
<listitem>
<para>The system credential may be used to control the execution mode of the generator.
Takes the same value as <varname>systemd.getty_auto=</varname> kernel command line option.</para>
<xi:include href="version-info.xml" xpointer="v258"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>getty.ttys.serial</varname></term>
<term><varname>getty.ttys.container</varname></term>
@@ -101,7 +137,7 @@
TTYs. The two credentials should contain a newline-separated list of TTY names to spawn instances of
<filename>serial-getty@.service</filename> (in case of <varname>getty.ttys.serial</varname>) and
<filename>container-getty@.service</filename> (in case of <varname>getty.ttys.container</varname>)
on.</para>
on. Any lines starting with a <literal>#</literal> will be ignored.</para>
<xi:include href="version-info.xml" xpointer="v254"/></listitem>
</varlistentry>
+9
View File
@@ -287,6 +287,15 @@
</listitem>
</varlistentry>
<varlistentry>
<term><varname>getty.auto</varname></term>
<listitem><para>Used for controlling the execution mode of <filename>systemd-getty-generator</filename>. See
<citerefentry><refentrytitle>systemd-getty-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry> for details.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>getty.ttys.serial</varname></term>
<term><varname>getty.ttys.container</varname></term>
+168 -74
View File
@@ -21,17 +21,27 @@
#include "unit-name.h"
#include "virt.h"
static const char *arg_dest = NULL;
static bool arg_enabled = true;
typedef enum {
GETTY_SOURCE_NONE = 0,
GETTY_SOURCE_CREDENTIAL = 1 << 0,
GETTY_SOURCE_CONTAINER = 1 << 1,
GETTY_SOURCE_CONSOLE = 1 << 2,
GETTY_SOURCE_BUILTIN = 1 << 3,
GETTY_SOURCE_ALL = GETTY_SOURCE_CREDENTIAL | GETTY_SOURCE_CONTAINER | GETTY_SOURCE_CONSOLE | GETTY_SOURCE_BUILTIN,
} GettySourceFlag;
static int add_getty_impl(const char *tty, const char *type, const char *unit_path) {
static const char *arg_dest = NULL;
static GettySourceFlag arg_getty_sources = GETTY_SOURCE_ALL;
static int add_getty_impl(const char *tty, const char *path, const char *type, const char *unit_path) {
int r;
assert(type);
assert(path);
assert(unit_path);
if (!filename_is_valid(tty)) {
log_debug("Invalid %s tty device specified, ignoring: %s", type, tty);
log_debug("Invalid %s tty device specified, ignoring: %s", type, path);
return 0;
}
@@ -45,17 +55,23 @@ static int add_getty_impl(const char *tty, const char *type, const char *unit_pa
return generator_add_symlink_full(arg_dest, "getty.target", "wants", unit_path, instance);
}
static int add_serial_getty(const char *tty) {
tty = skip_dev_prefix(ASSERT_PTR(tty));
return add_getty_impl(tty, "serial", SYSTEM_DATA_UNIT_DIR "/serial-getty@.service");
static int add_serial_getty(const char *path) {
const char *tty = skip_dev_prefix(ASSERT_PTR(path));
return add_getty_impl(tty, path, "serial", SYSTEM_DATA_UNIT_DIR "/serial-getty@.service");
}
static int add_container_getty(const char *tty) {
if (is_path(tty))
/* Check if it is actually a pty. */
tty = path_startswith(skip_dev_prefix(tty), "pts/");
static int add_container_getty(const char *tty_or_path) {
const char *tty;
return add_getty_impl(tty, "container", SYSTEM_DATA_UNIT_DIR "/container-getty@.service");
assert(tty_or_path);
if (is_path(tty_or_path))
/* Check if it is actually a pty. */
tty = path_startswith(skip_dev_prefix(tty_or_path), "pts/");
else
tty = tty_or_path;
return add_getty_impl(tty, tty_or_path, "container", SYSTEM_DATA_UNIT_DIR "/container-getty@.service");
}
static int verify_tty(const char *path) {
@@ -159,24 +175,101 @@ static int add_credential_gettys(void) {
return 0;
}
static int parse_getty_sources(const char *s, GettySourceFlag *ret) {
int r;
assert(ret);
if (isempty(s)) {
*ret = GETTY_SOURCE_ALL;
return 0;
}
r = parse_boolean(s);
if (r >= 0) {
*ret = r ? GETTY_SOURCE_ALL : GETTY_SOURCE_NONE;
return 0;
}
static struct {
GettySourceFlag flag;
const char *str;
} table[] = {
{ GETTY_SOURCE_CREDENTIAL, "credential", },
{ GETTY_SOURCE_CONTAINER, "container", },
{ GETTY_SOURCE_CONSOLE, "console", },
{ GETTY_SOURCE_BUILTIN, "builtin", },
};
GettySourceFlag flags = 0;
for (const char *p = s;;) {
_cleanup_free_ char *word = NULL;
r = extract_first_word(&p, &word, ",", /* flags = */ 0);
if (r < 0)
return r;
if (r == 0)
break;
bool found = false;
FOREACH_ELEMENT(i, table)
if (streq(word, i->str)) {
flags |= i->flag;
found = true;
break;
}
if (!found)
return -EINVAL;
}
*ret = flags;
return 0;
}
static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
int r;
assert(key);
if (proc_cmdline_key_streq(key, "systemd.getty_auto")) {
r = value ? parse_boolean(value) : 1;
r = parse_getty_sources(value, &arg_getty_sources);
if (r < 0)
log_warning_errno(r, "Failed to parse getty_auto switch \"%s\", ignoring: %m", value);
else
arg_enabled = r;
log_warning_errno(r, "Failed to parse systemd.getty_auto= kernel command line option, ignoring: %s", value);
}
return 0;
}
static void parse_env(void) {
_cleanup_free_ char *value = NULL;
int r;
r = getenv_for_pid(1, "SYSTEMD_GETTY_AUTO", &value);
if (r < 0)
log_warning_errno(r, "Failed to parse $SYSTEMD_GETTY_AUTO environment variable, ignoring: %m");
else if (r > 0) {
r = parse_getty_sources(value, &arg_getty_sources);
if (r < 0)
log_warning_errno(r, "Failed to parse $SYSTEMD_GETTY_AUTO environment variable, ignoring: %s", value);
}
}
static void parse_credentials(void) {
_cleanup_free_ char *value = NULL;
int r;
r = read_credential_with_decryption("getty.auto", (void**) &value, /* ret_size = */ NULL);
if (r < 0)
log_debug_errno(r, "Failed to read credential 'getty.auto', ignoring: %m");
else if (r > 0) {
r = parse_getty_sources(value, &arg_getty_sources);
if (r < 0)
log_warning_errno(r, "Invalid 'getty.auto' credential, ignoring: %s", value);
}
}
static int run(const char *dest, const char *dest_early, const char *dest_late) {
_cleanup_free_ char *getty_auto = NULL;
int r;
assert_se(arg_dest = dest);
@@ -190,72 +283,73 @@ static int run(const char *dest, const char *dest_early, const char *dest_late)
if (r < 0)
log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
r = getenv_for_pid(1, "SYSTEMD_GETTY_AUTO", &getty_auto);
if (r < 0)
log_warning_errno(r, "Failed to parse $SYSTEMD_GETTY_AUTO environment variable, ignoring: %m");
else if (r > 0) {
r = parse_boolean(getty_auto);
if (r < 0)
log_warning_errno(r, "Failed to parse $SYSTEMD_GETTY_AUTO value \"%s\", ignoring: %m", getty_auto);
else
arg_enabled = r;
}
parse_env();
parse_credentials();
if (!arg_enabled) {
if (arg_getty_sources == GETTY_SOURCE_NONE) {
log_debug("Disabled, exiting.");
return 0;
}
r = add_credential_gettys();
if (r < 0)
return r;
if (detect_container() > 0)
/* Add console shell and look at $container_ttys, but don't do add any
* further magic if we are in a container. */
return run_container();
/* Automatically add in a serial getty on all active kernel consoles */
_cleanup_strv_free_ char **consoles = NULL;
r = get_kernel_consoles(&consoles);
if (r < 0)
log_warning_errno(r, "Failed to get active kernel consoles, ignoring: %m");
else if (r > 0)
STRV_FOREACH(i, consoles) {
/* We assume that gettys on virtual terminals are started via manual configuration
* and do this magic only for non-VC terminals. */
if (tty_is_vc(*i))
continue;
if (verify_tty(*i) < 0)
continue;
r = add_serial_getty(*i);
if (r < 0)
return r;
}
/* Automatically add a serial getty to each available virtualizer console. */
FOREACH_STRING(j,
"hvc0",
"xvc0",
"hvsi0",
"sclp_line0",
"ttysclp0",
"3270/tty1") {
_cleanup_free_ char *p = NULL;
p = path_join("/dev", j);
if (!p)
return log_oom();
if (access(p, F_OK) < 0)
continue;
r = add_serial_getty(j);
if (FLAGS_SET(arg_getty_sources, GETTY_SOURCE_CREDENTIAL)) {
r = add_credential_gettys();
if (r < 0)
return r;
}
if (detect_container() > 0) {
/* Add console shell and look at $container_ttys, but don't do add any
* further magic if we are in a container. */
if (FLAGS_SET(arg_getty_sources, GETTY_SOURCE_CONTAINER))
return run_container();
return 0;
}
/* Automatically add in a serial getty on all active kernel consoles */
if (FLAGS_SET(arg_getty_sources, GETTY_SOURCE_CONSOLE)) {
_cleanup_strv_free_ char **consoles = NULL;
r = get_kernel_consoles(&consoles);
if (r < 0)
log_warning_errno(r, "Failed to get active kernel consoles, ignoring: %m");
else if (r > 0)
STRV_FOREACH(i, consoles) {
/* We assume that gettys on virtual terminals are started via manual configuration
* and do this magic only for non-VC terminals. */
if (tty_is_vc(*i))
continue;
if (verify_tty(*i) < 0)
continue;
r = add_serial_getty(*i);
if (r < 0)
return r;
}
}
/* Automatically add a serial getty to each available virtualizer console. */
if (FLAGS_SET(arg_getty_sources, GETTY_SOURCE_BUILTIN))
FOREACH_STRING(j,
"hvc0",
"xvc0",
"hvsi0",
"sclp_line0",
"ttysclp0",
"3270/tty1") {
_cleanup_free_ char *p = NULL;
p = path_join("/dev", j);
if (!p)
return log_oom();
if (access(p, F_OK) < 0)
continue;
r = add_serial_getty(j);
if (r < 0)
return r;
}
return 0;
}
+139 -20
View File
@@ -20,20 +20,25 @@ trap at_exit EXIT
test -x "${GENERATOR_BIN:?}"
if in_container; then
# Do a limited test in a container, as writing to /dev is usually restrited
: "getty-generator: \$container_ttys env (container)"
# In a container we allow only /dev/pts/* ptys
PID1_ENVIRON="container_ttys=tty0 pts/0 /dev/tty0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
check_none() {
[[ "$(find "$OUT_DIR" ! -type d | wc -l)" -eq 0 ]]
}
# console-getty.service is always pulled in in containers
check_container() {
link_endswith "$OUT_DIR/normal/getty.target.wants/console-getty.service" "/lib/systemd/system/console-getty.service"
link_endswith "$OUT_DIR/normal/getty.target.wants/container-getty@0.service" "/lib/systemd/system/container-getty@.service"
test ! -e "$OUT_DIR/normal/getty.target.wants/container-getty@tty0.service"
test ! -h "$OUT_DIR/normal/getty.target.wants/container-getty@tty0.service"
}
exit 0
fi
check_no_container() {
local unit
for unit in console-getty.service container-getty@0.service container-getty@tty0.service; do
test ! -e "$OUT_DIR/normal/getty.target.wants/$unit"
test ! -h "$OUT_DIR/normal/getty.target.wants/$unit"
done
}
DUMMY_ACTIVE_CONSOLES=(
"hvc99"
@@ -52,6 +57,94 @@ DUMMY_CONSOLES=(
"${DUMMY_ACTIVE_CONSOLES[@]}"
"${DUMMY_INACTIVE_CONSOLES[@]}"
)
check_console() {
local console unit
for console in "${DUMMY_ACTIVE_CONSOLES[@]}"; do
unit="$(systemd-escape --template serial-getty@.service "$console")"
link_endswith "$OUT_DIR/normal/getty.target.wants/$unit" "/lib/systemd/system/serial-getty@.service"
done
for console in "${DUMMY_INACTIVE_CONSOLES[@]}" /dev/notatty99; do
unit="$(systemd-escape --template serial-getty@.service "$console")"
test ! -e "$OUT_DIR/normal/getty.target.wants/$unit"
test ! -h "$OUT_DIR/normal/getty.target.wants/$unit"
done
}
check_no_console() {
local console unit
for console in "${DUMMY_CONSOLES[@]}" /dev/notatty99; do
unit="$(systemd-escape --template serial-getty@.service "$console")"
test ! -e "$OUT_DIR/normal/getty.target.wants/$unit"
test ! -h "$OUT_DIR/normal/getty.target.wants/$unit"
done
}
BUILTINS=(
"hvc0"
"xvc0"
"hvsi0"
"sclp_line0"
"ttysclp0"
"3270/tty1"
)
check_builtin() {
local console unit
for console in "${BUILTINS[@]}"; do
unit="$(systemd-escape --template serial-getty@.service "$console")"
if [[ -e "/dev/$console" ]]; then
link_endswith "$OUT_DIR/normal/getty.target.wants/$unit" "/lib/systemd/system/serial-getty@.service"
else
test ! -e "$OUT_DIR/normal/getty.target.wants/$unit"
test ! -h "$OUT_DIR/normal/getty.target.wants/$unit"
fi
done
}
check_no_builtin() {
local console unit
for console in "${BUILTINS[@]}"; do
unit="$(systemd-escape --template serial-getty@.service "$console")"
test ! -e "$OUT_DIR/normal/getty.target.wants/$unit"
test ! -h "$OUT_DIR/normal/getty.target.wants/$unit"
done
}
if in_container; then
# Do a limited test in a container, as writing to /dev is usually restrited
: "getty-generator: \$container_ttys env (container)"
# In a container we allow only /dev/pts/* ptys
PID1_ENVIRON="container_ttys=tty0 pts/0 /dev/tty0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
check_container
check_no_console
check_no_builtin
: "getty-generator: SYSTEMD_GETTY_AUTO=0 in PID1's environment (container)"
PID1_ENVIRON="container_ttys=tty0 pts/0 /dev/tty0\0SYSTEMD_GETTY_AUTO=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
check_none
: "getty-generator: SYSTEMD_GETTY_AUTO=console in PID1's environment (container)"
PID1_ENVIRON="container_ttys=tty0 pts/0 /dev/tty0\0SYSTEMD_GETTY_AUTO=console" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
check_none
: "getty-generator: SYSTEMD_GETTY_AUTO=console,builtin in PID1's environment (container)"
PID1_ENVIRON="container_ttys=tty0 pts/0 /dev/tty0\0SYSTEMD_GETTY_AUTO=console,builtin" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
check_none
: "getty-generator: SYSTEMD_GETTY_AUTO=console,builtin,container in PID1's environment (container)"
PID1_ENVIRON="container_ttys=tty0 pts/0 /dev/tty0\0SYSTEMD_GETTY_AUTO=console,builtin,container" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
check_container
check_no_console
check_no_builtin
exit 0
fi
# Create a bunch of dummy consoles
for console in "${DUMMY_CONSOLES[@]}"; do
mknod "/dev/$console" c 4 0
@@ -60,29 +153,55 @@ done
touch /dev/notatty99
# Temporarily replace /sys/class/tty/console/active with our list of dummy
# consoles so getty-generator can process them
echo -ne "${DUMMY_ACTIVE_CONSOLES[@]}" /dev/notatty99 >/tmp/dummy-active-consoles
echo -ne "${DUMMY_ACTIVE_CONSOLES[@]}" /dev/notatty99 >/tmp/dummy-active-consoles
mount -v --bind /tmp/dummy-active-consoles /sys/class/tty/console/active
: "getty-generator: no arguments"
# Sneak in an invalid value for $SYSTEMD_GETTY_AUTO to test things out
PID1_ENVIRON="SYSTEMD_GETTY_AUTO=foo" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
for console in "${DUMMY_ACTIVE_CONSOLES[@]}"; do
unit="$(systemd-escape --template serial-getty@.service "$console")"
link_endswith "$OUT_DIR/normal/getty.target.wants/$unit" "/lib/systemd/system/serial-getty@.service"
done
for console in "${DUMMY_INACTIVE_CONSOLES[@]}" /dev/notatty99; do
unit="$(systemd-escape --template serial-getty@.service "$console")"
test ! -e "$OUT_DIR/normal/getty.target.wants/$unit"
test ! -h "$OUT_DIR/normal/getty.target.wants/$unit"
done
check_no_container
check_console
check_builtin
: "getty-generator: systemd.getty_auto=0 on kernel cmdline"
SYSTEMD_PROC_CMDLINE="systemd.getty_auto=foo systemd.getty_auto=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
[[ "$(find "$OUT_DIR" ! -type d | wc -l)" -eq 0 ]]
check_none
: "getty-generator: systemd.getty_auto=container on kernel cmdline"
SYSTEMD_PROC_CMDLINE="systemd.getty_auto=foo systemd.getty_auto=container" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
check_none
: "getty-generator: systemd.getty_auto=container,console on kernel cmdline"
SYSTEMD_PROC_CMDLINE="systemd.getty_auto=foo systemd.getty_auto=container,console" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
check_no_container
check_console
check_no_builtin
: "getty-generator: systemd.getty_auto=container,builtin on kernel cmdline"
SYSTEMD_PROC_CMDLINE="systemd.getty_auto=foo systemd.getty_auto=container,builtin" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
check_no_container
check_no_console
check_builtin
: "getty-generator: SYSTEMD_GETTY_AUTO=0 in PID1's environment"
PID1_ENVIRON="SYSTEMD_GETTY_AUTO=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
[[ "$(find "$OUT_DIR" ! -type d | wc -l)" -eq 0 ]]
check_none
: "getty-generator: SYSTEMD_GETTY_AUTO=container in PID1's environment"
PID1_ENVIRON="SYSTEMD_GETTY_AUTO=container" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
check_none
: "getty-generator: SYSTEMD_GETTY_AUTO=container,console in PID1's environment"
PID1_ENVIRON="SYSTEMD_GETTY_AUTO=container,console" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
check_no_container
check_console
check_no_builtin
: "getty-generator: SYSTEMD_GETTY_AUTO=container,builtin in PID1's environment"
PID1_ENVIRON="SYSTEMD_GETTY_AUTO=container,builtin" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
check_no_container
check_no_console
check_builtin
# Cleanup
umount /sys/class/tty/console/active --lazy