Example #1
0
    def testWaitpid(self):
        if posix_.environ.get('EINTR_TEST'):
            # Now we can do kill -TERM PID can get EINTR.
            signal.signal(signal.SIGTERM, _Handler)

            p = subprocess.Popen(['sleep', '5'])
            log('started sleep pid %d', p.pid)

            log('Hanging on waitpid in pid %d', posix_.getpid())
            posix_.waitpid(-1, 0)
Example #2
0
    def WaitForOne(self):
        # type: () -> bool
        """Wait until the next process returns (or maybe Ctrl-C).

    Returns:
      True if we got a notification, or False if there was nothing to wait for.

      In the interactive shell, we return True if we get a Ctrl-C, so the
      caller will try again.
    """
        # This is a list of async jobs
        try:
            # -1 makes it like wait(), which waits for any process.
            # NOTE: WUNTRACED is necessary to get stopped jobs.  What about
            # WCONTINUED?
            pid, status = posix.waitpid(-1, WUNTRACED)
        except OSError as e:
            #log('wait() error: %s', e)
            if e.errno == errno_.ECHILD:
                return False  # nothing to wait for caller should stop
            else:
                # We should never get here.  EINTR was handled by the 'posix'
                # module.  The only other error is EINVAL, which doesn't apply to
                # this call.
                raise
        except KeyboardInterrupt:
            # NOTE: Another way to handle this is to disable SIGINT when a process is
            # running.  Not sure if there's any real difference.  bash and dash
            # handle SIGINT pretty differently.
            if self.exec_opts.interactive():
                # Caller should keep waiting.  If we run 'sleep 3' and hit Ctrl-C, both
                # processes will get SIGINT, but the shell has to wait again to get the
                # exit code.
                return True
            else:
                raise  # abort a batch script

        #log('WAIT got %s %s', pid, status)

        # All child processes are supposed to be in this dict.  But this may
        # legitimately happen if a grandchild outlives the child (its parent).
        # Then it is reparented under this process, so we might receive
        # notification of its exit, even though we didn't start it.  We can't have
        # any knowledge of such processes, so print a warning.
        if pid not in self.job_state.child_procs:
            stderr_line("osh: PID %d stopped, but osh didn't start it", pid)
            return True  # caller should keep waiting

        proc = self.job_state.child_procs[pid]

        if WIFSIGNALED(status):
            term_sig = WTERMSIG(status)
            status = 128 + term_sig

            # Print newline after Ctrl-C.
            if term_sig == signal_.SIGINT:
                print('')

            proc.WhenDone(pid, status)

        elif WIFEXITED(status):
            status = WEXITSTATUS(status)
            #log('exit status: %s', status)
            proc.WhenDone(pid, status)

        elif WIFSTOPPED(status):
            #sig = posix.WSTOPSIG(status)

            # TODO: Do something nicer here.  Implement 'fg' command.
            # Show in jobs list.
            log('')
            log('[PID %d] Stopped', pid)
            self.job_state.NotifyStopped(
                pid)  # show in 'jobs' list, enable 'fg'
            proc.WhenStopped()

        self.last_status = status  # for wait -n

        return True  # caller should keep waiting
Example #3
0
    def WaitForOne(self, eintr_retry):
        # type: (bool) -> int
        """Wait until the next process returns (or maybe Ctrl-C).

    Args:
      Should be True to prevent zombies

    Returns:
      -1      Nothing to wait for
      0       OK, job mutated
      >= 128  Interrupted with signal

      In the interactive shell, we return 0 if we get a Ctrl-C, so the caller
      will try again.

    Callers:
      wait -n  -- WaitForOne() just once
      wait     -- WaitForOne() in a loop
      wait $!  -- job.JobWait()

    Comparisons:
      bash: jobs.c waitchld() Has a special case macro(!) CHECK_WAIT_INTR for
      the wait builtin

      dash: jobs.c waitproc() uses sigfillset(), sigprocmask(), etc.  Runs in a
      loop while (gotsigchld), but that might be a hack for System V!

    You could imagine a clean API like posix::wait_for_one() 

    wait_result =
      ECHILD                     -- nothing to wait for
    | Done(int pid, int status)  -- process done
    | EINTR(bool sigint)         -- may or may not retry

    But I think we want to keep KeyboardInterrupt as an exception for now.

    """
        # This is a list of async jobs
        try:
            # Notes:
            # - The arg -1 makes it like wait(), which waits for any process.
            # - WUNTRACED is necessary to get stopped jobs.  What about WCONTINUED?
            # - Unlike other syscalls, we do NOT try on EINTR, because the 'wait'
            #   builtin should be interruptable.  This doesn't appear to cause any
            #   problems for other WaitForOne callers?
            pid, status = posix.waitpid(-1, WUNTRACED)
        except OSError as e:
            #log('wait() error: %s', e)
            if e.errno == errno_.ECHILD:
                return -1  # nothing to wait for caller should stop
            elif e.errno == errno_.EINTR:  # Bug #858 fix
                # Examples:
                # - 128 + SIGUSR1 = 128 + 10 = 138
                # - 128 + SIGUSR2 = 128 + 12 = 140
                return 0 if eintr_retry else (128 +
                                              self.sig_state.last_sig_num)
            else:
                # The signature of waitpid() means this shouldn't happen
                raise AssertionError()
        except KeyboardInterrupt:
            # NOTE: Another way to handle this is to disable SIGINT when a process is
            # running.  Not sure if there's any real difference.  bash and dash
            # handle SIGINT pretty differently.
            if self.exec_opts.interactive():
                # Caller should keep waiting.  If we run 'sleep 3' and hit Ctrl-C, both
                # processes will get SIGINT, but the shell has to wait again to get the
                # exit code.
                return 0
            else:
                raise  # abort a batch script

        # All child processes are supposed to be in this dict.  But this may
        # legitimately happen if a grandchild outlives the child (its parent).
        # Then it is reparented under this process, so we might receive
        # notification of its exit, even though we didn't start it.  We can't have
        # any knowledge of such processes, so print a warning.
        if pid not in self.job_state.child_procs:
            stderr_line("osh: PID %d stopped, but osh didn't start it", pid)
            return True  # caller should keep waiting

        proc = self.job_state.child_procs[pid]

        if WIFSIGNALED(status):
            term_sig = WTERMSIG(status)
            status = 128 + term_sig

            # Print newline after Ctrl-C.
            if term_sig == signal_.SIGINT:
                print('')

            proc.WhenDone(pid, status)

        elif WIFEXITED(status):
            status = WEXITSTATUS(status)
            #log('exit status: %s', status)
            proc.WhenDone(pid, status)

        elif WIFSTOPPED(status):
            #sig = posix.WSTOPSIG(status)

            # TODO: Do something nicer here.  Implement 'fg' command.
            # Show in jobs list.
            log('')
            log('[PID %d] Stopped', pid)
            self.job_state.NotifyStopped(
                pid)  # show in 'jobs' list, enable 'fg'
            proc.WhenStopped()

        else:
            raise AssertionError(status)

        self.last_status = status  # for wait -n
        self.tracer.OnProcessEnd(pid, status)
        return 0  # caller should keep waiting