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 |