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.
This commit is contained in:
Lennart Poettering 2011-05-18 01:07:31 +02:00
parent 9131f660ee
commit 6ea832a207
16 changed files with 275 additions and 31 deletions

12
TODO
View File

@ -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

View File

@ -421,6 +421,36 @@
TTY (see above). Defaults to
<filename>/dev/console</filename>.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>TTYReset=</varname></term>
<listitem><para>Reset the terminal
device specified with
<varname>TTYPath=</varname> before and
after execution. Defaults to
<literal>no</literal>.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>TTYVHangup=</varname></term>
<listitem><para>Disconnect all clients
which have opened the terminal device
specified with
<varname>TTYPath=</varname>
before and after execution. Defaults
to
<literal>no</literal>.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>TTYVTDisallocate=</varname></term>
<listitem><para>If the the terminal
device specified with
<varname>TTYPath=</varname> 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
<literal>no</literal>.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>SyslogIdentifier=</varname></term>
<listitem><para>Sets the process name

View File

@ -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 }, \

View File

@ -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) {

View File

@ -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);

View File

@ -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" },

View File

@ -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);

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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",

View File

@ -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)

View File

@ -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

View File

@ -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