locks.py: fix _close_fds docstring
[portage.git] / pym / portage / locks.py
index 59026e90adce6c6a5d72b03d343f8629f7bb8883..59fbc6ec09b3288b4781c38478b6caafc224ba41 100644 (file)
@@ -1,5 +1,5 @@
 # portage: Lock management code
-# Copyright 2004-2011 Gentoo Foundation
+# Copyright 2004-2012 Gentoo Foundation
 # Distributed under the terms of the GNU General Public License v2
 
 __all__ = ["lockdir", "unlockdir", "lockfile", "unlockfile", \
@@ -36,6 +36,19 @@ if platform.python_implementation() == 'PyPy':
 # so that it doesn't interfere with the status display.
 _quiet = False
 
+
+_open_fds = set()
+
+def _close_fds():
+       """
+       This is intended to be called after a fork, in order to close file
+       descriptors for locks held by the parent process. This can be called
+       safely after a fork without exec, unlike the _setup_pipes close_fds
+       behavior.
+       """
+       while _open_fds:
+               os.close(_open_fds.pop())
+
 def lockdir(mydir, flags=0):
        return lockfile(mydir, wantnewlockfile=1, flags=flags)
 def unlockdir(mylock):
@@ -51,14 +64,24 @@ def lockfile(mypath, wantnewlockfile=0, unlinkfile=0,
        if not mypath:
                raise InvalidData(_("Empty path given"))
 
+       # Support for file object or integer file descriptor parameters is
+       # deprecated due to ambiguity in whether or not it's safe to close
+       # the file descriptor, making it prone to "Bad file descriptor" errors
+       # or file descriptor leaks.
        if isinstance(mypath, basestring) and mypath[-1] == '/':
                mypath = mypath[:-1]
 
        lockfilename_path = mypath
        if hasattr(mypath, 'fileno'):
+               warnings.warn("portage.locks.lockfile() support for "
+                       "file object parameters is deprecated. Use a file path instead.",
+                       DeprecationWarning, stacklevel=2)
                lockfilename_path = getattr(mypath, 'name', None)
                mypath = mypath.fileno()
        if isinstance(mypath, int):
+               warnings.warn("portage.locks.lockfile() support for integer file "
+                       "descriptor parameters is deprecated. Use a file path instead.",
+                       DeprecationWarning, stacklevel=2)
                lockfilename    = mypath
                wantnewlockfile = 0
                unlinkfile      = 0
@@ -157,7 +180,7 @@ def lockfile(mypath, wantnewlockfile=0, unlinkfile=0,
                        if not isinstance(lockfilename, int):
                                # If a file object was passed in, it's not safe
                                # to close the file descriptor because it may
-                               # still be in use (for example, see emergelog).
+                               # still be in use.
                                os.close(myfd)
                        lockfilename_path = _unicode_decode(lockfilename_path,
                                encoding=_encodings['fs'], errors='strict')
@@ -183,6 +206,9 @@ def lockfile(mypath, wantnewlockfile=0, unlinkfile=0,
                        mypath, wantnewlockfile=wantnewlockfile, unlinkfile=unlinkfile,
                        waiting_msg=waiting_msg, flags=flags)
 
+       if myfd != HARDLINK_FD:
+               _open_fds.add(myfd)
+
        writemsg(str((lockfilename,myfd,unlinkfile))+"\n",1)
        return (lockfilename,myfd,unlinkfile,locking_method)
 
@@ -223,6 +249,7 @@ def unlockfile(mytuple):
                writemsg(_("lockfile does not exist '%s'\n") % lockfilename,1)
                if myfd is not None:
                        os.close(myfd)
+                       _open_fds.remove(myfd)
                return False
 
        try:
@@ -233,6 +260,7 @@ def unlockfile(mytuple):
        except OSError:
                if isinstance(lockfilename, basestring):
                        os.close(myfd)
+                       _open_fds.remove(myfd)
                raise IOError(_("Failed to unlock file '%s'\n") % lockfilename)
 
        try:
@@ -254,6 +282,7 @@ def unlockfile(mytuple):
                        else:
                                writemsg(_("lockfile does not exist '%s'\n") % lockfilename, 1)
                                os.close(myfd)
+                               _open_fds.remove(myfd)
                                return False
        except SystemExit:
                raise
@@ -266,6 +295,7 @@ def unlockfile(mytuple):
        # open fd closed automatically on them.
        if isinstance(lockfilename, basestring):
                os.close(myfd)
+               _open_fds.remove(myfd)
 
        return True
 
@@ -306,10 +336,11 @@ def hardlink_lockfile(lockfilename, max_wait=DeprecationWarning,
        global _quiet
        out = None
        displayed_waiting_msg = False
+       preexisting = os.path.exists(lockfilename)
        myhardlock = hardlock_name(lockfilename)
 
        # myhardlock must not exist prior to our link() call, and we can
-       # can safely unlink it since its file name is unique to our PID
+       # safely unlink it since its file name is unique to our PID
        try:
                os.unlink(myhardlock)
        except OSError as e:
@@ -340,8 +371,11 @@ def hardlink_lockfile(lockfilename, max_wait=DeprecationWarning,
                        myfd_st = None
                        try:
                                myfd_st = os.fstat(myfd)
-                               if myfd_st.st_gid != portage_gid:
-                                       os.fchown(myfd, -1, portage_gid)
+                               if not preexisting:
+                                       # Don't chown the file if it is preexisting, since we
+                                       # want to preserve existing permissions in that case.
+                                       if myfd_st.st_gid != portage_gid:
+                                               os.fchown(myfd, -1, portage_gid)
                        except OSError as e:
                                if e.errno not in (errno.ENOENT, errno.ESTALE):
                                        writemsg("%s: fchown('%s', -1, %d)\n" % \