1 # portage: Lock management code
2 # Copyright 2004-2011 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, _encodings, _unicode_decode
18 from portage.exception import DirectoryNotFound, FileNotFound, \
19 InvalidData, TryAgain, OperationNotPermitted, PermissionDenied
20 from portage.data import portage_gid
21 from portage.util import writemsg
22 from portage.localization import _
24 if sys.hexversion >= 0x3000000:
28 _HARDLINK_POLL_LATENCY = 3 # seconds
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 lockfilename_path = mypath
58 if hasattr(mypath, 'fileno'):
59 lockfilename_path = getattr(mypath, 'name', None)
60 mypath = mypath.fileno()
61 if isinstance(mypath, int):
66 base, tail = os.path.split(mypath)
67 lockfilename = os.path.join(base, "." + tail + ".portage_lockfile")
68 lockfilename_path = lockfilename
73 if isinstance(mypath, basestring):
74 if not os.path.exists(os.path.dirname(mypath)):
75 raise DirectoryNotFound(os.path.dirname(mypath))
76 preexisting = os.path.exists(lockfilename)
77 old_mask = os.umask(000)
80 myfd = os.open(lockfilename, os.O_CREAT|os.O_RDWR, 0o660)
82 func_call = "open('%s')" % lockfilename
83 if e.errno == OperationNotPermitted.errno:
84 raise OperationNotPermitted(func_call)
85 elif e.errno == PermissionDenied.errno:
86 raise PermissionDenied(func_call)
92 if os.stat(lockfilename).st_gid != portage_gid:
93 os.chown(lockfilename, -1, portage_gid)
95 if e.errno in (errno.ENOENT, errno.ESTALE):
96 return lockfile(mypath,
97 wantnewlockfile=wantnewlockfile,
98 unlinkfile=unlinkfile, waiting_msg=waiting_msg,
101 writemsg("%s: chown('%s', -1, %d)\n" % \
102 (e, lockfilename, portage_gid), noiselevel=-1)
103 writemsg(_("Cannot chown a lockfile: '%s'\n") % \
104 lockfilename, noiselevel=-1)
105 writemsg(_("Group IDs of current user: %s\n") % \
106 " ".join(str(n) for n in os.getgroups()),
111 elif isinstance(mypath, int):
115 raise ValueError(_("Unknown type passed in '%s': '%s'") % \
116 (type(mypath), mypath))
118 # try for a non-blocking lock, if it's held, throw a message
119 # we're waiting on lockfile and use a blocking attempt.
120 locking_method = _default_lock_fn
122 if "__PORTAGE_TEST_HARDLINK_LOCKS" in os.environ:
123 raise IOError(errno.ENOSYS, "Function not implemented")
124 locking_method(myfd, fcntl.LOCK_EX|fcntl.LOCK_NB)
126 if not hasattr(e, "errno"):
128 if e.errno in (errno.EACCES, errno.EAGAIN):
129 # resource temp unavailable; eg, someone beat us to the lock.
130 if flags & os.O_NONBLOCK:
132 raise TryAgain(mypath)
138 out = portage.output.EOutput()
139 if waiting_msg is None:
140 if isinstance(mypath, int):
141 waiting_msg = _("waiting for lock on fd %i") % myfd
143 waiting_msg = _("waiting for lock on %s\n") % lockfilename
145 out.ebegin(waiting_msg)
146 # try for the exclusive lock now.
148 locking_method(myfd, fcntl.LOCK_EX)
149 except EnvironmentError as e:
155 elif e.errno in (errno.ENOSYS, errno.ENOLCK):
156 # We're not allowed to lock on this FS.
157 if not isinstance(lockfilename, int):
158 # If a file object was passed in, it's not safe
159 # to close the file descriptor because it may
160 # still be in use (for example, see emergelog).
162 lockfilename_path = _unicode_decode(lockfilename_path,
163 encoding=_encodings['fs'], errors='strict')
164 if not isinstance(lockfilename_path, basestring):
166 link_success = hardlink_lockfile(lockfilename_path,
167 waiting_msg=waiting_msg, flags=flags)
170 lockfilename = lockfilename_path
171 locking_method = None
177 if isinstance(lockfilename, basestring) and \
178 myfd != HARDLINK_FD and _fstat_nlink(myfd) == 0:
179 # The file was deleted on us... Keep trying to make one...
181 writemsg(_("lockfile recurse\n"), 1)
182 lockfilename, myfd, unlinkfile, locking_method = lockfile(
183 mypath, wantnewlockfile=wantnewlockfile, unlinkfile=unlinkfile,
184 waiting_msg=waiting_msg, flags=flags)
186 writemsg(str((lockfilename,myfd,unlinkfile))+"\n",1)
187 return (lockfilename,myfd,unlinkfile,locking_method)
189 def _fstat_nlink(fd):
191 @param fd: an open file descriptor
194 @return: the current number of hardlinks to the file
197 return os.fstat(fd).st_nlink
198 except EnvironmentError as e:
199 if e.errno in (errno.ENOENT, errno.ESTALE):
200 # Some filesystems such as CIFS return
201 # ENOENT which means st_nlink == 0.
205 def unlockfile(mytuple):
207 #XXX: Compatability hack.
208 if len(mytuple) == 3:
209 lockfilename,myfd,unlinkfile = mytuple
210 locking_method = fcntl.flock
211 elif len(mytuple) == 4:
212 lockfilename,myfd,unlinkfile,locking_method = mytuple
216 if(myfd == HARDLINK_FD):
217 unhardlink_lockfile(lockfilename, unlinkfile=unlinkfile)
220 # myfd may be None here due to myfd = mypath in lockfile()
221 if isinstance(lockfilename, basestring) and \
222 not os.path.exists(lockfilename):
223 writemsg(_("lockfile does not exist '%s'\n") % lockfilename,1)
230 myfd = os.open(lockfilename, os.O_WRONLY,0o660)
232 locking_method(myfd,fcntl.LOCK_UN)
234 if isinstance(lockfilename, basestring):
236 raise IOError(_("Failed to unlock file '%s'\n") % lockfilename)
239 # This sleep call was added to allow other processes that are
240 # waiting for a lock to be able to grab it before it is deleted.
241 # lockfile() already accounts for this situation, however, and
242 # the sleep here adds more time than is saved overall, so am
243 # commenting until it is proved necessary.
246 locking_method(myfd,fcntl.LOCK_EX|fcntl.LOCK_NB)
247 # We won the lock, so there isn't competition for it.
248 # We can safely delete the file.
249 writemsg(_("Got the lockfile...\n"), 1)
250 if _fstat_nlink(myfd) == 1:
251 os.unlink(lockfilename)
252 writemsg(_("Unlinked lockfile...\n"), 1)
253 locking_method(myfd,fcntl.LOCK_UN)
255 writemsg(_("lockfile does not exist '%s'\n") % lockfilename, 1)
260 except Exception as e:
261 writemsg(_("Failed to get lock... someone took it.\n"), 1)
262 writemsg(str(e)+"\n",1)
264 # why test lockfilename? because we may have been handed an
265 # fd originally, and the caller might not like having their
266 # open fd closed automatically on them.
267 if isinstance(lockfilename, basestring):
275 def hardlock_name(path):
276 base, tail = os.path.split(path)
277 return os.path.join(base, ".%s.hardlock-%s-%s" %
278 (tail, os.uname()[1], os.getpid()))
280 def hardlink_is_mine(link,lock):
282 lock_st = os.stat(lock)
283 if lock_st.st_nlink == 2:
284 link_st = os.stat(link)
285 return lock_st.st_ino == link_st.st_ino and \
286 lock_st.st_dev == link_st.st_dev
290 def hardlink_lockfile(lockfilename, max_wait=DeprecationWarning,
291 waiting_msg=None, flags=0):
292 """Does the NFS, hardlink shuffle to ensure locking on the disk.
293 We create a PRIVATE hardlink to the real lockfile, that is just a
294 placeholder on the disk.
295 If our file can 2 references, then we have the lock. :)
296 Otherwise we lather, rise, and repeat.
299 if max_wait is not DeprecationWarning:
300 warnings.warn("The 'max_wait' parameter of "
301 "portage.locks.hardlink_lockfile() is now unused. Use "
302 "flags=os.O_NONBLOCK instead.",
303 DeprecationWarning, stacklevel=2)
307 displayed_waiting_msg = False
308 myhardlock = hardlock_name(lockfilename)
310 # myhardlock must not exist prior to our link() call, and we can
311 # can safely unlink it since its file name is unique to our PID
313 os.unlink(myhardlock)
315 if e.errno in (errno.ENOENT, errno.ESTALE):
318 func_call = "unlink('%s')" % myhardlock
319 if e.errno == OperationNotPermitted.errno:
320 raise OperationNotPermitted(func_call)
321 elif e.errno == PermissionDenied.errno:
322 raise PermissionDenied(func_call)
327 # create lockfilename if it doesn't exist yet
329 myfd = os.open(lockfilename, os.O_CREAT|os.O_RDWR, 0o660)
331 func_call = "open('%s')" % lockfilename
332 if e.errno == OperationNotPermitted.errno:
333 raise OperationNotPermitted(func_call)
334 elif e.errno == PermissionDenied.errno:
335 raise PermissionDenied(func_call)
340 if os.fstat(myfd).st_gid != portage_gid:
341 os.fchown(myfd, -1, portage_gid)
343 if e.errno not in (errno.ENOENT, errno.ESTALE):
344 writemsg("%s: fchown('%s', -1, %d)\n" % \
345 (e, lockfilename, portage_gid), noiselevel=-1)
346 writemsg(_("Cannot chown a lockfile: '%s'\n") % \
347 lockfilename, noiselevel=-1)
348 writemsg(_("Group IDs of current user: %s\n") % \
349 " ".join(str(n) for n in os.getgroups()),
355 os.link(lockfilename, myhardlock)
357 func_call = "link('%s', '%s')" % (lockfilename, myhardlock)
358 if e.errno == OperationNotPermitted.errno:
359 raise OperationNotPermitted(func_call)
360 elif e.errno == PermissionDenied.errno:
361 raise PermissionDenied(func_call)
362 elif e.errno in (errno.ESTALE, errno.ENOENT):
363 # another process has removed the file, so we'll have
369 if hardlink_is_mine(myhardlock, lockfilename):
375 os.unlink(myhardlock)
377 # This should not happen, since the file name of
378 # myhardlock is unique to our host and PID,
379 # and the above link() call succeeded.
380 if e.errno not in (errno.ENOENT, errno.ESTALE):
382 raise FileNotFound(myhardlock)
384 if flags & os.O_NONBLOCK:
385 raise TryAgain(lockfilename)
387 if out is None and not _quiet:
388 out = portage.output.EOutput()
389 if out is not None and not displayed_waiting_msg:
390 displayed_waiting_msg = True
391 if waiting_msg is None:
392 waiting_msg = _("waiting for lock on %s\n") % lockfilename
393 out.ebegin(waiting_msg)
395 time.sleep(_HARDLINK_POLL_LATENCY)
399 def unhardlink_lockfile(lockfilename, unlinkfile=True):
400 myhardlock = hardlock_name(lockfilename)
401 if unlinkfile and hardlink_is_mine(myhardlock, lockfilename):
402 # Make sure not to touch lockfilename unless we really have a lock.
404 os.unlink(lockfilename)
408 os.unlink(myhardlock)
412 def hardlock_cleanup(path, remove_all_locks=False):
413 mypid = str(os.getpid())
414 myhost = os.uname()[1]
415 mydl = os.listdir(path)
422 if os.path.isfile(path+"/"+x):
423 parts = x.split(".hardlock-")
425 filename = parts[0][1:]
426 hostpid = parts[1].split("-")
427 host = "-".join(hostpid[:-1])
430 if filename not in mylist:
431 mylist[filename] = {}
432 if host not in mylist[filename]:
433 mylist[filename][host] = []
434 mylist[filename][host].append(pid)
439 results.append(_("Found %(count)s locks") % {"count":mycount})
442 if myhost in mylist[x] or remove_all_locks:
443 mylockname = hardlock_name(path+"/"+x)
444 if hardlink_is_mine(mylockname, path+"/"+x) or \
445 not os.path.exists(path+"/"+x) or \
448 for z in mylist[x][y]:
449 filename = path+"/."+x+".hardlock-"+y+"-"+z
450 if filename == mylockname:
453 # We're sweeping through, unlinking everyone's locks.
455 results.append(_("Unlinked: ") + filename)
459 os.unlink(path+"/"+x)
460 results.append(_("Unlinked: ") + path+"/"+x)
461 os.unlink(mylockname)
462 results.append(_("Unlinked: ") + mylockname)
467 os.unlink(mylockname)
468 results.append(_("Unlinked: ") + mylockname)