~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lock.py

  • Committer: Jelmer Vernooij
  • Date: 2012-01-28 02:10:19 UTC
  • mto: This revision was merged to the branch mainline in revision 6450.
  • Revision ID: jelmer@samba.org-20120128021019-047mhvjsr9t72xfi
Fix reading weave files.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
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
16
 
 
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
16
 
18
17
"""Locking using OS file locks or file existence.
19
18
 
26
25
 
27
26
It is not specified whether these locks are reentrant (i.e. can be
28
27
taken repeatedly by a single process) or whether they exclude
29
 
different threads in a single process.  That reentrancy is provided by 
 
28
different threads in a single process.  That reentrancy is provided by
30
29
LockableFiles.
31
30
 
32
31
This defines two classes: ReadLock and WriteLock, which can be
34
33
unlock() method.
35
34
"""
36
35
 
 
36
from __future__ import absolute_import
 
37
 
37
38
import errno
38
39
import os
39
40
import sys
40
 
 
41
 
from bzrlib.errors import LockError
42
 
from bzrlib.osutils import realpath
43
 
from bzrlib.trace import mutter
44
 
 
45
 
 
46
 
class _base_Lock(object):
 
41
import warnings
 
42
 
 
43
from bzrlib import (
 
44
    debug,
 
45
    errors,
 
46
    osutils,
 
47
    trace,
 
48
    )
 
49
from bzrlib.hooks import Hooks
 
50
from bzrlib.i18n import gettext
 
51
 
 
52
class LockHooks(Hooks):
 
53
 
 
54
    def __init__(self):
 
55
        Hooks.__init__(self, "bzrlib.lock", "Lock.hooks")
 
56
        self.add_hook('lock_acquired',
 
57
            "Called with a bzrlib.lock.LockResult when a physical lock is "
 
58
            "acquired.", (1, 8))
 
59
        self.add_hook('lock_released',
 
60
            "Called with a bzrlib.lock.LockResult when a physical lock is "
 
61
            "released.", (1, 8))
 
62
        self.add_hook('lock_broken',
 
63
            "Called with a bzrlib.lock.LockResult when a physical lock is "
 
64
            "broken.", (1, 15))
 
65
 
 
66
 
 
67
class Lock(object):
 
68
    """Base class for locks.
 
69
 
 
70
    :cvar hooks: Hook dictionary for operations on locks.
 
71
    """
 
72
 
 
73
    hooks = LockHooks()
 
74
 
 
75
 
 
76
class LockResult(object):
 
77
    """Result of an operation on a lock; passed to a hook"""
 
78
 
 
79
    def __init__(self, lock_url, details=None):
 
80
        """Create a lock result for lock with optional details about the lock."""
 
81
        self.lock_url = lock_url
 
82
        self.details = details
 
83
 
 
84
    def __eq__(self, other):
 
85
        return self.lock_url == other.lock_url and self.details == other.details
 
86
 
 
87
    def __repr__(self):
 
88
        return '%s(%s, %s)' % (self.__class__.__name__,
 
89
                             self.lock_url, self.details)
 
90
 
 
91
 
 
92
class LogicalLockResult(object):
 
93
    """The result of a lock_read/lock_write/lock_tree_write call on lockables.
 
94
 
 
95
    :ivar unlock: A callable which will unlock the lock.
 
96
    """
 
97
 
 
98
    def __init__(self, unlock):
 
99
        self.unlock = unlock
 
100
 
 
101
    def __repr__(self):
 
102
        return "LogicalLockResult(%s)" % (self.unlock)
 
103
 
 
104
 
 
105
 
 
106
def cant_unlock_not_held(locked_object):
 
107
    """An attempt to unlock failed because the object was not locked.
 
108
 
 
109
    This provides a policy point from which we can generate either a warning 
 
110
    or an exception.
 
111
    """
 
112
    # This is typically masking some other error and called from a finally
 
113
    # block, so it's useful to have the option not to generate a new error
 
114
    # here.  You can use -Werror to make it fatal.  It should possibly also
 
115
    # raise LockNotHeld.
 
116
    if 'unlock' in debug.debug_flags:
 
