Viewing file: yum-updatesd-helper (22.73 KB) -rwxr-xr-x Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
#!/usr/bin/python -tt
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License
# as published by the Free Software Foundation.
#
# 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 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.
# (c)2006-2007 Duke University, Red Hat, Inc.
# Seth Vidal
# Jeremy Katz
import sys
import gzip
import dbus
import dbus.service
import dbus.glib
import smtplib
from optparse import OptionParser
from email.MIMEText import MIMEText
import socket
import rfc822
import subprocess
import yum
import yum.Errors
import syslog
from yum.constants import *
from yum.update_md import UpdateMetadata
# FIXME: is it really sane to use this from here?
sys.path.append('/usr/share/yum-cli')
import callback
from yum.constants import YUM_PID_FILE
class UpdateEmitter(object):
"""Abstract object for implementing different types of emitters."""
def __init__(self):
pass
def updatesAvailable(self, updateInfo):
"""Emitted when there are updates available to be installed.
If not doing the download here, then called immediately on finding
new updates. If we do the download here, then called after the
updates have been downloaded."""
pass
def updatesDownloading(self, updateInfo):
"""Emitted to give feedback of update download starting."""
pass
def updatesInstalling(self, updateInfo):
"""Emitted to give feedback of update download starting."""
pass
def updatesApplied(self, updateInfo):
"""Emitted on successful installation of updates."""
pass
def updatesFailed(self, errmsgs):
"""Emitted when an update has failed to install."""
pass
def checkFailed(self, error):
"""Emitted when checking for updates failed."""
pass
def setupFailed(self, error, translation_domain):
"""Emitted when plugin initialization failed."""
pass
def locked(self, error):
"""Emitted when the yum lock is already held."""
pass
def updateInfo(self, update):
"""Emitted with information on available updates (async)"""
pass
class SyslogUpdateEmitter(UpdateEmitter):
def __init__(self, syslog_facility, ident = "yum-updatesd",
level = "WARN"):
UpdateEmitter.__init__(self)
syslog.openlog(ident, 0, self._facilityMap(syslog_facility))
self.level = level
def updatesAvailable(self, updateInfo):
num = len(updateInfo)
level = self.level
if num > 1:
msg = "%d updates available" %(num,)
elif num == 1:
msg = "1 update available"
else:
msg = "No updates available"
level = syslog.LOG_DEBUG
syslog.syslog(self._levelMap(level), msg)
def _levelMap(self, lvl):
level_map = { "EMERG": syslog.LOG_EMERG,
"ALERT": syslog.LOG_ALERT,
"CRIT": syslog.LOG_CRIT,
"ERR": syslog.LOG_ERR,
"WARN": syslog.LOG_WARNING,
"NOTICE": syslog.LOG_NOTICE,
"INFO": syslog.LOG_INFO,
"DEBUG": syslog.LOG_DEBUG }
if type(lvl) == int:
return lvl
if level_map.has_key(lvl.upper()):
return level_map[lvl.upper()]
return syslog.LOG_INFO
def _facilityMap(self, facility):
facility_map = { "KERN": syslog.LOG_KERN,
"USER": syslog.LOG_USER,
"MAIL": syslog.LOG_MAIL,
"DAEMON": syslog.LOG_DAEMON,
"AUTH": syslog.LOG_AUTH,
"LPR": syslog.LOG_LPR,
"NEWS": syslog.LOG_NEWS,
"UUCP": syslog.LOG_UUCP,
"CRON": syslog.LOG_CRON,
"LOCAL0": syslog.LOG_LOCAL0,
"LOCAL1": syslog.LOG_LOCAL1,
"LOCAL2": syslog.LOG_LOCAL2,
"LOCAL3": syslog.LOG_LOCAL3,
"LOCAL4": syslog.LOG_LOCAL4,
"LOCAL5": syslog.LOG_LOCAL5,
"LOCAL6": syslog.LOG_LOCAL6,
"LOCAL7": syslog.LOG_LOCAL7,}
if type(facility) == int:
return facility
elif facility_map.has_key(facility.upper()):
return facility_map[facility.upper()]
return syslog.LOG_DAEMON
class EmailUpdateEmitter(UpdateEmitter):
def __init__(self, sender, rcpts, smtp_server, sendmail):
UpdateEmitter.__init__(self)
self.sender = sender
self.rcpts = rcpts
self.smtp_server = smtp_server
self.sendmail = sendmail
def _msgGreeting(self):
output = """Hi,
This is the automatic update system on %s.
""" % socket.gethostname()
return output
def _msgFooter(self):
output = """
Thank You,
Your Computer
"""
return output
def _msgPacketList(self, updateInfo):
output = ""
for package in updateInfo:
if package[0].has_key('type'):
output += " %-30s %-10s\n" % (package[0]['name'], package[0]['type'])
else:
output += " %-30s\n" % (package[0]['name'])
return output
def _sendMessage(self, subject, body):
msg = MIMEText(body)
msg['Subject'] = "yum: %s (on %s) " % (subject, socket.gethostname())
msg['From'] = self.sender
msg['To'] = self.rcpts
msg['Date'] = rfc822.formatdate()
if self.sendmail:
p = subprocess.Popen(("/usr/sbin/sendmail", "-t"), stdin=subprocess.PIPE)
p.stdin.write(str(msg))
p.communicate()
else:
s = smtplib.SMTP()
if self.smtp_server:
s.connect(self.smtp_server)
else:
s.connect()
s.sendmail(self.sender, self.rcpts.split(','), msg.as_string())
s.close()
def updatesAvailable(self, updateInfo):
num = len(updateInfo)
if num < 1:
return
output = self._msgGreeting()
output += """There are %d package updates available. Please run the system updater.
Packages available for update:
""" % num
output += self._msgPacketList(updateInfo)
output += self._msgFooter()
self._sendMessage("%d Updates Available" % num, output)
def updatesApplied(self, updateInfo):
num = len(updateInfo)
if num < 1:
return
output = self._msgGreeting()
output += """The system successfully installed/updated %d packages.
Packages installed or updated:
""" % num
output += self._msgPacketList(updateInfo)
output += self._msgFooter()
self._sendMessage("%d packets installed/updated" % num, output)
def updatesFailed(self, errmsgs):
output = self._msgGreeting()
output += """There was a problem updating the system. The following error message
was reported:
%s
If the problem persists, manual intervention may be required.""" % errmsgs
output += self._msgFooter()
self._sendMessage("problem updating system", output)
class DbusUpdateEmitter(UpdateEmitter):
def __init__(self):
UpdateEmitter.__init__(self)
bus = dbus.SystemBus()
name = dbus.service.BusName('edu.duke.linux.yum', bus = bus)
yum_dbus = YumDbusInterface(name)
self.dbusintf = yum_dbus
def updatesAvailable(self, updateInfo):
num = len(updateInfo)
msg = "%d" %(num,)
if num > 0:
self.dbusintf.UpdatesAvailableSignal(msg)
else:
self.dbusintf.NoUpdatesAvailableSignal(msg)
def updatesFailed(self, errmsgs):
self.dbusintf.UpdatesFailedSignal(errmsgs)
def updatesApplied(self, updinfo):
self.dbusintf.UpdatesAppliedSignal(updinfo)
def checkFailed(self, error):
self.dbusintf.CheckFailedSignal(error)
def setupFailed(self, error, translation_domain):
self.dbusintf.SetupFailedSignal(error, translation_domain)
def updateInfo(self, upd):
self.dbusintf.UpdateInfoSignal(upd)
def locked(self, error):
self.dbusintf.LockedSignal(error)
class StderrUpdateEmitter(UpdateEmitter):
def __init__(self):
UpdateEmitter.__init__(self)
def updatesAvailable(self, updateInfo):
print >> sys.stderr, "%d updates available" %(len(updateInfo),)
def updatesFailed(self, errmsgs):
print >> sys.stderr, "Updates failed: %s" %(errmsgs,)
def updatesApplied(self, updinfo):
print >> sys.stderr, "Updates applied successfully: %s" %(updinfo)
def checkFailed(self, error):
print >> sys.stderr, "Check for updates failed: %s" %(error,)
def setupFailed(self, error, translation_domain):
print >> sys.stderr, "Update setup failed: %s" %(error,)
def updateInfo(self, upd):
print >> sys.stderr, "Update available: %s" %(upd,)
def locked(self, error):
print >> sys.stderr, "Yum lock already held: %s" %(error,)
class YumDbusInterface(dbus.service.Object):
def __init__(self, bus_name, object_path='/UpdatesAvail'):
dbus.service.Object.__init__(self, bus_name, object_path)
@dbus.service.signal('edu.duke.linux.yum')
def UpdatesAvailableSignal(self, message):
pass
@dbus.service.signal('edu.duke.linux.yum')
def NoUpdatesAvailableSignal(self, message):
pass
@dbus.service.signal('edu.duke.linux.yum')
def UpdatesFailedSignal(self, errmsgs):
pass
@dbus.service.signal('edu.duke.linux.yum')
def UpdatesAppliedSignal(self, updinfo):
pass
@dbus.service.signal('edu.duke.linux.yum')
def CheckFailedSignal(self, message):
pass
@dbus.service.signal('edu.duke.linux.yum')
def SetupFailedSignal(self, message, translation_domain=""):
pass
@dbus.service.signal('edu.duke.linux.yum')
def UpdateInfoSignal(self, message):
pass
@dbus.service.signal('edu.duke.linux.yum')
def LockedSignal(self, message):
pass
class UpdatesDaemon(yum.YumBase):
def __init__(self, options):
yum.YumBase.__init__(self)
self.options = options
self.emitters = []
if self.options.dbus:
self.emitters.append(DbusUpdateEmitter())
if self.options.email:
self.emitters.append(EmailUpdateEmitter(self.options.emailfrom,
self.options.emailto,
self.options.smtpserver,
self.options.sendmail))
if self.options.syslog:
self.emitters.append(SyslogUpdateEmitter(self.options.logfacility,
self.options.logident,
self.options.loglevel))
if self.options.debug:
self.emitters.append(StderrUpdateEmitter())
self.updateInfo = []
def doSetup(self):
try:
self.doConfigSetup(fn=self.options.config)
except Exception, e:
syslog.syslog(syslog.LOG_WARNING,
"error initializing: %s" % e)
if isinstance(e, yum.plugins.PluginYumExit):
self.emitSetupFailed(e.value, e.translation_domain)
else:
# if we don't know where the string is from, then assume
# it's not marked for translation (versus sending
# gettext.textdomain() and assuming it's from the default
# domain for this app)
self.emitSetupFailed(str(e))
# Override the metadata expire, because there's no point running
# yum-updatesd-helper if we use cached metadata
for repo in self.repos.listEnabled():
repo.metadata_expire = 1
def doLock(self):
try:
yum.YumBase.doLock(self, YUM_PID_FILE)
except yum.Errors.LockError, e:
self.emitLocked("%s" %(e,))
sys.exit(1)
def refreshMetadata(self):
self.doLock()
try:
self.doRepoSetup()
self.doSackSetup()
self.doTsSetup()
self.doRpmDBSetup()
self.doUpdateSetup()
try:
self.doGroupSetup()
except yum.Errors.GroupsError:
pass
except Exception, e:
syslog.syslog(syslog.LOG_WARNING,
"error getting update info: %s" %(e,))
self.emitCheckFailed("%s" %(e,))
self.doUnlock(YUM_PID_FILE)
return False
return True
def populateUpdateMetadata(self):
self.updateMetadata = UpdateMetadata()
repos = []
for (new, old) in self.up.getUpdatesTuples():
pkg = self.getPackageObject(new)
if pkg.repoid not in repos:
repo = self.repos.getRepo(pkg.repoid)
repos.append(repo.id)
try: # grab the updateinfo.xml.gz from the repodata
md = repo.retrieveMD('updateinfo')
except Exception: # can't find any; silently move on
continue
md = gzip.open(md)
self.updateMetadata.add(md)
md.close()
def populateUpdates(self):
def getDbusPackageDict(pkg):
"""Returns a dictionary corresponding to the package object
in the form that we can send over the wire for dbus."""
pkgDict = {
"name": pkg.returnSimple("name"),
"version": pkg.returnSimple("version"),
"release": pkg.returnSimple("release"),
"epoch": pkg.returnSimple("epoch"),
"arch": pkg.returnSimple("arch"),
"sourcerpm": pkg.returnSimple("sourcerpm"),
"summary": pkg.returnSimple("summary") or "",
}
# check if any updateinfo is available
md = self.updateMetadata.get_notice((pkg.name, pkg.ver, pkg.rel))
if md:
# right now we only want to know if it is a security update
pkgDict['type'] = md['type']
return pkgDict
if self.up is None:
# we're _only_ called after updates are setup
return
self.populateUpdateMetadata()
self.updateInfo = []
for (new, old) in self.up.getUpdatesTuples():
updating = self.getPackageObject(new)
updated = self.rpmdb.searchPkgTuple(old)[0]
self.tsInfo.addUpdate(updating, updated)
n = getDbusPackageDict(updating)
o = getDbusPackageDict(updated)
self.updateInfo.append((n, o))
if self.conf.obsoletes:
for (obs, inst) in self.up.getObsoletesTuples():
obsoleting = self.getPackageObject(obs)
installed = self.rpmdb.searchPkgTuple(inst)[0]
self.tsInfo.addObsoleting(obsoleting, installed)
self.tsInfo.addObsoleted(installed, obsoleting)
n = getDbusPackageDict(obsoleting)
o = getDbusPackageDict(installed)
self.updateInfo.append((n, o))
def refreshUpdates(self):
try:
self.populateUpdates()
except Exception, e:
self.emitCheckFailed("%s" %(e,))
return False
return True
def findDeps(self):
try:
(res, resmsg) = self.buildTransaction()
except yum.Errors.RepoError, e:
self.emitCheckFailed("%s" %(e,))
return False
if res != 2:
self.emitUpdateFailed("Failed to build transaction: %s" %(str.join("\n", resmsg),))
return False
return True
def downloadUpdates(self):
dlpkgs = map(lambda x: x.po, filter(lambda txmbr:
txmbr.ts_state in ("i", "u"),
self.tsInfo.getMembers()))
try:
self.downloadPkgs(dlpkgs)
except (yum.Errors.RepoError, IndexError), e:
self.emitCheckFailed("%s" %(e,))
return False
return True
def installUpdates(self):
dlpkgs = map(lambda x: x.po, filter(lambda txmbr:
txmbr.ts_state in ("i", "u"),
self.tsInfo.getMembers()))
for po in dlpkgs:
result, err = self.sigCheckPkg(po)
if result == 0:
continue
elif result == 1:
try:
self.getKeyForPackage(po)
except yum.Errors.YumBaseError, errmsg:
self.emitUpdateFailed([str(errmsg)])
return False
del self.ts
self.initActionTs() # make a new, blank ts to populate
self.populateTs(keepold=0)
self.ts.check() #required for ordering
self.ts.order() # order
cb = callback.RPMInstallCallback(output = 0)
cb.filelog = True
cb.tsInfo = self.tsInfo
try:
self.runTransaction(cb=cb)
except yum.Errors.YumBaseError, err:
self.emitUpdateFailed([str(err)])
return False
self.emitUpdateApplied()
return True
def releaseLocks(self):
self.closeRpmDB()
self.doUnlock(YUM_PID_FILE)
def emitAvailable(self):
"""method to emit a notice about updates"""
map(lambda x: self.emitUpdateInfo(x), self.updateInfo)
map(lambda x: x.updatesAvailable(self.updateInfo), self.emitters)
def emitDownloading(self):
"""method to emit a notice about updates downloading"""
map(lambda x: x.updatesDownloading(self.updateInfo), self.emitters)
def emitInstalling(self):
"""method to emit a notice about updates installing"""
map(lambda x: x.updatesInstalling(self.updateInfo), self.emitters)
def emitUpdateApplied(self):
"""method to emit a notice when automatic updates applied"""
map(lambda x: x.updatesApplied(self.updateInfo), self.emitters)
def emitUpdateFailed(self, errmsgs):
"""method to emit a notice when automatic updates failed"""
map(lambda x: x.updatesFailed(errmsgs), self.emitters)
def emitCheckFailed(self, error):
"""method to emit a notice when checking for updates failed"""
map(lambda x: x.checkFailed(error), self.emitters)
def emitSetupFailed(self, error, translation_domain=""):
"""method to emit a notice when checking for updates failed"""
map(lambda x: x.setupFailed(error, translation_domain), self.emitters)
def emitUpdateInfo(self, update):
"""method to emit information about an available update"""
map(lambda x: x.updateInfo(update), self.emitters)
def emitLocked(self, error):
"""method to emit to say that something has the yum lock"""
map(lambda x: x.locked(error), self.emitters)
def main(options = None):
if options is None:
parser = OptionParser()
# options around what actions to do
parser.add_option("-c", "--check", action="store_true", default=False, dest="check")
parser.add_option("-d", "--download", action="store_true", default=False, dest="download")
parser.add_option("", "--deps", action="store_true", default=False, dest="finddeps")
parser.add_option("-a", "--apply", action="store_true", default=False, dest="apply")
parser.add_option("", "--network-fail", action="store_true", default=False, dest="netfail")
# debugging, different configs
parser.add_option("", "--debug", action="store_true", default=False, dest="debug")
parser.add_option("", "--config", type="string", default="/etc/yum.conf", dest="config")
## options for how to emit and config for each
parser.add_option("", "--dbus", action="store_true", default=False, dest="dbus")
# syslog
parser.add_option("", "--syslog", action="store_true", default=False, dest="syslog")
parser.add_option("", "--syslog-facility", type="string", default="DAEMON", dest="logfacility")
parser.add_option("", "--syslog-level", type="string", default="WARN", dest="loglevel")
parser.add_option("", "--syslog-ident", type="string", default="yum-updatesd", dest="logident")
# email
parser.add_option("", "--email", action="store_true", default=False, dest="email")
parser.add_option("", "--email-from", type="string", default="root", dest="emailfrom")
parser.add_option("", "--email-to", type="string", default="root", dest="emailto")
parser.add_option("", "--smtp-server", type="string", default="localhost:25", dest="smtpserver")
parser.add_option("", "--sendmail", action="store_true", default=False, dest="sendmail")
(options, args) = parser.parse_args()
syslog.openlog("yum-updatesd-helper", 0, syslog.LOG_DAEMON)
updd = UpdatesDaemon(options)
if options.netfail:
updd.emitCheckFailed("No network available")
sys.exit(1)
updd.doSetup()
if not updd.refreshMetadata():
sys.exit(1)
# set up some reasonable things
if options.apply:
options.download = options.finddeps = options.check = True
if options.finddeps:
options.download = options.check = True
if options.download:
options.check = True
# do the check if so configured
if not options.check:
sys.exit(0)
if not updd.refreshUpdates():
sys.exit(1)
# download if set up to do so, else tell about the updates and exit
if not options.download or len(updd.updateInfo) == 0:
updd.emitAvailable()
updd.releaseLocks()
sys.exit(0)
updd.emitDownloading()
if options.finddeps:
if not updd.findDeps():
sys.exit(1)
if not updd.downloadUpdates():
sys.exit(1)
# now apply if we're set up to do so; else just tell that things are
# available
if not options.apply:
updd.emitAvailable()
updd.releaseLocks()
sys.exit(0)
if not updd.installUpdates():
sys.exit(1)
sys.exit(0)
if __name__ == "__main__":
main()
bool(false)
|