chiark / gitweb /
systemctl: make sure the tty agent does not retain a copy of stdio
[elogind.git] / src / systemctl.c
index 5d2efbe95f8709026b7c1ff6044fdafc3af898e4..66903aba28a5a1c276e081fa65a9a713be0e93ea 100644 (file)
@@ -118,6 +118,12 @@ static void pager_open(void);
 static bool on_tty(void) {
         static int t = -1;
 
+        /* Note that this is invoked relatively early, before we start
+         * the pager. That means the value we return reflects whether
+         * we originally were started on a tty, not if we currently
+         * are. But this is intended, since we want color, and so on
+         * when run in our own pager. */
+
         if (_unlikely_(t < 0))
                 t = isatty(STDOUT_FILENO) > 0;
 
@@ -145,13 +151,15 @@ static void spawn_ask_password_agent(void) {
 
         if (child == 0) {
                 /* In the child */
-
                 const char * const args[] = {
                         SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH,
                         "--watch",
                         NULL
                 };
 
+                int fd;
+
+                /* Make sure the agent goes away when the parent dies */
                 if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
                         _exit(EXIT_FAILURE);
 
@@ -160,6 +168,31 @@ static void spawn_ask_password_agent(void) {
                 if (getppid() != parent)
                         _exit(EXIT_SUCCESS);
 
+                /* Don't leak fds to the agent */
+                close_all_fds(NULL, 0);
+
+                /* Detach from stdin/stdout/stderr. and reopen
+                 * /dev/tty for them. This is important to ensure that
+                 * when systemctl is started via popen() or a similar
+                 * call that expects to read EOF we actually do
+                 * generate EOF and not delay this indefinitely by
+                 * because we keep an unused copy of stdin around. */
+                if ((fd = open("/dev/tty", O_RDWR|O_CLOEXEC|O_NONBLOCK)) < 0) {
+                        log_error("Failed to open /dev/tty: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
+                close(STDIN_FILENO);
+                close(STDOUT_FILENO);
+                close(STDERR_FILENO);
+
+                dup2(fd, STDIN_FILENO);
+                dup2(fd, STDOUT_FILENO);
+                dup2(fd, STDERR_FILENO);
+
+                if (fd > 2)
+                        close(fd);
+
                 execv(args[0], (char **) args);
                 _exit(EXIT_FAILURE);
         }
@@ -496,8 +529,10 @@ static int list_units(DBusConnection *bus, char **args, unsigned n) {
                 c++;
         }
 
-        qsort(unit_infos, c, sizeof(struct unit_info), compare_unit_info);
-        output_units_list(unit_infos, c);
+        if (c > 0) {
+                qsort(unit_infos, c, sizeof(struct unit_info), compare_unit_info);
+                output_units_list(unit_infos, c);
+        }
 
         r = 0;
 
@@ -1609,6 +1644,8 @@ finish:
 }
 
 typedef struct ExecStatusInfo {
+        char *name;
+
         char *path;
         char **argv;
 
@@ -1626,6 +1663,7 @@ typedef struct ExecStatusInfo {
 static void exec_status_info_free(ExecStatusInfo *i) {
         assert(i);
 
+        free(i->name);
         free(i->path);
         strv_free(i->argv);
         free(i);
@@ -1838,15 +1876,31 @@ static void print_status_info(UnitStatusInfo *i) {
 
         LIST_FOREACH(exec, p, i->exec) {
                 char *t;
+                bool good;
 
                 /* Only show exited processes here */
                 if (p->code == 0)
                         continue;
 
                 t = strv_join(p->argv, " ");
-                printf("\t Process: %u (%s, code=%s, ", p->pid, strna(t), sigchld_code_to_string(p->code));
+                printf("\t Process: %u %s=%s ", p->pid, p->name, strna(t));
                 free(t);
 
+#ifdef HAVE_SYSV_COMPAT
+                if (i->is_sysv)
+                        good = is_clean_exit_lsb(p->code, p->status);
+                else
+#endif
+                        good = is_clean_exit(p->code, p->status);
+
+                if (!good) {
+                        on = ansi_highlight(true);
+                        off = ansi_highlight(false);
+                } else
+                        on = off = "";
+
+                printf("%s(code=%s, ", on, sigchld_code_to_string(p->code));
+
                 if (p->code == CLD_EXITED) {
                         const char *c;
 
@@ -1861,7 +1915,10 @@ static void print_status_info(UnitStatusInfo *i) {
 
                 } else
                         printf("signal=%s", signal_to_string(p->status));
-                printf(")\n");
+
+                printf(")%s\n", off);
+
+                on = off = NULL;
 
                 if (i->main_pid == p->pid &&
                     i->start_timestamp == p->start_timestamp &&
@@ -2077,6 +2134,11 @@ static int status_property(const char *name, DBusMessageIter *iter, UnitStatusIn
                                 if (!(info = new0(ExecStatusInfo, 1)))
                                         return -ENOMEM;
 
+                                if (!(info->name = strdup(name))) {
+                                        free(info);
+                                        return -ENOMEM;
+                                }
+
                                 if ((r = exec_status_info_deserialize(&sub, info)) < 0) {
                                         free(info);
                                         return r;
@@ -5310,6 +5372,10 @@ static void pager_open(void) {
                 if (!*pager || streq(pager, "cat"))
                         return;
 
+        /* Determine and cache number of columns before we spawn the
+         * pager so that we get the value from the actual tty */
+        columns();
+
         if (pipe(fd) < 0) {
                 log_error("Failed to create pager pipe: %m");
                 return;
@@ -5336,7 +5402,14 @@ static void pager_open(void) {
                         execlp(pager, pager, NULL);
                         execl("/bin/sh", "sh", "-c", pager, NULL);
                 } else {
-                        execlp("sensible-pager", "sensible-pager", NULL);
+                        /* Debian's alternatives command for pagers is
+                         * called 'pager'. Note that we do not call
+                         * sensible-pagers here, since that is just a
+                         * shell script that implements a logic that
+                         * is similar to this one anyway, but is
+                         * Debian-specific. */
+                        execlp("pager", "pager", NULL);
+
                         execlp("less", "less", NULL);
                         execlp("more", "more", NULL);
                 }