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"