|
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, WINFUNCTYPE |
|
38 from ctypes.wintypes import BOOL, BYTE, DWORD, HANDLE, LPCWSTR, LPWSTR, UINT, WORD, \ |
|
39 c_buffer, c_ulong, byref |
|
40 from qijo import QueryInformationJobObject |
|
41 |
|
42 LPVOID = c_void_p |
|
43 LPBYTE = POINTER(BYTE) |
|
44 LPDWORD = POINTER(DWORD) |
|
45 LPBOOL = POINTER(BOOL) |
|
46 |
|
47 def ErrCheckBool(result, func, args): |
|
48 """errcheck function for Windows functions that return a BOOL True |
|
49 on success""" |
|
50 if not result: |
|
51 raise WinError() |
|
52 return args |
|
53 |
|
54 |
|
55 # AutoHANDLE |
|
56 |
|
57 class AutoHANDLE(HANDLE): |
|
58 """Subclass of HANDLE which will call CloseHandle() on deletion.""" |
|
59 |
|
60 CloseHandleProto = WINFUNCTYPE(BOOL, HANDLE) |
|
61 CloseHandle = CloseHandleProto(("CloseHandle", windll.kernel32)) |
|
62 CloseHandle.errcheck = ErrCheckBool |
|
63 |
|
64 def Close(self): |
|
65 if self.value and self.value != HANDLE(-1).value: |
|
66 self.CloseHandle(self) |
|
67 self.value = 0 |
|
68 |
|
69 def __del__(self): |
|
70 self.Close() |
|
71 |
|
72 def __int__(self): |
|
73 return self.value |
|
74 |
|
75 def ErrCheckHandle(result, func, args): |
|
76 """errcheck function for Windows functions that return a HANDLE.""" |
|
77 if not result: |
|
78 raise WinError() |
|
79 return AutoHANDLE(result) |
|
80 |
|
81 # PROCESS_INFORMATION structure |
|
82 |
|
83 class PROCESS_INFORMATION(Structure): |
|
84 _fields_ = [("hProcess", HANDLE), |
|
85 ("hThread", HANDLE), |
|
86 ("dwProcessID", DWORD), |
|
87 ("dwThreadID", DWORD)] |
|
88 |
|
89 def __init__(self): |
|
90 Structure.__init__(self) |
|
91 |
|
92 self.cb = sizeof(self) |
|
93 |
|
94 LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION) |
|
95 |
|
96 # STARTUPINFO structure |
|
97 |
|
98 class STARTUPINFO(Structure): |
|
99 _fields_ = [("cb", DWORD), |
|
100 ("lpReserved", LPWSTR), |
|
101 ("lpDesktop", LPWSTR), |
|
102 ("lpTitle", LPWSTR), |
|
103 ("dwX", DWORD), |
|
104 ("dwY", DWORD), |
|
105 ("dwXSize", DWORD), |
|
106 ("dwYSize", DWORD), |
|
107 ("dwXCountChars", DWORD), |
|
108 ("dwYCountChars", DWORD), |
|
109 ("dwFillAttribute", DWORD), |
|
110 ("dwFlags", DWORD), |
|
111 ("wShowWindow", WORD), |
|
112 ("cbReserved2", WORD), |
|
113 ("lpReserved2", LPBYTE), |
|
114 ("hStdInput", HANDLE), |
|
115 ("hStdOutput", HANDLE), |
|
116 ("hStdError", HANDLE) |
|
117 ] |
|
118 LPSTARTUPINFO = POINTER(STARTUPINFO) |
|
119 |
|
120 SW_HIDE = 0 |
|
121 |
|
122 STARTF_USESHOWWINDOW = 0x01 |
|
123 STARTF_USESIZE = 0x02 |
|
124 STARTF_USEPOSITION = 0x04 |
|
125 STARTF_USECOUNTCHARS = 0x08 |
|
126 STARTF_USEFILLATTRIBUTE = 0x10 |
|
127 STARTF_RUNFULLSCREEN = 0x20 |
|
128 STARTF_FORCEONFEEDBACK = 0x40 |
|
129 STARTF_FORCEOFFFEEDBACK = 0x80 |
|
130 STARTF_USESTDHANDLES = 0x100 |
|
131 |
|
132 # EnvironmentBlock |
|
133 |
|
134 class EnvironmentBlock: |
|
135 """An object which can be passed as the lpEnv parameter of CreateProcess. |
|
136 It is initialized with a dictionary.""" |
|
137 |
|
138 def __init__(self, dict): |
|
139 if not dict: |
|
140 self._as_parameter_ = None |
|
141 else: |
|
142 values = ["%s=%s" % (key, value) |
|
143 for (key, value) in dict.iteritems()] |
|
144 values.append("") |
|
145 self._as_parameter_ = LPCWSTR("\0".join(values)) |
|
146 |
|
147 # CreateProcess() |
|
148 |
|
149 CreateProcessProto = WINFUNCTYPE(BOOL, # Return type |
|
150 LPCWSTR, # lpApplicationName |
|
151 LPWSTR, # lpCommandLine |
|
152 LPVOID, # lpProcessAttributes |
|
153 LPVOID, # lpThreadAttributes |
|
154 BOOL, # bInheritHandles |
|
155 DWORD, # dwCreationFlags |
|
156 LPVOID, # lpEnvironment |
|
157 LPCWSTR, # lpCurrentDirectory |
|
158 LPSTARTUPINFO, # lpStartupInfo |
|
159 LPPROCESS_INFORMATION # lpProcessInformation |
|
160 ) |
|
161 |
|
162 CreateProcessFlags = ((1, "lpApplicationName", None), |
|
163 (1, "lpCommandLine"), |
|
164 (1, "lpProcessAttributes", None), |
|
165 (1, "lpThreadAttributes", None), |
|
166 (1, "bInheritHandles", True), |
|
167 (1, "dwCreationFlags", 0), |
|
168 (1, "lpEnvironment", None), |
|
169 (1, "lpCurrentDirectory", None), |
|
170 (1, "lpStartupInfo"), |
|
171 (2, "lpProcessInformation")) |
|
172 |
|
173 def ErrCheckCreateProcess(result, func, args): |
|
174 ErrCheckBool(result, func, args) |
|
175 # return a tuple (hProcess, hThread, dwProcessID, dwThreadID) |
|
176 pi = args[9] |
|
177 return AutoHANDLE(pi.hProcess), AutoHANDLE(pi.hThread), pi.dwProcessID, pi.dwThreadID |
|
178 |
|
179 CreateProcess = CreateProcessProto(("CreateProcessW", windll.kernel32), |
|
180 CreateProcessFlags) |
|
181 CreateProcess.errcheck = ErrCheckCreateProcess |
|
182 |
|
183 # flags for CreateProcess |
|
184 CREATE_BREAKAWAY_FROM_JOB = 0x01000000 |
|
185 CREATE_DEFAULT_ERROR_MODE = 0x04000000 |
|
186 CREATE_NEW_CONSOLE = 0x00000010 |
|
187 CREATE_NEW_PROCESS_GROUP = 0x00000200 |
|
188 CREATE_NO_WINDOW = 0x08000000 |
|
189 CREATE_SUSPENDED = 0x00000004 |
|
190 CREATE_UNICODE_ENVIRONMENT = 0x00000400 |
|
191 |
|
192 # flags for job limit information |
|
193 # see http://msdn.microsoft.com/en-us/library/ms684147%28VS.85%29.aspx |
|
194 JOB_OBJECT_LIMIT_BREAKAWAY_OK = 0x00000800 |
|
195 JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK = 0x00001000 |
|
196 |
|
197 # XXX these flags should be documented |
|
198 DEBUG_ONLY_THIS_PROCESS = 0x00000002 |
|
199 DEBUG_PROCESS = 0x00000001 |
|
200 DETACHED_PROCESS = 0x00000008 |
|
201 |
|
202 # CreateJobObject() |
|
203 |
|
204 CreateJobObjectProto = WINFUNCTYPE(HANDLE, # Return type |
|
205 LPVOID, # lpJobAttributes |
|
206 LPCWSTR # lpName |
|
207 ) |
|
208 |
|
209 CreateJobObjectFlags = ((1, "lpJobAttributes", None), |
|
210 (1, "lpName", None)) |
|
211 |
|
212 CreateJobObject = CreateJobObjectProto(("CreateJobObjectW", windll.kernel32), |
|
213 CreateJobObjectFlags) |
|
214 CreateJobObject.errcheck = ErrCheckHandle |
|
215 |
|
216 # AssignProcessToJobObject() |
|
217 |
|
218 AssignProcessToJobObjectProto = WINFUNCTYPE(BOOL, # Return type |
|
219 HANDLE, # hJob |
|
220 HANDLE # hProcess |
|
221 ) |
|
222 AssignProcessToJobObjectFlags = ((1, "hJob"), |
|
223 (1, "hProcess")) |
|
224 AssignProcessToJobObject = AssignProcessToJobObjectProto( |
|
225 ("AssignProcessToJobObject", windll.kernel32), |
|
226 AssignProcessToJobObjectFlags) |
|
227 AssignProcessToJobObject.errcheck = ErrCheckBool |
|
228 |
|
229 # GetCurrentProcess() |
|
230 # because os.getPid() is way too easy |
|
231 GetCurrentProcessProto = WINFUNCTYPE(HANDLE # Return type |
|
232 ) |
|
233 GetCurrentProcessFlags = () |
|
234 GetCurrentProcess = GetCurrentProcessProto( |
|
235 ("GetCurrentProcess", windll.kernel32), |
|
236 GetCurrentProcessFlags) |
|
237 GetCurrentProcess.errcheck = ErrCheckHandle |
|
238 |
|
239 # IsProcessInJob() |
|
240 try: |
|
241 IsProcessInJobProto = WINFUNCTYPE(BOOL, # Return type |
|
242 HANDLE, # Process Handle |
|
243 HANDLE, # Job Handle |
|
244 LPBOOL # Result |
|
245 ) |
|
246 IsProcessInJobFlags = ((1, "ProcessHandle"), |
|
247 (1, "JobHandle", HANDLE(0)), |
|
248 (2, "Result")) |
|
249 IsProcessInJob = IsProcessInJobProto( |
|
250 ("IsProcessInJob", windll.kernel32), |
|
251 IsProcessInJobFlags) |
|
252 IsProcessInJob.errcheck = ErrCheckBool |
|
253 except AttributeError: |
|
254 # windows 2k doesn't have this API |
|
255 def IsProcessInJob(process): |
|
256 return False |
|
257 |
|
258 |
|
259 # ResumeThread() |
|
260 |
|
261 def ErrCheckResumeThread(result, func, args): |
|
262 if result == -1: |
|
263 raise WinError() |
|
264 |
|
265 return args |
|
266 |
|
267 ResumeThreadProto = WINFUNCTYPE(DWORD, # Return type |
|
268 HANDLE # hThread |
|
269 ) |
|
270 ResumeThreadFlags = ((1, "hThread"),) |
|
271 ResumeThread = ResumeThreadProto(("ResumeThread", windll.kernel32), |
|
272 ResumeThreadFlags) |
|
273 ResumeThread.errcheck = ErrCheckResumeThread |
|
274 |
|
275 # TerminateProcess() |
|
276 |
|
277 TerminateProcessProto = WINFUNCTYPE(BOOL, # Return type |
|
278 HANDLE, # hProcess |
|
279 UINT # uExitCode |
|
280 ) |
|
281 TerminateProcessFlags = ((1, "hProcess"), |
|
282 (1, "uExitCode", 127)) |
|
283 TerminateProcess = TerminateProcessProto( |
|
284 ("TerminateProcess", windll.kernel32), |
|
285 TerminateProcessFlags) |
|
286 TerminateProcess.errcheck = ErrCheckBool |
|
287 |
|
288 # TerminateJobObject() |
|
289 |
|
290 TerminateJobObjectProto = WINFUNCTYPE(BOOL, # Return type |
|
291 HANDLE, # hJob |
|
292 UINT # uExitCode |
|
293 ) |
|
294 TerminateJobObjectFlags = ((1, "hJob"), |
|
295 (1, "uExitCode", 127)) |
|
296 TerminateJobObject = TerminateJobObjectProto( |
|
297 ("TerminateJobObject", windll.kernel32), |
|
298 TerminateJobObjectFlags) |
|
299 TerminateJobObject.errcheck = ErrCheckBool |
|
300 |
|
301 # WaitForSingleObject() |
|
302 |
|
303 WaitForSingleObjectProto = WINFUNCTYPE(DWORD, # Return type |
|
304 HANDLE, # hHandle |
|
305 DWORD, # dwMilliseconds |
|
306 ) |
|
307 WaitForSingleObjectFlags = ((1, "hHandle"), |
|
308 (1, "dwMilliseconds", -1)) |
|
309 WaitForSingleObject = WaitForSingleObjectProto( |
|
310 ("WaitForSingleObject", windll.kernel32), |
|
311 WaitForSingleObjectFlags) |
|
312 |
|
313 INFINITE = -1 |
|
314 WAIT_TIMEOUT = 0x0102 |
|
315 WAIT_OBJECT_0 = 0x0 |
|
316 WAIT_ABANDONED = 0x0080 |
|
317 WAIT_FAILED = 0xFFFFFFFF |
|
318 |
|
319 # GetExitCodeProcess() |
|
320 |
|
321 GetExitCodeProcessProto = WINFUNCTYPE(BOOL, # Return type |
|
322 HANDLE, # hProcess |
|
323 LPDWORD, # lpExitCode |
|
324 ) |
|
325 GetExitCodeProcessFlags = ((1, "hProcess"), |
|
326 (2, "lpExitCode")) |
|
327 GetExitCodeProcess = GetExitCodeProcessProto( |
|
328 ("GetExitCodeProcess", windll.kernel32), |
|
329 GetExitCodeProcessFlags) |
|
330 GetExitCodeProcess.errcheck = ErrCheckBool |
|
331 |
|
332 def CanCreateJobObject(): |
|
333 # Running firefox in a job (from cfx) hangs on sites using flash plugin |
|
334 # so job creation is turned off for now. (see Bug 768651). |
|
335 return False |
|
336 |
|
337 ### testing functions |
|
338 |
|
339 def parent(): |
|
340 print 'Starting parent' |
|
341 currentProc = GetCurrentProcess() |
|
342 if IsProcessInJob(currentProc): |
|
343 print >> sys.stderr, "You should not be in a job object to test" |
|
344 sys.exit(1) |
|
345 assert CanCreateJobObject() |
|
346 print 'File: %s' % __file__ |
|
347 command = [sys.executable, __file__, '-child'] |
|
348 print 'Running command: %s' % command |
|
349 process = Popen(command) |
|
350 process.kill() |
|
351 code = process.returncode |
|
352 print 'Child code: %s' % code |
|
353 assert code == 127 |
|
354 |
|
355 def child(): |
|
356 print 'Starting child' |
|
357 currentProc = GetCurrentProcess() |
|
358 injob = IsProcessInJob(currentProc) |
|
359 print "Is in a job?: %s" % injob |
|
360 can_create = CanCreateJobObject() |
|
361 print 'Can create job?: %s' % can_create |
|
362 process = Popen('c:\\windows\\notepad.exe') |
|
363 assert process._job |
|
364 jobinfo = QueryInformationJobObject(process._job, 'JobObjectExtendedLimitInformation') |
|
365 print 'Job info: %s' % jobinfo |
|
366 limitflags = jobinfo['BasicLimitInformation']['LimitFlags'] |
|
367 print 'LimitFlags: %s' % limitflags |
|
368 process.kill() |
|
369 |
|
370 if __name__ == '__main__': |
|
371 import sys |
|
372 from killableprocess import Popen |
|
373 nargs = len(sys.argv[1:]) |
|
374 if nargs: |
|
375 if nargs != 1 or sys.argv[1] != '-child': |
|
376 raise AssertionError('Wrong flags; run like `python /path/to/winprocess.py`') |
|
377 child() |
|
378 else: |
|
379 parent() |