190 lines
5.4 KiB
Python
190 lines
5.4 KiB
Python
#
|
|
# 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 <lalle@sics.se>.
|
|
#
|
|
|
|
# $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()
|
|
|