117
        warnings.warn("%r is already unlocked" % (locked_object,),
 
118
            stacklevel=3)
 
119
    else:
 
120
        raise errors.LockNotHeld(locked_object)
 
121
 
 
122
 
 
123
try:
 
124
    import fcntl
 
125
    have_fcntl = True
 
126
except ImportError:
 
127
    have_fcntl = False
 
128
 
 
129
have_pywin32 = False
 
130
have_ctypes_win32 = False
 
131
if sys.platform == 'win32':
 
132
    import msvcrt
 
133
    try:
 
134
        import win32file, pywintypes, winerror
 
135
        have_pywin32 = True
 
136
    except ImportError:
 
137
        pass
 
138
 
 
139
    try:
 
140
        import ctypes
 
141
        have_ctypes_win32 = True
 
142
    except ImportError:
 
143
        pass
 
144
 
 
145
 
 
146
class _OSLock(object):
 
147
 
 
148
    def __init__(self):
 
149
        self.f = None
 
150
        self.filename = None
47
151
 
48
152
    def _open(self, filename, filemode):
 
153
        self.filename = osutils.realpath(filename)
49
154
        try:
50
 
            self.f = open(filename, filemode)
 
155
            self.f = open(self.filename, filemode)
51
156
            return self.f
52
157
        except IOError, e:
 
158
            if e.errno in (errno.EACCES, errno.EPERM):
 
159
                raise errors.LockFailed(self.filename, str(e))
53
160
            if e.errno != errno.ENOENT:
54
161
                raise
55
162
 
56
163
            # maybe this is an old branch (before may 2005)
57
 
            mutter("trying to create missing branch lock %r", filename)
58
 
            
59
 
            self.f = open(filename, 'wb+')
 
164
            trace.mutter("trying to create missing lock %r", self.filename)
 
165
 
 
166
            self.f = open(self.filename, 'wb+')
60
167
            return self.f
61
168
 
62
 
    def __del__(self):
 
169
    def _clear_f(self):
 
170
        """Clear the self.f attribute cleanly."""
63
171
        if self.f:
64
 
            from warnings import warn
65
 
            warn("lock on %r not released" % self.f)
66
 
            self.unlock()
67
 
            
 
172
            self.f.close()
 
173
            self.f = None
 
174
 
68
175
    def unlock(self):
69
176
        raise NotImplementedError()
70
177
 
71
178
 
72
 
############################################################
73
 
# msvcrt locks
74
 
 
75
 
 
76
 
try:
77
 
    import fcntl
78
 
 
79
 
    class _fcntl_FileLock(_base_Lock):
80
 
 
81
 
        f = None
 
179
_lock_classes = []
 
180
 
 
181
 
 
182
if have_fcntl:
 
183
 
 
184
    class _fcntl_FileLock(_OSLock):
82
185
 
83
186
        def _unlock(self):
84
187
            fcntl.lockf(self.f, fcntl.LOCK_UN)
85
188
            self._clear_f()
86
189
 
87
 
        def _clear_f(self):
88
 
            """Clear the self.f attribute cleanly."""
89
 
            self.f.close()
90
 
            del self.f 
91
 
 
92
190
 
93
191
    class _fcntl_WriteLock(_fcntl_FileLock):
94
192
 
95
 
        open_locks = {}
 
193
        _open_locks = set()
96
194
 
97
195
        def __init__(self, filename):
98
 
            # standard IO errors get exposed directly.
99
 
            self._open(filename, 'wb')
 
196
            super(_fcntl_WriteLock, self).__init__()
 
197
            # Check we can grab a lock before we actually open the file.
 
198
            self.filename = osutils.realpath(filename)
 
199
            if self.filename in _fcntl_WriteLock._open_locks:
 
200
                self._clear_f()
 
201
                raise errors.LockContention(self.filename)
 
202
            if self.filename in _fcntl_ReadLock._open_locks:
 
203
                if 'strict_locks' in debug.debug_flags:
 
204
                    self._clear_f()
 
205
                    raise errors.LockContention(self.filename)
 
206
                else:
 
