Cleanups around tty_is_vc() and get_kernel_consoles() (#36171)

Prompted by #36167
Replaces #36167 and #36175
This commit is contained in:
Daan De Meyer
2025-01-27 14:43:39 +01:00
committed by GitHub
4 changed files with 93 additions and 103 deletions
+31 -24
View File
@@ -764,21 +764,7 @@ int make_console_stdio(void) {
return 0;
}
bool tty_is_vc(const char *tty) {
assert(tty);
return vtnr_from_tty(tty) >= 0;
}
bool tty_is_console(const char *tty) {
assert(tty);
return streq(skip_dev_prefix(tty), "console");
}
int vtnr_from_tty(const char *tty) {
int r;
static int vtnr_from_tty_raw(const char *tty, unsigned *ret) {
assert(tty);
tty = skip_dev_prefix(tty);
@@ -787,8 +773,16 @@ int vtnr_from_tty(const char *tty) {
if (!e)
return -EINVAL;
return safe_atou(e, ret);
}
int vtnr_from_tty(const char *tty) {
unsigned u;
r = safe_atou(e, &u);
int r;
assert(tty);
r = vtnr_from_tty_raw(tty, &u);
if (r < 0)
return r;
if (!vtnr_is_valid(u))
@@ -797,6 +791,23 @@ int vtnr_from_tty(const char *tty) {
return (int) u;
}
bool tty_is_vc(const char *tty) {
assert(tty);
/* NB: for >= 0 values no range check is conducted here, on the assumption that the caller will
* either extract vtnr through vtnr_from_tty() later where ERANGE would be reported, or doesn't care
* about whether it's strictly valid, but only asking "does this fall into the vt catogory?", for which
* "yes" seems to be a better answer. */
return vtnr_from_tty_raw(tty, /* ret = */ NULL) >= 0;
}
bool tty_is_console(const char *tty) {
assert(tty);
return streq(skip_dev_prefix(tty), "console");
}
int resolve_dev_console(char **ret) {
int r;
@@ -855,13 +866,12 @@ int resolve_dev_console(char **ret) {
int get_kernel_consoles(char ***ret) {
_cleanup_strv_free_ char **l = NULL;
_cleanup_free_ char *line = NULL;
const char *p;
int r;
assert(ret);
/* If /sys is mounted read-only this means we are running in some kind of container environment. In that
* case /sys would reflect the host system, not us, hence ignore the data we can read from it. */
/* If /sys/ is mounted read-only this means we are running in some kind of container environment.
* In that case /sys/ would reflect the host system, not us, hence ignore the data we can read from it. */
if (path_is_read_only_fs("/sys") > 0)
goto fallback;
@@ -869,8 +879,7 @@ int get_kernel_consoles(char ***ret) {
if (r < 0)
return r;
p = line;
for (;;) {
for (const char *p = line;;) {
_cleanup_free_ char *tty = NULL, *path = NULL;
r = extract_first_word(&p, &tty, NULL, 0);
@@ -906,8 +915,7 @@ int get_kernel_consoles(char ***ret) {
}
*ret = TAKE_PTR(l);
return 0;
return strv_length(*ret);
fallback:
r = strv_extend(&l, "/dev/console");
@@ -915,7 +923,6 @@ fallback:
return r;
*ret = TAKE_PTR(l);
return 0;
}
+40 -68
View File
@@ -25,72 +25,53 @@
static const char *arg_dest = NULL;
static bool arg_enabled = true;
static int add_symlink(const char *fservice, const char *tservice) {
const char *from, *to;
assert(fservice);
assert(tservice);
from = strjoina(SYSTEM_DATA_UNIT_DIR "/", fservice);
to = strjoina(arg_dest, "/getty.target.wants/", tservice);
(void) mkdir_parents_label(to, 0755);
if (symlink(from, to) < 0) {
/* In case console=hvc0 is passed this will very likely result in EEXIST */
if (errno == EEXIST)
return 0;
return log_error_errno(errno, "Failed to create symlink %s: %m", to);
}
return 0;
}
static int add_serial_getty(const char *tty) {
_cleanup_free_ char *n = NULL;
_cleanup_free_ char *instance = NULL;
int r;
assert(tty);
tty = skip_dev_prefix(tty);
log_debug("Automatically adding serial getty for /dev/%s.", tty);
r = unit_name_from_path_instance("serial-getty", tty, ".service", &n);
r = unit_name_path_escape(tty, &instance);
if (r < 0)
return log_error_errno(r, "Failed to generate service name: %m");
return log_error_errno(r, "Failed to escape tty path: %m");
return add_symlink("serial-getty@.service", n);
return generator_add_symlink_full(arg_dest,
"getty.target", "wants",
SYSTEM_DATA_UNIT_DIR "/serial-getty@.service", instance);
}
static int add_container_getty(const char *tty) {
_cleanup_free_ char *n = NULL;
_cleanup_free_ char *instance = NULL;
int r;
assert(tty);
assert(!path_startswith(tty, "/dev/"));
log_debug("Automatically adding container getty for /dev/pts/%s.", tty);
r = unit_name_from_path_instance("container-getty", tty, ".service", &n);
r = unit_name_path_escape(tty, &instance);
if (r < 0)
return log_error_errno(r, "Failed to generate service name: %m");
return log_error_errno(r, "Failed to escape tty path: %m");
return add_symlink("container-getty@.service", n);
return generator_add_symlink_full(arg_dest,
"getty.target", "wants",
SYSTEM_DATA_UNIT_DIR "/container-getty@.service", instance);
}
static int verify_tty(const char *name) {
static int verify_tty(const char *path) {
_cleanup_close_ int fd = -EBADF;
const char *p;
/* Some TTYs are weird and have been enumerated but don't work
* when you try to use them, such as classic ttyS0 and
* friends. Let's check that and open the device and run
* isatty() on it. */
assert(path);
p = strjoina("/dev/", name);
/* Some TTYs are weird and have been enumerated but don't work when you try to use them, such as
* classic ttyS0 and friends. Let's check that and open the device and run isatty() on it. */
/* O_NONBLOCK is essential here, to make sure we don't wait
* for DCD */
fd = open(p, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC|O_NOFOLLOW);
/* O_NONBLOCK is essential here, to make sure we don't wait for DCD */
fd = open(path, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC|O_NOFOLLOW);
if (fd < 0)
return -errno;
@@ -101,18 +82,17 @@ static int verify_tty(const char *name) {
}
static int run_container(void) {
_cleanup_free_ char *container_ttys = NULL;
int r;
log_debug("Automatically adding console shell.");
r = add_symlink("console-getty.service", "console-getty.service");
r = generator_add_symlink(arg_dest, "getty.target", "wants", SYSTEM_DATA_UNIT_DIR "/console-getty.service");
if (r < 0)
return r;
/* When $container_ttys is set for PID 1, spawn gettys on all ptys named therein.
* Note that despite the variable name we only support ptys here. */
_cleanup_free_ char *container_ttys = NULL;
(void) getenv_for_pid(1, "container_ttys", &container_ttys);
for (const char *p = container_ttys;;) {
@@ -124,10 +104,8 @@ static int run_container(void) {
if (r == 0)
return 0;
const char *tty = word;
/* First strip off /dev/ if it is specified */
tty = path_startswith(tty, "/dev/") ?: tty;
const char *tty = skip_dev_prefix(word);
/* Then, make sure it's actually a pty */
tty = path_startswith(tty, "pts/");
@@ -245,30 +223,24 @@ static int run(const char *dest, const char *dest_early, const char *dest_late)
return run_container();
/* Automatically add in a serial getty on all active kernel consoles */
_cleanup_free_ char *active = NULL;
(void) read_one_line_file("/sys/class/tty/console/active", &active);
for (const char *p = active;;) {
_cleanup_free_ char *tty = NULL;
_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;
r = extract_first_word(&p, &tty, NULL, 0);
if (r < 0)
return log_error_errno(r, "Failed to parse /sys/class/tty/console/active: %m");
if (r == 0)
break;
if (verify_tty(*i) < 0)
continue;
/* We assume that gettys on virtual terminals are started via manual configuration and do
* this magic only for non-VC terminals. */
if (isempty(tty) || tty_is_vc(tty))
continue;
if (verify_tty(tty) < 0)
continue;
r = add_serial_getty(tty);
if (r < 0)
return r;
}
r = add_serial_getty(*i);
if (r < 0)
return r;
}
/* Automatically add in a serial getty on the first virtualizer console */
FOREACH_STRING(j,
+1 -2
View File
@@ -348,8 +348,7 @@ static int get_seat_from_display(const char *display, const char **seat, uint32_
v = vtnr_from_tty(tty);
if (v < 0)
return v;
else if (v == 0)
return -ENOENT;
assert(v > 0);
if (seat)
*seat = "seat0";
@@ -40,6 +40,7 @@
#include "set.h"
#include "signal-util.h"
#include "socket-util.h"
#include "static-destruct.h"
#include "string-util.h"
#include "strv.h"
#include "terminal-util.h"
@@ -54,7 +55,9 @@ static enum {
static bool arg_plymouth = false;
static bool arg_console = false;
static const char *arg_device = NULL;
static char *arg_device = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_device, freep);
static int send_passwords(const char *socket_name, char **passwords) {
int r;
@@ -530,12 +533,13 @@ static int parse_argv(int argc, char *argv[]) {
case ARG_CONSOLE:
arg_console = true;
if (optarg) {
if (isempty(optarg))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Empty console device path is not allowed.");
arg_device = optarg;
arg_device = strdup(optarg);
if (!arg_device)
return log_oom();
}
break;
@@ -667,6 +671,11 @@ static int ask_on_consoles(char *argv[]) {
r = get_kernel_consoles(&consoles);
if (r < 0)
return log_error_errno(r, "Failed to determine devices of /dev/console: %m");
if (r <= 1) {
/* No need to spawn subprocesses, there's only one console or using /dev/console as fallback */
arg_device = TAKE_PTR(consoles[0]);
return 0;
}
pids = set_new(NULL);
if (!pids)
@@ -714,7 +723,7 @@ static int ask_on_consoles(char *argv[]) {
}
terminate_agents(pids);
return 0;
return 1;
}
static int run(int argc, char *argv[]) {
@@ -728,11 +737,14 @@ static int run(int argc, char *argv[]) {
if (r <= 0)
return r;
if (arg_console && !arg_device)
/*
* Spawn a separate process for each console device.
*/
return ask_on_consoles(argv);
/* Spawn a separate process for each console device if there're multiple. */
if (arg_console && !arg_device) {
r = ask_on_consoles(argv);
if (r != 0)
return r;
assert(arg_device);
}
if (arg_device)
/* Later on, a controlling terminal will be acquired, therefore the current process has to