From 6b77a16f8be4094d7dade52aff706ddee3fb0793 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 5 Aug 2026 17:18:40 +0200 Subject: [PATCH] conf: add missing passenv to tty blocks The line-based format has had the flag since v4.4 (issue #286), where it prepends -p to the built-in getty, which turns it into login -p and passes the environment on. The block format was written from the three documented tty variants and the flags listed in the tty documentation, and passenv was in neither, so it was left out. Converting a tty line that used it therefore lost it, with nothing said. It only reaches the built-in getty. An external getty is handed its arguments through command, so there is nowhere to put a -p, and the setting is refused with a warning rather than quietly ignored. Signed-off-by: Joachim Wiberg --- doc/ChangeLog.md | 2 ++ doc/config/migration.md | 8 +++++ doc/config/tty.md | 1 + src/conf.c | 15 +++++++++ test/Makefile.am | 2 ++ test/conf-dup-title.sh | 8 ----- test/conf-if.sh | 8 ----- test/conf-provides.sh | 8 ----- test/conf-tty.sh | 71 +++++++++++++++++++++++++++++++++++++++++ test/lib/setup.sh | 10 ++++++ 10 files changed, 109 insertions(+), 24 deletions(-) create mode 100755 test/conf-tty.sh diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index fb3c4fe6..5b25a99b 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -8,6 +8,8 @@ All relevant changes are documented in this file. ### Changes +- The `tty` block takes `passenv`, which the line-based format has had + since v4.4 (issue #286) and the block format was missing - New `provides` setting for run/task/service/sysv blocks, naming conditions the service asserts in addition to its own `pid/`. Lets variants of one service, qualified with `if`, share the barrier diff --git a/doc/config/migration.md b/doc/config/migration.md index 8a949876..61b0562a 100644 --- a/doc/config/migration.md +++ b/doc/config/migration.md @@ -216,6 +216,14 @@ become notty = true } +The bare flags keep their names as booleans: `noclear`, `nowait`, +`nologin`, `passenv`, `rescue`, e.g. `passenv` becomes +`passenv = true`. + +A `tty` block is named by the device, not the title, so all three above +show up as `tty:ttyS0` and the like in `initctl`. The title still has +to be unique within the file. + Top-level directives -------------------- diff --git a/doc/config/tty.md b/doc/config/tty.md index 138a7b81..8d5ee43f 100644 --- a/doc/config/tty.md +++ b/doc/config/tty.md @@ -24,6 +24,7 @@ Settings common to all three: | `noclear` | | Do not clear the TTY after each session | | `nowait` | | Do not wait for Enter before the login prompt | | `nologin` | | Skip login, give a shell straight away | +| `passenv` | | Pass the environment on to `login`, built-in getty only | The `device` variant takes two more: diff --git a/src/conf.c b/src/conf.c index bf1fe475..6ffd6afc 100644 --- a/src/conf.c +++ b/src/conf.c @@ -313,6 +313,7 @@ static cfg_opt_t tty_opts[] = { CFG_BOOL ("noclear", cfg_false, CFGF_NODEFAULT), CFG_BOOL ("nowait", cfg_false, CFGF_NODEFAULT), CFG_BOOL ("nologin", cfg_false, CFGF_NODEFAULT), + CFG_BOOL ("passenv", cfg_false, CFGF_NODEFAULT), CFG_STR_LIST("command", NULL, CFGF_NODEFAULT), /* candidates, see svc_command() */ CFG_BOOL ("notty", cfg_false, CFGF_NODEFAULT), CFG_BOOL ("rescue", cfg_false, CFGF_NODEFAULT), @@ -1748,6 +1749,20 @@ static void tty_translate(cfg_t *sec, struct rlimit rlimit[], char *file) if (sec_getbool(sec, "nologin", NULL)) addtok(line, sizeof(line), "nologin"); + /* + * passenv is handed to the built-in getty as -p, which it turns + * into login -p. An external getty gets its arguments from + * command, so there is nowhere to put it. + */ + if (sec_getbool(sec, "passenv", NULL)) { + if (dev || !cmd) + addtok(line, sizeof(line), "passenv"); + else + logit(LOG_WARNING, "%s: %s: passenv applies to the" + " built-in getty, pass -p in command instead," + " ignoring", file, cfg_title(sec)); + } + if (dev && (str = sec_getstr(sec, "term", NULL))) addtok(line, sizeof(line), "%s", str); diff --git a/test/Makefile.am b/test/Makefile.am index c46205f5..a12c296b 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -34,6 +34,7 @@ EXTRA_DIST += cond-start-task.sh EXTRA_DIST += conf-format.sh EXTRA_DIST += conf-command.sh EXTRA_DIST += conf-provides.sh +EXTRA_DIST += conf-tty.sh EXTRA_DIST += conf-dup-title.sh EXTRA_DIST += conf-dirs.sh EXTRA_DIST += conf-if.sh @@ -86,6 +87,7 @@ TESTS += cond-start-task.sh TESTS += conf-format.sh TESTS += conf-command.sh TESTS += conf-provides.sh +TESTS += conf-tty.sh TESTS += conf-dup-title.sh TESTS += conf-dirs.sh TESTS += conf-if.sh diff --git a/test/conf-dup-title.sh b/test/conf-dup-title.sh index 26a7193a..cbb4da96 100755 --- a/test/conf-dup-title.sh +++ b/test/conf-dup-title.sh @@ -15,14 +15,6 @@ BOOTSTRAP="service service.sh { command = \"service.sh\" }" -# initctl status prints a detail block for a single match and a table -# only for several, so count lines in the full listing instead. -assert_loaded() -{ - assert "Service $1 loaded: $2" \ - "$(texec initctl -t status | awk -v n="$1" '$2 == n' | wc -l)" -eq "$2" -} - test_teardown() { say "Running test teardown." diff --git a/test/conf-if.sh b/test/conf-if.sh index 3f9b08be..f78cc629 100755 --- a/test/conf-if.sh +++ b/test/conf-if.sh @@ -43,14 +43,6 @@ service mixed { command = \"serv -np -i mixed\" }" -# initctl status prints a detail block for a single match and a table -# only for several, so count lines in the full listing instead. -assert_loaded() -{ - assert "Service $1 loaded: $2" \ - "$(texec initctl -t status | awk -v n="$1" '$2 == n' | wc -l)" -eq "$2" -} - test_teardown() { say "Running test teardown." diff --git a/test/conf-provides.sh b/test/conf-provides.sh index 1012e57c..7b1843cd 100755 --- a/test/conf-provides.sh +++ b/test/conf-provides.sh @@ -32,14 +32,6 @@ service downstream { command = \"serv -np -i downstream\" }" -# initctl status prints a detail block for a single match and a table -# only for several, so count lines in the full listing instead. -assert_loaded() -{ - assert "Service $1 loaded: $2" \ - "$(texec initctl -t status | awk -v n="$1" '$2 == n' | wc -l)" -eq "$2" -} - # The IDENT column of 'initctl cond dump' names the owner of a # condition, which for a provided one is the service that claimed it. assert_provider() diff --git a/test/conf-tty.sh b/test/conf-tty.sh new file mode 100755 index 00000000..e04aafed --- /dev/null +++ b/test/conf-tty.sh @@ -0,0 +1,71 @@ +#!/bin/sh +# Verify tty block settings that have no equivalent elsewhere. The +# 'passenv' flag is handed to the built-in getty as -p, which it turns +# into 'login -p', passing the environment on to the login program. +set -eu + +TEST_DIR=$(dirname "$0") + +# shellcheck disable=SC2034 +BOOTSTRAP="tty console { + runlevel = \"12345\" + device = \"/dev/console\" + passenv = true + noclear = true +}" + +# The built-in getty is exec'd with -p ahead of the device, so the flag +# is visible in the command Finit registered. +assert_cmd_has() +{ + assert "Command of $1 contains '$2'" \ + "$(texec initctl status "$1" | grep 'Command' | grep -c -- "$2")" -eq 1 +} + +test_teardown() +{ + say "Running test teardown." + run "rm -f $FINIT_CONF" +} + +# shellcheck source=/dev/null +. "$TEST_DIR/lib/setup.sh" + +say 'The tty block loaded, so passenv is a known setting' +retry 'assert_loaded tty:console 1' + +say 'passenv reaches the built-in getty as -p' +assert_cmd_has tty:console " -p" + +say 'Waiting for bootstrap to finish before rewriting the configuration' +retry "test \"\$(texec sh -c \"initctl runlevel | awk '{print \\\$2;}'\")\" = 2" 20 1 + +say 'Without passenv the flag is absent' +run "echo 'tty console {' > $FINIT_CONF" +run "echo ' runlevel = \"12345\"' >> $FINIT_CONF" +run "echo ' device = \"/dev/console\"' >> $FINIT_CONF" +run "echo ' noclear = true' >> $FINIT_CONF" +run "echo '}' >> $FINIT_CONF" +run "initctl reload" + +retry 'assert_loaded tty:console 1' +assert "Command has no -p" \ + "$(texec initctl status tty:console | grep 'Command' | grep -c -- ' -p')" -eq 0 + +# passenv is a built-in getty flag. An external getty takes its +# arguments from command, so there is nowhere to put it, and the +# setting is refused rather than silently dropped. Note the device +# still has to be in there: an external getty is told which TTY to +# open by its own arguments, and that is also where Finit reads it +# from to name the service. +say 'passenv with an external getty is refused, the tty still loads' +run "echo 'tty extgetty {' > $FINIT_CONF" +run "echo ' runlevel = \"12345\"' >> $FINIT_CONF" +run "echo ' command = \"/bin/sh /dev/console\"' >> $FINIT_CONF" +run "echo ' passenv = true' >> $FINIT_CONF" +run "echo '}' >> $FINIT_CONF" +run "initctl reload" + +retry 'assert_loaded tty:console 1' +assert "external getty command has no -p" \ + "$(texec initctl status tty:console | grep 'Command' | grep -c -- ' -p')" -eq 0 diff --git a/test/lib/setup.sh b/test/lib/setup.sh index 3ffdfb60..18b90138 100755 --- a/test/lib/setup.sh +++ b/test/lib/setup.sh @@ -41,6 +41,16 @@ assert_num_services() assert "$1 services are loaded" "$(texec initctl -t status "$2" | wc -l)" -eq "$1" } +# Unlike assert_num_services() this matches the identity exactly, so +# 'foo' does not count 'foobar', and an instance must be spelled out as +# 'foo:1'. initctl status prints a detail block for a single match and +# a table only for several, hence counting lines in the full listing. +assert_loaded() +{ + assert "Service $1 loaded: $2" \ + "$(texec initctl -t status | awk -v n="$1" '$2 == n' | wc -l)" -eq "$2" +} + assert_forking() { assert "service $1 forking:$2" "$(texec initctl -j status "$1" | jq -M .forking)" = "$2"