207
                    trace.mutter('Write lock taken w/ an open read lock on: %s'
 
208
                                 % (self.filename,))
 
209
 
 
210
            self._open(self.filename, 'rb+')
 
211
            # reserve a slot for this lock - even if the lockf call fails,
 
212
            # at this point unlock() will be called, because self.f is set.
 
213
            # TODO: make this fully threadsafe, if we decide we care.
 
214
            _fcntl_WriteLock._open_locks.add(self.filename)
100
215
            try:
101
 
                self.filename = realpath(filename)
102
 
                if self.filename in self.open_locks:
103
 
                    self._clear_f() 
104
 
                    raise LockError("Lock already held.")
105
 
                # reserve a slot for this lock - even if the lockf call fails, 
106
 
                # at thisi point unlock() will be called, because self.f is set.
107
 
                # TODO: make this fully threadsafe, if we decide we care.
108
 
                self.open_locks[self.filename] = self.filename
109
 
                fcntl.lockf(self.f, fcntl.LOCK_EX)
 
216
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
217
                # lock right away.
 
218
                fcntl.lockf(self.f, fcntl.LOCK_EX | fcntl.LOCK_NB)
110
219
            except IOError, e:
 
220
                if e.errno in (errno.EAGAIN, errno.EACCES):
 
221
                    # We couldn't grab the lock
 
222
                    self.unlock()
111
223
                # we should be more precise about whats a locking
112
224
                # error and whats a random-other error
113
 
                raise LockError(e)
 
225
                raise errors.LockContention(self.filename, e)
114
226
 
115
227
        def unlock(self):
116
 
            del self.open_locks[self.filename]
 
228
            _fcntl_WriteLock._open_locks.remove(self.filename)
117
229
            self._unlock()
118
230
 
119
231
 
120
232
    class _fcntl_ReadLock(_fcntl_FileLock):
121
233
 
 
234
        _open_locks = {}
 
235
 
122
236
        def __init__(self, filename):
123
 
            # standard IO errors get exposed directly.
 
237
            super(_fcntl_ReadLock, self).__init__()
 
238
            self.filename = osutils.realpath(filename)
 
239
            if self.filename in _fcntl_WriteLock._open_locks:
 
240
                if 'strict_locks' in debug.debug_flags:
 
241
                    # We raise before calling _open so we don't need to
 
242
                    # _clear_f
 
243
                    raise errors.LockContention(self.filename)
 
244
                else:
 
245
                    trace.mutter('Read lock taken w/ an open write lock on: %s'
 
246
                                 % (self.filename,))
 
247
            _fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
 
248
            _fcntl_ReadLock._open_locks[self.filename] += 1
124
249
            self._open(filename, 'rb')
125
250
            try:
126
 
                fcntl.lockf(self.f, fcntl.LOCK_SH)
 
251
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
252
                # lock right away.
 
253
                fcntl.lockf(self.f, fcntl.LOCK_SH | fcntl.LOCK_NB)
127
254
            except IOError, e:
128
255
                # we should be more precise about whats a locking
129
256
                # error and whats a random-other error
130
 
                raise LockError(e)
 
257
                raise errors.LockContention(self.filename, e)
131
258
 
132
259
        def unlock(self):
 
260
            count = _fcntl_ReadLock._open_locks[self.filename]
 
261
            if count == 1:
 
262
                del _fcntl_ReadLock._open_locks[self.filename]
 
263
            else:
 
264
                _fcntl_ReadLock._open_locks[self.filename] = count - 1
133
265
            self._unlock()
134
266
 
135
 
 
136
 
    WriteLock = _fcntl_WriteLock
137
 
    ReadLock = _fcntl_ReadLock
138
 
 
139
 
 
140
 
except ImportError:
141
 
    try:
142
 
        import win32con, win32file, pywintypes
143
 
 
144
 
 
145
 
        LOCK_SH = 0 # the default
146
 
        LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
147
 
        LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
148
 
 
149
 
        class _w32c_FileLock(_base_Lock):
150
 
            def _lock(self, filename, openmode, lockmode):
151
 
                try:
152
 
                    self._open(filename, openmode)
