]> code.delx.au - gnu-emacs/commitdiff
Don't let call-process be a zombie factory.
authorPaul Eggert <eggert@cs.ucla.edu>
Mon, 3 Dec 2012 21:42:12 +0000 (13:42 -0800)
committerPaul Eggert <eggert@cs.ucla.edu>
Mon, 3 Dec 2012 21:42:12 +0000 (13:42 -0800)
Fixing this bug required some cleanup of the signal-handling code.
As a side effect, this change also fixes a longstanding rare race
condition whereby Emacs could mistakenly kill unrelated processes,
and it fixes a bug where a second C-g does not kill a recalcitrant
synchronous process in GNU/Linux and similar platforms.
The patch should also fix the last vestiges of Bug#9488,
a bug which has mostly been fixed on the trunk by other changes.
* callproc.c, process.h (synch_process_alive, synch_process_death)
(synch_process_termsig, sync_process_retcode):
Remove.  All uses removed, to simplify analysis and so that
less consing is done inside critical sections.
* callproc.c (call_process_exited): Remove.  All uses replaced
with !synch_process_pid.
* callproc.c (synch_process_pid, synch_process_fd): New static vars.
These take the role of what used to be in unwind-protect arg.
All uses changed.
(block_child_signal, unblock_child_signal):
New functions, to avoid races that could kill innocent-victim processes.
(call_process_kill, call_process_cleanup, Fcall_process): Use them.
(call_process_kill): Record killed processes as deleted, so that
zombies do not clutter up the system.  Do this inside a critical
section, to avoid a race that would allow the clutter.
(call_process_cleanup): Fix code so that the second C-g works again
on common platforms such as GNU/Linux.
(Fcall_process): Create the child process in a critical section,
to fix a race condition.  If creating an asynchronous process,
record it as deleted so that zombies do not clutter up the system.
Do unwind-protect for WINDOWSNT too, as that's simpler in the
light of these changes.  Omit unnecessary call to emacs_close
before failure, as the unwind-protect code does that.
* callproc.c (call_process_cleanup):
* w32proc.c (waitpid): Simplify now that synch_process_alive is gone.
* process.c (record_deleted_pid): New function, containing
code refactored out of Fdelete_process.
(Fdelete_process): Use it.
(process_status_retrieved): Remove.  All callers changed to use
child_status_change.
(record_child_status_change): Remove, folding its contents into ...
(handle_child_signal): ... this signal handler.  Now, this
function is purely a handler for SIGCHLD, and is not called after
a synchronous waitpid returns; the synchronous code is moved to
wait_for_termination.  There is no need to worry about reaping
more than one child now.
* sysdep.c (get_child_status, child_status_changed): New functions.
(wait_for_termination): Now takes int * status and bool
interruptible arguments, too.  Do not record child status change;
that's now the caller's responsibility.  All callers changed.
Reimplement in terms of get_child_status.
(wait_for_termination_1, interruptible_wait_for_termination):
Remove.  All callers changed to use wait_for_termination.
* syswait.h: Include <stdbool.h>, for bool.
(record_child_status_change, interruptible_wait_for_termination):
Remove decls.
(record_deleted_pid, child_status_changed): New decls.
(wait_for_termination): Adjust to API changes noted above.

Fixes: debbugs:12980
src/ChangeLog
src/callproc.c
src/process.c
src/process.h
src/sysdep.c
src/syswait.h
src/w32proc.c

index 019caf306b7ca5a672bc3ea916bf09b2a930c087..1a91eb0f1a3b02452d8a1576dd66b24222b57d2b 100644 (file)
@@ -1,5 +1,62 @@
 2012-12-03  Paul Eggert  <eggert@cs.ucla.edu>
 
