# -*- test-case-name: twisted.test.test_lockfile -*- # Copyright (c) 2005 Divmod, Inc. # See LICENSE for details. """Lock files. Currently in a state of flux, API is unstable. """ __metaclass__ = type import errno, os from time import time as _uniquefloat def unique(): return str(long(_uniquefloat() * 1000)) try: from os import symlink from os import readlink from os import remove as rmlink from os import rename as mvlink except: # XXX Implement an atomic thingamajig for win32 import shutil def symlink(value, filename): newlinkname = filename+"."+unique()+'.newlink' newvalname = os.path.join(newlinkname,"symlink") os.mkdir(newlinkname) f = open(newvalname,'wb') f.write(value) f.flush() f.close() try: os.rename(newlinkname, filename) except: os.remove(newvalname) os.rmdir(newlinkname) raise def readlink(filename): return open(os.path.join(filename,'symlink'),'rb').read() def rmlink(filename): shutil.rmtree(filename) def mvlink(src, dest): try: shutil.rmtree(dest) except: pass os.rename(src,dest) class FilesystemLock: """A mutex. This relies on the filesystem property that creating a symlink is an atomic operation and that it will fail if the symlink already exists. Deleting the symlink will release the lock. @ivar name: The name of the file associated with this lock. @ivar clean: Indicates whether this lock was released cleanly by its last owner. Only meaningful after C{lock} has been called and returns True. """ clean = None locked = False def __init__(self, name): self.name = name def lock(self): """Acquire this lock. @rtype: C{bool} @return: True if the lock is acquired, false otherwise. @raise: Any exception os.symlink() may raise, other than EEXIST. """ try: pid = readlink(self.name) except (OSError, IOError), e: if e.errno != errno.ENOENT: raise self.clean = True else: if not hasattr(os, 'kill'): return False try: os.kill(int(pid), 0) except (OSError, IOError), e: if e.errno != errno.ESRCH: raise rmlink(self.name) self.clean = False else: return False symlink(str(os.getpid()), self.name) self.locked = True return True def unlock(self): """Release this lock. This deletes the directory with the given name. @raise: Any exception os.readlink() may raise, or ValueError if the lock is not owned by this process. """ pid = readlink(self.name) if int(pid) != os.getpid(): raise ValueError("Lock %r not owned by this process" % (self.name,)) rmlink(self.name) self.locked = False def isLocked(name): """Determine if the lock of the given name is held or not. @type name: C{str} @param name: The filesystem path to the lock to test @rtype: C{bool} @return: True if the lock is held, False otherwise. """ l = FilesystemLock(name) result = None try: result = l.lock() finally: if result: l.unlock() return not result __all__ = ['FilesystemLock', 'isLocked']