##// END OF EJS Templates
win32.py: let samefile and samedevice work on directories too
Adrian Buehlmann -
r17006:6fc7fd72 default
parent child Browse files
Show More
@@ -1,396 +1,396 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 _FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
63
62 64 # SetFileAttributes
63 65 _FILE_ATTRIBUTE_NORMAL = 0x80
64 66 _FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x2000
65 67
66 68 # Process Security and Access Rights
67 69 _PROCESS_QUERY_INFORMATION = 0x0400
68 70
69 71 # GetExitCodeProcess
70 72 _STILL_ACTIVE = 259
71 73
72 74 class _STARTUPINFO(ctypes.Structure):
73 75 _fields_ = [('cb', _DWORD),
74 76 ('lpReserved', _LPSTR),
75 77 ('lpDesktop', _LPSTR),
76 78 ('lpTitle', _LPSTR),
77 79 ('dwX', _DWORD),
78 80 ('dwY', _DWORD),
79 81 ('dwXSize', _DWORD),
80 82 ('dwYSize', _DWORD),
81 83 ('dwXCountChars', _DWORD),
82 84 ('dwYCountChars', _DWORD),
83 85 ('dwFillAttribute', _DWORD),
84 86 ('dwFlags', _DWORD),
85 87 ('wShowWindow', _WORD),
86 88 ('cbReserved2', _WORD),
87 89 ('lpReserved2', ctypes.c_char_p),
88 90 ('hStdInput', _HANDLE),
89 91 ('hStdOutput', _HANDLE),
90 92 ('hStdError', _HANDLE)]
91 93
92 94 class _PROCESS_INFORMATION(ctypes.Structure):
93 95 _fields_ = [('hProcess', _HANDLE),
94 96 ('hThread', _HANDLE),
95 97 ('dwProcessId', _DWORD),
96 98 ('dwThreadId', _DWORD)]
97 99
98 100 _DETACHED_PROCESS = 0x00000008
99 101 _STARTF_USESHOWWINDOW = 0x00000001
100 102 _SW_HIDE = 0
101 103
102 104 class _COORD(ctypes.Structure):
103 105 _fields_ = [('X', ctypes.c_short),
104 106 ('Y', ctypes.c_short)]
105 107
106 108 class _SMALL_RECT(ctypes.Structure):
107 109 _fields_ = [('Left', ctypes.c_short),
108 110 ('Top', ctypes.c_short),
109 111 ('Right', ctypes.c_short),
110 112 ('Bottom', ctypes.c_short)]
111 113
112 114 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
113 115 _fields_ = [('dwSize', _COORD),
114 116 ('dwCursorPosition', _COORD),
115 117 ('wAttributes', _WORD),
116 118 ('srWindow', _SMALL_RECT),
117 119 ('dwMaximumWindowSize', _COORD)]
118 120
119 121 _STD_ERROR_HANDLE = _DWORD(-12).value
120 122
121 123 # types of parameters of C functions used (required by pypy)
122 124
123 125 _kernel32.CreateFileA.argtypes = [_LPCSTR, _DWORD, _DWORD, ctypes.c_void_p,
124 126 _DWORD, _DWORD, _HANDLE]
125 127 _kernel32.CreateFileA.restype = _HANDLE
126 128
127 129 _kernel32.GetFileInformationByHandle.argtypes = [_HANDLE, ctypes.c_void_p]
128 130 _kernel32.GetFileInformationByHandle.restype = _BOOL
129 131
130 132 _kernel32.CloseHandle.argtypes = [_HANDLE]
131 133 _kernel32.CloseHandle.restype = _BOOL
132 134
133 135 try:
134 136 _kernel32.CreateHardLinkA.argtypes = [_LPCSTR, _LPCSTR, ctypes.c_void_p]
135 137 _kernel32.CreateHardLinkA.restype = _BOOL
136 138 except AttributeError:
137 139 pass
138 140
139 141 _kernel32.SetFileAttributesA.argtypes = [_LPCSTR, _DWORD]
140 142 _kernel32.SetFileAttributesA.restype = _BOOL
141 143
142 144 _kernel32.OpenProcess.argtypes = [_DWORD, _BOOL, _DWORD]
143 145 _kernel32.OpenProcess.restype = _HANDLE
144 146
145 147 _kernel32.GetExitCodeProcess.argtypes = [_HANDLE, ctypes.c_void_p]
146 148 _kernel32.GetExitCodeProcess.restype = _BOOL
147 149
148 150 _kernel32.GetLastError.argtypes = []
149 151 _kernel32.GetLastError.restype = _DWORD
150 152
151 153 _kernel32.GetModuleFileNameA.argtypes = [_HANDLE, ctypes.c_void_p, _DWORD]
152 154 _kernel32.GetModuleFileNameA.restype = _DWORD
153 155
154 156 _kernel32.CreateProcessA.argtypes = [_LPCSTR, _LPCSTR, ctypes.c_void_p,
155 157 ctypes.c_void_p, _BOOL, _DWORD, ctypes.c_void_p, _LPCSTR, ctypes.c_void_p,
156 158 ctypes.c_void_p]
157 159 _kernel32.CreateProcessA.restype = _BOOL
158 160
159 161 _kernel32.ExitProcess.argtypes = [_UINT]
160 162 _kernel32.ExitProcess.restype = None
161 163
162 164 _kernel32.GetCurrentProcessId.argtypes = []
163 165 _kernel32.GetCurrentProcessId.restype = _DWORD
164 166
165 167 _SIGNAL_HANDLER = ctypes.WINFUNCTYPE(_BOOL, _DWORD)
166 168 _kernel32.SetConsoleCtrlHandler.argtypes = [_SIGNAL_HANDLER, _BOOL]
167 169 _kernel32.SetConsoleCtrlHandler.restype = _BOOL
168 170
169 171 _kernel32.GetStdHandle.argtypes = [_DWORD]
170 172 _kernel32.GetStdHandle.restype = _HANDLE
171 173
172 174 _kernel32.GetConsoleScreenBufferInfo.argtypes = [_HANDLE, ctypes.c_void_p]
173 175 _kernel32.GetConsoleScreenBufferInfo.restype = _BOOL
174 176
175 177 _advapi32.GetUserNameA.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
176 178 _advapi32.GetUserNameA.restype = _BOOL
177 179
178 180 _user32.GetWindowThreadProcessId.argtypes = [_HANDLE, ctypes.c_void_p]
179 181 _user32.GetWindowThreadProcessId.restype = _DWORD
180 182
181 183 _user32.ShowWindow.argtypes = [_HANDLE, ctypes.c_int]
182 184 _user32.ShowWindow.restype = _BOOL
183 185
184 186 _WNDENUMPROC = ctypes.WINFUNCTYPE(_BOOL, _HWND, _LPARAM)
185 187 _user32.EnumWindows.argtypes = [_WNDENUMPROC, _LPARAM]
186 188 _user32.EnumWindows.restype = _BOOL
187 189
188 190 def _raiseoserror(name):
189 191 err = ctypes.WinError()
190 192 raise OSError(err.errno, '%s: %s' % (name, err.strerror))
191 193
192 194 def _getfileinfo(name):
193 195 fh = _kernel32.CreateFileA(name, 0,
194 196 _FILE_SHARE_READ | _FILE_SHARE_WRITE | _FILE_SHARE_DELETE,
195 None, _OPEN_EXISTING, 0, None)
197 None, _OPEN_EXISTING, _FILE_FLAG_BACKUP_SEMANTICS, None)
196 198 if fh == _INVALID_HANDLE_VALUE:
197 199 _raiseoserror(name)
198 200 try:
199 201 fi = _BY_HANDLE_FILE_INFORMATION()
200 202 if not _kernel32.GetFileInformationByHandle(fh, ctypes.byref(fi)):
201 203 _raiseoserror(name)
202 204 return fi
203 205 finally:
204 206 _kernel32.CloseHandle(fh)
205 207
206 208 def oslink(src, dst):
207 209 try:
208 210 if not _kernel32.CreateHardLinkA(dst, src, None):
209 211 _raiseoserror(src)
210 212 except AttributeError: # Wine doesn't support this function
211 213 _raiseoserror(src)
212 214
213 215 def nlinks(name):
214 216 '''return number of hardlinks for the given file'''
215 217 return _getfileinfo(name).nNumberOfLinks
216 218
217 def samefile(fpath1, fpath2):
218 '''Returns whether fpath1 and fpath2 refer to the same file. This is only
219 guaranteed to work for files, not directories.'''
220 res1 = _getfileinfo(fpath1)
221 res2 = _getfileinfo(fpath2)
219 def samefile(path1, path2):
220 '''Returns whether path1 and path2 refer to the same file or directory.'''
221 res1 = _getfileinfo(path1)
222 res2 = _getfileinfo(path2)
222 223 return (res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber
223 224 and res1.nFileIndexHigh == res2.nFileIndexHigh
224 225 and res1.nFileIndexLow == res2.nFileIndexLow)
225 226
226 def samedevice(fpath1, fpath2):
227 '''Returns whether fpath1 and fpath2 are on the same device. This is only
228 guaranteed to work for files, not directories.'''
229 res1 = _getfileinfo(fpath1)
230 res2 = _getfileinfo(fpath2)
227 def samedevice(path1, path2):
228 '''Returns whether path1 and path2 are on the same device.'''
229 res1 = _getfileinfo(path1)
230 res2 = _getfileinfo(path2)
231 231 return res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber
232 232
233 233 def testpid(pid):
234 234 '''return True if pid is still running or unable to
235 235 determine, False otherwise'''
236 236 h = _kernel32.OpenProcess(_PROCESS_QUERY_INFORMATION, False, pid)
237 237 if h:
238 238 try:
239 239 status = _DWORD()
240 240 if _kernel32.GetExitCodeProcess(h, ctypes.byref(status)):
241 241 return status.value == _STILL_ACTIVE
242 242 finally:
243 243 _kernel32.CloseHandle(h)
244 244 return _kernel32.GetLastError() != _ERROR_INVALID_PARAMETER
245 245
246 246 def executablepath():
247 247 '''return full path of hg.exe'''
248 248 size = 600
249 249 buf = ctypes.create_string_buffer(size + 1)
250 250 len = _kernel32.GetModuleFileNameA(None, ctypes.byref(buf), size)
251 251 if len == 0:
252 252 raise ctypes.WinError
253 253 elif len == size:
254 254 raise ctypes.WinError(_ERROR_INSUFFICIENT_BUFFER)
255 255 return buf.value
256 256
257 257 def getuser():
258 258 '''return name of current user'''
259 259 size = _DWORD(300)
260 260 buf = ctypes.create_string_buffer(size.value + 1)
261 261 if not _advapi32.GetUserNameA(ctypes.byref(buf), ctypes.byref(size)):
262 262 raise ctypes.WinError
263 263 return buf.value
264 264
265 265 _signalhandler = []
266 266
267 267 def setsignalhandler():
268 268 '''Register a termination handler for console events including
269 269 CTRL+C. python signal handlers do not work well with socket
270 270 operations.
271 271 '''
272 272 def handler(event):
273 273 _kernel32.ExitProcess(1)
274 274
275 275 if _signalhandler:
276 276 return # already registered
277 277 h = _SIGNAL_HANDLER(handler)
278 278 _signalhandler.append(h) # needed to prevent garbage collection
279 279 if not _kernel32.SetConsoleCtrlHandler(h, True):
280 280 raise ctypes.WinError
281 281
282 282 def hidewindow():
283 283
284 284 def callback(hwnd, pid):
285 285 wpid = _DWORD()
286 286 _user32.GetWindowThreadProcessId(hwnd, ctypes.byref(wpid))
287 287 if pid == wpid.value:
288 288 _user32.ShowWindow(hwnd, _SW_HIDE)
289 289 return False # stop enumerating windows
290 290 return True
291 291
292 292 pid = _kernel32.GetCurrentProcessId()
293 293 _user32.EnumWindows(_WNDENUMPROC(callback), pid)
294 294
295 295 def termwidth():
296 296 # cmd.exe does not handle CR like a unix console, the CR is
297 297 # counted in the line length. On 80 columns consoles, if 80
298 298 # characters are written, the following CR won't apply on the
299 299 # current line but on the new one. Keep room for it.
300 300 width = 79
301 301 # Query stderr to avoid problems with redirections
302 302 screenbuf = _kernel32.GetStdHandle(
303 303 _STD_ERROR_HANDLE) # don't close the handle returned
304 304 if screenbuf is None or screenbuf == _INVALID_HANDLE_VALUE:
305 305 return width
306 306 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
307 307 if not _kernel32.GetConsoleScreenBufferInfo(
308 308 screenbuf, ctypes.byref(csbi)):
309 309 return width
310 310 width = csbi.srWindow.Right - csbi.srWindow.Left
311 311 return width
312 312
313 313 def spawndetached(args):
314 314 # No standard library function really spawns a fully detached
315 315 # process under win32 because they allocate pipes or other objects
316 316 # to handle standard streams communications. Passing these objects
317 317 # to the child process requires handle inheritance to be enabled
318 318 # which makes really detached processes impossible.
319 319 si = _STARTUPINFO()
320 320 si.cb = ctypes.sizeof(_STARTUPINFO)
321 321 si.dwFlags = _STARTF_USESHOWWINDOW
322 322 si.wShowWindow = _SW_HIDE
323 323
324 324 pi = _PROCESS_INFORMATION()
325 325
326 326 env = ''
327 327 for k in os.environ:
328 328 env += "%s=%s\0" % (k, os.environ[k])
329 329 if not env:
330 330 env = '\0'
331 331 env += '\0'
332 332
333 333 args = subprocess.list2cmdline(args)
334 334 # Not running the command in shell mode makes python26 hang when
335 335 # writing to hgweb output socket.
336 336 comspec = os.environ.get("COMSPEC", "cmd.exe")
337 337 args = comspec + " /c " + args
338 338
339 339 res = _kernel32.CreateProcessA(
340 340 None, args, None, None, False, _DETACHED_PROCESS,
341 341 env, os.getcwd(), ctypes.byref(si), ctypes.byref(pi))
342 342 if not res:
343 343 raise ctypes.WinError
344 344
345 345 return pi.dwProcessId
346 346
347 347 def unlink(f):
348 348 '''try to implement POSIX' unlink semantics on Windows'''
349 349
350 350 # POSIX allows to unlink and rename open files. Windows has serious
351 351 # problems with doing that:
352 352 # - Calling os.unlink (or os.rename) on a file f fails if f or any
353 353 # hardlinked copy of f has been opened with Python's open(). There is no
354 354 # way such a file can be deleted or renamed on Windows (other than
355 355 # scheduling the delete or rename for the next reboot).
356 356 # - Calling os.unlink on a file that has been opened with Mercurial's
357 357 # posixfile (or comparable methods) will delay the actual deletion of
358 358 # the file for as long as the file is held open. The filename is blocked
359 359 # during that time and cannot be used for recreating a new file under
360 360 # that same name ("zombie file"). Directories containing such zombie files
361 361 # cannot be removed or moved.
362 362 # A file that has been opened with posixfile can be renamed, so we rename
363 363 # f to a random temporary name before calling os.unlink on it. This allows
364 364 # callers to recreate f immediately while having other readers do their
365 365 # implicit zombie filename blocking on a temporary name.
366 366
367 367 for tries in xrange(10):
368 368 temp = '%s-%08x' % (f, random.randint(0, 0xffffffff))
369 369 try:
370 370 os.rename(f, temp) # raises OSError EEXIST if temp exists
371 371 break
372 372 except OSError, e:
373 373 if e.errno != errno.EEXIST:
374 374 raise
375 375 else:
376 376 raise IOError, (errno.EEXIST, "No usable temporary filename found")
377 377
378 378 try:
379 379 os.unlink(temp)
380 380 except OSError:
381 381 # The unlink might have failed because the READONLY attribute may heave
382 382 # been set on the original file. Rename works fine with READONLY set,
383 383 # but not os.unlink. Reset all attributes and try again.
384 384 _kernel32.SetFileAttributesA(temp, _FILE_ATTRIBUTE_NORMAL)
385 385 try:
386 386 os.unlink(temp)
387 387 except OSError:
388 388 # The unlink might have failed due to some very rude AV-Scanners.
389 389 # Leaking a tempfile is the lesser evil than aborting here and
390 390 # leaving some potentially serious inconsistencies.
391 391 pass
392 392
393 393 def makedir(path, notindexed):
394 394 os.mkdir(path)
395 395 if notindexed:
396 396 _kernel32.SetFileAttributesA(path, _FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)
General Comments 0
You need to be logged in to leave comments. Login now