+       Don't let call-process be a zombie factory (Bug#12980).
+       Fixing this bug required some cleanup of the signal-handling code.
+       As a side effect, this change also fixes a longstanding rare race
+       condition whereby Emacs could mistakenly kill unrelated processes,
+       and it fixes a bug where a second C-g does not kill a recalcitrant
+       synchronous process in GNU/Linux and similar platforms.
+       The patch should also fix the last vestiges of Bug#9488,
+       a bug which has mostly been fixed on the trunk by other changes.
+       * callproc.c, process.h (synch_process_alive, synch_process_death)
+       (synch_process_termsig, sync_process_retcode):
+       Remove.  All uses removed, to simplify analysis and so that
+       less consing is done inside critical sections.
+       * callproc.c (call_process_exited): Remove.  All uses replaced
+       with !synch_process_pid.
+       * callproc.c (synch_process_pid, synch_process_fd): New static vars.
+       These take the role of what used to be in unwind-protect arg.
+       All uses changed.
+       (block_child_signal, unblock_child_signal):
+       New functions, to avoid races that could kill innocent-victim processes.
+       (call_process_kill, call_process_cleanup, Fcall_process): Use them.
+       (call_process_kill): Record killed processes as deleted, so that
+       zombies do not clutter up the system.  Do this inside a critical
+       section, to avoid a race that would allow the clutter.
+       (call_process_cleanup): Fix code so that the second C-g works again
+       on common platforms such as GNU/Linux.
+       (Fcall_process): Create the child process in a critical section,
+       to fix a race condition.  If creating an asynchronous process,
+       record it as deleted so that zombies do not clutter up the system.
+       Do unwind-protect for WINDOWSNT too, as that's simpler in the
+       light of these changes.  Omit unnecessary call to emacs_close
+       before failure, as the unwind-protect code does that.
+       * callproc.c (call_process_cleanup):
+       * w32proc.c (waitpid): Simplify now that synch_process_alive is gone.
+       * process.c (record_deleted_pid): New function, containing
+       code refactored out of Fdelete_process.
+       (Fdelete_process): Use it.
+       (process_status_retrieved): Remove.  All callers changed to use
+       child_status_change.
+       (record_child_status_change): Remove, folding its contents into ...
+       (handle_child_signal): ... this signal handler.  Now, this
+       function is purely a handler for SIGCHLD, and is not called after
+       a synchronous waitpid returns; the synchronous code is moved to
+       wait_for_termination.  There is no need to worry about reaping
+       more than one child now.
+       * sysdep.c (get_child_status, child_status_changed): New functions.
+       (wait_for_termination): Now takes int * status and bool
+       interruptible arguments, too.  Do not record child status change;
+       that's now the caller's responsibility.  All callers changed.
+       Reimplement in terms of get_child_status.
+       (wait_for_termination_1, interruptible_wait_for_termination):
+       Remove.  All callers changed to use wait_for_termination.
+       * syswait.h: Include <stdbool.h>, for bool.
+       (record_child_status_change, interruptible_wait_for_termination):
+       Remove decls.
+       (record_deleted_pid, child_status_changed): New decls.
+       (wait_for_termination): Adjust to API changes noted above.
+
        * bytecode.c, lisp.h (Qbytecode): Remove.
        No longer needed after 2012-11-20 interactive-p changes.
 
index 0242755eb5fdcaba50ae741d7ed102a72b0625a8..21c52d09e6b1e5b5cefef3e60c908e51f50e67a3 100644 (file)
@@ -67,88 +67,110 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 /* Pattern used by call-process-region to make temp files.  */
 static Lisp_Object Vtemp_file_name_pattern;
 
-/* True if we are about to fork off a synchronous process or if we
-   are waiting for it.  */
-bool synch_process_alive;
-
-/* Nonzero => this is a string explaining death of synchronous subprocess.  */
-const char *synch_process_death;
+/* The next two variables are valid only while record-unwind-protect
+   is in place during call-process for a synchronous subprocess.  At
+   other times, their contents are irrelevant.  Doing this via static
+   C variables is more convenient than putting them into the arguments
+   of record-unwind-protect, as they need to be updated at randomish
+   times in the code, and Lisp cannot always store these values as
+   Emacs integers.  It's safe to use static variables here, as the
+   code is never invoked reentrantly.  */
+
+/* If nonzero, a process-ID that has not been reaped.  */
+static pid_t synch_process_pid;
+
+/* If nonnegative, a file descriptor that has not been closed.  */
+static int synch_process_fd;
+\f
+/* Block SIGCHLD.  */
 
-/* Nonzero => this is the signal number that terminated the subprocess.  */
-int synch_process_termsig;
+static void
+block_child_signal (void)
+{
+#ifdef SIGCHLD
+  sigset_t blocked;
+  sigemptyset (&blocked);
+  sigaddset (&blocked, SIGCHLD);
+  pthread_sigmask (SIG_BLOCK, &blocked, 0);
+#endif
+}
 
-/* If synch_process_death is zero,
-   this is exit code of synchronous subprocess.  */
-int synch_process_retcode;
+/* Unblock SIGCHLD.  */
 
-\f
-/* Clean up when exiting Fcall_process.
-   On MSDOS, delete the temporary file on any kind of termination.
-   On Unix, kill the process and any children on termination by signal.  */
+static void
+unblock_child_signal (void)
+{
+#ifdef SIGCHLD
+  pthread_sigmask (SIG_SETMASK, &empty_mask, 0);
+#endif
+}
 
-/* True if this is termination due to exit.  */
-static bool call_process_exited;
+/* Clean up when exiting call_process_cleanup.  */
 
 static Lisp_Object
