Blame dnf/yum/rpmtrans.py

Packit 6f3914
# This program is free software; you can redistribute it and/or modify
Packit 6f3914
# it under the terms of the GNU General Public License as published by
Packit 6f3914
# the Free Software Foundation; either version 2 of the License, or
Packit 6f3914
# (at your option) any later version.
Packit 6f3914
#
Packit 6f3914
# This program is distributed in the hope that it will be useful,
Packit 6f3914
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6f3914
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 6f3914
# GNU Library General Public License for more details.
Packit 6f3914
#
Packit 6f3914
# You should have received a copy of the GNU General Public License
Packit 6f3914
# along with this program; if not, write to the Free Software
Packit 6f3914
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Packit 6f3914
# Copyright 2005 Duke University
Packit 6f3914
# Parts Copyright 2007 Red Hat, Inc
Packit 6f3914
Packit 6f3914
from __future__ import print_function, absolute_import
Packit 6f3914
from __future__ import unicode_literals
Packit 6f3914
Packit 6f3914
import libdnf.transaction
Packit 6f3914
Packit 6f3914
from dnf.i18n import _, ucd
Packit 6f3914
import dnf.callback
Packit 6f3914
import dnf.transaction
Packit 6f3914
import dnf.util
Packit 6f3914
import rpm
Packit 6f3914
import os
Packit 6f3914
import logging
Packit 6f3914
import sys
Packit 6f3914
import tempfile
Packit 6f3914
import traceback
Packit 6f3914
import warnings
Packit 6f3914
Packit 6f3914
Packit 6f3914
# TODO: merge w/ libdnf
Packit 6f3914
# transaction set states
Packit 6f3914
TS_UPDATE = 10
Packit 6f3914
TS_INSTALL = 20
Packit 6f3914
TS_ERASE = 40
Packit 6f3914
TS_OBSOLETED = 50
Packit 6f3914
TS_OBSOLETING = 60
Packit 6f3914
TS_AVAILABLE = 70
Packit 6f3914
TS_UPDATED = 90
Packit 6f3914
TS_FAILED = 100
Packit 6f3914
Packit 6f3914
TS_INSTALL_STATES = [TS_INSTALL, TS_UPDATE, TS_OBSOLETING]
Packit 6f3914
TS_REMOVE_STATES = [TS_ERASE, TS_OBSOLETED, TS_UPDATED]
Packit 6f3914
Packit 6f3914
logger = logging.getLogger('dnf')
Packit 6f3914
Packit 6f3914
Packit 6f3914
def _add_deprecated_action(name):
Packit 6f3914
    """
Packit 6f3914
    Wrapper to return a deprecated action constant
Packit 6f3914
    while printing a deprecation warning.
Packit 6f3914
    """
Packit 6f3914
    @property
Packit 6f3914
    def _func(self):
Packit 6f3914
        msg = "%s.%s is deprecated. Use dnf.callback.%s instead." \
Packit 6f3914
            % (self.__class__.__name__, name, name)
Packit 6f3914
        warnings.warn(msg, DeprecationWarning, stacklevel=2)
Packit 6f3914
        value = getattr(dnf.callback, name)
Packit 6f3914
        return value
Packit 6f3914
    return _func
Packit 6f3914
Packit 6f3914
Packit 6f3914
class TransactionDisplay(object):
Packit 6f3914
    # :api
Packit 6f3914
Packit 6f3914
    def __init__(self):
Packit 6f3914
        # :api
Packit 6f3914
        pass
Packit 6f3914
Packit 6f3914
    # use constants from dnf.callback which are the official API
Packit 6f3914
    PKG_CLEANUP = _add_deprecated_action("PKG_CLEANUP")
Packit 6f3914
    PKG_DOWNGRADE = _add_deprecated_action("PKG_DOWNGRADE")
Packit 6f3914
    PKG_REMOVE = _add_deprecated_action("PKG_REMOVE")
Packit 6f3914
    PKG_ERASE = PKG_REMOVE
