OLD | NEW |
(Empty) | |
| 1 # A module to expose various thread/process/job related structures and |
| 2 # methods from kernel32 |
| 3 # |
| 4 # The MIT License |
| 5 # |
| 6 # Copyright (c) 2003-2004 by Peter Astrand <astrand@lysator.liu.se> |
| 7 # |
| 8 # Additions and modifications written by Benjamin Smedberg |
| 9 # <benjamin@smedbergs.us> are Copyright (c) 2006 by the Mozilla Foundation |
| 10 # <http://www.mozilla.org/> |
| 11 # |
| 12 # More Modifications |
| 13 # Copyright (c) 2006-2007 by Mike Taylor <bear@code-bear.com> |
| 14 # Copyright (c) 2007-2008 by Mikeal Rogers <mikeal@mozilla.com> |
| 15 # |
| 16 # By obtaining, using, and/or copying this software and/or its |
| 17 # associated documentation, you agree that you have read, understood, |
| 18 # and will comply with the following terms and conditions: |
| 19 # |
| 20 # Permission to use, copy, modify, and distribute this software and |
| 21 # its associated documentation for any purpose and without fee is |
| 22 # hereby granted, provided that the above copyright notice appears in |
| 23 # all copies, and that both that copyright notice and this permission |
| 24 # notice appear in supporting documentation, and that the name of the |
| 25 # author not be used in advertising or publicity pertaining to |
| 26 # distribution of the software without specific, written prior |
| 27 # permission. |
| 28 # |
| 29 # THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, |
| 30 # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. |
| 31 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR |
| 32 # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS |
| 33 # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, |
| 34 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION |
| 35 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| 36 |
| 37 from ctypes import c_void_p, POINTER, sizeof, Structure, windll, WinError, WINFU
NCTYPE |
| 38 from ctypes.wintypes import BOOL, BYTE, DWORD, HANDLE, LPCWSTR, LPWSTR, UINT, WO
RD |
| 39 from qijo import QueryInformationJobObject |
| 40 |
| 41 LPVOID = c_void_p |
| 42 LPBYTE = POINTER(BYTE) |
| 43 LPDWORD = POINTER(DWORD) |
| 44 LPBOOL = POINTER(BOOL) |
| 45 |
| 46 def ErrCheckBool(result, func, args): |
| 47 """errcheck function for Windows functions that return a BOOL True |
| 48 on success""" |
| 49 if not result: |
| 50 raise WinError() |
| 51 return args |
| 52 |
| 53 |
| 54 # AutoHANDLE |
| 55 |
| 56 class AutoHANDLE(HANDLE): |
| 57 """Subclass of HANDLE which will call CloseHandle() on deletion.""" |
| 58 |
| 59 CloseHandleProto = WINFUNCTYPE(BOOL, HANDLE) |
| 60 CloseHandle = CloseHandleProto(("CloseHandle", windll.kernel32)) |
| 61 CloseHandle.errcheck = ErrCheckBool |
| 62 |
| 63 def Close(self): |
| 64 if self.value and self.value != HANDLE(-1).value: |
| 65 self.CloseHandle(self) |
| 66 self.value = 0 |
| 67 |
| 68 def __del__(self): |
| 69 self.Close() |
| 70 |
| 71 def __int__(self): |
| 72 return self.value |
| 73 |
| 74 def ErrCheckHandle(result, func, args): |
| 75 """errcheck function for Windows functions that return a HANDLE.""" |
| 76 if not result: |
| 77 raise WinError() |
| 78 return AutoHANDLE(result) |
| 79 |
| 80 # PROCESS_INFORMATION structure |
| 81 |
| 82 class PROCESS_INFORMATION(Structure): |
| 83 _fields_ = [("hProcess", HANDLE), |
| 84 ("hThread", HANDLE), |
| 85 ("dwProcessID", DWORD), |
| 86 ("dwThreadID", DWORD)] |
| 87 |
| 88 def __init__(self): |
| 89 Structure.__init__(self) |
| 90 |
| 91 self.cb = sizeof(self) |
| 92 |
| 93 LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION) |
| 94 |
| 95 # STARTUPINFO structure |
| 96 |
| 97 class STARTUPINFO(Structure): |
| 98 _fields_ = [("cb", DWORD), |
| 99 ("lpReserved", LPWSTR), |
| 100 ("lpDesktop", LPWSTR), |
| 101 ("lpTitle", LPWSTR), |
| 102 ("dwX", DWORD), |
| 103 ("dwY", DWORD), |
| 104 ("dwXSize", DWORD), |
| 105 ("dwYSize", DWORD), |
| 106 ("dwXCountChars", DWORD), |
| 107 ("dwYCountChars", DWORD), |
| 108 ("dwFillAttribute", DWORD), |
| 109 ("dwFlags", DWORD), |
| 110 ("wShowWindow", WORD), |
| 111 ("cbReserved2", WORD), |
| 112 ("lpReserved2", LPBYTE), |
| 113 ("hStdInput", HANDLE), |
| 114 ("hStdOutput", HANDLE), |
| 115 ("hStdError", HANDLE) |
| 116 ] |
| 117 LPSTARTUPINFO = POINTER(STARTUPINFO) |
| 118 |
| 119 SW_HIDE = 0 |
| 120 |
| 121 STARTF_USESHOWWINDOW = 0x01 |
| 122 STARTF_USESIZE = 0x02 |
| 123 STARTF_USEPOSITION = 0x04 |
| 124 STARTF_USECOUNTCHARS = 0x08 |
| 125 STARTF_USEFILLATTRIBUTE = 0x10 |
| 126 STARTF_RUNFULLSCREEN = 0x20 |
| 127 STARTF_FORCEONFEEDBACK = 0x40 |
| 128 STARTF_FORCEOFFFEEDBACK = 0x80 |
| 129 STARTF_USESTDHANDLES = 0x100 |
| 130 |
| 131 # EnvironmentBlock |
| 132 |
| 133 class EnvironmentBlock: |
| 134 """An object which can be passed as the lpEnv parameter of CreateProcess. |
| 135 It is initialized with a dictionary.""" |
| 136 |
| 137 def __init__(self, dict): |
| 138 if not dict: |
| 139 self._as_parameter_ = None |
| 140 else: |
| 141 values = ["%s=%s" % (key, value) |
| 142 for (key, value) in dict.iteritems()] |
| 143 values.append("") |
| 144 self._as_parameter_ = LPCWSTR("\0".join(values)) |
| 145 |
| 146 # CreateProcess() |
| 147 |
| 148 CreateProcessProto = WINFUNCTYPE(BOOL, # Return type |
| 149 LPCWSTR, # lpApplicationName |
| 150 LPWSTR, # lpCommandLine |
| 151 LPVOID, # lpProcessAttributes |
| 152 LPVOID, # lpThreadAttributes |
| 153 BOOL, # bInheritHandles |
| 154 DWORD, # dwCreationFlags |
| 155 LPVOID, # lpEnvironment |
| 156 LPCWSTR, # lpCurrentDirectory |
| 157 LPSTARTUPINFO, # lpStartupInfo |
| 158 LPPROCESS_INFORMATION # lpProcessInformation |
| 159 ) |
| 160 |
| 161 CreateProcessFlags = ((1, "lpApplicationName", None), |
| 162 (1, "lpCommandLine"), |
| 163 (1, "lpProcessAttributes", None), |
| 164 (1, "lpThreadAttributes", None), |
| 165 (1, "bInheritHandles", True), |
| 166 (1, "dwCreationFlags", 0), |
| 167 (1, "lpEnvironment", None), |
| 168 (1, "lpCurrentDirectory", None), |
| 169 (1, "lpStartupInfo"), |
| 170 (2, "lpProcessInformation")) |
| 171 |
| 172 def ErrCheckCreateProcess(result, func, args): |
| 173 ErrCheckBool(result, func, args) |
| 174 # return a tuple (hProcess, hThread, dwProcessID, dwThreadID) |
| 175 pi = args[9] |
| 176 return AutoHANDLE(pi.hProcess), AutoHANDLE(pi.hThread), pi.dwProcessID, pi.d
wThreadID |
| 177 |
| 178 CreateProcess = CreateProcessProto(("CreateProcessW", windll.kernel32), |
| 179 CreateProcessFlags) |
| 180 CreateProcess.errcheck = ErrCheckCreateProcess |
| 181 |
| 182 # flags for CreateProcess |
| 183 CREATE_BREAKAWAY_FROM_JOB = 0x01000000 |
| 184 CREATE_DEFAULT_ERROR_MODE = 0x04000000 |
| 185 CREATE_NEW_CONSOLE = 0x00000010 |
| 186 CREATE_NEW_PROCESS_GROUP = 0x00000200 |
| 187 CREATE_NO_WINDOW = 0x08000000 |
| 188 CREATE_SUSPENDED = 0x00000004 |
| 189 CREATE_UNICODE_ENVIRONMENT = 0x00000400 |
| 190 |
| 191 # flags for job limit information |
| 192 # see http://msdn.microsoft.com/en-us/library/ms684147%28VS.85%29.aspx |
| 193 JOB_OBJECT_LIMIT_BREAKAWAY_OK = 0x00000800 |
| 194 JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK = 0x00001000 |
| 195 |
| 196 # XXX these flags should be documented |
| 197 DEBUG_ONLY_THIS_PROCESS = 0x00000002 |
| 198 DEBUG_PROCESS = 0x00000001 |
| 199 DETACHED_PROCESS = 0x00000008 |
| 200 |
| 201 # CreateJobObject() |
| 202 |
| 203 CreateJobObjectProto = WINFUNCTYPE(HANDLE, # Return type |
| 204 LPVOID, # lpJobAttributes |
| 205 LPCWSTR # lpName |
| 206 ) |
| 207 |
| 208 CreateJobObjectFlags = ((1, "lpJobAttributes", None), |
| 209 (1, "lpName", None)) |
| 210 |
| 211 CreateJobObject = CreateJobObjectProto(("CreateJobObjectW", windll.kernel32), |
| 212 CreateJobObjectFlags) |
| 213 CreateJobObject.errcheck = ErrCheckHandle |
| 214 |
| 215 # AssignProcessToJobObject() |
| 216 |
| 217 AssignProcessToJobObjectProto = WINFUNCTYPE(BOOL, # Return type |
| 218 HANDLE, # hJob |
| 219 HANDLE # hProcess |
| 220 ) |
| 221 AssignProcessToJobObjectFlags = ((1, "hJob"), |
| 222 (1, "hProcess")) |
| 223 AssignProcessToJobObject = AssignProcessToJobObjectProto( |
| 224 ("AssignProcessToJobObject", windll.kernel32), |
| 225 AssignProcessToJobObjectFlags) |
| 226 AssignProcessToJobObject.errcheck = ErrCheckBool |
| 227 |
| 228 # GetCurrentProcess() |
| 229 # because os.getPid() is way too easy |
| 230 GetCurrentProcessProto = WINFUNCTYPE(HANDLE # Return type |
| 231 ) |
| 232 GetCurrentProcessFlags = () |
| 233 GetCurrentProcess = GetCurrentProcessProto( |
| 234 ("GetCurrentProcess", windll.kernel32), |
| 235 GetCurrentProcessFlags) |
| 236 GetCurrentProcess.errcheck = ErrCheckHandle |
| 237 |
| 238 # IsProcessInJob() |
| 239 try: |
| 240 IsProcessInJobProto = WINFUNCTYPE(BOOL, # Return type |
| 241 HANDLE, # Process Handle |
| 242 HANDLE, # Job Handle |
| 243 LPBOOL # Result |
| 244 ) |
| 245 IsProcessInJobFlags = ((1, "ProcessHandle"), |
| 246 (1, "JobHandle", HANDLE(0)), |
| 247 (2, "Result")) |
| 248 IsProcessInJob = IsProcessInJobProto( |
| 249 ("IsProcessInJob", windll.kernel32), |
| 250 IsProcessInJobFlags) |
| 251 IsProcessInJob.errcheck = ErrCheckBool |
| 252 except AttributeError: |
| 253 # windows 2k doesn't have this API |
| 254 def IsProcessInJob(process): |
| 255 return False |
| 256 |
| 257 |
| 258 # ResumeThread() |
| 259 |
| 260 def ErrCheckResumeThread(result, func, args): |
| 261 if result == -1: |
| 262 raise WinError() |
| 263 |
| 264 return args |
| 265 |
| 266 ResumeThreadProto = WINFUNCTYPE(DWORD, # Return type |
| 267 HANDLE # hThread |
| 268 ) |
| 269 ResumeThreadFlags = ((1, "hThread"),) |
| 270 ResumeThread = ResumeThreadProto(("ResumeThread", windll.kernel32), |
| 271 ResumeThreadFlags) |
| 272 ResumeThread.errcheck = ErrCheckResumeThread |
| 273 |
| 274 # TerminateProcess() |
| 275 |
| 276 TerminateProcessProto = WINFUNCTYPE(BOOL, # Return type |
| 277 HANDLE, # hProcess |
| 278 UINT # uExitCode |
| 279 ) |
| 280 TerminateProcessFlags = ((1, "hProcess"), |
| 281 (1, "uExitCode", 127)) |
| 282 TerminateProcess = TerminateProcessProto( |
| 283 ("TerminateProcess", windll.kernel32), |
| 284 TerminateProcessFlags) |
| 285 TerminateProcess.errcheck = ErrCheckBool |
| 286 |
| 287 # TerminateJobObject() |
| 288 |
| 289 TerminateJobObjectProto = WINFUNCTYPE(BOOL, # Return type |
| 290 HANDLE, # hJob |
| 291 UINT # uExitCode |
| 292 ) |
| 293 TerminateJobObjectFlags = ((1, "hJob"), |
| 294 (1, "uExitCode", 127)) |
| 295 TerminateJobObject = TerminateJobObjectProto( |
| 296 ("TerminateJobObject", windll.kernel32), |
| 297 TerminateJobObjectFlags) |
| 298 TerminateJobObject.errcheck = ErrCheckBool |
| 299 |
| 300 # WaitForSingleObject() |
| 301 |
| 302 WaitForSingleObjectProto = WINFUNCTYPE(DWORD, # Return type |
| 303 HANDLE, # hHandle |
| 304 DWORD, # dwMilliseconds |
| 305 ) |
| 306 WaitForSingleObjectFlags = ((1, "hHandle"), |
| 307 (1, "dwMilliseconds", -1)) |
| 308 WaitForSingleObject = WaitForSingleObjectProto( |
| 309 ("WaitForSingleObject", windll.kernel32), |
| 310 WaitForSingleObjectFlags) |
| 311 |
| 312 INFINITE = -1 |
| 313 WAIT_TIMEOUT = 0x0102 |
| 314 WAIT_OBJECT_0 = 0x0 |
| 315 WAIT_ABANDONED = 0x0080 |
| 316 |
| 317 # GetExitCodeProcess() |
| 318 |
| 319 GetExitCodeProcessProto = WINFUNCTYPE(BOOL, # Return type |
| 320 HANDLE, # hProcess |
| 321 LPDWORD, # lpExitCode |
| 322 ) |
| 323 GetExitCodeProcessFlags = ((1, "hProcess"), |
| 324 (2, "lpExitCode")) |
| 325 GetExitCodeProcess = GetExitCodeProcessProto( |
| 326 ("GetExitCodeProcess", windll.kernel32), |
| 327 GetExitCodeProcessFlags) |
| 328 GetExitCodeProcess.errcheck = ErrCheckBool |
| 329 |
| 330 def CanCreateJobObject(): |
| 331 currentProc = GetCurrentProcess() |
| 332 if IsProcessInJob(currentProc): |
| 333 jobinfo = QueryInformationJobObject(HANDLE(0), 'JobObjectExtendedLimitIn
formation') |
| 334 limitflags = jobinfo['BasicLimitInformation']['LimitFlags'] |
| 335 return bool(limitflags & JOB_OBJECT_LIMIT_BREAKAWAY_OK) or bool(limitfla
gs & JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK) |
| 336 else: |
| 337 return True |
| 338 |
| 339 ### testing functions |
| 340 |
| 341 def parent(): |
| 342 print 'Starting parent' |
| 343 currentProc = GetCurrentProcess() |
| 344 if IsProcessInJob(currentProc): |
| 345 print >> sys.stderr, "You should not be in a job object to test" |
| 346 sys.exit(1) |
| 347 assert CanCreateJobObject() |
| 348 print 'File: %s' % __file__ |
| 349 command = [sys.executable, __file__, '-child'] |
| 350 print 'Running command: %s' % command |
| 351 process = Popen(command) |
| 352 process.kill() |
| 353 code = process.returncode |
| 354 print 'Child code: %s' % code |
| 355 assert code == 127 |
| 356 |
| 357 def child(): |
| 358 print 'Starting child' |
| 359 currentProc = GetCurrentProcess() |
| 360 injob = IsProcessInJob(currentProc) |
| 361 print "Is in a job?: %s" % injob |
| 362 can_create = CanCreateJobObject() |
| 363 print 'Can create job?: %s' % can_create |
| 364 process = Popen('c:\\windows\\notepad.exe') |
| 365 assert process._job |
| 366 jobinfo = QueryInformationJobObject(process._job, 'JobObjectExtendedLimitInf
ormation') |
| 367 print 'Job info: %s' % jobinfo |
| 368 limitflags = jobinfo['BasicLimitInformation']['LimitFlags'] |
| 369 print 'LimitFlags: %s' % limitflags |
| 370 process.kill() |
| 371 |
| 372 if __name__ == '__main__': |
| 373 import sys |
| 374 from killableprocess import Popen |
| 375 nargs = len(sys.argv[1:]) |
| 376 if nargs: |
| 377 if nargs != 1 or sys.argv[1] != '-child': |
| 378 raise AssertionError('Wrong flags; run like `python /path/to/winproc
ess.py`') |
| 379 child() |
| 380 else: |
| 381 parent() |
OLD | NEW |