Package yum :: Module rpmtrans
[hide private]
[frames] | no frames]

Source Code for Module yum.rpmtrans

  1  #!/usr/bin/python -t 
  2  # This program is free software; you can redistribute it and/or modify 
  3  # it under the terms of the GNU General Public License as published by 
  4  # the Free Software Foundation; either version 2 of the License, or 
  5  # (at your option) any later version. 
  6  # 
  7  # This program is distributed in the hope that it will be useful, 
  8  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
  9  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 10  # GNU Library General Public License for more details. 
 11  # 
 12  # You should have received a copy of the GNU General Public License 
 13  # along with this program; if not, write to the Free Software 
 14  # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 
 15  # Copyright 2005 Duke University 
 16  # Parts Copyright 2007 Red Hat, Inc 
 17   
 18   
 19  import rpm 
 20  import os 
 21  import fcntl 
 22  import time 
 23  import logging 
 24  import types 
 25  import sys 
 26  from yum.constants import * 
 27  from yum import _ 
 28  import misc 
 29  import tempfile 
 30   
31 -class NoOutputCallBack:
32 - def __init__(self):
33 pass
34
35 - def event(self, package, action, te_current, te_total, ts_current, ts_total):
36 """ 37 @param package: A yum package object or simple string of a package name 38 @param action: A yum.constant transaction set state or in the obscure 39 rpm repackage case it could be the string 'repackaging' 40 @param te_current: current number of bytes processed in the transaction 41 element being processed 42 @param te_total: total number of bytes in the transaction element being 43 processed 44 @param ts_current: number of processes completed in whole transaction 45 @param ts_total: total number of processes in the transaction. 46 """ 47 # this is where a progress bar would be called 48 49 pass
50
51 - def scriptout(self, package, msgs):
52 """package is the package. msgs is the messages that were 53 output (if any).""" 54 pass
55
56 - def errorlog(self, msg):
57 """takes a simple error msg string""" 58 59 pass
60
61 - def filelog(self, package, action):
62 # check package object type - if it is a string - just output it 63 """package is the same as in event() - a package object or simple string 64 action is also the same as in event()""" 65 pass
66
67 -class RPMBaseCallback:
68 ''' 69 Base class for a RPMTransaction display callback class 70 '''
71 - def __init__(self):
72 self.action = { TS_UPDATE : _('Updating'), 73 TS_ERASE: _('Erasing'), 74 TS_INSTALL: _('Installing'), 75 TS_TRUEINSTALL : _('Installing'), 76 TS_OBSOLETED: _('Obsoleted'), 77 TS_OBSOLETING: _('Installing'), 78 TS_UPDATED: _('Cleanup'), 79 'repackaging': _('Repackaging')} 80 # The fileaction are not translated, most sane IMHO / Tim 81 self.fileaction = { TS_UPDATE: 'Updated', 82 TS_ERASE: 'Erased', 83 TS_INSTALL: 'Installed', 84 TS_TRUEINSTALL: 'Installed', 85 TS_OBSOLETED: 'Obsoleted', 86 TS_OBSOLETING: 'Installed', 87 TS_UPDATED: 'Cleanup'} 88 self.logger = logging.getLogger('yum.filelogging.RPMInstallCallback')
89
90 - def event(self, package, action, te_current, te_total, ts_current, ts_total):
91 """ 92 @param package: A yum package object or simple string of a package name 93 @param action: A yum.constant transaction set state or in the obscure 94 rpm repackage case it could be the string 'repackaging' 95 @param te_current: Current number of bytes processed in the transaction 96 element being processed 97 @param te_total: Total number of bytes in the transaction element being 98 processed 99 @param ts_current: number of processes completed in whole transaction 100 @param ts_total: total number of processes in the transaction. 101 """ 102 raise NotImplementedError()
103
104 - def scriptout(self, package, msgs):
105 """package is the package. msgs is the messages that were 106 output (if any).""" 107 pass
108
109 - def errorlog(self, msg):
110 # FIXME this should probably dump to the filelog, too 111 print >> sys.stderr, msg
112
113 - def filelog(self, package, action):
114 # If the action is not in the fileaction list then dump it as a string 115 # hurky but, sadly, not much else 116 if action in self.fileaction: 117 msg = '%s: %s' % (self.fileaction[action], package) 118 else: 119 msg = '%s: %s' % (package, action) 120 self.logger.info(msg)
121 122
123 -class SimpleCliCallBack(RPMBaseCallback):
124 - def __init__(self):
125 RPMBaseCallback.__init__(self) 126 self.lastmsg = None 127 self.lastpackage = None # name of last package we looked at
128
129 - def event(self, package, action, te_current, te_total, ts_current, ts_total):
130 # this is where a progress bar would be called 131 msg = '%s: %s %s/%s [%s/%s]' % (self.action[action], package, 132 te_current, te_total, ts_current, ts_total) 133 if msg != self.lastmsg: 134 print msg 135 self.lastmsg = msg 136 self.lastpackage = package
137
138 - def scriptout(self, package, msgs):
139 if msgs: 140 print msgs,
141 142 # This is ugly, but atm. rpm can go insane and run the "cleanup" phase 143 # without the "install" phase if it gets an exception in it's callback. The 144 # following means that we don't really need to know/care about that in the 145 # display callback functions. 146 # Note try/except's in RPMTransaction are for the same reason.
147 -class _WrapNoExceptions:
148 - def __init__(self, parent):
149 self.__parent = parent
150
151 - def __getattr__(self, name):
152 """ Wraps all access to the parent functions. This is so it'll eat all 153 exceptions because rpm doesn't like exceptions in the callback. """ 154 func = getattr(self.__parent, name) 155 156 def newFunc(*args, **kwargs): 157 try: 158 func(*args, **kwargs) 159 except: 160 pass
161 162 newFunc.__name__ = func.__name__ 163 newFunc.__doc__ = func.__doc__ 164 newFunc.__dict__.update(func.__dict__) 165 return newFunc
166
167 -class RPMTransaction:
168 - def __init__(self, base, test=False, display=NoOutputCallBack):
169 if not callable(display): 170 self.display = display 171 else: 172 self.display = display() # display callback 173 self.display = _WrapNoExceptions(self.display) 174 self.base = base # base yum object b/c we need so much 175 self.test = test # are we a test? 176 self.trans_running = False 177 self.filehandles = {} 178 self.total_actions = 0 179 self.total_installed = 0 180 self.complete_actions = 0 181 self.installed_pkg_names = [] 182 self.total_removed = 0 183 self.logger = logging.getLogger('yum.filelogging.RPMInstallCallback') 184 self.filelog = False 185 186 self._setupOutputLogging(base.conf.rpmverbosity) 187 if not os.path.exists(self.base.conf.persistdir): 188 os.makedirs(self.base.conf.persistdir) # make the dir, just in case
189 190 # Error checking? -- these should probably be where else
191 - def _fdSetNonblock(self, fd):
192 """ Set the Non-blocking flag for a filedescriptor. """ 193 flag = os.O_NONBLOCK 194 current_flags = fcntl.fcntl(fd, fcntl.F_GETFL) 195 if current_flags & flag: 196 return 197 fcntl.fcntl(fd, fcntl.F_SETFL, current_flags | flag)
198
199 - def _fdSetCloseOnExec(self, fd):
200 """ Set the close on exec. flag for a filedescriptor. """ 201 flag = fcntl.FD_CLOEXEC 202 current_flags = fcntl.fcntl(fd, fcntl.F_GETFD) 203 if current_flags & flag: 204 return 205 fcntl.fcntl(fd, fcntl.F_SETFD, current_flags | flag)
206
207 - def _setupOutputLogging(self, rpmverbosity="info"):
208 # UGLY... set up the transaction to record output from scriptlets 209 io_r = tempfile.NamedTemporaryFile() 210 self._readpipe = io_r 211 self._writepipe = open(io_r.name, 'w+b') 212 # This is dark magic, it really needs to be "base.ts.ts". 213 self.base.ts.ts.scriptFd = self._writepipe.fileno() 214 rpmverbosity = {'critical' : 'crit', 215 'emergency' : 'emerg', 216 'error' : 'err', 217 'information' : 'info', 218 'warn' : 'warning'}.get(rpmverbosity, rpmverbosity) 219 rpmverbosity = 'RPMLOG_' + rpmverbosity.upper() 220 if not hasattr(rpm, rpmverbosity): 221 rpmverbosity = 'RPMLOG_INFO' 222 rpm.setVerbosity(getattr(rpm, rpmverbosity)) 223 rpm.setLogFile(self._writepipe)
224
225 - def _shutdownOutputLogging(self):
226 # reset rpm bits from reording output 227 rpm.setVerbosity(rpm.RPMLOG_NOTICE) 228 rpm.setLogFile(sys.stderr) 229 try: 230 self._writepipe.close() 231 except: 232 pass
233
234 - def _scriptOutput(self):
235 try: 236 out = self._readpipe.read() 237 if not out: 238 return None 239 return out 240 except IOError: 241 pass
242
243 - def _scriptout(self, data):
244 msgs = self._scriptOutput() 245 self.display.scriptout(data, msgs) 246 self.base.history.log_scriptlet_output(data, msgs)
247
248 - def __del__(self):
250
251 - def _dopkgtup(self, hdr):
252 tmpepoch = hdr['epoch'] 253 if tmpepoch is None: epoch = '0' 254 else: epoch = str(tmpepoch) 255 256 return (hdr['name'], hdr['arch'], epoch, hdr['version'], hdr['release'])
257
258 - def _makeHandle(self, hdr):
259 handle = '%s:%s.%s-%s-%s' % (hdr['epoch'], hdr['name'], hdr['version'], 260 hdr['release'], hdr['arch']) 261 262 return handle
263
264 - def ts_done(self, package, action):
265 """writes out the portions of the transaction which have completed""" 266 267 if self.test: return 268 269 if not hasattr(self, '_ts_done'): 270 self.ts_done_fn = '%s/transaction-done.%s' % (self.base.conf.persistdir, self._ts_time) 271 272 try: 273 self._ts_done = open(self.ts_done_fn, 'w') 274 except (IOError, OSError), e: 275 self.display.errorlog('could not open ts_done file: %s' % e) 276 return 277 self._fdSetCloseOnExec(self._ts_done.fileno()) 278 279 # walk back through self._te_tuples 280 # make sure the package and the action make some kind of sense 281 # write it out and pop(0) from the list 282 283 # make sure we have a list to work from 284 if len(self._te_tuples) == 0: 285 # if we don't then this is pretrans or postrans or a trigger 286 # either way we have to respond correctly so just return and don't 287 # emit anything 288 return 289 290 (t,e,n,v,r,a) = self._te_tuples[0] # what we should be on 291 292 # make sure we're in the right action state 293 msg = 'ts_done state is %s %s should be %s %s' % (package, action, t, n) 294 if action in TS_REMOVE_STATES: 295 if t != 'erase': 296 self.display.filelog(package, msg) 297 if action in TS_INSTALL_STATES: 298 if t != 'install': 299 self.display.filelog(package, msg) 300 301 # check the pkg name out to make sure it matches 302 if type(package) in types.StringTypes: 303 name = package 304 else: 305 name = package.name 306 307 if n != name: 308 msg = 'ts_done name in te is %s should be %s' % (n, package) 309 self.display.filelog(package, msg) 310 311 # hope springs eternal that this isn't wrong 312 msg = '%s %s:%s-%s-%s.%s\n' % (t,e,n,v,r,a) 313 314 try: 315 self._ts_done.write(msg) 316 self._ts_done.flush() 317 except (IOError, OSError), e: 318 # Having incomplete transactions is probably worse than having 319 # nothing. 320 del self._ts_done 321 misc.unlink_f(self.ts_done_fn) 322 self._te_tuples.pop(0)
323
324 - def ts_all(self):
325 """write out what our transaction will do""" 326 327 # save the transaction elements into a list so we can run across them 328 if not hasattr(self, '_te_tuples'): 329 self._te_tuples = [] 330 331 for te in self.base.ts: 332 n = te.N() 333 a = te.A() 334 v = te.V() 335 r = te.R() 336 e = te.E() 337 if e is None: 338 e = '0' 339 if te.Type() == 1: 340 t = 'install' 341 elif te.Type() == 2: 342 t = 'erase' 343 else: 344 t = te.Type() 345 346 # save this in a list 347 self._te_tuples.append((t,e,n,v,r,a)) 348 349 # write to a file 350 self._ts_time = time.strftime('%Y-%m-%d.%H:%M.%S') 351 tsfn = '%s/transaction-all.%s' % (self.base.conf.persistdir, self._ts_time) 352 self.ts_all_fn = tsfn 353 # to handle us being inside a chroot at this point 354 # we hand back the right path to those 'outside' of the chroot() calls 355 # but we're using the right path inside. 356 if self.base.conf.installroot != '/': 357 tsfn = tsfn.replace(os.path.normpath(self.base.conf.installroot),'') 358 try: 359 if not os.path.exists(os.path.dirname(tsfn)): 360 os.makedirs(os.path.dirname(tsfn)) # make the dir, 361 fo = open(tsfn, 'w') 362 except (IOError, OSError), e: 363 self.display.errorlog('could not open ts_all file: %s' % e) 364 return 365 366 try: 367 for (t,e,n,v,r,a) in self._te_tuples: 368 msg = "%s %s:%s-%s-%s.%s\n" % (t,e,n,v,r,a) 369 fo.write(msg) 370 fo.flush() 371 fo.close() 372 except (IOError, OSError), e: 373 # Having incomplete transactions is probably worse than having 374 # nothing. 375 misc.unlink_f(tsfn)
376
377 - def callback( self, what, bytes, total, h, user ):
378 if what == rpm.RPMCALLBACK_TRANS_START: 379 self._transStart( bytes, total, h ) 380 elif what == rpm.RPMCALLBACK_TRANS_PROGRESS: 381 self._transProgress( bytes, total, h ) 382 elif what == rpm.RPMCALLBACK_TRANS_STOP: 383 self._transStop( bytes, total, h ) 384 elif what == rpm.RPMCALLBACK_INST_OPEN_FILE: 385 return self._instOpenFile( bytes, total, h ) 386 elif what == rpm.RPMCALLBACK_INST_CLOSE_FILE: 387 self._instCloseFile( bytes, total, h ) 388 elif what == rpm.RPMCALLBACK_INST_PROGRESS: 389 self._instProgress( bytes, total, h ) 390 elif what == rpm.RPMCALLBACK_UNINST_START: 391 self._unInstStart( bytes, total, h ) 392 elif what == rpm.RPMCALLBACK_UNINST_PROGRESS: 393 self._unInstProgress( bytes, total, h ) 394 elif what == rpm.RPMCALLBACK_UNINST_STOP: 395 self._unInstStop( bytes, total, h ) 396 elif what == rpm.RPMCALLBACK_REPACKAGE_START: 397 self._rePackageStart( bytes, total, h ) 398 elif what == rpm.RPMCALLBACK_REPACKAGE_STOP: 399 self._rePackageStop( bytes, total, h ) 400 elif what == rpm.RPMCALLBACK_REPACKAGE_PROGRESS: 401 self._rePackageProgress( bytes, total, h ) 402 elif what == rpm.RPMCALLBACK_CPIO_ERROR: 403 self._cpioError(bytes, total, h) 404 elif what == rpm.RPMCALLBACK_UNPACK_ERROR: 405 self._unpackError(bytes, total, h) 406 # SCRIPT_ERROR is only in rpm >= 4.6.0 407 elif hasattr(rpm, "RPMCALLBACK_SCRIPT_ERROR") and what == rpm.RPMCALLBACK_SCRIPT_ERROR: 408 self._scriptError(bytes, total, h)
409 410
411 - def _transStart(self, bytes, total, h):
412 if bytes == 6: 413 self.total_actions = total 414 if self.test: return 415 self.trans_running = True 416 self.ts_all() # write out what transaction will do
417
418 - def _transProgress(self, bytes, total, h):
419 pass
420
421 - def _transStop(self, bytes, total, h):
422 pass
423
424 - def _instOpenFile(self, bytes, total, h):
425 self.lastmsg = None 426 hdr = None 427 if h is not None: 428 hdr, rpmloc = h[0], h[1] 429 handle = self._makeHandle(hdr) 430 try: 431 fd = os.open(rpmloc, os.O_RDONLY) 432 except OSError, e: 433 self.display.errorlog("Error: Cannot open file %s: %s" % (rpmloc, e)) 434 else: 435 self.filehandles[handle]=fd 436 if self.trans_running: 437 self.total_installed += 1 438 self.complete_actions += 1 439 self.installed_pkg_names.append(hdr['name']) 440 return fd 441 else: 442 self.display.errorlog("Error: No Header to INST_OPEN_FILE")
443
444 - def _instCloseFile(self, bytes, total, h):
445 hdr = None 446 if h is not None: 447 hdr, rpmloc = h[0], h[1] 448 handle = self._makeHandle(hdr) 449 os.close(self.filehandles[handle]) 450 fd = 0 451 if self.test: return 452 if self.trans_running: 453 pkgtup = self._dopkgtup(hdr) 454 txmbrs = self.base.tsInfo.getMembers(pkgtup=pkgtup) 455 for txmbr in txmbrs: 456 self.display.filelog(txmbr.po, txmbr.output_state) 457 self._scriptout(txmbr.po) 458 # NOTE: We only do this for install, not erase atm. 459 # because we don't get pkgtup data for erase (this 460 # includes "Updated" pkgs). 461 pid = self.base.history.pkg2pid(txmbr.po) 462 state = self.base.history.txmbr2state(txmbr) 463 self.base.history.trans_data_pid_end(pid, state) 464 self.ts_done(txmbr.po, txmbr.output_state)
465
466 - def _instProgress(self, bytes, total, h):
467 if h is not None: 468 # If h is a string, we're repackaging. 469 # Why the RPMCALLBACK_REPACKAGE_PROGRESS flag isn't set, I have no idea 470 if type(h) == type(""): 471 self.display.event(h, 'repackaging', bytes, total, 472 self.complete_actions, self.total_actions) 473 474 else: 475 hdr, rpmloc = h[0], h[1] 476 pkgtup = self._dopkgtup(hdr) 477 txmbrs = self.base.tsInfo.getMembers(pkgtup=pkgtup) 478 for txmbr in txmbrs: 479 action = txmbr.output_state 480 self.display.event(txmbr.po, action, bytes, total, 481 self.complete_actions, self.total_actions)
482 - def _unInstStart(self, bytes, total, h):
483 pass
484
485 - def _unInstProgress(self, bytes, total, h):
486 pass
487
488 - def _unInstStop(self, bytes, total, h):
489 self.total_removed += 1 490 self.complete_actions += 1 491 if h not in self.installed_pkg_names: 492 self.display.filelog(h, TS_ERASE) 493 action = TS_ERASE 494 else: 495 action = TS_UPDATED 496 497 self.display.event(h, action, 100, 100, self.complete_actions, 498 self.total_actions) 499 self._scriptout(h) 500 501 if self.test: return # and we're done 502 self.ts_done(h, action)
503 504
505 - def _rePackageStart(self, bytes, total, h):
506 pass
507
508 - def _rePackageStop(self, bytes, total, h):
509 pass
510
511 - def _rePackageProgress(self, bytes, total, h):
512 pass
513
514 - def _cpioError(self, bytes, total, h):
515 hdr, rpmloc = h[0], h[1] 516 pkgtup = self._dopkgtup(hdr) 517 txmbrs = self.base.tsInfo.getMembers(pkgtup=pkgtup) 518 for txmbr in txmbrs: 519 msg = "Error in cpio payload of rpm package %s" % txmbr.po 520 txmbr.output_state = TS_FAILED 521 self.display.errorlog(msg)
522 # FIXME - what else should we do here? raise a failure and abort? 523
524 - def _unpackError(self, bytes, total, h):
525 hdr, rpmloc = h[0], h[1] 526 pkgtup = self._dopkgtup(hdr) 527 txmbrs = self.base.tsInfo.getMembers(pkgtup=pkgtup) 528 for txmbr in txmbrs: 529 txmbr.output_state = TS_FAILED 530 msg = "Error unpacking rpm package %s" % txmbr.po 531 self.display.errorlog(msg)
532 # FIXME - should we raise? I need a test case pkg to see what the 533 # right behavior should be 534
535 - def _scriptError(self, bytes, total, h):
536 if not isinstance(h, types.TupleType): 537 # fun with install/erase transactions, see rhbz#484729 538 h = (h, None) 539 hdr, rpmloc = h[0], h[1] 540 remove_hdr = False # if we're in a clean up/remove then hdr will not be an rpm.hdr 541 if not isinstance(hdr, rpm.hdr): 542 txmbrs = [hdr] 543 remove_hdr = True 544 else: 545 pkgtup = self._dopkgtup(hdr) 546 txmbrs = self.base.tsInfo.getMembers(pkgtup=pkgtup) 547 548 for pkg in txmbrs: 549 # "bytes" carries the failed scriptlet tag, 550 # "total" carries fatal/non-fatal status 551 scriptlet_name = rpm.tagnames.get(bytes, "<unknown>") 552 if remove_hdr: 553 package_name = pkg 554 else: 555 package_name = pkg.po 556 557 if total: 558 msg = ("Error in %s scriptlet in rpm package %s" % 559 (scriptlet_name, package_name)) 560 if not remove_hdr: 561 pkg.output_state = TS_FAILED 562 else: 563 msg = ("Non-fatal %s scriptlet failure in rpm package %s" % 564 (scriptlet_name, package_name)) 565 self.display.errorlog(msg)
566 # FIXME - what else should we do here? raise a failure and abort? 567