def _setupTree1(self): self._tmpd = tempfile.mkdtemp(prefix='hotwiretest') os.mkdir(path_join(self._tmpd, 'testdir')) if is_unix(): self._test_exe = 'testf' elif is_windows(): self._test_exe = 'testf.exe' self._test_exe_path = path_join(self._tmpd, self._test_exe) open(self._test_exe_path, 'w').close() if is_unix(): os.chmod(self._test_exe_path, 744) os.mkdir(path_join(self._tmpd, 'dir with spaces'))
def load(): if is_unix(): import hotwire_ui.adaptors.aliases_unix fs = Filesystem.getInstance() if fs.executable_on_path('hotwire-ssh'): import hotwire_ui.adaptors.ssh if fs.executable_on_path('hotwire-sudo'): import hotwire_ui.adaptors.sudo import hotwire.sysdep.unix_completers import hotwire_ui.adaptors.edit import hotwire_ui.adaptors.view
def _setupTree2(self): self._setupTree1() if is_unix(): self._test_exe2_path = path_join(self._tmpd, 'testf2') open(self._test_exe2_path, 'w').close() os.chmod(self._test_exe2_path, 744) elif is_windows(): self._test_exe2_path = path_join(self._tmpd, 'testf2.exe') open(self._test_exe2_path, 'w').close() open(path_join(self._tmpd, 'f3test'), 'w').close() open(path_join(self._tmpd, 'otherfile'), 'w').close() os.mkdir(path_join(self._tmpd, 'testdir2')) open(path_join(self._tmpd, 'testdir2', 'blah'), 'w').close() open(path_join(self._tmpd, 'testdir2', 'moo'), 'w').close() os.mkdir(path_join(self._tmpd, 'testdir2', 'moodir'))
def execute(self, context, args, in_opt_format=None, out_opt_format=None): # This function is complex. There are two major variables. First, # are we on Unix or Windows? This is effectively determined by # pty_available, though I suppose some Unixes might not have ptys. # Second, out_opt_format tells us whether we want to stream the # output as lines (out_opt_format is None), or as unbuffered byte chunks # (determined by bytearray/chunked). There is also a special hack # x-filedescriptor/special where we pass along a file descriptor from # the subprocess; this is used in unicode.py to directly read the output. using_pty_out = pty_available and (out_opt_format not in (None, 'x-unix-pipe-file-object/special')) using_pty_in = pty_available and (in_opt_format is None) and \ context.input_is_first and hasattr(context.input, 'connect') _logger.debug("using pty in: %s out: %s", using_pty_in, using_pty_out) # TODO - we need to rework things so that we allocate only one pty per pipeline. # In the very common case of exactly one command, this doesn't matter, but # allocating two ptys will probably bite us in odd ways if someone does create # a pipeline. Maybe have a context.request_pty() function? if using_pty_in or using_pty_out: # We create a pseudo-terminal to ensure that the subprocess is line-buffered. # Yes, this is gross, but as far as I know there is no other way to # control the buffering used by subprocesses. (master_fd, slave_fd) = pty.openpty() # Set the terminal to not do any processing; if you change this, you'll also # need to update unicode.py most likely. attrs = termios.tcgetattr(master_fd) attrs[1] = attrs[1] & (~termios.OPOST) termios.tcsetattr(master_fd, termios.TCSANOW, attrs) _logger.debug("allocated pty fds %d %d", master_fd, slave_fd) if using_pty_out: stdout_target = slave_fd else: stdout_target = subprocess.PIPE if context.input is None: stdin_target = None if using_pty_in: stdin_target = slave_fd elif in_opt_format == 'x-unix-pipe-file-object/special': stdin_target = next(iter(context.input)) else: stdin_target = subprocess.PIPE _logger.debug("using stdin target: %r", stdin_target) context.attribs['master_fd'] = master_fd else: _logger.debug("no pty available or non-chunked output, not allocating fds") (master_fd, slave_fd) = (None, None) stdout_target = subprocess.PIPE if context.input is None: stdin_target = None elif in_opt_format == 'x-unix-pipe-file-object/special': stdin_target = next(iter(context.input)) else: stdin_target = subprocess.PIPE _logger.debug("using stdin target: %r", stdin_target) subproc_args = {'bufsize': 0, 'stdin': stdin_target, 'stdout': stdout_target, 'stderr': subprocess.STDOUT, 'cwd': context.cwd} fs_encoding = sys.getfilesystemencoding() stdin_encoding = sys.stdin.encoding _logger.debug("recoding path to %r, args to %r", fs_encoding, stdin_encoding) # We need to encode arguments to the system encoding because subprocess.py won't do it for us. if fs_encoding is not None: args[0] = args[0].encode(fs_encoding) if stdin_encoding is not None: args[1:] = [x.encode(stdin_encoding) for x in args[1:]] if is_windows(): subproc_args['universal_newlines'] = True startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW import win32con startupinfo.wShowWindow = win32con.SW_HIDE elif is_unix(): subproc_args['close_fds'] = True # Support freedesktop.org startup notification # http://standards.freedesktop.org/startup-notification-spec/startup-notification-latest.txt if context.gtk_event_time: env = dict(os.environ) env['DESKTOP_STARTUP_ID'] = 'hotwire%d_TIME%d' % (os.getpid(), context.gtk_event_time,) subproc_args['env'] = env def preexec(): os.setsid() if using_pty_out and hasattr(termios, 'TIOCSCTTY'): # Set our controlling TTY fcntl.ioctl(1, termios.TIOCSCTTY, '') # We ignore SIGHUP by default, because some broken programs expect that the lifetime # of subcommands is tied to an open window, instead of until when the toplevel # process exits. signal.signal(signal.SIGHUP, signal.SIG_IGN) subproc_args['preexec_fn'] = preexec else: assert(False) subproc = subprocess.Popen(args, **subproc_args) if not subproc.pid: if master_fd is not None: os.close(master_fd) if slave_fd is not None: os.close(slave_fd) raise ValueError('Failed to execute %s' % (args[0],)) context.attribs['pid'] = subproc.pid if using_pty_in or using_pty_out: os.close(slave_fd) context.status_notify('pid %d' % (context.attribs['pid'],)) if in_opt_format == 'x-unix-pipe-file-object/special': stdin_target.close() # If we were passed a file descriptor from another SysBuiltin, close it here; # it's now owned by the child. elif context.input: if using_pty_in: stdin_stream = BareFdStreamWriter(master_fd) else: stdin_stream = subproc.stdin # FIXME hack - need to rework input streaming if context.input_is_first and hasattr(context.input, 'connect'): context.attribs['input_connected'] = True context.input.connect(self.__on_input, stdin_stream) else: MiniThreadPool.getInstance().run(self.__inputwriter, args=(context.input, stdin_stream)) if using_pty_out: stdout_read = None stdout_fd = master_fd else: stdout_read = subproc.stdout stdout_fd = subproc.stdout.fileno() if out_opt_format is None: for line in SysBuiltin.__unbuffered_readlines(stdout_read): yield line elif out_opt_format == 'bytearray/chunked': try: for buf in SysBuiltin.__unbuffered_read_pipe(stream=stdout_read, fd=stdout_fd): yield buf except OSError as e: pass elif out_opt_format == 'x-unix-pipe-file-object/special': yield subproc.stdout elif out_opt_format == 'x-filedescriptor/special': context.attribs['master_fd_passed'] = True yield stdout_fd else: assert(False) retcode = subproc.wait() if retcode >= 0: retcode_str = '%d' % (retcode,) else: retcode_str = _('signal %d') % (abs(retcode),) context.status_notify(_('Exit %s') % (retcode_str,))
try: import pty, termios, fcntl pty_available = True except: pty_available = False import hotwire from hotwire.logutil import log_except from hotwire.text import MarkupText from hotwire.async import MiniThreadPool from hotwire.externals.singletonmixin import Singleton from hotwire.builtin import Builtin, BuiltinRegistry, InputStreamSchema, OutputStreamSchema, MultiArgSpec from hotwire.sysdep import is_windows, is_unix from hotwire.sysdep.proc import ProcessManager if is_unix(): import signal _logger = logging.getLogger("hotwire.builtin.Sys") class SystemCompleters(dict, Singleton): def __init__(self): super(SystemCompleters, self).__init__() # This object is necessary because we don't want the file object # to close the pty FD when it's unreffed. class BareFdStreamWriter(object): def __init__(self, fd): self.fd = fd def write(self, obj):
def get_opt_formats(self): if is_unix(): return ['x-filedescriptor/special', 'bytearray/chunked'] else: return ['bytearray/chunked']
self.cmd = cmd self.owner_name = owner_name def kill(self): raise NotImplementedError() def __str__(self): return "Process '%s' (%s) of %s" % (self.cmd, self.pid, self.owner_name) _module = None if is_linux(): import hotwire.sysdep.proc_impl.proc_linux _module = hotwire.sysdep.proc_impl.proc_linux elif is_unix(): import hotwire.sysdep.proc_impl.proc_unix _module = hotwire.sysdep.proc_impl.proc_unix elif is_windows(): import hotwire.sysdep.proc_impl.proc_win32 _module = hotwire.sysdep.proc_impl.proc_win32 else: raise NotImplementedError("No Process implemented for %r" % (platform.system(), )) _instance = None class ProcessManager(object): @staticmethod def getInstance():
def execute(self, context, args, in_opt_format=None, out_opt_format=None): # This function is complex. There are two major variables. First, # are we on Unix or Windows? This is effectively determined by # pty_available, though I suppose some Unixes might not have ptys. # Second, out_opt_format tells us whether we want to stream the # output as lines (out_opt_format is None), or as unbuffered byte chunks # (determined by bytearray/chunked). There is also a special hack # x-filedescriptor/special where we pass along a file descriptor from # the subprocess; this is used in unicode.py to directly read the output. using_pty_out = pty_available and (out_opt_format not in ( None, 'x-unix-pipe-file-object/special')) using_pty_in = pty_available and (in_opt_format is None) and \ context.input_is_first and hasattr(context.input, 'connect') _logger.debug("using pty in: %s out: %s", using_pty_in, using_pty_out) # TODO - we need to rework things so that we allocate only one pty per pipeline. # In the very common case of exactly one command, this doesn't matter, but # allocating two ptys will probably bite us in odd ways if someone does create # a pipeline. Maybe have a context.request_pty() function? if using_pty_in or using_pty_out: # We create a pseudo-terminal to ensure that the subprocess is line-buffered. # Yes, this is gross, but as far as I know there is no other way to # control the buffering used by subprocesses. (master_fd, slave_fd) = pty.openpty() # Set the terminal to not do any processing; if you change this, you'll also # need to update unicode.py most likely. attrs = termios.tcgetattr(master_fd) attrs[1] = attrs[1] & (~termios.OPOST) termios.tcsetattr(master_fd, termios.TCSANOW, attrs) _logger.debug("allocated pty fds %d %d", master_fd, slave_fd) if using_pty_out: stdout_target = slave_fd else: stdout_target = subprocess.PIPE if context.input is None: stdin_target = None if using_pty_in: stdin_target = slave_fd elif in_opt_format == 'x-unix-pipe-file-object/special': stdin_target = next(iter(context.input)) else: stdin_target = subprocess.PIPE _logger.debug("using stdin target: %r", stdin_target) context.attribs['master_fd'] = master_fd else: _logger.debug( "no pty available or non-chunked output, not allocating fds") (master_fd, slave_fd) = (None, None) stdout_target = subprocess.PIPE if context.input is None: stdin_target = None elif in_opt_format == 'x-unix-pipe-file-object/special': stdin_target = next(iter(context.input)) else: stdin_target = subprocess.PIPE _logger.debug("using stdin target: %r", stdin_target) subproc_args = { 'bufsize': 0, 'stdin': stdin_target, 'stdout': stdout_target, 'stderr': subprocess.STDOUT, 'cwd': context.cwd } fs_encoding = sys.getfilesystemencoding() stdin_encoding = sys.stdin.encoding _logger.debug("recoding path to %r, args to %r", fs_encoding, stdin_encoding) # We need to encode arguments to the system encoding because subprocess.py won't do it for us. if fs_encoding is not None: args[0] = args[0].encode(fs_encoding) if stdin_encoding is not None: args[1:] = [x.encode(stdin_encoding) for x in args[1:]] if is_windows(): subproc_args['universal_newlines'] = True startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW import win32con startupinfo.wShowWindow = win32con.SW_HIDE elif is_unix(): subproc_args['close_fds'] = True # Support freedesktop.org startup notification # http://standards.freedesktop.org/startup-notification-spec/startup-notification-latest.txt if context.gtk_event_time: env = dict(os.environ) env['DESKTOP_STARTUP_ID'] = 'hotwire%d_TIME%d' % ( os.getpid(), context.gtk_event_time, ) subproc_args['env'] = env def preexec(): os.setsid() if using_pty_out and hasattr(termios, 'TIOCSCTTY'): # Set our controlling TTY fcntl.ioctl(1, termios.TIOCSCTTY, '') # We ignore SIGHUP by default, because some broken programs expect that the lifetime # of subcommands is tied to an open window, instead of until when the toplevel # process exits. signal.signal(signal.SIGHUP, signal.SIG_IGN) subproc_args['preexec_fn'] = preexec else: assert (False) subproc = subprocess.Popen(args, **subproc_args) if not subproc.pid: if master_fd is not None: os.close(master_fd) if slave_fd is not None: os.close(slave_fd) raise ValueError('Failed to execute %s' % (args[0], )) context.attribs['pid'] = subproc.pid if using_pty_in or using_pty_out: os.close(slave_fd) context.status_notify('pid %d' % (context.attribs['pid'], )) if in_opt_format == 'x-unix-pipe-file-object/special': stdin_target.close() # If we were passed a file descriptor from another SysBuiltin, close it here; # it's now owned by the child. elif context.input: if using_pty_in: stdin_stream = BareFdStreamWriter(master_fd) else: stdin_stream = subproc.stdin # FIXME hack - need to rework input streaming if context.input_is_first and hasattr(context.input, 'connect'): context.attribs['input_connected'] = True context.input.connect(self.__on_input, stdin_stream) else: MiniThreadPool.getInstance().run(self.__inputwriter, args=(context.input, stdin_stream)) if using_pty_out: stdout_read = None stdout_fd = master_fd else: stdout_read = subproc.stdout stdout_fd = subproc.stdout.fileno() if out_opt_format is None: for line in SysBuiltin.__unbuffered_readlines(stdout_read): yield line elif out_opt_format == 'bytearray/chunked': try: for buf in SysBuiltin.__unbuffered_read_pipe( stream=stdout_read, fd=stdout_fd): yield buf except OSError as e: pass elif out_opt_format == 'x-unix-pipe-file-object/special': yield subproc.stdout elif out_opt_format == 'x-filedescriptor/special': context.attribs['master_fd_passed'] = True yield stdout_fd else: assert (False) retcode = subproc.wait() if retcode >= 0: retcode_str = '%d' % (retcode, ) else: retcode_str = _('signal %d') % (abs(retcode), ) context.status_notify(_('Exit %s') % (retcode_str, ))