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 |