1
 
# Copyright (C) 2005, 2006, 2008, 2009 Canonical Ltd
 
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.
 
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.
 
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
 
17
 
from bzrlib.lazy_import import lazy_import
 
18
 
lazy_import(globals(), """
 
32
 
from bzrlib.decorators import (
 
37
 
# XXX: The tracking here of lock counts and whether the lock is held is
 
38
 
# somewhat redundant with what's done in LockDir; the main difference is that
 
39
 
# LockableFiles permits reentrancy.
 
41
 
class _LockWarner(object):
 
42
 
    """Hold a counter for a lock and warn if GCed while the count is >= 1.
 
44
 
    This is separate from LockableFiles because putting a __del__ on
 
45
 
    LockableFiles can result in uncollectable cycles.
 
48
 
    def __init__(self, repr):
 
53
 
        if self.lock_count >= 1:
 
54
 
            # There should have been a try/finally to unlock this.
 
55
 
            warnings.warn("%r was gc'd while locked" % self.repr)
 
58
 
class LockableFiles(object):
 
59
 
    """Object representing a set of related files locked within the same scope.
 
61
 
    This coordinates access to the lock along with providing a transaction.
 
63
 
    LockableFiles manage a lock count and can be locked repeatedly by
 
64
 
    a single caller.  (The underlying lock implementation generally does not
 
67
 
    Instances of this class are often called control_files.
 
69
 
    This class is now deprecated; code should move to using the Transport
 
70
 
    directly for file operations and using the lock or CountedLock for
 
73
 
    :ivar _lock: The real underlying lock (e.g. a LockDir)
 
74
 
    :ivar _counted_lock: A lock decorated with a semaphore, so that it 
 
78
 
    # _lock_mode: None, or 'r' or 'w'
 
80
 
    # _lock_count: If _lock_mode is true, a positive count of the number of
 
81
 
    # times the lock has been taken *by this process*.
 
83
 
    def __init__(self, transport, lock_name, lock_class):
 
84
 
        """Create a LockableFiles group
 
86
 
        :param transport: Transport pointing to the directory holding the
 
87
 
            control files and lock.
 
88
 
        :param lock_name: Name of the lock guarding these files.
 
89
 
        :param lock_class: Class of lock strategy to use: typically
 
90
 
            either LockDir or TransportLock.
 
92
 
        self._transport = transport
 
93
 
        self.lock_name = lock_name
 
94
 
        self._transaction = None
 
95
 
        self._lock_mode = None
 
96
 
        self._lock_warner = _LockWarner(repr(self))
 
98
 
        esc_name = self._escape(lock_name)
 
99
 
        self._lock = lock_class(transport, esc_name,
 
100
 
                                file_modebits=self._file_mode,
 
101
 
                                dir_modebits=self._dir_mode)
 
102
 
        self._counted_lock = counted_lock.CountedLock(self._lock)
 
104
 
    def create_lock(self):
 
107
 
        This should normally be called only when the LockableFiles directory
 
108
 
        is first created on disk.
 
110
 
        self._lock.create(mode=self._dir_mode)
 
113
 
        return '%s(%r)' % (self.__class__.__name__,
 
116
 
        return 'LockableFiles(%s, %s)' % (self.lock_name, self._transport.base)
 
118
 
    def break_lock(self):
 
119
 
        """Break the lock of this lockable files group if it is held.
 
121
 
        The current ui factory will be used to prompt for user conformation.
 
123
 
        self._lock.break_lock()
 
125
 
    def _escape(self, file_or_path):
 
126
 
        """DEPRECATED: Do not use outside this class"""
 
127
 
        if not isinstance(file_or_path, basestring):
 
128
 
            file_or_path = '/'.join(file_or_path)
 
129
 
        if file_or_path == '':
 
131
 
        return urlutils.escape(osutils.safe_unicode(file_or_path))
 
133
 
    def _find_modes(self):
 
134
 
        """Determine the appropriate modes for files and directories.
 
136
 
        :deprecated: Replaced by BzrDir._find_creation_modes.
 
138
 
        # XXX: The properties created by this can be removed or deprecated
 
139
 
        # once all the _get_text_store methods etc no longer use them.
 
142
 
            st = self._transport.stat('.')
 
143
 
        except errors.TransportNotPossible:
 
144
 
            self._dir_mode = 0755
 
145
 
            self._file_mode = 0644
 
147
 
            # Check the directory mode, but also make sure the created
 
148
 
            # directories and files are read-write for this user. This is
 
149
 
            # mostly a workaround for filesystems which lie about being able to
 
150
 
            # write to a directory (cygwin & win32)
 
151
 
            self._dir_mode = (st.st_mode & 07777) | 00700
 
152
 
            # Remove the sticky and execute bits for files
 
153
 
            self._file_mode = self._dir_mode & ~07111
 
155
 
    def leave_in_place(self):
 
156
 
        """Set this LockableFiles to not clear the physical lock on unlock."""
 
157
 
        self._lock.leave_in_place()
 
159
 
    def dont_leave_in_place(self):
 
160
 
        """Set this LockableFiles to clear the physical lock on unlock."""
 
161
 
        self._lock.dont_leave_in_place()
 
163
 
    def lock_write(self, token=None):
 
164
 
        """Lock this group of files for writing.
 
166
 
        :param token: if this is already locked, then lock_write will fail
 
167
 
            unless the token matches the existing lock.
 
168
 
        :returns: a token if this instance supports tokens, otherwise None.
 
169
 
        :raises TokenLockingNotSupported: when a token is given but this
 
170
 
            instance doesn't support using token locks.
 
171
 
        :raises MismatchedToken: if the specified token doesn't match the token
 
172
 
            of the existing lock.
 
174
 
        A token should be passed in if you know that you have locked the object
 
175
 
        some other way, and need to synchronise this object's state with that
 
178
 
        # TODO: Upgrade locking to support using a Transport,
 
179
 
        # and potentially a remote locking protocol
 
181
 
            if self._lock_mode != 'w' or not self.get_transaction().writeable():
 
182
 
                raise errors.ReadOnlyError(self)
 
183
 
            self._lock.validate_token(token)
 
184
 
            self._lock_warner.lock_count += 1
 
185
 
            return self._token_from_lock
 
187
 
            token_from_lock = self._lock.lock_write(token=token)
 
188
 
            #traceback.print_stack()
 
189
 
            self._lock_mode = 'w'
 
190
 
            self._lock_warner.lock_count = 1
 
191
 
            self._set_write_transaction()
 
192
 
            self._token_from_lock = token_from_lock
 
193
 
            return token_from_lock
 
197
 
            if self._lock_mode not in ('r', 'w'):
 
198
 
                raise ValueError("invalid lock mode %r" % (self._lock_mode,))
 
199
 
            self._lock_warner.lock_count += 1
 
201
 
            self._lock.lock_read()
 
202
 
            #traceback.print_stack()
 
203
 
            self._lock_mode = 'r'
 
204
 
            self._lock_warner.lock_count = 1
 
205
 
            self._set_read_transaction()
 
207
 
    def _set_read_transaction(self):
 
208
 
        """Setup a read transaction."""
 
209
 
        self._set_transaction(transactions.ReadOnlyTransaction())
 
210
 
        # 5K may be excessive, but hey, its a knob.
 
211
 
        self.get_transaction().set_cache_size(5000)
 
213
 
    def _set_write_transaction(self):
 
214
 
        """Setup a write transaction."""
 
215
 
        self._set_transaction(transactions.WriteTransaction())
 
217
 
    @only_raises(errors.LockNotHeld, errors.LockBroken)
 
219
 
        if not self._lock_mode:
 
220
 
            return lock.cant_unlock_not_held(self)
 
221
 
        if self._lock_warner.lock_count > 1:
 
222
 
            self._lock_warner.lock_count -= 1
 
224
 
            #traceback.print_stack()
 
225
 
            self._finish_transaction()
 
229
 
                self._lock_mode = self._lock_warner.lock_count = None
 
232
 
    def _lock_count(self):
 
233
 
        return self._lock_warner.lock_count
 
236
 
        """Return true if this LockableFiles group is locked"""
 
237
 
        return self._lock_warner.lock_count >= 1
 
239
 
    def get_physical_lock_status(self):
 
240
 
        """Return physical lock status.
 
242
 
        Returns true if a lock is held on the transport. If no lock is held, or
 
243
 
        the underlying locking mechanism does not support querying lock
 
244
 
        status, false is returned.
 
247
 
            return self._lock.peek() is not None
 
248
 
        except NotImplementedError:
 
251
 
    def get_transaction(self):
 
252
 
        """Return the current active transaction.
 
254
 
        If no transaction is active, this returns a passthrough object
 
255
 
        for which all data is immediately flushed and no caching happens.
 
257
 
        if self._transaction is None:
 
258
 
            return transactions.PassThroughTransaction()
 
260
 
            return self._transaction
 
262
 
    def _set_transaction(self, new_transaction):
 
263
 
        """Set a new active transaction."""
 
264
 
        if self._transaction is not None:
 
265
 
            raise errors.LockError('Branch %s is in a transaction already.' %
 
267
 
        self._transaction = new_transaction
 
269
 
    def _finish_transaction(self):
 
270
 
        """Exit the current transaction."""
 
271
 
        if self._transaction is None:
 
272
 
            raise errors.LockError('Branch %s is not in a transaction' %
 
274
 
        transaction = self._transaction
 
275
 
        self._transaction = None
 
279
 
class TransportLock(object):
 
280
 
    """Locking method which uses transport-dependent locks.
 
282
 
    On the local filesystem these transform into OS-managed locks.
 
284
 
    These do not guard against concurrent access via different
 
287
 
    This is suitable for use only in WorkingTrees (which are at present
 
290
 
    def __init__(self, transport, escaped_name, file_modebits, dir_modebits):
 
291
 
        self._transport = transport
 
292
 
        self._escaped_name = escaped_name
 
293
 
        self._file_modebits = file_modebits
 
294
 
        self._dir_modebits = dir_modebits
 
296
 
    def break_lock(self):
 
297
 
        raise NotImplementedError(self.break_lock)
 
299
 
    def leave_in_place(self):
 
300
 
        raise NotImplementedError(self.leave_in_place)
 
302
 
    def dont_leave_in_place(self):
 
303
 
        raise NotImplementedError(self.dont_leave_in_place)
 
305
 
    def lock_write(self, token=None):
 
306
 
        if token is not None:
 
307
 
            raise errors.TokenLockingNotSupported(self)
 
308
 
        self._lock = self._transport.lock_write(self._escaped_name)
 
311
 
        self._lock = self._transport.lock_read(self._escaped_name)
 
318
 
        raise NotImplementedError()
 
320
 
    def create(self, mode=None):
 
321
 
        """Create lock mechanism"""
 
322
 
        # for old-style locks, create the file now
 
323
 
        self._transport.put_bytes(self._escaped_name, '',
 
324
 
                            mode=self._file_modebits)
 
326
 
    def validate_token(self, token):
 
327
 
        if token is not None:
 
328
 
            raise errors.TokenLockingNotSupported(self)