Packit 6f3914
    PKG_INSTALL = _add_deprecated_action("PKG_INSTALL")
Packit 6f3914
    PKG_OBSOLETE = _add_deprecated_action("PKG_OBSOLETE")
Packit 6f3914
    PKG_REINSTALL = _add_deprecated_action("PKG_REINSTALL")
Packit 6f3914
    PKG_UPGRADE = _add_deprecated_action("PKG_UPGRADE")
Packit 6f3914
    PKG_VERIFY = _add_deprecated_action("PKG_VERIFY")
Packit 6f3914
    TRANS_PREPARATION = _add_deprecated_action("TRANS_PREPARATION")
Packit 6f3914
    PKG_SCRIPTLET = _add_deprecated_action("PKG_SCRIPTLET")
Packit 6f3914
    TRANS_POST = _add_deprecated_action("TRANS_POST")
Packit 6f3914
Packit 6f3914
    def progress(self, package, action, ti_done, ti_total, ts_done, ts_total):
Packit 6f3914
        """Report ongoing progress on a transaction item. :api
Packit 6f3914
Packit 6f3914
        :param package: a package being processed
Packit 6f3914
        :param action: the action being performed
Packit 6f3914
        :param ti_done: number of processed bytes of the transaction
Packit 6f3914
           item being processed
Packit 6f3914
        :param ti_total: total number of bytes of the transaction item
Packit 6f3914
           being processed
Packit 6f3914
        :param ts_done: number of actions processed in the whole
Packit 6f3914
           transaction
Packit 6f3914
        :param ts_total: total number of actions in the whole
Packit 6f3914
           transaction
Packit 6f3914
Packit 6f3914
        """
Packit 6f3914
        pass
Packit 6f3914
Packit 6f3914
    def scriptout(self, msgs):
Packit 6f3914
        """msgs is the messages that were output (if any)."""
Packit 6f3914
        pass
Packit 6f3914
Packit 6f3914
    def error(self, message):
Packit 6f3914
        """Report an error that occurred during the transaction. :api"""
Packit 6f3914
        pass
Packit 6f3914
Packit 6f3914
    def filelog(self, package, action):
Packit 6f3914
        # check package object type - if it is a string - just output it
Packit 6f3914
        """package is the same as in progress() - a package object or simple
Packit 6f3914
           string action is also the same as in progress()"""
Packit 6f3914
        pass
Packit 6f3914
Packit 6f3914
    def verify_tsi_package(self, pkg, count, total):
Packit 6f3914
        # TODO: replace with verify_tsi?
Packit 6f3914
        self.progress(pkg, dnf.transaction.PKG_VERIFY, 100, 100, count, total)
Packit 6f3914
Packit 6f3914
Packit 6f3914
class ErrorTransactionDisplay(TransactionDisplay):
Packit 6f3914
Packit 6f3914
    """An RPMTransaction display that prints errors to standard output."""
Packit 6f3914
Packit 6f3914
    def error(self, message):
Packit 6f3914
        super(ErrorTransactionDisplay, self).error(message)
Packit 6f3914
        dnf.util._terminal_messenger('print', message, sys.stderr)
Packit 6f3914
Packit 6f3914
Packit 6f3914
class LoggingTransactionDisplay(ErrorTransactionDisplay):
Packit 6f3914
    '''
Packit 6f3914
    Base class for a RPMTransaction display callback class
Packit 6f3914
    '''
Packit 6f3914
    def __init__(self):
Packit 6f3914
        super(LoggingTransactionDisplay, self).__init__()
Packit 6f3914
        self.rpm_logger = logging.getLogger('dnf.rpm')
Packit 6f3914
Packit 6f3914
    def error(self, message):
Packit 6f3914
        self.rpm_logger.error(message)
Packit 6f3914
Packit 6f3914
    def filelog(self, package, action):
Packit 6f3914
        action_str = dnf.transaction.FILE_ACTIONS[action]
Packit 6f3914
        msg = '%s: %s' % (action_str, package)
