1 | """CVS locking algorithm.
|
---|
2 |
|
---|
3 | CVS locking strategy
|
---|
4 | ====================
|
---|
5 |
|
---|
6 | As 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
|
---|
9 | write locks for multiple directories); all lock files are placed in
|
---|
10 | the 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
|
---|
14 | algorithm can still be fooled (1) if the repository's mode is changed
|
---|
15 | while attempting to lock; (2) if this file exists and is writable but
|
---|
16 | the directory is not.]
|
---|
17 |
|
---|
18 | - While creating the actual read/write lock files (which may exist for
|
---|
19 | a long time), a "meta-lock" is held. The meta-lock is a directory
|
---|
20 | named "#cvs.lock" in the repository. The meta-lock is also held while
|
---|
21 | a 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 |
|
---|
46 | Additional 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
|
---|
52 | deadlocks, I presume it should always lock them top-down in the
|
---|
53 | directory hierarchy).
|
---|
54 |
|
---|
55 | - A process should make sure it removes all its lock files and
|
---|
56 | directories when it crashes.
|
---|
57 |
|
---|
58 | - Limitation: one user id should not be committing files into the same
|
---|
59 | repository at the same time.
|
---|
60 |
|
---|
61 |
|
---|
62 | Turn this into Python code
|
---|
63 | --------------------------
|
---|
64 |
|
---|
65 | rl = ReadLock(repository, waittime)
|
---|
66 |
|
---|
67 | wl = WriteLock(repository, waittime)
|
---|
68 |
|
---|
69 | list = MultipleWriteLock([repository1, repository2, ...], waittime)
|
---|
70 |
|
---|
71 | """
|
---|
72 |
|
---|
73 |
|
---|
74 | import os
|
---|
75 | import time
|
---|
76 | import stat
|
---|
77 | import pwd
|
---|
78 |
|
---|
79 |
|
---|
80 | # Default wait time
|
---|
81 | DELAY = 10
|
---|
82 |
|
---|
83 |
|
---|
84 | # XXX This should be the same on all Unix versions
|
---|
85 | EEXIST = 17
|
---|
86 |
|
---|
87 |
|
---|
88 | # Files used for locking (must match cvs.h in the CVS sources)
|
---|
89 | CVSLCK = "#cvs.lck"
|
---|
90 | CVSRFL = "#cvs.rfl."
|
---|
91 | CVSWFL = "#cvs.wfl."
|
---|
92 |
|
---|
93 |
|
---|
94 | class 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 |
|
---|
106 | class Locked(Error):
|
---|
107 | pass
|
---|
108 |
|
---|
109 |
|
---|
110 | class 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 |
|
---|
173 | def 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 |
|
---|
187 | class 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 |
|
---|
204 | class 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 |
|
---|
231 | def 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 |
|
---|
246 | def 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 |
|
---|
279 | if __name__ == '__main__':
|
---|
280 | test()
|
---|