1
# Copyright (C) 2005, 2006, 2007 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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
47
class _base_Lock(object):
53
def _open(self, filename, filemode):
54
self.filename = osutils.realpath(filename)
56
self.f = open(self.filename, filemode)
59
if e.errno in (errno.EACCES, errno.EPERM):
60
raise errors.ReadOnlyLockError(self.filename, str(e))
61
if e.errno != errno.ENOENT:
64
# maybe this is an old branch (before may 2005)
65
trace.mutter("trying to create missing lock %r", self.filename)
67
self.f = open(self.filename, 'wb+')
71
"""Clear the self.f attribute cleanly."""
78
from warnings import warn
79
warn("lock on %r not released" % self.f)
83
raise NotImplementedError()
92
import win32con, win32file, pywintypes, winerror, msvcrt
107
LOCK_SH = fcntl.LOCK_SH
108
LOCK_NB = fcntl.LOCK_NB
109
lock_EX = fcntl.LOCK_EX
112
class _fcntl_FileLock(_base_Lock):
115
fcntl.lockf(self.f, fcntl.LOCK_UN)
119
class _fcntl_WriteLock(_fcntl_FileLock):
123
def __init__(self, filename):
124
super(_fcntl_WriteLock, self).__init__()
125
# Check we can grab a lock before we actually open the file.
126
self.filename = osutils.realpath(filename)
127
if (self.filename in _fcntl_WriteLock._open_locks
128
or self.filename in _fcntl_ReadLock._open_locks):
130
raise errors.LockContention(self.filename)
132
self._open(self.filename, 'rb+')
133
# reserve a slot for this lock - even if the lockf call fails,
134
# at thisi point unlock() will be called, because self.f is set.
135
# TODO: make this fully threadsafe, if we decide we care.
136
_fcntl_WriteLock._open_locks.add(self.filename)
138
# LOCK_NB will cause IOError to be raised if we can't grab a
140
fcntl.lockf(self.f, fcntl.LOCK_EX | fcntl.LOCK_NB)
142
if e.errno in (errno.EAGAIN, errno.EACCES):
143
# We couldn't grab the lock
145
# we should be more precise about whats a locking
146
# error and whats a random-other error
147
raise errors.LockError(e)
150
_fcntl_WriteLock._open_locks.remove(self.filename)
154
class _fcntl_ReadLock(_fcntl_FileLock):
158
def __init__(self, filename, _ignore_write_lock=False):
159
super(_fcntl_ReadLock, self).__init__()
160
self.filename = osutils.realpath(filename)
161
if not _ignore_write_lock and self.filename in _fcntl_WriteLock._open_locks:
162
raise errors.LockContention(self.filename)
163
_fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
164
_fcntl_ReadLock._open_locks[self.filename] += 1
165
self._open(filename, 'rb')
167
# LOCK_NB will cause IOError to be raised if we can't grab a
169
fcntl.lockf(self.f, fcntl.LOCK_SH | fcntl.LOCK_NB)
171
# we should be more precise about whats a locking
172
# error and whats a random-other error
173
raise errors.LockError(e)
176
count = _fcntl_ReadLock._open_locks[self.filename]
178
del _fcntl_ReadLock._open_locks[self.filename]
180
_fcntl_ReadLock._open_locks[self.filename] = count - 1
183
def temporary_write_lock(self):
184
"""Try to grab a write lock on the file.
186
On platforms that support it, this will upgrade to a write lock
187
without unlocking the file.
188
Otherwise, this will release the read lock, and try to acquire a
191
:return: A token which can be used to switch back to a read lock.
193
assert self.filename not in _fcntl_WriteLock._open_locks
195
wlock = _fcntl_TemporaryWriteLock(self)
196
except errors.LockError:
197
# We didn't unlock, so we can just return 'self'
202
class _fcntl_TemporaryWriteLock(_base_Lock):
203
"""A token used when grabbing a temporary_write_lock.
205
Call restore_read_lock() when you are done with the write lock.
208
def __init__(self, read_lock):
209
super(_fcntl_TemporaryWriteLock, self).__init__()
210
self._read_lock = read_lock
211
self.filename = read_lock.filename
213
count = _fcntl_ReadLock._open_locks[self.filename]
215
# Something else also has a read-lock, so we cannot grab a
217
raise errors.LockContention(self.filename)
219
assert self.filename not in _fcntl_WriteLock._open_locks
221
# See if we can open the file for writing. Another process might
222
# have a read lock. We don't use self._open() because we don't want
223
# to create the file if it exists. That would have already been
224
# done by _fcntl_ReadLock
226
new_f = open(self.filename, 'rb+')
228
if e.errno in (errno.EACCES, errno.EPERM):
229
raise errors.ReadOnlyLockError(self.filename, str(e))
232
# LOCK_NB will cause IOError to be raised if we can't grab a
234
fcntl.lockf(new_f, fcntl.LOCK_SH | fcntl.LOCK_NB)
236
# TODO: Raise a more specific error based on the type of error
237
raise errors.LockError(e)
238
_fcntl_WriteLock._open_locks.add(self.filename)
242
def restore_read_lock(self):
243
"""Restore the original ReadLock."""
244
# For fcntl, since we never released the read lock, just release the
245
# write lock, and return the original lock.
246
fcntl.lockf(self.f, fcntl.LOCK_UN)
248
_fcntl_WriteLock._open_locks.remove(self.filename)
249
# Avoid reference cycles
250
read_lock = self._read_lock
251
self._read_lock = None
255
_lock_classes.append(('fcntl', _fcntl_WriteLock, _fcntl_ReadLock))
258
if have_pywin32 and sys.platform == 'win32':
259
LOCK_SH = 0 # the default
260
LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
261
LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
264
class _w32c_FileLock(_base_Lock):
266
def _lock(self, filename, openmode, lockmode):
267
self._open(filename, openmode)
269
self.hfile = msvcrt.get_osfhandle(self.f.fileno())
270
overlapped = pywintypes.OVERLAPPED()
272
win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000,
274
except pywintypes.error, e:
276
if e.args[0] in (winerror.ERROR_LOCK_VIOLATION,):
277
raise errors.LockContention(filename)
278
## import pdb; pdb.set_trace()
282
raise errors.LockError(e)
285
overlapped = pywintypes.OVERLAPPED()
287
win32file.UnlockFileEx(self.hfile, 0, 0x7fff0000, overlapped)
290
raise errors.LockError(e)
293
class _w32c_ReadLock(_w32c_FileLock):
294
def __init__(self, filename):
295
super(_w32c_ReadLock, self).__init__()
296
self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
298
def temporary_write_lock(self):
299
"""Try to grab a write lock on the file.
301
On platforms that support it, this will upgrade to a write lock
302
without unlocking the file.
303
Otherwise, this will release the read lock, and try to acquire a
306
:return: A token which can be used to switch back to a read lock.
308
# I can't find a way to upgrade a read lock to a write lock without
309
# unlocking first. So here, we do just that.
312
wlock = _w32c_WriteLock(self.filename)
313
except errors.LockError:
314
return False, _w32c_ReadLock(self.filename)
318
class _w32c_WriteLock(_w32c_FileLock):
319
def __init__(self, filename):
320
super(_w32c_WriteLock, self).__init__()
321
self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
323
def restore_read_lock(self):
324
"""Restore the original ReadLock."""
325
# For win32 we had to completely let go of the original lock, so we
326
# just unlock and create a new read lock.
328
return _w32c_ReadLock(self.filename)
331
_lock_classes.append(('pywin32', _w32c_WriteLock, _w32c_ReadLock))
334
if have_ctypes and sys.platform == 'win32':
335
# These constants were copied from the win32con.py module.
336
LOCKFILE_FAIL_IMMEDIATELY = 1
337
LOCKFILE_EXCLUSIVE_LOCK = 2
338
# Constant taken from winerror.py module
339
ERROR_LOCK_VIOLATION = 33
342
LOCK_EX = LOCKFILE_EXCLUSIVE_LOCK
343
LOCK_NB = LOCKFILE_FAIL_IMMEDIATELY
344
_LockFileEx = ctypes.windll.kernel32.LockFileEx
345
_UnlockFileEx = ctypes.windll.kernel32.UnlockFileEx
346
_GetLastError = ctypes.windll.kernel32.GetLastError
348
### Define the OVERLAPPED structure.
349
# http://msdn2.microsoft.com/en-us/library/ms684342.aspx
350
# typedef struct _OVERLAPPED {
351
# ULONG_PTR Internal;
352
# ULONG_PTR InternalHigh;
363
class _inner_struct(ctypes.Structure):
364
_fields_ = [('Offset', ctypes.c_uint), # DWORD
365
('OffsetHigh', ctypes.c_uint), # DWORD
368
class _inner_union(ctypes.Union):
369
_fields_ = [('anon_struct', _inner_struct), # struct
370
('Pointer', ctypes.c_void_p), # PVOID
373
class OVERLAPPED(ctypes.Structure):
374
_fields_ = [('Internal', ctypes.c_void_p), # ULONG_PTR
375
('InternalHigh', ctypes.c_void_p), # ULONG_PTR
376
('_inner_union', _inner_union),
377
('hEvent', ctypes.c_void_p), # HANDLE
380
class _ctypes_FileLock(_base_Lock):
382
def _lock(self, filename, openmode, lockmode):
383
self._open(filename, openmode)
385
self.hfile = msvcrt.get_osfhandle(self.f.fileno())
386
overlapped = OVERLAPPED()
387
result = _LockFileEx(self.hfile, # HANDLE hFile
388
lockmode, # DWORD dwFlags
389
0, # DWORD dwReserved
390
0x7fffffff, # DWORD nNumberOfBytesToLockLow
391
0x00000000, # DWORD nNumberOfBytesToLockHigh
392
ctypes.byref(overlapped), # lpOverlapped
396
last_err = _GetLastError()
397
if last_err in (ERROR_LOCK_VIOLATION,):
398
raise errors.LockContention(filename)
399
raise errors.LockError('Unknown locking error: %s'
403
overlapped = OVERLAPPED()
404
result = _UnlockFileEx(self.hfile, # HANDLE hFile
405
0, # DWORD dwReserved
406
0x7fffffff, # DWORD nNumberOfBytesToLockLow
407
0x00000000, # DWORD nNumberOfBytesToLockHigh
408
ctypes.byref(overlapped), # lpOverlapped
413
last_err = _GetLastError()
414
raise errors.LockError('Unknown unlocking error: %s'
418
class _ctypes_ReadLock(_ctypes_FileLock):
419
def __init__(self, filename):
420
super(_ctypes_ReadLock, self).__init__()
421
self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
423
def temporary_write_lock(self):
424
"""Try to grab a write lock on the file.
426
On platforms that support it, this will upgrade to a write lock
427
without unlocking the file.
428
Otherwise, this will release the read lock, and try to acquire a
431
:return: A token which can be used to switch back to a read lock.
433
# I can't find a way to upgrade a read lock to a write lock without
434
# unlocking first. So here, we do just that.
437
wlock = _ctypes_WriteLock(self.filename)
438
except errors.LockError:
439
return False, _ctypes_ReadLock(self.filename)
442
class _ctypes_WriteLock(_ctypes_FileLock):
443
def __init__(self, filename):
444
super(_ctypes_WriteLock, self).__init__()
445
self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
447
def restore_read_lock(self):
448
"""Restore the original ReadLock."""
449
# For win32 we had to completely let go of the original lock, so we
450
# just unlock and create a new read lock.
452
return _ctypes_ReadLock(self.filename)
455
_lock_classes.append(('ctypes', _ctypes_WriteLock, _ctypes_ReadLock))
458
if len(_lock_classes) == 0:
459
raise NotImplementedError(
460
"We must have one of fcntl, pywin32, or ctypes available"
461
" to support OS locking."
465
# We default to using the first available lock class.
466
_lock_type, WriteLock, ReadLock = _lock_classes[0]