-call_process_kill (Lisp_Object fdpid)
+call_process_kill (Lisp_Object ignored)
 {
-  int fd;
-  pid_t pid;
-  CONS_TO_INTEGER (Fcar (fdpid), int, fd);
-  CONS_TO_INTEGER (Fcdr (fdpid), pid_t, pid);
-  emacs_close (fd);
-  EMACS_KILLPG (pid, SIGKILL);
-  synch_process_alive = 0;
+  if (0 <= synch_process_fd)
+    emacs_close (synch_process_fd);
+
+  /* If PID is reapable, kill it and record it as a deleted process.
+     Do this in a critical section.  Unless PID is wedged it will be
+     reaped on receipt of the first SIGCHLD after the critical section.  */
+  if (synch_process_pid)
+    {
+      block_child_signal ();
+      record_deleted_pid (synch_process_pid);
+      EMACS_KILLPG (synch_process_pid, SIGKILL);
+      unblock_child_signal ();
+    }
+
   return Qnil;
 }
 
+/* Clean up when exiting Fcall_process.
+   On MSDOS, delete the temporary file on any kind of termination.
+   On Unix, kill the process and any children on termination by signal.  */
+
 static Lisp_Object
 call_process_cleanup (Lisp_Object arg)
 {
-  Lisp_Object fdpid = Fcdr (arg);
-  int fd;
-#if defined (MSDOS)
-  Lisp_Object file;
+#ifdef MSDOS
+  Lisp_Object buffer = Fcar (arg);
+  Lisp_Object file = Fcdr (arg);
 #else
-  pid_t pid;
+  Lisp_Object buffer = arg;
 #endif
 
-  Fset_buffer (Fcar (arg));
-  CONS_TO_INTEGER (Fcar (fdpid), int, fd);
-
-#if defined (MSDOS)
-  /* for MSDOS fdpid is really (fd . tempfile)  */
-  file = Fcdr (fdpid);
-  /* FD is -1 and FILE is "" when we didn't actually create a
-     temporary file in call-process.  */
-  if (fd >= 0)
-    emacs_close (fd);
-  if (!(strcmp (SDATA (file), NULL_DEVICE) == 0 || SREF (file, 0) == '\0'))
-    unlink (SDATA (file));
-#else /* not MSDOS */
-  CONS_TO_INTEGER (Fcdr (fdpid), pid_t, pid);
+  Fset_buffer (buffer);
 
-  if (call_process_exited)
-    {
-      emacs_close (fd);
-      return Qnil;
-    }
-
-  if (EMACS_KILLPG (pid, SIGINT) == 0)
+#ifndef MSDOS
+  /* If the process still exists, kill its process group.  */
+  if (synch_process_pid)
     {
       ptrdiff_t count = SPECPDL_INDEX ();
-      record_unwind_protect (call_process_kill, fdpid);
+      EMACS_KILLPG (synch_process_pid, SIGINT);
+      record_unwind_protect (call_process_kill, make_number (0));
       message1 ("Waiting for process to die...(type C-g again to kill it instantly)");
       immediate_quit = 1;
       QUIT;
-      wait_for_termination (pid);
+      wait_for_termination (synch_process_pid, 0, 1);
+      synch_process_pid = 0;
       immediate_quit = 0;
       specpdl_ptr = specpdl + count; /* Discard the unwind protect.  */
       message1 ("Waiting for process to die...done");
     }
-  synch_process_alive = 0;
-  emacs_close (fd);
-#endif /* not MSDOS */
+#endif
+
+  if (0 <= synch_process_fd)
+    emacs_close (synch_process_fd);
+
+#ifdef MSDOS
+  /* FILE is "" when we didn't actually create a temporary file in
+     call-process.  */
+  if (!(strcmp (SDATA (file), NULL_DEVICE) == 0 || SREF (file, 0) == '\0'))
+    unlink (SDATA (file));
+#endif
+
   return Qnil;
 }
 
@@ -181,9 +203,10 @@ If you quit, the process is killed with SIGINT, or SIGKILL if you quit again.
 usage: (call-process PROGRAM &optional INFILE BUFFER DISPLAY &rest ARGS)  */)
   (ptrdiff_t nargs, Lisp_Object *args)
 {
-  Lisp_Object infile, buffer, current_dir, path, cleanup_info_tail;
+  Lisp_Object infile, buffer, current_dir, path;
   bool display_p;
   int fd0, fd1, filefd;
+  int status;
   ptrdiff_t count = SPECPDL_INDEX ();
   USE_SAFE_ALLOCA;
 
@@ -199,7 +222,7 @@ usage: (call-process PROGRAM &optional INFILE BUFFER DISPLAY &rest ARGS)  */)
 #else
   pid_t pid;
 #endif
