~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lock.py

  • Committer: Vincent Ladeuil
  • Date: 2011-12-21 14:25:26 UTC
  • mto: This revision was merged to the branch mainline in revision 6397.
  • Revision ID: v.ladeuil+lp@free.fr-20111221142526-pnwau0xnalimujts
Provides MemoryStack to simplify configuration setup in tests

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Canonical Ltd
2
 
 
 
1
# Copyright (C) 2005-2010 Canonical Ltd
 
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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
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
 
 
17
 
 
18
 
"""Locking wrappers.
19
 
 
20
 
This only does local locking using OS locks for now.
 
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.
21
22
 
22
23
This module causes two methods, lock() and unlock() to be defined in
23
24
any way that works on the current platform.
24
25
 
25
26
It is not specified whether these locks are reentrant (i.e. can be
26
27
taken repeatedly by a single process) or whether they exclude
27
 
different threads in a single process.  
28
 
 
29
 
Eventually we may need to use some kind of lock representation that
30
 
will work on a dumb filesystem without actual locking primitives.
 
28
different threads in a single process.  That reentrancy is provided by
 
29
LockableFiles.
31
30
 
32
31
This defines two classes: ReadLock and WriteLock, which can be
33
32
implemented in different ways on different platforms.  Both have an
34
33
unlock() method.
35
34
"""
36
35
 
 
36
from __future__ import absolute_import
37
37
 
 
38
import errno
 
39
import os
38
40
import sys
39
 
import os
40
 
 
41
 
from bzrlib.trace import mutter, note, warning
42
 
from bzrlib.errors import LockError
43
 
 
44
 
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
 
151
 
45
152
    def _open(self, filename, filemode):
46
 
        import errno
 
153
        self.filename = osutils.realpath(filename)
47
154
        try:
48
 
            self.f = open(filename, filemode)
 
155
            self.f = open(self.filename, filemode)
49
156
            return self.f
50
157
        except IOError, e:
 
158
            if e.errno in (errno.EACCES, errno.EPERM):
 
159
                raise errors.LockFailed(self.filename, str(e))
51
160
            if e.errno != errno.ENOENT:
52
161
                raise
53
162
 
54
163
            # maybe this is an old branch (before may 2005)
55
 
            mutter("trying to create missing branch lock %r" % filename)
56
 
            
57
 
            self.f = open(filename, 'wb')
 
164
            trace.mutter("trying to create missing lock %r", self.filename)
 
165
 
 
166
            self.f = open(self.filename, 'wb+')
58
167
            return self.f
59
168
 
60
 
 
61
 
    def __del__(self):
 
169
    def _clear_f(self):
 
170
        """Clear the self.f attribute cleanly."""
62
171
        if self.f:
63
 
            from warnings import warn
64
 
            warn("lock on %r not released" % self.f)
65
 
            self.unlock()
66
 
            
 
172
            self.f.close()
 
173
            self.f = None
67
174
 
68
175
    def unlock(self):
69
176
        raise NotImplementedError()
70
177
 
71
 
        
72
 
 
73
 
 
74
 
 
75
 
 
76
 
############################################################
77
 
# msvcrt locks
78
 
 
79
 
 
80
 
try:
81
 
    import fcntl
82
 
 
83
 
    class _fcntl_FileLock(_base_Lock):
84
 
        f = None
85
 
 
86
 
        def unlock(self):
87
 
            fcntl.flock(self.f, fcntl.LOCK_UN)
88
 
            self.f.close()
89
 
            del self.f 
 
178
 
 
179
_lock_classes = []
 
180
 
 
181
 
 
182
if have_fcntl:
 
183
 
 
184
    class _fcntl_FileLock(_OSLock):
 
185
 
 
186
        def _unlock(self):
 
187
            fcntl.lockf(self.f, fcntl.LOCK_UN)
 
188
            self._clear_f()
90
189
 
91
190
 
92
191
    class _fcntl_WriteLock(_fcntl_FileLock):
 
192
 
 
193
        _open_locks = set()
 
194
 
93
195
        def __init__(self, filename):
 
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)
94
215
            try:
95
 
                fcntl.flock(self._open(filename, 'wb'), fcntl.LOCK_EX)
96
 
            except Exception, e:
97
 
                raise LockError(e)
 
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)
 
219
            except IOError, e:
 
220
                if e.errno in (errno.EAGAIN, errno.EACCES):
 
221
                    # We couldn't grab the lock
 
222
                    self.unlock()
 
223
                # we should be more precise about whats a locking
 
224
                # error and whats a random-other error
 
225
                raise errors.LockContention(self.filename, e)
 
226
 
 
227
        def unlock(self):
 
228
            _fcntl_WriteLock._open_locks.remove(self.filename)
 
229
            self._unlock()
98
230
 
99
231
 
100
232
    class _fcntl_ReadLock(_fcntl_FileLock):
101
 
        def __init__(self, filename):
102
 
            try:
103
 
                fcntl.flock(self._open(filename, 'rb'), fcntl.LOCK_SH)
104
 
            except Exception, e:
105
 
                raise LockError(e)
106
 
 
107
 
    WriteLock = _fcntl_WriteLock
108
 
    ReadLock = _fcntl_ReadLock
109
 
 
110
 
except ImportError:
111
 
    try:
112
 
        import win32con, win32file, pywintypes
113
 
 
114
 
 
115
 
        #LOCK_SH = 0 # the default
116
 
        #LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
117
 
        #LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
118
 
 
119
 
        class _w32c_FileLock(_base_Lock):
120
 
            def _lock(self, filename, openmode, lockmode):
121
 
                try:
122
 
                    self._open(filename, openmode)
123
 
                    self.hfile = win32file._get_osfhandle(self.f.fileno())
