Gentoo Archives: gentoo-portage-dev

From: "Michał Górny" <mgorny@g.o>
To: gentoo-portage-dev@l.g.o
Cc: "Michał Górny" <mgorny@g.o>
Subject: [gentoo-portage-dev] [PATCH v2] Make manifest-required-hashes configurable
Date: Tue, 07 Nov 2017 20:31:35
Message-Id: 20171107203125.7595-1-mgorny@gentoo.org
1 The set of required hashes specify which hashes must be present for
2 a distfile not to be refetched. It makes little sense to hardcode this
3 value, and it is mostly useful for transition periods, so make it
4 configurable via layout.conf and default to all hashes
5 in manifest-hashes.
6 ---
7 man/portage.5 | 13 +++++++---
8 pym/portage/_emirrordist/FetchTask.py | 2 +-
9 pym/portage/const.py | 2 +-
10 pym/portage/manifest.py | 26 +++++++++++++-------
11 pym/portage/package/ebuild/digestgen.py | 4 ++--
12 pym/portage/repository/config.py | 42 ++++++++++++++++++++++++++-------
13 pym/portage/tests/ebuild/test_config.py | 1 +
14 repoman/pym/repoman/repos.py | 14 +++++++----
15 8 files changed, 74 insertions(+), 30 deletions(-)
16
17 diff --git a/man/portage.5 b/man/portage.5
18 index 7605d7cfa..e724e1f08 100644
19 --- a/man/portage.5
20 +++ b/man/portage.5
21 @@ -1260,9 +1260,16 @@ for every file), "true" (if an entry exists for a file, enforce it), or "false"
22 (don't check Manifest files at all).
23 .TP
24 .BR manifest\-hashes
25 -List of hashes to generate/check in Manifest files. Valid hashes depend on the
26 -current version of portage; see the portage.checksum module for the current
27 -list.
28 +List of hashes to generate in new/updated entries Manifest files. Valid hashes
29 +depend on the current version of portage; see the portage.checksum module for
30 +the current list. Portage will not rewrite old entries if they satisfy
31 +manifest\-required\-hashes.
32 +.TP
33 +.BR manifest\-required\-hashes
34 +List of hashes that must be used in all Manifest entries. If the hashes listed
35 +here are not present in the Manifest, Portage will refetch all distfiles
36 +and update the respective entries to include them. Must be a subset
37 +of manifest\-hashes. If not specified, defaults to all manifest\-hashes.
38 .TP
39 .BR update\-changelog " = [true|" false "]"
40 The default setting for repoman's --echangelog option.
41 diff --git a/pym/portage/_emirrordist/FetchTask.py b/pym/portage/_emirrordist/FetchTask.py
42 index 203b8c213..47908cb6b 100644
43 --- a/pym/portage/_emirrordist/FetchTask.py
44 +++ b/pym/portage/_emirrordist/FetchTask.py
45 @@ -20,7 +20,7 @@ from portage.util._async.PipeLogger import PipeLogger
46 from portage.util._async.PopenProcess import PopenProcess
47 from _emerge.CompositeTask import CompositeTask
48
49 -default_hash_name = portage.const.MANIFEST2_REQUIRED_HASH
50 +default_hash_name = portage.const.MANIFEST2_HASH_DEFAULT
51
52 # Use --no-check-certificate since Manifest digests should provide
53 # enough security, and certificates can be self-signed or whatnot.
54 diff --git a/pym/portage/const.py b/pym/portage/const.py
55 index 0af57d0e2..ec877b841 100644
56 --- a/pym/portage/const.py
57 +++ b/pym/portage/const.py
58 @@ -207,7 +207,7 @@ EAPI = 6
59 HASHING_BLOCKSIZE = 32768
60
61 MANIFEST2_HASH_DEFAULTS = frozenset(["SHA256", "SHA512", "WHIRLPOOL"])
62 -MANIFEST2_REQUIRED_HASH = "SHA512"
63 +MANIFEST2_HASH_DEFAULT = "SHA512"
64
65 MANIFEST2_IDENTIFIERS = ("AUX", "MISC", "DIST", "EBUILD")
66
67 diff --git a/pym/portage/manifest.py b/pym/portage/manifest.py
68 index 36c82690c..4ec20515e 100644
69 --- a/pym/portage/manifest.py
70 +++ b/pym/portage/manifest.py
71 @@ -26,8 +26,7 @@ from portage import _unicode_encode
72 from portage.exception import DigestException, FileNotFound, \
73 InvalidDataType, MissingParameter, PermissionDenied, \
74 PortageException, PortagePackageException
75 -from portage.const import (MANIFEST2_HASH_DEFAULTS,
76 - MANIFEST2_IDENTIFIERS, MANIFEST2_REQUIRED_HASH)
77 +from portage.const import (MANIFEST2_HASH_DEFAULTS, MANIFEST2_IDENTIFIERS)
78 from portage.localization import _
79
80 _manifest_re = re.compile(
81 @@ -128,7 +127,7 @@ class Manifest(object):
82 parsers = (parseManifest2,)
83 def __init__(self, pkgdir, distdir=None, fetchlist_dict=None,
84 manifest1_compat=DeprecationWarning, from_scratch=False, thin=False,
85 - allow_missing=False, allow_create=True, hashes=None,
86 + allow_missing=False, allow_create=True, hashes=None, required_hashes=None,
87 find_invalid_path_char=None, strict_misc_digests=True):
88 """ Create new Manifest instance for package in pkgdir.
89 Do not parse Manifest file if from_scratch == True (only for internal use)
90 @@ -148,15 +147,21 @@ class Manifest(object):
91 self.pkgdir = _unicode_decode(pkgdir).rstrip(os.sep) + os.sep
92 self.fhashdict = {}
93 self.hashes = set()
94 + self.required_hashes = set()
95
96 if hashes is None:
97 hashes = MANIFEST2_HASH_DEFAULTS
98 + if required_hashes is None:
99 + required_hashes = hashes
100
101 self.hashes.update(hashes)
102 self.hashes.difference_update(hashname for hashname in \
103 list(self.hashes) if hashname not in get_valid_checksum_keys())
104 self.hashes.add("size")
105 - self.hashes.add(MANIFEST2_REQUIRED_HASH)
106 +
107 + self.required_hashes.update(required_hashes)
108 + self.required_hashes.intersection_update(self.hashes)
109 +
110 for t in MANIFEST2_IDENTIFIERS:
111 self.fhashdict[t] = {}
112 if not from_scratch:
113 @@ -269,9 +274,11 @@ class Manifest(object):
114 def checkIntegrity(self):
115 for t in self.fhashdict:
116 for f in self.fhashdict[t]:
117 - if MANIFEST2_REQUIRED_HASH not in self.fhashdict[t][f]:
118 - raise MissingParameter(_("Missing %s checksum: %s %s") %
119 - (MANIFEST2_REQUIRED_HASH, t, f))
120 + diff = self.required_hashes.difference(
121 + set(self.fhashdict[t][f]))
122 + if diff:
123 + raise MissingParameter(_("Missing %s checksum(s): %s %s") %
124 + (' '.join(diff), t, f))
125
126 def write(self, sign=False, force=False):
127 """ Write Manifest instance to disk, optionally signing it. Returns
128 @@ -422,7 +429,7 @@ class Manifest(object):
129 self.fhashdict[ftype][fname] = {}
130 if hashdict != None:
131 self.fhashdict[ftype][fname].update(hashdict)
132 - if not MANIFEST2_REQUIRED_HASH in self.fhashdict[ftype][fname]:
133 + if self.required_hashes.difference(set(self.fhashdict[ftype][fname])):
134 self.updateFileHashes(ftype, fname, checkExisting=False, ignoreMissing=ignoreMissing)
135
136 def removeFile(self, ftype, fname):
137 @@ -462,6 +469,7 @@ class Manifest(object):
138 fetchlist_dict=self.fetchlist_dict, from_scratch=True,
139 thin=self.thin, allow_missing=self.allow_missing,
140 allow_create=self.allow_create, hashes=self.hashes,
141 + required_hashes=self.required_hashes,
142 find_invalid_path_char=self._find_invalid_path_char,
143 strict_misc_digests=self.strict_misc_digests)
144 pn = os.path.basename(self.pkgdir.rstrip(os.path.sep))
145 @@ -487,7 +495,7 @@ class Manifest(object):
146 requiredDistfiles = distlist.copy()
147 required_hash_types = set()
148 required_hash_types.add("size")
149 - required_hash_types.add(MANIFEST2_REQUIRED_HASH)
150 + required_hash_types.update(self.required_hashes)
151 for f in distlist:
152 fname = os.path.join(self.distdir, f)
153 mystat = None
154 diff --git a/pym/portage/package/ebuild/digestgen.py b/pym/portage/package/ebuild/digestgen.py
155 index 95d02db9b..40c1b7288 100644
156 --- a/pym/portage/package/ebuild/digestgen.py
157 +++ b/pym/portage/package/ebuild/digestgen.py
158 @@ -11,7 +11,6 @@ portage.proxy.lazyimport.lazyimport(globals(),
159 )
160
161 from portage import os
162 -from portage.const import MANIFEST2_REQUIRED_HASH
163 from portage.dbapi.porttree import FetchlistDict
164 from portage.dep import use_reduce
165 from portage.exception import InvalidDependString, FileNotFound, \
166 @@ -58,6 +57,7 @@ def digestgen(myarchives=None, mysettings=None, myportdb=None):
167 mytree = os.path.realpath(mytree)
168 mf = mysettings.repositories.get_repo_for_location(mytree)
169
170 + repo_required_hashes = mf.manifest_required_hashes
171 mf = mf.load_manifest(mysettings["O"], mysettings["DISTDIR"],
172 fetchlist_dict=fetchlist_dict)
173
174 @@ -72,7 +72,7 @@ def digestgen(myarchives=None, mysettings=None, myportdb=None):
175 # exist before and after the transition.
176 required_hash_types = set()
177 required_hash_types.add("size")
178 - required_hash_types.add(MANIFEST2_REQUIRED_HASH)
179 + required_hash_types.update(repo_required_hashes)
180 dist_hashes = mf.fhashdict.get("DIST", {})
181
182 # To avoid accidental regeneration of digests with the incorrect
183 diff --git a/pym/portage/repository/config.py b/pym/portage/repository/config.py
184 index 3be0e8bda..be31ed3b1 100644
185 --- a/pym/portage/repository/config.py
186 +++ b/pym/portage/repository/config.py
187 @@ -12,8 +12,7 @@ import re
188 import portage
189 from portage import eclass_cache, os
190 from portage.checksum import get_valid_checksum_keys
191 -from portage.const import (MANIFEST2_REQUIRED_HASH,
192 - PORTAGE_BASE_PATH, REPO_NAME_LOC, USER_CONFIG_PATH)
193 +from portage.const import (PORTAGE_BASE_PATH, REPO_NAME_LOC, USER_CONFIG_PATH)
194 from portage.eapi import eapi_allows_directories_on_profile_level_and_repository_level
195 from portage.env.loaders import KeyValuePairFileLoader
196 from portage.util import (normalize_path, read_corresponding_eapi_file, shlex_split,
197 @@ -86,7 +85,7 @@ class RepoConfig(object):
198 'sync_depth', 'sync_hooks_only_on_change',
199 'sync_type', 'sync_umask', 'sync_uri', 'sync_user', 'thin_manifest',
200 'update_changelog', '_eapis_banned', '_eapis_deprecated',
201 - '_masters_orig', 'module_specific_options',
202 + '_masters_orig', 'module_specific_options', 'manifest_required_hashes',
203 )
204
205 def __init__(self, name, repo_opts, local_config=True):
206 @@ -227,6 +226,7 @@ class RepoConfig(object):
207 self.create_manifest = True
208 self.disable_manifest = False
209 self.manifest_hashes = None
210 + self.manifest_required_hashes = None
211 self.update_changelog = False
212 self.cache_formats = None
213 self.portage1_profiles = True
214 @@ -262,7 +262,7 @@ class RepoConfig(object):
215 for value in ('allow-missing-manifest',
216 'allow-provide-virtual', 'cache-formats',
217 'create-manifest', 'disable-manifest', 'manifest-hashes',
218 - 'profile-formats',
219 + 'manifest-required-hashes', 'profile-formats',
220 'sign-commit', 'sign-manifest', 'thin-manifest', 'update-changelog'):
221 setattr(self, value.lower().replace("-", "_"), layout_data[value])
222
223 @@ -337,6 +337,7 @@ class RepoConfig(object):
224 kwds['allow_missing'] = self.allow_missing_manifest
225 kwds['allow_create'] = self.create_manifest
226 kwds['hashes'] = self.manifest_hashes
227 + kwds['required_hashes'] = self.manifest_required_hashes
228 kwds['strict_misc_digests'] = self.strict_misc_digests
229 if self.disable_manifest:
230 kwds['from_scratch'] = True
231 @@ -1046,20 +1047,41 @@ def parse_layout_conf(repo_location, repo_name=None):
232 data['cache-formats'] = tuple(cache_formats)
233
234 manifest_hashes = layout_data.get('manifest-hashes')
235 + manifest_required_hashes = layout_data.get('manifest-required-hashes')
236 +
237 + if manifest_required_hashes is not None and manifest_hashes is None:
238 + repo_name = _get_repo_name(repo_location, cached=repo_name)
239 + warnings.warn((_("Repository named '%(repo_name)s' specifies "
240 + "'manifest-required-hashes' setting without corresponding "
241 + "'manifest-hashes'. Portage will default it to match "
242 + "the required set but please add the missing entry "
243 + "to: %(layout_filename)s") %
244 + {"repo_name": repo_name or 'unspecified',
245 + "layout_filename":layout_filename}),
246 + SyntaxWarning)
247 + manifest_hashes = manifest_required_hashes
248 +
249 if manifest_hashes is not None:
250 + # require all the hashes unless specified otherwise
251 + if manifest_required_hashes is None:
252 + manifest_required_hashes = manifest_hashes
253 +
254 + manifest_required_hashes = frozenset(manifest_required_hashes.upper().split())
255 manifest_hashes = frozenset(manifest_hashes.upper().split())
256 - if MANIFEST2_REQUIRED_HASH not in manifest_hashes:
257 + missing_required_hashes = manifest_required_hashes.difference(
258 + manifest_hashes)
259 + if missing_required_hashes:
260 repo_name = _get_repo_name(repo_location, cached=repo_name)
261 warnings.warn((_("Repository named '%(repo_name)s' has a "
262 "'manifest-hashes' setting that does not contain "
263 - "the '%(hash)s' hash which is required by this "
264 - "portage version. You will have to upgrade portage "
265 + "the '%(hash)s' hashes which are listed in "
266 + "'manifest-required-hashes'. Please fix that file "
267 "if you want to generate valid manifests for this "
268 "repository: %(layout_filename)s") %
269 {"repo_name": repo_name or 'unspecified',
270 - "hash":MANIFEST2_REQUIRED_HASH,
271 + "hash": ' '.join(missing_required_hashes),
272 "layout_filename":layout_filename}),
273 - DeprecationWarning)
274 + SyntaxWarning)
275 unsupported_hashes = manifest_hashes.difference(
276 get_valid_checksum_keys())
277 if unsupported_hashes:
278 @@ -1074,7 +1096,9 @@ def parse_layout_conf(repo_location, repo_name=None):
279 "hashes":" ".join(sorted(unsupported_hashes)),
280 "layout_filename":layout_filename}),
281 DeprecationWarning)
282 +
283 data['manifest-hashes'] = manifest_hashes
284 + data['manifest-required-hashes'] = manifest_required_hashes
285
286 data['update-changelog'] = layout_data.get('update-changelog', 'false').lower() \
287 == 'true'
288 diff --git a/pym/portage/tests/ebuild/test_config.py b/pym/portage/tests/ebuild/test_config.py
289 index 1dd828538..dcb5ffe0d 100644
290 --- a/pym/portage/tests/ebuild/test_config.py
291 +++ b/pym/portage/tests/ebuild/test_config.py
292 @@ -228,6 +228,7 @@ class ConfigTestCase(TestCase):
293 "profile-formats = pms",
294 "thin-manifests = true",
295 "manifest-hashes = SHA256 SHA512 WHIRLPOOL",
296 + "manifest-required-hashes = SHA512",
297 "# use implicit masters"
298 ),
299 }
300 diff --git a/repoman/pym/repoman/repos.py b/repoman/pym/repoman/repos.py
301 index 11a6231de..e942a599e 100644
302 --- a/repoman/pym/repoman/repos.py
303 +++ b/repoman/pym/repoman/repos.py
304 @@ -100,18 +100,22 @@ class RepoSettings(object):
305 sys.exit(1)
306
307 manifest_hashes = self.repo_config.manifest_hashes
308 + manifest_required_hashes = self.repo_config.manifest_required_hashes
309 if manifest_hashes is None:
310 manifest_hashes = portage.const.MANIFEST2_HASH_DEFAULTS
311 + manifest_required_hashes = manifest_hashes
312
313 if options.mode in ("commit", "fix", "manifest"):
314 - if portage.const.MANIFEST2_REQUIRED_HASH not in manifest_hashes:
315 + missing_required_hashes = manifest_required_hashes.difference(
316 + manifest_hashes)
317 + if missing_required_hashes:
318 msg = (
319 "The 'manifest-hashes' setting in the '%s' repository's "
320 - "metadata/layout.conf does not contain the '%s' hash which "
321 - "is required by this portage version. You will have to "
322 - "upgrade portage if you want to generate valid manifests for "
323 + "metadata/layout.conf does not contain the '%s' hashes which "
324 + "are listed in 'manifest-required-hashes'. Please fix that "
325 + "file if you want to generate valid manifests for "
326 "this repository.") % (
327 - self.repo_config.name, portage.const.MANIFEST2_REQUIRED_HASH)
328 + self.repo_config.name, ' '.join(missing_required_hashes))
329 for line in textwrap.wrap(msg, 70):
330 logging.error(line)
331 sys.exit(1)
332 --
333 2.15.0

Replies