Packit 6f3914
        self.rpm_logger.log(dnf.logging.SUBDEBUG, msg)
Packit 6f3914
Packit 6f3914
Packit 6f3914
class RPMTransaction(object):
Packit 6f3914
    def __init__(self, base, test=False, displays=()):
Packit 6f3914
        if not displays:
Packit 6f3914
            displays = [ErrorTransactionDisplay()]
Packit 6f3914
        self.displays = displays
Packit 6f3914
        self.base = base
Packit 6f3914
        self.test = test  # are we a test?
Packit 6f3914
        self.trans_running = False
Packit 6f3914
        self.fd = None
Packit 6f3914
        self.total_actions = 0
Packit 6f3914
        self.total_installed = 0
Packit 6f3914
        self.complete_actions = 0
Packit 6f3914
        self.installed_pkg_names = set()
Packit 6f3914
        self.total_removed = 0
Packit 6f3914
Packit 6f3914
        self._setupOutputLogging(base.conf.rpmverbosity)
Packit 6f3914
        self._te_list = []
Packit 6f3914
        # Index in _te_list of the transaction element being processed (for use
Packit 6f3914
        # in callbacks)
Packit 6f3914
        self._te_index = 0
Packit 6f3914
        self._tsi_cache = None
Packit 6f3914
Packit 6f3914
    def _setupOutputLogging(self, rpmverbosity="info"):
Packit 6f3914
        # UGLY... set up the transaction to record output from scriptlets
Packit 6f3914
        io_r = tempfile.NamedTemporaryFile()
Packit 6f3914
        self._readpipe = io_r
Packit 6f3914
        self._writepipe = open(io_r.name, 'w+b')
Packit 6f3914
        self.base._ts.setScriptFd(self._writepipe)
Packit 6f3914
        rpmverbosity = {'critical' : 'crit',
Packit 6f3914
                        'emergency' : 'emerg',
Packit 6f3914
                        'error' : 'err',
Packit 6f3914
                        'information' : 'info',
Packit 6f3914
                        'warn' : 'warning'}.get(rpmverbosity, rpmverbosity)
Packit 6f3914
        rpmverbosity = 'RPMLOG_' + rpmverbosity.upper()
Packit 6f3914
        if not hasattr(rpm, rpmverbosity):
Packit 6f3914
            rpmverbosity = 'RPMLOG_INFO'
Packit 6f3914
        rpm.setVerbosity(getattr(rpm, rpmverbosity))
Packit 6f3914
        rpm.setLogFile(self._writepipe)
Packit 6f3914
Packit 6f3914
    def _shutdownOutputLogging(self):
Packit 6f3914
        # reset rpm bits from recording output
Packit 6f3914
        rpm.setVerbosity(rpm.RPMLOG_NOTICE)
Packit 6f3914
        rpm.setLogFile(sys.stderr)
Packit 6f3914
        try:
Packit 6f3914
            self._writepipe.close()
Packit 6f3914
        except:
Packit 6f3914
            pass
Packit 6f3914
Packit 6f3914
    def _scriptOutput(self):
Packit 6f3914
        try:
Packit 6f3914
            # XXX ugly workaround of problem which started after upgrading glibc
Packit 6f3914
            # from glibc-2.27-32.fc28.x86_64 to glibc-2.28-9.fc29.x86_64
Packit 6f3914
            # After this upgrade nothing is read from _readpipe, so every
Packit 6f3914
            # posttrans and postun scriptlet output is lost. The problem
Packit 6f3914
            # only occurs when using dnf-2, dnf-3 is OK.
Packit 6f3914
            # I did not find the root cause of this error yet.
Packit 6f3914
            self._readpipe.seek(self._readpipe.tell())
Packit 6f3914
            out = self._readpipe.read()
Packit 6f3914
            if not out:
Packit 6f3914
                return None
Packit 6f3914
            return out
Packit 6f3914
        except IOError:
Packit 6f3914
            pass
Packit 6f3914
Packit 6f3914
    def messages(self):
Packit 6f3914
        messages = self._scriptOutput()
