# package.py # Module defining the dnf.Package class. # # Copyright (C) 2012-2016 Red Hat, Inc. # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # the GNU General Public License v.2, or (at your option) any later version. # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY expressed or implied, including the implied warranties of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General # Public License for more details. You should have received a copy of the # GNU General Public License along with this program; if not, write to the # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. Any Red Hat trademarks that are incorporated in the # source code or documentation are not subject to the GNU General Public # License and may only be used or replicated with the express permission of # Red Hat, Inc. # """ Contains the dnf.Package class. """ from __future__ import absolute_import from __future__ import unicode_literals from dnf.i18n import _ import binascii import dnf.exceptions import dnf.rpm import dnf.yum.misc import hawkey import libdnf.error import libdnf.utils import logging import os import rpm logger = logging.getLogger("dnf") class Package(hawkey.Package): """ Represents a package. #:api """ DEBUGINFO_SUFFIX = "-debuginfo" # :api DEBUGSOURCE_SUFFIX = "-debugsource" # :api def __init__(self, initobject, base): super(Package, self).__init__(initobject) self.base = base self._priv_chksum = None self._repo = None self._priv_size = None @property def _chksum(self): if self._priv_chksum: return self._priv_chksum if self._from_cmdline: chksum_type = dnf.yum.misc.get_default_chksum_type() try: chksum_val = libdnf.utils.checksum_value(chksum_type, self.location) except libdnf.error.Error as e: raise dnf.exceptions.MiscError(str(e)) return (hawkey.chksum_type(chksum_type), binascii.unhexlify(chksum_val)) return super(Package, self).chksum @_chksum.setter def _chksum(self, val): self._priv_chksum = val @property def _from_cmdline(self): return self.reponame == hawkey.CMDLINE_REPO_NAME @property def _from_system(self): return self.reponame == hawkey.SYSTEM_REPO_NAME @property def _from_repo(self): """ For installed packages returns id of repository from which the package was installed prefixed with '@' (if such information is available in the history database). Otherwise returns id of repository the package belongs to (@System for installed packages of unknown origin) """ pkgrepo = None if self._from_system: pkgrepo = self.base.history.repo(self) if pkgrepo: return '@' + pkgrepo return self.reponame @property def from_repo(self): # :api if self._from_system: return self.base.history.repo(self) return "" @property def _header(self): """ Returns the header of a locally present rpm package file. As opposed to self.get_header(), which retrieves the header of an installed package from rpmdb. """ return dnf.rpm._header(self.localPkg()) @property def _size(self): if self._priv_size: return self._priv_size return super(Package, self).size @_size.setter def _size(self, val): self._priv_size = val @property def _pkgid(self): if self.hdr_chksum is None: return None (_, chksum) = self.hdr_chksum return binascii.hexlify(chksum) @property def source_name(self): # :api """ returns name of source package e.g. krb5-libs -> krb5 """ if self.sourcerpm is not None: # trim suffix first srcname = dnf.util.rtrim(self.sourcerpm, ".src.rpm") # sourcerpm should be in form of name-version-release now, so we # will strip the two rightmost parts separated by dash. # Using rtrim with version and release of self is not sufficient # because the package can have different version to the source # package. srcname = srcname.rsplit('-', 2)[0] else: srcname = None return srcname @property def debug_name(self): # :api """ Returns name of the debuginfo package for this package. If this package is a debuginfo package, returns its name. If this package is a debugsource package, returns the debuginfo package for the base package. e.g. kernel-PAE -> kernel-PAE-debuginfo """ if self.name.endswith(self.DEBUGINFO_SUFFIX): return self.name name = self.name if self.name.endswith(self.DEBUGSOURCE_SUFFIX): name = name[:-len(self.DEBUGSOURCE_SUFFIX)] return name + self.DEBUGINFO_SUFFIX @property def debugsource_name(self): # :api """ Returns name of the debugsource package for this package. e.g. krb5-libs -> krb5-debugsource """ # assuming self.source_name is None only for a source package src_name = self.source_name if self.source_name is not None else self.name return src_name + self.DEBUGSOURCE_SUFFIX def get_header(self): """ Returns the rpm header of the package if it is installed. If not installed, returns None. The header is not cached, it is retrieved from rpmdb on every call. In case of a failure (e.g. when the rpmdb changes between loading the data and calling this method), raises an instance of PackageNotFoundError. """ if not self._from_system: return None try: # RPMDBI_PACKAGES stands for the header of the package return next(self.base._ts.dbMatch(rpm.RPMDBI_PACKAGES, self.rpmdbid)) except StopIteration: raise dnf.exceptions.PackageNotFoundError("Package not found when attempting to retrieve header", str(self)) @property def source_debug_name(self): # :api """ returns name of debuginfo package for source package of given package e.g. krb5-libs -> krb5-debuginfo """ # assuming self.source_name is None only for a source package src_name = self.source_name if self.source_name is not None else self.name return src_name + self.DEBUGINFO_SUFFIX @property # yum compatibility attribute def idx(self): """ Always type it to int, rpm bindings expect it like that. """ return int(self.rpmdbid) @property # yum compatibility attribute def repoid(self): return self.reponame @property # yum compatibility attribute def pkgtup(self): return (self.name, self.arch, str(self.e), self.v, self.r) @property # yum compatibility attribute def repo(self): if self._repo: return self._repo return self.base.repos[self.reponame] @repo.setter def repo(self, val): self._repo = val @property def reason(self): if self.repoid != hawkey.SYSTEM_REPO_NAME: return None return self.base.history.rpm.get_reason_name(self) @property # yum compatibility attribute def relativepath(self): return self.location @property # yum compatibility attribute def a(self): return self.arch @property # yum compatibility attribute def e(self): return self.epoch @property # yum compatibility attribute def v(self): return self.version @property # yum compatibility attribute def r(self): return self.release @property # yum compatibility attribute def ui_from_repo(self): return self.reponame # yum compatibility method def evr_eq(self, pkg): return self.evr_cmp(pkg) == 0 # yum compatibility method def evr_gt(self, pkg): return self.evr_cmp(pkg) > 0 # yum compatibility method def evr_lt(self, pkg): return self.evr_cmp(pkg) < 0 # yum compatibility method def getDiscNum(self): return self.medianr # yum compatibility method def localPkg(self): """ Package's location in the filesystem. For packages in remote repo returns where the package will be/has been downloaded. """ if self._from_cmdline: return self.location loc = self.location if self.repo._repo.isLocal() and self.baseurl and self.baseurl.startswith('file://'): return os.path.join(self.get_local_baseurl(), loc.lstrip("/")) if not self._is_local_pkg(): loc = os.path.basename(loc) return os.path.join(self.pkgdir, loc.lstrip("/")) def remote_location(self, schemes=('http', 'ftp', 'file', 'https')): # :api """ The location from where the package can be downloaded from. Returns None for installed and commandline packages. :param schemes: list of allowed protocols. Default is ('http', 'ftp', 'file', 'https') :return: location (string) or None """ if self._from_system or self._from_cmdline: return None return self.repo.remote_location(self.location, schemes) def _is_local_pkg(self): if self._from_system: return True if '://' in self.location and not self.location.startswith('file://'): # the package has a remote URL as its location return False return self._from_cmdline or \ (self.repo._repo.isLocal() and (not self.baseurl or self.baseurl.startswith('file://'))) @property def pkgdir(self): if (self.repo._repo.isLocal() and not self._is_local_pkg()): return self.repo.cache_pkgdir() else: return self.repo.pkgdir # yum compatibility method def returnIdSum(self): """ Return the chksum type and chksum string how the legacy yum expects it. """ if self._chksum is None: return (None, None) (chksum_type, chksum) = self._chksum return (hawkey.chksum_name(chksum_type), binascii.hexlify(chksum).decode()) # yum compatibility method def verifyLocalPkg(self): if self._from_system: raise ValueError("Can not verify an installed package.") if self._from_cmdline: return True # local package always verifies against itself (chksum_type, chksum) = self.returnIdSum() try: return libdnf.utils.checksum_check(chksum_type, self.localPkg(), chksum) except libdnf.error.Error as e: raise dnf.exceptions.MiscError(str(e))