""" module defining basic hook for executing commands in a - as much as possible - platform independent way. Current list: exec_cmd(cmd) executes the given command and returns output or ExecutionFailed exception (if exit status!=0) """ import os, sys import py #----------------------------------------------------------- # posix external command execution #----------------------------------------------------------- def posix_exec_cmd(cmd): """ return output of executing 'cmd'. raise ExecutionFailed exeception if the command failed. the exception will provide an 'err' attribute containing the error-output from the command. """ __tracebackhide__ = True import popen2 import errno #print "execing", cmd child = popen2.Popen3(cmd, 1) stdin, stdout, stderr = child.tochild, child.fromchild, child.childerr stdin.close() # XXX sometimes we get a blocked r.read() call (see below) # although select told us there is something to read. # only the next three lines appear to prevent # the read call from blocking infinitely. import fcntl def set_non_block(fd): flags = fcntl.fcntl(fd, fcntl.F_GETFL) flags = flags | os.O_NONBLOCK fcntl.fcntl(fd, fcntl.F_SETFL, flags) set_non_block(stdout.fileno()) set_non_block(stderr.fileno()) #fcntl.fcntl(stdout, fcntl.F_SETFL, os.O_NONBLOCK) #fcntl.fcntl(stderr, fcntl.F_SETFL, os.O_NONBLOCK) import select out, err = [], [] while 1: r_list = filter(lambda x: x and not x.closed, [stdout, stderr]) if not r_list: break try: r_list = select.select(r_list, [], [])[0] except (select.error, IOError), se: if se.args[0] == errno.EINTR: continue else: raise for r in r_list: try: data = r.read() # XXX see XXX above except IOError, io: if io.args[0] == errno.EAGAIN: continue # Connection Lost raise except OSError, ose: if ose.errno == errno.EPIPE: # Connection Lost raise if ose.errno == errno.EAGAIN: # MacOS-X does this continue raise if not data: r.close() continue if r is stdout: out.append(data) else: err.append(data) pid, systemstatus = os.waitpid(child.pid, 0) if pid != child.pid: raise ExecutionFailed, "child process disappeared during: "+ cmd if systemstatus: if os.WIFSIGNALED(systemstatus): status = os.WTERMSIG(systemstatus) + 128 else: status = os.WEXITSTATUS(systemstatus) raise ExecutionFailed(status, systemstatus, cmd, ''.join(out), ''.join(err)) return "".join(out) #----------------------------------------------------------- # simple win32 external command execution #----------------------------------------------------------- def win32_exec_cmd(cmd): """ return output of executing 'cmd'. raise ExecutionFailed exeception if the command failed. the exception will provide an 'err' attribute containing the error-output from the command. Note that this method can currently deadlock because we don't have WaitForMultipleObjects in the std-python api. Further note that the rules for quoting are very special under Windows. Do a HELP CMD in a shell, and tell me if you understand this. For now, I try to do a fix. """ #print "*****", cmd # the following quoting is only valid for CMD.EXE, not COMMAND.COM cmd_quoting = True try: if os.environ['COMSPEC'].upper().endswith('COMMAND.COM'): cmd_quoting = False except KeyError: pass if cmd_quoting: if '"' in cmd and not cmd.startswith('""'): cmd = '"%s"' % cmd return popen3_exec_cmd(cmd) def popen3_exec_cmd(cmd): stdin, stdout, stderr = os.popen3(cmd) out = stdout.read() err = stderr.read() stdout.close() stderr.close() status = stdin.close() if status: raise ExecutionFailed(status, status, cmd, out, err) return out def pypy_exec_cmd(cmd): return popen3_exec_cmd(cmd) class ExecutionFailed(py.error.Error): def __init__(self, status, systemstatus, cmd, out, err): Exception.__init__(self) self.status = status self.systemstatus = systemstatus self.cmd = cmd self.err = err self.out = out def __str__(self): return "ExecutionFailed: %d %s\n%s" %(self.status, self.cmd, self.err) # # choose correct platform-version # if sys.platform == 'win32': cmdexec = win32_exec_cmd elif hasattr(sys, 'pypy') or hasattr(sys, 'pypy_objspaceclass'): cmdexec = popen3_exec_cmd else: cmdexec = posix_exec_cmd # export the exception under the name 'py.process.cmdexec.Error' cmdexec.Error = ExecutionFailed try: ExecutionFailed.__module__ = 'py.process.cmdexec' ExecutionFailed.__name__ = 'Error' except (AttributeError, TypeError): pass