48
from .hooks import Hooks
49
from .i18n import gettext
52
class LockHooks(Hooks):
55
Hooks.__init__(self, "breezy.lock", "Lock.hooks")
58
"Called with a breezy.lock.LockResult when a physical lock is "
62
"Called with a breezy.lock.LockResult when a physical lock is "
66
"Called with a breezy.lock.LockResult when a physical lock is "
71
"""Base class for locks.
73
:cvar hooks: Hook dictionary for operations on locks.
79
class LockResult(object):
80
"""Result of an operation on a lock; passed to a hook"""
82
def __init__(self, lock_url, details=None):
83
"""Create a lock result for lock with optional details about the lock."""
84
self.lock_url = lock_url
85
self.details = details
87
def __eq__(self, other):
88
return self.lock_url == other.lock_url and self.details == other.details
91
return '%s(%s, %s)' % (self.__class__.__name__,
92
self.lock_url, self.details)
95
class LogicalLockResult(object):
96
"""The result of a lock_read/lock_write/lock_tree_write call on lockables.
98
:ivar unlock: A callable which will unlock the lock.
101
def __init__(self, unlock, token=None):
106
return "LogicalLockResult(%s)" % (self.unlock)
111
def __exit__(self, exc_type, exc_val, exc_tb):
112
# If there was an error raised, prefer the original one
115
except BaseException:
121
def cant_unlock_not_held(locked_object):
122
"""An attempt to unlock failed because the object was not locked.
124
This provides a policy point from which we can generate either a warning or
127
# This is typically masking some other error and called from a finally
128
# block, so it's useful to have the option not to generate a new error
129
# here. You can use -Werror to make it fatal. It should possibly also
131
if 'unlock' in debug.debug_flags:
132
warnings.warn("%r is already unlocked" % (locked_object,),
135
raise errors.LockNotHeld(locked_object)
144
have_ctypes_win32 = False
145
if sys.platform == 'win32':
149
have_ctypes_win32 = True
154
class _OSLock(object):
47
class _base_Lock(object):
156
49
def __init__(self):
344
248
self._read_lock = None
347
252
_lock_classes.append(('fcntl', _fcntl_WriteLock, _fcntl_ReadLock))
350
if have_ctypes_win32:
351
from ctypes.wintypes import DWORD, LPWSTR
352
LPSECURITY_ATTRIBUTES = ctypes.c_void_p # used as NULL no need to declare
353
HANDLE = ctypes.c_int # rather than unsigned as in ctypes.wintypes
354
_function_name = "CreateFileW"
356
# CreateFile <http://msdn.microsoft.com/en-us/library/aa363858.aspx>
357
_CreateFile = ctypes.WINFUNCTYPE(
358
HANDLE, # return value
360
DWORD, # dwDesiredAccess
362
LPSECURITY_ATTRIBUTES, # lpSecurityAttributes
363
DWORD, # dwCreationDisposition
364
DWORD, # dwFlagsAndAttributes
365
HANDLE # hTemplateFile
366
)((_function_name, ctypes.windll.kernel32))
368
INVALID_HANDLE_VALUE = -1
370
GENERIC_READ = 0x80000000
371
GENERIC_WRITE = 0x40000000
374
FILE_ATTRIBUTE_NORMAL = 128
376
ERROR_ACCESS_DENIED = 5
377
ERROR_SHARING_VIOLATION = 32
379
class _ctypes_FileLock(_OSLock):
381
def _open(self, filename, access, share, cflags, pymode):
382
self.filename = osutils.realpath(filename)
383
handle = _CreateFile(filename, access, share, None, OPEN_ALWAYS,
384
FILE_ATTRIBUTE_NORMAL, 0)
385
if handle in (INVALID_HANDLE_VALUE, 0):
386
e = ctypes.WinError()
387
if e.args[0] == ERROR_ACCESS_DENIED:
388
raise errors.LockFailed(filename, e)
389
if e.args[0] == ERROR_SHARING_VIOLATION:
390
raise errors.LockContention(filename, e)
392
fd = msvcrt.open_osfhandle(handle, cflags)
393
self.f = os.fdopen(fd, pymode)
255
if have_pywin32 and sys.platform == 'win32':
256
LOCK_SH = 0 # the default
257
LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
258
LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
261
class _w32c_FileLock(_base_Lock):
263
def _lock(self, filename, openmode, lockmode):
264
self._open(filename, openmode)
266
self.hfile = msvcrt.get_osfhandle(self.f.fileno())
267
overlapped = pywintypes.OVERLAPPED()
269
win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000,
271
except pywintypes.error, e:
273
if e.args[0] in (winerror.ERROR_LOCK_VIOLATION,):
274
raise errors.LockContention(filename)
275
## import pdb; pdb.set_trace()
279
raise errors.LockContention(e)
282
overlapped = pywintypes.OVERLAPPED()
284
win32file.UnlockFileEx(self.hfile, 0, 0x7fff0000, overlapped)
287
raise errors.LockContention(e)
290
class _w32c_ReadLock(_w32c_FileLock):
291
def __init__(self, filename):
292
super(_w32c_ReadLock, self).__init__()
293
self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
295
def temporary_write_lock(self):
296
"""Try to grab a write lock on the file.
298
On platforms that support it, this will upgrade to a write lock
299
without unlocking the file.
300
Otherwise, this will release the read lock, and try to acquire a
303
:return: A token which can be used to switch back to a read lock.
305
# I can't find a way to upgrade a read lock to a write lock without
306
# unlocking first. So here, we do just that.
309
wlock = _w32c_WriteLock(self.filename)
310
except errors.LockError:
311
return False, _w32c_ReadLock(self.filename)
315
class _w32c_WriteLock(_w32c_FileLock):
316
def __init__(self, filename):
317
super(_w32c_WriteLock, self).__init__()
318
self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
320
def restore_read_lock(self):
321
"""Restore the original ReadLock."""
322
# For win32 we had to completely let go of the original lock, so we
323
# just unlock and create a new read lock.
325
return _w32c_ReadLock(self.filename)
328
_lock_classes.append(('pywin32', _w32c_WriteLock, _w32c_ReadLock))
331
if have_ctypes and sys.platform == 'win32':
332
# These constants were copied from the win32con.py module.
333
LOCKFILE_FAIL_IMMEDIATELY = 1
334
LOCKFILE_EXCLUSIVE_LOCK = 2
335
# Constant taken from winerror.py module
336
ERROR_LOCK_VIOLATION = 33
339
LOCK_EX = LOCKFILE_EXCLUSIVE_LOCK
340
LOCK_NB = LOCKFILE_FAIL_IMMEDIATELY
341
_LockFileEx = ctypes.windll.kernel32.LockFileEx
342
_UnlockFileEx = ctypes.windll.kernel32.UnlockFileEx
343
_GetLastError = ctypes.windll.kernel32.GetLastError
345
### Define the OVERLAPPED structure.
346
# http://msdn2.microsoft.com/en-us/library/ms684342.aspx
347
# typedef struct _OVERLAPPED {
348
# ULONG_PTR Internal;
349
# ULONG_PTR InternalHigh;
360
class _inner_struct(ctypes.Structure):
361
_fields_ = [('Offset', ctypes.c_uint), # DWORD
362
('OffsetHigh', ctypes.c_uint), # DWORD
365
class _inner_union(ctypes.Union):
366
_fields_ = [('anon_struct', _inner_struct), # struct
367
('Pointer', ctypes.c_void_p), # PVOID
370
class OVERLAPPED(ctypes.Structure):
371
_fields_ = [('Internal', ctypes.c_void_p), # ULONG_PTR
372
('InternalHigh', ctypes.c_void_p), # ULONG_PTR
373
('_inner_union', _inner_union),
374
('hEvent', ctypes.c_void_p), # HANDLE
377
class _ctypes_FileLock(_base_Lock):
379
def _lock(self, filename, openmode, lockmode):
380
self._open(filename, openmode)
382
self.hfile = msvcrt.get_osfhandle(self.f.fileno())
383
overlapped = OVERLAPPED()
384
result = _LockFileEx(self.hfile, # HANDLE hFile
385
lockmode, # DWORD dwFlags
386
0, # DWORD dwReserved
387
0x7fffffff, # DWORD nNumberOfBytesToLockLow
388
0x00000000, # DWORD nNumberOfBytesToLockHigh
389
ctypes.byref(overlapped), # lpOverlapped
393
last_err = _GetLastError()
394
if last_err in (ERROR_LOCK_VIOLATION,):
395
raise errors.LockContention(filename)
396
raise errors.LockContention('Unknown locking error: %s'
400
overlapped = OVERLAPPED()
401
result = _UnlockFileEx(self.hfile, # HANDLE hFile
402
0, # DWORD dwReserved
403
0x7fffffff, # DWORD nNumberOfBytesToLockLow
404
0x00000000, # DWORD nNumberOfBytesToLockHigh
405
ctypes.byref(overlapped), # lpOverlapped
410
last_err = _GetLastError()
411
raise errors.LockContention('Unknown unlocking error: %s'
399
415
class _ctypes_ReadLock(_ctypes_FileLock):
400
416
def __init__(self, filename):
401
417
super(_ctypes_ReadLock, self).__init__()
402
self._open(filename, GENERIC_READ, FILE_SHARE_READ, os.O_RDONLY,
418
self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
405
420
def temporary_write_lock(self):
406
421
"""Try to grab a write lock on the file.