~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lock.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-09-24 07:26:47 UTC
  • mfrom: (3732.1.1 ianc-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20080924072647-hpc17iasylpwiaem
fix bzr st -rbranch:path-to-branch (Lukas Lalinsky)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Canonical Ltd
2
 
 
 
1
# Copyright (C) 2005, 2006, 2007, 2008 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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
18
 
"""Locking wrappers.
 
18
"""Locking using OS file locks or file existence.
19
19
 
20
 
This only does local locking using OS locks for now.
 
20
Note: This method of locking is generally deprecated in favour of LockDir, but
 
21
is used to lock local WorkingTrees, and by some old formats.  It's accessed
 
22
through Transport.lock_read(), etc.
21
23
 
22
24
This module causes two methods, lock() and unlock() to be defined in
23
25
any way that works on the current platform.
24
26
 
25
27
It is not specified whether these locks are reentrant (i.e. can be
26
28
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.
 
29
different threads in a single process.  That reentrancy is provided by
 
30
LockableFiles.
31
31
 
32
32
This defines two classes: ReadLock and WriteLock, which can be
33
33
implemented in different ways on different platforms.  Both have an
34
34
unlock() method.
35
35
"""
36
36
 
37
 
 
 
37
import errno
38
38
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):
 
39
 
 
40
from bzrlib import (
 
41
    errors,
 
42
    osutils,
 
43
    trace,
 
44
    )
 
45
from bzrlib.hooks import Hooks
 
46
 
 
47
 
 
48
class LockHooks(Hooks):
 
49
 
 
50
    def __init__(self):
 
51
        Hooks.__init__(self)
 
52
 
 
53
        # added in 1.8; called with a LockResult when a physical lock is
 
54
        # acquired
 
55
        self['lock_acquired'] = []
 
56
 
 
57
        # added in 1.8; called with a LockResult when a physical lock is
 
58
        # acquired
 
59
        self['lock_released'] = []
 
60
 
 
61
 
 
62
class Lock(object):
 
63
    """Base class for locks.
 
64
 
 
65
    :cvar hooks: Hook dictionary for operations on locks.
 
66
    """
 
67
 
 
68
    hooks = LockHooks()
 
69
 
 
70
 
 
71
class LockResult(object):
 
72
    """Result of an operation on a lock; passed to a hook"""
 
73
 
 
74
    def __init__(self, lock_url, details=None):
 
75
        """Create a lock result for lock with optional details about the lock."""
 
76
        self.lock_url = lock_url
 
77
        self.details = details
 
78
 
 
79
    def __eq__(self, other):
 
80
        return self.lock_url == other.lock_url and self.details == other.details
 
81
 
 
82
 
 
83
class _OSLock(object):
 
84
 
 
85
    def __init__(self):
 
86
        self.f = None
 
87
        self.filename = None
 
88
 
45
89
    def _open(self, filename, filemode):
46
 
        import errno
 
90
        self.filename = osutils.realpath(filename)
47
91
        try:
48
 
            self.f = open(filename, filemode)
 
92
            self.f = open(self.filename, filemode)
49
93
            return self.f
50
94
        except IOError, e:
 
95
            if e.errno in (errno.EACCES, errno.EPERM):
 
96
                raise errors.LockFailed(self.filename, str(e))
51
97
            if e.errno != errno.ENOENT:
52
98
                raise
53
99
 
54
100
            # 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')
 
101
            trace.mutter("trying to create missing lock %r", self.filename)
 
102
 
 
103
            self.f = open(self.filename, 'wb+')
58
104
            return self.f
59
105
 
 
106
    def _clear_f(self):
 
107
        """Clear the self.f attribute cleanly."""
 
108
        if self.f:
 
109
            self.f.close()
 
110
            self.f = None
60
111
 
61
112
    def __del__(self):
62
113
        if self.f:
63
114
            from warnings import warn
64
115
            warn("lock on %r not released" % self.f)
65
116
            self.unlock()
66
 
            
67
117
 
68
118
    def unlock(self):
69
119
        raise NotImplementedError()
70
120
 
71
 
        
72
 
 
73
 
 
74
 
 
75
 
 
76
 
############################################################
77
 
# msvcrt locks
78
 
 
79
121
 
80
122
try:
81
123
    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 
 
124
    have_fcntl = True
 
125
except ImportError:
 
126
    have_fcntl = False
 
127
try:
 
128
    import win32con, win32file, pywintypes, winerror, msvcrt
 
129
    have_pywin32 = True
 
130
except ImportError:
 
131
    have_pywin32 = False
 
132
try:
 
133
    import ctypes, msvcrt
 
134
    have_ctypes = True
 
135
except ImportError:
 
136
    have_ctypes = False
 
137
 
 
138
 
 
139
_lock_classes = []
 
140
 
 
141
 
 
142
if have_fcntl:
 
143
    LOCK_SH = fcntl.LOCK_SH
 
144
    LOCK_NB = fcntl.LOCK_NB
 
145
    lock_EX = fcntl.LOCK_EX
 
146
 
 
147
 
 
148
    class _fcntl_FileLock(_OSLock):
 
149
 
 
150
        def _unlock(self):
 
151
            fcntl.lockf(self.f, fcntl.LOCK_UN)
 
152
            self._clear_f()
90
153
 
91
154
 
92
155
    class _fcntl_WriteLock(_fcntl_FileLock):
 
156
 
 
157
        _open_locks = set()
 
158
 
93
159
        def __init__(self, filename):
 
160
            super(_fcntl_WriteLock, self).__init__()
 
161
            # Check we can grab a lock before we actually open the file.
 
162
            self.filename = osutils.realpath(filename)
 
163
            if self.filename in _fcntl_WriteLock._open_locks:
 
164
                self._clear_f()
 
165
                raise errors.LockContention(self.filename)
 
166
 
 
167
            self._open(self.filename, 'rb+')
 
168
            # reserve a slot for this lock - even if the lockf call fails,
 
169
            # at thisi point unlock() will be called, because self.f is set.
 
170
            # TODO: make this fully threadsafe, if we decide we care.
 
171
            _fcntl_WriteLock._open_locks.add(self.filename)
94
172
            try:
95
 
                fcntl.flock(self._open(filename, 'wb'), fcntl.LOCK_EX)
96
 
            except Exception, e:
97
 
                raise LockError(e)
 
173
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
174
                # lock right away.
 
175
                fcntl.lockf(self.f, fcntl.LOCK_EX | fcntl.LOCK_NB)
 
176
            except IOError, e:
 
177
                if e.errno in (errno.EAGAIN, errno.EACCES):
 
178
                    # We couldn't grab the lock
 
179
                    self.unlock()
 
180
                # we should be more precise about whats a locking
 
181
                # error and whats a random-other error
 
182
                raise errors.LockContention(e)
 
183
 
 
184
        def unlock(self):
 
185
            _fcntl_WriteLock._open_locks.remove(self.filename)
 
186
            self._unlock()
98
187
 
99
188
 
100
189
    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
 
 
 
190
 
 
191
        _open_locks = {}
 
192
 
 
193
        def __init__(self, filename):
 
194
            super(_fcntl_ReadLock, self).__init__()
 
195
            self.filename = osutils.realpath(filename)
 
196
            _fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
 
197
            _fcntl_ReadLock._open_locks[self.filename] += 1
 
198
            self._open(filename, 'rb')
 
199
            try:
 
200
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
201
                # lock right away.
 
202
                fcntl.lockf(self.f, fcntl.LOCK_SH | fcntl.LOCK_NB)
 
203
            except IOError, e:
 
204
                # we should be more precise about whats a locking
 
205
                # error and whats a random-other error
 
206
                raise errors.LockContention(e)
 
207
 
 
208
        def unlock(self):
 
209
            count = _fcntl_ReadLock._open_locks[self.filename]
 
210
            if count == 1:
 
211
                del _fcntl_ReadLock._open_locks[self.filename]
 
212
            else:
 
213
                _fcntl_ReadLock._open_locks[self.filename] = count - 1
 
214
            self._unlock()
 
215
 
 
216
        def temporary_write_lock(self):
 
217
            """Try to grab a write lock on the file.
 
218
 
 
219
            On platforms that support it, this will upgrade to a write lock
 
220
            without unlocking the file.
 
221
            Otherwise, this will release the read lock, and try to acquire a
 
222
            write lock.
 
223
 
 
224
            :return: A token which can be used to switch back to a read lock.
 
225
            """
 
226
            if self.filename in _fcntl_WriteLock._open_locks:
 
227
                raise AssertionError('file already locked: %r'
 
228
                    % (self.filename,))
 
229
            try:
 
230
                wlock = _fcntl_TemporaryWriteLock(self)
 
231
            except errors.LockError:
 
232
                # We didn't unlock, so we can just return 'self'
 
233
                return False, self
 
234
            return True, wlock
 
235
 
 
236
 
 
237
    class _fcntl_TemporaryWriteLock(_OSLock):
 
238
        """A token used when grabbing a temporary_write_lock.
 
239
 
 
240
        Call restore_read_lock() when you are done with the write lock.
 
241
        """
 
242
 
 
243
        def __init__(self, read_lock):
 
244
            super(_fcntl_TemporaryWriteLock, self).__init__()
 
245
            self._read_lock = read_lock
 
246
            self.filename = read_lock.filename
 
247
 
 
248
            count = _fcntl_ReadLock._open_locks[self.filename]
 
249
            if count > 1:
 
250
                # Something else also has a read-lock, so we cannot grab a
 
251
                # write lock.
 
252
                raise errors.LockContention(self.filename)
 
253
 
 
254
            if self.filename in _fcntl_WriteLock._open_locks:
 
255
                raise AssertionError('file already locked: %r'
 
256
                    % (self.filename,))
 
257
 
 
258
            # See if we can open the file for writing. Another process might
 
259
            # have a read lock. We don't use self._open() because we don't want
 
260
            # to create the file if it exists. That would have already been
 
261
            # done by _fcntl_ReadLock
 
262
            try:
 
263
                new_f = open(self.filename, 'rb+')
 
264
            except IOError, e:
 
265
                if e.errno in (errno.EACCES, errno.EPERM):
 
266
                    raise errors.LockFailed(self.filename, str(e))
 
267
                raise
 
268
            try:
 
269
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
270
                # lock right away.
 
271
                fcntl.lockf(new_f, fcntl.LOCK_EX | fcntl.LOCK_NB)
 
272
            except IOError, e:
 
273
                # TODO: Raise a more specific error based on the type of error
 
274
                raise errors.LockContention(e)
 
275
            _fcntl_WriteLock._open_locks.add(self.filename)
 
276
 
 
277
            self.f = new_f
 
278
 
 
279
        def restore_read_lock(self):
 
280
            """Restore the original ReadLock."""
 
281
            # For fcntl, since we never released the read lock, just release the
 
282
            # write lock, and return the original lock.
 
283
            fcntl.lockf(self.f, fcntl.LOCK_UN)
 
284
            self._clear_f()
 
285
            _fcntl_WriteLock._open_locks.remove(self.filename)
 
286
            # Avoid reference cycles
 
287
            read_lock = self._read_lock
 
288
            self._read_lock = None
 
289
            return read_lock
 
290
 
 
291
 
 
292
    _lock_classes.append(('fcntl', _fcntl_WriteLock, _fcntl_ReadLock))
 
293
 
 
294
 
 
295
if have_pywin32 and sys.platform == 'win32':
 
296
    LOCK_SH = 0 # the default
 
297
    LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
 
298
    LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
 
299
 
 
300
 
 
301
    class _w32c_FileLock(_OSLock):
 
302
 
 
303
        def _lock(self, filename, openmode, lockmode):
 
304
            self._open(filename, openmode)
 
305
 
 
306
            self.hfile = msvcrt.get_osfhandle(self.f.fileno())
 
307
            overlapped = pywintypes.OVERLAPPED()
 
308
            try:
 
309
                win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000,
 
310
                                     overlapped)
 
311
            except pywintypes.error, e:
 
312
                self._clear_f()
 
313
                if e.args[0] in (winerror.ERROR_LOCK_VIOLATION,):
 
314
                    raise errors.LockContention(filename)
 
315
                ## import pdb; pdb.set_trace()
 
316
                raise
 
317
            except Exception, e:
 
318
                self._clear_f()
 
319
                raise errors.LockContention(e)
 
320
 
 
321
        def unlock(self):
 
322
            overlapped = pywintypes.OVERLAPPED()
 
323
            try:
 
324
                win32file.UnlockFileEx(self.hfile, 0, 0x7fff0000, overlapped)
 
325
                self._clear_f()
 
326
            except Exception, e:
 
327
                raise errors.LockContention(e)
 
328
 
 
329
 
 
330
    class _w32c_ReadLock(_w32c_FileLock):
 
331
        def __init__(self, filename):
 
332
            super(_w32c_ReadLock, self).__init__()
 
333
            self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
 
334
 
 
335
        def temporary_write_lock(self):
 
336
            """Try to grab a write lock on the file.
 
337
 
 
338
            On platforms that support it, this will upgrade to a write lock
 
339
            without unlocking the file.
 
340
            Otherwise, this will release the read lock, and try to acquire a
 
341
            write lock.
 
342
 
 
343
            :return: A token which can be used to switch back to a read lock.
 
344
            """
 
345
            # I can't find a way to upgrade a read lock to a write lock without
 
346
            # unlocking first. So here, we do just that.
 
347
            self.unlock()
 
348
            try:
 
349
                wlock = _w32c_WriteLock(self.filename)
 
350
            except errors.LockError:
 
351
                return False, _w32c_ReadLock(self.filename)
 
352
            return True, wlock
 
353
 
 
354
 
 
355
    class _w32c_WriteLock(_w32c_FileLock):
 
356
        def __init__(self, filename):
 
357
            super(_w32c_WriteLock, self).__init__()
 
358
            self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
 
359
 
 
360
        def restore_read_lock(self):
 
361
            """Restore the original ReadLock."""
 
362
            # For win32 we had to completely let go of the original lock, so we
 
363
            # just unlock and create a new read lock.
 
364
            self.unlock()
 
365
            return _w32c_ReadLock(self.filename)
 
366
 
 
367
 
 
368
    _lock_classes.append(('pywin32', _w32c_WriteLock, _w32c_ReadLock))
 
369
 
 
370
 
 
371
if have_ctypes and sys.platform == 'win32':
 
372
    # These constants were copied from the win32con.py module.
 
373
    LOCKFILE_FAIL_IMMEDIATELY = 1
 
374
    LOCKFILE_EXCLUSIVE_LOCK = 2
 
375
    # Constant taken from winerror.py module
 
376
    ERROR_LOCK_VIOLATION = 33
 
377
 
 
378
    LOCK_SH = 0
 
379
    LOCK_EX = LOCKFILE_EXCLUSIVE_LOCK
 
380
    LOCK_NB = LOCKFILE_FAIL_IMMEDIATELY
 
381
    _LockFileEx = ctypes.windll.kernel32.LockFileEx
 
382
    _UnlockFileEx = ctypes.windll.kernel32.UnlockFileEx
 
383
    _GetLastError = ctypes.windll.kernel32.GetLastError
 
384
 
 
385
    ### Define the OVERLAPPED structure.
 
386
    #   http://msdn2.microsoft.com/en-us/library/ms684342.aspx
 
387
    # typedef struct _OVERLAPPED {
 
388
    #   ULONG_PTR Internal;
 
389
    #   ULONG_PTR InternalHigh;
 
390
    #   union {
 
391
    #     struct {
 
392
    #       DWORD Offset;
 
393
    #       DWORD OffsetHigh;
 
394
    #     };
 
395
    #     PVOID Pointer;
 
396
    #   };
 
397
    #   HANDLE hEvent;
 
398
    # } OVERLAPPED,
 
399
 
 
400
    class _inner_struct(ctypes.Structure):
 
401
        _fields_ = [('Offset', ctypes.c_uint), # DWORD
 
402
                    ('OffsetHigh', ctypes.c_uint), # DWORD
 
403
                   ]
 
404
 
 
405
    class _inner_union(ctypes.Union):
 
406
        _fields_  = [('anon_struct', _inner_struct), # struct
 
407
                     ('Pointer', ctypes.c_void_p), # PVOID
 
408
                    ]
 
409
 
 
410
    class OVERLAPPED(ctypes.Structure):
 
411
        _fields_ = [('Internal', ctypes.c_void_p), # ULONG_PTR
 
412
                    ('InternalHigh', ctypes.c_void_p), # ULONG_PTR
 
413
                    ('_inner_union', _inner_union),
 
414
                    ('hEvent', ctypes.c_void_p), # HANDLE
 
415
                   ]
 
416
 
 
417
    class _ctypes_FileLock(_OSLock):
 
418
 
 
419
        def _lock(self, filename, openmode, lockmode):
 
420
            self._open(filename, openmode)
 
421
 
 
422
            self.hfile = msvcrt.get_osfhandle(self.f.fileno())
 
423
            overlapped = OVERLAPPED()
 
424
            result = _LockFileEx(self.hfile, # HANDLE hFile
 
425
                                 lockmode,   # DWORD dwFlags
 
426
                                 0,          # DWORD dwReserved
 
427
                                 0x7fffffff, # DWORD nNumberOfBytesToLockLow
 
428
                                 0x00000000, # DWORD nNumberOfBytesToLockHigh
 
429
                                 ctypes.byref(overlapped), # lpOverlapped
 
430
                                )
 
431
            if result == 0:
 
432
                self._clear_f()
 
433
                last_err = _GetLastError()
 
434
                if last_err in (ERROR_LOCK_VIOLATION,):
 
435
                    raise errors.LockContention(filename)
 
436
                raise errors.LockContention('Unknown locking error: %s'
 
437
                                            % (last_err,))
 
438
 
 
439
        def unlock(self):
 
440
            overlapped = OVERLAPPED()
 
441
            result = _UnlockFileEx(self.hfile, # HANDLE hFile
 
442
                                   0,          # DWORD dwReserved
 
443
                                   0x7fffffff, # DWORD nNumberOfBytesToLockLow
 
444
                                   0x00000000, # DWORD nNumberOfBytesToLockHigh
 
445
                                   ctypes.byref(overlapped), # lpOverlapped
 
446
                                  )
 
447
            self._clear_f()
 
448
            if result == 0:
 
449
                self._clear_f()
 
450
                last_err = _GetLastError()
 
451
                raise errors.LockContention('Unknown unlocking error: %s'
 
452
                                            % (last_err,))
 
453
 
 
454
 
 
455
    class _ctypes_ReadLock(_ctypes_FileLock):
 
456
        def __init__(self, filename):
 
457
            super(_ctypes_ReadLock, self).__init__()
 
458
            self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
 
459
 
 
460
        def temporary_write_lock(self):
 
461
            """Try to grab a write lock on the file.
 
462
 
 
463
            On platforms that support it, this will upgrade to a write lock
 
464
            without unlocking the file.
 
465
            Otherwise, this will release the read lock, and try to acquire a
 
466
            write lock.
 
467
 
 
468
            :return: A token which can be used to switch back to a read lock.
 
469
            """
 
470
            # I can't find a way to upgrade a read lock to a write lock without
 
471
            # unlocking first. So here, we do just that.
 
472
            self.unlock()
 
473
            try:
 
474
                wlock = _ctypes_WriteLock(self.filename)
 
475
            except errors.LockError:
 
476
                return False, _ctypes_ReadLock(self.filename)
 
477
            return True, wlock
 
478
 
 
479
    class _ctypes_WriteLock(_ctypes_FileLock):
 
480
        def __init__(self, filename):
 
481
            super(_ctypes_WriteLock, self).__init__()
 
482
            self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
 
483
 
 
484
        def restore_read_lock(self):
 
485
            """Restore the original ReadLock."""
 
486
            # For win32 we had to completely let go of the original lock, so we
 
487
            # just unlock and create a new read lock.
 
488
            self.unlock()
 
489
            return _ctypes_ReadLock(self.filename)
 
490
 
 
491
 
 
492
    _lock_classes.append(('ctypes', _ctypes_WriteLock, _ctypes_ReadLock))
 
493
 
 
494
 
 
495
if len(_lock_classes) == 0:
 
496
    raise NotImplementedError(
 
497
        "We must have one of fcntl, pywin32, or ctypes available"
 
498
        " to support OS locking."
 
499
        )
 
500
 
 
501
 
 
502
# We default to using the first available lock class.
 
503
_lock_type, WriteLock, ReadLock = _lock_classes[0]
251
504