153
 
                    self.hfile = win32file._get_osfhandle(self.f.fileno())
154
 
                    overlapped = pywintypes.OVERLAPPED()
155
 
                    win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000, overlapped)
156
 
                except Exception, e:
157
 
                    if self.f:
158
 
                        self.f.close()
159
 
                        self.f = None
160
 
                    raise LockError(e)
161
 
 
162
 
            def unlock(self):
163
 
                try:
164
 
                    overlapped = pywintypes.OVERLAPPED()
165
 
                    win32file.UnlockFileEx(self.hfile, 0, 0x7fff0000, overlapped)
166
 
                    self.f.close()
167
 
                    self.f = None
168
 
                except Exception, e:
169
 
                    raise LockError(e)
170
 
 
171
 
 
172
 
        class _w32c_ReadLock(_w32c_FileLock):
173
 
            def __init__(self, filename):
174
 
                _w32c_FileLock._lock(self, filename, 'rb',
175
 
                                     LOCK_NB)
176
 
 
177
 
        class _w32c_WriteLock(_w32c_FileLock):
178
 
            def __init__(self, filename):
179
 
                _w32c_FileLock._lock(self, filename, 'wb',
180
 
                                     LOCK_EX + LOCK_NB)
181
 
 
182
 
 
183
 
        WriteLock = _w32c_WriteLock
184
 
        ReadLock = _w32c_ReadLock
185
 
 
186
 
    except ImportError:
187
 
        try:
188
 
            import msvcrt
189
 
 
190
 
 
191
 
            # Unfortunately, msvcrt.locking() doesn't distinguish between
192
 
            # read locks and write locks. Also, the way the combinations
193
 
            # work to get non-blocking is not the same, so we
194
 
            # have to write extra special functions here.
195
 
 
196
 
 
197
 
            class _msvc_FileLock(_base_Lock):
198
 
                LOCK_SH = 1
199
 
                LOCK_EX = 2
200
 
                LOCK_NB = 4
201
 
                def unlock(self):
202
 
                    _msvc_unlock(self.f)
203
 
                    self.f.close()
204
 
                    self.f = None
205
 
 
206
 
 
207
 
            class _msvc_ReadLock(_msvc_FileLock):
208
 
                def __init__(self, filename):
209
 
                    _msvc_lock(self._open(filename, 'rb'), self.LOCK_SH)
210
 
 
211
 
 
212
 
            class _msvc_WriteLock(_msvc_FileLock):
213
 
                def __init__(self, filename):
214
 
                    _msvc_lock(self._open(filename, 'wb'), self.LOCK_EX)
215
 
 
216
 
 
217
 
            def _msvc_lock(f, flags):
218
 
                try:
219
 
                    # Unfortunately, msvcrt.LK_RLCK is equivalent to msvcrt.LK_LOCK
220
 
                    # according to the comments, LK_RLCK is open the lock for writing.
221
 
 
222
 
                    # Unfortunately, msvcrt.locking() also has the side effect that it
223
 
                    # will only block for 10 seconds at most, and then it will throw an
224
 
                    # exception, this isn't terrible, though.
225
 
                    if type(f) == file:
226
 
                        fpos = f.tell()
227
 
                        fn = f.fileno()
228
 
                        f.seek(0)
229
 
                    else:
230
 
                        fn = f
231
 
                        fpos = os.lseek(fn, 0,0)
232
 
                        os.lseek(fn, 0,0)
233
 
 
234
 
                    if flags & _msvc_FileLock.LOCK_SH:
235
 
                        if flags & _msvc_FileLock.LOCK_NB:
236
 
                            lock_mode = msvcrt.LK_NBLCK
237
 
                        else:
238
 
                            lock_mode = msvcrt.LK_LOCK
239
 
                    elif flags & _msvc_FileLock.LOCK_EX:
240
 
                        if flags & _msvc_FileLock.LOCK_NB:
241
 
                            lock_mode = msvcrt.LK_NBRLCK
242
 
                        else:
243
 
                            lock_mode = msvcrt.LK_RLCK
244
 
                    else:
245
 
                        raise ValueError('Invalid lock mode: %r' % flags)
