1import os 2import sys 3from . import exceptions 4from . import constants 5 6 7if os.name == 'nt': # pragma: no cover 8 import win32con 9 import win32file 10 import pywintypes 11 import winerror 12 import msvcrt 13 __overlapped = pywintypes.OVERLAPPED() 14 15 if sys.version_info.major == 2: 16 lock_length = -1 17 else: 18 lock_length = int(2**31 - 1) 19 20 def lock(file_, flags): 21 if flags & constants.LOCK_SH: 22 if sys.version_info.major == 2: 23 if flags & constants.LOCK_NB: 24 mode = win32con.LOCKFILE_FAIL_IMMEDIATELY 25 else: 26 mode = 0 27 28 else: 29 if flags & constants.LOCK_NB: 30 mode = msvcrt.LK_NBRLCK 31 else: 32 mode = msvcrt.LK_RLCK 33 34 # is there any reason not to reuse the following structure? 35 hfile = win32file._get_osfhandle(file_.fileno()) 36 try: 37 win32file.LockFileEx(hfile, mode, 0, -0x10000, __overlapped) 38 except pywintypes.error as exc_value: 39 # error: (33, 'LockFileEx', 'The process cannot access the file 40 # because another process has locked a portion of the file.') 41 if exc_value.winerror == winerror.ERROR_LOCK_VIOLATION: 42 raise exceptions.LockException( 43 exceptions.LockException.LOCK_FAILED, 44 exc_value.strerror, 45 fh=file_) 46 else: 47 # Q: Are there exceptions/codes we should be dealing with 48 # here? 49 raise 50 else: 51 mode = win32con.LOCKFILE_EXCLUSIVE_LOCK 52 if flags & constants.LOCK_NB: 53 mode |= win32con.LOCKFILE_FAIL_IMMEDIATELY 54 55 if flags & constants.LOCK_NB: 56 mode = msvcrt.LK_NBLCK 57 else: 58 mode = msvcrt.LK_LOCK 59 60 # windows locks byte ranges, so make sure to lock from file start 61 try: 62 savepos = file_.tell() 63 if savepos: 64 # [ ] test exclusive lock fails on seek here 65 # [ ] test if shared lock passes this point 66 file_.seek(0) 67 # [x] check if 0 param locks entire file (not documented in 68 # Python) 69 # [x] fails with "IOError: [Errno 13] Permission denied", 70 # but -1 seems to do the trick 71 72 try: 73 msvcrt.locking(file_.fileno(), mode, lock_length) 74 except IOError as exc_value: 75 # [ ] be more specific here 76 raise exceptions.LockException( 77 exceptions.LockException.LOCK_FAILED, 78 exc_value.strerror, 79 fh=file_) 80 finally: 81 if savepos: 82 file_.seek(savepos) 83 except IOError as exc_value: 84 raise exceptions.LockException( 85 exceptions.LockException.LOCK_FAILED, exc_value.strerror, 86 fh=file_) 87 88 def unlock(file_): 89 try: 90 savepos = file_.tell() 91 if savepos: 92 file_.seek(0) 93 94 try: 95 msvcrt.locking(file_.fileno(), constants.LOCK_UN, lock_length) 96 except IOError as exc_value: 97 if exc_value.strerror == 'Permission denied': 98 hfile = win32file._get_osfhandle(file_.fileno()) 99 try: 100 win32file.UnlockFileEx( 101 hfile, 0, -0x10000, __overlapped) 102 except pywintypes.error as exc_value: 103 if exc_value.winerror == winerror.ERROR_NOT_LOCKED: 104 # error: (158, 'UnlockFileEx', 105 # 'The segment is already unlocked.') 106 # To match the 'posix' implementation, silently 107 # ignore this error 108 pass 109 else: 110 # Q: Are there exceptions/codes we should be 111 # dealing with here? 112 raise 113 else: 114 raise exceptions.LockException( 115 exceptions.LockException.LOCK_FAILED, 116 exc_value.strerror, 117 fh=file_) 118 finally: 119 if savepos: 120 file_.seek(savepos) 121 except IOError as exc_value: 122 raise exceptions.LockException( 123 exceptions.LockException.LOCK_FAILED, exc_value.strerror, 124 fh=file_) 125 126elif os.name == 'posix': # pragma: no cover 127 import fcntl 128 129 def lock(file_, flags): 130 locking_exceptions = IOError, 131 try: # pragma: no cover 132 locking_exceptions += BlockingIOError, 133 except NameError: # pragma: no cover 134 pass 135 136 try: 137 fcntl.flock(file_.fileno(), flags) 138 except locking_exceptions as exc_value: 139 # The exception code varies on different systems so we'll catch 140 # every IO error 141 raise exceptions.LockException(exc_value, fh=file_) 142 143 def unlock(file_): 144 fcntl.flock(file_.fileno(), constants.LOCK_UN) 145 146else: # pragma: no cover 147 raise RuntimeError('PortaLocker only defined for nt and posix platforms') 148 149