/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/lock.py

  • Committer: Gustav Hartvigsson
  • Date: 2021-01-09 21:36:27 UTC
  • Revision ID: gustav.hartvigsson@gmail.com-20210109213627-h1xwcutzy9m7a99b
Added 'Case Preserving Working Tree Use Cases' from Canonical Wiki

* Addod a page from the Canonical Bazaar wiki
  with information on the scmeatics of case
  perserving filesystems an a case insensitive
  filesystem works.
  
  * Needs re-work, but this will do as it is the
    same inforamoton as what was on the linked
    page in the currint documentation.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005-2010 Canonical Ltd
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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
 
16
 
 
17
"""Locking using OS file locks or file existence.
 
18
 
 
19
Note: This method of locking is generally deprecated in favour of LockDir, but
 
20
is used to lock local WorkingTrees, and by some old formats.  It's accessed
 
21
through Transport.lock_read(), etc.
 
22
 
 
23
This module causes two methods, lock() and unlock() to be defined in
 
24
any way that works on the current platform.
 
25
 
 
26
It is not specified whether these locks are reentrant (i.e. can be
 
27
taken repeatedly by a single process) or whether they exclude
 
28
different threads in a single process.  That reentrancy is provided by
 
29
LockableFiles.
 
30
 
 
31
This defines two classes: ReadLock and WriteLock, which can be
 
32
implemented in different ways on different platforms.  Both have an
 
33
unlock() method.
 
34
"""
 
35
 
 
36
import contextlib
 
37
import errno
 
38
import os
 
39
import sys
 
40
import warnings
 
41
 
 
42
from . import (
 
43
    debug,
 
44
    errors,
 
45
    osutils,
 
46
    trace,
 
47
    )
 
48
from .hooks import Hooks
 
49
from .i18n import gettext
 
50
 
 
51
 
 
52
class LockHooks(Hooks):
 
53
 
 
54
    def __init__(self):
 
55
        Hooks.__init__(self, "breezy.lock", "Lock.hooks")
 
56
        self.add_hook(
 
57
            'lock_acquired',
 
58
            "Called with a breezy.lock.LockResult when a physical lock is "
 
59
            "acquired.", (1, 8))
 
60
        self.add_hook(
 
61
            'lock_released',
 
62
            "Called with a breezy.lock.LockResult when a physical lock is "
 
63
            "released.", (1, 8))
 
64
        self.add_hook(
 
65
            'lock_broken',
 
66
            "Called with a breezy.lock.LockResult when a physical lock is "
 
67
            "broken.", (1, 15))
 
68
 
 
69
 
 
70
class Lock(object):
 
71
    """Base class for locks.
 
72
 
 
73
    :cvar hooks: Hook dictionary for operations on locks.
 
74
    """
 
75
 
 
76
    hooks = LockHooks()
 
77
 
 
78
 
 
79
class LockResult(object):
 
80
    """Result of an operation on a lock; passed to a hook"""
 
81
 
 
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
 
86
 
 
87
    def __eq__(self, other):
 
88
        return self.lock_url == other.lock_url and self.details == other.details
 
89
 
 
90
    def __repr__(self):
 
91
        return '%s(%s, %s)' % (self.__class__.__name__,
 
92
                               self.lock_url, self.details)
 
93
 
 
94
 
 
95
class LogicalLockResult(object):
 
96
    """The result of a lock_read/lock_write/lock_tree_write call on lockables.
 
97
 
 
98
    :ivar unlock: A callable which will unlock the lock.
 
99
    """
 
100
 
 
101
    def __init__(self, unlock, token=None):
 
102
        self.unlock = unlock
 
103
        self.token = token
 
104
 
 
105
    def __repr__(self):
 
106
        return "LogicalLockResult(%s)" % (self.unlock)
 
107
 
 
108
    def __enter__(self):
 
109
        return self
 
110
 
 
111
    def __exit__(self, exc_type, exc_val, exc_tb):
 
112
        # If there was an error raised, prefer the original one
 
113
        try:
 
114
            self.unlock()
 
115
        except BaseException:
 
116
            if exc_type is None:
 
117
                raise
 
118
        return False
 
119
 
 
120
 
 
121
def cant_unlock_not_held(locked_object):
 
122
    """An attempt to unlock failed because the object was not locked.
 
123
 
 
124
    This provides a policy point from which we can generate either a warning or
 
125
    an exception.
 
126
    """
 
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
 
130
    # raise LockNotHeld.
 
131
    if 'unlock' in debug.debug_flags:
 
132
        warnings.warn("%r is already unlocked" % (locked_object,),
 
133
                      stacklevel=3)
 
134
    else:
 
