comparison mercurial/win32.py @ 13375:f1fa8f481c7c

port win32.py to using the Python ctypes library The pywin32 package is no longer needed. ctypes is now required for running Mercurial on Windows. ctypes is included in Python since version 2.5. For Python 2.4, ctypes is available as an extra installer package for Windows. Moved spawndetached() from windows.py to win32.py and fixed it, using ctypes as well. spawndetached was defunct with Python 2.6.6 because Python removed their undocumented subprocess.CreateProcess. This fixes 'hg serve -d' on Windows.
author Adrian Buehlmann <adrian@cadifra.com>
date Mon, 14 Feb 2011 11:12:26 +0100
parents 1c613c1ae43d
children 60b5c6c3fd12
comparison
equal deleted inserted replaced
13374:1c613c1ae43d 13375:f1fa8f481c7c
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 # 4 #
5 # This software may be used and distributed according to the terms of the 5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version. 6 # GNU General Public License version 2 or any later version.
7 7
8 """Utility functions that use win32 API.
9
10 Mark Hammond's win32all package allows better functionality on
11 Windows. This module overrides definitions in util.py. If not
12 available, import of this module will fail, and generic code will be
13 used.
14 """
15
16 import win32api
17
18 import errno, os, sys, pywintypes, win32con, win32file, win32process
19 import winerror, win32gui, win32console
20 import osutil, encoding 8 import osutil, encoding
21 from win32com.shell import shell, shellcon 9 import ctypes, errno, os, struct, subprocess
10
11 _kernel32 = ctypes.windll.kernel32
12
13 _BOOL = ctypes.c_long
14 _WORD = ctypes.c_ushort
15 _DWORD = ctypes.c_ulong
16 _LPCSTR = _LPSTR = ctypes.c_char_p
17 _HANDLE = ctypes.c_void_p
18 _HWND = _HANDLE
19
20 _INVALID_HANDLE_VALUE = -1
21
22 # GetLastError
23 _ERROR_SUCCESS = 0
24 _ERROR_INVALID_PARAMETER = 87
25 _ERROR_INSUFFICIENT_BUFFER = 122
26
27 # WPARAM is defined as UINT_PTR (unsigned type)
28 # LPARAM is defined as LONG_PTR (signed type)
29 if ctypes.sizeof(ctypes.c_long) == ctypes.sizeof(ctypes.c_void_p):
30 _WPARAM = ctypes.c_ulong
31 _LPARAM = ctypes.c_long
32 elif ctypes.sizeof(ctypes.c_longlong) == ctypes.sizeof(ctypes.c_void_p):
33 _WPARAM = ctypes.c_ulonglong
34 _LPARAM = ctypes.c_longlong
35
36 class _FILETIME(ctypes.Structure):
37 _fields_ = [('dwLowDateTime', _DWORD),
38 ('dwHighDateTime', _DWORD)]
39
40 class _BY_HANDLE_FILE_INFORMATION(ctypes.Structure):
41 _fields_ = [('dwFileAttributes', _DWORD),
42 ('ftCreationTime', _FILETIME),
43 ('ftLastAccessTime', _FILETIME),
44 ('ftLastWriteTime', _FILETIME),
45 ('dwVolumeSerialNumber', _DWORD),
46 ('nFileSizeHigh', _DWORD),
47 ('nFileSizeLow', _DWORD),
48 ('nNumberOfLinks', _DWORD),
49 ('nFileIndexHigh', _DWORD),
50 ('nFileIndexLow', _DWORD)]
51
52 # CreateFile
53 _FILE_SHARE_READ = 0x00000001
54 _FILE_SHARE_WRITE = 0x00000002
55 _FILE_SHARE_DELETE = 0x00000004
56
57 _OPEN_EXISTING = 3
58
59 # Process Security and Access Rights
60 _PROCESS_QUERY_INFORMATION = 0x0400
61
62 # GetExitCodeProcess
63 _STILL_ACTIVE = 259
64
65 # registry
66 _HKEY_CURRENT_USER = 0x80000001L
67 _HKEY_LOCAL_MACHINE = 0x80000002L
68 _KEY_READ = 0x20019
69 _REG_SZ = 1
70 _REG_DWORD = 4
71
72 class _STARTUPINFO(ctypes.Structure):
73 _fields_ = [('cb', _DWORD),
74 ('lpReserved', _LPSTR),
75 ('lpDesktop', _LPSTR),
76 ('lpTitle', _LPSTR),
77 ('dwX', _DWORD),
78 ('dwY', _DWORD),
79 ('dwXSize', _DWORD),
80 ('dwYSize', _DWORD),
81 ('dwXCountChars', _DWORD),
82 ('dwYCountChars', _DWORD),
83 ('dwFillAttribute', _DWORD),
84 ('dwFlags', _DWORD),
85 ('wShowWindow', _WORD),
86 ('cbReserved2', _WORD),
87 ('lpReserved2', ctypes.c_char_p),
88 ('hStdInput', _HANDLE),
89 ('hStdOutput', _HANDLE),
90 ('hStdError', _HANDLE)]
91
92 class _PROCESS_INFORMATION(ctypes.Structure):
93 _fields_ = [('hProcess', _HANDLE),
94 ('hThread', _HANDLE),
95 ('dwProcessId', _DWORD),
96 ('dwThreadId', _DWORD)]
97
98 _DETACHED_PROCESS = 0x00000008
99 _STARTF_USESHOWWINDOW = 0x00000001
100 _SW_HIDE = 0
101
102 class _COORD(ctypes.Structure):
103 _fields_ = [('X', ctypes.c_short),
104 ('Y', ctypes.c_short)]
105
106 class _SMALL_RECT(ctypes.Structure):
107 _fields_ = [('Left', ctypes.c_short),
108 ('Top', ctypes.c_short),
109 ('Right', ctypes.c_short),
110 ('Bottom', ctypes.c_short)]
111
112 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
113 _fields_ = [('dwSize', _COORD),
114 ('dwCursorPosition', _COORD),
115 ('wAttributes', _WORD),
116 ('srWindow', _SMALL_RECT),
117 ('dwMaximumWindowSize', _COORD)]
118
119 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
120
121 def _raiseoserror(name):
122 err = ctypes.WinError()
123 raise OSError(err.errno, '%s: %s' % (name, err.strerror))
124
125 def _getfileinfo(name):
126 fh = _kernel32.CreateFileA(name, 0,
127 _FILE_SHARE_READ | _FILE_SHARE_WRITE | _FILE_SHARE_DELETE,
128 None, _OPEN_EXISTING, 0, None)
129 if fh == _INVALID_HANDLE_VALUE:
130 _raiseoserror(name)
131 try:
132 fi = _BY_HANDLE_FILE_INFORMATION()
133 if not _kernel32.GetFileInformationByHandle(fh, ctypes.byref(fi)):
134 _raiseoserror(name)
135 return fi
136 finally:
137 _kernel32.CloseHandle(fh)
22 138
23 def os_link(src, dst): 139 def os_link(src, dst):
24 try: 140 if not _kernel32.CreateHardLinkA(dst, src, None):
25 win32file.CreateHardLink(dst, src) 141 _raiseoserror(src)
26 except pywintypes.error: 142
27 raise OSError(errno.EINVAL, 'target implements hardlinks improperly') 143 def nlinks(name):
28 except NotImplementedError: # Another fake error win Win98 144 '''return number of hardlinks for the given file'''
29 raise OSError(errno.EINVAL, 'Hardlinking not supported') 145 return _getfileinfo(name).nNumberOfLinks
30
31 def _getfileinfo(pathname):
32 """Return number of hardlinks for the given file."""
33 try:
34 fh = win32file.CreateFile(pathname, 0,
35 win32file.FILE_SHARE_READ | win32file.FILE_SHARE_WRITE |
36 win32file.FILE_SHARE_DELETE,
37 None, win32file.OPEN_EXISTING, 0, None)
38 except pywintypes.error:
39 raise OSError(errno.ENOENT, 'The system cannot find the file specified')
40 try:
41 return win32file.GetFileInformationByHandle(fh)
42 finally:
43 fh.Close()
44
45 def nlinks(pathname):
46 """Return number of hardlinks for the given file."""
47 return _getfileinfo(pathname)[7]
48 146
49 def samefile(fpath1, fpath2): 147 def samefile(fpath1, fpath2):
50 """Returns whether fpath1 and fpath2 refer to the same file. This is only 148 '''Returns whether fpath1 and fpath2 refer to the same file. This is only
51 guaranteed to work for files, not directories.""" 149 guaranteed to work for files, not directories.'''
52 res1 = _getfileinfo(fpath1) 150 res1 = _getfileinfo(fpath1)
53 res2 = _getfileinfo(fpath2) 151 res2 = _getfileinfo(fpath2)
54 # Index 4 is the volume serial number, and 8 and 9 contain the file ID 152 return (res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber
55 return res1[4] == res2[4] and res1[8] == res2[8] and res1[9] == res2[9] 153 and res1.nFileIndexHigh == res2.nFileIndexHigh
154 and res1.nFileIndexLow == res2.nFileIndexLow)
56 155
57 def samedevice(fpath1, fpath2): 156 def samedevice(fpath1, fpath2):
58 """Returns whether fpath1 and fpath2 are on the same device. This is only 157 '''Returns whether fpath1 and fpath2 are on the same device. This is only
59 guaranteed to work for files, not directories.""" 158 guaranteed to work for files, not directories.'''
60 res1 = _getfileinfo(fpath1) 159 res1 = _getfileinfo(fpath1)
61 res2 = _getfileinfo(fpath2) 160 res2 = _getfileinfo(fpath2)
62 return res1[4] == res2[4] 161 return res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber
63 162
64 def testpid(pid): 163 def testpid(pid):
65 '''return True if pid is still running or unable to 164 '''return True if pid is still running or unable to
66 determine, False otherwise''' 165 determine, False otherwise'''
67 try: 166 h = _kernel32.OpenProcess(_PROCESS_QUERY_INFORMATION, False, pid)
68 handle = win32api.OpenProcess( 167 if h:
69 win32con.PROCESS_QUERY_INFORMATION, False, pid) 168 try:
70 if handle: 169 status = _DWORD()
71 status = win32process.GetExitCodeProcess(handle) 170 if _kernel32.GetExitCodeProcess(h, ctypes.byref(status)):
72 return status == win32con.STILL_ACTIVE 171 return status.value == _STILL_ACTIVE
73 except pywintypes.error, details: 172 finally:
74 return details[0] != winerror.ERROR_INVALID_PARAMETER 173 _kernel32.CloseHandle(h)
75 return True 174 return _kernel32.GetLastError() != _ERROR_INVALID_PARAMETER
76 175
77 def lookup_reg(key, valname=None, scope=None): 176 def lookup_reg(key, valname=None, scope=None):
78 ''' Look up a key/value name in the Windows registry. 177 ''' Look up a key/value name in the Windows registry.
79 178
80 valname: value name. If unspecified, the default value for the key 179 valname: value name. If unspecified, the default value for the key
81 is used. 180 is used.
82 scope: optionally specify scope for registry lookup, this can be 181 scope: optionally specify scope for registry lookup, this can be
83 a sequence of scopes to look up in order. Default (CURRENT_USER, 182 a sequence of scopes to look up in order. Default (CURRENT_USER,
84 LOCAL_MACHINE). 183 LOCAL_MACHINE).
85 ''' 184 '''
86 try: 185 adv = ctypes.windll.advapi32
87 from _winreg import HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, \ 186 byref = ctypes.byref
88 QueryValueEx, OpenKey
89 except ImportError:
90 return None
91
92 if scope is None: 187 if scope is None:
93 scope = (HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE) 188 scope = (_HKEY_CURRENT_USER, _HKEY_LOCAL_MACHINE)
94 elif not isinstance(scope, (list, tuple)): 189 elif not isinstance(scope, (list, tuple)):
95 scope = (scope,) 190 scope = (scope,)
96 for s in scope: 191 for s in scope:
192 kh = _HANDLE()
193 res = adv.RegOpenKeyExA(s, key, 0, _KEY_READ, ctypes.byref(kh))
194 if res != _ERROR_SUCCESS:
195 continue
97 try: 196 try:
98 val = QueryValueEx(OpenKey(s, key), valname)[0] 197 size = _DWORD(600)
99 # never let a Unicode string escape into the wild 198 type = _DWORD()
100 return encoding.tolocal(val.encode('UTF-8')) 199 buf = ctypes.create_string_buffer(size.value + 1)
101 except EnvironmentError: 200 res = adv.RegQueryValueExA(kh.value, valname, None,
102 pass 201 byref(type), buf, byref(size))
202 if res != _ERROR_SUCCESS:
203 continue
204 if type.value == _REG_SZ:
205 # never let a Unicode string escape into the wild
206 return encoding.tolocal(buf.value.encode('UTF-8'))
207 elif type.value == _REG_DWORD:
208 fmt = '<L'
209 s = ctypes.string_at(byref(buf), struct.calcsize(fmt))
210 return struct.unpack(fmt, s)[0]
211 finally:
212 adv.RegCloseKey(kh.value)
103 213
104 def system_rcpath_win32(): 214 def system_rcpath_win32():
105 '''return default os-specific hgrc search path''' 215 '''return default os-specific hgrc search path'''
106 filename = win32api.GetModuleFileName(0) 216 rcpath = []
217 size = 600
218 buf = ctypes.create_string_buffer(size + 1)
219 len = _kernel32.GetModuleFileNameA(None, ctypes.byref(buf), size)
220 if len == 0:
221 raise ctypes.WinError()
222 elif len == size:
223 raise ctypes.WinError(_ERROR_INSUFFICIENT_BUFFER)
224 filename = buf.value
107 # Use mercurial.ini found in directory with hg.exe 225 # Use mercurial.ini found in directory with hg.exe
108 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini') 226 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
109 if os.path.isfile(progrc): 227 if os.path.isfile(progrc):
110 return [progrc] 228 rcpath.append(progrc)
229 return rcpath
111 # Use hgrc.d found in directory with hg.exe 230 # Use hgrc.d found in directory with hg.exe
112 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d') 231 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
113 if os.path.isdir(progrcd): 232 if os.path.isdir(progrcd):
114 rcpath = []
115 for f, kind in osutil.listdir(progrcd): 233 for f, kind in osutil.listdir(progrcd):
116 if f.endswith('.rc'): 234 if f.endswith('.rc'):
117 rcpath.append(os.path.join(progrcd, f)) 235 rcpath.append(os.path.join(progrcd, f))
118 return rcpath 236 return rcpath
119 # else look for a system rcpath in the registry 237 # else look for a system rcpath in the registry
120 try: 238 value = lookup_reg('SOFTWARE\\Mercurial', None, _HKEY_LOCAL_MACHINE)
121 value = win32api.RegQueryValue( 239 if not isinstance(value, str) or not value:
122 win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Mercurial')
123 rcpath = []
124 for p in value.split(os.pathsep):
125 if p.lower().endswith('mercurial.ini'):
126 rcpath.append(p)
127 elif os.path.isdir(p):
128 for f, kind in osutil.listdir(p):
129 if f.endswith('.rc'):
130 rcpath.append(os.path.join(p, f))
131 return rcpath 240 return rcpath
132 except pywintypes.error: 241 value = value.replace('/', os.sep)
133 return [] 242 for p in value.split(os.pathsep):
243 if p.lower().endswith('mercurial.ini'):
244 rcpath.append(p)
245 elif os.path.isdir(p):
246 for f, kind in osutil.listdir(p):
247 if f.endswith('.rc'):
248 rcpath.append(os.path.join(p, f))
249 return rcpath
134 250
135 def user_rcpath_win32(): 251 def user_rcpath_win32():
136 '''return os-specific hgrc search path to the user dir''' 252 '''return os-specific hgrc search path to the user dir'''
137 userdir = os.path.expanduser('~') 253 userdir = os.path.expanduser('~')
138 if sys.getwindowsversion()[3] != 2 and userdir == '~':
139 # We are on win < nt: fetch the APPDATA directory location and use
140 # the parent directory as the user home dir.
141 appdir = shell.SHGetPathFromIDList(
142 shell.SHGetSpecialFolderLocation(0, shellcon.CSIDL_APPDATA))
143 userdir = os.path.dirname(appdir)
144 return [os.path.join(userdir, 'mercurial.ini'), 254 return [os.path.join(userdir, 'mercurial.ini'),
145 os.path.join(userdir, '.hgrc')] 255 os.path.join(userdir, '.hgrc')]
146 256
147 def getuser(): 257 def getuser():
148 '''return name of current user''' 258 '''return name of current user'''
149 return win32api.GetUserName() 259 adv = ctypes.windll.advapi32
150 260 size = _DWORD(300)
151 def set_signal_handler_win32(): 261 buf = ctypes.create_string_buffer(size.value + 1)
152 """Register a termination handler for console events including 262 if not adv.GetUserNameA(ctypes.byref(buf), ctypes.byref(size)):
263 raise ctypes.WinError()
264 return buf.value
265
266 _SIGNAL_HANDLER = ctypes.WINFUNCTYPE(_BOOL, _DWORD)
267 _signal_handler = []
268
269 def set_signal_handler():
270 '''Register a termination handler for console events including
153 CTRL+C. python signal handlers do not work well with socket 271 CTRL+C. python signal handlers do not work well with socket
154 operations. 272 operations.
155 """ 273 '''
156 def handler(event): 274 def handler(event):
157 win32process.ExitProcess(1) 275 _kernel32.ExitProcess(1)
158 win32api.SetConsoleCtrlHandler(handler) 276
277 if _signal_handler:
278 return # already registered
279 h = _SIGNAL_HANDLER(handler)
280 _signal_handler.append(h) # needed to prevent garbage collection
281 if not _kernel32.SetConsoleCtrlHandler(h, True):
282 raise ctypes.WinError()
283
284 _WNDENUMPROC = ctypes.WINFUNCTYPE(_BOOL, _HWND, _LPARAM)
159 285
160 def hidewindow(): 286 def hidewindow():
161 def callback(*args, **kwargs): 287 user32 = ctypes.windll.user32
162 hwnd, pid = args 288
163 wpid = win32process.GetWindowThreadProcessId(hwnd)[1] 289 def callback(hwnd, pid):
164 if pid == wpid: 290 wpid = _DWORD()
165 win32gui.ShowWindow(hwnd, win32con.SW_HIDE) 291 user32.GetWindowThreadProcessId(hwnd, ctypes.byref(wpid))
166 292 if pid == wpid.value:
167 pid = win32process.GetCurrentProcessId() 293 user32.ShowWindow(hwnd, _SW_HIDE)
168 win32gui.EnumWindows(callback, pid) 294 return False # stop enumerating windows
295 return True
296
297 pid = _kernel32.GetCurrentProcessId()
298 user32.EnumWindows(_WNDENUMPROC(callback), pid)
169 299
170 def termwidth(): 300 def termwidth():
171 try: 301 # cmd.exe does not handle CR like a unix console, the CR is
172 # Query stderr to avoid problems with redirections 302 # counted in the line length. On 80 columns consoles, if 80
173 screenbuf = win32console.GetStdHandle(win32console.STD_ERROR_HANDLE) 303 # characters are written, the following CR won't apply on the
174 if screenbuf is None: 304 # current line but on the new one. Keep room for it.
175 return 79 305 width = 79
176 try: 306 # Query stderr to avoid problems with redirections
177 window = screenbuf.GetConsoleScreenBufferInfo()['Window'] 307 screenbuf = _kernel32.GetStdHandle(
178 width = window.Right - window.Left 308 _STD_ERROR_HANDLE) # don't close the handle returned
179 return width 309 if screenbuf is None or screenbuf == _INVALID_HANDLE_VALUE:
180 finally: 310 return width
181 screenbuf.Detach() 311 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
182 except pywintypes.error: 312 if not _kernel32.GetConsoleScreenBufferInfo(
183 return 79 313 screenbuf, ctypes.byref(csbi)):
314 return width
315 width = csbi.srWindow.Right - csbi.srWindow.Left
316 return width
317
318 def spawndetached(args):
319 # No standard library function really spawns a fully detached
320 # process under win32 because they allocate pipes or other objects
321 # to handle standard streams communications. Passing these objects
322 # to the child process requires handle inheritance to be enabled
323 # which makes really detached processes impossible.
324 si = _STARTUPINFO()
325 si.cb = ctypes.sizeof(_STARTUPINFO)
326 si.dwFlags = _STARTF_USESHOWWINDOW
327 si.wShowWindow = _SW_HIDE
328
329 pi = _PROCESS_INFORMATION()
330
331 env = ''
332 for k in os.environ:
333 env += "%s=%s\0" % (k, os.environ[k])
334 if not env:
335 env = '\0'
336 env += '\0'
337
338 args = subprocess.list2cmdline(args)
339 # Not running the command in shell mode makes python26 hang when
340 # writing to hgweb output socket.
341 comspec = os.environ.get("COMSPEC", "cmd.exe")
342 args = comspec + " /c " + args
343
344 res = _kernel32.CreateProcessA(
345 None, args, None, None, False, _DETACHED_PROCESS,
346 env, os.getcwd(), ctypes.byref(si), ctypes.byref(pi))
347 if not res:
348 raise ctypes.WinError()
349
350 return pi.dwProcessId