1"""CVS locking algorithm. 2 3CVS locking strategy 4==================== 5 6As reverse engineered from the CVS 1.3 sources (file lock.c): 7 8- Locking is done on a per repository basis (but a process can hold 9write locks for multiple directories); all lock files are placed in 10the repository and have names beginning with "#cvs.". 11 12- Before even attempting to lock, a file "#cvs.tfl.<pid>" is created 13(and removed again), to test that we can write the repository. [The 14algorithm can still be fooled (1) if the repository's mode is changed 15while attempting to lock; (2) if this file exists and is writable but 16the directory is not.] 17 18- While creating the actual read/write lock files (which may exist for 19a long time), a "meta-lock" is held. The meta-lock is a directory 20named "#cvs.lock" in the repository. The meta-lock is also held while 21a write lock is held. 22 23- To set a read lock: 24 25 - acquire the meta-lock 26 - create the file "#cvs.rfl.<pid>" 27 - release the meta-lock 28 29- To set a write lock: 30 31 - acquire the meta-lock 32 - check that there are no files called "#cvs.rfl.*" 33 - if there are, release the meta-lock, sleep, try again 34 - create the file "#cvs.wfl.<pid>" 35 36- To release a write lock: 37 38 - remove the file "#cvs.wfl.<pid>" 39 - rmdir the meta-lock 40 41- To release a read lock: 42 43 - remove the file "#cvs.rfl.<pid>" 44 45 46Additional notes 47---------------- 48 49- A process should read-lock at most one repository at a time. 50 51- A process may write-lock as many repositories as it wishes (to avoid 52deadlocks, I presume it should always lock them top-down in the 53directory hierarchy). 54 55- A process should make sure it removes all its lock files and 56directories when it crashes. 57 58- Limitation: one user id should not be committing files into the same 59repository at the same time. 60 61 62Turn this into Python code 63-------------------------- 64 65rl = ReadLock(repository, waittime) 66 67wl = WriteLock(repository, waittime) 68 69list = MultipleWriteLock([repository1, repository2, ...], waittime) 70 71""" 72 73 74import os 75import time 76import stat 77import pwd 78 79 80# Default wait time 81DELAY = 10 82 83 84# XXX This should be the same on all Unix versions 85EEXIST = 17 86 87 88# Files used for locking (must match cvs.h in the CVS sources) 89CVSLCK = "#cvs.lck" 90CVSRFL = "#cvs.rfl." 91CVSWFL = "#cvs.wfl." 92 93 94class Error: 95 96 def __init__(self, msg): 97 self.msg = msg 98 99 def __repr__(self): 100 return repr(self.msg) 101 102 def __str__(self): 103 return str(self.msg) 104 105 106class Locked(Error): 107 pass 108 109 110class Lock: 111 112 def __init__(self, repository = ".", delay = DELAY): 113 self.repository = repository 114 self.delay = delay 115 self.lockdir = None 116 self.lockfile = None 117 pid = repr(os.getpid()) 118 self.cvslck = self.join(CVSLCK) 119 self.cvsrfl = self.join(CVSRFL + pid) 120 self.cvswfl = self.join(CVSWFL + pid) 121 122 def __del__(self): 123 print "__del__" 124 self.unlock() 125 126 def setlockdir(self): 127 while 1: 128 try: 129 self.lockdir = self.cvslck 130 os.mkdir(self.cvslck, 0777) 131 return 132 except os.error, msg: 133 self.lockdir = None 134 if msg[0] == EEXIST: 135 try: 136 st = os.stat(self.cvslck) 137 except os.error: 138 continue 139 self.sleep(st) 140 continue 141 raise Error("failed to lock %s: %s" % ( 142 self.repository, msg)) 143 144 def unlock(self): 145 self.unlockfile() 146 self.unlockdir() 147 148 def unlockfile(self): 149 if self.lockfile: 150 print "unlink", self.lockfile 151 try: 152 os.unlink(self.lockfile) 153 except os.error: 154 pass 155 self.lockfile = None 156 157 def unlockdir(self): 158 if self.lockdir: 159 print "rmdir", self.lockdir 160 try: 161 os.rmdir(self.lockdir) 162 except os.error: 163 pass 164 self.lockdir = None 165 166 def sleep(self, st): 167 sleep(st, self.repository, self.delay) 168 169 def join(self, name): 170 return os.path.join(self.repository, name) 171 172 173def sleep(st, repository, delay): 174 if delay <= 0: 175 raise Locked(st) 176 uid = st[stat.ST_UID] 177 try: 178 pwent = pwd.getpwuid(uid) 179 user = pwent[0] 180 except KeyError: 181 user = "uid %d" % uid 182 print "[%s]" % time.ctime(time.time())[11:19], 183 print "Waiting for %s's lock in" % user, repository 184 time.sleep(delay) 185 186 187class ReadLock(Lock): 188 189 def __init__(self, repository, delay = DELAY): 190 Lock.__init__(self, repository, delay) 191 ok = 0 192 try: 193 self.setlockdir() 194 self.lockfile = self.cvsrfl 195 fp = open(self.lockfile, 'w') 196 fp.close() 197 ok = 1 198 finally: 199 if not ok: 200 self.unlockfile() 201 self.unlockdir() 202 203 204class WriteLock(Lock): 205 206 def __init__(self, repository, delay = DELAY): 207 Lock.__init__(self, repository, delay) 208 self.setlockdir() 209 while 1: 210 uid = self.readers_exist() 211 if not uid: 212 break 213 self.unlockdir() 214 self.sleep(uid) 215 self.lockfile = self.cvswfl 216 fp = open(self.lockfile, 'w') 217 fp.close() 218 219 def readers_exist(self): 220 n = len(CVSRFL) 221 for name in os.listdir(self.repository): 222 if name[:n] == CVSRFL: 223 try: 224 st = os.stat(self.join(name)) 225 except os.error: 226 continue 227 return st 228 return None 229 230 231def MultipleWriteLock(repositories, delay = DELAY): 232 while 1: 233 locks = [] 234 for r in repositories: 235 try: 236 locks.append(WriteLock(r, 0)) 237 except Locked, instance: 238 del locks 239 break 240 else: 241 break 242 sleep(instance.msg, r, delay) 243 return list 244 245 246def test(): 247 import sys 248 if sys.argv[1:]: 249 repository = sys.argv[1] 250 else: 251 repository = "." 252 rl = None 253 wl = None 254 try: 255 print "attempting write lock ..." 256 wl = WriteLock(repository) 257 print "got it." 258 wl.unlock() 259 print "attempting read lock ..." 260 rl = ReadLock(repository) 261 print "got it." 262 rl.unlock() 263 finally: 264 print [1] 265 sys.exc_traceback = None 266 print [2] 267 if rl: 268 rl.unlock() 269 print [3] 270 if wl: 271 wl.unlock() 272 print [4] 273 rl = None 274 print [5] 275 wl = None 276 print [6] 277 278 279if __name__ == '__main__': 280 test() 281