曾与蒿藜同雨露,한때 잡초와 쑥과 함께 비와 이슬을 나누던 곳이 이제는 소나무와 삼나무와 함께 서리와 눈을 견뎌내고 있다.终随松柏到冰霜.かつては雑草やヨモギと共に雨や露を分かち合っていたが、今では松やヒノキと共に霜や雪に耐えている。曾与蒿藜同雨露,Once sharing rain and dew with weeds and wormwood, now enduring frost and snow with pines and cypresses.终随松柏到冰霜.曾与蒿藜同雨露한때 잡초와 쑥과 함께 비와 이슬을 나누던 곳이 이제는 소나무와 삼나무와 함께 서리와 눈을 견뎌내고 있다.,终随松柏到冰霜.譖セ荳手珍阯懷酔髮ィ髴イ�檎サ磯囂譚セ譟丞芦蜀ー髴�曾与蒿藜同雨露,鏇句笌钂胯棞鍚岄洦闇诧紝缁堥殢鏉炬煆鍒板啺闇�终随松柏到冰霜.曾与蒿藜同雨露,한때 잡초와 쑥과 함께 비와 이슬을 나누던 곳이 이제는 소나무와 삼나무와 함께 서리와 눈을 견뎌내고 있다.终随松柏到冰霜.曾与蒿藜同雨露,终随松柏到冰霜.
#!/usr/bin/python -t
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# Copyright 2005 Duke University
# Parts Copyright 2007 Red Hat, Inc
import rpm
import os
import fcntl
import time
import logging
import types
import sys
from yum.constants import *
from yum import _
from yum.transactioninfo import TransactionMember
import misc
import tempfile
class NoOutputCallBack:
def __init__(self):
pass
def event(self, package, action, te_current, te_total, ts_current, ts_total):
"""
@param package: A yum package object or simple string of a package name
@param action: A yum.constant transaction set state or in the obscure
rpm repackage case it could be the string 'repackaging'
@param te_current: current number of bytes processed in the transaction
element being processed
@param te_total: total number of bytes in the transaction element being
processed
@param ts_current: number of processes completed in whole transaction
@param ts_total: total number of processes in the transaction.
"""
# this is where a progress bar would be called
pass
def scriptout(self, package, msgs):
"""package is the package. msgs is the messages that were
output (if any)."""
pass
def errorlog(self, msg):
"""takes a simple error msg string"""
pass
def filelog(self, package, action):
# check package object type - if it is a string - just output it
"""package is the same as in event() - a package object or simple string
action is also the same as in event()"""
pass
class RPMBaseCallback:
'''
Base class for a RPMTransaction display callback class
'''
def __init__(self):
self.action = { TS_UPDATE : _('Updating'),
TS_ERASE: _('Erasing'),
TS_INSTALL: _('Installing'),
TS_TRUEINSTALL : _('Installing'),
TS_OBSOLETED: _('Obsoleted'),
TS_OBSOLETING: _('Installing'),
TS_UPDATED: _('Cleanup'),
'repackaging': _('Repackaging')}
# The fileaction are not translated, most sane IMHO / Tim
self.fileaction = { TS_UPDATE: 'Updated',
TS_ERASE: 'Erased',
TS_INSTALL: 'Installed',
TS_TRUEINSTALL: 'Installed',
TS_OBSOLETED: 'Obsoleted',
TS_OBSOLETING: 'Installed',
TS_UPDATED: 'Cleanup'}
self.logger = logging.getLogger('yum.filelogging.RPMInstallCallback')
def event(self, package, action, te_current, te_total, ts_current, ts_total):
"""
@param package: A yum package object or simple string of a package name
@param action: A yum.constant transaction set state or in the obscure
rpm repackage case it could be the string 'repackaging'
@param te_current: Current number of bytes processed in the transaction
element being processed
@param te_total: Total number of bytes in the transaction element being
processed
@param ts_current: number of processes completed in whole transaction
@param ts_total: total number of processes in the transaction.
"""
raise NotImplementedError()
def scriptout(self, package, msgs):
"""package is the package. msgs is the messages that were
output (if any)."""
pass
def errorlog(self, msg):
# FIXME this should probably dump to the filelog, too
print >> sys.stderr, msg
def filelog(self, package, action):
# If the action is not in the fileaction list then dump it as a string
# hurky but, sadly, not much else
if action in self.fileaction:
msg = '%s: %s' % (self.fileaction[action], package)
else:
msg = '%s: %s' % (package, action)
self.logger.info(msg)
def verify_txmbr(self, base, txmbr, count):
" Callback for post transaction when we are in verifyTransaction(). "
pass
class SimpleCliCallBack(RPMBaseCallback):
def __init__(self):
RPMBaseCallback.__init__(self)
self.lastmsg = None
self.lastpackage = None # name of last package we looked at
def event(self, package, action, te_current, te_total, ts_current, ts_total):
# this is where a progress bar would be called
msg = '%s: %s %s/%s [%s/%s]' % (self.action[action], package,
te_current, te_total, ts_current, ts_total)
if msg != self.lastmsg:
print msg
self.lastmsg = msg
self.lastpackage = package
def scriptout(self, package, msgs):
if msgs:
print msgs,
def verify_txmbr(self, base, txmbr, count):
" Callback for post transaction when we are in verifyTransaction(). "
print _("Verify: %u/%u: %s") % (count, len(base.tsInfo), txmbr)
# This is ugly, but atm. rpm can go insane and run the "cleanup" phase
# without the "install" phase if it gets an exception in it's callback. The
# following means that we don't really need to know/care about that in the
# display callback functions.
# Note try/except's in RPMTransaction are for the same reason.
class _WrapNoExceptions:
def __init__(self, parent):
self.__parent = parent
def __getattr__(self, name):
""" Wraps all access to the parent functions. This is so it'll eat all
exceptions because rpm doesn't like exceptions in the callback. """
func = getattr(self.__parent, name)
def newFunc(*args, **kwargs):
try:
func(*args, **kwargs)
except Exception, e:
# It's impossible to debug stuff without this:
try:
print "Error:", "display callback failed:", e
except:
pass
newFunc.__name__ = func.__name__
newFunc.__doc__ = func.__doc__
newFunc.__dict__.update(func.__dict__)
return newFunc
class RPMTransaction:
def __init__(self, base, test=False, display=NoOutputCallBack):
if not callable(display):
self.display = display
else:
self.display = display() # display callback
self.display = _WrapNoExceptions(self.display)
self.base = base # base yum object b/c we need so much
self.test = test # are we a test?
self.trans_running = False
self.fd = None
self.total_actions = 0
self.total_installed = 0
self.complete_actions = 0
self.installed_pkg_names = set()
self.total_removed = 0
self.logger = logging.getLogger('yum.filelogging.RPMInstallCallback')
self.filelog = False
self._setupOutputLogging(base.conf.rpmverbosity)
if not os.path.exists(self.base.conf.persistdir):
os.makedirs(self.base.conf.persistdir) # make the dir, just in case
# Error checking? -- these should probably be where else
def _fdSetNonblock(self, fd):
""" Set the Non-blocking flag for a filedescriptor. """
flag = os.O_NONBLOCK
current_flags = fcntl.fcntl(fd, fcntl.F_GETFL)
if current_flags & flag:
return
fcntl.fcntl(fd, fcntl.F_SETFL, current_flags | flag)
def _fdSetCloseOnExec(self, fd):
""" Set the close on exec. flag for a filedescriptor. """
flag = fcntl.FD_CLOEXEC
current_flags = fcntl.fcntl(fd, fcntl.F_GETFD)
if current_flags & flag:
return
fcntl.fcntl(fd, fcntl.F_SETFD, current_flags | flag)
def _setupOutputLogging(self, rpmverbosity="info"):
# UGLY... set up the transaction to record output from scriptlets
io_r = tempfile.NamedTemporaryFile()
self._readpipe = io_r
self._writepipe = open(io_r.name, 'w+b')
self.base.ts.setScriptFd(self._writepipe)
rpmverbosity = {'critical' : 'crit',
'emergency' : 'emerg',
'error' : 'err',
'information' : 'info',
'warn' : 'warning'}.get(rpmverbosity, rpmverbosity)
rpmverbosity = 'RPMLOG_' + rpmverbosity.upper()
if not hasattr(rpm, rpmverbosity):
rpmverbosity = 'RPMLOG_INFO'
rpm.setVerbosity(getattr(rpm, rpmverbosity))
rpm.setLogFile(self._writepipe)
def _shutdownOutputLogging(self):
# reset rpm bits from reording output
rpm.setVerbosity(rpm.RPMLOG_NOTICE)
rpm.setLogFile(sys.stderr)
try:
self._writepipe.close()
except:
pass
def _scriptOutput(self):
try:
out = self._readpipe.read()
if not out:
return None
return out
except IOError:
pass
def _scriptout(self, data):
msgs = self._scriptOutput()
self.display.scriptout(data, msgs)
self.base.history.log_scriptlet_output(data, msgs)
def __del__(self):
self._shutdownOutputLogging()
def _dopkgtup(self, hdr):
tmpepoch = hdr['epoch']
if tmpepoch is None: epoch = '0'
else: epoch = str(tmpepoch)
return (hdr['name'], hdr['arch'], epoch, hdr['version'], hdr['release'])
# Find out txmbr based on the callback key. On erasures we dont know
# the exact txmbr but we always have a name, so return (name, txmbr)
# tuples so callers have less twists to deal with.
def _getTxmbr(self, cbkey, erase=False):
if isinstance(cbkey, TransactionMember):
return (cbkey.name, cbkey)
elif isinstance(cbkey, tuple):
pkgtup = self._dopkgtup(cbkey[0])
txmbrs = self.base.tsInfo.getMembers(pkgtup=pkgtup)
# if this is not one, somebody screwed up
assert len(txmbrs) == 1
return (txmbrs[0].name, txmbrs[0])
elif isinstance(cbkey, basestring):
ret = None
# If we don't have a tuple, it's because this is an erase txmbr and
# rpm doesn't provide one in that case. So we can "cheat" and look
# through all our txmbrs for the name we have, and if we find a
# single match ... that must be it.
if not erase:
return (cbkey, None)
for txmbr in self.base.tsInfo.matchNaevr(name=cbkey):
if txmbr.output_state not in TS_REMOVE_STATES:
continue
# If we have more than one match, then we don't know which one
# it is ... so just give up.
if ret is not None:
return (cbkey, None)
ret = txmbr
return (cbkey, ret)
else:
return (None, None)
def _fn_rm_installroot(self, filename):
""" Remove the installroot from the filename. """
# to handle us being inside a chroot at this point
# we hand back the right path to those 'outside' of the chroot() calls
# but we're using the right path inside.
if self.base.conf.installroot == '/':
return filename
return filename.replace(os.path.normpath(self.base.conf.installroot),'')
def ts_done_open(self):
""" Open the transaction done file, must be started outside the
chroot. """
if self.test: return False
if hasattr(self, '_ts_done'):
return True
self.ts_done_fn = '%s/transaction-done.%s' % (self.base.conf.persistdir,
self._ts_time)
ts_done_fn = self._fn_rm_installroot(self.ts_done_fn)
try:
self._ts_done = open(ts_done_fn, 'w')
except (IOError, OSError), e:
self.display.errorlog('could not open ts_done file: %s' % e)
self._ts_done = None
return False
self._fdSetCloseOnExec(self._ts_done.fileno())
return True
def ts_done_write(self, msg):
""" Write some data to the transaction done file. """
if self._ts_done is None:
return
try:
self._ts_done.write(msg)
self._ts_done.flush()
except (IOError, OSError), e:
# Having incomplete transactions is probably worse than having
# nothing.
self.display.errorlog('could not write to ts_done file: %s' % e)
self._ts_done = None
misc.unlink_f(self.ts_done_fn)
def ts_done(self, package, action):
"""writes out the portions of the transaction which have completed"""
if not self.ts_done_open(): return
# walk back through self._te_tuples
# make sure the package and the action make some kind of sense
# write it out and pop(0) from the list
# make sure we have a list to work from
if len(self._te_tuples) == 0:
# if we don't then this is pretrans or postrans or a trigger
# either way we have to respond correctly so just return and don't
# emit anything
return
(t,e,n,v,r,a) = self._te_tuples[0] # what we should be on
# make sure we're in the right action state
msg = 'ts_done state is %s %s should be %s %s' % (package, action, t, n)
if action in TS_REMOVE_STATES:
if t != 'erase':
self.display.filelog(package, msg)
if action in TS_INSTALL_STATES:
if t != 'install':
self.display.filelog(package, msg)
# check the pkg name out to make sure it matches
if type(package) in types.StringTypes:
name = package
else:
name = package.name
if n != name:
msg = 'ts_done name in te is %s should be %s' % (n, package)
self.display.filelog(package, msg)
# hope springs eternal that this isn't wrong
msg = '%s %s:%s-%s-%s.%s\n' % (t,e,n,v,r,a)
self.ts_done_write(msg)
self._te_tuples.pop(0)
def ts_all(self):
"""write out what our transaction will do"""
# save the transaction elements into a list so we can run across them
if not hasattr(self, '_te_tuples'):
self._te_tuples = []
for te in self.base.ts:
n = te.N()
a = te.A()
v = te.V()
r = te.R()
e = te.E()
if e is None:
e = '0'
if te.Type() == 1:
t = 'install'
elif te.Type() == 2:
t = 'erase'
else:
t = te.Type()
# save this in a list
self._te_tuples.append((t,e,n,v,r,a))
# write to a file
self._ts_time = time.strftime('%Y-%m-%d.%H:%M.%S')
tsfn = '%s/transaction-all.%s' % (self.base.conf.persistdir, self._ts_time)
self.ts_all_fn = tsfn
tsfn = self._fn_rm_installroot(tsfn)
try:
if not os.path.exists(os.path.dirname(tsfn)):
os.makedirs(os.path.dirname(tsfn)) # make the dir,
fo = open(tsfn, 'w')
except (IOError, OSError), e:
self.display.errorlog('could not open ts_all file: %s' % e)
self._ts_done = None
return
try:
for (t,e,n,v,r,a) in self._te_tuples:
msg = "%s %s:%s-%s-%s.%s\n" % (t,e,n,v,r,a)
fo.write(msg)
fo.flush()
fo.close()
except (IOError, OSError), e:
# Having incomplete transactions is probably worse than having
# nothing.
self.display.errorlog('could not write to ts_all file: %s' % e)
misc.unlink_f(tsfn)
self._ts_done = None
def callback( self, what, bytes, total, h, user ):
if what == rpm.RPMCALLBACK_TRANS_START:
self._transStart( bytes, total, h )
elif what == rpm.RPMCALLBACK_TRANS_PROGRESS:
self._transProgress( bytes, total, h )
elif what == rpm.RPMCALLBACK_TRANS_STOP:
self._transStop( bytes, total, h )
elif what == rpm.RPMCALLBACK_INST_OPEN_FILE:
return self._instOpenFile( bytes, total, h )
elif what == rpm.RPMCALLBACK_INST_CLOSE_FILE:
self._instCloseFile( bytes, total, h )
elif what == rpm.RPMCALLBACK_INST_PROGRESS:
self._instProgress( bytes, total, h )
elif what == rpm.RPMCALLBACK_UNINST_START:
self._unInstStart( bytes, total, h )
elif what == rpm.RPMCALLBACK_UNINST_PROGRESS:
self._unInstProgress( bytes, total, h )
elif what == rpm.RPMCALLBACK_UNINST_STOP:
self._unInstStop( bytes, total, h )
elif what == rpm.RPMCALLBACK_REPACKAGE_START:
self._rePackageStart( bytes, total, h )
elif what == rpm.RPMCALLBACK_REPACKAGE_STOP:
self._rePackageStop( bytes, total, h )
elif what == rpm.RPMCALLBACK_REPACKAGE_PROGRESS:
self._rePackageProgress( bytes, total, h )
elif what == rpm.RPMCALLBACK_CPIO_ERROR:
self._cpioError(bytes, total, h)
elif what == rpm.RPMCALLBACK_UNPACK_ERROR:
self._unpackError(bytes, total, h)
# SCRIPT_ERROR is only in rpm >= 4.6.0
elif hasattr(rpm, "RPMCALLBACK_SCRIPT_ERROR") and what == rpm.RPMCALLBACK_SCRIPT_ERROR:
self._scriptError(bytes, total, h)
# SCRIPT_START and SCRIPT_STOP are only in rpm >= 4.10
elif hasattr(rpm, "RPMCALLBACK_SCRIPT_START") and what == rpm.RPMCALLBACK_SCRIPT_START:
self._scriptStart(bytes, total, h);
elif hasattr(rpm, "RPMCALLBACK_SCRIPT_STOP") and what == rpm.RPMCALLBACK_SCRIPT_STOP:
self._scriptStop(bytes, total, h);
def _transStart(self, bytes, total, h):
self.total_actions = total
if self.test: return
self.trans_running = True
self.ts_all() # write out what transaction will do
self.ts_done_open()
def _transProgress(self, bytes, total, h):
pass
def _transStop(self, bytes, total, h):
pass
def _instOpenFile(self, bytes, total, h):
self.lastmsg = None
name, txmbr = self._getTxmbr(h)
if txmbr is not None:
rpmloc = txmbr.po.localPkg()
try:
self.fd = file(rpmloc)
except IOError, e:
self.display.errorlog("Error: Cannot open file %s: %s" % (rpmloc, e))
else:
if self.trans_running:
self.total_installed += 1
self.complete_actions += 1
self.installed_pkg_names.add(name)
return self.fd.fileno()
else:
self.display.errorlog("Error: No Header to INST_OPEN_FILE")
def _instCloseFile(self, bytes, total, h):
name, txmbr = self._getTxmbr(h)
if txmbr is not None:
self.fd.close()
self.fd = None
if self.test: return
if self.trans_running:
self.display.filelog(txmbr.po, txmbr.output_state)
self._scriptout(txmbr.po)
pid = self.base.history.pkg2pid(txmbr.po)
state = self.base.history.txmbr2state(txmbr)
self.base.history.trans_data_pid_end(pid, state)
self.ts_done(txmbr.po, txmbr.output_state)
def _instProgress(self, bytes, total, h):
name, txmbr = self._getTxmbr(h)
if name is not None:
# If we only have a name, we're repackaging.
# Why the RPMCALLBACK_REPACKAGE_PROGRESS flag isn't set, I have no idea
if txmbr is None:
self.display.event(name, 'repackaging', bytes, total,
self.complete_actions, self.total_actions)
else:
action = txmbr.output_state
self.display.event(txmbr.po, action, bytes, total,
self.complete_actions, self.total_actions)
def _unInstStart(self, bytes, total, h):
pass
def _unInstProgress(self, bytes, total, h):
pass
def _unInstStop(self, bytes, total, h):
name, txmbr = self._getTxmbr(h, erase=True)
self.total_removed += 1
self.complete_actions += 1
if name not in self.installed_pkg_names:
if txmbr is not None:
self.display.filelog(txmbr.po, TS_ERASE)
else:
self.display.filelog(name, TS_ERASE)
action = TS_ERASE
else:
action = TS_UPDATED
# FIXME: Do we want to pass txmbr.po here too?
self.display.event(name, action, 100, 100, self.complete_actions,
self.total_actions)
if self.test: return # and we're done
if txmbr is not None:
self._scriptout(txmbr.po)
# Note that we are currently inside the chroot, which makes
# sqlite panic when it tries to open it's journal file.
# So let's have some "fun" and workaround that:
_do_chroot = False
if _do_chroot and self.base.conf.installroot != '/':
os.chroot(".")
pid = self.base.history.pkg2pid(txmbr.po)
state = self.base.history.txmbr2state(txmbr)
self.base.history.trans_data_pid_end(pid, state)
if _do_chroot and self.base.conf.installroot != '/':
os.chroot(self.base.conf.installroot)
self.ts_done(txmbr.po, txmbr.output_state)
else:
self._scriptout(name)
self.ts_done(name, action)
def _rePackageStart(self, bytes, total, h):
pass
def _rePackageStop(self, bytes, total, h):
pass
def _rePackageProgress(self, bytes, total, h):
pass
def _cpioError(self, bytes, total, h):
name, txmbr = self._getTxmbr(h)
# In the case of a remove, we only have a name, not a txmbr
if txmbr is not None:
msg = "Error in cpio payload of rpm package %s" % txmbr.po
txmbr.output_state = TS_FAILED
self.display.errorlog(msg)
# FIXME - what else should we do here? raise a failure and abort?
def _unpackError(self, bytes, total, h):
name, txmbr = self._getTxmbr(h)
# In the case of a remove, we only have a name, not a txmbr
if txmbr is not None:
txmbr.output_state = TS_FAILED
msg = "Error unpacking rpm package %s" % txmbr.po
self.display.errorlog(msg)
# FIXME - should we raise? I need a test case pkg to see what the
# right behavior should be
def _scriptError(self, bytes, total, h):
# "bytes" carries the failed scriptlet tag,
# "total" carries fatal/non-fatal status
scriptlet_name = rpm.tagnames.get(bytes, "<unknown>")
name, txmbr = self._getTxmbr(h, erase=True)
if txmbr is None:
package_name = name
else:
package_name = txmbr.po
if total:
msg = ("Error in %s scriptlet in rpm package %s" %
(scriptlet_name, package_name))
# In the case of a remove, we only have a name, not a txmbr
if txmbr is not None:
txmbr.output_state = TS_FAILED
else:
msg = ("Non-fatal %s scriptlet failure in rpm package %s" %
(scriptlet_name, package_name))
self.display.errorlog(msg)
self._scriptout(package_name)
# FIXME - what else should we do here? raise a failure and abort?
def _scriptStart(self, bytes, total, h):
pass
def _scriptStop(self, bytes, total, h):
name, txmbr = self._getTxmbr(h)
self._scriptout(txmbr or name)
def verify_txmbr(self, txmbr, count):
" Callback for post transaction when we are in verifyTransaction(). "
if not hasattr(self.display, 'verify_txmbr'):
return
self.display.verify_txmbr(self.base, txmbr, count)