1 # portage: Lock management code
2 # Copyright 2004-2010 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
5 __all__ = ["lockdir", "unlockdir", "lockfile", "unlockfile", \
6 "hardlock_name", "hardlink_is_mine", "hardlink_lockfile", \
7 "unhardlink_lockfile", "hardlock_cleanup"]
17 from portage import os
18 from portage.const import PORTAGE_BIN_PATH
19 from portage.exception import DirectoryNotFound, FileNotFound, \
20 InvalidData, TryAgain, OperationNotPermitted, PermissionDenied
21 from portage.data import portage_gid
22 from portage.util import writemsg
23 from portage.localization import _
25 if sys.hexversion >= 0x3000000:
29 _default_lock_fn = fcntl.lockf
31 if platform.python_implementation() == 'PyPy':
32 # workaround for https://bugs.pypy.org/issue747
33 _default_lock_fn = fcntl.flock
35 # Used by emerge in order to disable the "waiting for lock" message
36 # so that it doesn't interfere with the status display.
39 def lockdir(mydir, flags=0):
40 return lockfile(mydir, wantnewlockfile=1, flags=flags)
41 def unlockdir(mylock):
42 return unlockfile(mylock)
44 def lockfile(mypath, wantnewlockfile=0, unlinkfile=0,
45 waiting_msg=None, flags=0):
47 If wantnewlockfile is True then this creates a lockfile in the parent
48 directory as the file: '.' + basename + '.portage_lockfile'.
52 raise InvalidData(_("Empty path given"))
54 if isinstance(mypath, basestring) and mypath[-1] == '/':
57 if hasattr(mypath, 'fileno'):
58 mypath = mypath.fileno()
59 if isinstance(mypath, int):
64 base, tail = os.path.split(mypath)
65 lockfilename = os.path.join(base, "." + tail + ".portage_lockfile")
71 if isinstance(mypath, basestring):
72 if not os.path.exists(os.path.dirname(mypath)):
73 raise DirectoryNotFound(os.path.dirname(mypath))
74 preexisting = os.path.exists(lockfilename)
75 old_mask = os.umask(000)
78 myfd = os.open(lockfilename, os.O_CREAT|os.O_RDWR, 0o660)
80 func_call = "open('%s')" % lockfilename
81 if e.errno == OperationNotPermitted.errno:
82 raise OperationNotPermitted(func_call)
83 elif e.errno == PermissionDenied.errno:
84 raise PermissionDenied(func_call)
90 if os.stat(lockfilename).st_gid != portage_gid:
91 os.chown(lockfilename, -1, portage_gid)
93 if e.errno in (errno.ENOENT, errno.ESTALE):
94 return lockfile(mypath,
95 wantnewlockfile=wantnewlockfile,
96 unlinkfile=unlinkfile, waiting_msg=waiting_msg,
99 writemsg("%s: chown('%s', -1, %d)\n" % \
100 (e, lockfilename, portage_gid), noiselevel=-1)
101 writemsg(_("Cannot chown a lockfile: '%s'\n") % \
102 lockfilename, noiselevel=-1)
103 writemsg(_("Group IDs of current user: %s\n") % \
104 " ".join(str(n) for n in os.getgroups()),
109 elif isinstance(mypath, int):
113 raise ValueError(_("Unknown type passed in '%s': '%s'") % \
114 (type(mypath), mypath))
116 # try for a non-blocking lock, if it's held, throw a message
117 # we're waiting on lockfile and use a blocking attempt.
118 locking_method = _default_lock_fn
120 locking_method(myfd, fcntl.LOCK_EX|fcntl.LOCK_NB)
122 if not hasattr(e, "errno"):
124 if e.errno in (errno.EACCES, errno.EAGAIN):
125 # resource temp unavailable; eg, someone beat us to the lock.
126 if flags & os.O_NONBLOCK:
128 raise TryAgain(mypath)
134 out = portage.output.EOutput()
135 if waiting_msg is None:
136 if isinstance(mypath, int):
137 waiting_msg = _("waiting for lock on fd %i") % myfd
139 waiting_msg = _("waiting for lock on %s\n") % lockfilename
141 out.ebegin(waiting_msg)
142 # try for the exclusive lock now.
144 locking_method(myfd, fcntl.LOCK_EX)
145 except EnvironmentError as e:
151 elif e.errno == errno.ENOLCK:
152 # We're not allowed to lock on this FS.
155 if lockfilename == str(lockfilename):
158 if os.stat(lockfilename)[stat.ST_NLINK] == 1:
159 os.unlink(lockfilename)
162 link_success = hardlink_lockfile(lockfilename)
165 locking_method = None
171 if isinstance(lockfilename, basestring) and \
172 myfd != HARDLINK_FD and _fstat_nlink(myfd) == 0:
173 # The file was deleted on us... Keep trying to make one...
175 writemsg(_("lockfile recurse\n"), 1)
176 lockfilename, myfd, unlinkfile, locking_method = lockfile(
177 mypath, wantnewlockfile=wantnewlockfile, unlinkfile=unlinkfile,
178 waiting_msg=waiting_msg, flags=flags)
180 writemsg(str((lockfilename,myfd,unlinkfile))+"\n",1)
181 return (lockfilename,myfd,unlinkfile,locking_method)
183 def _fstat_nlink(fd):
185 @param fd: an open file descriptor
188 @return: the current number of hardlinks to the file
191 return os.fstat(fd).st_nlink
192 except EnvironmentError as e:
193 if e.errno in (errno.ENOENT, errno.ESTALE):
194 # Some filesystems such as CIFS return
195 # ENOENT which means st_nlink == 0.
199 def unlockfile(mytuple):
201 #XXX: Compatability hack.
202 if len(mytuple) == 3:
203 lockfilename,myfd,unlinkfile = mytuple
204 locking_method = fcntl.flock
205 elif len(mytuple) == 4:
206 lockfilename,myfd,unlinkfile,locking_method = mytuple
210 if(myfd == HARDLINK_FD):
211 unhardlink_lockfile(lockfilename)
214 # myfd may be None here due to myfd = mypath in lockfile()
215 if isinstance(lockfilename, basestring) and \
216 not os.path.exists(lockfilename):
217 writemsg(_("lockfile does not exist '%s'\n") % lockfilename,1)
224 myfd = os.open(lockfilename, os.O_WRONLY,0o660)
226 locking_method(myfd,fcntl.LOCK_UN)
228 if isinstance(lockfilename, basestring):
230 raise IOError(_("Failed to unlock file '%s'\n") % lockfilename)
233 # This sleep call was added to allow other processes that are
234 # waiting for a lock to be able to grab it before it is deleted.
235 # lockfile() already accounts for this situation, however, and
236 # the sleep here adds more time than is saved overall, so am
237 # commenting until it is proved necessary.
240 locking_method(myfd,fcntl.LOCK_EX|fcntl.LOCK_NB)
241 # We won the lock, so there isn't competition for it.
242 # We can safely delete the file.
243 writemsg(_("Got the lockfile...\n"), 1)
244 if _fstat_nlink(myfd) == 1:
245 os.unlink(lockfilename)
246 writemsg(_("Unlinked lockfile...\n"), 1)
247 locking_method(myfd,fcntl.LOCK_UN)
249 writemsg(_("lockfile does not exist '%s'\n") % lockfilename, 1)
254 except Exception as e:
255 writemsg(_("Failed to get lock... someone took it.\n"), 1)
256 writemsg(str(e)+"\n",1)
258 # why test lockfilename? because we may have been handed an
259 # fd originally, and the caller might not like having their
260 # open fd closed automatically on them.
261 if isinstance(lockfilename, basestring):
269 def hardlock_name(path):
270 return path+".hardlock-"+os.uname()[1]+"-"+str(os.getpid())
272 def hardlink_is_mine(link,lock):
274 return os.stat(link).st_nlink == 2
278 def hardlink_lockfile(lockfilename, max_wait=14400):
279 """Does the NFS, hardlink shuffle to ensure locking on the disk.
280 We create a PRIVATE lockfile, that is just a placeholder on the disk.
281 Then we HARDLINK the real lockfile to that private file.
282 If our file can 2 references, then we have the lock. :)
283 Otherwise we lather, rise, and repeat.
284 We default to a 4 hour timeout.
287 start_time = time.time()
288 myhardlock = hardlock_name(lockfilename)
289 reported_waiting = False
291 while(time.time() < (start_time + max_wait)):
292 # We only need it to exist.
293 myfd = os.open(myhardlock, os.O_CREAT|os.O_RDWR,0o660)
296 if not os.path.exists(myhardlock):
298 _("Created lockfile is missing: %(filename)s") % \
299 {"filename" : myhardlock})
302 res = os.link(myhardlock, lockfilename)
306 if hardlink_is_mine(myhardlock, lockfilename):
309 writemsg("\n", noiselevel=-1)
313 writemsg(".", noiselevel=-1)
315 reported_waiting = True
316 msg = _("\nWaiting on (hardlink) lockfile: (one '.' per 3 seconds)\n"
317 "%(bin_path)s/clean_locks can fix stuck locks.\n"
318 "Lockfile: %(lockfilename)s\n") % \
319 {"bin_path": PORTAGE_BIN_PATH, "lockfilename": lockfilename}
320 writemsg(msg, noiselevel=-1)
323 os.unlink(myhardlock)
326 def unhardlink_lockfile(lockfilename):
327 myhardlock = hardlock_name(lockfilename)
328 if hardlink_is_mine(myhardlock, lockfilename):
329 # Make sure not to touch lockfilename unless we really have a lock.
331 os.unlink(lockfilename)
335 os.unlink(myhardlock)
339 def hardlock_cleanup(path, remove_all_locks=False):
340 mypid = str(os.getpid())
341 myhost = os.uname()[1]
342 mydl = os.listdir(path)
349 if os.path.isfile(path+"/"+x):
350 parts = x.split(".hardlock-")
353 hostpid = parts[1].split("-")
354 host = "-".join(hostpid[:-1])
357 if filename not in mylist:
358 mylist[filename] = {}
359 if host not in mylist[filename]:
360 mylist[filename][host] = []
361 mylist[filename][host].append(pid)
366 results.append(_("Found %(count)s locks") % {"count":mycount})
369 if myhost in mylist[x] or remove_all_locks:
370 mylockname = hardlock_name(path+"/"+x)
371 if hardlink_is_mine(mylockname, path+"/"+x) or \
372 not os.path.exists(path+"/"+x) or \
375 for z in mylist[x][y]:
376 filename = path+"/"+x+".hardlock-"+y+"-"+z
377 if filename == mylockname:
380 # We're sweeping through, unlinking everyone's locks.
382 results.append(_("Unlinked: ") + filename)
386 os.unlink(path+"/"+x)
387 results.append(_("Unlinked: ") + path+"/"+x)
388 os.unlink(mylockname)
389 results.append(_("Unlinked: ") + mylockname)
394 os.unlink(mylockname)
395 results.append(_("Unlinked: ") + mylockname)