diff -urN /src/buildbot-slave-0.8.6p1.orig/buildslave/runprocess.py /src/buildbot-slave-0.8.6p1/buildslave/runprocess.py --- buildbot-slave-0.8.6p1.orig/buildslave/runprocess.py 2012-03-26 04:09:10 +0400 +++ buildbot-slave-0.8.6p1/buildslave/runprocess.py 2013-03-31 05:18:55 +0400 @@ -24,6 +24,7 @@ import re import subprocess import traceback +import tempfile import stat from collections import deque @@ -36,6 +37,89 @@ if runtime.platformType == 'posix': from twisted.internet.process import Process +if os.name == 'nt': + import win32api + import win32process + import win32event + import pywintypes + +def safe_terminate_process (proc, code): + if os.name == 'nt': + log.msg ("Obtaining current process handle") + cp = win32api.GetCurrentProcess () + result = False + log.msg ("Expanding target process handle permissions") + dupproc = win32api.DuplicateHandle (cp, proc._handle, cp, 2 | 1024 | 8 | 32 | 16 | 0x100000, 0, 0) + log.msg ("Expanded.") + try: + log.msg ("Checking exit code of target process") + exitcode = win32process.GetExitCodeProcess (dupproc) + log.msg ("Exit code is %d" % exitcode) + if exitcode == 0x103: + log.msg ("Opening kernel32.dll") + kernel32 = win32api.GetModuleHandle ("kernel32") + log.msg ("Getting ExitProcess() address") + exitprocess = win32api.GetProcAddress (kernel32, "ExitProcess") + try: + log.msg ("Creating remote thread") + th = 0 + tid = 0 + failed = False + th, tid = win32process.CreateRemoteThread (dupproc, None, 0, exitprocess, code, 0) + log.msg ("Created remote thread %d" % tid) + except pywintypes.error as e: + if e[0] == 5: + log.msg ("Access denied. It still might die, so don't fail yet") + pass + else: + log.msg("exception %s - %s" % (sys.exc_info()[0], sys.exc_info()[1])) + failed = True + except Exception as e: + log.msg("exception %s - %s" % (sys.exc_info()[0], sys.exc_info()[1])) + failed = True + if not failed: + log.msg ("Wait for 5 seconds or until it dies (usually takes around 1 microsecond)") + waitresult = win32event.WaitForSingleObject (dupproc, 5) + log.msg ("Result of waiting: %d" % waitresult) + win32api.CloseHandle (th) + if waitresult == 0: + result = True + else: + result = True + except: + log.msg("exception %s - %s" % (sys.exc_info()[0], sys.exc_info()[1])) + finally: + win32api.CloseHandle (dupproc) + return result + else: + return proc.kill () + +class Dummy(object): + def SetHandle (self, h): + self._handle = h + +def safe_terminate_process_by_pid (proc, code): + if os.name == 'nt': + try: + log.msg("Opening process %d" % proc) + openproc = win32api.OpenProcess (2 | 1024 | 8 | 32 | 16 | 0x100000, 0, proc) + log.msg("Opened process %d" % proc) + try: + d = Dummy () + d.SetHandle (openproc) + log.msg("Terminating it safely") + safe_terminate_process (d, code) + log.msg("Finished terminating") + finally: + log.msg("Closing process handle") + win32api.CloseHandle (openproc) + except: + log.msg("exception %s - %s" % (sys.exc_info()[0], sys.exc_info()[1])) + pass + else: + return os.kill (proc, code) + + def shell_quote(cmd_list): # attempt to quote cmd_list such that a shell will properly re-interpret # it. The pipes module is only available on UNIX, and Windows "shell" @@ -148,6 +232,7 @@ self.pending_stdin = "" self.stdin_finished = False self.killed = False + self.scriptfile = "" def setStdin(self, data): assert not self.connected @@ -198,6 +283,11 @@ rc = 1 else: rc = -1 + if self.scriptfile: + try: + os.remove (self.scriptfile) + except: + pass self.command.finished(sig, rc) @@ -408,9 +498,14 @@ if type(self.command) in types.StringTypes: if runtime.platformType == 'win32': - argv = os.environ['COMSPEC'].split() # allow %COMSPEC% to have args - if '/c' not in argv: argv += ['/c'] - argv += [self.command] + if os.environ['BUILDSLAVE_SHELL']: + argv = os.environ['BUILDSLAVE_SHELL'].split() # allow %COMSPEC% to have args + argv += [self.command] + else: + argv = os.environ['COMSPEC'].split() # allow %COMSPEC% to have args + if '/c' not in argv: + argv += ['/c'] + argv += [self.command] else: # for posix, use /bin/sh. for other non-posix, well, doesn't # hurt to try @@ -424,9 +519,26 @@ # handle path searching, etc. if runtime.platformType == 'win32' and not \ (self.command[0].lower().endswith(".exe") and os.path.isabs(self.command[0])): - argv = os.environ['COMSPEC'].split() # allow %COMSPEC% to have args - if '/c' not in argv: argv += ['/c'] - argv += list(self.command) + if os.environ['BUILDSLAVE_SHELL']: + argv = os.environ['BUILDSLAVE_SHELL'].split() + # Create a temporary script file that changes current directory + # and runs the command we want + # It will be deleted after command is finished running (see RunProcessPP) + tf, tf_name = tempfile.mkstemp () + f = os.fdopen (tf, 'wb') + fcontents = '#!/bin/sh\ncd {}\n{}'.format ( + re.sub(r'(?