]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
su: (pty) improve SIGSTOP/SIGCONT semantic
authorKarel Zak <kzak@redhat.com>
Thu, 7 Sep 2017 12:19:20 +0000 (14:19 +0200)
committerKarel Zak <kzak@redhat.com>
Mon, 18 Sep 2017 09:49:11 +0000 (11:49 +0200)
We want to use waitpid() only when child is terminated or stopped to
pick up child status, otherwise PTY proxy has to be active. This is
difference between "su" and "su --pty". For "su" we keep parent all
time in waitpid().

It would be possible to use separate code based on signalfd_siginfo,
but it seems better to keep all this stuff on one place -- it  means
wait_for_child().

Signed-off-by: Karel Zak <kzak@redhat.com>
login-utils/su-common.c

index 925cef9839104b33a032b12f0c8c5a4396a8c887..b9deeeab14a34469a798b2f9e4d8eefbc85f5a8a 100644 (file)
@@ -196,14 +196,28 @@ static int wait_for_child(struct su_context *su)
                return su->childstatus;
 
        if (su->child != (pid_t) -1) {
+               /*
+                * The "su" parent process spends all time here in waitpid(),
+                * but "su --pty" uses pty_proxy_master() and waitpid() is only
+                * called to pick up child status or to react to SIGSTOP.
+                */
                DBG(SIG, ul_debug("waiting for child [%d]...", su->child));
                for (;;) {
                        pid = waitpid(su->child, &status, WUNTRACED);
 
                        if (pid != (pid_t) - 1 && WIFSTOPPED(status)) {
+                               DBG(SIG, ul_debug(" child got SIGSTOP -- stop all session"));
                                kill(getpid(), SIGSTOP);
                                /* once we get here, we must have resumed */
                                kill(pid, SIGCONT);
+                               DBG(SIG, ul_debug(" session resumed -- continue"));
+#ifdef USE_PTY
+                               /* Let's go back to pty_proxy_master() */
+                               if (su->pty_sigfd != -1) {
+                                       DBG(SIG, ul_debug(" leaving on child SIGSTOP"));
+                                       return 0;
+                               }
+#endif
                        } else
                                break;
                }
@@ -421,8 +435,14 @@ static int pty_handle_signal(struct su_context *su, int fd)
        switch (info.ssi_signo) {
        case SIGCHLD:
                DBG(SIG, ul_debug(" get signal SIGCHLD"));
-               wait_for_child(su);
-               su->poll_timeout = 10;
+
+               /* The child terminated or stopped. Note that we ignore SIGCONT
+                * here, because stop/cont semantic is handled by wait_for_child() */
+               if (info.ssi_code == CLD_EXITED || info.ssi_status == SIGSTOP)
+                       wait_for_child(su);
+               /* The child is dead, force poll() timeout. */
+               if (su->child == (pid_t) -1)
+                       su->poll_timeout = 10;
                return 0;
        case SIGWINCH:
                DBG(SIG, ul_debug(" get signal SIGWINCH"));
@@ -555,6 +575,8 @@ static void pty_proxy_master(struct su_context *su)
                }
        }
 
+       close(su->pty_sigfd);
+       su->pty_sigfd = -1;
        DBG(PTY, ul_debug("poll() done [signal=%d, rc=%d]", caught_signal, rc));
 }
 #endif /* USE_PTY */