-  int vfork_errno;
+  int child_errno;
   int fd_output = -1;
   struct coding_system process_coding; /* coding-system of process output */
   struct coding_system argument_coding;        /* coding-system of arguments */
@@ -493,16 +516,6 @@ usage: (call-process PROGRAM &optional INFILE BUFFER DISPLAY &rest ARGS)  */)
     if (fd_output >= 0)
       fd1 = fd_output;
 
-    /* Record that we're about to create a synchronous process.  */
-    synch_process_alive = 1;
-
-    /* These vars record information from process termination.
-       Clear them now before process can possibly terminate,
-       to avoid timing error if process terminates soon.  */
-    synch_process_death = 0;
-    synch_process_retcode = 0;
-    synch_process_termsig = 0;
-
     if (NILP (error_file))
       fd_error = emacs_open (NULL_DEVICE, O_WRONLY, 0);
     else if (STRINGP (error_file))
@@ -535,23 +548,21 @@ usage: (call-process PROGRAM &optional INFILE BUFFER DISPLAY &rest ARGS)  */)
 
 #ifdef MSDOS /* MW, July 1993 */
     /* Note that on MSDOS `child_setup' actually returns the child process
-       exit status, not its PID, so we assign it to `synch_process_retcode'
-       below.  */
+       exit status, not its PID, so assign it to status below.  */
     pid = child_setup (filefd, outfilefd, fd_error, new_argv, 0, current_dir);
-
-    /* Record that the synchronous process exited and note its
-       termination status.  */
-    synch_process_alive = 0;
-    synch_process_retcode = pid;
-    if (synch_process_retcode < 0)  /* means it couldn't be exec'ed */
-      {
-       synchronize_system_messages_locale ();
-       synch_process_death = strerror (errno);
-      }
+    child_errno = errno;
 
     emacs_close (outfilefd);
     if (fd_error != outfilefd)
       emacs_close (fd_error);
+    if (pid < 0)
+      {
+       synchronize_system_messages_locale ();
+       return
+         code_convert_string_norecord (build_string (strerror (child_errno)),
+                                       Vlocale_coding_system, 0);
+      }
+    status = pid;
     fd1 = -1; /* No harm in closing that one!  */
     if (tempfile)
       {
@@ -569,12 +580,21 @@ usage: (call-process PROGRAM &optional INFILE BUFFER DISPLAY &rest ARGS)  */)
     else
       fd0 = -1; /* We are not going to read from tempfile.   */
 #else /* not MSDOS */
+
+    /* Do the unwind-protect now, even though the pid is not known, so
+       that no storage allocation is done in the critical section.
+       The actual PID will be filled in during the critical section.  */
+    synch_process_pid = 0;
+    synch_process_fd = fd0;
+    record_unwind_protect (call_process_cleanup, Fcurrent_buffer ());
+
+    block_input ();
+    block_child_signal ();
+
 #ifdef WINDOWSNT
     pid = child_setup (filefd, fd1, fd_error, new_argv, 0, current_dir);
 #else  /* not WINDOWSNT */
 
-    block_input ();
-
     /* vfork, and prevent local vars from being clobbered by the vfork.  */
     {
       Lisp_Object volatile buffer_volatile = buffer;
@@ -593,6 +613,7 @@ usage: (call-process PROGRAM &optional INFILE BUFFER DISPLAY &rest ARGS)  */)
       char **volatile new_argv_volatile = new_argv;
 
       pid = vfork ();
+      child_errno = errno;
 
       buffer = buffer_volatile;
       coding_systems = coding_systems_volatile;
@@ -612,6 +633,8 @@ usage: (call-process PROGRAM &optional INFILE BUFFER DISPLAY &rest ARGS)  */)
 
     if (pid == 0)
       {
+       unblock_child_signal ();
+
        if (fd0 >= 0)
          emacs_close (fd0);
 
@@ -623,11 +646,21 @@ usage: (call-process PROGRAM &optional INFILE BUFFER DISPLAY &rest ARGS)  */)
        child_setup (filefd, fd1, fd_error, new_argv, 0, current_dir);
       }
 
-    vfork_errno = errno;
-    unblock_input ();
-
 #endif /* not WINDOWSNT */
 
+    child_errno = errno;
+
+    if (0 < pid)
+      {
+       if (INTEGERP (buffer))
+         record_deleted_pid (pid);
+       else
+         synch_process_pid = pid;
+      }
+
+    unblock_child_signal ();
+    unblock_input ();
+
     /* The MSDOS case did this already.  */
     if (fd_error >= 0)
       emacs_close (fd_error);