246
 
                    try:
247
 
                        msvcrt.locking(fn, lock_mode, -1)
248
 
                    finally:
249
 
                        os.lseek(fn, fpos, 0)
250
 
                except Exception, e:
251
 
                    raise LockError(e)
252
 
 
253
 
            def _msvc_unlock(f):
254
 
                try:
255
 
                    if type(f) == file:
256
 
                        fpos = f.tell()
257
 
                        fn = f.fileno()
258
 
                        f.seek(0)
259
 
                    else:
260
 
                        fn = f
261
 
                        fpos = os.lseek(fn, 0,0)
262
 
                        os.lseek(fn, 0,0)
263
 
 
264
 
                    try:
265
 
                        msvcrt.locking(fn, msvcrt.LK_UNLCK, -1)
266
 
                    finally:
267
 
                        os.lseek(fn, fpos, 0)
268
 
                except Exception, e:
269
 
                    raise LockError(e)
270
 
 
271
 
 
272
 
            WriteLock = _msvc_WriteLock
273
 
            ReadLock = _msvc_ReadLock
274
 
        except ImportError:
275
 
            raise NotImplementedError("please write a locking method "
276
 
                                      "for platform %r" % sys.platform)
 
267
        def temporary_write_lock(self):
 
268
            """Try to grab a write lock on the file.
 
269
 
 
270
            On platforms that support it, this will upgrade to a write lock
 
271
            without unlocking the file.
 
272
            Otherwise, this will release the read lock, and try to acquire a
 
273
            write lock.
 
274
 
 
275
            :return: A token which can be used to switch back to a read lock.
 
276
            """
 
277
            if self.filename in _fcntl_WriteLock._open_locks:
 
278
                raise AssertionError('file already locked: %r'
 
279
                    % (self.filename,))
 
280
            try:
 
281
                wlock = _fcntl_TemporaryWriteLock(self)
 
282
            except errors.LockError:
 
283
                # We didn't unlock, so we can just return 'self'
 
284
                return False, self
 
285
            return True, wlock
 
286
 
 
287
 
 
288
    class _fcntl_TemporaryWriteLock(_OSLock):
 
289
        """A token used when grabbing a temporary_write_lock.
 
290
 
 
291
        Call restore_read_lock() when you are done with the write lock.
 
292
        """
 
293
 
 
294
        def __init__(self, read_lock):
 
295
            super(_fcntl_TemporaryWriteLock, self).__init__()
 
296
            self._read_lock = read_lock
 
297
            self.filename = read_lock.filename
 
298
 
 
299
            count = _fcntl_ReadLock._open_locks[self.filename]
 
300
            if count > 1:
 
301
                # Something else also has a read-lock, so we cannot grab a
 
302
                # write lock.
 
303
                raise errors.LockContention(self.filename)
 
304
 
 
305
            if self.filename in _fcntl_WriteLock._open_locks:
 
306
                raise AssertionError('file already locked: %r'
 
307
                    % (self.filename,))
 
308
 
 
309
            # See if we can open the file for writing. Another process might
 
310
            # have a read lock. We don't use self._open() because we don't want
 
311
            # to create the file if it exists. That would have already been
 
312
            # done by _fcntl_ReadLock
 
313
            try:
 
314
                new_f = open(self.filename, 'rb+')
 
315
            except IOError, e:
 
316
                if e.errno in (errno.EACCES, errno.EPERM):
 
317
                    raise errors.LockFailed(self.filename, str(e))
 
318
                raise
 
319
            try:
 
320
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
321
                # lock right away.
 
322
                fcntl.lockf(new_f, fcntl.LOCK_EX | fcntl.LOCK_NB)
 
323
            except IOError, e:
 
324
                # TODO: Raise a more specific error based on the type of error
 
325
                raise errors.LockContention(self.filename, e)
 
326
            _fcntl_WriteLock._open_locks.add(self.filename)
 
327
 
 
328
            self.f = new_f
 
329
 
 
330
        def restore_read_lock(self):
 
331
            """Restore the original ReadLock."""
 
332
            # For fcntl, since we never released the read lock, just release the
 