Packit 6f3914
        if messages:
Packit 6f3914
            for line in messages.splitlines():
Packit 6f3914
                yield ucd(line)
Packit 6f3914
Packit 6f3914
    def _scriptout(self):
Packit 6f3914
        msgs = self._scriptOutput()
Packit 6f3914
        for display in self.displays:
Packit 6f3914
            display.scriptout(msgs)
Packit 6f3914
        self.base.history.log_scriptlet_output(msgs)
Packit 6f3914
Packit 6f3914
    def __del__(self):
Packit 6f3914
        self._shutdownOutputLogging()
Packit 6f3914
Packit 6f3914
    def _extract_cbkey(self, cbkey):
Packit 6f3914
        """Obtain the package related to the calling callback."""
Packit 6f3914
Packit 6f3914
        if hasattr(cbkey, "pkg"):
Packit 6f3914
            tsi = cbkey
Packit 6f3914
            return [tsi]
Packit 6f3914
Packit 6f3914
        te = self._te_list[self._te_index]
Packit 6f3914
        te_nevra = dnf.util._te_nevra(te)
Packit 6f3914
        if self._tsi_cache:
Packit 6f3914
            if str(self._tsi_cache[0]) == te_nevra:
Packit 6f3914
                return self._tsi_cache
Packit 6f3914
        items = []
Packit 6f3914
        for tsi in self.base.transaction:
Packit 6f3914
            if tsi.action == libdnf.transaction.TransactionItemAction_REINSTALL:
Packit 6f3914
                # skip REINSTALL in order to return REINSTALLED
Packit 6f3914
                continue
Packit 6f3914
            if str(tsi) == te_nevra:
Packit 6f3914
                items.append(tsi)
Packit 6f3914
        if items:
Packit 6f3914
            self._tsi_cache = items
Packit 6f3914
            return items
Packit 6f3914
        raise RuntimeError("TransactionItem not found for key: %s" % cbkey)
Packit 6f3914
Packit 6f3914
    def callback(self, what, amount, total, key, client_data):
Packit 6f3914
        try:
Packit 6f3914
            if isinstance(key, str):
Packit 6f3914
                key = ucd(key)
Packit 6f3914
            if what == rpm.RPMCALLBACK_TRANS_START:
Packit 6f3914
                self._transStart(total)
Packit 6f3914
            elif what == rpm.RPMCALLBACK_TRANS_STOP:
Packit 6f3914
                pass
Packit 6f3914
            elif what == rpm.RPMCALLBACK_TRANS_PROGRESS:
Packit 6f3914
                self._trans_progress(amount, total)
Packit 6f3914
            elif what == rpm.RPMCALLBACK_ELEM_PROGRESS:
Packit 6f3914
                # This callback type is issued every time the next transaction
Packit 6f3914
                # element is about to be processed by RPM, before any other
Packit 6f3914
                # callbacks are issued.  "amount" carries the index of the element.
Packit 6f3914
                self._elemProgress(key, amount)
Packit 6f3914
            elif what == rpm.RPMCALLBACK_INST_OPEN_FILE:
Packit 6f3914
                return self._instOpenFile(key)
Packit 6f3914
            elif what == rpm.RPMCALLBACK_INST_CLOSE_FILE:
Packit 6f3914
                self._instCloseFile(key)
Packit 6f3914
            elif what == rpm.RPMCALLBACK_INST_START:
Packit 6f3914
                self._inst_start(key)
Packit 6f3914
            elif what == rpm.RPMCALLBACK_INST_STOP:
Packit 6f3914
                self._inst_stop(key)
Packit 6f3914
            elif what == rpm.RPMCALLBACK_INST_PROGRESS:
Packit 6f3914
                self._instProgress(amount, total, key)
Packit 6f3914
            elif what == rpm.RPMCALLBACK_UNINST_START:
Packit 6f3914
                self._uninst_start(key)
Packit 6f3914
            elif what == rpm.RPMCALLBACK_UNINST_STOP:
