# VirtSubproc is part of autopkgtest
# autopkgtest is a tool for testing Debian binary packages
#
-# autopkgtest is Copyright (C) 2006 Canonical Ltd.
+# autopkgtest is Copyright (C) 2006-2007 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
import signal
import subprocess
import traceback
+import re as regexp
debuglevel = None
progname = "<VirtSubproc>"
devnull_read = file('/dev/null','r')
caller = __main__
+copy_timeout = 300
class Quit:
def __init__(q,ec,m): q.ec = ec; q.m = m
+class Timeout: pass
+def alarm_handler(*a): raise Timeout()
+def timeout_start(to): signal.alarm(to)
+def timeout_stop(): signal.alarm(0)
+
+class FailedCmd:
+ def __init__(fc,e): fc.e = e
+
def debug(m):
if not debuglevel: return
print >> sys.stderr, progname+": debug:", m
def ok(): print 'ok'
-def cmdnumargs(c, ce, nargs=0):
- if len(c) == nargs + 1: return
- bomb("wrong number of arguments to command `%s'" % ce[0])
+def cmdnumargs(c, ce, nargs=0, noptargs=0):
+ if len(c) < 1+nargs:
+ bomb("too few arguments to command `%s'" % ce[0])
+ if noptargs is not None and len(c) > 1+nargs+noptargs:
+ bomb("too many arguments to command `%s'" % ce[0])
def cmd_capabilities(c, ce):
cmdnumargs(c, ce)
- return caller.hook_capabilities()
+ return caller.hook_capabilities() + ['execute-debug',
+ 'print-execute-command']
def cmd_quit(c, ce):
cmdnumargs(c, ce)
raise Quit(0, '')
-def execute_raw(what, instr, *popenargs, **popenargsk):
+def cmd_close(c, ce):
+ cmdnumargs(c, ce)
+ if not downtmp: bomb("`close' when not open")
+ cleanup()
+
+def cmd_print_execute_command(c, ce):
+ cmdnumargs(c, ce)
+ if not downtmp: bomb("`print-execute-command' when not open")
+ if hasattr(caller,'hook_callerexeccmd'):
+ (cl,kvl) = caller.hook_callerexeccmd()
+ else:
+ cl = down
+ kvl = ['shstring']
+ return [','.join(map(urllib.quote, cl))] + kvl
+
+def preexecfn():
+ caller.hook_forked_inchild()
+
+def execute_raw(what, instr, timeout, *popenargs, **popenargsk):
debug(" ++ %s" % string.join(popenargs[0]))
- sp = subprocess.Popen(*popenargs, **popenargsk)
+ sp = subprocess.Popen(preexec_fn=preexecfn, *popenargs, **popenargsk)
if instr is None: popenargsk['stdin'] = devnull_read
+ timeout_start(timeout)
(out, err) = sp.communicate(instr)
+ timeout_stop()
if err: bomb("%s unexpectedly produced stderr output `%s'" %
(what, err))
status = sp.wait()
return (status, out)
-def execute(cmd_string, cmd_list=[], downp=False, outp=False):
+def execute(cmd_string, cmd_list=[], downp=False, outp=False, timeout=0):
cmdl = cmd_string.split()
if downp: perhaps_down = down
cmd = cmdl + cmd_list
if len(perhaps_down): cmd = perhaps_down + [' '.join(cmd)]
- (status, out) = execute_raw(cmdl[0], None, cmd, stdout=stdout)
+ (status, out) = execute_raw(cmdl[0], None, timeout,
+ cmd, stdout=stdout)
if status: bomb("%s%s failed (exit status %d)" %
((downp and "(down) " or ""), cmdl[0], status))
downtmp = caller.hook_open()
return [downtmp]
-def cmd_reset(c, ce):
+def cmd_revert(c, ce):
cmdnumargs(c, ce)
- if not downtmp: bomb("`reset' when not open")
+ if not downtmp: bomb("`revert' when not open")
if not 'revert' in caller.hook_capabilities():
- bomb("`reset' when `revert' not advertised")
- caller.hook_reset()
+ bomb("`revert' when `revert' not advertised")
+ caller.hook_revert()
def down_python_script(gobody, functions=''):
# Many things are made much harder by the inability of
"import os\n"
"def setfd(fd,fnamee,write,mode=0666):\n"
" fname = urllib.unquote(fnamee)\n"
- " if write: rw = os.O_WRONLY|os.O_CREAT\n"
+ " if write: rw = os.O_WRONLY|os.O_CREAT|os.O_TRUNC\n"
" else: rw = os.O_RDONLY\n"
" nfd = os.open(fname, rw, mode)\n"
- " os.dup2(nfd,fd)\n"
+ " if fd >= 0: os.dup2(nfd,fd)\n"
+ functions +
"def go():\n" )
script += ( " os.environ['TMPDIR']= urllib.unquote('%s')\n" %
debug("+P ...\n"+script)
scripte = urllib.quote(script)
- cmdl = down + ['python','-c',
- "'import urllib; s = urllib.unquote(%s); exec s'" %
- ('"%s"' % scripte)]
+ cmdl = down + ["exec python -c 'import urllib; s = urllib.unquote(%s);"
+ " exec s'" % ('"%s"' % scripte)]
return cmdl
def cmd_execute(c, ce):
- cmdnumargs(c, ce, 5)
+ cmdnumargs(c, ce, 5, None)
+ debug_re = regexp.compile('debug=(\d+)\-(\d+)$')
+ debug_g = None
+ timeout = 0
+ envs = []
+ for kw in ce[6:]:
+ if kw.startswith('debug='):
+ if debug_g: bomb("multiple debug= in execute")
+ m = debug_re.match(kw)
+ if not m: bomb("invalid execute debug arg `%s'" % kw)
+ debug_g = m.groups()
+ elif kw.startswith('timeout='):
+ try: timeout = int(kw[8:],0)
+ except ValueError: bomb("invalid timeout arg `%s'" %kw)
+ elif kw.startswith('env='):
+ es = kw[4:]; eq = es.find('=')
+ if eq <= 0: bomb("invalid env arg `%s'" % kw)
+ envs.append((es[:eq], es[eq+1:]))
+ else: bomb("invalid execute kw arg `%s'" % kw)
+
gobody = " import sys\n"
+ stdout = None
+ tfd = None
+ if debug_g:
+ (tfd,hfd) = m.groups()
+ tfd = int(tfd)
+ gobody += " os.dup2(1,%d)\n" % tfd
+ stdout = int(hfd)
for ioe in range(3):
+ ioe_tfd = ioe
+ if ioe == tfd: ioe_tfd = -1
gobody += " setfd(%d,'%s',%d)\n" % (
- ioe, ce[ioe+2], ioe>0 )
+ ioe_tfd, ce[ioe+2], ioe>0 )
+ for e in envs:
+ gobody += (" os.environ[urllib.unquote('%s')]"
+ " = urllib.unquote('%s')\n"
+ % tuple(map(urllib.quote, e)))
gobody += " os.chdir(urllib.unquote('" + ce[5] +"'))\n"
gobody += " cmd = '%s'\n" % ce[1]
gobody += (" cmd = cmd.split(',')\n"
" mode = status.st_mode | 0111\n"
" os.chmod(c0, mode)\n"
" try: os.execvp(c0, cmd)\n"
- " except OSError, e:\n"
+ " except (IOError,OSError), e:\n"
" print >>sys.stderr, \"%s: %s\" % (\n"
" (c0, os.strerror(e.errno)))\n"
" os._exit(127)\n")
cmdl = down_python_script(gobody)
- (status, out) = execute_raw('sub-python', None, cmdl,
+ stdout_copy = None
+ try:
+ if type(stdout) == type(2): stdout_copy = os.dup(stdout)
+ try:
+ (status, out) = execute_raw('sub-python', None,
+ timeout, cmdl, stdout=stdout_copy,
stdin=devnull_read, stderr=subprocess.PIPE)
+ except Timeout:
+ raise FailedCmd(['timeout'])
+ finally:
+ if stdout_copy is not None: os.close(stdout_copy)
+
if out: bomb("sub-python unexpected produced stdout"
" visible to us `%s'" % out)
return [`status`]
localfd = None
deststdout = devnull_read
srcstdin = devnull_read
- preexecfns = [None, None]
if not dirsp:
modestr = ''
if upp:
gobody = " dir = urllib.unquote('%s')\n" % sde[iremote]
if upp:
try: os.mkdir(sd[ilocal])
- except OSError, oe:
+ except (IOError,OSError), oe:
if oe.errno != errno.EEXIST: raise
else:
gobody += (" try: os.mkdir(dir)\n"
- " except OSError, oe:\n"
+ " except (IOError,OSError), oe:\n"
" if oe.errno != errno.EEXIST: raise\n")
gobody +=( " os.chdir(dir)\n"
" tarcmd = 'tar -f -'.split()\n")
subprocs = [None,None]
debug(" +< %s" % string.join(cmdls[0]))
subprocs[0] = subprocess.Popen(cmdls[0], stdin=srcstdin,
- stdout=subprocess.PIPE, preexec_fn=preexecfns[0])
+ stdout=subprocess.PIPE, preexec_fn=preexecfn)
debug(" +> %s" % string.join(cmdls[1]))
subprocs[1] = subprocess.Popen(cmdls[1], stdin=subprocs[0].stdout,
- stdout=deststdout, preexec_fn=preexecfns[1])
+ stdout=deststdout, preexec_fn=preexecfn)
+ subprocs[0].stdout.close()
+ timeout_start(copy_timeout)
for sdn in [1,0]:
+ debug(" +"+"<>"[sdn]+"?");
status = subprocs[sdn].wait()
- if status: bomb("%s %s failed, status %d" %
- (wh, ['source','destination'][sdn], status))
+ if not (status==0 or (sdn==0 and status==-13)):
+ timeout_stop()
+ bomb("%s %s failed, status %d" %
+ (wh, ['source','destination'][sdn], status))
+ timeout_stop()
def cmd_copydown(c, ce): copyupdown(c, ce, False)
def cmd_copyup(c, ce): copyupdown(c, ce, True)
c = map(urllib.unquote, ce)
if not c: bomb('empty commands are not permitted')
debug('executing '+string.join(ce))
- try: f = globals()['cmd_'+c[0]]
+ c_lookup = c[0].replace('-','_')
+ try: f = globals()['cmd_'+c_lookup]
except KeyError: bomb("unknown command `%s'" % ce[0])
- r = f(c, ce)
- if not r: r = []
- r.insert(0, 'ok')
+ try:
+ r = f(c, ce)
+ if not r: r = []
+ r.insert(0, 'ok')
+ except FailedCmd, fc:
+ r = fc.e
print string.join(r)
+signal_list = [ signal.SIGHUP, signal.SIGTERM,
+ signal.SIGINT, signal.SIGPIPE ]
+
+def sethandlers(f):
+ for signum in signal_list: signal.signal(signum, f)
+
def cleanup():
global downtmp, cleaning
+ debug("cleanup...");
+ sethandlers(signal.SIG_DFL)
cleaning = True
if downtmp: caller.hook_cleanup()
cleaning = False
def prepare():
global downtmp, cleaning
downtmp = None
- signal_list = [ signal.SIGHUP, signal.SIGTERM,
- signal.SIGINT, signal.SIGPIPE ]
- def sethandlers(f):
- for signum in signal_list: signal.signal(signum, f)
def handler(sig, *any):
- sethandlers(signal.SIG_DFL)
cleanup()
os.kill(os.getpid(), sig)
sethandlers(handler)
sys.exit(16)
def main():
+ signal.signal(signal.SIGALRM, alarm_handler)
debug("down = %s" % string.join(down))
ok()
prepare()