Gentoo Archives: gentoo-portage-dev

From: Zac Medico <zmedico@g.o>
To: gentoo-portage-dev@l.g.o
Cc: Zac Medico <zmedico@g.o>
Subject: [gentoo-portage-dev] [PATCH] locks: handle lock file removal on NFS (bug 636798)
Date: Thu, 07 Feb 2019 00:01:27
Message-Id: 20190207000053.29985-1-zmedico@gentoo.org
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