1 # portage: Lock management code
2 # Copyright 2004 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
6 __all__ = ["lockdir", "unlockdir", "lockfile", "unlockfile", \
7 "hardlock_name", "hardlink_is_mine", "hardlink_lockfile", \
8 "unhardlink_lockfile", "hardlock_cleanup"]
10 import errno, os, stat, time
11 from portage.exception import DirectoryNotFound, FileNotFound, \
12 InvalidData, TryAgain, OperationNotPermitted, PermissionDenied
13 from portage.data import portage_gid
14 from portage.output import EOutput
15 from portage.util import writemsg
16 from portage.localization import _
20 # Used by emerge in order to disable the "waiting for lock" message
21 # so that it doesn't interfere with the status display.
25 return lockfile(mydir,wantnewlockfile=1)
26 def unlockdir(mylock):
27 return unlockfile(mylock)
29 def lockfile(mypath, wantnewlockfile=0, unlinkfile=0,
30 waiting_msg=None, flags=0):
31 """Creates all dirs upto, the given dir. Creates a lockfile
32 for the given directory as the file: directoryname+'.portage_lockfile'."""
36 raise InvalidData("Empty path given")
38 if isinstance(mypath, basestring) and mypath[-1] == '/':
41 if hasattr(mypath, 'fileno'):
42 mypath = mypath.fileno()
43 if isinstance(mypath, int):
48 base, tail = os.path.split(mypath)
49 lockfilename = os.path.join(base, "." + tail + ".portage_lockfile")
55 if isinstance(mypath, basestring):
56 if not os.path.exists(os.path.dirname(mypath)):
57 raise DirectoryNotFound(os.path.dirname(mypath))
58 preexisting = os.path.exists(lockfilename)
59 old_mask = os.umask(000)
62 myfd = os.open(lockfilename, os.O_CREAT|os.O_RDWR, 0660)
64 func_call = "open('%s')" % lockfilename
65 if e.errno == OperationNotPermitted.errno:
66 raise OperationNotPermitted(func_call)
67 elif e.errno == PermissionDenied.errno:
68 raise PermissionDenied(func_call)
74 if os.stat(lockfilename).st_gid != portage_gid:
75 os.chown(lockfilename, -1, portage_gid)
77 if e.errno in (errno.ENOENT, errno.ESTALE):
78 return lockfile(mypath,
79 wantnewlockfile=wantnewlockfile,
80 unlinkfile=unlinkfile, waiting_msg=waiting_msg,
83 writemsg("Cannot chown a lockfile. This could " + \
84 "cause inconvenience later.\n")
89 elif isinstance(mypath, int):
93 raise ValueError("Unknown type passed in '%s': '%s'" % \
94 (type(mypath), mypath))
96 # try for a non-blocking lock, if it's held, throw a message
97 # we're waiting on lockfile and use a blocking attempt.
98 locking_method = fcntl.lockf
100 fcntl.lockf(myfd,fcntl.LOCK_EX|fcntl.LOCK_NB)
102 if "errno" not in dir(e):
104 if e.errno in (errno.EACCES, errno.EAGAIN):
105 # resource temp unavailable; eg, someone beat us to the lock.
106 if flags & os.O_NONBLOCK:
107 raise TryAgain(mypath)
112 if waiting_msg is None:
113 if isinstance(mypath, int):
114 waiting_msg = "waiting for lock on fd %i" % myfd
116 waiting_msg = "waiting for lock on %s\n" % lockfilename
117 out.ebegin(waiting_msg)
118 # try for the exclusive lock now.
120 fcntl.lockf(myfd, fcntl.LOCK_EX)
121 except EnvironmentError, e:
125 elif e.errno == errno.ENOLCK:
126 # We're not allowed to lock on this FS.
129 if lockfilename == str(lockfilename):
132 if os.stat(lockfilename)[stat.ST_NLINK] == 1:
133 os.unlink(lockfilename)
136 link_success = hardlink_lockfile(lockfilename)
139 locking_method = None
145 if isinstance(lockfilename, basestring) and \
146 myfd != HARDLINK_FD and _fstat_nlink(myfd) == 0:
147 # The file was deleted on us... Keep trying to make one...
149 writemsg("lockfile recurse\n",1)
150 lockfilename, myfd, unlinkfile, locking_method = lockfile(
151 mypath, wantnewlockfile=wantnewlockfile, unlinkfile=unlinkfile,
152 waiting_msg=waiting_msg, flags=flags)
154 writemsg(str((lockfilename,myfd,unlinkfile))+"\n",1)
155 return (lockfilename,myfd,unlinkfile,locking_method)
157 def _fstat_nlink(fd):
159 @param fd: an open file descriptor
162 @return: the current number of hardlinks to the file
165 return os.fstat(fd).st_nlink
166 except EnvironmentError, e:
167 if e.errno in (errno.ENOENT, errno.ESTALE):
168 # Some filesystems such as CIFS return
169 # ENOENT which means st_nlink == 0.
173 def unlockfile(mytuple):
176 #XXX: Compatability hack.
177 if len(mytuple) == 3:
178 lockfilename,myfd,unlinkfile = mytuple
179 locking_method = fcntl.flock
180 elif len(mytuple) == 4:
181 lockfilename,myfd,unlinkfile,locking_method = mytuple
185 if(myfd == HARDLINK_FD):
186 unhardlink_lockfile(lockfilename)
189 # myfd may be None here due to myfd = mypath in lockfile()
190 if isinstance(lockfilename, basestring) and \
191 not os.path.exists(lockfilename):
192 writemsg("lockfile does not exist '%s'\n" % lockfilename,1)
199 myfd = os.open(lockfilename, os.O_WRONLY,0660)
201 locking_method(myfd,fcntl.LOCK_UN)
203 if isinstance(lockfilename, basestring):
205 raise IOError("Failed to unlock file '%s'\n" % lockfilename)
208 # This sleep call was added to allow other processes that are
209 # waiting for a lock to be able to grab it before it is deleted.
210 # lockfile() already accounts for this situation, however, and
211 # the sleep here adds more time than is saved overall, so am
212 # commenting until it is proved necessary.
215 locking_method(myfd,fcntl.LOCK_EX|fcntl.LOCK_NB)
216 # We won the lock, so there isn't competition for it.
217 # We can safely delete the file.
218 writemsg("Got the lockfile...\n",1)
219 if _fstat_nlink(myfd) == 1:
220 os.unlink(lockfilename)
221 writemsg("Unlinked lockfile...\n",1)
222 locking_method(myfd,fcntl.LOCK_UN)
224 writemsg("lockfile does not exist '%s'\n" % lockfilename,1)
228 writemsg("Failed to get lock... someone took it.\n",1)
229 writemsg(str(e)+"\n",1)
231 # why test lockfilename? because we may have been handed an
232 # fd originally, and the caller might not like having their
233 # open fd closed automatically on them.
234 if isinstance(lockfilename, basestring):
242 def hardlock_name(path):
243 return path+".hardlock-"+os.uname()[1]+"-"+str(os.getpid())
245 def hardlink_is_mine(link,lock):
247 return os.stat(link).st_nlink == 2
251 def hardlink_lockfile(lockfilename, max_wait=14400):
252 """Does the NFS, hardlink shuffle to ensure locking on the disk.
253 We create a PRIVATE lockfile, that is just a placeholder on the disk.
254 Then we HARDLINK the real lockfile to that private file.
255 If our file can 2 references, then we have the lock. :)
256 Otherwise we lather, rise, and repeat.
257 We default to a 4 hour timeout.
260 start_time = time.time()
261 myhardlock = hardlock_name(lockfilename)
262 reported_waiting = False
264 while(time.time() < (start_time + max_wait)):
265 # We only need it to exist.
266 myfd = os.open(myhardlock, os.O_CREAT|os.O_RDWR,0660)
269 if not os.path.exists(myhardlock):
271 _("Created lockfile is missing: %(filename)s") % \
272 {"filename" : myhardlock})
275 res = os.link(myhardlock, lockfilename)
279 if hardlink_is_mine(myhardlock, lockfilename):
282 writemsg("\n", noiselevel=-1)
286 writemsg(".", noiselevel=-1)
288 reported_waiting = True
289 from portage.const import PORTAGE_BIN_PATH
290 msg = "\nWaiting on (hardlink) lockfile:" + \
291 " (one '.' per 3 seconds)\n" + \
292 "%s/clean_locks can fix stuck locks.\n" % PORTAGE_BIN_PATH + \
293 "Lockfile: %s\n" % lockfilename
294 writemsg(msg, noiselevel=-1)
297 os.unlink(myhardlock)
300 def unhardlink_lockfile(lockfilename):
301 myhardlock = hardlock_name(lockfilename)
302 if hardlink_is_mine(myhardlock, lockfilename):
303 # Make sure not to touch lockfilename unless we really have a lock.
305 os.unlink(lockfilename)
309 os.unlink(myhardlock)
313 def hardlock_cleanup(path, remove_all_locks=False):
314 mypid = str(os.getpid())
315 myhost = os.uname()[1]
316 mydl = os.listdir(path)
323 if os.path.isfile(path+"/"+x):
324 parts = x.split(".hardlock-")
327 hostpid = parts[1].split("-")
328 host = "-".join(hostpid[:-1])
331 if filename not in mylist:
332 mylist[filename] = {}
333 if host not in mylist[filename]:
334 mylist[filename][host] = []
335 mylist[filename][host].append(pid)
340 results.append("Found %(count)s locks" % {"count":mycount})
343 if myhost in mylist[x] or remove_all_locks:
344 mylockname = hardlock_name(path+"/"+x)
345 if hardlink_is_mine(mylockname, path+"/"+x) or \
346 not os.path.exists(path+"/"+x) or \
349 for z in mylist[x][y]:
350 filename = path+"/"+x+".hardlock-"+y+"-"+z
351 if filename == mylockname:
354 # We're sweeping through, unlinking everyone's locks.
356 results.append(_("Unlinked: ") + filename)
360 os.unlink(path+"/"+x)
361 results.append(_("Unlinked: ") + path+"/"+x)
362 os.unlink(mylockname)
363 results.append(_("Unlinked: ") + mylockname)
368 os.unlink(mylockname)
369 results.append(_("Unlinked: ") + mylockname)