Packit 6f3914
                self._unInstStop(key)
Packit 6f3914
            elif what == rpm.RPMCALLBACK_UNINST_PROGRESS:
Packit 6f3914
                self._uninst_progress(amount, total, key)
Packit 6f3914
            elif what == rpm.RPMCALLBACK_CPIO_ERROR:
Packit 6f3914
                self._cpioError(key)
Packit 6f3914
            elif what == rpm.RPMCALLBACK_UNPACK_ERROR:
Packit 6f3914
                self._unpackError(key)
Packit 6f3914
            elif what == rpm.RPMCALLBACK_SCRIPT_ERROR:
Packit 6f3914
                self._scriptError(amount, total, key)
Packit 6f3914
            elif what == rpm.RPMCALLBACK_SCRIPT_START:
Packit 6f3914
                self._script_start(key)
Packit 6f3914
            elif what == rpm.RPMCALLBACK_SCRIPT_STOP:
Packit 6f3914
                self._scriptStop()
Packit 6f3914
        except Exception:
Packit 6f3914
            exc_type, exc_value, exc_traceback = sys.exc_info()
Packit 6f3914
            except_list = traceback.format_exception(exc_type, exc_value, exc_traceback)
Packit 6f3914
            logger.critical(''.join(except_list))
Packit 6f3914
Packit 6f3914
    def _transStart(self, total):
Packit 6f3914
        self.total_actions = total
Packit 6f3914
        if self.test: return
Packit 6f3914
        self.trans_running = True
Packit 6f3914
        self._te_list = list(self.base._ts)
Packit 6f3914
Packit 6f3914
    def _trans_progress(self, amount, total):
Packit 6f3914
        action = dnf.transaction.TRANS_PREPARATION
Packit 6f3914
        for display in self.displays:
Packit 6f3914
            display.progress('', action, amount + 1, total, 1, 1)
Packit 6f3914
Packit 6f3914
    def _elemProgress(self, key, index):
Packit 6f3914
        self._te_index = index
Packit 6f3914
        self.complete_actions += 1
Packit 6f3914
        if not self.test:
Packit 6f3914
            transaction_list = self._extract_cbkey(key)
Packit 6f3914
            for display in self.displays:
Packit 6f3914
                display.filelog(transaction_list[0].pkg, transaction_list[0].action)
Packit 6f3914
Packit 6f3914
    def _instOpenFile(self, key):
Packit 6f3914
        self.lastmsg = None
Packit 6f3914
        transaction_list = self._extract_cbkey(key)
Packit 6f3914
        pkg = transaction_list[0].pkg
Packit 6f3914
        rpmloc = pkg.localPkg()
Packit 6f3914
        try:
Packit 6f3914
            self.fd = open(rpmloc)
Packit 6f3914
        except IOError as e:
Packit 6f3914
            for display in self.displays:
Packit 6f3914
                display.error("Error: Cannot open file %s: %s" % (rpmloc, e))
Packit 6f3914
        else:
Packit 6f3914
            if self.trans_running:
Packit 6f3914
                self.total_installed += 1
Packit 6f3914
                self.installed_pkg_names.add(pkg.name)
Packit 6f3914
            return self.fd.fileno()
Packit 6f3914
Packit 6f3914
    def _instCloseFile(self, key):
Packit 6f3914
        self.fd.close()
Packit 6f3914
        self.fd = None
Packit 6f3914
Packit 6f3914
    def _inst_start(self, key):
Packit 6f3914
        pass
Packit 6f3914
Packit 6f3914
    def _inst_stop(self, key):
Packit 6f3914
        if self.test or not self.trans_running:
Packit 6f3914
            return
Packit 6f3914
Packit 6f3914
        self._scriptout()
Packit 6f3914
Packit 6f3914
        if self.complete_actions == self.total_actions:
Packit 6f3914
            # RPM doesn't explicitly report when post-trans phase starts
Packit 6f3914
            action = dnf.transaction.TRANS_POST
Packit 6f3914
            for display in self.displays:
