catalyst/targets/generic_target.py: Pass TERM through to the chroot
[catalyst.git] / catalyst / lock.py
1 #!/usr/bin/python
2 import os
3 import fcntl
4 import errno
5 import sys
6 import string
7 import time
8 from catalyst.support import *
9
10 def writemsg(mystr):
11         sys.stderr.write(mystr)
12         sys.stderr.flush()
13
14 class LockDir:
15         locking_method=fcntl.flock
16         lock_dirs_in_use=[]
17         die_on_failed_lock=True
18         def __del__(self):
19                 self.clean_my_hardlocks()
20                 self.delete_lock_from_path_list()
21                 if self.islocked():
22                         self.fcntl_unlock()
23
24         def __init__(self,lockdir):
25                 self.locked=False
26                 self.myfd=None
27                 self.set_gid(250)
28                 self.locking_method=LockDir.locking_method
29                 self.set_lockdir(lockdir)
30                 self.set_lockfilename(".catalyst_lock")
31                 self.set_lockfile()
32
33                 if LockDir.lock_dirs_in_use.count(lockdir)>0:
34                         raise "This directory already associated with a lock object"
35                 else:
36                         LockDir.lock_dirs_in_use.append(lockdir)
37
38                 self.hardlock_paths={}
39
40         def delete_lock_from_path_list(self):
41                 i=0
42                 try:
43                         if LockDir.lock_dirs_in_use:
44                                 for x in LockDir.lock_dirs_in_use:
45                                         if LockDir.lock_dirs_in_use[i] == self.lockdir:
46                                                 del LockDir.lock_dirs_in_use[i]
47                                                 break
48                                                 i=i+1
49                 except AttributeError:
50                         pass
51
52         def islocked(self):
53                 if self.locked:
54                         return True
55                 else:
56                         return False
57
58         def set_gid(self,gid):
59                 if not self.islocked():
60 #                       if "DEBUG" in self.settings:
61 #                               print "setting gid to", gid
62                         self.gid=gid
63
64         def set_lockdir(self,lockdir):
65                 if not os.path.exists(lockdir):
66                         os.makedirs(lockdir)
67                 if os.path.isdir(lockdir):
68                         if not self.islocked():
69                                 if lockdir[-1] == "/":
70                                         lockdir=lockdir[:-1]
71                                 self.lockdir=normpath(lockdir)
72 #                               if "DEBUG" in self.settings:
73 #                                       print "setting lockdir to", self.lockdir
74                 else:
75                         raise "the lock object needs a path to a dir"
76
77         def set_lockfilename(self,lockfilename):
78                 if not self.islocked():
79                         self.lockfilename=lockfilename
80 #                       if "DEBUG" in self.settings:
81 #                               print "setting lockfilename to", self.lockfilename
82
83         def set_lockfile(self):
84                 if not self.islocked():
85                         self.lockfile=normpath(self.lockdir+'/'+self.lockfilename)
86 #                       if "DEBUG" in self.settings:
87 #                               print "setting lockfile to", self.lockfile
88
89         def read_lock(self):
90                 if not self.locking_method == "HARDLOCK":
91                         self.fcntl_lock("read")
92                 else:
93                         print "HARDLOCKING doesnt support shared-read locks"
94                         print "using exclusive write locks"
95                         self.hard_lock()
96
97         def write_lock(self):
98                 if not self.locking_method == "HARDLOCK":
99                         self.fcntl_lock("write")
100                 else:
101                         self.hard_lock()
102
103         def unlock(self):
104                 if not self.locking_method == "HARDLOCK":
105                         self.fcntl_unlock()
106                 else:
107                         self.hard_unlock()
108
109         def fcntl_lock(self,locktype):
110                 if self.myfd==None:
111                         if not os.path.exists(os.path.dirname(self.lockdir)):
112                                 raise DirectoryNotFound, os.path.dirname(self.lockdir)
113                         if not os.path.exists(self.lockfile):
114                                 old_mask=os.umask(000)
115                                 self.myfd = os.open(self.lockfile, os.O_CREAT|os.O_RDWR,0660)
116                                 try:
117                                         if os.stat(self.lockfile).st_gid != self.gid:
118                                                 os.chown(self.lockfile,os.getuid(),self.gid)
119                                 except SystemExit, e:
120                                         raise
121                                 except OSError, e:
122                                         if e[0] == 2: #XXX: No such file or directory
123                                                 return self.fcntl_locking(locktype)
124                                         else:
125                                                 writemsg("Cannot chown a lockfile. This could cause inconvenience later.\n")
126
127                                 os.umask(old_mask)
128                         else:
129                                 self.myfd = os.open(self.lockfile, os.O_CREAT|os.O_RDWR,0660)
130
131                 try:
132                         if locktype == "read":
133                                 self.locking_method(self.myfd,fcntl.LOCK_SH|fcntl.LOCK_NB)
134                         else:
135                                 self.locking_method(self.myfd,fcntl.LOCK_EX|fcntl.LOCK_NB)
136                 except IOError, e:
137                         if "errno" not in dir(e):
138                                 raise
139                         if e.errno == errno.EAGAIN:
140                                 if not LockDir.die_on_failed_lock:
141                                         # Resource temp unavailable; eg, someone beat us to the lock.
142                                         writemsg("waiting for lock on %s\n" % self.lockfile)
143
144                                         # Try for the exclusive or shared lock again.
145                                         if locktype == "read":
146                                                 self.locking_method(self.myfd,fcntl.LOCK_SH)
147                                         else:
148                                                 self.locking_method(self.myfd,fcntl.LOCK_EX)
149                                 else:
150                                         raise LockInUse,self.lockfile
151                         elif e.errno == errno.ENOLCK:
152                                 pass
153                         else:
154                                 raise
155                 if not os.path.exists(self.lockfile):
156                         os.close(self.myfd)
157                         self.myfd=None
158                         #writemsg("lockfile recurse\n")
159                         self.fcntl_lock(locktype)
160                 else:
161                         self.locked=True
162                         #writemsg("Lockfile obtained\n")
163
164         def fcntl_unlock(self):
165                 import fcntl
166                 unlinkfile = 1
167                 if not os.path.exists(self.lockfile):
168                         print "lockfile does not exist '%s'" % self.lockfile
169                         if (self.myfd != None):
170                                 try:
171                                         os.close(myfd)
172                                         self.myfd=None
173                                 except:
174                                         pass
175                                 return False
176
177                         try:
178                                 if self.myfd == None:
179                                         self.myfd = os.open(self.lockfile, os.O_WRONLY,0660)
180                                         unlinkfile = 1
181                                         self.locking_method(self.myfd,fcntl.LOCK_UN)
182                         except SystemExit, e:
183                                 raise
184                         except Exception, e:
185                                 os.close(self.myfd)
186                                 self.myfd=None
187                                 raise IOError, "Failed to unlock file '%s'\n" % self.lockfile
188                                 try:
189                                         # This sleep call was added to allow other processes that are
190                                         # waiting for a lock to be able to grab it before it is deleted.
191                                         # lockfile() already accounts for this situation, however, and
192                                         # the sleep here adds more time than is saved overall, so am
193                                         # commenting until it is proved necessary.
194                                         #time.sleep(0.0001)
195                                         if unlinkfile:
196                                                 InUse=False
197                                                 try:
198                                                         self.locking_method(self.myfd,fcntl.LOCK_EX|fcntl.LOCK_NB)
199                                                 except:
200                                                         print "Read lock may be in effect. skipping lockfile delete..."
201                                                         InUse=True
202                                                         # We won the lock, so there isn't competition for it.
203                                                         # We can safely delete the file.
204                                                         #writemsg("Got the lockfile...\n")
205                                                         #writemsg("Unlinking...\n")
206                                                         self.locking_method(self.myfd,fcntl.LOCK_UN)
207                                         if not InUse:
208                                                 os.unlink(self.lockfile)
209                                                 os.close(self.myfd)
210                                                 self.myfd=None
211 #                                               if "DEBUG" in self.settings:
212 #                                                       print "Unlinked lockfile..."
213                                 except SystemExit, e:
214                                         raise
215                                 except Exception, e:
216                                         # We really don't care... Someone else has the lock.
217                                         # So it is their problem now.
218                                         print "Failed to get lock... someone took it."
219                                         print str(e)
220
221                                         # Why test lockfilename?  Because we may have been handed an
222                                         # fd originally, and the caller might not like having their
223                                         # open fd closed automatically on them.
224                                         #if type(lockfilename) == types.StringType:
225                                         #        os.close(myfd)
226
227                 if (self.myfd != None):
228                         os.close(self.myfd)
229                         self.myfd=None
230                         self.locked=False
231                         time.sleep(.0001)
232
233         def hard_lock(self,max_wait=14400):
234                 """Does the NFS, hardlink shuffle to ensure locking on the disk.
235                 We create a PRIVATE lockfile, that is just a placeholder on the disk.
236                 Then we HARDLINK the real lockfile to that private file.
237                 If our file can 2 references, then we have the lock. :)
238                 Otherwise we lather, rise, and repeat.
239                 We default to a 4 hour timeout.
240                 """
241
242                 self.myhardlock = self.hardlock_name(self.lockdir)
243
244                 start_time = time.time()
245                 reported_waiting = False
246
247                 while(time.time() < (start_time + max_wait)):
248                         # We only need it to exist.
249                         self.myfd = os.open(self.myhardlock, os.O_CREAT|os.O_RDWR,0660)
250                         os.close(self.myfd)
251
252                         self.add_hardlock_file_to_cleanup()
253                         if not os.path.exists(self.myhardlock):
254                                 raise FileNotFound, "Created lockfile is missing: %(filename)s" % {"filename":self.myhardlock}
255                         try:
256                                 res = os.link(self.myhardlock, self.lockfile)
257                         except SystemExit, e:
258                                 raise
259                         except Exception, e:
260 #                               if "DEBUG" in self.settings:
261 #                                       print "lockfile(): Hardlink: Link failed."
262 #                                       print "Exception: ",e
263                                 pass
264
265                         if self.hardlink_is_mine(self.myhardlock, self.lockfile):
266                                 # We have the lock.
267                                 if reported_waiting:
268                                         print
269                                 return True
270
271                         if reported_waiting:
272                                 writemsg(".")
273                         else:
274                                 reported_waiting = True
275                                 print
276                                 print "Waiting on (hardlink) lockfile: (one '.' per 3 seconds)"
277                                 print "Lockfile: " + self.lockfile
278                         time.sleep(3)
279
280                 os.unlink(self.myhardlock)
281                 return False
282
283         def hard_unlock(self):
284                 try:
285                         if os.path.exists(self.myhardlock):
286                                 os.unlink(self.myhardlock)
287                         if os.path.exists(self.lockfile):
288                                 os.unlink(self.lockfile)
289                 except SystemExit, e:
290                         raise
291                 except:
292                         writemsg("Something strange happened to our hardlink locks.\n")
293
294         def add_hardlock_file_to_cleanup(self):
295                 #mypath = self.normpath(path)
296                 if os.path.isdir(self.lockdir) and os.path.isfile(self.myhardlock):
297                         self.hardlock_paths[self.lockdir]=self.myhardlock
298
299         def remove_hardlock_file_from_cleanup(self):
300                 if self.lockdir in self.hardlock_paths:
301                         del self.hardlock_paths[self.lockdir]
302                         print self.hardlock_paths
303
304         def hardlock_name(self, path):
305                 mypath=path+"/.hardlock-"+os.uname()[1]+"-"+str(os.getpid())
306                 newpath = os.path.normpath(mypath)
307                 if len(newpath) > 1:
308                         if newpath[1] == "/":
309                                 newpath = "/"+newpath.lstrip("/")
310                 return newpath
311
312         def hardlink_is_mine(self,link,lock):
313                 import stat
314                 try:
315                         myhls = os.stat(link)
316                         mylfs = os.stat(lock)
317                 except SystemExit, e:
318                         raise
319                 except:
320                         myhls = None
321                         mylfs = None
322
323                 if myhls:
324                         if myhls[stat.ST_NLINK] == 2:
325                                 return True
326                 if mylfs:
327                         if mylfs[stat.ST_INO] == myhls[stat.ST_INO]:
328                                 return True
329                 return False
330
331         def hardlink_active(lock):
332                 if not os.path.exists(lock):
333                         return False
334
335         def clean_my_hardlocks(self):
336                 try:
337                         for x in self.hardlock_paths.keys():
338                                 self.hardlock_cleanup(x)
339                 except AttributeError:
340                         pass
341
342         def hardlock_cleanup(self,path):
343                 mypid  = str(os.getpid())
344                 myhost = os.uname()[1]
345                 mydl = os.listdir(path)
346                 results = []
347                 mycount = 0
348
349                 mylist = {}
350                 for x in mydl:
351                         filepath=path+"/"+x
352                         if os.path.isfile(filepath):
353                                 parts = filepath.split(".hardlock-")
354                         if len(parts) == 2:
355                                 filename = parts[0]
356                                 hostpid  = parts[1].split("-")
357                                 host  = "-".join(hostpid[:-1])
358                                 pid   = hostpid[-1]
359                         if filename not in mylist:
360                                 mylist[filename] = {}
361
362                         if host not in mylist[filename]:
363                                 mylist[filename][host] = []
364                                 mylist[filename][host].append(pid)
365                                 mycount += 1
366                         else:
367                                 mylist[filename][host].append(pid)
368                                 mycount += 1
369
370
371                 results.append("Found %(count)s locks" % {"count":mycount})
372                 for x in mylist.keys():
373                         if myhost in mylist[x]:
374                                 mylockname = self.hardlock_name(x)
375                                 if self.hardlink_is_mine(mylockname, self.lockfile) or \
376                                         not os.path.exists(self.lockfile):
377                                         for y in mylist[x].keys():
378                                                 for z in mylist[x][y]:
379                                                         filename = x+".hardlock-"+y+"-"+z
380                                                         if filename == mylockname:
381                                                                 self.hard_unlock()
382                                                                 continue
383                                                         try:
384                                                                 # We're sweeping through, unlinking everyone's locks.
385                                                                 os.unlink(filename)
386                                                                 results.append("Unlinked: " + filename)
387                                                         except SystemExit, e:
388                                                                 raise
389                                                         except Exception,e:
390                                                                 pass
391                                         try:
392                                                 os.unlink(x)
393                                                 results.append("Unlinked: " + x)
394                                                 os.unlink(mylockname)
395                                                 results.append("Unlinked: " + mylockname)
396                                         except SystemExit, e:
397                                                 raise
398                                         except Exception,e:
399                                                 pass
400                                 else:
401                                         try:
402                                                 os.unlink(mylockname)
403                                                 results.append("Unlinked: " + mylockname)
404                                         except SystemExit, e:
405                                                 raise
406                                         except Exception,e:
407                                                 pass
408                 return results
409
410 if __name__ == "__main__":
411
412         def lock_work():
413                 print
414                 for i in range(1,6):
415                         print i,time.time()
416                         time.sleep(1)
417                 print
418         def normpath(mypath):
419                 newpath = os.path.normpath(mypath)
420                 if len(newpath) > 1:
421                         if newpath[1] == "/":
422                                 newpath = "/"+newpath.lstrip("/")
423                 return newpath
424
425         print "Lock 5 starting"
426         import time
427         Lock1=LockDir("/tmp/lock_path")
428         Lock1.write_lock()
429         print "Lock1 write lock"
430
431         lock_work()
432
433         Lock1.unlock()
434         print "Lock1 unlock"
435
436         Lock1.read_lock()
437         print "Lock1 read lock"
438
439         lock_work()
440
441         Lock1.unlock()
442         print "Lock1 unlock"
443
444         Lock1.read_lock()
445         print "Lock1 read lock"
446
447         Lock1.write_lock()
448         print "Lock1 write lock"
449
450         lock_work()
451
452         Lock1.unlock()
453         print "Lock1 unlock"
454
455         Lock1.read_lock()
456         print "Lock1 read lock"
457
458         lock_work()
459
460         Lock1.unlock()
461         print "Lock1 unlock"
462
463 #Lock1.write_lock()
464 #time.sleep(2)
465 #Lock1.unlock()
466     ##Lock1.write_lock()
467     #time.sleep(2)
468     #Lock1.unlock()