123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364 |
- # coding: utf-8
- import os
- import stat
- import sys
- import shutil
- import logging
- from six import reraise
- import library.python.func
- import library.python.strings
- logger = logging.getLogger(__name__)
- ERRORS = {
- 'SUCCESS': 0,
- 'PATH_NOT_FOUND': 3,
- 'ACCESS_DENIED': 5,
- 'SHARING_VIOLATION': 32,
- 'INSUFFICIENT_BUFFER': 122,
- 'DIR_NOT_EMPTY': 145,
- }
- RETRIABLE_FILE_ERRORS = (ERRORS['ACCESS_DENIED'], ERRORS['SHARING_VIOLATION'])
- RETRIABLE_DIR_ERRORS = (ERRORS['ACCESS_DENIED'], ERRORS['DIR_NOT_EMPTY'], ERRORS['SHARING_VIOLATION'])
- # Check if on Windows
- @library.python.func.lazy
- def on_win():
- return os.name == 'nt'
- class NotOnWindowsError(RuntimeError):
- def __init__(self, message):
- super(NotOnWindowsError, self).__init__(message)
- class DisabledOnWindowsError(RuntimeError):
- def __init__(self, message):
- super(DisabledOnWindowsError, self).__init__(message)
- class NoCTypesError(RuntimeError):
- def __init__(self, message):
- super(NoCTypesError, self).__init__(message)
- # Decorator for Windows-only functions
- def win_only(f):
- def f_wrapped(*args, **kwargs):
- if not on_win():
- raise NotOnWindowsError('Windows-only function is called, but platform is not Windows')
- return f(*args, **kwargs)
- return f_wrapped
- # Decorator for functions disabled on Windows
- def win_disabled(f):
- def f_wrapped(*args, **kwargs):
- if on_win():
- run_disabled()
- return f(*args, **kwargs)
- return f_wrapped
- def errorfix(f):
- if not on_win():
- return f
- def f_wrapped(*args, **kwargs):
- try:
- return f(*args, **kwargs)
- except WindowsError:
- tp, value, tb = sys.exc_info()
- fix_error(value)
- reraise(tp, value, tb)
- return f_wrapped
- # Decorator for diehard wrapper
- # On Windows platform retries to run function while specific WindowsError is thrown
- # On non-Windows platforms fallbacks to function itself
- def diehard(winerrors, tries=100, delay=1):
- def wrap(f):
- if not on_win():
- return f
- return lambda *args, **kwargs: run_diehard(f, winerrors, tries, delay, *args, **kwargs)
- return wrap
- if on_win():
- import msvcrt
- import time
- import library.python.strings
- _has_ctypes = True
- try:
- import ctypes
- from ctypes import wintypes
- except ImportError:
- _has_ctypes = False
- _INVALID_HANDLE_VALUE = -1
- _MOVEFILE_REPLACE_EXISTING = 0x1
- _MOVEFILE_WRITE_THROUGH = 0x8
- _SEM_FAILCRITICALERRORS = 0x1
- _SEM_NOGPFAULTERRORBOX = 0x2
- _SEM_NOALIGNMENTFAULTEXCEPT = 0x4
- _SEM_NOOPENFILEERRORBOX = 0x8
- _SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1
- _CREATE_NO_WINDOW = 0x8000000
- _ATOMIC_RENAME_FILE_TRANSACTION_DEFAULT_TIMEOUT = 1000
- _HANDLE_FLAG_INHERIT = 0x1
- @win_only
- def require_ctypes(f):
- def f_wrapped(*args, **kwargs):
- if not _has_ctypes:
- raise NoCTypesError('No ctypes found')
- return f(*args, **kwargs)
- return f_wrapped
- # Run function in diehard mode (see diehard decorator commentary)
- @win_only
- def run_diehard(f, winerrors, tries, delay, *args, **kwargs):
- if isinstance(winerrors, int):
- winerrors = (winerrors,)
- ei = None
- for t in range(tries):
- if t:
- logger.debug('Diehard [errs %s]: try #%d in %s', ','.join(str(x) for x in winerrors), t, f)
- try:
- return f(*args, **kwargs)
- except WindowsError as e:
- if e.winerror not in winerrors:
- raise
- ei = sys.exc_info()
- time.sleep(delay)
- reraise(ei[0], ei[1], ei[2])
- # Placeholder for disabled functions
- @win_only
- def run_disabled(*args, **kwargs):
- raise DisabledOnWindowsError('Function called is disabled on Windows')
- class CustomWinError(WindowsError):
- def __init__(self, winerror, message='', filename=None):
- super(CustomWinError, self).__init__(winerror, message)
- self.message = message
- self.strerror = self.message if self.message else format_error(self.windows_error)
- self.filename = filename
- self.utf8 = True
- @win_only
- def unicode_path(path):
- return library.python.strings.to_unicode(path, library.python.strings.fs_encoding())
- @win_only
- @require_ctypes
- def format_error(error):
- if isinstance(error, WindowsError):
- error = error.winerror
- if not isinstance(error, int):
- return 'Unknown'
- return ctypes.FormatError(error)
- @win_only
- def fix_error(windows_error):
- if not windows_error.strerror:
- windows_error.strerror = format_error(windows_error)
- transcode_error(windows_error)
- @win_only
- def transcode_error(windows_error, to_enc='utf-8'):
- from_enc = 'utf-8' if getattr(windows_error, 'utf8', False) else library.python.strings.guess_default_encoding()
- if from_enc != to_enc:
- windows_error.strerror = library.python.strings.to_str(windows_error.strerror, to_enc=to_enc, from_enc=from_enc)
- setattr(windows_error, 'utf8', to_enc == 'utf-8')
- class Transaction(object):
- def __init__(self, timeout=None, description=''):
- self.timeout = timeout
- self.description = description
- @require_ctypes
- def __enter__(self):
- self._handle = ctypes.windll.ktmw32.CreateTransaction(None, 0, 0, 0, 0, self.timeout, self.description)
- if self._handle == _INVALID_HANDLE_VALUE:
- raise ctypes.WinError()
- return self._handle
- @require_ctypes
- def __exit__(self, t, v, tb):
- try:
- if not ctypes.windll.ktmw32.CommitTransaction(self._handle):
- raise ctypes.WinError()
- finally:
- ctypes.windll.kernel32.CloseHandle(self._handle)
- @win_only
- def file_handle(f):
- return msvcrt.get_osfhandle(f.fileno())
- # https://www.python.org/dev/peps/pep-0446/
- # http://mihalop.blogspot.ru/2014/05/python-subprocess-and-file-descriptors.html
- @require_ctypes
- @win_only
- def open_file(*args, **kwargs):
- f = open(*args, **kwargs)
- ctypes.windll.kernel32.SetHandleInformation(file_handle(f), _HANDLE_FLAG_INHERIT, 0)
- return f
- @win_only
- @require_ctypes
- def replace_file(src, dst):
- if not ctypes.windll.kernel32.MoveFileExW(unicode_path(src), unicode_path(dst), _MOVEFILE_REPLACE_EXISTING | _MOVEFILE_WRITE_THROUGH):
- raise ctypes.WinError()
- @win_only
- @require_ctypes
- def replace_file_across_devices(src, dst):
- with Transaction(timeout=_ATOMIC_RENAME_FILE_TRANSACTION_DEFAULT_TIMEOUT, description='ya library.python.windows replace_file_across_devices') as transaction:
- if not ctypes.windll.kernel32.MoveFileTransactedW(unicode_path(src), unicode_path(dst), None, None, _MOVEFILE_REPLACE_EXISTING | _MOVEFILE_WRITE_THROUGH, transaction):
- raise ctypes.WinError()
- @win_only
- @require_ctypes
- def hardlink(src, lnk):
- if not ctypes.windll.kernel32.CreateHardLinkW(unicode_path(lnk), unicode_path(src), None):
- raise ctypes.WinError()
- # Requires SE_CREATE_SYMBOLIC_LINK_NAME privilege
- @win_only
- @win_disabled
- @require_ctypes
- def symlink_file(src, lnk):
- if not ctypes.windll.kernel32.CreateSymbolicLinkW(unicode_path(lnk), unicode_path(src), 0):
- raise ctypes.WinError()
- # Requires SE_CREATE_SYMBOLIC_LINK_NAME privilege
- @win_only
- @win_disabled
- @require_ctypes
- def symlink_dir(src, lnk):
- if not ctypes.windll.kernel32.CreateSymbolicLinkW(unicode_path(lnk), unicode_path(src), _SYMBOLIC_LINK_FLAG_DIRECTORY):
- raise ctypes.WinError()
- @win_only
- @require_ctypes
- def lock_file(f, offset, length, raises=True):
- locked = ctypes.windll.kernel32.LockFile(file_handle(f), _low_dword(offset), _high_dword(offset), _low_dword(length), _high_dword(length))
- if not raises:
- return bool(locked)
- if not locked:
- raise ctypes.WinError()
- @win_only
- @require_ctypes
- def unlock_file(f, offset, length, raises=True):
- unlocked = ctypes.windll.kernel32.UnlockFile(file_handle(f), _low_dword(offset), _high_dword(offset), _low_dword(length), _high_dword(length))
- if not raises:
- return bool(unlocked)
- if not unlocked:
- raise ctypes.WinError()
- @win_only
- @require_ctypes
- def set_error_mode(mode):
- return ctypes.windll.kernel32.SetErrorMode(mode)
- @win_only
- def rmtree(path):
- def error_handler(func, handling_path, execinfo):
- e = execinfo[1]
- if e.winerror == ERRORS['PATH_NOT_FOUND']:
- handling_path = "\\\\?\\" + handling_path # handle path over 256 symbols
- if os.path.exists(path):
- return func(handling_path)
- if e.winerror == ERRORS['ACCESS_DENIED']:
- try:
- # removing of r/w directory with read-only files in it yields ACCESS_DENIED
- # which is not an insuperable obstacle https://bugs.python.org/issue19643
- os.chmod(handling_path, stat.S_IWRITE)
- except OSError:
- pass
- else:
- # propagate true last error if this attempt fails
- return func(handling_path)
- raise e
- shutil.rmtree(path, onerror=error_handler)
- # Don't display the Windows GPF dialog if the invoked program dies.
- # http://msdn.microsoft.com/en-us/library/windows/desktop/ms680621.aspx
- @win_only
- def disable_error_dialogs():
- set_error_mode(_SEM_NOGPFAULTERRORBOX | _SEM_FAILCRITICALERRORS)
- @win_only
- def default_process_creation_flags():
- return 0
- @require_ctypes
- def _low_dword(x):
- return ctypes.c_ulong(x & ((1 << 32) - 1))
- @require_ctypes
- def _high_dword(x):
- return ctypes.c_ulong((x >> 32) & ((1 << 32) - 1))
- @win_only
- @require_ctypes
- def get_current_process():
- handle = ctypes.windll.kernel32.GetCurrentProcess()
- if not handle:
- raise ctypes.WinError()
- return wintypes.HANDLE(handle)
- @win_only
- @require_ctypes
- def get_process_handle_count(proc_handle):
- assert isinstance(proc_handle, wintypes.HANDLE)
- GetProcessHandleCount = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HANDLE, wintypes.POINTER(wintypes.DWORD))(("GetProcessHandleCount", ctypes.windll.kernel32))
- hndcnt = wintypes.DWORD()
- if not GetProcessHandleCount(proc_handle, ctypes.byref(hndcnt)):
- raise ctypes.WinError()
- return hndcnt.value
- @win_only
- @require_ctypes
- def set_handle_information(file, inherit=None, protect_from_close=None):
- for flag, value in [(inherit, 1), (protect_from_close, 2)]:
- if flag is not None:
- assert isinstance(flag, bool)
- if not ctypes.windll.kernel32.SetHandleInformation(file_handle(file), _low_dword(value), _low_dword(int(flag))):
- raise ctypes.WinError()
- @win_only
- @require_ctypes
- def get_windows_directory():
- buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH)
- size = ctypes.windll.kernel32.GetWindowsDirectoryW(buf, ctypes.wintypes.MAX_PATH)
- if not size:
- raise ctypes.WinError()
- if size > ctypes.wintypes.MAX_PATH - 1:
- raise CustomWinError(ERRORS['INSUFFICIENT_BUFFER'])
- return ctypes.wstring_at(buf, size)
|