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 |