@@ -644,9 +677,7 @@ usage: (call-process PROGRAM &optional INFILE BUFFER DISPLAY &rest ARGS)  */)
 
   if (pid < 0)
     {
-      if (fd0 >= 0)
-       emacs_close (fd0);
-      errno = vfork_errno;
+      errno = child_errno;
       report_file_error ("Doing vfork", Qnil);
     }
 
@@ -657,19 +688,12 @@ usage: (call-process PROGRAM &optional INFILE BUFFER DISPLAY &rest ARGS)  */)
       return Qnil;
     }
 
-  /* Enable sending signal if user quits below.  */
-  call_process_exited = 0;
-
 #if defined (MSDOS)
   /* MSDOS needs different cleanup information.  */
-  cleanup_info_tail = build_string (tempfile ? tempfile : "");
-#else
-  cleanup_info_tail = INTEGER_TO_CONS (pid);
-#endif /* not MSDOS */
   record_unwind_protect (call_process_cleanup,
                         Fcons (Fcurrent_buffer (),
-                               Fcons (INTEGER_TO_CONS (fd0),
-                                      cleanup_info_tail)));
+                               build_string (tempfile ? tempfile : "")));
+#endif
 
   if (BUFFERP (buffer))
     Fset_buffer (buffer);
@@ -856,38 +880,34 @@ usage: (call-process PROGRAM &optional INFILE BUFFER DISPLAY &rest ARGS)  */)
 
 #ifndef MSDOS
   /* Wait for it to terminate, unless it already has.  */
-  if (output_to_buffer)
-    wait_for_termination (pid);
-  else
-    interruptible_wait_for_termination (pid);
+  wait_for_termination (pid, &status, !output_to_buffer);
 #endif
 
   immediate_quit = 0;
 
   /* Don't kill any children that the subprocess may have left behind
      when exiting.  */
-  call_process_exited = 1;
+  synch_process_pid = 0;
 
   SAFE_FREE ();
   unbind_to (count, Qnil);
 
-  if (synch_process_termsig)
+  if (WIFSIGNALED (status))
     {
       const char *signame;
 
       synchronize_system_messages_locale ();
-      signame = strsignal (synch_process_termsig);
+      signame = strsignal (WTERMSIG (status));
 
       if (signame == 0)
        signame = "unknown";
 
-      synch_process_death = signame;
+      return code_convert_string_norecord (build_string (signame),
+                                          Vlocale_coding_system, 0);
     }
 
-  if (synch_process_death)
-    return code_convert_string_norecord (build_string (synch_process_death),
-                                        Vlocale_coding_system, 0);
-  return make_number (synch_process_retcode);
+  eassert (WIFEXITED (status));
+  return make_number (WEXITSTATUS (status));
 }
 \f
 static Lisp_Object
index c6139c9f9294e9b62c7dec0c3eba41e2d219c8e0..27009882c99a3115018af96674ef78c2ed0d98e5 100644 (file)
@@ -777,10 +777,23 @@ get_process (register Lisp_Object name)
 /* Fdelete_process promises to immediately forget about the process, but in
    reality, Emacs needs to remember those processes until they have been
    treated by the SIGCHLD handler and waitpid has been invoked on them;
-   otherwise they might fill up the kernel's process table.  */
+   otherwise they might fill up the kernel's process table.
+
+   Some processes created by call-process are also put onto this list.  */
 static Lisp_Object deleted_pid_list;
 #endif
 
+void
+record_deleted_pid (pid_t pid)
+{
+#ifdef SIGCHLD
+  deleted_pid_list = Fcons (make_fixnum_or_float (pid),
+                           /* GC treated elements set to nil.  */
+                           Fdelq (Qnil, deleted_pid_list));
+
+#endif
+}
+
 DEFUN ("delete-process", Fdelete_process, Sdelete_process, 1, 1, 0,
        doc: /* Delete PROCESS: kill it and forget about it immediately.
 PROCESS may be a process, a buffer, the name of a process or buffer, or
@@ -807,9 +820,7 @@ nil, indicating the current buffer's process.  */)
       pid_t pid = p->pid;
 
       /* No problem storing the pid here, as it is still in Vprocess_alist.  */
-      deleted_pid_list = Fcons (make_fixnum_or_float (pid),
-                               /* GC treated elements set to nil.  */
-                               Fdelq (Qnil, deleted_pid_list));
+      record_deleted_pid (pid);
       /* If the process has already signaled, remove it from the list.  */
       if (p->raw_status_new)
        update_status (p);