Packit 6f3914
                display.progress(None, action, None, None, None, None)
Packit 6f3914
Packit 6f3914
    def _instProgress(self, amount, total, key):
Packit 6f3914
        transaction_list = self._extract_cbkey(key)
Packit 6f3914
        pkg = transaction_list[0].pkg
Packit 6f3914
        action = transaction_list[0].action
Packit 6f3914
        for display in self.displays:
Packit 6f3914
            display.progress(pkg, action, amount, total, self.complete_actions, self.total_actions)
Packit 6f3914
Packit 6f3914
    def _uninst_start(self, key):
Packit 6f3914
        self.total_removed += 1
Packit 6f3914
Packit 6f3914
    def _uninst_progress(self, amount, total, key):
Packit 6f3914
        transaction_list = self._extract_cbkey(key)
Packit 6f3914
        pkg = transaction_list[0].pkg
Packit 6f3914
        action = transaction_list[0].action
Packit 6f3914
        for display in self.displays:
Packit 6f3914
            display.progress(pkg, action, amount, total, self.complete_actions, self.total_actions)
Packit 6f3914
Packit 6f3914
    def _unInstStop(self, key):
Packit 6f3914
        if self.test:
Packit 6f3914
            return
Packit 6f3914
Packit 6f3914
        self._scriptout()
Packit 6f3914
Packit 6f3914
    def _cpioError(self, key):
Packit 6f3914
        transaction_list = self._extract_cbkey(key)
Packit 6f3914
        msg = "Error in cpio payload of rpm package %s" % transaction_list[0].pkg
Packit 6f3914
        for display in self.displays:
Packit 6f3914
            display.error(msg)
Packit 6f3914
Packit 6f3914
    def _unpackError(self, key):
Packit 6f3914
        transaction_list = self._extract_cbkey(key)
Packit 6f3914
        msg = "Error unpacking rpm package %s" % transaction_list[0].pkg
Packit 6f3914
        for display in self.displays:
Packit 6f3914
            display.error(msg)
Packit 6f3914
Packit 6f3914
    def _scriptError(self, amount, total, key):
Packit 6f3914
        # "amount" carries the failed scriptlet tag,
Packit 6f3914
        # "total" carries fatal/non-fatal status
Packit 6f3914
        scriptlet_name = rpm.tagnames.get(amount, "<unknown>")
Packit 6f3914
Packit 6f3914
        transaction_list = self._extract_cbkey(key)
Packit 6f3914
        name = transaction_list[0].pkg.name
Packit 6f3914
Packit 6f3914
        msg = ("Error in %s scriptlet in rpm package %s" % (scriptlet_name, name))
Packit 6f3914
Packit 6f3914
        for display in self.displays:
Packit 6f3914
            display.error(msg)
Packit 6f3914
Packit 6f3914
    def _script_start(self, key):
Packit 6f3914
        # TODO: this doesn't fit into libdnf TransactionItem use cases
Packit 6f3914
        action = dnf.transaction.PKG_SCRIPTLET
Packit 6f3914
        if key is None and self._te_list == []:
Packit 6f3914
            pkg = 'None'
Packit 6f3914
        else:
Packit 6f3914
            transaction_list = self._extract_cbkey(key)
Packit 6f3914
            pkg = transaction_list[0].pkg
Packit 6f3914
        complete = self.complete_actions if self.total_actions != 0 and self.complete_actions != 0 \
Packit 6f3914
            else 1
Packit 6f3914
        total = self.total_actions if self.total_actions != 0 and self.complete_actions != 0 else 1
Packit 6f3914
        for display in self.displays:
Packit 6f3914
            display.progress(pkg, action, 100, 100, complete, total)
Packit 6f3914
Packit 6f3914
    def _scriptStop(self):
Packit 6f3914
        self._scriptout()
Packit 6f3914
Packit 6f3914
    def verify_tsi_package(self, pkg, count, total):
Packit 6f3914
        for display in self.displays:
Packit 6f3914
            display.verify_tsi_package(pkg, count, total)