def _shell_command_full(self): """ Generates a shell command which execution is equal to self.execute() including handling of errors in the middle of the process pipe. """ command = BytesIO() pipe_ok_statuses = [] self._shell_command(command, pipe_ok_statuses) if len(pipe_ok_statuses) == 1: return command.getvalue() command.write(b"; statuses=(${PIPESTATUS[@]});") for process_id, ok_statuses in enumerate(pipe_ok_statuses): if process_id == len(pipe_ok_statuses) - 1: command.write( psys.b(" exit ${{statuses[{0}]}};".format(process_id))) else: command.write( psys.b(" case ${{statuses[{0}]}} in".format(process_id))) if ok_statuses: command.write( psys.b(" {0});;".format("|".join( str(status) for status in ok_statuses)))) command.write(b" *) exit 128;; esac;") return b"bash -c '" + command.getvalue().replace(b"'", b"""'"'"'""") + b"'"
def _shell_command_full(self): """ Generates a shell command which execution is equal to self.execute() including handling of errors in the middle of the process pipe. """ command = BytesIO() pipe_ok_statuses = [] self._shell_command(command, pipe_ok_statuses) if len(pipe_ok_statuses) == 1: return command.getvalue() command.write(b"; statuses=(${PIPESTATUS[@]});") for process_id, ok_statuses in enumerate(pipe_ok_statuses): if process_id == len(pipe_ok_statuses) - 1: command.write(psys.b(" exit ${{statuses[{0}]}};".format(process_id))) else: command.write(psys.b(" case ${{statuses[{0}]}} in".format(process_id))) if ok_statuses: command.write(psys.b(" {0});;".format("|".join(str(status) for status in ok_statuses)))) command.write(b" *) exit 128;; esac;") return b"bash -c '" + command.getvalue().replace(b"'", b"""'"'"'""") + b"'"
def _check_output(obj, valid_stdout, valid_stderr): """Checks a program output.""" stdout = obj.stdout() assert type(stdout) == str and stdout == valid_stdout stderr = obj.stderr() assert type(stderr) == str and stderr == valid_stderr raw_stdout = obj.raw_stdout() assert type(raw_stdout) == bytes and raw_stdout == psys.b(valid_stdout) raw_stderr = obj.raw_stderr() assert type(raw_stderr) == bytes and raw_stderr == psys.b(valid_stderr)
def test_command_with_redirection(test): """Tests command arguments representation.""" process = sh.echo("test", _stdout=STDERR) assert str(process) == "echo test >&2" assert bytes(process) == psys.b(str(process)) process = sh.echo("test", _stdout=File("/tmp/test.path")) assert str(process) == "echo test > /tmp/test.path" assert bytes(process) == psys.b(str(process)) process = sh.echo("test", _stdout=File("/tmp/test.path", append=True)) assert str(process) == "echo test >> /tmp/test.path" assert bytes(process) == psys.b(str(process)) process = sh.echo("test", _stderr=File("/tmp/test.path")) assert str(process) == "echo test 2> /tmp/test.path" assert bytes(process) == psys.b(str(process)) process = sh.echo("test", _stderr=File("/tmp/test.path", append=True)) assert str(process) == "echo test 2>> /tmp/test.path" assert bytes(process) == psys.b(str(process)) process = sh.echo("test", _stderr=STDOUT) assert str(process) == "echo test 2>&1" assert bytes(process) == psys.b(str(process)) process = sh.echo("test", _stdout=DEVNULL, _stderr=DEVNULL) assert str(process) == "echo test > /dev/null 2> /dev/null" assert bytes(process) == psys.b(str(process)) process = sh.echo("test", _stdout=File("/\rtmp\t/test.\rpath")) assert str(process) == "echo test > '/\\rtmp\\t/test.\\rpath'" assert bytes(process) == psys.b(str(process))
def write_arg(stream, arg): arg = psys.b(arg) if simple_arg_re.search(arg) is None: stream.write(b"'" + arg.replace(b"'", b"""'"'"'""") + b"'") else: stream.write(arg)
def __str__(self): """Returns the command string. .. note:: Very lazy formatting. """ return psys.b(self.__unicode__())
def __to_bytes(self): """Returns the command string. .. note:: Very lazy formatting. """ return psys.b(self.__to_str())
def write_arg(stream, arg): arg = psys.b(arg) if simple_arg_re.search(arg) is None: # Or probably we should use br"$'\x{0:02x}'".format(char) stream.write(b"'" + arg.replace(b"'", b"""'"'"'""") + b"'") else: stream.write(arg)
def test_string_input(test): """Tests process input from string.""" assert sh.grep("тест", _stdin="aaa\nтест\nbbb\n").execute().stdout() == "тест\n" assert sh.grep( "тест", _stdin=psys.b("aaa\nтест\nbbb\n")).execute().stdout() == "тест\n"
def test_command_arguments(test): """Tests command argument parsing.""" process = sh.test() assert process.command() == [ "test" ] assert str(process) == "test" assert bytes(process) == psys.b(str(process)) process = sh.complex_command_name() assert process.command() == [ "complex-command-name" ] assert str(process) == "complex-command-name" assert bytes(process) == psys.b(str(process)) process = sh("complex command name")("arg1", "arg2") assert process.command() == [ "complex command name", "arg1", "arg2" ] assert str(process) == "'complex command name' arg1 arg2" assert bytes(process) == psys.b(str(process)) process = sh.test( b"arg", b"space arg", b"carriage\rline", b"line\narg", b"tab\targ", br"slash\arg", b"quote'arg", b'quote"arg', psys.b("тест"), psys.b("тест тест"), "arg", "space arg", "carriage\rline", "line\narg", "tab\targ", r"slash\arg", "quote'arg", 'quote"arg', "тест", "тест тест", 3, 2 ** 128, 2.0 ) assert process.command() == [ "test", b"arg", b"space arg", b"carriage\rline", b"line\narg", b"tab\targ", br"slash\arg", b"quote'arg", b'quote"arg', psys.b("тест"), psys.b("тест тест"), "arg", "space arg", "carriage\rline", "line\narg", "tab\targ", r"slash\arg", "quote'arg", 'quote"arg', "тест", "тест тест", "3", "340282366920938463463374607431768211456", "2.0" ] assert str(process) == ("test " r"""arg 'space arg' 'carriage\rline' 'line\narg' 'tab\targ' 'slash\\arg' "quote'arg" 'quote"arg' \xd1\x82\xd0\xb5\xd1\x81\xd1\x82 '\xd1\x82\xd0\xb5\xd1\x81\xd1\x82 \xd1\x82\xd0\xb5\xd1\x81\xd1\x82' """ r"""arg 'space arg' 'carriage\rline' 'line\narg' 'tab\targ' 'slash\\arg' 'quote\'arg' 'quote"arg' тест 'тест тест' """ "3 340282366920938463463374607431768211456 2.0" ) assert bytes(process) == psys.b(str(process)) process = sh.test("space arg", s = "short_arg") assert process.command() == [ "test", "-s", "short_arg", "space arg" ] assert str(process) == "test -s short_arg 'space arg'" assert bytes(process) == psys.b(str(process)) process = sh.test("arg", long_long_arg = "long arg") assert process.command() == [ "test", "--long-long-arg", "long arg", "arg" ] assert str(process) == "test --long-long-arg 'long arg' arg" assert bytes(process) == psys.b(str(process)) process = sh.test("arg", bool_arg = True) assert process.command() == [ "test", "--bool-arg", "arg" ] assert str(process) == "test --bool-arg arg" assert bytes(process) == psys.b(str(process)) process = sh.test("arg", bool_arg = False) assert process.command() == [ "test", "arg" ] assert str(process) == "test arg" assert bytes(process) == psys.b(str(process))
def test_output_iteration_with_raw_true(test): """Tests iteration over process' output with _iter_raw = True.""" with sh.cat(_stdin="aaa\nтест\nbbb", _iter_raw=True) as process: stdout = [line for line in process] assert stdout == [b"aaa\n", psys.b("тест\n"), b"bbb"] for line in stdout: assert type(line) == bytes
def test_command_with_redirection(test): """Tests command arguments representation.""" process = sh.echo("test", _stdout=STDERR) assert str(process) == "echo test >&2" assert bytes(process) == psys.b(str(process)) process = sh.echo("test", _stdout=File("/tmp/test.path")) assert str(process) == "echo test > /tmp/test.path" assert bytes(process) == psys.b(str(process)) process = sh.echo("test", _stderr=File("/tmp/test.path")) assert str(process) == "echo test 2> /tmp/test.path" assert bytes(process) == psys.b(str(process)) process = sh.echo("test", _stderr=STDOUT) assert str(process) == "echo test 2>&1" assert bytes(process) == psys.b(str(process)) process = sh.echo("test", _stdout=DEVNULL, _stderr=DEVNULL) assert str(process) == "echo test > /dev/null 2> /dev/null" assert bytes(process) == psys.b(str(process)) process = sh.echo("test", _stdout=File("/\rtmp\t/test.\rpath")) assert str(process) == "echo test > '/\\rtmp\\t/test.\\rpath'" assert bytes(process) == psys.b(str(process))
def test_stdin_from_file(test, capfd): """Tests redirecting a file to stdin.""" logging.disable(logging.CRITICAL) try: with tempfile.NamedTemporaryFile() as temp_file: temp_file.write(psys.b("test\nтест")) temp_file.flush() process = sh.tr("t", "z", _stdin=File(temp_file.name)).execute() assert process.stdout() == "zesz\nтест" assert process.stderr() == "" stdout, stderr = capfd.readouterr() assert stdout == "" assert stderr == "" finally: logging.disable(logging.NOTSET)
def test_stdout_to_dev_null_and_stderr_to_file(test, capfd): """Tests redirection of stdout to /dev/null and stderr to a file.""" logging.disable(logging.CRITICAL) try: with tempfile.NamedTemporaryFile() as temp_file: process = sh.sh("-c", "echo test1; echo test2 >&2; echo тест3; echo тест4 >&2;", _stdout=DEVNULL, _stderr=File(temp_file.name)) process.execute() assert process.stdout() == "" assert process.stderr() == "" stdout, stderr = capfd.readouterr() assert stdout == "" assert stderr == "" assert temp_file.read() == psys.b("test2\nтест4\n") finally: logging.disable(logging.NOTSET)
def test_stdout_to_dev_null_and_stderr_to_file(test, capfd): """Tests redirection of stdout to /dev/null and stderr to a file.""" logging.disable(logging.CRITICAL) try: with tempfile.NamedTemporaryFile() as temp_file: process = sh.sh( "-c", "echo test1; echo test2 >&2; echo тест3; echo тест4 >&2;", _stdout=DEVNULL, _stderr=File(temp_file.name)) process.execute() assert process.stdout() == "" assert process.stderr() == "" stdout, stderr = capfd.readouterr() assert stdout == "" assert stderr == "" assert temp_file.read() == psys.b("test2\nтест4\n") finally: logging.disable(logging.NOTSET)
def test_command_arguments(test): """Tests command argument parsing.""" process = sh.test() assert process.command() == ["test"] assert str(process) == "test" assert bytes(process) == psys.b(str(process)) process = sh.complex_command_name() assert process.command() == ["complex-command-name"] assert str(process) == "complex-command-name" assert bytes(process) == psys.b(str(process)) process = sh("complex command name")("arg1", "arg2") assert process.command() == ["complex command name", "arg1", "arg2"] assert str(process) == "'complex command name' arg1 arg2" assert bytes(process) == psys.b(str(process)) process = sh.test(b"arg", b"space arg", b"carriage\rline", b"line\narg", b"tab\targ", br"slash\arg", b"quote'arg", b'quote"arg', psys.b("тест"), psys.b("тест тест"), "arg", "space arg", "carriage\rline", "line\narg", "tab\targ", r"slash\arg", "quote'arg", 'quote"arg', "тест", "тест тест", 3, 2**128, 2.0) assert process.command() == [ "test", b"arg", b"space arg", b"carriage\rline", b"line\narg", b"tab\targ", br"slash\arg", b"quote'arg", b'quote"arg', psys.b("тест"), psys.b("тест тест"), "arg", "space arg", "carriage\rline", "line\narg", "tab\targ", r"slash\arg", "quote'arg", 'quote"arg', "тест", "тест тест", "3", "340282366920938463463374607431768211456", "2.0" ] assert str(process) == ( "test " r"""arg 'space arg' 'carriage\rline' 'line\narg' 'tab\targ' 'slash\\arg' "quote'arg" 'quote"arg' \xd1\x82\xd0\xb5\xd1\x81\xd1\x82 '\xd1\x82\xd0\xb5\xd1\x81\xd1\x82 \xd1\x82\xd0\xb5\xd1\x81\xd1\x82' """ r"""arg 'space arg' 'carriage\rline' 'line\narg' 'tab\targ' 'slash\\arg' 'quote\'arg' 'quote"arg' тест 'тест тест' """ "3 340282366920938463463374607431768211456 2.0") assert bytes(process) == psys.b(str(process)) process = sh.test("space arg", s="short_arg") assert process.command() == ["test", "-s", "short_arg", "space arg"] assert str(process) == "test -s short_arg 'space arg'" assert bytes(process) == psys.b(str(process)) process = sh.test("arg", long_long_arg="long arg") assert process.command() == ["test", "--long-long-arg", "long arg", "arg"] assert str(process) == "test --long-long-arg 'long arg' arg" assert bytes(process) == psys.b(str(process)) process = sh.test("arg", bool_arg=True) assert process.command() == ["test", "--bool-arg", "arg"] assert str(process) == "test --bool-arg arg" assert bytes(process) == psys.b(str(process)) process = sh.test("arg", bool_arg=False) assert process.command() == ["test", "arg"] assert str(process) == "test arg" assert bytes(process) == psys.b(str(process))
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 test_string_input(test): """Tests process input from string.""" assert sh.grep("тест", _stdin="aaa\nтест\nbbb\n").execute().stdout() == "тест\n" assert sh.grep("тест", _stdin=psys.b("aaa\nтест\nbbb\n")).execute().stdout() == "тест\n"
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)
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 generate_klass(name, subclasses, dict): if six.PY2: name = psys.b(name) else: name = psys.u(name) return type(name, subclasses, dict)
def __parse_args(self, args, options): """Parses command arguments and options.""" # Process options --> def check_arg(option, value, types=tuple(), instance_of=tuple(), values=tuple()): if type(value) not in types and not isinstance(value, instance_of) and value not in values: raise InvalidArgument("Invalid value for option {0}", option) return value for option, value in options.items(): if not option.startswith("_"): continue if option == "_defer": self.__defer = check_arg(option, value, types=(bool,)) elif option == "_env": if value is None: self.__env = value else: self.__env = dict( ( psys.b(check_arg(option, k, types=(bytes, str))), psys.b(check_arg(option, v, types=(bytes, str))) ) for k, v in value.items()) elif option == "_iter_delimiter": self.__iter_delimiter = psys.b( check_arg(option, value, types=(bytes, str))) elif option == "_iter_raw": self.__iter_raw = check_arg(option, value, types=(bool,)) elif option == "_ok_statuses": self.__ok_statuses = [ check_arg(option, status, types=(int,)) for status in value] elif option == "_on_execute": self.__on_execute = check_arg(option, value, instance_of=collections.Callable) elif option == "_shell": self.__shell = check_arg(option, value, types=(bool,)) elif option == "_stderr": self.__stderr_target = check_arg( option, value, instance_of=File, values=(STDOUT, STDERR, PIPE)) elif option == "_stdin": self.__stdin_source = check_arg(option, value, types=(bytes, str), instance_of=(File, collections.Iterator, collections.Iterable), values=(STDIN,)) elif option == "_stdout": self.__stdout_target = check_arg( option, value, instance_of=File, values=(STDOUT, STDERR, PIPE)) elif option == "_truncate_output": self.__truncate_output = check_arg(option, value, types=(bool,)) elif option == "_wait_for_output": self.__wait_for_output = check_arg(option, value, types=(bool,)) else: raise InvalidArgument("Invalid option: {0}", option) # Process options <-- # Command arguments --> self.__command.append(self.__program) for option, value in options.items(): if option.startswith("_"): pass elif len(option) == 1: if value is not False: self.__command.append("-" + option) if value is not True: self.__command.append(_get_arg_value(value, self.__shell)) else: if value is not False: self.__command.append("--" + option.replace("_", "-")) if value is not True: self.__command.append(_get_arg_value(value, self.__shell)) self.__command += [_get_arg_value(arg, self.__shell) for arg in args]
def __parse_args(self, args, options): """Parses command arguments and options.""" # Process options --> def check_arg(option, value, types=tuple(), instance_of=tuple(), values=tuple()): if type(value) not in types and not isinstance( value, instance_of) and value not in values: raise InvalidArgument("Invalid value for option {0}", option) return value for option, value in options.items(): if not option.startswith("_"): continue if option == "_defer": self.__defer = check_arg(option, value, types=(bool, )) elif option == "_env": if value is None: self.__env = value else: self.__env = dict( (psys.b(check_arg(option, k, types=(bytes, str))), psys.b(check_arg(option, v, types=(bytes, str)))) for k, v in value.items()) elif option == "_iter_delimiter": self.__iter_delimiter = psys.b( check_arg(option, value, types=(bytes, str))) elif option == "_iter_raw": self.__iter_raw = check_arg(option, value, types=(bool, )) elif option == "_ok_statuses": self.__ok_statuses = [ check_arg(option, status, types=(int, )) for status in value ] elif option == "_on_execute": self.__on_execute = check_arg(option, value, instance_of=collections.Callable) elif option == "_shell": self.__shell = check_arg(option, value, types=(bool, )) elif option == "_stderr": self.__stderr_target = check_arg(option, value, instance_of=File, values=(STDOUT, STDERR, PIPE)) elif option == "_stdin": self.__stdin_source = check_arg( option, value, types=(bytes, str), instance_of=(File, collections.Iterator, collections.Iterable), values=(STDIN, )) elif option == "_stdout": self.__stdout_target = check_arg(option, value, instance_of=File, values=(STDOUT, STDERR, PIPE)) elif option == "_truncate_output": self.__truncate_output = check_arg(option, value, types=(bool, )) elif option == "_wait_for_output": self.__wait_for_output = check_arg(option, value, types=(bool, )) else: raise InvalidArgument("Invalid option: {0}", option) # Process options <-- # Command arguments --> self.__command.append(self.__program) for option, value in options.items(): if option.startswith("_"): pass elif len(option) == 1: if value is not False: self.__command.append("-" + option) if value is not True: self.__command.append( _get_arg_value(value, self.__shell)) else: if value is not False: self.__command.append("--" + option.replace("_", "-")) if value is not True: self.__command.append( _get_arg_value(value, self.__shell)) self.__command += [_get_arg_value(arg, self.__shell) for arg in args]
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