| """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() |