##// END OF EJS Templates
win32: spawndetached returns pid of detached process and not of cmd.exe...
Simon Heimberg -
r20425:ca6aa836 default
parent child Browse files
Show More
@@ -1,399 +1,475 b''
1 1 # win32.py - utility functions that use win32 API
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import ctypes, errno, os, subprocess, random
9 9
10 10 _kernel32 = ctypes.windll.kernel32
11 11 _advapi32 = ctypes.windll.advapi32
12 12 _user32 = ctypes.windll.user32
13 13
14 14 _BOOL = ctypes.c_long
15 15 _WORD = ctypes.c_ushort
16 16 _DWORD = ctypes.c_ulong
17 17 _UINT = ctypes.c_uint
18 18 _LONG = ctypes.c_long
19 19 _LPCSTR = _LPSTR = ctypes.c_char_p
20 20 _HANDLE = ctypes.c_void_p
21 21 _HWND = _HANDLE
22 22
23 23 _INVALID_HANDLE_VALUE = _HANDLE(-1).value
24 24
25 25 # GetLastError
26 26 _ERROR_SUCCESS = 0
27 27 _ERROR_INVALID_PARAMETER = 87
28 28 _ERROR_INSUFFICIENT_BUFFER = 122
29 29
30 30 # WPARAM is defined as UINT_PTR (unsigned type)
31 31 # LPARAM is defined as LONG_PTR (signed type)
32 32 if ctypes.sizeof(ctypes.c_long) == ctypes.sizeof(ctypes.c_void_p):
33 33 _WPARAM = ctypes.c_ulong
34 34 _LPARAM = ctypes.c_long
35 35 elif ctypes.sizeof(ctypes.c_longlong) == ctypes.sizeof(ctypes.c_void_p):
36 36 _WPARAM = ctypes.c_ulonglong
37 37 _LPARAM = ctypes.c_longlong
38 38
39 39 class _FILETIME(ctypes.Structure):
40 40 _fields_ = [('dwLowDateTime', _DWORD),
41 41 ('dwHighDateTime', _DWORD)]
42 42
43 43 class _BY_HANDLE_FILE_INFORMATION(ctypes.Structure):
44 44 _fields_ = [('dwFileAttributes', _DWORD),
45 45 ('ftCreationTime', _FILETIME),
46 46 ('ftLastAccessTime', _FILETIME),
47 47 ('ftLastWriteTime', _FILETIME),
48 48 ('dwVolumeSerialNumber', _DWORD),
49 49 ('nFileSizeHigh', _DWORD),
50 50 ('nFileSizeLow', _DWORD),
51 51 ('nNumberOfLinks', _DWORD),
52 52 ('nFileIndexHigh', _DWORD),
53 53 ('nFileIndexLow', _DWORD)]
54 54
55 55 # CreateFile
56 56 _FILE_SHARE_READ = 0x00000001
57 57 _FILE_SHARE_WRITE = 0x00000002
58 58 _FILE_SHARE_DELETE = 0x00000004
59 59
60 60 _OPEN_EXISTING = 3
61 61
62 62 _FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
63 63
64 64 # SetFileAttributes
65 65 _FILE_ATTRIBUTE_NORMAL = 0x80
66 66 _FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x2000
67 67
68 68 # Process Security and Access Rights
69 69 _PROCESS_QUERY_INFORMATION = 0x0400
70 70
71 71 # GetExitCodeProcess
72 72 _STILL_ACTIVE = 259
73 73
74 74 class _STARTUPINFO(ctypes.Structure):
75 75 _fields_ = [('cb', _DWORD),
76 76 ('lpReserved', _LPSTR),
77 77 ('lpDesktop', _LPSTR),
78 78 ('lpTitle', _LPSTR),
79 79 ('dwX', _DWORD),
80 80 ('dwY', _DWORD),
81 81 ('dwXSize', _DWORD),
82 82 ('dwYSize', _DWORD),
83 83 ('dwXCountChars', _DWORD),
84 84 ('dwYCountChars', _DWORD),
85 85 ('dwFillAttribute', _DWORD),
86 86 ('dwFlags', _DWORD),
87 87 ('wShowWindow', _WORD),
88 88 ('cbReserved2', _WORD),
89 89 ('lpReserved2', ctypes.c_char_p),
90 90 ('hStdInput', _HANDLE),
91 91 ('hStdOutput', _HANDLE),
92 92 ('hStdError', _HANDLE)]
93 93
94 94 class _PROCESS_INFORMATION(ctypes.Structure):
95 95 _fields_ = [('hProcess', _HANDLE),
96 96 ('hThread', _HANDLE),
97 97 ('dwProcessId', _DWORD),
98 98 ('dwThreadId', _DWORD)]
99 99
100 100 _CREATE_NO_WINDOW = 0x08000000
101 101 _SW_HIDE = 0
102 102
103 103 class _COORD(ctypes.Structure):
104 104 _fields_ = [('X', ctypes.c_short),
105 105 ('Y', ctypes.c_short)]
106 106
107 107 class _SMALL_RECT(ctypes.Structure):
108 108 _fields_ = [('Left', ctypes.c_short),
109 109 ('Top', ctypes.c_short),
110 110 ('Right', ctypes.c_short),
111 111 ('Bottom', ctypes.c_short)]
112 112
113 113 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
114 114 _fields_ = [('dwSize', _COORD),
115 115 ('dwCursorPosition', _COORD),
116 116 ('wAttributes', _WORD),
117 117 ('srWindow', _SMALL_RECT),
118 118 ('dwMaximumWindowSize', _COORD)]
119 119
120 120 _STD_ERROR_HANDLE = _DWORD(-12).value
121 121
122 # CreateToolhelp32Snapshot, Process32First, Process32Next
123 _TH32CS_SNAPPROCESS = 0x00000002
124 _MAX_PATH = 260
125
126 class _tagPROCESSENTRY32(ctypes.Structure):
127 _fields_ = [('dwsize', _DWORD),
128 ('cntUsage', _DWORD),
129 ('th32ProcessID', _DWORD),
130 ('th32DefaultHeapID', ctypes.c_void_p),
131 ('th32ModuleID', _DWORD),
132 ('cntThreads', _DWORD),
133 ('th32ParentProcessID', _DWORD),
134 ('pcPriClassBase', _LONG),
135 ('dwFlags', _DWORD),
136 ('szExeFile', ctypes.c_char * _MAX_PATH)]
137
138 def __init__(self):
139 super(_tagPROCESSENTRY32, self).__init__()
140 self.dwsize = ctypes.sizeof(self)
141
142
122 143 # types of parameters of C functions used (required by pypy)
123 144
124 145 _kernel32.CreateFileA.argtypes = [_LPCSTR, _DWORD, _DWORD, ctypes.c_void_p,
125 146 _DWORD, _DWORD, _HANDLE]
126 147 _kernel32.CreateFileA.restype = _HANDLE
127 148
128 149 _kernel32.GetFileInformationByHandle.argtypes = [_HANDLE, ctypes.c_void_p]
129 150 _kernel32.GetFileInformationByHandle.restype = _BOOL
130 151
131 152 _kernel32.CloseHandle.argtypes = [_HANDLE]
132 153 _kernel32.CloseHandle.restype = _BOOL
133 154
134 155 try:
135 156 _kernel32.CreateHardLinkA.argtypes = [_LPCSTR, _LPCSTR, ctypes.c_void_p]
136 157 _kernel32.CreateHardLinkA.restype = _BOOL
137 158 except AttributeError:
138 159 pass
139 160
140 161 _kernel32.SetFileAttributesA.argtypes = [_LPCSTR, _DWORD]
141 162 _kernel32.SetFileAttributesA.restype = _BOOL
142 163
143 164 _kernel32.OpenProcess.argtypes = [_DWORD, _BOOL, _DWORD]
144 165 _kernel32.OpenProcess.restype = _HANDLE
145 166
146 167 _kernel32.GetExitCodeProcess.argtypes = [_HANDLE, ctypes.c_void_p]
147 168 _kernel32.GetExitCodeProcess.restype = _BOOL
148 169
149 170 _kernel32.GetLastError.argtypes = []
150 171 _kernel32.GetLastError.restype = _DWORD
151 172
152 173 _kernel32.GetModuleFileNameA.argtypes = [_HANDLE, ctypes.c_void_p, _DWORD]
153 174 _kernel32.GetModuleFileNameA.restype = _DWORD
154 175
155 176 _kernel32.CreateProcessA.argtypes = [_LPCSTR, _LPCSTR, ctypes.c_void_p,
156 177 ctypes.c_void_p, _BOOL, _DWORD, ctypes.c_void_p, _LPCSTR, ctypes.c_void_p,
157 178 ctypes.c_void_p]
158 179 _kernel32.CreateProcessA.restype = _BOOL
159 180
160 181 _kernel32.ExitProcess.argtypes = [_UINT]
161 182 _kernel32.ExitProcess.restype = None
162 183
163 184 _kernel32.GetCurrentProcessId.argtypes = []
164 185 _kernel32.GetCurrentProcessId.restype = _DWORD
165 186
166 187 _SIGNAL_HANDLER = ctypes.WINFUNCTYPE(_BOOL, _DWORD)
167 188 _kernel32.SetConsoleCtrlHandler.argtypes = [_SIGNAL_HANDLER, _BOOL]
168 189 _kernel32.SetConsoleCtrlHandler.restype = _BOOL
169 190
170 191 _kernel32.GetStdHandle.argtypes = [_DWORD]
171 192 _kernel32.GetStdHandle.restype = _HANDLE
172 193
173 194 _kernel32.GetConsoleScreenBufferInfo.argtypes = [_HANDLE, ctypes.c_void_p]
174 195 _kernel32.GetConsoleScreenBufferInfo.restype = _BOOL
175 196
176 197 _advapi32.GetUserNameA.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
177 198 _advapi32.GetUserNameA.restype = _BOOL
178 199
179 200 _user32.GetWindowThreadProcessId.argtypes = [_HANDLE, ctypes.c_void_p]
180 201 _user32.GetWindowThreadProcessId.restype = _DWORD
181 202
182 203 _user32.ShowWindow.argtypes = [_HANDLE, ctypes.c_int]
183 204 _user32.ShowWindow.restype = _BOOL
184 205
185 206 _WNDENUMPROC = ctypes.WINFUNCTYPE(_BOOL, _HWND, _LPARAM)
186 207 _user32.EnumWindows.argtypes = [_WNDENUMPROC, _LPARAM]
187 208 _user32.EnumWindows.restype = _BOOL
188 209
210 _kernel32.CreateToolhelp32Snapshot.argtypes = [_DWORD, _DWORD]
211 _kernel32.CreateToolhelp32Snapshot.restype = _BOOL
212
213 _kernel32.Process32First.argtypes = [_HANDLE, ctypes.c_void_p]
214 _kernel32.Process32First.restype = _BOOL
215
216 _kernel32.Process32Next.argtypes = [_HANDLE, ctypes.c_void_p]
217 _kernel32.Process32Next.restype = _BOOL
218
189 219 def _raiseoserror(name):
190 220 err = ctypes.WinError()
191 221 raise OSError(err.errno, '%s: %s' % (name, err.strerror))
192 222
193 223 def _getfileinfo(name):
194 224 fh = _kernel32.CreateFileA(name, 0,
195 225 _FILE_SHARE_READ | _FILE_SHARE_WRITE | _FILE_SHARE_DELETE,
196 226 None, _OPEN_EXISTING, _FILE_FLAG_BACKUP_SEMANTICS, None)
197 227 if fh == _INVALID_HANDLE_VALUE:
198 228 _raiseoserror(name)
199 229 try:
200 230 fi = _BY_HANDLE_FILE_INFORMATION()
201 231 if not _kernel32.GetFileInformationByHandle(fh, ctypes.byref(fi)):
202 232 _raiseoserror(name)
203 233 return fi
204 234 finally:
205 235 _kernel32.CloseHandle(fh)
206 236
207 237 def oslink(src, dst):
208 238 try:
209 239 if not _kernel32.CreateHardLinkA(dst, src, None):
210 240 _raiseoserror(src)
211 241 except AttributeError: # Wine doesn't support this function
212 242 _raiseoserror(src)
213 243
214 244 def nlinks(name):
215 245 '''return number of hardlinks for the given file'''
216 246 return _getfileinfo(name).nNumberOfLinks
217 247
218 248 def samefile(path1, path2):
219 249 '''Returns whether path1 and path2 refer to the same file or directory.'''
220 250 res1 = _getfileinfo(path1)
221 251 res2 = _getfileinfo(path2)
222 252 return (res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber
223 253 and res1.nFileIndexHigh == res2.nFileIndexHigh
224 254 and res1.nFileIndexLow == res2.nFileIndexLow)
225 255
226 256 def samedevice(path1, path2):
227 257 '''Returns whether path1 and path2 are on the same device.'''
228 258 res1 = _getfileinfo(path1)
229 259 res2 = _getfileinfo(path2)
230 260 return res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber
231 261
232 262 def testpid(pid):
233 263 '''return True if pid is still running or unable to
234 264 determine, False otherwise'''
235 265 h = _kernel32.OpenProcess(_PROCESS_QUERY_INFORMATION, False, pid)
236 266 if h:
237 267 try:
238 268 status = _DWORD()
239 269 if _kernel32.GetExitCodeProcess(h, ctypes.byref(status)):
240 270 return status.value == _STILL_ACTIVE
241 271 finally:
242 272 _kernel32.CloseHandle(h)
243 273 return _kernel32.GetLastError() != _ERROR_INVALID_PARAMETER
244 274
245 275 def executablepath():
246 276 '''return full path of hg.exe'''
247 277 size = 600
248 278 buf = ctypes.create_string_buffer(size + 1)
249 279 len = _kernel32.GetModuleFileNameA(None, ctypes.byref(buf), size)
250 280 if len == 0:
251 281 raise ctypes.WinError
252 282 elif len == size:
253 283 raise ctypes.WinError(_ERROR_INSUFFICIENT_BUFFER)
254 284 return buf.value
255 285
256 286 def getuser():
257 287 '''return name of current user'''
258 288 size = _DWORD(300)
259 289 buf = ctypes.create_string_buffer(size.value + 1)
260 290 if not _advapi32.GetUserNameA(ctypes.byref(buf), ctypes.byref(size)):
261 291 raise ctypes.WinError
262 292 return buf.value
263 293
264 294 _signalhandler = []
265 295
266 296 def setsignalhandler():
267 297 '''Register a termination handler for console events including
268 298 CTRL+C. python signal handlers do not work well with socket
269 299 operations.
270 300 '''
271 301 def handler(event):
272 302 _kernel32.ExitProcess(1)
273 303
274 304 if _signalhandler:
275 305 return # already registered
276 306 h = _SIGNAL_HANDLER(handler)
277 307 _signalhandler.append(h) # needed to prevent garbage collection
278 308 if not _kernel32.SetConsoleCtrlHandler(h, True):
279 309 raise ctypes.WinError
280 310
281 311 def hidewindow():
282 312
283 313 def callback(hwnd, pid):
284 314 wpid = _DWORD()
285 315 _user32.GetWindowThreadProcessId(hwnd, ctypes.byref(wpid))
286 316 if pid == wpid.value:
287 317 _user32.ShowWindow(hwnd, _SW_HIDE)
288 318 return False # stop enumerating windows
289 319 return True
290 320
291 321 pid = _kernel32.GetCurrentProcessId()
292 322 _user32.EnumWindows(_WNDENUMPROC(callback), pid)
293 323
294 324 def termwidth():
295 325 # cmd.exe does not handle CR like a unix console, the CR is
296 326 # counted in the line length. On 80 columns consoles, if 80
297 327 # characters are written, the following CR won't apply on the
298 328 # current line but on the new one. Keep room for it.
299 329 width = 79
300 330 # Query stderr to avoid problems with redirections
301 331 screenbuf = _kernel32.GetStdHandle(
302 332 _STD_ERROR_HANDLE) # don't close the handle returned
303 333 if screenbuf is None or screenbuf == _INVALID_HANDLE_VALUE:
304 334 return width
305 335 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
306 336 if not _kernel32.GetConsoleScreenBufferInfo(
307 337 screenbuf, ctypes.byref(csbi)):
308 338 return width
309 339 width = csbi.srWindow.Right - csbi.srWindow.Left
310 340 return width
311 341
342 def _1stchild(pid):
343 '''return the 1st found child of the given pid
344
345 None is returned when no child is found'''
346 pe = _tagPROCESSENTRY32()
347
348 # create handle to list all processes
349 ph = _kernel32.CreateToolhelp32Snapshot(_TH32CS_SNAPPROCESS, 0)
350 if ph == _INVALID_HANDLE_VALUE:
351 raise ctypes.WinError
352 try:
353 r = _kernel32.Process32First(ph, ctypes.byref(pe))
354 # loop over all processes
355 while r:
356 if pe.th32ParentProcessID == pid:
357 # return first child found
358 return pe.th32ProcessID
359 r = _kernel32.Process32Next(ph, ctypes.byref(pe))
360 finally:
361 _kernel32.CloseHandle(ph)
362 if _kernel32.GetLastError() != _ERROR_NO_MORE_FILES:
363 raise ctypes.WinError
364 return None # no child found
365
366 class _tochildpid(int): # pid is _DWORD, which always matches in an int
367 '''helper for spawndetached, returns the child pid on conversion to string
368
369 Does not resolve the child pid immediately because the child may not yet be
370 started.
371 '''
372 def childpid(self):
373 '''returns the child pid of the first found child of the process
374 with this pid'''
375 return _1stchild(self)
376 def __str__(self):
377 # run when the pid is written to the file
378 ppid = self.childpid()
379 if ppid is None:
380 # race, child has exited since check
381 # fall back to this pid. Its process will also have disappeared,
382 # raising the same error type later as when the child pid would
383 # be returned.
384 return " %d" % self
385 return str(ppid)
386
312 387 def spawndetached(args):
313 388 # No standard library function really spawns a fully detached
314 389 # process under win32 because they allocate pipes or other objects
315 390 # to handle standard streams communications. Passing these objects
316 391 # to the child process requires handle inheritance to be enabled
317 392 # which makes really detached processes impossible.
318 393 si = _STARTUPINFO()
319 394 si.cb = ctypes.sizeof(_STARTUPINFO)
320 395
321 396 pi = _PROCESS_INFORMATION()
322 397
323 398 env = ''
324 399 for k in os.environ:
325 400 env += "%s=%s\0" % (k, os.environ[k])
326 401 if not env:
327 402 env = '\0'
328 403 env += '\0'
329 404
330 405 args = subprocess.list2cmdline(args)
331 406 # Not running the command in shell mode makes Python 2.6 hang when
332 407 # writing to hgweb output socket.
333 408 comspec = os.environ.get("COMSPEC", "cmd.exe")
334 409 args = comspec + " /c " + args
335 410
336 411 res = _kernel32.CreateProcessA(
337 412 None, args, None, None, False, _CREATE_NO_WINDOW,
338 413 env, os.getcwd(), ctypes.byref(si), ctypes.byref(pi))
339 414 if not res:
340 415 raise ctypes.WinError
341 416
342 return pi.dwProcessId
417 # _tochildpid because the process is the child of COMSPEC
418 return _tochildpid(pi.dwProcessId)
343 419
344 420 def unlink(f):
345 421 '''try to implement POSIX' unlink semantics on Windows'''
346 422
347 423 if os.path.isdir(f):
348 424 # use EPERM because it is POSIX prescribed value, even though
349 425 # unlink(2) on directories returns EISDIR on Linux
350 426 raise IOError(errno.EPERM,
351 427 "Unlinking directory not permitted: '%s'" % f)
352 428
353 429 # POSIX allows to unlink and rename open files. Windows has serious
354 430 # problems with doing that:
355 431 # - Calling os.unlink (or os.rename) on a file f fails if f or any
356 432 # hardlinked copy of f has been opened with Python's open(). There is no
357 433 # way such a file can be deleted or renamed on Windows (other than
358 434 # scheduling the delete or rename for the next reboot).
359 435 # - Calling os.unlink on a file that has been opened with Mercurial's
360 436 # posixfile (or comparable methods) will delay the actual deletion of
361 437 # the file for as long as the file is held open. The filename is blocked
362 438 # during that time and cannot be used for recreating a new file under
363 439 # that same name ("zombie file"). Directories containing such zombie files
364 440 # cannot be removed or moved.
365 441 # A file that has been opened with posixfile can be renamed, so we rename
366 442 # f to a random temporary name before calling os.unlink on it. This allows
367 443 # callers to recreate f immediately while having other readers do their
368 444 # implicit zombie filename blocking on a temporary name.
369 445
370 446 for tries in xrange(10):
371 447 temp = '%s-%08x' % (f, random.randint(0, 0xffffffff))
372 448 try:
373 449 os.rename(f, temp) # raises OSError EEXIST if temp exists
374 450 break
375 451 except OSError, e:
376 452 if e.errno != errno.EEXIST:
377 453 raise
378 454 else:
379 455 raise IOError(errno.EEXIST, "No usable temporary filename found")
380 456
381 457 try:
382 458 os.unlink(temp)
383 459 except OSError:
384 460 # The unlink might have failed because the READONLY attribute may heave
385 461 # been set on the original file. Rename works fine with READONLY set,
386 462 # but not os.unlink. Reset all attributes and try again.
387 463 _kernel32.SetFileAttributesA(temp, _FILE_ATTRIBUTE_NORMAL)
388 464 try:
389 465 os.unlink(temp)
390 466 except OSError:
391 467 # The unlink might have failed due to some very rude AV-Scanners.
392 468 # Leaking a tempfile is the lesser evil than aborting here and
393 469 # leaving some potentially serious inconsistencies.
394 470 pass
395 471
396 472 def makedir(path, notindexed):
397 473 os.mkdir(path)
398 474 if notindexed:
399 475 _kernel32.SetFileAttributesA(path, _FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)
General Comments 0
You need to be logged in to leave comments. Login now