blob: ffaa15e1d3351209a2f95e125233d19c272861e5 [file] [log] [blame]
Guido van Rossum8608ab61995-06-23 22:03:28 +00001"""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
Tim Peterse6ddc8b2004-07-18 05:56:09 +000025 - acquire the meta-lock
26 - create the file "#cvs.rfl.<pid>"
27 - release the meta-lock
Guido van Rossum8608ab61995-06-23 22:03:28 +000028
29- To set a write lock:
30
Tim Peterse6ddc8b2004-07-18 05:56:09 +000031 - 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>"
Guido van Rossum8608ab61995-06-23 22:03:28 +000035
36- To release a write lock:
37
Tim Peterse6ddc8b2004-07-18 05:56:09 +000038 - remove the file "#cvs.wfl.<pid>"
39 - rmdir the meta-lock
Guido van Rossum8608ab61995-06-23 22:03:28 +000040
41- To release a read lock:
42
Tim Peterse6ddc8b2004-07-18 05:56:09 +000043 - remove the file "#cvs.rfl.<pid>"
Guido van Rossum8608ab61995-06-23 22:03:28 +000044
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
Tim Peterse6ddc8b2004-07-18 05:56:09 +000096 def __init__(self, msg):
97 self.msg = msg
Guido van Rossum8608ab61995-06-23 22:03:28 +000098
Tim Peterse6ddc8b2004-07-18 05:56:09 +000099 def __repr__(self):
100 return repr(self.msg)
Guido van Rossum8608ab61995-06-23 22:03:28 +0000101
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000102 def __str__(self):
103 return str(self.msg)
Guido van Rossum8608ab61995-06-23 22:03:28 +0000104
105
106class Locked(Error):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000107 pass
Guido van Rossum8608ab61995-06-23 22:03:28 +0000108
109
110class Lock:
111
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000112 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)
Guido van Rossum8608ab61995-06-23 22:03:28 +0000121
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000122 def __del__(self):
Collin Winter6f2df4d2007-07-17 20:59:35 +0000123 print("__del__")
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000124 self.unlock()
Guido van Rossum8608ab61995-06-23 22:03:28 +0000125
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000126 def setlockdir(self):
127 while 1:
128 try:
129 self.lockdir = self.cvslck
Collin Winter6f2df4d2007-07-17 20:59:35 +0000130 os.mkdir(self.cvslck, 0o777)
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000131 return
Guido van Rossumb940e112007-01-10 16:19:56 +0000132 except os.error as msg:
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000133 self.lockdir = None
Georg Brandld11b68a2008-01-06 21:13:42 +0000134 if msg.args[0] == EEXIST:
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000135 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))
Guido van Rossum8608ab61995-06-23 22:03:28 +0000143
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000144 def unlock(self):
145 self.unlockfile()
146 self.unlockdir()
Guido van Rossum8608ab61995-06-23 22:03:28 +0000147
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000148 def unlockfile(self):
149 if self.lockfile:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000150 print("unlink", self.lockfile)
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000151 try:
152 os.unlink(self.lockfile)
153 except os.error:
154 pass
155 self.lockfile = None
Guido van Rossum8608ab61995-06-23 22:03:28 +0000156
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000157 def unlockdir(self):
158 if self.lockdir:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000159 print("rmdir", self.lockdir)
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000160 try:
161 os.rmdir(self.lockdir)
162 except os.error:
163 pass
164 self.lockdir = None
Guido van Rossum8608ab61995-06-23 22:03:28 +0000165
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000166 def sleep(self, st):
167 sleep(st, self.repository, self.delay)
Guido van Rossum8608ab61995-06-23 22:03:28 +0000168
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000169 def join(self, name):
170 return os.path.join(self.repository, name)
Guido van Rossum8608ab61995-06-23 22:03:28 +0000171
172
173def sleep(st, repository, delay):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000174 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
Collin Winter6f2df4d2007-07-17 20:59:35 +0000182 print("[%s]" % time.ctime(time.time())[11:19], end=' ')
183 print("Waiting for %s's lock in" % user, repository)
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000184 time.sleep(delay)
Guido van Rossum8608ab61995-06-23 22:03:28 +0000185
186
187class ReadLock(Lock):
188
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000189 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()
Guido van Rossum8608ab61995-06-23 22:03:28 +0000202
203
204class WriteLock(Lock):
205
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000206 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()
Guido van Rossum8608ab61995-06-23 22:03:28 +0000218
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000219 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
Guido van Rossum8608ab61995-06-23 22:03:28 +0000229
230
231def MultipleWriteLock(repositories, delay = DELAY):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000232 while 1:
233 locks = []
234 for r in repositories:
235 try:
236 locks.append(WriteLock(r, 0))
Guido van Rossumb940e112007-01-10 16:19:56 +0000237 except Locked as instance:
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000238 del locks
239 break
240 else:
241 break
242 sleep(instance.msg, r, delay)
243 return list
Guido van Rossum8608ab61995-06-23 22:03:28 +0000244
245
246def test():
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000247 import sys
248 if sys.argv[1:]:
249 repository = sys.argv[1]
250 else:
251 repository = "."
252 rl = None
253 wl = None
254 try:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000255 print("attempting write lock ...")
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000256 wl = WriteLock(repository)
Collin Winter6f2df4d2007-07-17 20:59:35 +0000257 print("got it.")
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000258 wl.unlock()
Collin Winter6f2df4d2007-07-17 20:59:35 +0000259 print("attempting read lock ...")
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000260 rl = ReadLock(repository)
Collin Winter6f2df4d2007-07-17 20:59:35 +0000261 print("got it.")
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000262 rl.unlock()
263 finally:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000264 print([1])
265 print([2])
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000266 if rl:
267 rl.unlock()
Collin Winter6f2df4d2007-07-17 20:59:35 +0000268 print([3])
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000269 if wl:
270 wl.unlock()
Collin Winter6f2df4d2007-07-17 20:59:35 +0000271 print([4])
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000272 rl = None
Collin Winter6f2df4d2007-07-17 20:59:35 +0000273 print([5])
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000274 wl = None
Collin Winter6f2df4d2007-07-17 20:59:35 +0000275 print([6])
Guido van Rossum8608ab61995-06-23 22:03:28 +0000276
277
278if __name__ == '__main__':
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000279 test()