135
        raise errors.LockNotHeld(locked_object)
 
136
 
 
137
 
 
138
try:
 
139
    import fcntl
 
140
    have_fcntl = True
 
141
except ImportError:
 
142
    have_fcntl = False
 
143
 
 
144
have_ctypes_win32 = False
 
145
if sys.platform == 'win32':
 
146
    import msvcrt
 
147
    try:
 
148
        import ctypes
 
149
        have_ctypes_win32 = True
 
150
    except ImportError:
 
151
        pass
 
152
 
 
153
 
 
154
class _OSLock(object):
 
155
 
 
156
    def __init__(self):
 
157
        self.f = None
 
158
        self.filename = None
 
159
 
 
160
    def _open(self, filename, filemode):
 
161
        self.filename = osutils.realpath(filename)
 
162
        try:
 
163
            self.f = open(self.filename, filemode)
 
164
            return self.f
 
165
        except IOError as e:
 
166
            if e.errno in (errno.EACCES, errno.EPERM):
 
167
                raise errors.LockFailed(self.filename, str(e))
 
168
            if e.errno != errno.ENOENT:
 
169
                raise
 
170
 
 
171
            # maybe this is an old branch (before may 2005)
 
172
            trace.mutter("trying to create missing lock %r", self.filename)
 
173
 
 
174
            self.f = open(self.filename, 'wb+')
 
175
            return self.f
 
176
 
 
177
    def _clear_f(self):
 
178
        """Clear the self.f attribute cleanly."""
 
179
        if self.f:
 
180
            self.f.close()
 
181
            self.f = None
 
182
 
 
183
    def unlock(self):
 
184
        raise NotImplementedError()
 
185
 
 
186
 
 
187
_lock_classes = []
 
188
 
 
189
 
 
190
if have_fcntl:
 
191
 
 
192
    class _fcntl_FileLock(_OSLock):
 
193
 
 
194
        def _unlock(self):
 
195
            fcntl.lockf(self.f, fcntl.LOCK_UN)
 
196
            self._clear_f()
 
197
 
 
198
    class _fcntl_WriteLock(_fcntl_FileLock):
 
199
 
 
200
        _open_locks = set()
 
201
 
 
202
        def __init__(self, filename):
 
203
            super(_fcntl_WriteLock, self).__init__()
 
204
            # Check we can grab a lock before we actually open the file.
 
205
            self.filename = osutils.realpath(filename)
 
206
            if self.filename in _fcntl_WriteLock._open_locks:
 
207
                self._clear_f()
 
208
                raise errors.LockContention(self.filename)
 
209
            if self.filename in _fcntl_ReadLock._open_locks:
 
210
                if 'strict_locks' in debug.debug_flags:
 
211
                    self._clear_f()
 
212
                    raise errors.LockContention(self.filename)
 
213
                else:
 
214
                    trace.mutter('Write lock taken w/ an open read lock on: %s'
 
215
                                 % (self.filename,))
 
216
 
 
217
            self._open(self.filename, 'rb+')
 
218
            # reserve a slot for this lock - even if the lockf call fails,
 
219
            # at this point unlock() will be called, because self.f is set.
 
220
            # TODO: make this fully threadsafe, if we decide we care.
 
221
            _fcntl_WriteLock._open_locks.add(self.filename)
 
222
            try:
 
223
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
224
                # lock right away.
 
225
                fcntl.lockf(self.f, fcntl.LOCK_EX | fcntl.LOCK_NB)
 
226
            except IOError as e:
 
227
                if e.errno in (errno.EAGAIN, errno.EACCES):
 
228
                    # We couldn't grab the lock
 
229
                    self.unlock()
 
230
                # we should be more precise about whats a locking
 
231
                # error and whats a random-other error
 
232
                raise errors.LockContention(self.filename, e)
 
233
 
 
234
        def unlock(self):
 
235
            _fcntl_WriteLock._open_locks.remove(self.filename)
 
236
            self._unlock()
 
237
 
 
238
    class _fcntl_ReadLock(_fcntl_FileLock):
 
239
 
 
240
        _open_locks = {}
 
241
 
 
242
        def __init__(self, filename):
 
243
            super(_fcntl_ReadLock, self).__init__()
 
244
            self.filename = osutils.realpath(filename)
 
245
            if self.filename in _fcntl_WriteLock._open_locks:
 
246
                if 'strict_locks' in debug.debug_flags:
 
247
                    # We raise before calling _open so we don't need to
 
248
                    # _clear_f
 
249
                    raise errors.LockContention(self.filename)
 
250
                else:
 
251
                    trace.mutter('Read lock taken w/ an open write lock on: %s'
 
252
                                 % (self.filename,))
 
