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] fetch: add force parameter (bug 697566)
Date: Sun, 20 Oct 2019 02:02:26
Message-Id: 20191020015814.18006-1-zmedico@gentoo.org
1 Add a force parameter which forces download even when a file already
2 exists in DISTDIR (and no digests are available to verify it). This
3 avoids the need to remove the existing file in advance, which makes
4 it possible to atomically replace the file and avoid interference
5 with concurrent processes. This is useful when using FETCHCOMMAND to
6 fetch a mirror's layout.conf file, for the purposes of bug 697566.
7
8 Bug: https://bugs.gentoo.org/697566
9 Signed-off-by: Zac Medico <zmedico@g.o>
10 ---
11 lib/portage/package/ebuild/fetch.py | 17 ++++++++---
12 lib/portage/tests/ebuild/test_fetch.py | 40 ++++++++++++++++++++++++--
13 2 files changed, 50 insertions(+), 7 deletions(-)
14
15 diff --git a/lib/portage/package/ebuild/fetch.py b/lib/portage/package/ebuild/fetch.py
16 index 76e4636c2..e01b4df02 100644
17 --- a/lib/portage/package/ebuild/fetch.py
18 +++ b/lib/portage/package/ebuild/fetch.py
19 @@ -432,7 +432,7 @@ def get_mirror_url(mirror_url, filename, cache_path=None):
20
21 def fetch(myuris, mysettings, listonly=0, fetchonly=0,
22 locks_in_subdir=".locks", use_locks=1, try_mirrors=1, digests=None,
23 - allow_missing_digests=True):
24 + allow_missing_digests=True, force=False):
25 """
26 Fetch files to DISTDIR and also verify digests if they are available.
27
28 @@ -455,6 +455,14 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
29 @param allow_missing_digests: Enable fetch even if there are no digests
30 available for verification.
31 @type allow_missing_digests: bool
32 + @param bool: Force download, even when a file already exists in
33 + DISTDIR. This is most useful when there are no digests available,
34 + since otherwise download will be automatically forced if the
35 + existing file does not match the available digests. Also, this
36 + avoids the need to remove the existing file in advance, which
37 + makes it possible to atomically replace the file and avoid
38 + interference with concurrent processes.
39 + @type force: bool
40 @rtype: int
41 @return: 1 if successful, 0 otherwise.
42 """
43 @@ -878,7 +886,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
44 eout.quiet = mysettings.get("PORTAGE_QUIET") == "1"
45 match, mystat = _check_distfile(
46 myfile_path, pruned_digests, eout, hash_filter=hash_filter)
47 - if match:
48 + if match and not (force and not orig_digests):
49 # Skip permission adjustment for symlinks, since we don't
50 # want to modify anything outside of the primary DISTDIR,
51 # and symlinks typically point to PORTAGE_RO_DISTDIRS.
52 @@ -1042,10 +1050,11 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
53 os.unlink(download_path)
54 except EnvironmentError:
55 pass
56 - elif myfile not in mydigests:
57 + elif not orig_digests:
58 # We don't have a digest, but the file exists. We must
59 # assume that it is fully downloaded.
60 - continue
61 + if not force:
62 + continue
63 else:
64 if (mydigests[myfile].get("size") is not None
65 and mystat.st_size < mydigests[myfile]["size"]
66 diff --git a/lib/portage/tests/ebuild/test_fetch.py b/lib/portage/tests/ebuild/test_fetch.py
67 index f50fea0dd..f4eb0404b 100644
68 --- a/lib/portage/tests/ebuild/test_fetch.py
69 +++ b/lib/portage/tests/ebuild/test_fetch.py
70 @@ -119,10 +119,44 @@ class EbuildFetchTestCase(TestCase):
71 with open(foo_path, 'rb') as f:
72 self.assertNotEqual(f.read(), distfiles['foo'])
73
74 - # Remove the stale file in order to forcefully update it.
75 - os.unlink(foo_path)
76 + # Use force=True to update the stale file.
77 + self.assertTrue(bool(run_async(fetch, foo_uri, settings, try_mirrors=False, force=True)))
78
79 - self.assertTrue(bool(run_async(fetch, foo_uri, settings, try_mirrors=False)))
80 + with open(foo_path, 'rb') as f:
81 + self.assertEqual(f.read(), distfiles['foo'])
82 +
83 + # Test force=True with FEATURES=skiprocheck, using read-only DISTDIR.
84 + # FETCHCOMMAND is set to temporarily chmod +w DISTDIR. Note that
85 + # FETCHCOMMAND must perform atomic rename itself due to read-only
86 + # DISTDIR.
87 + with open(foo_path, 'wb') as f:
88 + f.write(b'stale content\n')
89 + orig_fetchcommand = settings['FETCHCOMMAND']
90 + orig_distdir_mode = os.stat(settings['DISTDIR']).st_mode
91 + temp_fetchcommand = os.path.join(eubin, 'fetchcommand')
92 + with open(temp_fetchcommand, 'w') as f:
93 + f.write("""
94 + set -e
95 + URI=$1
96 + DISTDIR=$2
97 + FILE=$3
98 + trap 'chmod a-w "${DISTDIR}"' EXIT
99 + chmod ug+w "${DISTDIR}"
100 + %s
101 + mv -f "${DISTDIR}/${FILE}.__download__" "${DISTDIR}/${FILE}"
102 + """ % orig_fetchcommand.replace('${FILE}', '${FILE}.__download__'))
103 + try:
104 + os.chmod(settings['DISTDIR'], 0o555)
105 + settings['FETCHCOMMAND'] = '"%s" "%s" "${URI}" "${DISTDIR}" "${FILE}"' % (BASH_BINARY, temp_fetchcommand)
106 + settings.features.add('skiprocheck')
107 + settings.features.remove('distlocks')
108 + self.assertTrue(bool(run_async(fetch, foo_uri, settings, try_mirrors=False, force=True)))
109 + finally:
110 + settings['FETCHCOMMAND'] = orig_fetchcommand
111 + os.chmod(settings['DISTDIR'], orig_distdir_mode)
112 + settings.features.remove('skiprocheck')
113 + settings.features.add('distlocks')
114 + os.unlink(temp_fetchcommand)
115
116 with open(foo_path, 'rb') as f:
117 self.assertEqual(f.read(), distfiles['foo'])
118 --
119 2.21.0