1
# Copyright (C) 2005, 2006, 2007, 2008, 2009 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
"""Locking using OS file locks or file existence.
20
Note: This method of locking is generally deprecated in favour of LockDir, but
21
is used to lock local WorkingTrees, and by some old formats. It's accessed
22
through Transport.lock_read(), etc.
24
This module causes two methods, lock() and unlock() to be defined in
25
any way that works on the current platform.
27
It is not specified whether these locks are reentrant (i.e. can be
28
taken repeatedly by a single process) or whether they exclude
29
different threads in a single process. That reentrancy is provided by
32
This defines two classes: ReadLock and WriteLock, which can be
33
implemented in different ways on different platforms. Both have an
48
from bzrlib.hooks import HookPoint, Hooks
51
class LockHooks(Hooks):
55
self.create_hook(HookPoint('lock_acquired',
56
"Called with a bzrlib.lock.LockResult when a physical lock is "
57
"acquired.", (1, 8), None))
58
self.create_hook(HookPoint('lock_released',
59
"Called with a bzrlib.lock.LockResult when a physical lock is "
60
"released.", (1, 8), None))
61
self.create_hook(HookPoint('lock_broken',
62
"Called with a bzrlib.lock.LockResult when a physical lock is "
63
"broken.", (1, 15), None))
67
"""Base class for locks.
69
:cvar hooks: Hook dictionary for operations on locks.
75
class LockResult(object):
76
"""Result of an operation on a lock; passed to a hook"""
78
def __init__(self, lock_url, details=None):
79
"""Create a lock result for lock with optional details about the lock."""
80
self.lock_url = lock_url
81
self.details = details
83
def __eq__(self, other):
84
return self.lock_url == other.lock_url and self.details == other.details
87
return '%s(%s%s)' % (self.__class__.__name__,
88
self.lock_url, self.details)
91
def cant_unlock_not_held(locked_object):
92
"""An attempt to unlock failed because the object was not locked.
94
This provides a policy point from which we can generate either a warning
97
# This is typically masking some other error and called from a finally
98
# block, so it's useful to have the option not to generate a new error
99
# here. You can use -Werror to make it fatal. It should possibly also
101
if 'unlock' in debug.debug_flags:
102
warnings.warn("%r is already unlocked" % (locked_object,),
105
raise errors.LockNotHeld(locked_object)
115
have_ctypes_win32 = False
116
if sys.platform == 'win32':
119
import win32file, pywintypes, winerror
126
have_ctypes_win32 = True
131
class _OSLock(object):
137
def _open(self, filename, filemode):
138
self.filename = osutils.realpath(filename)
140
self.f = open(self.filename, filemode)
143
if e.errno in (errno.EACCES, errno.EPERM):
144
raise errors.LockFailed(self.filename, str(e))
145
if e.errno != errno.ENOENT:
148
# maybe this is an old branch (before may 2005)
149
trace.mutter("trying to create missing lock %r", self.filename)
151
self.f = open(self.filename, 'wb+')
155
"""Clear the self.f attribute cleanly."""
162
from warnings import warn
163
warn("lock on %r not released" % self.f)
167
raise NotImplementedError()
175
class _fcntl_FileLock(_OSLock):
178
fcntl.lockf(self.f, fcntl.LOCK_UN)
182
class _fcntl_WriteLock(_fcntl_FileLock):
186
def __init__(self, filename):
187
super(_fcntl_WriteLock, self).__init__()
188
# Check we can grab a lock before we actually open the file.
189
self.filename = osutils.realpath(filename)
190
if self.filename in _fcntl_WriteLock._open_locks:
192
raise errors.LockContention(self.filename)
194
self._open(self.filename, 'rb+')
195
# reserve a slot for this lock - even if the lockf call fails,
196
# at thisi point unlock() will be called, because self.f is set.
197
# TODO: make this fully threadsafe, if we decide we care.
198
_fcntl_WriteLock._open_locks.add(self.filename)
200
# LOCK_NB will cause IOError to be raised if we can't grab a
202
fcntl.lockf(self.f, fcntl.LOCK_EX | fcntl.LOCK_NB)
204
if e.errno in (errno.EAGAIN, errno.EACCES):
205
# We couldn't grab the lock
207
# we should be more precise about whats a locking
208
# error and whats a random-other error
209
raise errors.LockContention(self.filename, e)
212
_fcntl_WriteLock._open_locks.remove(self.filename)
216
class _fcntl_ReadLock(_fcntl_FileLock):
220
def __init__(self, filename):
221
super(_fcntl_ReadLock, self).__init__()
222
self.filename = osutils.realpath(filename)
223
_fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
224
_fcntl_ReadLock._open_locks[self.filename] += 1
225
self._open(filename, 'rb')
227
# LOCK_NB will cause IOError to be raised if we can't grab a
229
fcntl.lockf(self.f, fcntl.LOCK_SH | fcntl.LOCK_NB)
231
# we should be more precise about whats a locking
232
# error and whats a random-other error
233
raise errors.LockContention(self.filename, e)
236
count = _fcntl_ReadLock._open_locks[self.filename]
238
del _fcntl_ReadLock._open_locks[self.filename]
240
_fcntl_ReadLock._open_locks[self.filename] = count - 1
243
def temporary_write_lock(self):
244
"""Try to grab a write lock on the file.
246
On platforms that support it, this will upgrade to a write lock
247
without unlocking the file.
248
Otherwise, this will release the read lock, and try to acquire a
251
:return: A token which can be used to switch back to a read lock.
253
if self.filename in _fcntl_WriteLock._open_locks:
254
raise AssertionError('file already locked: %r'
257
wlock = _fcntl_TemporaryWriteLock(self)
258
except errors.LockError:
259
# We didn't unlock, so we can just return 'self'
264
class _fcntl_TemporaryWriteLock(_OSLock):
265
"""A token used when grabbing a temporary_write_lock.
267
Call restore_read_lock() when you are done with the write lock.
270
def __init__(self, read_lock):
271
super(_fcntl_TemporaryWriteLock, self).__init__()
272
self._read_lock = read_lock
273
self.filename = read_lock.filename
275
count = _fcntl_ReadLock._open_locks[self.filename]
277
# Something else also has a read-lock, so we cannot grab a
279
raise errors.LockContention(self.filename)
281
if self.filename in _fcntl_WriteLock._open_locks:
282
raise AssertionError('file already locked: %r'
285
# See if we can open the file for writing. Another process might
286
# have a read lock. We don't use self._open() because we don't want
287
# to create the file if it exists. That would have already been
288
# done by _fcntl_ReadLock
290
new_f = open(self.filename, 'rb+')
292
if e.errno in (errno.EACCES, errno.EPERM):
293
raise errors.LockFailed(self.filename, str(e))
296
# LOCK_NB will cause IOError to be raised if we can't grab a
298
fcntl.lockf(new_f, fcntl.LOCK_EX | fcntl.LOCK_NB)
300
# TODO: Raise a more specific error based on the type of error
301
raise errors.LockContention(self.filename, e)
302
_fcntl_WriteLock._open_locks.add(self.filename)
306
def restore_read_lock(self):
307
"""Restore the original ReadLock."""
308
# For fcntl, since we never released the read lock, just release the
309
# write lock, and return the original lock.
310
fcntl.lockf(self.f, fcntl.LOCK_UN)
312
_fcntl_WriteLock._open_locks.remove(self.filename)
313
# Avoid reference cycles
314
read_lock = self._read_lock
315
self._read_lock = None
319
_lock_classes.append(('fcntl', _fcntl_WriteLock, _fcntl_ReadLock))
322
if have_pywin32 and sys.platform == 'win32':
323
if os.path.supports_unicode_filenames:
324
# for Windows NT/2K/XP/etc
325
win32file_CreateFile = win32file.CreateFileW
328
win32file_CreateFile = win32file.CreateFile
330
class _w32c_FileLock(_OSLock):
332
def _open(self, filename, access, share, cflags, pymode):
333
self.filename = osutils.realpath(filename)
335
self._handle = win32file_CreateFile(filename, access, share,
336
None, win32file.OPEN_ALWAYS,
337
win32file.FILE_ATTRIBUTE_NORMAL, None)
338
except pywintypes.error, e:
339
if e.args[0] == winerror.ERROR_ACCESS_DENIED:
340
raise errors.LockFailed(filename, e)
341
if e.args[0] == winerror.ERROR_SHARING_VIOLATION:
342
raise errors.LockContention(filename, e)
344
fd = win32file._open_osfhandle(self._handle, cflags)
345
self.f = os.fdopen(fd, pymode)
353
class _w32c_ReadLock(_w32c_FileLock):
354
def __init__(self, filename):
355
super(_w32c_ReadLock, self).__init__()
356
self._open(filename, win32file.GENERIC_READ,
357
win32file.FILE_SHARE_READ, os.O_RDONLY, "rb")
359
def temporary_write_lock(self):
360
"""Try to grab a write lock on the file.
362
On platforms that support it, this will upgrade to a write lock
363
without unlocking the file.
364
Otherwise, this will release the read lock, and try to acquire a
367
:return: A token which can be used to switch back to a read lock.
369
# I can't find a way to upgrade a read lock to a write lock without
370
# unlocking first. So here, we do just that.
373
wlock = _w32c_WriteLock(self.filename)
374
except errors.LockError:
375
return False, _w32c_ReadLock(self.filename)
379
class _w32c_WriteLock(_w32c_FileLock):
380
def __init__(self, filename):
381
super(_w32c_WriteLock, self).__init__()
383
win32file.GENERIC_READ | win32file.GENERIC_WRITE, 0,
386
def restore_read_lock(self):
387
"""Restore the original ReadLock."""
388
# For win32 we had to completely let go of the original lock, so we
389
# just unlock and create a new read lock.
391
return _w32c_ReadLock(self.filename)
394
_lock_classes.append(('pywin32', _w32c_WriteLock, _w32c_ReadLock))
397
if have_ctypes_win32:
398
from ctypes.wintypes import DWORD, LPCSTR, LPCWSTR
399
LPSECURITY_ATTRIBUTES = ctypes.c_void_p # used as NULL no need to declare
400
HANDLE = ctypes.c_int # rather than unsigned as in ctypes.wintypes
401
if os.path.supports_unicode_filenames:
402
_function_name = "CreateFileW"
405
_function_name = "CreateFileA"
406
class LPTSTR(LPCSTR):
407
def __new__(cls, obj):
408
return LPCSTR.__new__(cls, obj.encode("mbcs"))
410
# CreateFile <http://msdn.microsoft.com/en-us/library/aa363858.aspx>
411
_CreateFile = ctypes.WINFUNCTYPE(
412
HANDLE, # return value
414
DWORD, # dwDesiredAccess
416
LPSECURITY_ATTRIBUTES, # lpSecurityAttributes
417
DWORD, # dwCreationDisposition
418
DWORD, # dwFlagsAndAttributes
419
HANDLE # hTemplateFile
420
)((_function_name, ctypes.windll.kernel32))
422
INVALID_HANDLE_VALUE = -1
424
GENERIC_READ = 0x80000000
425
GENERIC_WRITE = 0x40000000
428
FILE_ATTRIBUTE_NORMAL = 128
430
ERROR_ACCESS_DENIED = 5
431
ERROR_SHARING_VIOLATION = 32
433
class _ctypes_FileLock(_OSLock):
435
def _open(self, filename, access, share, cflags, pymode):
436
self.filename = osutils.realpath(filename)
437
handle = _CreateFile(filename, access, share, None, OPEN_ALWAYS,
438
FILE_ATTRIBUTE_NORMAL, 0)
439
if handle in (INVALID_HANDLE_VALUE, 0):
440
e = ctypes.WinError()
441
if e.args[0] == ERROR_ACCESS_DENIED:
442
raise errors.LockFailed(filename, e)
443
if e.args[0] == ERROR_SHARING_VIOLATION:
444
raise errors.LockContention(filename, e)
446
fd = msvcrt.open_osfhandle(handle, cflags)
447
self.f = os.fdopen(fd, pymode)
454
class _ctypes_ReadLock(_ctypes_FileLock):
455
def __init__(self, filename):
456
super(_ctypes_ReadLock, self).__init__()
457
self._open(filename, GENERIC_READ, FILE_SHARE_READ, os.O_RDONLY,
460
def temporary_write_lock(self):
461
"""Try to grab a write lock on the file.
463
On platforms that support it, this will upgrade to a write lock
464
without unlocking the file.
465
Otherwise, this will release the read lock, and try to acquire a
468
:return: A token which can be used to switch back to a read lock.
470
# I can't find a way to upgrade a read lock to a write lock without
471
# unlocking first. So here, we do just that.
474
wlock = _ctypes_WriteLock(self.filename)
475
except errors.LockError:
476
return False, _ctypes_ReadLock(self.filename)
479
class _ctypes_WriteLock(_ctypes_FileLock):
480
def __init__(self, filename):
481
super(_ctypes_WriteLock, self).__init__()
482
self._open(filename, GENERIC_READ | GENERIC_WRITE, 0, os.O_RDWR,
485
def restore_read_lock(self):
486
"""Restore the original ReadLock."""
487
# For win32 we had to completely let go of the original lock, so we
488
# just unlock and create a new read lock.
490
return _ctypes_ReadLock(self.filename)
493
_lock_classes.append(('ctypes', _ctypes_WriteLock, _ctypes_ReadLock))
496
if len(_lock_classes) == 0:
497
raise NotImplementedError(
498
"We must have one of fcntl, pywin32, or ctypes available"
499
" to support OS locking."
503
# We default to using the first available lock class.
504
_lock_type, WriteLock, ReadLock = _lock_classes[0]