253
            _fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
 
254
            _fcntl_ReadLock._open_locks[self.filename] += 1
 
255
            self._open(filename, 'rb')
 
256
            try:
 
257
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
258
                # lock right away.
 
259
                fcntl.lockf(self.f, fcntl.LOCK_SH | fcntl.LOCK_NB)
 
260
            except IOError as e:
 
261
                # we should be more precise about whats a locking
 
262
                # error and whats a random-other error
 
263
                raise errors.LockContention(self.filename, e)
 
264
 
 
265
        def unlock(self):
 
266
            count = _fcntl_ReadLock._open_locks[self.filename]
 
267
            if count == 1:
 
268
                del _fcntl_ReadLock._open_locks[self.filename]
 
269
            else:
 
270
                _fcntl_ReadLock._open_locks[self.filename] = count - 1
 
271
            self._unlock()
 
272
 
 
273
        def temporary_write_lock(self):
 
274
            """Try to grab a write lock on the file.
 
275
 
 
276
            On platforms that support it, this will upgrade to a write lock
 
277
            without unlocking the file.
 
278
            Otherwise, this will release the read lock, and try to acquire a
 
279
            write lock.
 
280
 
 
281
            :return: A token which can be used to switch back to a read lock.
 
282
            """
 
283
            if self.filename in _fcntl_WriteLock._open_locks:
 
284
                raise AssertionError('file already locked: %r'
 
285
                                     % (self.filename,))
 
286
            try:
 
287
                wlock = _fcntl_TemporaryWriteLock(self)
 
288
            except errors.LockError:
 
289
                # We didn't unlock, so we can just return 'self'
 
290
                return False, self
 
291
            return True, wlock
 
292
 
 
293
    class _fcntl_TemporaryWriteLock(_OSLock):
 
294
        """A token used when grabbing a temporary_write_lock.
 
295
 
 
296
        Call restore_read_lock() when you are done with the write lock.
 
297
        """
 
298
 
 
299
        def __init__(self, read_lock):
 
300
            super(_fcntl_TemporaryWriteLock, self).__init__()
 
301
            self._read_lock = read_lock
 
302
            self.filename = read_lock.filename
 
303
 
 
304
            count = _fcntl_ReadLock._open_locks[self.filename]
 
305
            if count > 1:
 
306
                # Something else also has a read-lock, so we cannot grab a
 
307
                # write lock.
 
308
                raise errors.LockContention(self.filename)
 
309
 
 
310
            if self.filename in _fcntl_WriteLock._open_locks:
 
311
                raise AssertionError('file already locked: %r'
 
312
                                     % (self.filename,))
 
313
 
 
314
            # See if we can open the file for writing. Another process might
 
315
            # have a read lock. We don't use self._open() because we don't want
 
316
            # to create the file if it exists. That would have already been
 
317
            # done by _fcntl_ReadLock
 
318
            try:
 
319
                new_f = open(self.filename, 'rb+')
 
320
            except IOError as e:
 
321
                if e.errno in (errno.EACCES, errno.EPERM):
 
322
                    raise errors.LockFailed(self.filename, str(e))
 
323
                raise
 
324
            try:
 
325
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
326
                # lock right away.
 
327
                fcntl.lockf(new_f, fcntl.LOCK_EX | fcntl.LOCK_NB)
 
328
            except IOError as e:
 
329
                # TODO: Raise a more specific error based on the type of error
 
330
                raise errors.LockContention(self.filename, e)
 
331
            _fcntl_WriteLock._open_locks.add(self.filename)
 
332
 
 
333
            self.f = new_f
 
334
 
 
335
        def restore_read_lock(self):
 
336
            """Restore the original ReadLock."""
 
337
            # For fcntl, since we never released the read lock, just release
 
338
            # the write lock, and return the original lock.
 
339
            fcntl.lockf(self.f, fcntl.LOCK_UN)
 
340
            self._clear_f()
 
341
            _fcntl_WriteLock._open_locks.remove(self.filename)
 
342
            # Avoid reference cycles
 
343
            read_lock = self._read_lock
 
344
            self._read_lock = None
 
345
            return read_lock
 
346
 
 
347
    _lock_classes.append(('fcntl', _fcntl_WriteLock, _fcntl_ReadLock))
 
348
 
 
349
 
 
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"
 
355
 
 
356
    # CreateFile <http://msdn.microsoft.com/en-us/library/aa363858.aspx>
 
