chiark / gitweb /
exec: hangup/reset/deallocate VTs in gettys
authorLennart Poettering <lennart@poettering.net>
Tue, 17 May 2011 23:07:31 +0000 (01:07 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 17 May 2011 23:07:36 +0000 (01:07 +0200)
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.

16 files changed:
TODO
man/systemd.exec.xml
src/dbus-execute.h
src/execute.c
src/execute.h
src/load-fragment.c
src/main.c
src/missing.h
src/mount.c
src/service.c
src/socket.c
src/swap.c
src/util.c
src/util.h
units/getty@.service.m4
units/serial-getty@.service.m4

diff --git a/TODO b/TODO
index 1d6ed5a..389e5e9 100644 (file)
--- 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
 
index 5b0d2ce..de1d9bf 100644 (file)
                                 <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
                                 to prefix log lines sent to syslog or
index ed66390..bf3160b 100644 (file)
         { 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          }, \
index 745dcfc..a62f9db 100644 (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) {
index 208fe4a..4ed79f0 100644 (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);
index 7c39d23..321214e 100644 (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" },
index b43d8ec..52b3031 100644 (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);
index f1dbb39..02b31b7 100644 (file)
 #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);
 }
index 4b30036..423cf43 100644 (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;
         }
index f826754..d59c4cb 100644 (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;
index 2b9362d..6c935c4 100644 (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;
index ef001a9..04df585 100644 (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;
         }
index f0051ee..14aa1f9 100644 (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",
index 79e98be..04049f7 100644 (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)
 
index a9733a6..bfceb39 100644 (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
index 8b4f0fb..082290c 100644 (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