[ SEA-GHOST MINI SHELL]
#!/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)
SEA-GHOST - SHELL CODING BY SEA-GHOST