@@ -6147,35 +6158,37 @@ process has been transmitted to the serial port.  */)
   return process;
 }
 \f
-/* If the status of the process DESIRED has changed, return true and
-   set *STATUS to its exit status; otherwise, return false.
-   If HAVE is nonnegative, assume that HAVE = waitpid (HAVE, STATUS, ...)
-   has already been invoked, and do not invoke waitpid again.  */
+#ifdef SIGCHLD
 
-static bool
-process_status_retrieved (pid_t desired, pid_t have, int *status)
-{
-  if (have < 0)
-    {
-      /* Invoke waitpid only with a known process ID; do not invoke
-        waitpid with a nonpositive argument.  Otherwise, Emacs might
-        reap an unwanted process by mistake.  For example, invoking
-        waitpid (-1, ...) can mess up glib by reaping glib's subprocesses,
-        so that another thread running glib won't find them.  */
-      do
-       have = waitpid (desired, status, WNOHANG | WUNTRACED);
-      while (have < 0 && errno == EINTR);
-    }
+/* The main Emacs thread records child processes in three places:
 
-  return have == desired;
-}
+   - Vprocess_alist, for asynchronous subprocesses, which are child
+     processes visible to Lisp.
+
+   - deleted_pid_list, for child processes invisible to Lisp,
+     typically because of delete-process.  These are recorded so that
+     the processes can be reaped when they exit, so that the operating
+     system's process table is not cluttered by zombies.
 
-/* If PID is nonnegative, the child process PID with wait status W has
-   changed its status; record this and return true.
+   - the local variable PID in Fcall_process, call_process_cleanup and
+     call_process_kill, for synchronous subprocesses.
+     record_unwind_protect is used to make sure this process is not
+     forgotten: if the user interrupts call-process and the child
+     process refuses to exit immediately even with two C-g's,
+     call_process_kill adds PID's contents to deleted_pid_list before
+     returning.
 
-   If PID is negative, ignore W, and look for known child processes
-   of Emacs whose status have changed.  For each one found, record its new
-   status.
+   The main Emacs thread invokes waitpid only on child processes that
+   it creates and that have not been reaped.  This avoid races on
+   platforms such as GTK, where other threads create their own
+   subprocesses which the main thread should not reap.  For example,
+   if the main thread attempted to reap an already-reaped child, it
+   might inadvertently reap a GTK-created process that happened to
+   have the same process ID.  */
+
+/* Handle a SIGCHLD signal by looking for known child processes of
+   Emacs whose status have changed.  For each one found, record its
+   new status.
 
    All we do is change the status; we do not run sentinels or print
    notifications.  That is saved for the next time keyboard input is
@@ -6198,20 +6211,15 @@ process_status_retrieved (pid_t desired, pid_t have, int *status)
    ** Malloc WARNING: This should never call malloc either directly or
    indirectly; if it does, that is a bug  */
 
-void
-record_child_status_change (pid_t pid, int w)
+static void
+handle_child_signal (int sig)
 {
-#ifdef SIGCHLD
-
-  /* Record at most one child only if we already know one child that
-     has exited.  */
-  bool record_at_most_one_child = 0 <= pid;
-
   Lisp_Object tail;
 
   /* Find the process that signaled us, and record its status.  */
 
-  /* The process can have been deleted by Fdelete_process.  */
+  /* The process can have been deleted by Fdelete_process, or have
+     been started asynchronously by Fcall_process.  */
   for (tail = deleted_pid_list; CONSP (tail); tail = XCDR (tail))
     {
       bool all_pids_are_fixnums
@@ -6225,12 +6233,8 @@ record_child_status_change (pid_t pid, int w)
            deleted_pid = XINT (xpid);
          else
            deleted_pid = XFLOAT_DATA (xpid);
-         if (process_status_retrieved (deleted_pid, pid, &w))
-           {
-             XSETCAR (tail, Qnil);
-             if (record_at_most_one_child)
-               return;
-           }
+         if (child_status_changed (deleted_pid, 0, 0))
+           XSETCAR (tail, Qnil);
        }
     }
 
@@ -6239,15 +6243,17 @@ record_child_status_change (pid_t pid, int w)
     {
       Lisp_Object proc = XCDR (XCAR (tail));
       struct Lisp_Process *p = XPROCESS (proc);
-      if (p->alive && process_status_retrieved (p->pid, pid, &w))
+      int status;
+
+      if (p->alive && child_status_changed (p->pid, &status, WUNTRACED))
        {
          /* Change the status of the process that was found.  */
          p->tick = ++process_tick;
-         p->raw_status = w;
+         p->raw_status = status;
          p->raw_status_new = 1;
 
          /* If process has terminated, stop waiting for its output.  */
-         if (WIFSIGNALED (w) || WIFEXITED (w))
+         if (WIFSIGNALED (status) || WIFEXITED (status))
            {
              int clear_desc_flag = 0;
              p->alive = 0;
@@ -6261,44 +6267,8 @@ record_child_status_change (pid_t pid, int w)
                  FD_CLR (p->infd, &non_keyboard_wait_mask);
                }
            }
-
-         /* Tell wait_reading_process_output that it needs to wake up and
-            look around.  */
-         if (input_available_clear_time)
-           *input_available_clear_time = make_emacs_time (0, 0);
-
-         if (record_at_most_one_child)
-           return;
        }
     }
