def Wait(self): # This is a list of async jobs while True: try: pid, status = posix.wait() 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 else: break # no exception thrown, so no need to retry #log('WAIT got %s %s', pid, status) # TODO: Also handle WIFSTOPPED case? if posix.WIFSIGNALED(status): status = 128 + posix.WTERMSIG(status) # Print newline after Ctrl-C. if posix.WTERMSIG(status) == signal.SIGINT: print() elif posix.WIFEXITED(status): status = posix.WEXITSTATUS(status) #log('exit status: %s', status) # This could happen via coding error. 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.callbacks: ui.Stderr("osh: PID %d stopped, but osh didn't start it", pid) return True # caller should keep waiting callback = self.callbacks.pop(pid) callback(pid, status) self.last_status = status # for wait -n return True # caller should keep waiting
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, posix.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 suppoed to be in this doc. 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 posix.WIFSIGNALED(status): status = 128 + posix.WTERMSIG(status) # Print newline after Ctrl-C. if posix.WTERMSIG(status) == signal.SIGINT: print('') proc.WhenDone(pid, status) elif posix.WIFEXITED(status): status = posix.WEXITSTATUS(status) #log('exit status: %s', status) proc.WhenDone(pid, status) elif posix.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