Use waitid if possible.
authorBruno Haible <bruno@clisp.org>
Mon, 3 Nov 2003 20:47:02 +0000 (20:47 +0000)
committerBruno Haible <bruno@clisp.org>
Mon, 3 Nov 2003 20:47:02 +0000 (20:47 +0000)
lib/ChangeLog
lib/wait-process.c
m4/ChangeLog
m4/wait-process.m4

index 9d637938dc6e607cc5557c7d1a1668d7a62e67cd..2a3e00316abc136627ea57b068254ebdc1d35aaa 100644 (file)
@@ -1,3 +1,9 @@
+2003-10-31  Bruno Haible  <bruno@clisp.org>
+
+       * wait-process.c (wait_process): Use waitid with WNOWAIT if available,
+       to avoid (extremely rare) race condition.
+       Suggested by Paul Eggert.
+
 2003-11-03  Jim Meyering  <jim@meyering.net>
 
        * userspec.c: Include "userspec.h".
index 138f25c64762100d6d74df5c9f534949e8a795c7..26a889ca48f8bbf8645b555de6872a9b0fd16115 100644 (file)
 #define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
 
 
+#if defined _MSC_VER || defined __MINGW32__
+
+/* The return value of spawnvp() is really a process handle as returned
+   by CreateProcess().  Therefore we can kill it using TerminateProcess.  */
+#define kill(pid,sig) TerminateProcess ((HANDLE) (pid), sig)
+
+#endif
+
+
 /* Type of an entry in the slaves array.
    The 'used' bit determines whether this entry is currently in use.
    (If pid_t was an atomic type like sig_atomic_t, we could just set the
@@ -243,6 +252,85 @@ wait_subprocess (pid_t child, const char *progname,
                 bool null_stderr,
                 bool slave_process, bool exit_on_error)
 {
+#if HAVE_WAITID && defined WNOWAIT
+  /* Use of waitid() with WNOWAIT avoids a race condition: If slave_process is
+     true, and this process sleeps a very long time between the return from
+     waitpid() and the execution of unregister_slave_subprocess(), and
+     meanwhile another process acquires the same PID as child, and then - still
+     before unregister_slave_subprocess() - this process gets a fatal signal,
+     it would kill the other totally unrelated process.  */
+  siginfo_t info;
+  for (;;)
+    {
+      if (waitid (P_PID, child, &info, slave_process ? WNOWAIT : 0) < 0)
+       {
+# ifdef EINTR
+         if (errno == EINTR)
+           continue;
+# endif
+         if (exit_on_error || !null_stderr)
+           error (exit_on_error ? EXIT_FAILURE : 0, errno,
+                  _("%s subprocess"), progname);
+         return 127;
+       }
+
+      /* info.si_code is set to one of CLD_EXITED, CLD_KILLED, CLD_DUMPED,
+        CLD_TRAPPED, CLD_STOPPED, CLD_CONTINUED.  Loop until the program
+        terminates.  */
+      if (info.si_code == CLD_EXITED
+         || info.si_code == CLD_KILLED || info.si_code == CLD_DUMPED)
+       break;
+    }
+
+  /* The child process has exited or was signalled.  */
+
+  if (slave_process)
+    {
+      /* Unregister the child from the list of slave subprocesses, so that
+        later, when we exit, we don't kill a totally unrelated process which
+        may have acquired the same pid.  */
+      unregister_slave_subprocess (child);
+
+      /* Now remove the zombie from the process list.  */
+      for (;;)
+       {
+         if (waitid (P_PID, child, &info, 0) < 0)
+           {
+# ifdef EINTR
+             if (errno == EINTR)
+               continue;
+# endif
+             if (exit_on_error || !null_stderr)
+               error (exit_on_error ? EXIT_FAILURE : 0, errno,
+                      _("%s subprocess"), progname);
+             return 127;
+           }
+         break;
+       }
+    }
+
+  switch (info.si_code)
+    {
+    case CLD_KILLED:
+    case CLD_DUMPED:
+      if (exit_on_error || !null_stderr)
+       error (exit_on_error ? EXIT_FAILURE : 0, 0,
+              _("%s subprocess got fatal signal %d"),
+              progname, info.si_status);
+      return 127;
+    case CLD_EXITED:
+      if (info.si_status == 127)
+       {
+         if (exit_on_error || !null_stderr)
+           error (exit_on_error ? EXIT_FAILURE : 0, 0,
+                  _("%s subprocess failed"), progname);
+         return 127;
+       }
+      return info.si_status;
+    default:
+      abort ();
+    }
+#else
   /* waitpid() is just as portable as wait() nowadays.  */
   WAIT_T status;
 
@@ -253,11 +341,11 @@ wait_subprocess (pid_t child, const char *progname,
 
       if (result != child)
        {
-#ifdef EINTR
+# ifdef EINTR
          if (errno == EINTR)
            continue;
-#endif
-#if 0 /* defined ECHILD */
+# endif
+# if 0 /* defined ECHILD */
          if (errno == ECHILD)
            {
              /* Child process nonexistent?! Assume it terminated
@@ -265,7 +353,7 @@ wait_subprocess (pid_t child, const char *progname,
              *(int *) &status = 0;
              break;
            }
-#endif
+# endif
          if (exit_on_error || !null_stderr)
            error (exit_on_error ? EXIT_FAILURE : 0, errno,
                   _("%s subprocess"), progname);
@@ -302,4 +390,5 @@ wait_subprocess (pid_t child, const char *progname,
       return 127;
     }
   return WEXITSTATUS (status);
+#endif
 }
index 98f3e00fc29f3007a9386da9c44f5405c19e67f3..ed2c25dddcf4efadfc7c276c49f46e59a9edee0a 100644 (file)
@@ -1,3 +1,7 @@
+2003-11-03  Bruno Haible  <bruno@clisp.org>
+
+       * wait-process.m4 (gl_WAIT_PROCESS): Also check for waitid.
+
 2003-10-30  Paul Eggert  <eggert@cs.ucla.edu>
 
        * host-os.m4 (UTILS_HOST_OS): Change netbsd*-gnu pattern back to
index 963d401857aa43a63b0cf6d948f06fd71a9eafed..7a18b23b367404f046e61b159910ffa117a1dc3f 100644 (file)
@@ -1,4 +1,4 @@
-# wait-process.m4 serial 1
+# wait-process.m4 serial 2
 dnl Copyright (C) 2003 Free Software Foundation, Inc.
 dnl This file is free software, distributed under the terms of the GNU
 dnl General Public License.  As a special exception to the GNU General
@@ -12,4 +12,5 @@ AC_DEFUN([gl_WAIT_PROCESS],
   AC_CHECK_HEADERS_ONCE(unistd.h)
   dnl Prerequisites of lib/wait-process.c.
   AC_REQUIRE([gt_TYPE_SIG_ATOMIC_T])
+  AC_CHECK_FUNCS(waitid)
 ])