357
    _CreateFile = ctypes.WINFUNCTYPE(
 
358
        HANDLE,                # return value
 
359
        LPWSTR,                # lpFileName
 
360
        DWORD,                 # dwDesiredAccess
 
361
        DWORD,                 # dwShareMode
 
362
        LPSECURITY_ATTRIBUTES,  # lpSecurityAttributes
 
363
        DWORD,                 # dwCreationDisposition
 
364
        DWORD,                 # dwFlagsAndAttributes
 
365
        HANDLE                 # hTemplateFile
 
366
        )((_function_name, ctypes.windll.kernel32))
 
367
 
 
368
    INVALID_HANDLE_VALUE = -1
 
369
 
 
370
    GENERIC_READ = 0x80000000
 
371
    GENERIC_WRITE = 0x40000000
 
372
    FILE_SHARE_READ = 1
 
373
    OPEN_ALWAYS = 4
 
374
    FILE_ATTRIBUTE_NORMAL = 128
 
375
 
 
376
    ERROR_ACCESS_DENIED = 5
 
377
    ERROR_SHARING_VIOLATION = 32
 
378
 
 
379
    class _ctypes_FileLock(_OSLock):
 
380
 
 
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)
 
391
                raise e
 
392
            fd = msvcrt.open_osfhandle(handle, cflags)
 
393
            self.f = os.fdopen(fd, pymode)
 
394
            return self.f
 
395
 
 
396
        def unlock(self):
 
397
            self._clear_f()
 
398
 
 
399
    class _ctypes_ReadLock(_ctypes_FileLock):
 
400
        def __init__(self, filename):
 
401
            super(_ctypes_ReadLock, self).__init__()
 
402
            self._open(filename, GENERIC_READ, FILE_SHARE_READ, os.O_RDONLY,
 
403
                       "rb")
 
404
 
 
405
        def temporary_write_lock(self):
 
406
            """Try to grab a write lock on the file.
 
407
 
 
408
            On platforms that support it, this will upgrade to a write lock
 
409
            without unlocking the file.
 
410
            Otherwise, this will release the read lock, and try to acquire a
 
411
            write lock.
 
412
 
 
413
            :return: A token which can be used to switch back to a read lock.
 
414
            """
 
415
            # I can't find a way to upgrade a read lock to a write lock without
 
416
            # unlocking first. So here, we do just that.
 
417
            self.unlock()
 
418
            try:
 
419
                wlock = _ctypes_WriteLock(self.filename)
 
420
            except errors.LockError:
 
421
                return False, _ctypes_ReadLock(self.filename)
 
422
            return True, wlock
 
423
 
 
424
    class _ctypes_WriteLock(_ctypes_FileLock):
 
425
        def __init__(self, filename):
 
426
            super(_ctypes_WriteLock, self).__init__()
 
427
            self._open(filename, GENERIC_READ | GENERIC_WRITE, 0, os.O_RDWR,
 
428
                       "rb+")
 
429
 
 
430
        def restore_read_lock(self):
 
431
            """Restore the original ReadLock."""
 
432
            # For win32 we had to completely let go of the original lock, so we
 
433
            # just unlock and create a new read lock.
 
434
            self.unlock()
 
435
            return _ctypes_ReadLock(self.filename)
 
436
 
 
437
    _lock_classes.append(('ctypes', _ctypes_WriteLock, _ctypes_ReadLock))
 
438
 
 
439
 
 
440
if len(_lock_classes) == 0:
 
441
    raise NotImplementedError(
 
442
        "We must have one of fcntl or ctypes available"
 
443
        " to support OS locking."
 
444
        )
 
445
 
 
446
 
 
447
# We default to using the first available lock class.
 
448
_lock_type, WriteLock, ReadLock = _lock_classes[0]
 
449
 
 
450
 
 
451
class _RelockDebugMixin(object):
 
452
    """Mixin support for -Drelock flag.
 
453
 
 
454
    Add this as a base class then call self._note_lock with 'r' or 'w' when
 
455
    acquiring a read- or write-lock.  If this object was previously locked (and
 
456
    locked the same way), and -Drelock is set, then this will trace.note a
 
457
    message about it.
 
458
    """
 
459
 
 
460
    _prev_lock = None
 
461
 
 
462
    def _note_lock(self, lock_type):
 
463
        if 'relock' in debug.debug_flags and self._prev_lock == lock_type:
 
464
            if lock_type == 'r':
 
465
                type_name = 'read'
 
466
            else:
 
467
                type_name = 'write'
 
468
            trace.note(gettext('{0!r} was {1} locked again'), self, type_name)
 
469
        self._prev_lock = lock_type
 
470
 
 
471
 
 
472
@contextlib.contextmanager
 
473
def write_locked(lockable):
 
474
    lockable.lock_write()
 
475
    try:
 
476
        yield lockable
 
477
    finally:
 
478
        lockable.unlock()