1 # Copyright 1999-2006 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
6 if not hasattr(__builtins__, "set"):
7 from sets import Set as set
9 import portage_exception, portage_versions, portage_const
10 from portage_checksum import *
11 from portage_exception import *
12 from portage_util import write_atomic
14 class FileNotInManifestException(PortageException):
17 def manifest2AuxfileFilter(filename):
18 filename = filename.strip(os.sep)
19 mysplit = filename.split(os.path.sep)
25 return not filename.startswith("digest-")
27 def manifest2MiscfileFilter(filename):
28 filename = filename.strip(os.sep)
29 return not (filename in ["CVS", ".svn", "files", "Manifest"] or filename.endswith(".ebuild"))
31 def guessManifestFileType(filename):
32 """ Perform a best effort guess of which type the given filename is, avoid using this if possible """
33 if filename.startswith("files" + os.sep + "digest-"):
35 if filename.startswith("files" + os.sep):
37 elif filename.endswith(".ebuild"):
39 elif filename in ["ChangeLog", "metadata.xml"]:
44 def parseManifest2(mysplit):
46 if len(mysplit) > 4 and mysplit[0] in portage_const.MANIFEST2_IDENTIFIERS:
49 mysize = int(mysplit[2])
50 myhashes = dict(zip(mysplit[3::2], mysplit[4::2]))
51 myhashes["size"] = mysize
52 myentry = Manifest2Entry(type=mytype, name=myname, hashes=myhashes)
55 def parseManifest1(mysplit):
57 if len(mysplit) == 4 and mysplit[0] in ["size"] + portage_const.MANIFEST1_HASH_FUNCTIONS:
60 mytype = guessManifestFileType(myname)
62 if myname.startswith("files" + os.path.sep):
64 mysize = int(mysplit[3])
65 myhashes = {mysplit[0]: mysplit[1]}
66 myhashes["size"] = mysize
67 myentry = Manifest1Entry(type=mytype, name=myname, hashes=myhashes)
70 class ManifestEntry(object):
71 __slots__ = ("type", "name", "hashes")
72 def __init__(self, **kwargs):
73 for k, v in kwargs.iteritems():
75 def __cmp__(self, other):
76 if str(self) == str(other):
80 class Manifest1Entry(ManifestEntry):
82 myhashkeys = self.hashes.keys()
83 for hashkey in myhashkeys:
86 hashvalue = self.hashes[hashkey]
88 if self.type == "AUX" and not myname.startswith("files" + os.sep):
89 myname = os.path.join("files", myname)
90 return " ".join([hashkey, str(hashvalue), myname, str(self.hashes["size"])])
92 class Manifest2Entry(ManifestEntry):
94 myline = " ".join([self.type, self.name, str(self.hashes["size"])])
95 myhashkeys = self.hashes.keys()
96 myhashkeys.remove("size")
99 myline += " " + h + " " + str(self.hashes[h])
102 class Manifest(object):
103 parsers = (parseManifest2, parseManifest1)
104 def __init__(self, pkgdir, distdir, fetchlist_dict=None,
105 manifest1_compat=True, from_scratch=False):
106 """ create new Manifest instance for package in pkgdir
107 and add compability entries for old portage versions if manifest1_compat == True.
108 Do not parse Manifest file if from_scratch == True (only for internal use)
109 The fetchlist_dict parameter is required only for generation of
110 a Manifest (not needed for parsing and checking sums)."""
111 self.pkgdir = pkgdir.rstrip(os.sep) + os.sep
113 self.hashes = portage_const.MANIFEST2_HASH_FUNCTIONS[:]
114 self.hashes.append("size")
116 self.hashes.extend(portage_const.MANIFEST1_HASH_FUNCTIONS)
117 self.hashes = sets.Set(self.hashes)
118 for t in portage_const.MANIFEST2_IDENTIFIERS:
119 self.fhashdict[t] = {}
122 self.compat = manifest1_compat
123 self.fetchlist_dict = 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)
181 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 if len(old_data) == 1 and "DIST" in old_data:
239 new_data = self._getDigestData(distlist)
240 if "DIST" in new_data:
241 for myfile in new_data["DIST"]:
243 new_data["DIST"][myfile].keys():
244 if hashname != "size" and hashname not in \
245 portage_const.MANIFEST1_HASH_FUNCTIONS:
246 del new_data["DIST"][myfile][hashname]
247 if new_data["DIST"] == old_data["DIST"]:
248 update_digest = False
249 except (IOError, OSError), e:
250 if errno.ENOENT == e.errno:
255 mylines = self._createDigestLines1(distlist, self.fhashdict)
257 mylines = "\n".join(mylines) + "\n"
260 write_atomic(dname, mylines)
264 def _getDigestData(self, distlist):
265 """create a hash dict for a specific list of files"""
267 for myname in distlist:
268 for mytype in self.fhashdict:
269 if myname in self.fhashdict[mytype]:
270 myhashdict.setdefault(mytype, {})
271 myhashdict[mytype].setdefault(myname, {})
272 myhashdict[mytype][myname].update(self.fhashdict[mytype][myname])
275 def _createDigestLines1(self, distlist, myhashdict):
276 """ Create an old style digest file."""
278 myfiles = myhashdict["DIST"].keys()
282 myhashkeys = myhashdict["DIST"][f].keys()
285 if h not in portage_const.MANIFEST1_HASH_FUNCTIONS:
287 myline = " ".join([h, str(myhashdict["DIST"][f][h]), f, str(myhashdict["DIST"][f]["size"])])
288 mylines.append(myline)
291 def _addDigestsToManifest(self, digests, fd):
292 """ Add entries for old style digest files to Manifest file """
294 for dname in digests:
295 myhashes = perform_multiple_checksums(dname, portage_const.MANIFEST1_HASH_FUNCTIONS+["size"])
297 mylines.append((" ".join([h, str(myhashes[h]), os.path.join("files", os.path.basename(dname)), str(myhashes["size"])])))
298 fd.write("\n".join(mylines))
301 def _createManifestEntries(self):
302 mytypes = self.fhashdict.keys()
305 myfiles = self.fhashdict[t].keys()
308 myentry = Manifest2Entry(
309 type=t, name=f, hashes=self.fhashdict[t][f].copy())
310 myhashkeys = myentry.hashes.keys()
312 if h not in ["size"] + portage_const.MANIFEST2_HASH_FUNCTIONS:
313 del myentry.hashes[h]
315 if self.compat and t != "DIST":
316 mysize = self.fhashdict[t][f]["size"]
317 myhashes = self.fhashdict[t][f]
319 if h not in portage_const.MANIFEST1_HASH_FUNCTIONS:
321 yield Manifest1Entry(
322 type=t, name=f, hashes={"size":mysize, h:myhashes[h]})
325 cvp_list = self.fetchlist_dict.keys()
328 digest_path = os.path.join("files", "digest-%s" % self._catsplit(cpv)[1])
329 dname = os.path.join(self.pkgdir, digest_path)
331 myhashes = perform_multiple_checksums(dname, portage_const.MANIFEST1_HASH_FUNCTIONS+["size"])
332 myhashkeys = myhashes.keys()
335 if h in portage_const.MANIFEST1_HASH_FUNCTIONS:
336 yield Manifest1Entry(type="AUX", name=digest_path,
337 hashes={"size":myhashes["size"], h:myhashes[h]})
341 def write(self, sign=False, force=False):
342 """ Write Manifest instance to disk, optionally signing it """
346 myentries = list(self._createManifestEntries())
347 update_manifest = True
350 f = open(self.getFullname(), "r")
351 oldentries = list(self._parseManifestLines(f))
353 if len(oldentries) == len(myentries):
354 update_manifest = False
355 for i in xrange(len(oldentries)):
356 if oldentries[i] != myentries[i]:
357 update_manifest = True
359 except (IOError, OSError), e:
360 if e.errno == errno.ENOENT:
365 fd = open(self.getFullname(), "w")
366 for myentry in myentries:
367 fd.write("%s\n" % str(myentry))
371 except (IOError, OSError), e:
372 if e.errno == errno.EACCES:
373 raise PermissionDenied(str(e))
377 """ Sign the Manifest """
378 raise NotImplementedError()
380 def validateSignature(self):
381 """ Validate signature on Manifest """
382 raise NotImplementedError()
384 def addFile(self, ftype, fname, hashdict=None):
385 """ Add entry to Manifest optionally using hashdict to avoid recalculation of hashes """
386 if not os.path.exists(self.pkgdir+fname):
387 raise FileNotFound(fname)
388 if not ftype in portage_const.MANIFEST2_IDENTIFIERS:
389 raise InvalidDataType(ftype)
390 self.fhashdict[ftype][fname] = {}
392 self.fhashdict[ftype][fname].update(hashdict)
393 if not portage_const.MANIFEST2_REQUIRED_HASH in self.fhashdict[ftype][fname]:
394 self.updateFileHashes(ftype, fname)
396 def removeFile(self, ftype, fname):
397 """ Remove given entry from Manifest """
398 del self.fhashdict[ftype][fname]
400 def hasFile(self, ftype, fname):
401 """ Return wether the Manifest contains an entry for the given type,filename pair """
402 return (fname in self.fhashdict[ftype])
404 def findFile(self, fname):
405 """ Return entrytype of the given file if present in Manifest or None if not present """
406 for t in portage_const.MANIFEST2_IDENTIFIERS:
407 if fname in self.fhashdict[t]:
411 def create(self, checkExisting=False, assumeDistHashesSometimes=False,
412 assumeDistHashesAlways=False, requiredDistfiles=[]):
413 """ Recreate this Manifest from scratch. This will not use any
414 existing checksums unless assumeDistHashesSometimes or
415 assumeDistHashesAlways is true (assumeDistHashesSometimes will only
416 cause DIST checksums to be reused if the file doesn't exist in
417 DISTDIR). The requiredDistfiles parameter specifies a list of
418 distfiles to raise a FileNotFound exception for (if no file or existing
419 checksums are available), and defaults to all distfiles when not
422 self.checkAllHashes()
423 if assumeDistHashesSometimes or assumeDistHashesAlways:
424 distfilehashes = self.fhashdict["DIST"]
427 self.__init__(self.pkgdir, self.distdir,
428 fetchlist_dict=self.fetchlist_dict, from_scratch=True)
429 for pkgdir, pkgdir_dirs, pkgdir_files in os.walk(self.pkgdir):
431 for f in pkgdir_files:
432 if f.endswith(".ebuild"):
434 elif manifest2MiscfileFilter(f):
438 self.fhashdict[mytype][f] = perform_multiple_checksums(self.pkgdir+f, self.hashes)
440 cut_len = len(os.path.join(self.pkgdir, "files") + os.sep)
441 for parentdir, dirs, files in os.walk(os.path.join(self.pkgdir, "files")):
443 full_path = os.path.join(parentdir, f)
444 recursive_files.append(full_path[cut_len:])
445 for f in recursive_files:
446 if not manifest2AuxfileFilter(f):
448 self.fhashdict["AUX"][f] = perform_multiple_checksums(
449 os.path.join(self.pkgdir, "files", f.lstrip(os.sep)), self.hashes)
450 cpvlist = [os.path.join(self._pkgdir_category(), x[:-7]) for x in os.listdir(self.pkgdir) if x.endswith(".ebuild")]
453 distlist.update(self._getCpvDistfiles(cpv))
454 if requiredDistfiles is None:
455 # This allows us to force removal of stale digests for the
456 # ebuild --force digest option (no distfiles are required).
457 requiredDistfiles = set()
458 elif len(requiredDistfiles) == 0:
459 # repoman passes in an empty list, which implies that all distfiles
461 requiredDistfiles = distlist.copy()
463 fname = os.path.join(self.distdir, f)
466 mystat = os.stat(fname)
469 if f in distfilehashes and \
470 ((assumeDistHashesSometimes and mystat is None) or \
471 (assumeDistHashesAlways and mystat is None) or \
472 (assumeDistHashesAlways and mystat is not None and \
473 len(distfilehashes[f]) == len(self.hashes) and \
474 distfilehashes[f]["size"] == mystat.st_size)):
475 self.fhashdict["DIST"][f] = distfilehashes[f]
478 self.fhashdict["DIST"][f] = perform_multiple_checksums(fname, self.hashes)
480 if f in requiredDistfiles:
483 def _pkgdir_category(self):
484 return self.pkgdir.rstrip(os.sep).split(os.sep)[-2]
486 def _getAbsname(self, ftype, fname):
488 absname = os.path.join(self.distdir, fname)
490 absname = os.path.join(self.pkgdir, "files", fname)
492 absname = os.path.join(self.pkgdir, fname)
495 def checkAllHashes(self, ignoreMissingFiles=False):
496 for t in portage_const.MANIFEST2_IDENTIFIERS:
497 self.checkTypeHashes(t, ignoreMissingFiles=ignoreMissingFiles)
499 def checkTypeHashes(self, idtype, ignoreMissingFiles=False):
500 for f in self.fhashdict[idtype]:
501 self.checkFileHashes(idtype, f, ignoreMissing=ignoreMissingFiles)
503 def checkFileHashes(self, ftype, fname, ignoreMissing=False):
504 myhashes = self.fhashdict[ftype][fname]
506 ok,reason = verify_all(self._getAbsname(ftype, fname), self.fhashdict[ftype][fname])
508 raise DigestException(tuple([self._getAbsname(ftype, fname)]+list(reason)))
510 except FileNotFound, e:
511 if not ignoreMissing:
513 return False, "File Not Found: '%s'" % str(e)
515 def checkCpvHashes(self, cpv, checkDistfiles=True, onlyDistfiles=False, checkMiscfiles=False):
516 """ check the hashes for all files associated to the given cpv, include all
517 AUX files and optionally all MISC files. """
518 if not onlyDistfiles:
519 self.checkTypeHashes("AUX", ignoreMissingFiles=False)
521 self.checkTypeHashes("MISC", ignoreMissingFiles=False)
522 ebuildname = "%s.ebuild" % self._catsplit(cpv)[1]
523 self.checkFileHashes("EBUILD", ebuildname, ignoreMissing=False)
526 for f in self._getCpvDistfiles(cpv):
527 self.checkFileHashes("DIST", f, ignoreMissing=False)
529 def _getCpvDistfiles(self, cpv):
530 """ Get a list of all DIST files associated to the given cpv """
531 return self.fetchlist_dict[cpv]
533 def getDistfilesSize(self, fetchlist):
536 total_bytes += int(self.fhashdict["DIST"][f]["size"])
539 def updateFileHashes(self, ftype, fname, checkExisting=True, ignoreMissing=True, reuseExisting=False):
540 """ Regenerate hashes for the given file """
542 self.checkFileHashes(ftype, fname, ignoreMissing=ignoreMissing)
543 if not ignoreMissing and not self.fhashdict[ftype].has_key(fname):
544 raise FileNotInManifestException(fname)
545 if not self.fhashdict[ftype].has_key(fname):
546 self.fhashdict[ftype][fname] = {}
547 myhashkeys = list(self.hashes)
549 for k in [h for h in self.fhashdict[ftype][fname] if h in myhashkeys]:
551 myhashes = perform_multiple_checksums(self._getAbsname(ftype, fname), myhashkeys)
552 self.fhashdict[ftype][fname].update(myhashes)
554 def updateTypeHashes(self, idtype, checkExisting=False, ignoreMissingFiles=True):
555 """ Regenerate all hashes for all files of the given type """
556 for fname in self.fhashdict[idtype]:
557 self.updateFileHashes(idtype, fname, checkExisting)
559 def updateAllHashes(self, checkExisting=False, ignoreMissingFiles=True):
560 """ Regenerate all hashes for all files in this Manifest. """
561 for ftype in portage_const.MANIFEST2_IDENTIFIERS:
562 self.updateTypeHashes(idtype, fname, checkExisting)
564 def updateCpvHashes(self, cpv, ignoreMissingFiles=True):
565 """ Regenerate all hashes associated to the given cpv (includes all AUX and MISC
567 self.updateTypeHashes("AUX", ignoreMissingFiles=ignoreMissingFiles)
568 self.updateTypeHashes("MISC", ignoreMissingFiles=ignoreMissingFiles)
569 ebuildname = "%s.ebuild" % self._catsplit(cpv)[1]
570 self.updateFileHashes("EBUILD", ebuildname, ignoreMissingFiles=ignoreMissingFiles)
571 for f in self._getCpvDistfiles(cpv):
572 self.updateFileHashes("DIST", f, ignoreMissingFiles=ignoreMissingFiles)
574 def updateHashesGuessType(self, fname, *args, **kwargs):
575 """ Regenerate hashes for the given file (guesses the type and then
576 calls updateFileHashes)."""
577 mytype = self.guessType(fname)
579 fname = fname[len("files" + os.sep):]
582 myrealtype = self.findFile(fname)
583 if myrealtype is not None:
585 return self.updateFileHashes(mytype, fname, *args, **kwargs)
587 def getFileData(self, ftype, fname, key):
588 """ Return the value of a specific (type,filename,key) triple, mainly useful
589 to get the size for distfiles."""
590 return self.fhashdict[ftype][fname][key]
592 def getVersions(self):
593 """ Returns a list of manifest versions present in the manifest file. """
595 mfname = self.getFullname()
596 if not os.path.exists(mfname):
598 myfile = open(mfname, "r")
599 lines = myfile.readlines()
603 if len(mysplit) == 4 and mysplit[0] in portage_const.MANIFEST1_HASH_FUNCTIONS and not 1 in rVal:
605 elif len(mysplit) > 4 and mysplit[0] in portage_const.MANIFEST2_IDENTIFIERS and ((len(mysplit) - 3) % 2) == 0 and not 2 in rVal:
609 def _catsplit(self, pkg_key):
610 """Split a category and package, returning a list of [cat, pkg].
611 This is compatible with portage.catsplit()"""
612 return pkg_key.split("/", 1)