-
-  if (0 <= pid)
-    {
-      /* The caller successfully waited for a pid but no asynchronous
-        process was found for it, so this is a synchronous process.  */
-
-      synch_process_alive = 0;
-
-      /* Report the status of the synchronous process.  */
-      if (WIFEXITED (w))
-       synch_process_retcode = WEXITSTATUS (w);
-      else if (WIFSIGNALED (w))
-       synch_process_termsig = WTERMSIG (w);
-
-      /* Tell wait_reading_process_output that it needs to wake up and
-        look around.  */
-      if (input_available_clear_time)
-       *input_available_clear_time = make_emacs_time (0, 0);
-    }
-#endif
-}
-
-#ifdef SIGCHLD
-
-static void
-handle_child_signal (int sig)
-{
-  record_child_status_change (-1, 0);
 }
 
 static void
index 74d1a124060c60139978ceae2544d7077f1cd6af..4fa22747ca9decd057c9f0a4b855cecdf1434718 100644 (file)
@@ -185,23 +185,6 @@ pset_gnutls_cred_type (struct Lisp_Process *p, Lisp_Object val)
 }
 #endif
 
-/* True if we are about to fork off a synchronous process or if we
-   are waiting for it.  */
-extern bool synch_process_alive;
-
-/* Communicate exit status of sync process to from sigchld_handler
-   to Fcall_process.  */
-
-/* Nonzero => this is a string explaining death of synchronous subprocess.  */
-extern const char *synch_process_death;
-
-/* Nonzero => this is the signal number that terminated the subprocess.  */
-extern int synch_process_termsig;
-
-/* If synch_process_death is zero,
-   this is exit code of synchronous subprocess.  */
-extern int synch_process_retcode;
-
 /* Nonzero means don't run process sentinels.  This is used
    when exiting.  */
 extern int inhibit_sentinels;
index 1a3834f03790d66dd0cffbf74985abba1f1bc9c5..7068a4f0977a3a04ca38bb1b3a9037b805d4e7e4 100644 (file)
@@ -266,45 +266,71 @@ init_baud_rate (int fd)
 
 #ifndef MSDOS
 
-static void
-wait_for_termination_1 (pid_t pid, int interruptible)
+/* Wait for the subprocess with process id CHILD to terminate or change status.
+   CHILD must be a child process that has not been reaped.
+   If STATUS is non-null, store the waitpid-style exit status into *STATUS
+   and tell wait_reading_process_output that it needs to look around.
+   Use waitpid-style OPTIONS when waiting.
+   If INTERRUPTIBLE, this function is interruptible by a signal.
+
+   Return CHILD if successful, 0 if no status is available;
+   the latter is possible only when options & NOHANG.  */
+static pid_t
+get_child_status (pid_t child, int *status, int options, bool interruptible)
 {
-  while (1)
+  pid_t pid;
+
+  /* Invoke waitpid only with a known process ID; do not invoke
+     waitpid with a nonpositive argument.  Otherwise, Emacs might
+     reap an unwanted process by mistake.  For example, invoking
+     waitpid (-1, ...) can mess up glib by reaping glib's subprocesses,
+     so that another thread running glib won't find them.  */
+  eassert (0 < child);
+
+  while ((pid = waitpid (child, status, options)) < 0)
     {
-      int status;
-      int wait_result = waitpid (pid, &status, 0);
-      if (wait_result < 0)
-       {
-         if (errno != EINTR)
-           break;
-       }
-      else
-       {
-         record_child_status_change (wait_result, status);
-         break;
-       }
+      /* CHILD must be a child process that has not been reaped, and
+        STATUS and OPTIONS must be valid.  */
+      eassert (errno == EINTR);
 
       /* Note: the MS-Windows emulation of waitpid calls QUIT
         internally.  */
       if (interruptible)
        QUIT;
     }
-}
 
