"""CVS locking algorithm.CVS locking strategy====================As reverse engineered from the CVS 1.3 sources (file lock.c):- Locking is done on a per repository basis (but a process can holdwrite locks for multiple directories); all lock files are placed inthe repository and have names beginning with "#cvs.".- Before even attempting to lock, a file "#cvs.tfl.<pid>" is created(and removed again), to test that we can write the repository. [Thealgorithm can still be fooled (1) if the repository's mode is changedwhile attempting to lock; (2) if this file exists and is writable butthe directory is not.]- While creating the actual read/write lock files (which may exist fora long time), a "meta-lock" is held. The meta-lock is a directorynamed "#cvs.lock" in the repository. The meta-lock is also held whilea write lock is held.- To set a read lock: - acquire the meta-lock - create the file "#cvs.rfl.<pid>" - release the meta-lock- To set a write lock: - acquire the meta-lock - check that there are no files called "#cvs.rfl.*" - if there are, release the meta-lock, sleep, try again - create the file "#cvs.wfl.<pid>"- To release a write lock: - remove the file "#cvs.wfl.<pid>" - rmdir the meta-lock- To release a read lock: - remove the file "#cvs.rfl.<pid>"Additional notes----------------- A process should read-lock at most one repository at a time.- A process may write-lock as many repositories as it wishes (to avoiddeadlocks, I presume it should always lock them top-down in thedirectory hierarchy).- A process should make sure it removes all its lock files anddirectories when it crashes.- Limitation: one user id should not be committing files into the samerepository at the same time.Turn this into Python code--------------------------rl = ReadLock(repository, waittime)wl = WriteLock(repository, waittime)list = MultipleWriteLock([repository1, repository2, ...], waittime)"""import osimport timeimport statimport pwd# Default wait timeDELAY = 10# XXX This should be the same on all Unix versionsEEXIST = 17# Files used for locking (must match cvs.h in the CVS sources)CVSLCK = "#cvs.lck"CVSRFL = "#cvs.rfl."CVSWFL = "#cvs.wfl."class Error: def __init__(self, msg): self.msg = msg def __repr__(self): return repr(self.msg) def __str__(self): return str(self.msg)class Locked(Error): passclass Lock: def __init__(self, repository = ".", delay = DELAY): self.repository = repository self.delay = delay self.lockdir = None self.lockfile = None pid = repr(os.getpid()) self.cvslck = self.join(CVSLCK) self.cvsrfl = self.join(CVSRFL + pid) self.cvswfl = self.join(CVSWFL + pid) def __del__(self): print "__del__" self.unlock() def setlockdir(self): while 1: try: self.lockdir = self.cvslck os.mkdir(self.cvslck, 0777) return except os.error, msg: self.lockdir = None if msg[0] == EEXIST: try: st = os.stat(self.cvslck) except os.error: continue self.sleep(st) continue raise Error("failed to lock %s: %s" % ( self.repository, msg)) def unlock(self): self.unlockfile() self.unlockdir() def unlockfile(self): if self.lockfile: print "unlink", self.lockfile try: os.unlink(self.lockfile) except os.error: pass self.lockfile = None def unlockdir(self): if self.lockdir: print "rmdir", self.lockdir try: os.rmdir(self.lockdir) except os.error: pass self.lockdir = None def sleep(self, st): sleep(st, self.repository, self.delay) def join(self, name): return os.path.join(self.repository, name)def sleep(st, repository, delay): if delay <= 0: raise Locked(st) uid = st[stat.ST_UID] try: pwent = pwd.getpwuid(uid) user = pwent[0] except KeyError: user = "uid %d" % uid print "[%s]" % time.ctime(time.time())[11:19], print "Waiting for %s's lock in" % user, repository time.sleep(delay)class ReadLock(Lock): def __init__(self, repository, delay = DELAY): Lock.__init__(self, repository, delay) ok = 0 try: self.setlockdir() self.lockfile = self.cvsrfl fp = open(self.lockfile, 'w') fp.close() ok = 1 finally: if not ok: self.unlockfile() self.unlockdir()class WriteLock(Lock): def __init__(self, repository, delay = DELAY): Lock.__init__(self, repository, delay) self.setlockdir() while 1: uid = self.readers_exist() if not uid: break self.unlockdir() self.sleep(uid) self.lockfile = self.cvswfl fp = open(self.lockfile, 'w') fp.close() def readers_exist(self): n = len(CVSRFL) for name in os.listdir(self.repository): if name[:n] == CVSRFL: try: st = os.stat(self.join(name)) except os.error: continue return st return Nonedef MultipleWriteLock(repositories, delay = DELAY): while 1: locks = [] for r in repositories: try: locks.append(WriteLock(r, 0)) except Locked, instance: del locks break else: break sleep(instance.msg, r, delay) return listdef test(): import sys if sys.argv[1:]: repository = sys.argv[1] else: repository = "." rl = None wl = None try: print "attempting write lock ..." wl = WriteLock(repository) print "got it." wl.unlock() print "attempting read lock ..." rl = ReadLock(repository) print "got it." rl.unlock() finally: print [1] sys.exc_traceback = None print [2] if rl: rl.unlock() print [3] if wl: wl.unlock() print [4] rl = None print [5] wl = None print [6]if __name__ == '__main__': test()
"""CVS locking algorithm.
CVS locking strategy
====================
As reverse engineered from the CVS 1.3 sources (file lock.c):
- Locking is done on a per repository basis (but a process can hold
write locks for multiple directories); all lock files are placed in
the repository and have names beginning with "#cvs.".
- Before even attempting to lock, a file "#cvs.tfl.<pid>" is created
(and removed again), to test that we can write the repository. [The
algorithm can still be fooled (1) if the repository's mode is changed
while attempting to lock; (2) if this file exists and is writable but
the directory is not.]
- While creating the actual read/write lock files (which may exist for
a long time), a "meta-lock" is held. The meta-lock is a directory
named "#cvs.lock" in the repository. The meta-lock is also held while
a write lock is held.
- To set a read lock:
- acquire the meta-lock
- create the file "#cvs.rfl.<pid>"
- release the meta-lock
- To set a write lock:
- acquire the meta-lock
- check that there are no files called "#cvs.rfl.*"
- if there are, release the meta-lock, sleep, try again
- create the file "#cvs.wfl.<pid>"
- To release a write lock:
- remove the file "#cvs.wfl.<pid>"
- rmdir the meta-lock
- To release a read lock:
- remove the file "#cvs.rfl.<pid>"
Additional notes
----------------
- A process should read-lock at most one repository at a time.
- A process may write-lock as many repositories as it wishes (to avoid
deadlocks, I presume it should always lock them top-down in the
directory hierarchy).
- A process should make sure it removes all its lock files and
directories when it crashes.
- Limitation: one user id should not be committing files into the same
repository at the same time.
Turn this into Python code
--------------------------
rl = ReadLock(repository, waittime)
wl = WriteLock(repository, waittime)
list = MultipleWriteLock([repository1, repository2, ...], waittime)
"""
import os
import time
import stat
import pwd
# Default wait time
DELAY = 10
# XXX This should be the same on all Unix versions
EEXIST = 17
# Files used for locking (must match cvs.h in the CVS sources)
CVSLCK = "#cvs.lck"
CVSRFL = "#cvs.rfl."
CVSWFL = "#cvs.wfl."
class Error:
def __init__(self, msg):
self.msg = msg
def __repr__(self):
return repr(self.msg)
def __str__(self):
return str(self.msg)
class Locked(Error):
pass
class Lock:
def __init__(self, repository = ".", delay = DELAY):
self.repository = repository
self.delay = delay
self.lockdir = None
self.lockfile = None
pid = repr(os.getpid())
self.cvslck = self.join(CVSLCK)
self.cvsrfl = self.join(CVSRFL + pid)
self.cvswfl = self.join(CVSWFL + pid)
def __del__(self):
print "__del__"
self.unlock()
def setlockdir(self):
while 1:
try:
self.lockdir = self.cvslck
os.mkdir(self.cvslck, 0777)
return
except os.error, msg:
self.lockdir = None
if msg[0] == EEXIST:
try:
st = os.stat(self.cvslck)
except os.error:
continue
self.sleep(st)
continue
raise Error("failed to lock %s: %s" % (
self.repository, msg))
def unlock(self):
self.unlockfile()
self.unlockdir()
def unlockfile(self):
if self.lockfile:
print "unlink", self.lockfile
try:
os.unlink(self.lockfile)
except os.error:
pass
self.lockfile = None
def unlockdir(self):
if self.lockdir:
print "rmdir", self.lockdir
try:
os.rmdir(self.lockdir)
except os.error:
pass
self.lockdir = None
def sleep(self, st):
sleep(st, self.repository, self.delay)
def join(self, name):
return os.path.join(self.repository, name)
def sleep(st, repository, delay):
if delay <= 0:
raise Locked(st)
uid = st[stat.ST_UID]
try:
pwent = pwd.getpwuid(uid)
user = pwent[0]
except KeyError:
user = "uid %d" % uid
print "[%s]" % time.ctime(time.time())[11:19],
print "Waiting for %s's lock in" % user, repository
time.sleep(delay)
class ReadLock(Lock):
def __init__(self, repository, delay = DELAY):
Lock.__init__(self, repository, delay)
ok = 0
try:
self.setlockdir()
self.lockfile = self.cvsrfl
fp = open(self.lockfile, 'w')
fp.close()
ok = 1
finally:
if not ok:
self.unlockfile()
self.unlockdir()
class WriteLock(Lock):
def __init__(self, repository, delay = DELAY):
Lock.__init__(self, repository, delay)
self.setlockdir()
while 1:
uid = self.readers_exist()
if not uid:
break
self.unlockdir()
self.sleep(uid)
self.lockfile = self.cvswfl
fp = open(self.lockfile, 'w')
fp.close()
def readers_exist(self):
n = len(CVSRFL)
for name in os.listdir(self.repository):
if name[:n] == CVSRFL:
try:
st = os.stat(self.join(name))
except os.error:
continue
return st
return None
def MultipleWriteLock(repositories, delay = DELAY):
while 1:
locks = []
for r in repositories:
try:
locks.append(WriteLock(r, 0))
except Locked, instance:
del locks
break
else:
break
sleep(instance.msg, r, delay)
return list
def test():
import sys
if sys.argv[1:]:
repository = sys.argv[1]
else:
repository = "."
rl = None
wl = None
try:
print "attempting write lock ..."
wl = WriteLock(repository)
print "got it."
wl.unlock()
print "attempting read lock ..."
rl = ReadLock(repository)
print "got it."
rl.unlock()
finally:
print [1]
sys.exc_traceback = None
print [2]
if rl:
rl.unlock()
print [3]
if wl:
wl.unlock()
print [4]
rl = None
print [5]
wl = None
print [6]
if __name__ == '__main__':
test()