1# Copyright (C) 2005-2011 Canonical Ltd 2# 3# This program is free software; you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation; either version 2 of the License, or 6# (at your option) any later version. 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with this program; if not, write to the Free Software 15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 17from .lazy_import import lazy_import 18lazy_import(globals(), """ 19from breezy import ( 20 counted_lock, 21 lock, 22 transactions, 23 urlutils, 24 ) 25""") 26 27from . import ( 28 errors, 29 ) 30from .decorators import ( 31 only_raises, 32 ) 33 34 35class LockableFiles(object): 36 """Object representing a set of related files locked within the same scope. 37 38 This coordinates access to the lock along with providing a transaction. 39 40 LockableFiles manage a lock count and can be locked repeatedly by 41 a single caller. (The underlying lock implementation generally does not 42 support this.) 43 44 Instances of this class are often called control_files. 45 46 This class is now deprecated; code should move to using the Transport 47 directly for file operations and using the lock or CountedLock for 48 locking. 49 50 :ivar _lock: The real underlying lock (e.g. a LockDir) 51 :ivar _lock_count: If _lock_mode is true, a positive count of the number 52 of times the lock has been taken (and not yet released) *by this 53 process*, through this particular object instance. 54 :ivar _lock_mode: None, or 'r' or 'w' 55 """ 56 57 def __init__(self, transport, lock_name, lock_class): 58 """Create a LockableFiles group 59 60 :param transport: Transport pointing to the directory holding the 61 control files and lock. 62 :param lock_name: Name of the lock guarding these files. 63 :param lock_class: Class of lock strategy to use: typically 64 either LockDir or TransportLock. 65 """ 66 self._transport = transport 67 self.lock_name = lock_name 68 self._transaction = None 69 self._lock_mode = None 70 self._lock_count = 0 71 self._find_modes() 72 esc_name = self._escape(lock_name) 73 self._lock = lock_class(transport, esc_name, 74 file_modebits=self._file_mode, 75 dir_modebits=self._dir_mode) 76 self._counted_lock = counted_lock.CountedLock(self._lock) 77 78 def create_lock(self): 79 """Create the lock. 80 81 This should normally be called only when the LockableFiles directory 82 is first created on disk. 83 """ 84 self._lock.create(mode=self._dir_mode) 85 86 def __repr__(self): 87 return '%s(%r)' % (self.__class__.__name__, 88 self._transport) 89 90 def __str__(self): 91 return 'LockableFiles(%s, %s)' % (self.lock_name, self._transport.base) 92 93 def break_lock(self): 94 """Break the lock of this lockable files group if it is held. 95 96 The current ui factory will be used to prompt for user conformation. 97 """ 98 self._lock.break_lock() 99 100 def _escape(self, file_or_path): 101 """DEPRECATED: Do not use outside this class""" 102 if file_or_path == '': 103 return u'' 104 return urlutils.escape(file_or_path) 105 106 def _find_modes(self): 107 """Determine the appropriate modes for files and directories. 108 109 :deprecated: Replaced by BzrDir._find_creation_modes. 110 """ 111 # XXX: The properties created by this can be removed or deprecated 112 # once all the _get_text_store methods etc no longer use them. 113 # -- mbp 20080512 114 try: 115 st = self._transport.stat('.') 116 except errors.TransportNotPossible: 117 self._dir_mode = 0o755 118 self._file_mode = 0o644 119 else: 120 # Check the directory mode, but also make sure the created 121 # directories and files are read-write for this user. This is 122 # mostly a workaround for filesystems which lie about being able to 123 # write to a directory (cygwin & win32) 124 self._dir_mode = (st.st_mode & 0o7777) | 0o0700 125 # Remove the sticky and execute bits for files 126 self._file_mode = self._dir_mode & ~0o7111 127 128 def leave_in_place(self): 129 """Set this LockableFiles to not clear the physical lock on unlock.""" 130 self._lock.leave_in_place() 131 132 def dont_leave_in_place(self): 133 """Set this LockableFiles to clear the physical lock on unlock.""" 134 self._lock.dont_leave_in_place() 135 136 def lock_write(self, token=None): 137 """Lock this group of files for writing. 138 139 :param token: if this is already locked, then lock_write will fail 140 unless the token matches the existing lock. 141 :returns: a token if this instance supports tokens, otherwise None. 142 :raises TokenLockingNotSupported: when a token is given but this 143 instance doesn't support using token locks. 144 :raises MismatchedToken: if the specified token doesn't match the token 145 of the existing lock. 146 147 A token should be passed in if you know that you have locked the object 148 some other way, and need to synchronise this object's state with that 149 fact. 150 """ 151 if self._lock_mode: 152 if (self._lock_mode != 'w' 153 or not self.get_transaction().writeable()): 154 raise errors.ReadOnlyError(self) 155 self._lock.validate_token(token) 156 self._lock_count += 1 157 return self._token_from_lock 158 else: 159 token_from_lock = self._lock.lock_write(token=token) 160 # traceback.print_stack() 161 self._lock_mode = 'w' 162 self._lock_count = 1 163 self._set_write_transaction() 164 self._token_from_lock = token_from_lock 165 return token_from_lock 166 167 def lock_read(self): 168 if self._lock_mode: 169 if self._lock_mode not in ('r', 'w'): 170 raise ValueError("invalid lock mode %r" % (self._lock_mode,)) 171 self._lock_count += 1 172 else: 173 self._lock.lock_read() 174 # traceback.print_stack() 175 self._lock_mode = 'r' 176 self._lock_count = 1 177 self._set_read_transaction() 178 179 def _set_read_transaction(self): 180 """Setup a read transaction.""" 181 self._set_transaction(transactions.ReadOnlyTransaction()) 182 # 5K may be excessive, but hey, its a knob. 183 self.get_transaction().set_cache_size(5000) 184 185 def _set_write_transaction(self): 186 """Setup a write transaction.""" 187 self._set_transaction(transactions.WriteTransaction()) 188 189 @only_raises(errors.LockNotHeld, errors.LockBroken) 190 def unlock(self): 191 if not self._lock_mode: 192 return lock.cant_unlock_not_held(self) 193 if self._lock_count > 1: 194 self._lock_count -= 1 195 else: 196 # traceback.print_stack() 197 self._finish_transaction() 198 try: 199 self._lock.unlock() 200 finally: 201 self._lock_count = 0 202 self._lock_mode = None 203 204 def is_locked(self): 205 """Return true if this LockableFiles group is locked""" 206 return self._lock_count >= 1 207 208 def get_physical_lock_status(self): 209 """Return physical lock status. 210 211 Returns true if a lock is held on the transport. If no lock is held, or 212 the underlying locking mechanism does not support querying lock 213 status, false is returned. 214 """ 215 try: 216 return self._lock.peek() is not None 217 except NotImplementedError: 218 return False 219 220 def get_transaction(self): 221 """Return the current active transaction. 222 223 If no transaction is active, this returns a passthrough object 224 for which all data is immediately flushed and no caching happens. 225 """ 226 if self._transaction is None: 227 return transactions.PassThroughTransaction() 228 else: 229 return self._transaction 230 231 def _set_transaction(self, new_transaction): 232 """Set a new active transaction.""" 233 if self._transaction is not None: 234 raise errors.LockError('Branch %s is in a transaction already.' % 235 self) 236 self._transaction = new_transaction 237 238 def _finish_transaction(self): 239 """Exit the current transaction.""" 240 if self._transaction is None: 241 raise errors.LockError('Branch %s is not in a transaction' % 242 self) 243 transaction = self._transaction 244 self._transaction = None 245 transaction.finish() 246 247 248class TransportLock(object): 249 """Locking method which uses transport-dependent locks. 250 251 On the local filesystem these transform into OS-managed locks. 252 253 These do not guard against concurrent access via different 254 transports. 255 256 This is suitable for use only in WorkingTrees (which are at present 257 always local). 258 """ 259 260 def __init__(self, transport, escaped_name, file_modebits, dir_modebits): 261 self._transport = transport 262 self._escaped_name = escaped_name 263 self._file_modebits = file_modebits 264 self._dir_modebits = dir_modebits 265 266 def break_lock(self): 267 raise NotImplementedError(self.break_lock) 268 269 def leave_in_place(self): 270 raise NotImplementedError(self.leave_in_place) 271 272 def dont_leave_in_place(self): 273 raise NotImplementedError(self.dont_leave_in_place) 274 275 def lock_write(self, token=None): 276 if token is not None: 277 raise errors.TokenLockingNotSupported(self) 278 self._lock = self._transport.lock_write(self._escaped_name) 279 280 def lock_read(self): 281 self._lock = self._transport.lock_read(self._escaped_name) 282 283 def unlock(self): 284 self._lock.unlock() 285 self._lock = None 286 287 def peek(self): 288 raise NotImplementedError() 289 290 def create(self, mode=None): 291 """Create lock mechanism""" 292 # for old-style locks, create the file now 293 self._transport.put_bytes(self._escaped_name, b'', 294 mode=self._file_modebits) 295 296 def validate_token(self, token): 297 if token is not None: 298 raise errors.TokenLockingNotSupported(self) 299