# # Copyright 2005-2007 Swedish Institute of Computer Science. # # Please refer to the file named LICENSE in the same directory as this file # for licensing information. # # Written and maintained by Lars Albertsson . # # $Id: command.py,v 1.1 2007/08/21 14:41:01 fros4943 Exp $ """Utility routines for running external commands.""" import errno import os import popen2 import re import signal import threading import chakana.error import chakana.linux import chakana.threads def quote(argument, useTick = 1): """Quote an argument, or a list of arguments, in order to pass it through a shell.""" if type(argument) in (type([]), type(())): return " ".join(map(quote, argument)) if useTick and not "'" in argument: return "'" + argument + "'" return '"' + argument.replace('\\', '\\\\').replace('"', '\\"').replace( '`', '\\`').replace('$', '\\$') + '"' def softQuote(argument): """Quote an argument, or a list of arguments, in order to pass it through a shell if it is necessary. Always quotes with quotation mark, never with tick.""" if type(argument) in (type([]), type(())): return " ".join(map(softQuote, argument)) if re.match(r'^[\w,./=-]*$', argument): return argument else: return quote(argument, useTick = 0) def deQuote(argument): """Remove quoting in the same manner as the shell (bash) does.""" if argument == "": return "" if argument[0] == "'": nextQuote = argument.find("'", 1) if nextQuote == -1: raise ValueError("Unmatched quote \"'\"") return argument[1 : nextQuote] + deQuote(argument[nextQuote + 1 :]) if len(argument) > 1 and argument[-1] == "'": return argument[1:-1] elif argument[0] == '"': ret = [] index = 1 try: while True: if argument[index] == '"': return "".join(ret) + deQuote(argument[index + 1 :]) if argument[index] == '\\': index += 1 if argument[index] != '\n': ret.append(argument[index]) else: ret.append(argument[index]) index += 1 except IndexError: raise ValueError("Unmatched quote '\"'") elif argument[0] == '\\': if len(argument) == 1: return argument[0] if argument[1] != '\n': return argument[1] + deQuote(argument[2:]) else: return argument[2:] return argument[0] + deQuote(argument[1:]) def runString(argv, environmentVars = ("PYTHONPATH", )): """Return a quoted string that can be pasted into a shell in order to rerun a command.""" envPrefix = "" for envVar in environmentVars: envPrefix += envVar + "=" + os.environ.get(envVar, "") + " " return "( cd " + os.getcwd() + " && " + envPrefix + quote(argv) + " )" class Reader: def __call__(self, fileObj): self._data = [] while 1: newData = fileObj.read() if newData == "": return self._data.append(newData) def result(self): return "".join(self._data) class Writer: def __init__(self, fileObj, data): self._fileObj = fileObj self._data = data if self._data == "": self._fileObj.close() self._threads = [] else: self._threads = [threading.Thread(target = self.write)] self._threads[0].start() def write(self): self._fileObj.write(self._data) self._fileObj.close() def activeThreads(self): return self._threads class Runner: def __init__(self, child, timeout): self._child = child self._timeout = timeout self._threads = [] self._output = [] if self._timeout is None: self.read() self.wait() else: self._threads.append(threading.Thread(target = self.read)) self._threads[-1].start() self._threads.append(threading.Thread(target = self.wait)) self._threads[-1].start() def activeThreads(self): return self._threads def read(self): while 1: newData = self._child.fromchild.read() if newData == "": return self._output.append(newData) def wait(self): self._status = self._child.wait() def output(self): return "".join(self._output) def status(self): return self._status def output(command, inputData = "", timeout = None): """Run command in a separate subprocess. Send inputData to stdin. Kill subprocess and raise error.Timeout after timeout seconds, unless timeout is None.""" # Due to python bug 1183780 (Popen4.wait not thread-safe), avoid # threads if we don't need them. child = popen2.Popen4(command) writer = Writer(child.tochild, inputData) runner = Runner(child, timeout) try: chakana.threads.waitForAll(writer.activeThreads() + runner.activeThreads(), timeout) except chakana.error.Timeout, timeoutErr: try: childProc = chakana.linux.Process(child.pid) childProc.killAllBelow() childProc.reallyKill() except OSError, err: if not err.errno in (errno.ESRCH, errno.ECHILD): raise raise chakana.error.CommandFailed(command, timeoutErr, runner.output()) if runner.status() != 0: raise chakana.error.CommandFailed( command, runner.status(), runner.output()) if re.search(r"\blibefence", os.environ.get("LD_PRELOAD", "")): lines = runner.output().splitlines() if (len(lines) >= 2) and (lines[0] == "") and \ lines[1].strip().startswith("Electric Fence "): return "\n".join(lines[2:] + [""]) return runner.output()