124
 
                    overlapped = pywintypes.OVERLAPPED()
125
 
                    win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000, overlapped)
126
 
                except Exception, e:
127
 
                    raise LockError(e)
128
 
 
129
 
            def unlock(self):
130
 
                try:
131
 
                    overlapped = pywintypes.OVERLAPPED()
132
 
                    win32file.UnlockFileEx(self.hfile, 0, 0x7fff0000, overlapped)
133
 
                    self.f.close()
134
 
                    self.f = None
135
 
                except Exception, e:
136
 
                    raise LockError(e)
137
 
 
138
 
 
139
 
 
140
 
        class _w32c_ReadLock(_w32c_FileLock):
141
 
            def __init__(self, filename):
142
 
                _w32c_FileLock._lock(self, filename, 'rb', 0)
143
 
 
144
 
        class _w32c_WriteLock(_w32c_FileLock):
145
 
            def __init__(self, filename):
146
 
                _w32c_FileLock._lock(self, filename, 'wb',
147
 
                                     win32con.LOCKFILE_EXCLUSIVE_LOCK)
148
 
 
149
 
 
150
 
 
151
 
        WriteLock = _w32c_WriteLock
152
 
        ReadLock = _w32c_ReadLock
153
 
 
154
 
    except ImportError:
155
 
        try:
156
 
            import msvcrt
157
 
 
158
 
 
159
 
            # Unfortunately, msvcrt.locking() doesn't distinguish between
160
 
            # read locks and write locks. Also, the way the combinations
161
 
            # work to get non-blocking is not the same, so we
162
 
            # have to write extra special functions here.
163
 
 
164
 
 
165
 
            class _msvc_FileLock(_base_Lock):
166
 
                LOCK_SH = 1
167
 
                LOCK_EX = 2
168
 
                LOCK_NB = 4
169
 
                def unlock(self):
170
 
                    _msvc_unlock(self.f)
171
 
 
172
 
 
173
 
            class _msvc_ReadLock(_msvc_FileLock):
174
 
                def __init__(self, filename):
175
 
                    _msvc_lock(self._open(filename, 'rb'), self.LOCK_SH)
176
 
 
177
 
 
178
 
            class _msvc_WriteLock(_msvc_FileLock):
179
 
                def __init__(self, filename):
180
 
                    _msvc_lock(self._open(filename, 'wb'), self.LOCK_EX)
181
 
 
182
 
 
183
 
 
184
 
            def _msvc_lock(f, flags):
185
 
                try:
186
 
                    # Unfortunately, msvcrt.LK_RLCK is equivalent to msvcrt.LK_LOCK
187
 
                    # according to the comments, LK_RLCK is open the lock for writing.
188
 
 
189
 
                    # Unfortunately, msvcrt.locking() also has the side effect that it
190
 
                    # will only block for 10 seconds at most, and then it will throw an
191
 
                    # exception, this isn't terrible, though.
192
 
                    if type(f) == file:
193
 
                        fpos = f.tell()
194
 
                        fn = f.fileno()
195
 
                        f.seek(0)
196
 
                    else:
197
 
                        fn = f
198
 
                        fpos = os.lseek(fn, 0,0)
199
 
                        os.lseek(fn, 0,0)
200
 
 
201
 
                    if flags & _msvc_FileLock.LOCK_SH:
202
 
                        if flags & _msvc_FileLock.LOCK_NB:
203
 
                            lock_mode = msvcrt.LK_NBLCK
204
 
                        else:
205
 
                            lock_mode = msvcrt.LK_LOCK
206
 
                    elif flags & _msvc_FileLock.LOCK_EX:
207
 
                        if flags & _msvc_FileLock.LOCK_NB:
208
 
                            lock_mode = msvcrt.LK_NBRLCK
209
 
                        else:
210
 
                            lock_mode = msvcrt.LK_RLCK
211
 
                    else:
212
 
                        raise ValueError('Invalid lock mode: %r' % flags)
213
 
                    try:
214
 
                        msvcrt.locking(fn, lock_mode, -1)
215
 
                    finally:
216
 
                        os.lseek(fn, fpos, 0)
217
 
                except Exception, e:
218
 
                    raise LockError(e)
219
 
 
220
 
            def _msvc_unlock(f):
221
 
                try:
222
 
                    if type(f) == file:
223
 
                        fpos = f.tell()
224
 
                        fn = f.fileno()
225
 
                        f.seek(0)
226
 
                    else:
227
 
                        fn = f
228
 
                        fpos = os.lseek(fn, 0,0)
229
 
                        os.lseek(fn, 0,0)
230
 
 
231
 
                    try:
232
 
                        msvcrt.locking(fn, msvcrt.LK_UNLCK, -1)
233
 
                    finally:
234
 
                        os.lseek(fn, fpos, 0)
235
 
                except Exception, e:
236
 
                    raise LockError(e)
237
 
 
238
 
 
239
 
 
240
 
            WriteLock = _msvc_WriteLock
241
 
            ReadLock = _msvc_ReadLock
242
 
        except ImportError:
243
 
            raise NotImplementedError("please write a locking method "
244
 
                                      "for platform %r" % sys.platform)
245
 
 
246
 
 
247
 
 
248
 
 
249
 
 
250
 
 
 
233
 
 
234
        _open_locks = {}
 
235
 
 
236
        def __init__(self, filename):
 
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
 
249
            self._open(filename, 'rb')
 
250
            try:
 
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)
 
254
            except IOError, e:
 
255
                # we should be more precise about whats a locking
 
256
                # error and whats a random-other error
 
257
                raise errors.LockContention(self.filename, e)
 
258
 
 
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
 
265
            self._unlock()
 
266
 
 
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
251
550