def release_pidfile(path, fd): """Releases a locked PID file.""" try: eintr_retry(os.unlink)(path) finally: eintr_retry(os.close)(fd)
def daemonize(do_fork=True, skip_fds=[]): """Daemonizes current process.""" if do_fork: if os.fork(): os._exit(0) else: os.setsid() if os.fork(): os._exit(0) os.chdir("/") os.umask(0) signal.signal(signal.SIGHUP, signal.SIG_IGN) signal.siginterrupt(signal.SIGHUP, False) # Redirecting standard streams to /dev/null and closing original descriptors null_dev = eintr_retry(os.open)("/dev/null", os.O_RDWR) try: for fd in (sys.stdin.fileno(), sys.stdout.fileno(), sys.stderr.fileno()): if fd not in skip_fds: os.dup2(null_dev, fd) finally: eintr_retry(os.close)(null_dev)
def __init__(self, io_loop): self.__client_id = 0 path = constants.SERVER_SOCKET_PATH LOG.info("Listening to client connections at '%s'...", path) self.__delete_socket() sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: sock.setblocking(False) try: sock.bind(path) sock.listen(128) except EnvironmentError as e: raise Error("Unable to create a UNIX socket '{0}': {1}.", path, e) super(Server, self).__init__(io_loop, sock, "Monitor's server socket") except: try: self.__delete_socket() except Exception as e: LOG.error(e) eintr_retry(sock.close()) raise
def __wait_pid_thread(self, fork_lock, termination_fd): """Waits for the process termination.""" try: # Wait for fork() process completion with fork_lock: pass # Wait only if we've successfully forked if self.__pid is None: return try: status = eintr_retry(os.waitpid)(self.__pid, 0)[1] except Exception as e: LOG.error("Failed to waitpid() process %s: %s.", self.__pid, psys.e(e)) self.__status = 127 else: if os.WIFEXITED(status): self.__status = os.WEXITSTATUS(status) LOG.debug("Command %s terminated with %s status code.", self, self.__status) elif os.WIFSIGNALED(status): signum = os.WTERMSIG(status) LOG.debug("Command %s terminated due to receipt of %s signal.", self, signum) self.__status = 128 + signum else: LOG.error("Command %s terminated due to unknown reason.", self) self.__status = 127 except Exception: LOG.exception("PID waiting thread crashed.") finally: try: eintr_retry(os.close)(termination_fd) except Exception as e: LOG.error("Unable to close a pipe: %s.", psys.e(e))
def close(self): """Closes the object.""" if self.__epoll is not None: try: eintr_retry(self.__epoll.close)() except Exception as e: LOG.error("Unable to close an epoll instance: %s.", psys.e(e)) else: self.__epoll = None
def __execute(self, stdout): """Executes the command.""" # Configure the standard I/O file descriptors self.__configure_stdio(stdout) # Fork the process --> fork_lock = threading.Lock() with fork_lock: # Allocate all resources before fork() to guarantee that we will # be able to control the process execution. # Execution thread --> poll = psys.poll.Poll() try: self.__communication_thread = threading.Thread( target=self.__communication_thread_func, args=(fork_lock, poll)) self.__communication_thread.daemon = True self.__communication_thread.start() except: poll.close() raise # Execution thread <-- # Wait thread --> try: self.__termination_fd, termination_fd = os.pipe() except Exception as e: raise Error("Unable to create a pipe: {0}.", psys.e(e)) try: self.__wait_thread = threading.Thread( target=self.__wait_pid_thread, args=[fork_lock, termination_fd]) self.__wait_thread.daemon = True self.__wait_thread.start() except BaseException as error: try: eintr_retry(os.close)(termination_fd) except Exception as e: LOG.error("Unable to close a pipe: %s.", psys.e(e)) raise error # Wait thread <-- self.__pid = os.fork() if self.__pid: self.__state = _PROCESS_STATE_RUNNING else: self.__child()
def write_pidfile(fd): """Write pid to pidfile previously allocated by acquire_pidfile()""" data = str(os.getpid()) datalen = len(data) while data: size = eintr_retry(os.write)(fd, data) data = data[size:] eintr_retry(os.ftruncate)(fd, datalen)
def __init__(self, nonblock=False): self.read, self.write = os.pipe() try: if nonblock: for fd in self.read, self.write: flags = eintr_retry(fcntl.fcntl)(fd, fcntl.F_GETFL) eintr_retry(fcntl.fcntl)(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) except: self.close() raise
def __poll_objects(self): """Polls the controlled objects.""" timeout = -1 if self.__deferred_calls: timeout = max(0, self.__deferred_calls[0].time - time.time()) for fd, flags in eintr_retry(self.__epoll.poll)(timeout=timeout): try: obj = self.__objects[fd] except KeyError: continue try: if flags & EPOLLERR: if not obj.closed(): obj.on_error(Error("Disconnected.")) if flags & EPOLLHUP: if not obj.closed(): obj.on_hang_up() if flags & EPOLLIN: if not obj.closed(): obj.on_read() if flags & EPOLLOUT: if not obj.closed(): obj.on_write() except Exception as e: if not isinstance(e, (EnvironmentError, EOFError)): LOG.exception("%s handling crashed.", obj) obj.on_error(e)
def __close(self): """ Frees all allocated resources unneeded after the process termination or its failed execution. """ for pipe in self.__pipes: pipe.close() del self.__pipes[:] if self.__termination_fd is not None: try: eintr_retry(os.close)(self.__termination_fd) except Exception as e: LOG.error("Unable to close a pipe: %s.", psys.e(e)) else: self.__termination_fd = None
def close(self, read=True, write=True): """Closes the pipe.""" if read and self.read is not None: try: eintr_retry(os.close)(self.read) except Exception as e: LOG.error("Unable to close a pipe: %s.", psys.e(e)) else: self.read = None if write and self.write is not None: try: eintr_retry(os.close)(self.write) except Exception as e: LOG.error("Unable to close a pipe: %s.", psys.e(e)) else: self.write = None
def close(self): """Closes the object.""" if self.__closed: return self.__closed = True # Detach all objects for obj in list(self.__objects.values()): obj.close() # Break possible cycle references del self.__deferred_calls[:] try: eintr_retry(self.__epoll.close)() except Exception as e: LOG.error("Failed to close a epoll object: %s.", e)
def on_read(self): """Called when we have data to read.""" try: connection = eintr_retry(self._file.accept)()[0] except EnvironmentError as e: if e.errno != errno.ECONNABORTED: LOG.error("Unable to accept a connection: %s.", e) else: connection_name = "Client connection #{0}".format(self.__client_id) self.__client_id += 1 LOG.debug("Accepting a new %s...", connection_name) try: _Client(self._weak_io_loop(), connection, connection_name) except Exception as e: LOG.error("Failed to accept %s: %s.", connection_name, e) eintr_retry(connection.close())
def __init__(self, process, raw, delimiter): # The process self.__process = process # Raw or unicode string iteration self.__raw = raw # Block delimiter self.__delimiter = delimiter # Output pipe self.__pipe = None # Polling object self.__poll = None # Output data buffer. None indicates EOF. self.__data = b"" # Is the iterator closed self.__closed = False if delimiter: self.__iter = self.__iter_with_delimiter else: if raw: self.__iter = self.__iter_without_delimiter else: raise InvalidOperation( "Can't iterate over unicode data without delimiter") try: self.__pipe = Pipe(psys.STDOUT_FILENO) flags = eintr_retry(fcntl.fcntl)(self.__pipe.read, fcntl.F_GETFL) eintr_retry(fcntl.fcntl)(self.__pipe.read, fcntl.F_SETFL, flags | os.O_NONBLOCK) self.__poll = psys.poll.Poll() self.__poll.register(self.__pipe.read, self.__poll.POLLIN) except: self.close() raise
def __backup_file(self, path): """Backups the specified file.""" try: try: fd = eintr_retry(os.open)(path, self.__open_flags) except EnvironmentError as e: # If O_NOATIME flag was specified, but the effective user ID # of the caller did not match the owner of the file and the # caller was not privileged (CAP_FOWNER), the EPERM will be # returned. if ( hasattr(os, "O_NOATIME") and e.errno == errno.EPERM and self.__open_flags & os.O_NOATIME ): # Just disable this flag on a first EPERM error LOG.debug("Got EPERM error. Disabling O_NOATIME for file opening operations...") self.__open_flags &= ~os.O_NOATIME fd = eintr_retry(os.open)(path, self.__open_flags) else: raise except EnvironmentError as e: # When O_NOFOLLOW is specified, indicates that this is a # symbolic link. if e.errno == errno.ELOOP: raise FileTypeChangedError() else: raise try: file_obj = os.fdopen(fd, "rb") except: try: eintr_retry(os.close)(fd) except Exception as e: LOG.error("Unable to close a file: %s.", psys.e(e)) raise with file_obj: stat_info = os.fstat(file_obj.fileno()) self.__backup.add_file(path, stat_info, file_obj = file_obj)
def __init__(self, process, raw, delimiter): # The process self.__process = process # Raw or unicode string iteration self.__raw = raw # Block delimiter self.__delimiter = delimiter # Output pipe self.__pipe = None # Polling object self.__poll = None # Output data buffer. None indicates EOF. self.__data = b"" # Is the iterator closed self.__closed = False if delimiter: self.__iter = self.__iter_with_delimiter else: if raw: self.__iter = self.__iter_without_delimiter else: raise InvalidOperation("Can't iterate over unicode data without delimiter") try: self.__pipe = Pipe(psys.STDOUT_FILENO) flags = eintr_retry(fcntl.fcntl)(self.__pipe.read, fcntl.F_GETFL) eintr_retry(fcntl.fcntl)(self.__pipe.read, fcntl.F_SETFL, flags | os.O_NONBLOCK) self.__poll = psys.poll.Poll() self.__poll.register(self.__pipe.read, self.__poll.POLLIN) except: self.close() raise
def __wait_pid_thread(self, fork_lock, termination_fd): """Waits for the process termination.""" try: # Wait for fork() process completion with fork_lock: pass # Wait only if we've successfully forked if self.__pid is None: return try: status = eintr_retry(os.waitpid)(self.__pid, 0)[1] except Exception as e: LOG.error("Failed to waitpid() process %s: %s.", self.__pid, psys.e(e)) self.__status = 127 else: if os.WIFEXITED(status): self.__status = os.WEXITSTATUS(status) LOG.debug("Command %s terminated with %s status code.", self, self.__status) elif os.WIFSIGNALED(status): signum = os.WTERMSIG(status) LOG.debug( "Command %s terminated due to receipt of %s signal.", self, signum) self.__status = 128 + signum else: LOG.error("Command %s terminated due to unknown reason.", self) self.__status = 127 except Exception: LOG.exception("PID waiting thread crashed.") finally: try: eintr_retry(os.close)(termination_fd) except Exception as e: LOG.error("Unable to close a pipe: %s.", psys.e(e))
def main(): """The daemon's main function.""" parser = argparse.ArgumentParser(description="XBee monitor") parser.add_argument("-d", "--debug", action="store_true", help="print debug messages") args = parser.parse_args() try: monitor.config.load() common.log.setup("xbee-monitor", debug_mode=args.debug) except Exception as e: sys.exit("Unable to start the daemon: {0}".format(e)) LOG.info("Starting the daemon...") try: with _MainLoop() as io_loop: signals = (signal.SIGINT, signal.SIGTERM, signal.SIGQUIT) read_fd, write_fd = os.pipe() try: try: _configure_termination_signals( io_loop, signals, read_fd, write_fd) except: eintr_retry(os.close)(read_fd) raise io_loop.start() finally: for sig in signals: signal.signal(sig, signal.SIG_IGN) eintr_retry(os.close)(write_fd) except Exception as e: LOG.error("The daemon has crashed: %s", e)
def main(): try: _configure_signal_handling() args = _parse_args() backup_dir = args.backup_dir pcli.log.setup( name="git-backup", debug_mode=args.debug, level=logging.WARNING if not args.debug and args.cron else None) _check_backup_dir(backup_dir) lock_file_path = os.path.expanduser(os.path.join(backup_dir, _LOCK_FILE_NAME)) try: lock_file_fd = psys.daemon.acquire_pidfile(lock_file_path) except psys.daemon.PidFileLockedError as e: if args.cron: log.debug("Exiting: %s", e) else: raise Error("{}", e) except psys.daemon.PidFileLockError as e: raise Error("{}", e) else: try: _backup(args.user, backup_dir) finally: try: os.unlink(lock_file_path) except EnvironmentError as e: log.error("Failed to delete lock file '%s': %s.", lock_file_path, e) finally: eintr_retry(os.close)(lock_file_fd) except Error as e: sys.exit("Error: {}".format(e))
def close(self): """Closes the object.""" if not self.closed(): LOG.debug("Close %s.", self) io_loop = self._weak_io_loop() if io_loop is not None: io_loop.remove_object(self) try: eintr_retry(self._file.close)() except Exception as e: LOG.error("Failed to close %s: %s.", self, e) self._file = None for handler in self.__on_close_handlers: try: handler() except: LOG.exception("A close handler for %s crashed.", self) self.__on_close_handlers = []
def __iter_without_delimiter(self): """Iterates over the data.""" try: self.__poll.poll() data = eintr_retry(os.read)(self.__pipe.read, psys.BUFSIZE) except: self.__finalize() raise else: if not data: self.__data = None self.__finalize() raise StopIteration() return data
def acquire_pidfile(path): """Creates and locks a PID file.""" fd = -1 try: fd = eintr_retry(os.open)(path, os.O_RDWR | os.O_CREAT, 0o600) if fd <= sys.stderr.fileno(): eintr_retry(os.dup2)(fd, sys.stderr.fileno() + 1) eintr_retry(os.close)(fd) fd = sys.stderr.fileno() + 1 try: fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) except EnvironmentError as e: if e.errno == errno.EWOULDBLOCK: raise PidFileLockedError(path) else: raise e fd_stat = os.fstat(fd) try: file_stat = os.stat(path) except EnvironmentError as e: if e.errno == errno.ENOENT: raise PidFileLockedError(path) else: raise e if( ( fd_stat[stat.ST_DEV], fd_stat[stat.ST_INO] ) != ( file_stat[stat.ST_DEV], file_stat[stat.ST_INO] ) ): raise PidFileLockedError(path) return fd except Exception as e: if fd != -1: eintr_retry(os.close)(fd) if isinstance(e, PidFileLockedError): raise e raise PidFileLockError(path, "Failed to lock PID file '{0}': {1}.", path, e)
def _read(self, size): """ Reads data from the file to the read buffer and returns True only when the specified size will be read. """ if len(self._read_buffer) < size: try: data = eintr_retry(os.read)(self.fileno(), size - len(self._read_buffer)) except EnvironmentError as e: if e.errno != errno.EWOULDBLOCK: raise else: if not data: raise EOFError("End of file has been reached.") self._read_buffer.extend(data) return len(self._read_buffer) >= size
def redirect_fd(path, fd, write=True, append=False): try: if write: file_fd = eintr_retry(os.open)( path, os.O_WRONLY | os.O_CREAT | (os.O_APPEND if append else 0), 0o666) else: file_fd = eintr_retry(os.open)(path, os.O_RDONLY) try: eintr_retry(os.dup2)(file_fd, fd) finally: eintr_retry(os.close)(file_fd) except Exception as e: raise Error("Unable to redirect {0} to {1}: {2}", fd_name[fd] if write else "'" + path + "'", "'" + path + "'" if write else fd_name[fd], psys.e(e))
def _write(self, data=None): """ Writes the data from the write buffer + the specified data and returns True only when all the data will be written. """ if data is not None: self._write_buffer.extend(data) if self._write_buffer: try: size = eintr_retry(os.write)(self.fileno(), self._write_buffer) except EnvironmentError as e: if e.errno == errno.EWOULDBLOCK: pass else: raise else: if size: del self._write_buffer[:size + 1] return not self._write_buffer
def redirect_fd(path, fd, write=True, append=False): try: if write: file_fd = eintr_retry( os.open)(path, os.O_WRONLY | os.O_CREAT | (os.O_APPEND if append else 0), 0o666) else: file_fd = eintr_retry(os.open)(path, os.O_RDONLY) try: eintr_retry(os.dup2)(file_fd, fd) finally: eintr_retry(os.close)(file_fd) except Exception as e: raise Error("Unable to redirect {0} to {1}: {2}", fd_name[fd] if write else "'" + path + "'", "'" + path + "'" if write else fd_name[fd], psys.e(e))
def __iter_with_delimiter(self): """Iterates over the data splitting it with the delimiter.""" try: pos = self.__data.index(self.__delimiter) except ValueError: while True: try: self.__poll.poll() data = eintr_retry(os.read)(self.__pipe.read, psys.BUFSIZE) except: self.__finalize(check_status=False) raise if data: try: pos = data.index(self.__delimiter) except ValueError: self.__data += data else: block = self.__data + data[:pos + 1] self.__data = data[pos + 1:] return self.__transform_block(block) else: block = self.__data self.__data = None self.__finalize() if not block: raise StopIteration() return self.__transform_block(block) else: block = self.__data[:pos + 1] self.__data = self.__data[pos + 1:] return self.__transform_block(block)
def __communicate(self, poll): """Communicates with the process and waits for its termination.""" pipe_map = {} poll_objects = 0 # Configure the poll object and pipes --> poll.register(self.__termination_fd, poll.POLLIN) poll_objects += 1 for pipe in self.__pipes: fd = pipe.read if pipe.output else pipe.write if fd is None: continue pipe_map[fd] = pipe flags = eintr_retry(fcntl.fcntl)(fd, fcntl.F_GETFL) eintr_retry(fcntl.fcntl)(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) poll.register(fd, poll.POLLIN if pipe.output else poll.POLLOUT) pipe.close(read=not pipe.output, write=pipe.output) poll_objects += 1 # Configure the poll object and pipes <-- # Communicate with the process --> stdin = None while poll_objects: events = poll.poll() for fd, flags in events: # Process termination if fd == self.__termination_fd: if self.__wait_for_output: poll.unregister(self.__termination_fd) poll_objects -= 1 continue else: poll_objects = 0 break pipe = pipe_map[fd] # stdin if pipe.source == psys.STDIN_FILENO: if stdin is None: try: stdin = next(self.__stdin_generator) try: if type(stdin) not in (bytes, str): raise TypeError("must be a string") stdin = psys.b(stdin) except Exception as e: raise InvalidArgument( "Invalid stdin data: {0}", e) except StopIteration: pass except Exception as e: self.__error = e stdin = None if stdin is None: poll.unregister(fd) poll_objects -= 1 pipe.close() else: try: size = eintr_retry(os.write)(fd, stdin) except EnvironmentError as e: # The process closed its stdin if e.errno == errno.EPIPE: poll.unregister(fd) poll_objects -= 1 pipe.close() else: raise e else: if size == len(stdin): stdin = None else: stdin = stdin[size:] # stdout/stderr elif pipe.source in (psys.STDOUT_FILENO, psys.STDERR_FILENO): data = eintr_retry(os.read)(fd, psys.BUFSIZE) if data: self.__on_output(pipe.source, data) else: poll.unregister(fd) poll_objects -= 1 else: raise LogicalError() # Communicate with the process <-- if not self.__wait_for_output: # The process has terminated, but we should continue communication # to get output that we haven't got from it yet. But we must do it # wisely, because the process might fork() itself, so we'll read # its child's output forever. # Maximum output size after process termination (bigger than any # pipe buffer size). max_data_size = 1024 * 1024 for pipe in self.__pipes: if (pipe.source in (psys.STDOUT_FILENO, psys.STDERR_FILENO) and pipe.read is not None): size = 0 while size < max_data_size: try: data = eintr_retry(os.read)( pipe.read, min(psys.BUFSIZE, max_data_size - size)) except EnvironmentError as e: if e.errno != errno.EAGAIN: raise e if not self.__truncate_output: self.__error = ProcessOutputWasTruncated( str(self), self.__status, self.__stdout.getvalue(), self.__stderr.getvalue()) break else: if data: size += len(data) self.__on_output(pipe.source, data) else: break
def __communicate(self, poll): """Communicates with the process and waits for its termination.""" pipe_map = {} poll_objects = 0 # Configure the poll object and pipes --> poll.register(self.__termination_fd, poll.POLLIN) poll_objects += 1 for pipe in self.__pipes: fd = pipe.read if pipe.output else pipe.write if fd is None: continue pipe_map[fd] = pipe flags = eintr_retry(fcntl.fcntl)(fd, fcntl.F_GETFL) eintr_retry(fcntl.fcntl)(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) poll.register(fd, poll.POLLIN if pipe.output else poll.POLLOUT) pipe.close(read=not pipe.output, write=pipe.output) poll_objects += 1 # Configure the poll object and pipes <-- # Communicate with the process --> stdin = None while poll_objects: events = poll.poll() for fd, flags in events: # Process termination if fd == self.__termination_fd: if self.__wait_for_output: poll.unregister(self.__termination_fd) poll_objects -= 1 continue else: poll_objects = 0 break pipe = pipe_map[fd] # stdin if pipe.source == psys.STDIN_FILENO: if stdin is None: try: stdin = next(self.__stdin_generator) try: if type(stdin) not in (bytes, str): raise TypeError("must be a string") stdin = psys.b(stdin) except Exception as e: raise InvalidArgument("Invalid stdin data: {0}", e) except StopIteration: pass except Exception as e: self.__error = e stdin = None if stdin is None: poll.unregister(fd) poll_objects -= 1 pipe.close() else: try: size = eintr_retry(os.write)(fd, stdin) except EnvironmentError as e: # The process closed its stdin if e.errno == errno.EPIPE: poll.unregister(fd) poll_objects -= 1 pipe.close() else: raise e else: if size == len(stdin): stdin = None else: stdin = stdin[size:] # stdout/stderr elif pipe.source in (psys.STDOUT_FILENO, psys.STDERR_FILENO): data = eintr_retry(os.read)(fd, psys.BUFSIZE) if data: self.__on_output(pipe.source, data) else: poll.unregister(fd) poll_objects -= 1 else: raise LogicalError() # Communicate with the process <-- if not self.__wait_for_output: # The process has terminated, but we should continue communication # to get output that we haven't got from it yet. But we must do it # wisely, because the process might fork() itself, so we'll read # its child's output forever. # Maximum output size after process termination (bigger than any # pipe buffer size). max_data_size = 1024 * 1024 for pipe in self.__pipes: if ( pipe.source in (psys.STDOUT_FILENO, psys.STDERR_FILENO) and pipe.read is not None ): size = 0 while size < max_data_size: try: data = eintr_retry(os.read)( pipe.read, min(psys.BUFSIZE, max_data_size - size)) except EnvironmentError as e: if e.errno != errno.EAGAIN: raise e if not self.__truncate_output: self.__error = ProcessOutputWasTruncated( str(self), self.__status, self.__stdout.getvalue(), self.__stderr.getvalue()) break else: if data: size += len(data) self.__on_output(pipe.source, data) else: break
def on_terminate(signum, stack): try: eintr_retry(os.write)(write_fd, b"\0") except EnvironmentError as e: if e.errno not in (errno.EPIPE, errno.EWOULDBLOCK): LOG.error("Failed to send termination signal to I/O loop: %s.", e)
def __child(self): """Handles child process execution.""" exit_code = 127 try: exec_error = False try: fd_name = { 0: "stdin", 1: "stdout", 2: "stderr", } def redirect_fd(path, fd, write=True, append=False): try: if write: file_fd = eintr_retry( os.open)(path, os.O_WRONLY | os.O_CREAT | (os.O_APPEND if append else 0), 0o666) else: file_fd = eintr_retry(os.open)(path, os.O_RDONLY) try: eintr_retry(os.dup2)(file_fd, fd) finally: eintr_retry(os.close)(file_fd) except Exception as e: raise Error("Unable to redirect {0} to {1}: {2}", fd_name[fd] if write else "'" + path + "'", "'" + path + "'" if write else fd_name[fd], psys.e(e)) # Connect all pipes for pipe in self.__pipes: try: eintr_retry( os.dup2)(pipe.write if pipe.output else pipe.read, pipe.source) except Exception as e: raise Error("Unable to connect a pipe to {0}: {1}", fd_name[pipe.source], psys.e(e)) pipe.close() # Close all file descriptors psys.close_all_fds() # Configure stdin if isinstance(self.__stdin_source, File): redirect_fd(self.__stdin_source.path, psys.STDIN_FILENO, write=False) # Configure stdout if self.__stdout_target is STDERR: try: eintr_retry(os.dup2)(psys.STDERR_FILENO, psys.STDOUT_FILENO) except Exception as e: raise Error("Unable to redirect stdout to stderr: {0}", psys.e(e)) elif isinstance(self.__stdout_target, File): redirect_fd(self.__stdout_target.path, psys.STDOUT_FILENO, append=self.__stdout_target.append) # Configure stderr if self.__stderr_target is STDOUT: try: eintr_retry(os.dup2)(psys.STDOUT_FILENO, psys.STDERR_FILENO) except Exception as e: raise Error("Unable to redirect stderr to stdout: {0}", psys.e(e)) elif isinstance(self.__stderr_target, File): redirect_fd(self.__stderr_target.path, psys.STDERR_FILENO, append=self.__stderr_target.append) # Required when we have C locale command = [psys.b(arg) for arg in self.__command] exec_error = True if self.__env is None: os.execvp(self.__program, command) else: os.execvpe(self.__program, command, self.__env) except Exception as e: if exec_error and isinstance( e, EnvironmentError) and e.errno == errno.EACCES: exit_code = 126 print("Failed to execute '{program}': {error}.".format( program=self.__program, error=psys.e(e)), file=sys.stderr) finally: os._exit(exit_code)
def __child(self): """Handles child process execution.""" exit_code = 127 try: exec_error = False try: fd_name = { 0: "stdin", 1: "stdout", 2: "stderr", } def redirect_fd(path, fd, write=True, append=False): try: if write: file_fd = eintr_retry(os.open)( path, os.O_WRONLY | os.O_CREAT | (os.O_APPEND if append else 0), 0o666) else: file_fd = eintr_retry(os.open)(path, os.O_RDONLY) try: eintr_retry(os.dup2)(file_fd, fd) finally: eintr_retry(os.close)(file_fd) except Exception as e: raise Error("Unable to redirect {0} to {1}: {2}", fd_name[fd] if write else "'" + path + "'", "'" + path + "'" if write else fd_name[fd], psys.e(e)) # Connect all pipes for pipe in self.__pipes: try: eintr_retry(os.dup2)(pipe.write if pipe.output else pipe.read, pipe.source) except Exception as e: raise Error("Unable to connect a pipe to {0}: {1}", fd_name[pipe.source], psys.e(e)) pipe.close() # Close all file descriptors psys.close_all_fds() # Configure stdin if isinstance(self.__stdin_source, File): redirect_fd(self.__stdin_source.path, psys.STDIN_FILENO, write=False) # Configure stdout if self.__stdout_target is STDERR: try: eintr_retry(os.dup2)(psys.STDERR_FILENO, psys.STDOUT_FILENO) except Exception as e: raise Error("Unable to redirect stderr to stdout: {0}", psys.e(e)) elif isinstance(self.__stdout_target, File): redirect_fd(self.__stdout_target.path, psys.STDOUT_FILENO, append=self.__stdout_target.append) # Configure stderr if self.__stderr_target is STDOUT: try: eintr_retry(os.dup2)(psys.STDOUT_FILENO, psys.STDERR_FILENO) except Exception as e: raise Error("Unable to redirect stderr to stdout: {0}", psys.e(e)) elif isinstance(self.__stderr_target, File): redirect_fd(self.__stderr_target.path, psys.STDERR_FILENO, append=self.__stderr_target.append) # Required when we have C locale command = [psys.b(arg) for arg in self.__command] exec_error = True if self.__env is None: os.execvp(self.__program, command) else: os.execvpe(self.__program, command, self.__env) except Exception as e: if exec_error and isinstance(e, EnvironmentError) and e.errno == errno.EACCES: exit_code = 126 print("Failed to execute '{program}': {error}.".format( program=self.__program, error=psys.e(e)), file=sys.stderr) finally: os._exit(exit_code)