-/* Wait for subprocess with process id `pid' to terminate and
-   make sure it will get eliminated (not remain forever as a zombie) */
+  /* If successful and status is requested, tell wait_reading_process_output
+     that it needs to wake up and look around.  */
+  if (pid && status && input_available_clear_time)
+    *input_available_clear_time = make_emacs_time (0, 0);
 
+  return pid;
+}
+
+/* Wait for the subprocess with process id CHILD to terminate.
+   CHILD must be a child process that has not been reaped.
+   If STATUS is non-null, store the waitpid-style exit status into *STATUS
+   and tell wait_reading_process_output that it needs to look around.
+   If INTERRUPTIBLE, this function is interruptible by a signal.  */
 void
-wait_for_termination (pid_t pid)
+wait_for_termination (pid_t child, int *status, bool interruptible)
 {
-  wait_for_termination_1 (pid, 0);
+  get_child_status (child, status, 0, interruptible);
 }
 
-/* Like the above, but allow keyboard interruption. */
-void
-interruptible_wait_for_termination (pid_t pid)
+/* Report whether the subprocess with process id CHILD has changed status.
+   Termination counts as a change of status.
+   CHILD must be a child process that has not been reaped.
+   If STATUS is non-null, store the waitpid-style exit status into *STATUS
+   and tell wait_reading_process_output that it needs to look around.
+   Use waitpid-style OPTIONS to check status, but do not wait.
+
+   Return CHILD if successful, 0 if no status is available because
+   the process's state has not changed.  */
+pid_t
+child_status_changed (pid_t child, int *status, int options)
 {
-  wait_for_termination_1 (pid, 1);
+  return get_child_status (child, status, WNOHANG | options, 0);
 }
 
 /*
@@ -454,6 +480,7 @@ sys_subshell (void)
   char oldwd[MAXPATHLEN+1]; /* Fixed length is safe on MSDOS.  */
 #endif
   pid_t pid;
+  int status;
   struct save_signal saved_handlers[5];
   Lisp_Object dir;
   unsigned char *volatile str_volatile = 0;
@@ -491,7 +518,6 @@ sys_subshell (void)
 #ifdef DOS_NT
   pid = 0;
   save_signal_handlers (saved_handlers);
-  synch_process_alive = 1;
 #else
   pid = vfork ();
   if (pid == -1)
@@ -560,14 +586,12 @@ sys_subshell (void)
   /* Do this now if we did not do it before.  */
 #ifndef MSDOS
   save_signal_handlers (saved_handlers);
-  synch_process_alive = 1;
 #endif
 
 #ifndef DOS_NT
-  wait_for_termination (pid);
+  wait_for_termination (pid, &status, 0);
 #endif
   restore_signal_handlers (saved_handlers);
-  synch_process_alive = 0;
 }
 
 static void
index aa4c4bcf52757550e0cdfba64ecb96843c3ebe29..360407d558e56157ba424b96cca401bce5b4b2e2 100644 (file)
@@ -23,6 +23,7 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #ifndef EMACS_SYSWAIT_H
 #define EMACS_SYSWAIT_H
 
+#include <stdbool.h>
 #include <sys/types.h>
 
 #ifdef HAVE_SYS_WAIT_H /* We have sys/wait.h with POSIXoid definitions. */
@@ -52,10 +53,10 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #endif
 
 /* Defined in process.c.  */
-extern void record_child_status_change (pid_t, int);
+extern void record_deleted_pid (pid_t);
 
 /* Defined in sysdep.c.  */
-extern void wait_for_termination (pid_t);
-extern void interruptible_wait_for_termination (pid_t);
+extern void wait_for_termination (pid_t, int *, bool);
+extern pid_t child_status_changed (pid_t, int *, int);
 
 #endif /* EMACS_SYSWAIT_H */
index 9b111b40e36fe10e5c38056c9b7eb8c1434b5c3c..87af8682390949b3cb9fc5222fc31202f2f5e796 100644 (file)
@@ -1274,33 +1274,7 @@ waitpid (pid_t pid, int *status, int options)
 #endif
 
   if (status)
-    {
-      *status = retval;
-    }
-  else if (synch_process_alive)
-    {
-      synch_process_alive = 0;
-
-      /* Report the status of the synchronous process.  */
-      if (WIFEXITED (retval))
-       synch_process_retcode = WEXITSTATUS (retval);
-      else if (WIFSIGNALED (retval))
-       {
-         int code = WTERMSIG (retval);
-         const char *signame;
-
-         synchronize_system_messages_locale ();
-         signame = strsignal (code);
-
-         if (signame == 0)
-           signame = "unknown";
-
-         synch_process_death = signame;
-       }
-
-      reap_subprocess (cp);
-    }
-
+    *status = retval;
   reap_subprocess (cp);
 
   return pid;