1 # VirtSubproc is part of autopkgtest
2 # autopkgtest is a tool for testing Debian binary packages
4 # autopkgtest is Copyright (C) 2006 Canonical Ltd.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 # See the file CREDITS for a full list of credits information (often
21 # installed as /usr/share/doc/autopkgtest/CREDITS).
34 progname = "<VirtSubproc>"
35 devnull_read = file('/dev/null','r')
39 def __init__(q,ec,m): q.ec = ec; q.m = m
42 if not debuglevel: return
43 print >> sys.stderr, progname+": debug:", m
46 raise Quit(12, progname+": failure: %s" % m)
50 def cmdnumargs(c, ce, nargs=0):
51 if len(c) == nargs + 1: return
52 bomb("wrong number of arguments to command `%s'" % ce[0])
54 def cmd_capabilities(c, ce):
56 return caller.hook_capabilities()
64 if not downtmp: bomb("`close' when not open")
67 def execute_raw(what, instr, *popenargs, **popenargsk):
68 debug(" ++ %s" % string.join(popenargs[0]))
69 sp = subprocess.Popen(*popenargs, **popenargsk)
70 if instr is None: popenargsk['stdin'] = devnull_read
71 (out, err) = sp.communicate(instr)
72 if err: bomb("%s unexpectedly produced stderr output `%s'" %
77 def execute(cmd_string, cmd_list=[], downp=False, outp=False):
78 cmdl = cmd_string.split()
80 if downp: perhaps_down = down
83 if outp: stdout = subprocess.PIPE
87 if len(perhaps_down): cmd = perhaps_down + [' '.join(cmd)]
89 (status, out) = execute_raw(cmdl[0], None, cmd, stdout=stdout)
91 if status: bomb("%s%s failed (exit status %d)" %
92 ((downp and "(down) " or ""), cmdl[0], status))
94 if outp and out and out[-1]=='\n': out = out[:-1]
100 if downtmp: bomb("`open' when already open")
101 downtmp = caller.hook_open()
104 def cmd_reset(c, ce):
106 if not downtmp: bomb("`reset' when not open")
107 if not 'revert' in caller.hook_capabilities():
108 bomb("`reset' when `revert' not advertised")
111 def down_python_script(gobody, functions=''):
112 # Many things are made much harder by the inability of
113 # dchroot, ssh, et al, to cope without mangling the arguments.
114 # So we run a sub-python on the testbed and feed it a script
115 # on stdin. The sub-python decodes the arguments.
117 script = ( "import urllib\n"
119 "def setfd(fd,fnamee,write,mode=0666):\n"
120 " fname = urllib.unquote(fnamee)\n"
121 " if write: rw = os.O_WRONLY|os.O_CREAT\n"
122 " else: rw = os.O_RDONLY\n"
123 " nfd = os.open(fname, rw, mode)\n"
127 script += ( " os.environ['TMPDIR']= urllib.unquote('%s')\n" %
128 urllib.quote(downtmp) )
129 script += ( " os.chdir(os.environ['TMPDIR'])\n" )
133 debug("+P ...\n"+script)
135 scripte = urllib.quote(script)
136 cmdl = down + ['python','-c',
137 "'import urllib; s = urllib.unquote(%s); exec s'" %
141 def cmd_execute(c, ce):
143 gobody = " import sys\n"
145 gobody += " setfd(%d,'%s',%d)\n" % (
146 ioe, ce[ioe+2], ioe>0 )
147 gobody += " os.chdir(urllib.unquote('" + ce[5] +"'))\n"
148 gobody += " cmd = '%s'\n" % ce[1]
149 gobody += (" cmd = cmd.split(',')\n"
150 " cmd = map(urllib.unquote, cmd)\n"
153 " if not os.access(c0, os.X_OK):\n"
154 " status = os.stat(c0)\n"
155 " mode = status.st_mode | 0111\n"
156 " os.chmod(c0, mode)\n"
157 " try: os.execvp(c0, cmd)\n"
158 " except OSError, e:\n"
159 " print >>sys.stderr, \"%s: %s\" % (\n"
160 " (c0, os.strerror(e.errno)))\n"
162 cmdl = down_python_script(gobody)
164 (status, out) = execute_raw('sub-python', None, cmdl,
165 stdin=devnull_read, stderr=subprocess.PIPE)
166 if out: bomb("sub-python unexpected produced stdout"
167 " visible to us `%s'" % out)
170 def copyupdown(c, ce, upp):
179 if not sd[0] or not sd[1]:
180 bomb("%s paths must be nonempty" % wh)
181 dirsp = sd[0][-1]=='/'
182 functions = "import errno\n"
183 if dirsp != (sd[1][-1]=='/'):
184 bomb("% paths must agree about directoryness"
185 " (presence or absence of trailing /)" % wh)
187 deststdout = devnull_read
188 srcstdin = devnull_read
189 preexecfns = [None, None]
193 deststdout = file(sd[idst], 'w')
195 srcstdin = file(sd[isrc], 'r')
196 status = os.fstat(srcstdin.fileno())
197 if status.st_mode & 0111: modestr = ',0777'
198 gobody = " setfd(%s,'%s',%s%s)\n" % (
199 1-upp, sde[iremote], not upp, modestr)
200 gobody += " os.execvp('cat', ['cat'])\n"
203 gobody = " dir = urllib.unquote('%s')\n" % sde[iremote]
205 try: os.mkdir(sd[ilocal])
207 if oe.errno != errno.EEXIST: raise
209 gobody += (" try: os.mkdir(dir)\n"
210 " except OSError, oe:\n"
211 " if oe.errno != errno.EEXIST: raise\n")
212 gobody +=( " os.chdir(dir)\n"
213 " tarcmd = 'tar -f -'.split()\n")
214 localcmdl = 'tar -f -'.split()
215 taropts = [None, None]
216 taropts[isrc] = '-c .'
217 taropts[idst] = '-p -x --no-same-owner'
218 gobody += " tarcmd += '%s'.split()\n" % taropts[iremote]
219 localcmdl += ['-C',sd[ilocal]]
220 localcmdl += taropts[ilocal].split()
221 gobody += " os.execvp('tar', tarcmd)\n";
223 downcmdl = down_python_script(gobody, functions)
225 if upp: cmdls = (downcmdl, localcmdl)
226 else: cmdls = (localcmdl, downcmdl)
228 debug(`["cmdls", `cmdls`]`)
229 debug(`["srcstdin", `srcstdin`, "deststdout", `deststdout`, "devnull_read", devnull_read]`)
231 subprocs = [None,None]
232 debug(" +< %s" % string.join(cmdls[0]))
233 subprocs[0] = subprocess.Popen(cmdls[0], stdin=srcstdin,
234 stdout=subprocess.PIPE, preexec_fn=preexecfns[0])
235 debug(" +> %s" % string.join(cmdls[1]))
236 subprocs[1] = subprocess.Popen(cmdls[1], stdin=subprocs[0].stdout,
237 stdout=deststdout, preexec_fn=preexecfns[1])
239 status = subprocs[sdn].wait()
240 if status: bomb("%s %s failed, status %d" %
241 (wh, ['source','destination'][sdn], status))
243 def cmd_copydown(c, ce): copyupdown(c, ce, False)
244 def cmd_copyup(c, ce): copyupdown(c, ce, True)
248 ce = sys.stdin.readline()
249 if not ce: bomb('end of file - caller quit?')
250 ce = ce.rstrip().split()
251 c = map(urllib.unquote, ce)
252 if not c: bomb('empty commands are not permitted')
253 debug('executing '+string.join(ce))
254 try: f = globals()['cmd_'+c[0]]
255 except KeyError: bomb("unknown command `%s'" % ce[0])
262 global downtmp, cleaning
264 if downtmp: caller.hook_cleanup()
275 print >> sys.stderr, q.m
277 print >> sys.stderr, "Unexpected cleanup error:"
278 traceback.print_exc()
279 print >> sys.stderr, ''
281 print >> sys.stderr, ("while cleaning up"
282 " because of another error:")
287 global downtmp, cleaning
289 signal_list = [ signal.SIGHUP, signal.SIGTERM,
290 signal.SIGINT, signal.SIGPIPE ]
292 for signum in signal_list: signal.signal(signum, f)
293 def handler(sig, *any):
294 sethandlers(signal.SIG_DFL)
296 os.kill(os.getpid(), sig)
301 while True: command()
304 if q.m: print >> sys.stderr, q.m
308 print >> sys.stderr, "Unexpected error:"
309 traceback.print_exc()
313 debug("down = %s" % string.join(down))