Gentoo Archives: gentoo-commits

From: Zac Medico <zmedico@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] proj/portage:master commit in: lib/portage/
Date: Mon, 11 Feb 2019 19:46:49
Message-Id: 1549566008.16d3d31dbc971ce95d80d6ec0645d6ee92b6baa2.zmedico@gentoo
1 commit: 16d3d31dbc971ce95d80d6ec0645d6ee92b6baa2
2 Author: Zac Medico <zachary.medico <AT> sony <DOT> com>
3 AuthorDate: Wed Feb 6 22:21:47 2019 +0000
4 Commit: Zac Medico <zmedico <AT> gentoo <DOT> org>
5 CommitDate: Thu Feb 7 19:00:08 2019 +0000
6 URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=16d3d31d
7
8 locks: handle lock file removal on NFS (bug 636798)
9
10 Handle cases where a lock file on NFS has been removed by a concurrent
11 process that held the lock earlier. Since stat is not reliable for
12 removed files on NFS with the default file attribute cache behavior
13 ('ac' mount option), create a temporary hardlink in order to prove
14 that the file path exists on the NFS server.
15
16 Bug: https://bugs.gentoo.org/636798
17 Copyright: Sony Interactive Entertainment Inc.
18 Signed-off-by: Zac Medico <zmedico <AT> gentoo.org>
19
20 lib/portage/locks.py | 78 +++++++++++++++++++++++++++++++++++++++++++---------
21 1 file changed, 65 insertions(+), 13 deletions(-)
22
23 diff --git a/lib/portage/locks.py b/lib/portage/locks.py
24 index a4e7ec53f..74c2c086a 100644
25 --- a/lib/portage/locks.py
26 +++ b/lib/portage/locks.py
27 @@ -1,5 +1,5 @@
28 # portage: Lock management code
29 -# Copyright 2004-2014 Gentoo Foundation
30 +# Copyright 2004-2019 Gentoo Authors
31 # Distributed under the terms of the GNU General Public License v2
32
33 __all__ = ["lockdir", "unlockdir", "lockfile", "unlockfile", \
34 @@ -20,6 +20,7 @@ from portage.exception import (DirectoryNotFound, FileNotFound,
35 InvalidData, TryAgain, OperationNotPermitted, PermissionDenied,
36 ReadOnlyFileSystem)
37 from portage.util import writemsg
38 +from portage.util.install_mask import _raise_exc
39 from portage.localization import _
40
41 if sys.hexversion >= 0x3000000:
42 @@ -148,18 +149,17 @@ def lockfile(mypath, wantnewlockfile=0, unlinkfile=0,
43 preexisting = os.path.exists(lockfilename)
44 old_mask = os.umask(000)
45 try:
46 - try:
47 - myfd = os.open(lockfilename, os.O_CREAT|os.O_RDWR, 0o660)
48 - except OSError as e:
49 - func_call = "open('%s')" % lockfilename
50 - if e.errno == OperationNotPermitted.errno:
51 - raise OperationNotPermitted(func_call)
52 - elif e.errno == PermissionDenied.errno:
53 - raise PermissionDenied(func_call)
54 - elif e.errno == ReadOnlyFileSystem.errno:
55 - raise ReadOnlyFileSystem(func_call)
56 + while True:
57 + try:
58 + myfd = os.open(lockfilename, os.O_CREAT|os.O_RDWR, 0o660)
59 + except OSError as e:
60 + if e.errno in (errno.ENOENT, errno.ESTALE) and os.path.isdir(os.path.dirname(lockfilename)):
61 + # Retry required for NFS (see bug 636798).
62 + continue
63 + else:
64 + _raise_exc(e)
65 else:
66 - raise
67 + break
68
69 if not preexisting:
70 try:
71 @@ -273,7 +273,7 @@ def lockfile(mypath, wantnewlockfile=0, unlinkfile=0,
72
73
74 if isinstance(lockfilename, basestring) and \
75 - myfd != HARDLINK_FD and _fstat_nlink(myfd) == 0:
76 + myfd != HARDLINK_FD and _lockfile_was_removed(myfd, lockfilename):
77 # The file was deleted on us... Keep trying to make one...
78 os.close(myfd)
79 writemsg(_("lockfile recurse\n"), 1)
80 @@ -298,6 +298,58 @@ def lockfile(mypath, wantnewlockfile=0, unlinkfile=0,
81 writemsg(str((lockfilename, myfd, unlinkfile)) + "\n", 1)
82 return (lockfilename, myfd, unlinkfile, locking_method)
83
84 +
85 +def _lockfile_was_removed(lock_fd, lock_path):
86 + """
87 + Check if lock_fd still refers to a file located at lock_path, since
88 + the file may have been removed by a concurrent process that held the
89 + lock earlier. This implementation includes support for NFS, where
90 + stat is not reliable for removed files due to the default file
91 + attribute cache behavior ('ac' mount option).
92 +
93 + @param lock_fd: an open file descriptor for a lock file
94 + @type lock_fd: int
95 + @param lock_path: path of lock file
96 + @type lock_path: str
97 + @rtype: bool
98 + @return: True if lock_path exists and corresponds to lock_fd, False otherwise
99 + """
100 + try:
101 + fstat_st = os.fstat(lock_fd)
102 + except OSError as e:
103 + if e.errno not in (errno.ENOENT, errno.ESTALE):
104 + _raise_exc(e)
105 + return True
106 +
107 + # Since stat is not reliable for removed files on NFS with the default
108 + # file attribute cache behavior ('ac' mount option), create a temporary
109 + # hardlink in order to prove that the file path exists on the NFS server.
110 + hardlink_path = hardlock_name(lock_path)
111 + try:
112 + os.unlink(hardlink_path)
113 + except OSError as e:
114 + if e.errno not in (errno.ENOENT, errno.ESTALE):
115 + _raise_exc(e)
116 + try:
117 + try:
118 + os.link(lock_path, hardlink_path)
119 + except OSError as e:
120 + if e.errno not in (errno.ENOENT, errno.ESTALE):
121 + _raise_exc(e)
122 + return True
123 +
124 + hardlink_stat = os.stat(hardlink_path)
125 + if hardlink_stat.st_ino != fstat_st.st_ino or hardlink_stat.st_dev != fstat_st.st_dev:
126 + return True
127 + finally:
128 + try:
129 + os.unlink(hardlink_path)
130 + except OSError as e:
131 + if e.errno not in (errno.ENOENT, errno.ESTALE):
132 + _raise_exc(e)
133 + return False
134 +
135 +
136 def _fstat_nlink(fd):
137 """
138 @param fd: an open file descriptor