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.
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.
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
26
from yum.constants import *
29
class NoOutputCallBack:
33
def event(self, package, action, te_current, te_total, ts_current, ts_total):
35
@param package: A yum package object or simple string of a package name
36
@param action: A yum.constant transaction set state or in the obscure
37
rpm repackage case it could be the string 'repackaging'
38
@param te_current: current number of bytes processed in the transaction
39
element being processed
40
@param te_total: total number of bytes in the transaction element being
42
@param ts_current: number of processes completed in whole transaction
43
@param ts_total: total number of processes in the transaction.
45
# this is where a progress bar would be called
49
def scriptout(self, package, msgs):
50
"""package is the package. msgs is the messages that were
54
def errorlog(self, msg):
55
"""takes a simple error msg string"""
59
def filelog(self, package, action):
60
# check package object type - if it is a string - just output it
61
"""package is the same as in event() - a package object or simple string
62
action is also the same as in event()"""
65
class RPMBaseCallback:
67
Base class for a RPMTransaction display callback class
70
self.action = { TS_UPDATE : 'Updating',
72
TS_INSTALL: 'Installing',
73
TS_TRUEINSTALL : 'Installing',
74
TS_OBSOLETED: 'Obsoleted',
75
TS_OBSOLETING: 'Installing',
76
TS_UPDATED: 'Cleanup',
77
'repackaging': 'Repackaging'}
78
self.fileaction = { TS_UPDATE: 'Updated',
80
TS_INSTALL: 'Installed',
81
TS_TRUEINSTALL: 'Installed',
82
TS_OBSOLETED: 'Obsoleted',
83
TS_OBSOLETING: 'Installed',
84
TS_UPDATED: 'Cleanup'}
85
self.logger = logging.getLogger('yum.filelogging.RPMInstallCallback')
87
def event(self, package, action, te_current, te_total, ts_current, ts_total):
89
@param package: A yum package object or simple string of a package name
90
@param action: A yum.constant transaction set state or in the obscure
91
rpm repackage case it could be the string 'repackaging'
92
@param te_current: Current number of bytes processed in the transaction
93
element being processed
94
@param te_total: Total number of bytes in the transaction element being
96
@param ts_current: number of processes completed in whole transaction
97
@param ts_total: total number of processes in the transaction.
99
raise NotImplementedError()
101
def scriptout(self, package, msgs):
102
"""package is the package. msgs is the messages that were
106
def errorlog(self, msg):
107
# FIXME this should probably dump to the filelog, too
108
print >> sys.stderr, msg
110
def filelog(self, package, action):
111
# If the action is not in the fileaction list then dump it as a string
112
# hurky but, sadly, not much else
113
if self.fileaction.has_key(action):
114
msg = '%s: %s' % (self.fileaction[action], package)
116
msg = '%s: %s' % (package, action)
117
self.logger.info(msg)
120
class SimpleCliCallBack(RPMBaseCallback):
122
RPMBaseCallback.__init__(self)
124
self.lastpackage = None # name of last package we looked at
126
def event(self, package, action, te_current, te_total, ts_current, ts_total):
127
# this is where a progress bar would be called
128
msg = '%s: %s %s/%s [%s/%s]' % (self.action[action], package,
129
te_current, te_total, ts_current, ts_total)
130
if msg != self.lastmsg:
133
self.lastpackage = package
135
def scriptout(self, package, msgs):
139
class RPMTransaction:
140
def __init__(self, base, test=False, display=NoOutputCallBack):
141
if not callable(display):
142
self.display = display
144
self.display = display() # display callback
145
self.base = base # base yum object b/c we need so much
146
self.test = test # are we a test?
147
self.trans_running = False
148
self.filehandles = {}
149
self.total_actions = 0
150
self.total_installed = 0
151
self.complete_actions = 0
152
self.installed_pkg_names = []
153
self.total_removed = 0
154
self.logger = logging.getLogger('yum.filelogging.RPMInstallCallback')
157
self._setupOutputLogging()
159
def _setupOutputLogging(self):
160
# UGLY... set up the transaction to record output from scriptlets
162
# need fd objects, and read should be non-blocking
163
self._readpipe = os.fdopen(r, 'r')
164
fcntl.fcntl(self._readpipe.fileno(), fcntl.F_SETFL,
165
fcntl.fcntl(self._readpipe.fileno(),
166
fcntl.F_GETFL) | os.O_NONBLOCK)
167
self._writepipe = os.fdopen(w, 'w')
168
self.base.ts.scriptFd = self._writepipe.fileno()
169
rpm.setVerbosity(rpm.RPMLOG_INFO)
170
rpm.setLogFile(self._writepipe)
172
def _shutdownOutputLogging(self):
173
# reset rpm bits from reording output
174
rpm.setVerbosity(rpm.RPMLOG_NOTICE)
175
rpm.setLogFile(sys.stderr)
177
self._writepipe.close()
181
def _scriptOutput(self):
183
out = self._readpipe.read()
189
self._shutdownOutputLogging()
191
def _dopkgtup(self, hdr):
192
tmpepoch = hdr['epoch']
193
if tmpepoch is None: epoch = '0'
194
else: epoch = str(tmpepoch)
196
return (hdr['name'], hdr['arch'], epoch, hdr['version'], hdr['release'])
198
def _makeHandle(self, hdr):
199
handle = '%s:%s.%s-%s-%s' % (hdr['epoch'], hdr['name'], hdr['version'],
200
hdr['release'], hdr['arch'])
204
def ts_done(self, package, action):
205
"""writes out the portions of the transaction which have completed"""
209
if not hasattr(self, '_ts_done'):
210
# need config variable to put this in a better path/name
211
te_fn = '%s/transaction-done.%s' % (self.base.conf.persistdir, self._ts_time)
212
self.ts_done_fn = te_fn
214
self._ts_done = open(te_fn, 'w')
215
except (IOError, OSError), e:
216
self.display.errorlog('could not open ts_done file: %s' % e)
219
# walk back through self._te_tuples
220
# make sure the package and the action make some kind of sense
221
# write it out and pop(0) from the list
223
# make sure we have a list to work from - rpm seems to be throwing us
225
if len(self._te_tuples) == 0:
226
msg = 'extra callback for package %s in state %d' % (package, action)
227
self.display.filelog(package, msg)
230
(t,e,n,v,r,a) = self._te_tuples[0] # what we should be on
232
# make sure we're in the right action state
233
msg = 'ts_done state is %s %s should be %s %s' % (package, action, t, n)
234
if action in TS_REMOVE_STATES:
236
self.display.filelog(package, msg)
237
if action in TS_INSTALL_STATES:
239
self.display.filelog(package, msg)
241
# check the pkg name out to make sure it matches
242
if type(package) in types.StringTypes:
248
msg = 'ts_done name in te is %s should be %s' % (n, package)
249
self.display.filelog(package, msg)
251
# hope springs eternal that this isn't wrong
252
msg = '%s %s:%s-%s-%s.%s\n' % (t,e,n,v,r,a)
254
self._ts_done.write(msg)
255
self._ts_done.flush()
256
self._te_tuples.pop(0)
259
"""write out what our transaction will do"""
261
# save the transaction elements into a list so we can run across them
262
if not hasattr(self, '_te_tuples'):
265
for te in self.base.ts:
280
# save this in a list
281
self._te_tuples.append((t,e,n,v,r,a))
284
self._ts_time = time.strftime('%Y-%m-%d.%H:%M.%S')
285
tsfn = '%s/transaction-all.%s' % (self.base.conf.persistdir, self._ts_time)
286
self.ts_all_fn = tsfn
288
# fixme - we should probably be making this elsewhere but I'd
289
# rather that the transaction not fail so we do it here, anyway
290
if not os.path.exists(self.base.conf.persistdir):
291
os.makedirs(self.base.conf.persistdir) # make the dir, just in case
294
except (IOError, OSError), e:
295
self.display.errorlog('could not open ts_all file: %s' % e)
299
for (t,e,n,v,r,a) in self._te_tuples:
300
msg = "%s %s:%s-%s-%s.%s\n" % (t,e,n,v,r,a)
305
def callback( self, what, bytes, total, h, user ):
306
if what == rpm.RPMCALLBACK_TRANS_START:
307
self._transStart( bytes, total, h )
308
elif what == rpm.RPMCALLBACK_TRANS_PROGRESS:
309
self._transProgress( bytes, total, h )
310
elif what == rpm.RPMCALLBACK_TRANS_STOP:
311
self._transStop( bytes, total, h )
312
elif what == rpm.RPMCALLBACK_INST_OPEN_FILE:
313
return self._instOpenFile( bytes, total, h )
314
elif what == rpm.RPMCALLBACK_INST_CLOSE_FILE:
315
self._instCloseFile( bytes, total, h )
316
elif what == rpm.RPMCALLBACK_INST_PROGRESS:
317
self._instProgress( bytes, total, h )
318
elif what == rpm.RPMCALLBACK_UNINST_START:
319
self._unInstStart( bytes, total, h )
320
elif what == rpm.RPMCALLBACK_UNINST_PROGRESS:
321
self._unInstProgress( bytes, total, h )
322
elif what == rpm.RPMCALLBACK_UNINST_STOP:
323
self._unInstStop( bytes, total, h )
324
elif what == rpm.RPMCALLBACK_REPACKAGE_START:
325
self._rePackageStart( bytes, total, h )
326
elif what == rpm.RPMCALLBACK_REPACKAGE_STOP:
327
self._rePackageStop( bytes, total, h )
328
elif what == rpm.RPMCALLBACK_REPACKAGE_PROGRESS:
329
self._rePackageProgress( bytes, total, h )
330
elif what == rpm.RPMCALLBACK_CPIO_ERROR:
331
self._cpioError(bytes, total, h)
332
elif what == rpm.RPMCALLBACK_UNPACK_ERROR:
333
self._unpackError(bytes, total, h)
336
def _transStart(self, bytes, total, h):
338
self.total_actions = total
340
self.trans_running = True
341
self.ts_all() # write out what transaction will do
343
def _transProgress(self, bytes, total, h):
346
def _transStop(self, bytes, total, h):
349
def _instOpenFile(self, bytes, total, h):
354
handle = self._makeHandle(hdr)
355
fd = os.open(rpmloc, os.O_RDONLY)
356
self.filehandles[handle]=fd
357
if self.trans_running:
358
self.total_installed += 1
359
self.complete_actions += 1
360
self.installed_pkg_names.append(hdr['name'])
363
self.display.errorlog("Error: No Header to INST_OPEN_FILE")
365
def _instCloseFile(self, bytes, total, h):
369
handle = self._makeHandle(hdr)
370
os.close(self.filehandles[handle])
373
if self.trans_running:
374
pkgtup = self._dopkgtup(hdr)
375
txmbrs = self.base.tsInfo.getMembers(pkgtup=pkgtup)
377
self.display.filelog(txmbr.po, txmbr.output_state)
378
self.display.scriptout(txmbr.po, self._scriptOutput())
379
self.ts_done(txmbr.po, txmbr.output_state)
383
def _instProgress(self, bytes, total, h):
385
# If h is a string, we're repackaging.
386
# Why the RPMCALLBACK_REPACKAGE_PROGRESS flag isn't set, I have no idea
387
if type(h) == type(""):
388
self.display.event(h, 'repackaging', bytes, total,
389
self.complete_actions, self.total_actions)
393
pkgtup = self._dopkgtup(hdr)
394
txmbrs = self.base.tsInfo.getMembers(pkgtup=pkgtup)
396
action = txmbr.output_state
397
self.display.event(txmbr.po, action, bytes, total,
398
self.complete_actions, self.total_actions)
399
def _unInstStart(self, bytes, total, h):
402
def _unInstProgress(self, bytes, total, h):
405
def _unInstStop(self, bytes, total, h):
406
self.total_removed += 1
407
self.complete_actions += 1
408
if h not in self.installed_pkg_names:
409
self.display.filelog(h, TS_ERASE)
414
self.display.event(h, action, 100, 100, self.complete_actions,
416
self.display.scriptout(h, self._scriptOutput())
418
if self.test: return # and we're done
419
self.ts_done(h, action)
422
def _rePackageStart(self, bytes, total, h):
425
def _rePackageStop(self, bytes, total, h):
428
def _rePackageProgress(self, bytes, total, h):
431
def _cpioError(self, bytes, total, h):
433
pkgtup = self._dopkgtup(hdr)
434
txmbrs = self.base.tsInfo.getMembers(pkgtup=pkgtup)
436
msg = "Error in cpio payload of rpm package %s" % txmbr.po
437
self.display.errorlog(msg)
438
# FIXME - what else should we do here? raise a failure and abort?
440
def _unpackError(self, bytes, total, h):
442
pkgtup = self._dopkgtup(hdr)
443
txmbrs = self.base.tsInfo.getMembers(pkgtup=pkgtup)
445
msg = "Error unpacking rpm package %s" % txmbr.po
446
self.display.errorlog(msg)
447
# FIXME - should we raise? I need a test case pkg to see what the
448
# right behavior should be