333
            # write lock, and return the original lock.
 
334
            fcntl.lockf(self.f, fcntl.LOCK_UN)
 
335
            self._clear_f()
 
336
            _fcntl_WriteLock._open_locks.remove(self.filename)
 
337
            # Avoid reference cycles
 
338
            read_lock = self._read_lock
 
339
            self._read_lock = None
 
340
            return read_lock
 
341
 
 
342
 
 
343
    _lock_classes.append(('fcntl', _fcntl_WriteLock, _fcntl_ReadLock))
 
344
 
 
345
 
 
346
if have_pywin32 and sys.platform == 'win32':
 
347
    if os.path.supports_unicode_filenames:
 
348
        # for Windows NT/2K/XP/etc
 
349
        win32file_CreateFile = win32file.CreateFileW
 
350
    else:
 
351
        # for Windows 98
 
352
        win32file_CreateFile = win32file.CreateFile
 
353
 
 
354
    class _w32c_FileLock(_OSLock):
 
355
 
 
356
        def _open(self, filename, access, share, cflags, pymode):
 
357
            self.filename = osutils.realpath(filename)
 
358
            try:
 
359
                self._handle = win32file_CreateFile(filename, access, share,
 
360
                    None, win32file.OPEN_ALWAYS,
 
361
                    win32file.FILE_ATTRIBUTE_NORMAL, None)
 
362
            except pywintypes.error, e:
 
363
                if e.args[0] == winerror.ERROR_ACCESS_DENIED:
 
364
                    raise errors.LockFailed(filename, e)
 
365
                if e.args[0] == winerror.ERROR_SHARING_VIOLATION:
 
366
                    raise errors.LockContention(filename, e)
 
367
                raise
 
368
            fd = win32file._open_osfhandle(self._handle, cflags)
 
369
            self.f = os.fdopen(fd, pymode)
 
370
            return self.f
 
371
 
 
372
        def unlock(self):
 
373
            self._clear_f()
 
374
            self._handle = None
 
375
 
 
376
 
 
377
    class _w32c_ReadLock(_w32c_FileLock):
 
378
        def __init__(self, filename):
 
379
            super(_w32c_ReadLock, self).__init__()
 
380
            self._open(filename, win32file.GENERIC_READ,
 
381
                win32file.FILE_SHARE_READ, os.O_RDONLY, "rb")
 
382
 
 
383
        def temporary_write_lock(self):
 
384
            """Try to grab a write lock on the file.
 
385
 
 
386
            On platforms that support it, this will upgrade to a write lock
 
387
            without unlocking the file.
 
388
            Otherwise, this will release the read lock, and try to acquire a
 
389
            write lock.
 
390
 
 
391
            :return: A token which can be used to switch back to a read lock.
 
392
            """
 
393
            # I can't find a way to upgrade a read lock to a write lock without
 
394
            # unlocking first. So here, we do just that.
 
395
            self.unlock()
 
396
            try:
 
397
                wlock = _w32c_WriteLock(self.filename)
 
398
            except errors.LockError:
 
399
                return False, _w32c_ReadLock(self.filename)
 
400
            return True, wlock
 
401
 
 
402
 
 
403
    class _w32c_WriteLock(_w32c_FileLock):
 
404
        def __init__(self, filename):
 
405
            super(_w32c_WriteLock, self).__init__()
 
406
            self._open(filename,
 
407
                win32file.GENERIC_READ | win32file.GENERIC_WRITE, 0,
 
408
                os.O_RDWR, "rb+")
 
409
 
 
410
        def restore_read_lock(self):
 
411
            """Restore the original ReadLock."""
 
412
            # For win32 we had to completely let go of the original lock, so we
 
413
            # just unlock and create a new read lock.
 
414
            self.unlock()
 
415
            return _w32c_ReadLock(self.filename)
 
416
 
 
417
 
 
418
    _lock_classes.append(('pywin32', _w32c_WriteLock, _w32c_ReadLock))
 
419
 
 
420
 
 
421
if have_ctypes_win32:
 
422
    from ctypes.wintypes import DWORD, LPCSTR, LPCWSTR
 
