1 # Copyright 1999-2006 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
7 import portage.versions, portage.const
8 from portage.checksum import *
9 from portage.exception import *
10 from portage.util import write_atomic
12 class FileNotInManifestException(PortageException):
15 def manifest2AuxfileFilter(filename):
16 filename = filename.strip(os.sep)
17 mysplit = filename.split(os.path.sep)
23 return not filename.startswith("digest-")
25 def manifest2MiscfileFilter(filename):
26 filename = filename.strip(os.sep)
27 return not (filename in ["CVS", ".svn", "files", "Manifest"] or filename.endswith(".ebuild"))
29 def guessManifestFileType(filename):
30 """ Perform a best effort guess of which type the given filename is, avoid using this if possible """
31 if filename.startswith("files" + os.sep + "digest-"):
33 if filename.startswith("files" + os.sep):
35 elif filename.endswith(".ebuild"):
37 elif filename in ["ChangeLog", "metadata.xml"]:
42 def parseManifest2(mysplit):
44 if len(mysplit) > 4 and mysplit[0] in portage.const.MANIFEST2_IDENTIFIERS:
47 mysize = int(mysplit[2])
48 myhashes = dict(zip(mysplit[3::2], mysplit[4::2]))
49 myhashes["size"] = mysize
50 myentry = Manifest2Entry(type=mytype, name=myname, hashes=myhashes)
53 def parseManifest1(mysplit):
55 if len(mysplit) == 4 and mysplit[0] in ["size"] + portage.const.MANIFEST1_HASH_FUNCTIONS:
58 mytype = guessManifestFileType(myname)
60 if myname.startswith("files" + os.path.sep):
62 mysize = int(mysplit[3])
63 myhashes = {mysplit[0]: mysplit[1]}
64 myhashes["size"] = mysize
65 myentry = Manifest1Entry(type=mytype, name=myname, hashes=myhashes)
68 class ManifestEntry(object):
69 __slots__ = ("type", "name", "hashes")
70 def __init__(self, **kwargs):
71 for k, v in kwargs.iteritems():
73 def __cmp__(self, other):
74 if str(self) == str(other):
78 class Manifest1Entry(ManifestEntry):
80 for hashkey in self.hashes:
83 hashvalue = self.hashes[hashkey]
85 if self.type == "AUX" and not myname.startswith("files" + os.sep):
86 myname = os.path.join("files", myname)
87 return " ".join([hashkey, str(hashvalue), myname, str(self.hashes["size"])])
89 class Manifest2Entry(ManifestEntry):
91 myline = " ".join([self.type, self.name, str(self.hashes["size"])])
92 myhashkeys = self.hashes.keys()
93 myhashkeys.remove("size")
96 myline += " " + h + " " + str(self.hashes[h])
99 class Manifest(object):
100 parsers = (parseManifest2, parseManifest1)
101 def __init__(self, pkgdir, distdir, fetchlist_dict=None,
102 manifest1_compat=True, from_scratch=False):
103 """ create new Manifest instance for package in pkgdir
104 and add compability entries for old portage versions if manifest1_compat == True.
105 Do not parse Manifest file if from_scratch == True (only for internal use)
106 The fetchlist_dict parameter is required only for generation of
107 a Manifest (not needed for parsing and checking sums)."""
108 self.pkgdir = pkgdir.rstrip(os.sep) + os.sep
110 self.hashes = portage.const.MANIFEST2_HASH_FUNCTIONS[:]
111 self.hashes.append("size")
113 self.hashes.extend(portage.const.MANIFEST1_HASH_FUNCTIONS)
114 self.hashes = set(self.hashes)
115 for t in portage.const.MANIFEST2_IDENTIFIERS:
116 self.fhashdict[t] = {}
117 self.compat = manifest1_compat
120 if fetchlist_dict != None:
121 self.fetchlist_dict = fetchlist_dict
123 self.fetchlist_dict = {}
124 self.distdir = distdir
125 self.guessType = guessManifestFileType
127 def getFullname(self):
128 """ Returns the absolute path to the Manifest file for this instance """
129 return os.path.join(self.pkgdir, "Manifest")
131 def getDigests(self):
132 """ Compability function for old digest/manifest code, returns dict of filename:{hashfunction:hashvalue} """
134 for t in portage.const.MANIFEST2_IDENTIFIERS:
135 rval.update(self.fhashdict[t])
138 def getTypeDigests(self, ftype):
139 """ Similar to getDigests(), but restricted to files of the given type. """
140 return self.fhashdict[ftype]
142 def _readDigests(self, myhashdict=None):
143 """ Parse old style digest files for this Manifest instance """
144 if myhashdict is None:
147 for d in os.listdir(os.path.join(self.pkgdir, "files")):
148 if d.startswith("digest-"):
149 self._readManifest(os.path.join(self.pkgdir, "files", d), mytype="DIST",
150 myhashdict=myhashdict)
151 except (IOError, OSError), e:
152 if e.errno == errno.ENOENT:
158 def _readManifest(self, file_path, myhashdict=None, **kwargs):
159 """Parse a manifest or an old style digest. If myhashdict is given
160 then data will be added too it. Otherwise, a new dict will be created
163 fd = open(file_path, "r")
164 if myhashdict is None:
166 self._parseDigests(fd, myhashdict=myhashdict, **kwargs)
169 except (OSError, IOError), e:
170 if e.errno == errno.ENOENT:
171 raise FileNotFound(file_path)
176 """ Parse Manifest file for this instance """
178 self._readManifest(self.getFullname(), myhashdict=self.fhashdict)
182 self._readDigests(myhashdict=self.fhashdict)
184 def _parseManifestLines(self, mylines):
185 """Parse manifest lines and return a list of manifest entries."""
186 for myline in mylines:
188 mysplit = myline.split()
189 for parser in self.parsers:
190 myentry = parser(mysplit)
191 if myentry is not None:
193 break # go to the next line
195 def _parseDigests(self, mylines, myhashdict=None, mytype=None):
196 """Parse manifest entries and store the data in myhashdict. If mytype
197 is specified, it will override the type for all parsed entries."""
198 if myhashdict is None:
200 for myentry in self._parseManifestLines(mylines):
202 myentry_type = myentry.type
204 myentry_type = mytype
205 myhashdict.setdefault(myentry_type, {})
206 myhashdict[myentry_type].setdefault(myentry.name, {})
207 myhashdict[myentry_type][myentry.name].update(myentry.hashes)
210 def _writeDigests(self, force=False):
211 """ Create old style digest files for this Manifest instance """
212 cpvlist = [os.path.join(self._pkgdir_category(), x[:-7]) for x in os.listdir(self.pkgdir) if x.endswith(".ebuild")]
215 os.makedirs(os.path.join(self.pkgdir, "files"))
217 if oe.errno == errno.EEXIST:
222 dname = os.path.join(self.pkgdir, "files", "digest-%s" % self._catsplit(cpv)[1])
223 distlist = self._getCpvDistfiles(cpv)
224 missing_digests = set()
226 if f not in self.fhashdict["DIST"] or len(self.fhashdict["DIST"][f]) == 0:
227 missing_digests.add(f)
229 # This allows us to force remove of stale digests for the
230 # ebuild --force digest option.
231 distlist = [f for f in distlist if f not in missing_digests]
236 old_data = self._parseDigests(f)
238 new_data = self._getDigestData(distlist)
239 if not old_data and not new_data:
241 update_digest = False
242 elif len(old_data) == 1 and "DIST" in old_data:
243 if "DIST" in new_data:
244 for myfile in new_data["DIST"]:
246 new_data["DIST"][myfile].keys():
247 if hashname != "size" and hashname not in \
248 portage.const.MANIFEST1_HASH_FUNCTIONS:
249 del new_data["DIST"][myfile][hashname]
250 if new_data["DIST"] == old_data["DIST"]:
251 update_digest = False
252 except (IOError, OSError), e:
253 if errno.ENOENT == e.errno:
258 mylines = self._createDigestLines1(distlist, self.fhashdict)
260 mylines = "\n".join(mylines) + "\n"
263 write_atomic(dname, mylines)
267 def _getDigestData(self, distlist):
268 """create a hash dict for a specific list of files"""
270 for myname in distlist:
271 for mytype in self.fhashdict:
272 if myname in self.fhashdict[mytype]:
273 myhashdict.setdefault(mytype, {})
274 myhashdict[mytype].setdefault(myname, {})
275 myhashdict[mytype][myname].update(self.fhashdict[mytype][myname])
278 def _createDigestLines1(self, distlist, myhashdict):
279 """ Create an old style digest file."""
281 myfiles = myhashdict["DIST"].keys()
285 myhashkeys = myhashdict["DIST"][f].keys()
288 if h not in portage.const.MANIFEST1_HASH_FUNCTIONS:
290 myline = " ".join([h, str(myhashdict["DIST"][f][h]), f, str(myhashdict["DIST"][f]["size"])])
291 mylines.append(myline)
294 def _addDigestsToManifest(self, digests, fd):
295 """ Add entries for old style digest files to Manifest file """
297 for dname in digests:
298 myhashes = perform_multiple_checksums(dname, portage.const.MANIFEST1_HASH_FUNCTIONS+["size"])
300 mylines.append((" ".join([h, str(myhashes[h]), os.path.join("files", os.path.basename(dname)), str(myhashes["size"])])))
301 fd.write("\n".join(mylines))
304 def _createManifestEntries(self):
305 mytypes = self.fhashdict.keys()
308 myfiles = self.fhashdict[t].keys()
311 myentry = Manifest2Entry(
312 type=t, name=f, hashes=self.fhashdict[t][f].copy())
313 myhashkeys = myentry.hashes.keys()
315 if h not in ["size"] + portage.const.MANIFEST2_HASH_FUNCTIONS:
316 del myentry.hashes[h]
318 if self.compat and t != "DIST":
319 mysize = self.fhashdict[t][f]["size"]
320 myhashes = self.fhashdict[t][f]
322 if h not in portage.const.MANIFEST1_HASH_FUNCTIONS:
324 yield Manifest1Entry(
325 type=t, name=f, hashes={"size":mysize, h:myhashes[h]})
328 cvp_list = self.fetchlist_dict.keys()
331 digest_path = os.path.join("files", "digest-%s" % self._catsplit(cpv)[1])
332 dname = os.path.join(self.pkgdir, digest_path)
334 myhashes = perform_multiple_checksums(dname, portage.const.MANIFEST1_HASH_FUNCTIONS+["size"])
335 myhashkeys = myhashes.keys()
338 if h in portage.const.MANIFEST1_HASH_FUNCTIONS:
339 yield Manifest1Entry(type="AUX", name=digest_path,
340 hashes={"size":myhashes["size"], h:myhashes[h]})
344 def checkIntegrity(self):
345 for t in self.fhashdict:
346 for f in self.fhashdict[t]:
347 if portage.const.MANIFEST2_REQUIRED_HASH not in self.fhashdict[t][f]:
348 raise MissingParameter("Missing %s checksum: %s %s" % (portage.const.MANIFEST2_REQUIRED_HASH, t, f))
350 def write(self, sign=False, force=False):
351 """ Write Manifest instance to disk, optionally signing it """
352 self.checkIntegrity()
356 myentries = list(self._createManifestEntries())
357 update_manifest = True
360 f = open(self.getFullname(), "r")
361 oldentries = list(self._parseManifestLines(f))
363 if len(oldentries) == len(myentries):
364 update_manifest = False
365 for i in xrange(len(oldentries)):
366 if oldentries[i] != myentries[i]:
367 update_manifest = True
369 except (IOError, OSError), e:
370 if e.errno == errno.ENOENT:
375 fd = open(self.getFullname(), "w")
376 for myentry in myentries:
377 fd.write("%s\n" % str(myentry))
381 except (IOError, OSError), e:
382 if e.errno == errno.EACCES:
383 raise PermissionDenied(str(e))
387 """ Sign the Manifest """
388 raise NotImplementedError()
390 def validateSignature(self):
391 """ Validate signature on Manifest """
392 raise NotImplementedError()
394 def addFile(self, ftype, fname, hashdict=None, ignoreMissing=False):
395 """ Add entry to Manifest optionally using hashdict to avoid recalculation of hashes """
396 if ftype == "AUX" and not fname.startswith("files/"):
397 fname = os.path.join("files", fname)
398 if not os.path.exists(self.pkgdir+fname) and not ignoreMissing:
399 raise FileNotFound(fname)
400 if not ftype in portage.const.MANIFEST2_IDENTIFIERS:
401 raise InvalidDataType(ftype)
402 if ftype == "AUX" and fname.startswith("files"):
404 self.fhashdict[ftype][fname] = {}
406 self.fhashdict[ftype][fname].update(hashdict)
407 if not portage.const.MANIFEST2_REQUIRED_HASH in self.fhashdict[ftype][fname]:
408 self.updateFileHashes(ftype, fname, checkExisting=False, ignoreMissing=ignoreMissing)
410 def removeFile(self, ftype, fname):
411 """ Remove given entry from Manifest """
412 del self.fhashdict[ftype][fname]
414 def hasFile(self, ftype, fname):
415 """ Return wether the Manifest contains an entry for the given type,filename pair """
416 return (fname in self.fhashdict[ftype])
418 def findFile(self, fname):
419 """ Return entrytype of the given file if present in Manifest or None if not present """
420 for t in portage.const.MANIFEST2_IDENTIFIERS:
421 if fname in self.fhashdict[t]:
425 def create(self, checkExisting=False, assumeDistHashesSometimes=False,
426 assumeDistHashesAlways=False, requiredDistfiles=[]):
427 """ Recreate this Manifest from scratch. This will not use any
428 existing checksums unless assumeDistHashesSometimes or
429 assumeDistHashesAlways is true (assumeDistHashesSometimes will only
430 cause DIST checksums to be reused if the file doesn't exist in
431 DISTDIR). The requiredDistfiles parameter specifies a list of
432 distfiles to raise a FileNotFound exception for (if no file or existing
433 checksums are available), and defaults to all distfiles when not
436 self.checkAllHashes()
437 if assumeDistHashesSometimes or assumeDistHashesAlways:
438 distfilehashes = self.fhashdict["DIST"]
441 self.__init__(self.pkgdir, self.distdir,
442 fetchlist_dict=self.fetchlist_dict, from_scratch=True,
443 manifest1_compat=self.compat)
444 for pkgdir, pkgdir_dirs, pkgdir_files in os.walk(self.pkgdir):
446 for f in pkgdir_files:
447 if f.endswith(".ebuild"):
449 elif manifest2MiscfileFilter(f):
453 self.fhashdict[mytype][f] = perform_multiple_checksums(self.pkgdir+f, self.hashes)
455 cut_len = len(os.path.join(self.pkgdir, "files") + os.sep)
456 for parentdir, dirs, files in os.walk(os.path.join(self.pkgdir, "files")):
458 full_path = os.path.join(parentdir, f)
459 recursive_files.append(full_path[cut_len:])
460 for f in recursive_files:
461 if not manifest2AuxfileFilter(f):
463 self.fhashdict["AUX"][f] = perform_multiple_checksums(
464 os.path.join(self.pkgdir, "files", f.lstrip(os.sep)), self.hashes)
465 cpvlist = [os.path.join(self._pkgdir_category(), x[:-7]) for x in os.listdir(self.pkgdir) if x.endswith(".ebuild")]
468 distlist.update(self._getCpvDistfiles(cpv))
469 if requiredDistfiles is None:
470 # This allows us to force removal of stale digests for the
471 # ebuild --force digest option (no distfiles are required).
472 requiredDistfiles = set()
473 elif len(requiredDistfiles) == 0:
474 # repoman passes in an empty list, which implies that all distfiles
476 requiredDistfiles = distlist.copy()
477 required_hash_types = set()
478 required_hash_types.add("size")
479 required_hash_types.add(portage.const.MANIFEST2_REQUIRED_HASH)
481 fname = os.path.join(self.distdir, f)
484 mystat = os.stat(fname)
487 if f in distfilehashes and \
488 not required_hash_types.difference(distfilehashes[f]) and \
489 ((assumeDistHashesSometimes and mystat is None) or \
490 (assumeDistHashesAlways and mystat is None) or \
491 (assumeDistHashesAlways and mystat is not None and \
492 len(distfilehashes[f]) == len(self.hashes) and \
493 distfilehashes[f]["size"] == mystat.st_size)):
494 self.fhashdict["DIST"][f] = distfilehashes[f]
497 self.fhashdict["DIST"][f] = perform_multiple_checksums(fname, self.hashes)
499 if f in requiredDistfiles:
502 def _pkgdir_category(self):
503 return self.pkgdir.rstrip(os.sep).split(os.sep)[-2]
505 def _getAbsname(self, ftype, fname):
507 absname = os.path.join(self.distdir, fname)
509 absname = os.path.join(self.pkgdir, "files", fname)
511 absname = os.path.join(self.pkgdir, fname)
514 def checkAllHashes(self, ignoreMissingFiles=False):
515 for t in portage.const.MANIFEST2_IDENTIFIERS:
516 self.checkTypeHashes(t, ignoreMissingFiles=ignoreMissingFiles)
518 def checkTypeHashes(self, idtype, ignoreMissingFiles=False):
519 for f in self.fhashdict[idtype]:
520 self.checkFileHashes(idtype, f, ignoreMissing=ignoreMissingFiles)
522 def checkFileHashes(self, ftype, fname, ignoreMissing=False):
523 myhashes = self.fhashdict[ftype][fname]
525 ok,reason = verify_all(self._getAbsname(ftype, fname), self.fhashdict[ftype][fname])
527 raise DigestException(tuple([self._getAbsname(ftype, fname)]+list(reason)))
529 except FileNotFound, e:
530 if not ignoreMissing:
532 return False, "File Not Found: '%s'" % str(e)
534 def checkCpvHashes(self, cpv, checkDistfiles=True, onlyDistfiles=False, checkMiscfiles=False):
535 """ check the hashes for all files associated to the given cpv, include all
536 AUX files and optionally all MISC files. """
537 if not onlyDistfiles:
538 self.checkTypeHashes("AUX", ignoreMissingFiles=False)
540 self.checkTypeHashes("MISC", ignoreMissingFiles=False)
541 ebuildname = "%s.ebuild" % self._catsplit(cpv)[1]
542 self.checkFileHashes("EBUILD", ebuildname, ignoreMissing=False)
543 if checkDistfiles or onlyDistfiles:
544 for f in self._getCpvDistfiles(cpv):
545 self.checkFileHashes("DIST", f, ignoreMissing=False)
547 def _getCpvDistfiles(self, cpv):
548 """ Get a list of all DIST files associated to the given cpv """
549 return self.fetchlist_dict[cpv]
551 def getDistfilesSize(self, fetchlist):
554 total_bytes += int(self.fhashdict["DIST"][f]["size"])
557 def updateFileHashes(self, ftype, fname, checkExisting=True, ignoreMissing=True, reuseExisting=False):
558 """ Regenerate hashes for the given file """
560 self.checkFileHashes(ftype, fname, ignoreMissing=ignoreMissing)
561 if not ignoreMissing and not self.fhashdict[ftype].has_key(fname):
562 raise FileNotInManifestException(fname)
563 if not self.fhashdict[ftype].has_key(fname):
564 self.fhashdict[ftype][fname] = {}
565 myhashkeys = list(self.hashes)
567 for k in [h for h in self.fhashdict[ftype][fname] if h in myhashkeys]:
569 myhashes = perform_multiple_checksums(self._getAbsname(ftype, fname), myhashkeys)
570 self.fhashdict[ftype][fname].update(myhashes)
572 def updateTypeHashes(self, idtype, checkExisting=False, ignoreMissingFiles=True):
573 """ Regenerate all hashes for all files of the given type """
574 for fname in self.fhashdict[idtype]:
575 self.updateFileHashes(idtype, fname, checkExisting)
577 def updateAllHashes(self, checkExisting=False, ignoreMissingFiles=True):
578 """ Regenerate all hashes for all files in this Manifest. """
579 for ftype in portage.const.MANIFEST2_IDENTIFIERS:
580 self.updateTypeHashes(idtype, fname, checkExisting)
582 def updateCpvHashes(self, cpv, ignoreMissingFiles=True):
583 """ Regenerate all hashes associated to the given cpv (includes all AUX and MISC
585 self.updateTypeHashes("AUX", ignoreMissingFiles=ignoreMissingFiles)
586 self.updateTypeHashes("MISC", ignoreMissingFiles=ignoreMissingFiles)
587 ebuildname = "%s.ebuild" % self._catsplit(cpv)[1]
588 self.updateFileHashes("EBUILD", ebuildname, ignoreMissingFiles=ignoreMissingFiles)
589 for f in self._getCpvDistfiles(cpv):
590 self.updateFileHashes("DIST", f, ignoreMissingFiles=ignoreMissingFiles)
592 def updateHashesGuessType(self, fname, *args, **kwargs):
593 """ Regenerate hashes for the given file (guesses the type and then
594 calls updateFileHashes)."""
595 mytype = self.guessType(fname)
597 fname = fname[len("files" + os.sep):]
600 myrealtype = self.findFile(fname)
601 if myrealtype is not None:
603 return self.updateFileHashes(mytype, fname, *args, **kwargs)
605 def getFileData(self, ftype, fname, key):
606 """ Return the value of a specific (type,filename,key) triple, mainly useful
607 to get the size for distfiles."""
608 return self.fhashdict[ftype][fname][key]
610 def getVersions(self):
611 """ Returns a list of manifest versions present in the manifest file. """
613 mfname = self.getFullname()
614 if not os.path.exists(mfname):
616 myfile = open(mfname, "r")
617 lines = myfile.readlines()
621 if len(mysplit) == 4 and mysplit[0] in portage.const.MANIFEST1_HASH_FUNCTIONS and not 1 in rVal:
623 elif len(mysplit) > 4 and mysplit[0] in portage.const.MANIFEST2_IDENTIFIERS and ((len(mysplit) - 3) % 2) == 0 and not 2 in rVal:
627 def _catsplit(self, pkg_key):
628 """Split a category and package, returning a list of [cat, pkg].
629 This is compatible with portage.catsplit()"""
630 return pkg_key.split("/", 1)