/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: Jelmer Vernooij
  • Date: 2019-08-12 20:24:50 UTC
  • mto: (7290.1.35 work)
  • mto: This revision was merged to the branch mainline in revision 7405.
  • Revision ID: jelmer@jelmer.uk-20190812202450-vdpamxay6sebo93w
Fix path to brz.

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