1 |
Handle cases where a lock file on NFS has been removed by a concurrent |
2 |
process that held the lock earlier. Since stat is not reliable for |
3 |
removed files on NFS with the default file attribute cache behavior |
4 |
('ac' mount option), create a temporary hardlink in order to prove |
5 |
that the file path exists on the NFS server. |
6 |
|
7 |
Bug: https://bugs.gentoo.org/636798 |
8 |
Signed-off-by: Zac Medico <zmedico@g.o> |
9 |
--- |
10 |
lib/portage/locks.py | 76 +++++++++++++++++++++++++++++++++++++------- |
11 |
1 file changed, 64 insertions(+), 12 deletions(-) |
12 |
|
13 |
diff --git a/lib/portage/locks.py b/lib/portage/locks.py |
14 |
index a4e7ec53f..563f3fa0a 100644 |
15 |
--- a/lib/portage/locks.py |
16 |
+++ b/lib/portage/locks.py |
17 |
@@ -20,6 +20,7 @@ from portage.exception import (DirectoryNotFound, FileNotFound, |
18 |
InvalidData, TryAgain, OperationNotPermitted, PermissionDenied, |
19 |
ReadOnlyFileSystem) |
20 |
from portage.util import writemsg |
21 |
+from portage.util.install_mask import _raise_exc |
22 |
from portage.localization import _ |
23 |
|
24 |
if sys.hexversion >= 0x3000000: |
25 |
@@ -148,18 +149,17 @@ def lockfile(mypath, wantnewlockfile=0, unlinkfile=0, |
26 |
preexisting = os.path.exists(lockfilename) |
27 |
old_mask = os.umask(000) |
28 |
try: |
29 |
- try: |
30 |
- myfd = os.open(lockfilename, os.O_CREAT|os.O_RDWR, 0o660) |
31 |
- except OSError as e: |
32 |
- func_call = "open('%s')" % lockfilename |
33 |
- if e.errno == OperationNotPermitted.errno: |
34 |
- raise OperationNotPermitted(func_call) |
35 |
- elif e.errno == PermissionDenied.errno: |
36 |
- raise PermissionDenied(func_call) |
37 |
- elif e.errno == ReadOnlyFileSystem.errno: |
38 |
- raise ReadOnlyFileSystem(func_call) |
39 |
+ while True: |
40 |
+ try: |
41 |
+ myfd = os.open(lockfilename, os.O_CREAT|os.O_RDWR, 0o660) |
42 |
+ except OSError as e: |
43 |
+ if e.errno in (errno.ENOENT, errno.ESTALE) and os.path.isdir(os.path.dirname(lockfilename)): |
44 |
+ # Retry required for NFS (see bug 636798). |
45 |
+ continue |
46 |
+ else: |
47 |
+ _raise_exc(e) |
48 |
else: |
49 |
- raise |
50 |
+ break |
51 |
|
52 |
if not preexisting: |
53 |
try: |
54 |
@@ -273,7 +273,7 @@ def lockfile(mypath, wantnewlockfile=0, unlinkfile=0, |
55 |
|
56 |
|
57 |
if isinstance(lockfilename, basestring) and \ |
58 |
- myfd != HARDLINK_FD and _fstat_nlink(myfd) == 0: |
59 |
+ myfd != HARDLINK_FD and _lockfile_was_removed(myfd, lockfilename): |
60 |
# The file was deleted on us... Keep trying to make one... |
61 |
os.close(myfd) |
62 |
writemsg(_("lockfile recurse\n"), 1) |
63 |
@@ -298,6 +298,58 @@ def lockfile(mypath, wantnewlockfile=0, unlinkfile=0, |
64 |
writemsg(str((lockfilename, myfd, unlinkfile)) + "\n", 1) |
65 |
return (lockfilename, myfd, unlinkfile, locking_method) |
66 |
|
67 |
+ |
68 |
+def _lockfile_was_removed(lock_fd, lock_path): |
69 |
+ """ |
70 |
+ Check if lock_fd still refers to a file located at lock_path, since |
71 |
+ the file may have been removed by a concurrent process that held the |
72 |
+ lock earlier. This implementation includes support for NFS, where |
73 |
+ stat is not reliable for removed files due to the default file |
74 |
+ attribute cache behavior ('ac' mount option). |
75 |
+ |
76 |
+ @param lock_fd: an open file descriptor for a lock file |
77 |
+ @type lock_fd: int |
78 |
+ @param lock_path: path of lock file |
79 |
+ @type lock_path: str |
80 |
+ @rtype: bool |
81 |
+ @return: True if lock_path exists and corresponds to lock_fd, False otherwise |
82 |
+ """ |
83 |
+ try: |
84 |
+ fstat_st = os.fstat(lock_fd) |
85 |
+ except OSError as e: |
86 |
+ if e.errno not in (errno.ENOENT, errno.ESTALE): |
87 |
+ _raise_exc(e) |
88 |
+ return True |
89 |
+ |
90 |
+ # Since stat is not reliable for removed files on NFS with the default |
91 |
+ # file attribute cache behavior ('ac' mount option), create a temporary |
92 |
+ # hardlink in order to prove that the file path exists on the NFS server. |
93 |
+ hardlink_path = hardlock_name(lock_path) |
94 |
+ try: |
95 |
+ os.unlink(hardlink_path) |
96 |
+ except OSError as e: |
97 |
+ if e.errno not in (errno.ENOENT, errno.ESTALE): |
98 |
+ _raise_exc(e) |
99 |
+ try: |
100 |
+ try: |
101 |
+ os.link(lock_path, hardlink_path) |
102 |
+ except OSError as e: |
103 |
+ if e.errno not in (errno.ENOENT, errno.ESTALE): |
104 |
+ _raise_exc(e) |
105 |
+ return True |
106 |
+ |
107 |
+ hardlink_stat = os.stat(hardlink_path) |
108 |
+ if hardlink_stat.st_ino != fstat_st.st_ino or hardlink_stat.st_dev != fstat_st.st_dev: |
109 |
+ return True |
110 |
+ finally: |
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 |
+ return False |
117 |
+ |
118 |
+ |
119 |
def _fstat_nlink(fd): |
120 |
""" |
121 |
@param fd: an open file descriptor |
122 |
-- |
123 |
2.18.1 |