From 6ea832a20700f5282c08c70f38422c6ab290a0b5 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 18 May 2011 01:07:31 +0200 Subject: [PATCH] exec: hangup/reset/deallocate VTs in gettys Explicitly disconnect all clients from a VT when a getty starts/finishes (requires TIOCVHANGUP, available in 2.6.29). Explicitly deallocate getty VTs in order to flush scrollback buffer. Explicitly reset terminals to a defined state before spawning getty. --- TODO | 12 +-- man/systemd.exec.xml | 30 +++++++ src/dbus-execute.h | 3 + src/execute.c | 35 +++++++-- src/execute.h | 7 +- src/load-fragment.c | 49 +++++++++++- src/main.c | 2 +- src/missing.h | 4 + src/mount.c | 2 +- src/service.c | 4 +- src/socket.c | 2 +- src/swap.c | 2 +- src/util.c | 138 ++++++++++++++++++++++++++++++++- src/util.h | 9 ++- units/getty@.service.m4 | 4 + units/serial-getty@.service.m4 | 3 + 16 files changed, 275 insertions(+), 31 deletions(-) diff --git a/TODO b/TODO index 1d6ed5a4..389e5e97 100644 --- a/TODO +++ b/TODO @@ -22,7 +22,7 @@ Features: * Make it possible to set the keymap independently from the font on the kernel cmdline. Right now setting one resets also the other. -* add dbus call to convert snapshot ino target +* add dbus call to convert snapshot into target * move nss-myhostname into systemd @@ -30,11 +30,6 @@ Features: * add dbus call to convert snapshot into target -* make use of TIOCVHANGUP to revoke access to tty before we spawn a getty on it - -* release VT before we spawn a getty on it to entirely clear scrollback buffer - https://bugzilla.redhat.com/show_bug.cgi?id=701704 - * move /selinux to /sys/fs/selinux * unset cgroup agents on shutdown @@ -45,14 +40,12 @@ Features: * add inode stat() check to readahead to suppress preloading changed files -* allow list of pathes in config_parse_condition_path() +* allow list of paths in config_parse_condition_path() * introduce dbus calls for enabling/disabling a service * support notifications for services being enabled/disabled -* Maybe merge nss-myhostname into systemd? - * GC unreferenced jobs (such as .device jobs) * support wildcard expansion in ListenStream= and friends @@ -68,7 +61,6 @@ Features: * write blog stories about: - enabling dbus services - status update - - you are a distro: why switch? - /etc/sysconfig and /etc/default - how to write socket activated services diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 5b0d2ce3..de1d9bf4 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -421,6 +421,36 @@ TTY (see above). Defaults to /dev/console. + + TTYReset= + Reset the terminal + device specified with + TTYPath= before and + after execution. Defaults to + no. + + + TTYVHangup= + Disconnect all clients + which have opened the terminal device + specified with + TTYPath= + before and after execution. Defaults + to + no. + + + TTYVTDisallocate= + If the the terminal + device specified with + TTYPath= is a + virtual console terminal try to + deallocate the TTY before and after + execution. This ensures that the + screen and scrollback buffer is + cleared. Defaults to + no. + SyslogIdentifier= Sets the process name diff --git a/src/dbus-execute.h b/src/dbus-execute.h index ed66390d..bf3160b0 100644 --- a/src/dbus-execute.h +++ b/src/dbus-execute.h @@ -128,6 +128,9 @@ { interface, "StandardOutput", bus_execute_append_output, "s", &(context).std_output }, \ { interface, "StandardError", bus_execute_append_output, "s", &(context).std_error }, \ { interface, "TTYPath", bus_property_append_string, "s", (context).tty_path }, \ + { interface, "TTYReset", bus_property_append_bool, "b", &(context).tty_reset }, \ + { interface, "TTYVHangup", bus_property_append_bool, "b", &(context).tty_vhangup }, \ + { interface, "TTYVTDisallocate", bus_property_append_bool, "b", &(context).tty_vt_disallocate }, \ { interface, "SyslogPriority", bus_property_append_int, "i", &(context).syslog_priority }, \ { interface, "SyslogIdentifier", bus_property_append_string, "s", (context).syslog_identifier }, \ { interface, "SyslogLevelPrefix", bus_property_append_bool, "b", &(context).syslog_level_prefix }, \ diff --git a/src/execute.c b/src/execute.c index 745dcfcd..a62f9dbb 100644 --- a/src/execute.c +++ b/src/execute.c @@ -140,6 +140,19 @@ static const char *tty_path(const ExecContext *context) { return "/dev/console"; } +void exec_context_tty_reset(const ExecContext *context) { + assert(context); + + if (context->tty_vhangup) + terminal_vhangup(tty_path(context)); + + if (context->tty_reset) + reset_terminal(tty_path(context)); + + if (context->tty_vt_disallocate && context->tty_path) + vt_disallocate(context->tty_path); +} + static int open_null_as(int flags, int nfd) { int fd, r; @@ -1089,6 +1102,8 @@ int exec_spawn(ExecCommand *command, } } + exec_context_tty_reset(context); + /* We skip the confirmation step if we shall not apply the TTY */ if (confirm_spawn && (!is_terminal_input(context->std_input) || apply_tty_stdin)) { @@ -1700,8 +1715,14 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { if (c->tty_path) fprintf(f, - "%sTTYPath: %s\n", - prefix, c->tty_path); + "%sTTYPath: %s\n" + "%sTTYReset: %s\n" + "%sTTYVHangup: %s\n" + "%sTTYVTDisallocate: %s\n", + prefix, c->tty_path, + prefix, yes_no(c->tty_reset), + prefix, yes_no(c->tty_vhangup), + prefix, yes_no(c->tty_vt_disallocate)); if (c->std_output == EXEC_OUTPUT_SYSLOG || c->std_output == EXEC_OUTPUT_KMSG || c->std_output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || c->std_output == EXEC_OUTPUT_KMSG_AND_CONSOLE || @@ -1802,7 +1823,7 @@ void exec_status_start(ExecStatus *s, pid_t pid) { dual_timestamp_get(&s->start_timestamp); } -void exec_status_exit(ExecStatus *s, pid_t pid, int code, int status, const char *utmp_id) { +void exec_status_exit(ExecStatus *s, ExecContext *context, pid_t pid, int code, int status) { assert(s); if ((s->pid && s->pid != pid) || @@ -1815,8 +1836,12 @@ void exec_status_exit(ExecStatus *s, pid_t pid, int code, int status, const char s->code = code; s->status = status; - if (utmp_id) - utmp_put_dead_process(utmp_id, pid, code, status); + if (context) { + if (context->utmp_id) + utmp_put_dead_process(context->utmp_id, pid, code, status); + + exec_context_tty_reset(context); + } } void exec_status_dump(ExecStatus *s, FILE *f, const char *prefix) { diff --git a/src/execute.h b/src/execute.h index 208fe4ad..4ed79f0d 100644 --- a/src/execute.h +++ b/src/execute.h @@ -123,6 +123,10 @@ struct ExecContext { char *tty_path; + bool tty_reset; + bool tty_vhangup; + bool tty_vt_disallocate; + /* Since resolving these names might might involve socket * connections and we don't want to deadlock ourselves these * names are resolved on execution only and in the child @@ -198,11 +202,12 @@ int exec_command_set(ExecCommand *c, const char *path, ...); void exec_context_init(ExecContext *c); void exec_context_done(ExecContext *c); void exec_context_dump(ExecContext *c, FILE* f, const char *prefix); +void exec_context_tty_reset(const ExecContext *context); int exec_context_load_environment(const ExecContext *c, char ***l); void exec_status_start(ExecStatus *s, pid_t pid); -void exec_status_exit(ExecStatus *s, pid_t pid, int code, int status, const char *utmp_id); +void exec_status_exit(ExecStatus *s, ExecContext *context, pid_t pid, int code, int status); void exec_status_dump(ExecStatus *s, FILE *f, const char *prefix); const char* exec_output_to_string(ExecOutput i); diff --git a/src/load-fragment.c b/src/load-fragment.c index 7c39d238..321214ef 100644 --- a/src/load-fragment.c +++ b/src/load-fragment.c @@ -188,6 +188,43 @@ static int config_parse_string_printf( return 0; } +static int config_parse_path_printf( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Unit *u = userdata; + char **s = data; + char *k; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(s); + assert(u); + + if (!(k = unit_full_printf(u, rvalue))) + return -ENOMEM; + + if (!path_is_absolute(k)) { + log_error("[%s:%u] Not an absolute path: %s", filename, line, k); + free(k); + return -EINVAL; + } + + path_kill_slashes(k); + + free(*s); + *s = k; + + return 0; +} + static int config_parse_listen( const char *filename, unsigned line, @@ -1719,6 +1756,7 @@ static void dump_items(FILE *f, const ConfigItem *items) { { config_parse_bool, "BOOLEAN" }, { config_parse_string, "STRING" }, { config_parse_path, "PATH" }, + { config_parse_path_printf, "PATH" }, { config_parse_strv, "STRING [...]" }, { config_parse_nice, "NICE" }, { config_parse_oom_score_adjust, "OOMSCOREADJUST" }, @@ -1812,8 +1850,8 @@ static int load_from_path(Unit *u, const char *path) { }; #define EXEC_CONTEXT_CONFIG_ITEMS(context, section) \ - { "WorkingDirectory", config_parse_path, 0, &(context).working_directory, section }, \ - { "RootDirectory", config_parse_path, 0, &(context).root_directory, section }, \ + { "WorkingDirectory", config_parse_path_printf, 0, &(context).working_directory, section }, \ + { "RootDirectory", config_parse_path_printf, 0, &(context).root_directory, section }, \ { "User", config_parse_string_printf, 0, &(context).user, section }, \ { "Group", config_parse_string_printf, 0, &(context).group, section }, \ { "SupplementaryGroups", config_parse_strv, 0, &(context).supplementary_groups, section }, \ @@ -1831,7 +1869,10 @@ static int load_from_path(Unit *u, const char *path) { { "StandardInput", config_parse_input, 0, &(context).std_input, section }, \ { "StandardOutput", config_parse_output, 0, &(context).std_output, section }, \ { "StandardError", config_parse_output, 0, &(context).std_error, section }, \ - { "TTYPath", config_parse_path, 0, &(context).tty_path, section }, \ + { "TTYPath", config_parse_path_printf, 0, &(context).tty_path, section }, \ + { "TTYReset", config_parse_bool, 0, &(context).tty_reset, section }, \ + { "TTYVHangup", config_parse_bool, 0, &(context).tty_vhangup, section }, \ + { "TTYVTDisallocate", config_parse_bool, 0, &(context).tty_vt_disallocate, section }, \ { "SyslogIdentifier", config_parse_string_printf, 0, &(context).syslog_identifier, section }, \ { "SyslogFacility", config_parse_facility, 0, &(context).syslog_priority, section }, \ { "SyslogLevel", config_parse_level, 0, &(context).syslog_priority, section }, \ @@ -1899,7 +1940,7 @@ static int load_from_path(Unit *u, const char *path) { { "ConditionSecurity", config_parse_condition_string, CONDITION_SECURITY, u, "Unit" }, { "ConditionNull", config_parse_condition_null, 0, u, "Unit" }, - { "PIDFile", config_parse_path, 0, &u->service.pid_file, "Service" }, + { "PIDFile", config_parse_path_printf, 0, &u->service.pid_file, "Service" }, { "ExecStartPre", config_parse_exec, 0, u->service.exec_command+SERVICE_EXEC_START_PRE, "Service" }, { "ExecStart", config_parse_exec, 0, u->service.exec_command+SERVICE_EXEC_START, "Service" }, { "ExecStartPost", config_parse_exec, 0, u->service.exec_command+SERVICE_EXEC_START_POST, "Service" }, diff --git a/src/main.c b/src/main.c index b43d8eca..52b3031f 100644 --- a/src/main.c +++ b/src/main.c @@ -203,7 +203,7 @@ static int console_setup(bool do_reset) { return -tty_fd; } - if ((r = reset_terminal(tty_fd)) < 0) + if ((r = reset_terminal_fd(tty_fd)) < 0) log_error("Failed to reset /dev/console: %s", strerror(-r)); close_nointr_nofail(tty_fd); diff --git a/src/missing.h b/src/missing.h index f1dbb398..02b31b74 100644 --- a/src/missing.h +++ b/src/missing.h @@ -76,6 +76,10 @@ #define AUDIT_SERVICE_STOP 1131 /* Service (daemon) stop */ #endif +#ifndef TIOCVHANGUP +#define TIOCVHANGUP 0x5437 +#endif + static inline int pivot_root(const char *new_root, const char *put_old) { return syscall(SYS_pivot_root, new_root, put_old); } diff --git a/src/mount.c b/src/mount.c index 4b300364..423cf434 100644 --- a/src/mount.c +++ b/src/mount.c @@ -1212,7 +1212,7 @@ static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) { m->failure = m->failure || !success; if (m->control_command) { - exec_status_exit(&m->control_command->exec_status, pid, code, status, m->exec_context.utmp_id); + exec_status_exit(&m->control_command->exec_status, &m->exec_context, pid, code, status); m->control_command = NULL; m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID; } diff --git a/src/service.c b/src/service.c index f8267541..d59c4cba 100644 --- a/src/service.c +++ b/src/service.c @@ -2571,7 +2571,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { if (s->main_pid == pid) { s->main_pid = 0; - exec_status_exit(&s->main_exec_status, pid, code, status, s->exec_context.utmp_id); + exec_status_exit(&s->main_exec_status, &s->exec_context, pid, code, status); /* If this is not a forking service than the main * process got started and hence we copy the exit @@ -2650,7 +2650,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { s->control_pid = 0; if (s->control_command) { - exec_status_exit(&s->control_command->exec_status, pid, code, status, s->exec_context.utmp_id); + exec_status_exit(&s->control_command->exec_status, &s->exec_context, pid, code, status); if (s->control_command->ignore) success = true; diff --git a/src/socket.c b/src/socket.c index 2b9362db..6c935c42 100644 --- a/src/socket.c +++ b/src/socket.c @@ -1808,7 +1808,7 @@ static void socket_sigchld_event(Unit *u, pid_t pid, int code, int status) { success = is_clean_exit(code, status); if (s->control_command) { - exec_status_exit(&s->control_command->exec_status, pid, code, status, s->exec_context.utmp_id); + exec_status_exit(&s->control_command->exec_status, &s->exec_context, pid, code, status); if (s->control_command->ignore) success = true; diff --git a/src/swap.c b/src/swap.c index ef001a98..04df5854 100644 --- a/src/swap.c +++ b/src/swap.c @@ -940,7 +940,7 @@ static void swap_sigchld_event(Unit *u, pid_t pid, int code, int status) { s->failure = s->failure || !success; if (s->control_command) { - exec_status_exit(&s->control_command->exec_status, pid, code, status, s->exec_context.utmp_id); + exec_status_exit(&s->control_command->exec_status, &s->exec_context, pid, code, status); s->control_command = NULL; s->control_command_id = _SWAP_EXEC_COMMAND_INVALID; } diff --git a/src/util.c b/src/util.c index f0051ee2..14aa1f97 100644 --- a/src/util.c +++ b/src/util.c @@ -2261,7 +2261,7 @@ int ask(char *ret, const char *replies, const char *text, ...) { } } -int reset_terminal(int fd) { +int reset_terminal_fd(int fd) { struct termios termios; int r = 0; long arg; @@ -2323,6 +2323,19 @@ finish: return r; } +int reset_terminal(const char *name) { + int fd, r; + + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + r = reset_terminal_fd(fd); + close_nointr_nofail(fd); + + return r; +} + int open_terminal(const char *name, int mode) { int fd, r; unsigned c = 0; @@ -2443,8 +2456,8 @@ int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocst /* We pass here O_NOCTTY only so that we can check the return * value TIOCSCTTY and have a reliable way to figure out if we * successfully became the controlling process of the tty */ - if ((fd = open_terminal(name, O_RDWR|O_NOCTTY)) < 0) - return -errno; + if ((fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC)) < 0) + return fd; /* First, try to get the tty */ r = ioctl(fd, TIOCSCTTY, force); @@ -2511,7 +2524,7 @@ int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocst if (notify >= 0) close_nointr_nofail(notify); - if ((r = reset_terminal(fd)) < 0) + if ((r = reset_terminal_fd(fd)) < 0) log_warning("Failed to reset terminal: %s", strerror(-r)); return fd; @@ -4413,6 +4426,123 @@ char* hostname_cleanup(char *s) { return s; } +int terminal_vhangup_fd(int fd) { + if (ioctl(fd, TIOCVHANGUP) < 0) + return -errno; + + return 0; +} + +int terminal_vhangup(const char *name) { + int fd, r; + + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + r = terminal_vhangup_fd(fd); + close_nointr_nofail(fd); + + return r; +} + +int vt_disallocate(const char *name) { + int fd, r; + unsigned u; + int temporary_vt, temporary_fd; + char tpath[64]; + struct vt_stat vt_stat; + + /* Deallocate the VT if possible. If not possible + * (i.e. because it is the active one), at least clear it + * entirely (including the scrollback buffer) */ + + if (!tty_is_vc(name)) + return -EIO; + + if (!startswith(name, "/dev/tty")) + return -EINVAL; + + r = safe_atou(name+8, &u); + if (r < 0) + return r; + + if (u <= 0) + return -EIO; + + fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + r = ioctl(fd, VT_DISALLOCATE, u); + if (r >= 0) { + close_nointr_nofail(fd); + return 0; + } + + if (errno != EBUSY) { + close_nointr_nofail(fd); + return -errno; + } + + if (ioctl(fd, VT_GETSTATE, &vt_stat) < 0) { + close_nointr_nofail(fd); + return -errno; + } + + if (u != vt_stat.v_active) { + close_nointr_nofail(fd); + return -EBUSY; + } + + if (ioctl(fd, VT_OPENQRY, &temporary_vt) < 0) { + close_nointr_nofail(fd); + return -errno; + } + + if (temporary_vt <= 0) { + close_nointr_nofail(fd); + return -EIO; + } + + /* Switch to temporary VT */ + snprintf(tpath, sizeof(tpath), "/dev/tty%i", temporary_vt); + char_array_0(tpath); + temporary_fd = open_terminal(tpath, O_RDWR|O_NOCTTY|O_CLOEXEC); + ioctl(fd, VT_ACTIVATE, temporary_vt); + if (temporary_fd >= 0) + close_nointr_nofail(temporary_fd); + + /* Reopen /dev/tty0 */ + close_nointr_nofail(fd); + fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + r = -errno; + else { + /* Disallocate the real VT */ + if (ioctl(fd, VT_DISALLOCATE, u) < 0) + r = -errno; + else + r = 0; + } + + /* Recreate original VT */ + temporary_fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + + if (temporary_fd >= 0) { + loop_write(temporary_fd, "\033[H\033[2J", 7, false); /* clear screen explicitly */ + close_nointr_nofail(temporary_fd); + } + + /* Switch back to original VT */ + if (fd >= 0) { + ioctl(fd, VT_ACTIVATE, vt_stat.v_active); + close_nointr_nofail(fd); + } + + return r; +} + static const char *const ioprio_class_table[] = { [IOPRIO_CLASS_NONE] = "none", [IOPRIO_CLASS_RT] = "realtime", diff --git a/src/util.h b/src/util.h index 79e98be2..04049f7d 100644 --- a/src/util.h +++ b/src/util.h @@ -315,7 +315,9 @@ int chvt(int vt); int read_one_char(FILE *f, char *ret, bool *need_nl); int ask(char *ret, const char *replies, const char *text, ...); -int reset_terminal(int fd); +int reset_terminal_fd(int fd); +int reset_terminal(const char *name); + int open_terminal(const char *name, int mode); int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocstty_eperm); int release_terminal(void); @@ -411,6 +413,11 @@ char* hostname_cleanup(char *s); char* strshorten(char *s, size_t l); +int terminal_vhangup_fd(int fd); +int terminal_vhangup(const char *name); + +int vt_disallocate(const char *name); + #define NULSTR_FOREACH(i, l) \ for ((i) = (l); (i) && *(i); (i) = strchr((i), 0)+1) diff --git a/units/getty@.service.m4 b/units/getty@.service.m4 index a9733a60..bfceb39f 100644 --- a/units/getty@.service.m4 +++ b/units/getty@.service.m4 @@ -36,6 +36,10 @@ ExecStart=-/sbin/agetty %I 38400 Restart=always RestartSec=0 UtmpIdentifier=%I +TTYPath=/dev/%I +TTYReset=yes +TTYVHangup=yes +TTYVTDisallocate=yes KillMode=process # Unset locale for the console getty since the console has problems diff --git a/units/serial-getty@.service.m4 b/units/serial-getty@.service.m4 index 8b4f0fb9..082290cb 100644 --- a/units/serial-getty@.service.m4 +++ b/units/serial-getty@.service.m4 @@ -36,6 +36,9 @@ ExecStart=-/sbin/agetty -s %I 115200,38400,9600 Restart=always RestartSec=0 UtmpIdentifier=%I +TTYPath=/dev/%I +TTYReset=yes +TTYVHangup=yes KillMode=process # Some login implementations ignore SIGTERM, so we send SIGHUP -- 2.39.5