win32.py
376 lines
| 12.9 KiB
| text/x-python
|
PythonLexer
/ mercurial / win32.py
Martin Geisler
|
r8226 | # win32.py - utility functions that use win32 API | ||
# | ||||
# Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
Martin Geisler
|
r8227 | |||
Adrian Buehlmann
|
r13377 | import encoding | ||
Adrian Buehlmann
|
r13775 | import ctypes, errno, os, struct, subprocess, random | ||
Adrian Buehlmann
|
r13375 | |||
_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 | ||||
Adrian Buehlmann
|
r13776 | # SetFileAttributes | ||
_FILE_ATTRIBUTE_NORMAL = 0x80 | ||||
Adrian Buehlmann
|
r13795 | _FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x2000 | ||
Adrian Buehlmann
|
r13776 | |||
Adrian Buehlmann
|
r13375 | # 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 | ||||
Martin Geisler
|
r8227 | |||
Adrian Buehlmann
|
r13375 | 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 | ||||
Matt Mackall
|
r7890 | |||
Adrian Buehlmann
|
r13375 | 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)] | ||||
Matt Mackall
|
r7890 | |||
Adrian Buehlmann
|
r13375 | _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) | ||||
Matt Mackall
|
r7890 | |||
def os_link(src, dst): | ||||
Matt Mackall
|
r13976 | try: | ||
if not _kernel32.CreateHardLinkA(dst, src, None): | ||||
_raiseoserror(src) | ||||
except AttributeError: # Wine doesn't support this function | ||||
Adrian Buehlmann
|
r13375 | _raiseoserror(src) | ||
Matt Mackall
|
r7890 | |||
Adrian Buehlmann
|
r13375 | def nlinks(name): | ||
'''return number of hardlinks for the given file''' | ||||
return _getfileinfo(name).nNumberOfLinks | ||||
Matt Mackall
|
r7890 | |||
Siddharth Agarwal
|
r10218 | def samefile(fpath1, fpath2): | ||
Adrian Buehlmann
|
r13375 | '''Returns whether fpath1 and fpath2 refer to the same file. This is only | ||
guaranteed to work for files, not directories.''' | ||||
Siddharth Agarwal
|
r10218 | res1 = _getfileinfo(fpath1) | ||
res2 = _getfileinfo(fpath2) | ||||
Adrian Buehlmann
|
r13375 | return (res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber | ||
and res1.nFileIndexHigh == res2.nFileIndexHigh | ||||
and res1.nFileIndexLow == res2.nFileIndexLow) | ||||
Siddharth Agarwal
|
r10218 | |||
def samedevice(fpath1, fpath2): | ||||
Adrian Buehlmann
|
r13375 | '''Returns whether fpath1 and fpath2 are on the same device. This is only | ||
guaranteed to work for files, not directories.''' | ||||
Siddharth Agarwal
|
r10218 | res1 = _getfileinfo(fpath1) | ||
res2 = _getfileinfo(fpath2) | ||||
Adrian Buehlmann
|
r13375 | return res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber | ||
Siddharth Agarwal
|
r10218 | |||
Matt Mackall
|
r7890 | def testpid(pid): | ||
'''return True if pid is still running or unable to | ||||
determine, False otherwise''' | ||||
Adrian Buehlmann
|
r13375 | 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 | ||||
Matt Mackall
|
r7890 | |||
def lookup_reg(key, valname=None, scope=None): | ||||
''' Look up a key/value name in the Windows registry. | ||||
valname: value name. If unspecified, the default value for the key | ||||
is used. | ||||
scope: optionally specify scope for registry lookup, this can be | ||||
a sequence of scopes to look up in order. Default (CURRENT_USER, | ||||
LOCAL_MACHINE). | ||||
''' | ||||
Adrian Buehlmann
|
r13375 | adv = ctypes.windll.advapi32 | ||
byref = ctypes.byref | ||||
Matt Mackall
|
r7890 | if scope is None: | ||
Adrian Buehlmann
|
r13375 | scope = (_HKEY_CURRENT_USER, _HKEY_LOCAL_MACHINE) | ||
Matt Mackall
|
r7890 | elif not isinstance(scope, (list, tuple)): | ||
scope = (scope,) | ||||
for s in scope: | ||||
Adrian Buehlmann
|
r13375 | kh = _HANDLE() | ||
res = adv.RegOpenKeyExA(s, key, 0, _KEY_READ, ctypes.byref(kh)) | ||||
if res != _ERROR_SUCCESS: | ||||
continue | ||||
Matt Mackall
|
r7890 | try: | ||
Adrian Buehlmann
|
r13375 | 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 = '<L' | ||||
s = ctypes.string_at(byref(buf), struct.calcsize(fmt)) | ||||
return struct.unpack(fmt, s)[0] | ||||
finally: | ||||
adv.RegCloseKey(kh.value) | ||||
Matt Mackall
|
r7890 | |||
Adrian Buehlmann
|
r13376 | def executable_path(): | ||
'''return full path of hg.exe''' | ||||
Adrian Buehlmann
|
r13375 | size = 600 | ||
buf = ctypes.create_string_buffer(size + 1) | ||||
len = _kernel32.GetModuleFileNameA(None, ctypes.byref(buf), size) | ||||
if len == 0: | ||||
raise ctypes.WinError() | ||||
elif len == size: | ||||
raise ctypes.WinError(_ERROR_INSUFFICIENT_BUFFER) | ||||
Adrian Buehlmann
|
r13376 | return buf.value | ||
Matt Mackall
|
r7890 | def getuser(): | ||
'''return name of current user''' | ||||
Adrian Buehlmann
|
r13375 | adv = ctypes.windll.advapi32 | ||
size = _DWORD(300) | ||||
buf = ctypes.create_string_buffer(size.value + 1) | ||||
if not adv.GetUserNameA(ctypes.byref(buf), ctypes.byref(size)): | ||||
raise ctypes.WinError() | ||||
return buf.value | ||||
Matt Mackall
|
r7890 | |||
Adrian Buehlmann
|
r13375 | _SIGNAL_HANDLER = ctypes.WINFUNCTYPE(_BOOL, _DWORD) | ||
_signal_handler = [] | ||||
def set_signal_handler(): | ||||
'''Register a termination handler for console events including | ||||
Matt Mackall
|
r7890 | CTRL+C. python signal handlers do not work well with socket | ||
operations. | ||||
Adrian Buehlmann
|
r13375 | ''' | ||
Matt Mackall
|
r7890 | def handler(event): | ||
Adrian Buehlmann
|
r13375 | _kernel32.ExitProcess(1) | ||
if _signal_handler: | ||||
return # already registered | ||||
h = _SIGNAL_HANDLER(handler) | ||||
_signal_handler.append(h) # needed to prevent garbage collection | ||||
if not _kernel32.SetConsoleCtrlHandler(h, True): | ||||
raise ctypes.WinError() | ||||
_WNDENUMPROC = ctypes.WINFUNCTYPE(_BOOL, _HWND, _LPARAM) | ||||
Matt Mackall
|
r7890 | |||
Patrick Mezard
|
r10240 | def hidewindow(): | ||
Adrian Buehlmann
|
r13375 | user32 = ctypes.windll.user32 | ||
Patrick Mezard
|
r10240 | |||
Adrian Buehlmann
|
r13375 | def callback(hwnd, pid): | ||
wpid = _DWORD() | ||||
user32.GetWindowThreadProcessId(hwnd, ctypes.byref(wpid)) | ||||
if pid == wpid.value: | ||||
user32.ShowWindow(hwnd, _SW_HIDE) | ||||
return False # stop enumerating windows | ||||
return True | ||||
pid = _kernel32.GetCurrentProcessId() | ||||
user32.EnumWindows(_WNDENUMPROC(callback), pid) | ||||
Patrick Mezard
|
r11012 | |||
Augie Fackler
|
r12689 | def termwidth(): | ||
Adrian Buehlmann
|
r13375 | # cmd.exe does not handle CR like a unix console, the CR is | ||
# counted in the line length. On 80 columns consoles, if 80 | ||||
# characters are written, the following CR won't apply on the | ||||
# current line but on the new one. Keep room for it. | ||||
width = 79 | ||||
# Query stderr to avoid problems with redirections | ||||
screenbuf = _kernel32.GetStdHandle( | ||||
_STD_ERROR_HANDLE) # don't close the handle returned | ||||
if screenbuf is None or screenbuf == _INVALID_HANDLE_VALUE: | ||||
return width | ||||
csbi = _CONSOLE_SCREEN_BUFFER_INFO() | ||||
if not _kernel32.GetConsoleScreenBufferInfo( | ||||
screenbuf, ctypes.byref(csbi)): | ||||
return width | ||||
width = csbi.srWindow.Right - csbi.srWindow.Left | ||||
return width | ||||
def spawndetached(args): | ||||
# No standard library function really spawns a fully detached | ||||
# process under win32 because they allocate pipes or other objects | ||||
# to handle standard streams communications. Passing these objects | ||||
# to the child process requires handle inheritance to be enabled | ||||
# which makes really detached processes impossible. | ||||
si = _STARTUPINFO() | ||||
si.cb = ctypes.sizeof(_STARTUPINFO) | ||||
si.dwFlags = _STARTF_USESHOWWINDOW | ||||
si.wShowWindow = _SW_HIDE | ||||
pi = _PROCESS_INFORMATION() | ||||
env = '' | ||||
for k in os.environ: | ||||
env += "%s=%s\0" % (k, os.environ[k]) | ||||
if not env: | ||||
env = '\0' | ||||
env += '\0' | ||||
args = subprocess.list2cmdline(args) | ||||
# Not running the command in shell mode makes python26 hang when | ||||
# writing to hgweb output socket. | ||||
comspec = os.environ.get("COMSPEC", "cmd.exe") | ||||
args = comspec + " /c " + args | ||||
res = _kernel32.CreateProcessA( | ||||
None, args, None, None, False, _DETACHED_PROCESS, | ||||
env, os.getcwd(), ctypes.byref(si), ctypes.byref(pi)) | ||||
if not res: | ||||
raise ctypes.WinError() | ||||
return pi.dwProcessId | ||||
Adrian Buehlmann
|
r13775 | |||
def unlink(f): | ||||
'''try to implement POSIX' unlink semantics on Windows''' | ||||
# POSIX allows to unlink and rename open files. Windows has serious | ||||
# problems with doing that: | ||||
# - Calling os.unlink (or os.rename) on a file f fails if f or any | ||||
# hardlinked copy of f has been opened with Python's open(). There is no | ||||
# way such a file can be deleted or renamed on Windows (other than | ||||
# scheduling the delete or rename for the next reboot). | ||||
# - Calling os.unlink on a file that has been opened with Mercurial's | ||||
# posixfile (or comparable methods) will delay the actual deletion of | ||||
# the file for as long as the file is held open. The filename is blocked | ||||
# during that time and cannot be used for recreating a new file under | ||||
# that same name ("zombie file"). Directories containing such zombie files | ||||
# cannot be removed or moved. | ||||
# A file that has been opened with posixfile can be renamed, so we rename | ||||
# f to a random temporary name before calling os.unlink on it. This allows | ||||
# callers to recreate f immediately while having other readers do their | ||||
# implicit zombie filename blocking on a temporary name. | ||||
for tries in xrange(10): | ||||
temp = '%s-%08x' % (f, random.randint(0, 0xffffffff)) | ||||
try: | ||||
os.rename(f, temp) # raises OSError EEXIST if temp exists | ||||
break | ||||
except OSError, e: | ||||
if e.errno != errno.EEXIST: | ||||
raise | ||||
else: | ||||
raise IOError, (errno.EEXIST, "No usable temporary filename found") | ||||
try: | ||||
os.unlink(temp) | ||||
Adrian Buehlmann
|
r13776 | except OSError: | ||
# The unlink might have failed because the READONLY attribute may heave | ||||
# been set on the original file. Rename works fine with READONLY set, | ||||
# but not os.unlink. Reset all attributes and try again. | ||||
_kernel32.SetFileAttributesA(temp, _FILE_ATTRIBUTE_NORMAL) | ||||
try: | ||||
os.unlink(temp) | ||||
except OSError: | ||||
# The unlink might have failed due to some very rude AV-Scanners. | ||||
# Leaking a tempfile is the lesser evil than aborting here and | ||||
# leaving some potentially serious inconsistencies. | ||||
pass | ||||
Adrian Buehlmann
|
r13795 | |||
def makedir(path, notindexed): | ||||
os.mkdir(path) | ||||
if notindexed: | ||||
_kernel32.SetFileAttributesA(path, _FILE_ATTRIBUTE_NOT_CONTENT_INDEXED) | ||||