423
    LPSECURITY_ATTRIBUTES = ctypes.c_void_p # used as NULL no need to declare
 
424
    HANDLE = ctypes.c_int # rather than unsigned as in ctypes.wintypes
 
425
    if os.path.supports_unicode_filenames:
 
426
        _function_name = "CreateFileW"
 
427
        LPTSTR = LPCWSTR
 
428
    else:
 
429
        _function_name = "CreateFileA"
 
430
        class LPTSTR(LPCSTR):
 
431
            def __new__(cls, obj):
 
432
                return LPCSTR.__new__(cls, obj.encode("mbcs"))
 
433
 
 
434
    # CreateFile <http://msdn.microsoft.com/en-us/library/aa363858.aspx>
 
435
    _CreateFile = ctypes.WINFUNCTYPE(
 
436
            HANDLE,                # return value
 
437
            LPTSTR,                # 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
 
 
478
    class _ctypes_ReadLock(_ctypes_FileLock):
 
479
        def __init__(self, filename):
 
480
            super(_ctypes_ReadLock, self).__init__()
 
481
            self._open(filename, GENERIC_READ, FILE_SHARE_READ, os.O_RDONLY,
 
482
                "rb")
 
483
 
 
484
        def temporary_write_lock(self):
 
485
            """Try to grab a write lock on the file.
 
486
 
 
487
            On platforms that support it, this will upgrade to a write lock
 
488
            without unlocking the file.
 
489
            Otherwise, this will release the read lock, and try to acquire a
 
490
            write lock.
 
491
 
 
492
            :return: A token which can be used to switch back to a read lock.
 
493
            """
 
494
            # I can't find a way to upgrade a read lock to a write lock without
 
495
            # unlocking first. So here, we do just that.
 
496
            self.unlock()
 
497
            try:
 
498
                wlock = _ctypes_WriteLock(self.filename)
 
499
            except errors.LockError:
 
500
                return False, _ctypes_ReadLock(self.filename)
 
501
            return True, wlock
 
502
 
 
503
    class _ctypes_WriteLock(_ctypes_FileLock):
 
504
        def __init__(self, filename):
 
505
            super(_ctypes_WriteLock, self).__init__()
 
506
            self._open(filename, GENERIC_READ | GENERIC_WRITE, 0, os.O_RDWR,
 
507
                "rb+")
 
508
 
 
509
        def restore_read_lock(self):
 
510
            """Restore the original ReadLock."""
 
511
            # For win32 we had to completely let go of the original lock, so we
 
512
            # just unlock and create a new read lock.
 
513
            self.unlock()
 
514
            return _ctypes_ReadLock(self.filename)
 
515
 
 
516
 
 
517
    _lock_classes.append(('ctypes', _ctypes_WriteLock, _ctypes_ReadLock))
 
518
 
 
519
 
 
520
if len(_lock_classes) == 0:
 
521
    raise NotImplementedError(
 
522
        "We must have one of fcntl, pywin32, or ctypes available"
 
523
        " to support OS locking."
 
524
        )
 
525
 
 
526
 
 
527
# We default to using the first available lock class.
 
528
_lock_type, WriteLock, ReadLock = _lock_classes[0]
 
529
 
 
530
 
 
531
class _RelockDebugMixin(object):
 
532
    """Mixin support for -Drelock flag.
 
533
 
 
534
    Add this as a base class then call self._note_lock with 'r' or 'w' when
 
535
    acquiring a read- or write-lock.  If this object was previously locked (and
 
536
    locked the same way), and -Drelock is set, then this will trace.note a
 
537
    message about it.
 
538
    """
 
539
 
 
540
    _prev_lock = None
 
541
 
 
542
    def _note_lock(self, lock_type):
 
543
        if 'relock' in debug.debug_flags and self._prev_lock == lock_type:
 
544
            if lock_type == 'r':
 
545
                type_name = 'read'
 
546
            else:
 
547
                type_name = 'write'
 
548
            trace.note(gettext('{0!r} was {1} locked again'), self, type_name)
 
549
        self._prev_lock = lock_type
 
550