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