# HG changeset patch # User Adrian Buehlmann # Date 2011-02-14 10:12:26 # Node ID f1fa8f481c7c4af377f22ba33d93db7f9025e43e # Parent 1c613c1ae43d756d6beecdd02283a7cea1dfe7ad 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. diff --git a/contrib/wix/dist.wxs b/contrib/wix/dist.wxs --- a/contrib/wix/dist.wxs +++ b/contrib/wix/dist.wxs @@ -16,23 +16,14 @@ - - - - - - - - - + - diff --git a/contrib/wix/guids.wxi b/contrib/wix/guids.wxi --- a/contrib/wix/guids.wxi +++ b/contrib/wix/guids.wxi @@ -9,7 +9,7 @@ - + diff --git a/mercurial/posix.py b/mercurial/posix.py --- a/mercurial/posix.py +++ b/mercurial/posix.py @@ -13,6 +13,7 @@ posixfile = open nulldev = '/dev/null' normpath = os.path.normpath samestat = os.path.samestat +os_link = os.link unlink = os.unlink rename = os.rename expandglobs = False @@ -24,6 +25,10 @@ def openhardlinks(): '''return true if it is safe to hold open file handles to hardlinks''' return True +def nlinks(name): + '''return number of hardlinks for the given file''' + return os.lstat(name).st_nlink + def rcfiles(path): rcs = [os.path.join(path, 'hgrc')] rcdir = os.path.join(path, 'hgrc.d') diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -554,16 +554,6 @@ class path_auditor(object): # want to add "foo/bar/baz" before checking if there's a "foo/.hg" self.auditeddir.update(prefixes) -def nlinks(pathname): - """Return number of hardlinks for the given file.""" - return os.lstat(pathname).st_nlink - -if hasattr(os, 'link'): - os_link = os.link -else: - def os_link(src, dst): - raise OSError(0, _("Hardlinks not supported")) - def lookup_reg(key, name=None, scope=None): return None diff --git a/mercurial/win32.py b/mercurial/win32.py --- a/mercurial/win32.py +++ b/mercurial/win32.py @@ -5,74 +5,173 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -"""Utility functions that use win32 API. +import osutil, encoding +import ctypes, errno, os, struct, subprocess + +_kernel32 = ctypes.windll.kernel32 + +_BOOL = ctypes.c_long +_WORD = ctypes.c_ushort +_DWORD = ctypes.c_ulong +_LPCSTR = _LPSTR = ctypes.c_char_p +_HANDLE = ctypes.c_void_p +_HWND = _HANDLE + +_INVALID_HANDLE_VALUE = -1 + +# GetLastError +_ERROR_SUCCESS = 0 +_ERROR_INVALID_PARAMETER = 87 +_ERROR_INSUFFICIENT_BUFFER = 122 + +# WPARAM is defined as UINT_PTR (unsigned type) +# LPARAM is defined as LONG_PTR (signed type) +if ctypes.sizeof(ctypes.c_long) == ctypes.sizeof(ctypes.c_void_p): + _WPARAM = ctypes.c_ulong + _LPARAM = ctypes.c_long +elif ctypes.sizeof(ctypes.c_longlong) == ctypes.sizeof(ctypes.c_void_p): + _WPARAM = ctypes.c_ulonglong + _LPARAM = ctypes.c_longlong + +class _FILETIME(ctypes.Structure): + _fields_ = [('dwLowDateTime', _DWORD), + ('dwHighDateTime', _DWORD)] + +class _BY_HANDLE_FILE_INFORMATION(ctypes.Structure): + _fields_ = [('dwFileAttributes', _DWORD), + ('ftCreationTime', _FILETIME), + ('ftLastAccessTime', _FILETIME), + ('ftLastWriteTime', _FILETIME), + ('dwVolumeSerialNumber', _DWORD), + ('nFileSizeHigh', _DWORD), + ('nFileSizeLow', _DWORD), + ('nNumberOfLinks', _DWORD), + ('nFileIndexHigh', _DWORD), + ('nFileIndexLow', _DWORD)] + +# CreateFile +_FILE_SHARE_READ = 0x00000001 +_FILE_SHARE_WRITE = 0x00000002 +_FILE_SHARE_DELETE = 0x00000004 + +_OPEN_EXISTING = 3 + +# Process Security and Access Rights +_PROCESS_QUERY_INFORMATION = 0x0400 + +# GetExitCodeProcess +_STILL_ACTIVE = 259 + +# registry +_HKEY_CURRENT_USER = 0x80000001L +_HKEY_LOCAL_MACHINE = 0x80000002L +_KEY_READ = 0x20019 +_REG_SZ = 1 +_REG_DWORD = 4 -Mark Hammond's win32all package allows better functionality on -Windows. This module overrides definitions in util.py. If not -available, import of this module will fail, and generic code will be -used. -""" +class _STARTUPINFO(ctypes.Structure): + _fields_ = [('cb', _DWORD), + ('lpReserved', _LPSTR), + ('lpDesktop', _LPSTR), + ('lpTitle', _LPSTR), + ('dwX', _DWORD), + ('dwY', _DWORD), + ('dwXSize', _DWORD), + ('dwYSize', _DWORD), + ('dwXCountChars', _DWORD), + ('dwYCountChars', _DWORD), + ('dwFillAttribute', _DWORD), + ('dwFlags', _DWORD), + ('wShowWindow', _WORD), + ('cbReserved2', _WORD), + ('lpReserved2', ctypes.c_char_p), + ('hStdInput', _HANDLE), + ('hStdOutput', _HANDLE), + ('hStdError', _HANDLE)] + +class _PROCESS_INFORMATION(ctypes.Structure): + _fields_ = [('hProcess', _HANDLE), + ('hThread', _HANDLE), + ('dwProcessId', _DWORD), + ('dwThreadId', _DWORD)] + +_DETACHED_PROCESS = 0x00000008 +_STARTF_USESHOWWINDOW = 0x00000001 +_SW_HIDE = 0 -import win32api +class _COORD(ctypes.Structure): + _fields_ = [('X', ctypes.c_short), + ('Y', ctypes.c_short)] + +class _SMALL_RECT(ctypes.Structure): + _fields_ = [('Left', ctypes.c_short), + ('Top', ctypes.c_short), + ('Right', ctypes.c_short), + ('Bottom', ctypes.c_short)] + +class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure): + _fields_ = [('dwSize', _COORD), + ('dwCursorPosition', _COORD), + ('wAttributes', _WORD), + ('srWindow', _SMALL_RECT), + ('dwMaximumWindowSize', _COORD)] -import errno, os, sys, pywintypes, win32con, win32file, win32process -import winerror, win32gui, win32console -import osutil, encoding -from win32com.shell import shell, shellcon +_STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12 + +def _raiseoserror(name): + err = ctypes.WinError() + raise OSError(err.errno, '%s: %s' % (name, err.strerror)) + +def _getfileinfo(name): + fh = _kernel32.CreateFileA(name, 0, + _FILE_SHARE_READ | _FILE_SHARE_WRITE | _FILE_SHARE_DELETE, + None, _OPEN_EXISTING, 0, None) + if fh == _INVALID_HANDLE_VALUE: + _raiseoserror(name) + try: + fi = _BY_HANDLE_FILE_INFORMATION() + if not _kernel32.GetFileInformationByHandle(fh, ctypes.byref(fi)): + _raiseoserror(name) + return fi + finally: + _kernel32.CloseHandle(fh) def os_link(src, dst): - try: - win32file.CreateHardLink(dst, src) - except pywintypes.error: - raise OSError(errno.EINVAL, 'target implements hardlinks improperly') - except NotImplementedError: # Another fake error win Win98 - raise OSError(errno.EINVAL, 'Hardlinking not supported') + if not _kernel32.CreateHardLinkA(dst, src, None): + _raiseoserror(src) -def _getfileinfo(pathname): - """Return number of hardlinks for the given file.""" - try: - fh = win32file.CreateFile(pathname, 0, - win32file.FILE_SHARE_READ | win32file.FILE_SHARE_WRITE | - win32file.FILE_SHARE_DELETE, - None, win32file.OPEN_EXISTING, 0, None) - except pywintypes.error: - raise OSError(errno.ENOENT, 'The system cannot find the file specified') - try: - return win32file.GetFileInformationByHandle(fh) - finally: - fh.Close() - -def nlinks(pathname): - """Return number of hardlinks for the given file.""" - return _getfileinfo(pathname)[7] +def nlinks(name): + '''return number of hardlinks for the given file''' + return _getfileinfo(name).nNumberOfLinks def samefile(fpath1, fpath2): - """Returns whether fpath1 and fpath2 refer to the same file. This is only - guaranteed to work for files, not directories.""" + '''Returns whether fpath1 and fpath2 refer to the same file. This is only + guaranteed to work for files, not directories.''' res1 = _getfileinfo(fpath1) res2 = _getfileinfo(fpath2) - # Index 4 is the volume serial number, and 8 and 9 contain the file ID - return res1[4] == res2[4] and res1[8] == res2[8] and res1[9] == res2[9] + return (res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber + and res1.nFileIndexHigh == res2.nFileIndexHigh + and res1.nFileIndexLow == res2.nFileIndexLow) def samedevice(fpath1, fpath2): - """Returns whether fpath1 and fpath2 are on the same device. This is only - guaranteed to work for files, not directories.""" + '''Returns whether fpath1 and fpath2 are on the same device. This is only + guaranteed to work for files, not directories.''' res1 = _getfileinfo(fpath1) res2 = _getfileinfo(fpath2) - return res1[4] == res2[4] + return res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber def testpid(pid): '''return True if pid is still running or unable to determine, False otherwise''' - try: - handle = win32api.OpenProcess( - win32con.PROCESS_QUERY_INFORMATION, False, pid) - if handle: - status = win32process.GetExitCodeProcess(handle) - return status == win32con.STILL_ACTIVE - except pywintypes.error, details: - return details[0] != winerror.ERROR_INVALID_PARAMETER - return True + h = _kernel32.OpenProcess(_PROCESS_QUERY_INFORMATION, False, pid) + if h: + try: + status = _DWORD() + if _kernel32.GetExitCodeProcess(h, ctypes.byref(status)): + return status.value == _STILL_ACTIVE + finally: + _kernel32.CloseHandle(h) + return _kernel32.GetLastError() != _ERROR_INVALID_PARAMETER def lookup_reg(key, valname=None, scope=None): ''' Look up a key/value name in the Windows registry. @@ -83,101 +182,169 @@ def lookup_reg(key, valname=None, scope= a sequence of scopes to look up in order. Default (CURRENT_USER, LOCAL_MACHINE). ''' - try: - from _winreg import HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, \ - QueryValueEx, OpenKey - except ImportError: - return None - + adv = ctypes.windll.advapi32 + byref = ctypes.byref if scope is None: - scope = (HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE) + scope = (_HKEY_CURRENT_USER, _HKEY_LOCAL_MACHINE) elif not isinstance(scope, (list, tuple)): scope = (scope,) for s in scope: + kh = _HANDLE() + res = adv.RegOpenKeyExA(s, key, 0, _KEY_READ, ctypes.byref(kh)) + if res != _ERROR_SUCCESS: + continue try: - val = QueryValueEx(OpenKey(s, key), valname)[0] - # never let a Unicode string escape into the wild - return encoding.tolocal(val.encode('UTF-8')) - except EnvironmentError: - pass + size = _DWORD(600) + type = _DWORD() + buf = ctypes.create_string_buffer(size.value + 1) + res = adv.RegQueryValueExA(kh.value, valname, None, + byref(type), buf, byref(size)) + if res != _ERROR_SUCCESS: + continue + if type.value == _REG_SZ: + # never let a Unicode string escape into the wild + return encoding.tolocal(buf.value.encode('UTF-8')) + elif type.value == _REG_DWORD: + fmt = '