~ubuntu-branches/ubuntu/jaunty/yum/jaunty

« back to all changes in this revision

Viewing changes to yum/rpmtrans.py

  • Committer: Bazaar Package Importer
  • Author(s): Ben Hutchings
  • Date: 2008-07-28 23:20:59 UTC
  • mfrom: (2.1.3 intrepid)
  • Revision ID: james.westby@ubuntu.com-20080728232059-24lo1r17smhr71l8
Tags: 3.2.12-1.2
* Non-maintainer upload
* Updated for compatibility with current python-pyme (Closes: #490368)
  based on patch by Martin Meredith <mez@ubuntu.com>
  - Changed import in yum/misc.py
  - Set versioned dependency on python-pyme

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
 
28
 
 
29
class NoOutputCallBack:
 
30
    def __init__(self):
 
31
        pass
 
32
        
 
33
    def event(self, package, action, te_current, te_total, ts_current, ts_total):
 
34
        """
 
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
 
41
                         processed
 
42
        @param ts_current: number of processes completed in whole transaction
 
43
        @param ts_total: total number of processes in the transaction.
 
44
        """
 
45
        # this is where a progress bar would be called
 
46
        
 
47
        pass
 
48
 
 
49
    def scriptout(self, package, msgs):
 
50
        """package is the package.  msgs is the messages that were
 
51
        output (if any)."""
 
52
        pass
 
53
 
 
54
    def errorlog(self, msg):
 
55
        """takes a simple error msg string"""
 
56
        
 
57
        pass
 
58
 
 
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()"""
 
63
        pass
 
64
        
 
65
class RPMBaseCallback:
 
66
    '''
 
67
    Base class for a RPMTransaction display callback class
 
68
    '''
 
69
    def __init__(self):
 
70
        self.action = { TS_UPDATE : 'Updating', 
 
71
                        TS_ERASE: 'Erasing',
 
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', 
 
79
                            TS_ERASE: 'Erased',
 
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')        
 
86
        
 
87
    def event(self, package, action, te_current, te_total, ts_current, ts_total):
 
88
        """
 
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
 
95
                         processed
 
96
        @param ts_current: number of processes completed in whole transaction
 
97
        @param ts_total: total number of processes in the transaction.
 
98
        """
 
99
        raise NotImplementedError()
 
100
 
 
101
    def scriptout(self, package, msgs):
 
102
        """package is the package.  msgs is the messages that were
 
103
        output (if any)."""
 
104
        pass
 
105
 
 
106
    def errorlog(self, msg):
 
107
        # FIXME this should probably dump to the filelog, too
 
108
        print >> sys.stderr, msg
 
109
 
 
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)
 
115
        else:
 
116
            msg = '%s: %s' % (package, action)
 
117
        self.logger.info(msg)
 
118
            
 
119
 
 
120
class SimpleCliCallBack(RPMBaseCallback):
 
121
    def __init__(self):
 
122
        RPMBaseCallback.__init__(self)
 
123
        self.lastmsg = None
 
124
        self.lastpackage = None # name of last package we looked at
 
125
        
 
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:
 
131
            print msg
 
132
        self.lastmsg = msg
 
133
        self.lastpackage = package
 
134
 
 
135
    def scriptout(self, package, msgs):
 
136
        if msgs:
 
137
            print msgs,
 
138
 
 
139
class RPMTransaction:
 
140
    def __init__(self, base, test=False, display=NoOutputCallBack):
 
141
        if not callable(display):
 
142
            self.display = display
 
143
        else:
 
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')
 
155
        self.filelog = False
 
156
 
 
157
        self._setupOutputLogging()
 
158
 
 
159
    def _setupOutputLogging(self):
 
160
        # UGLY... set up the transaction to record output from scriptlets
 
161
        (r, w) = os.pipe()
 
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)
 
171
 
 
172
    def _shutdownOutputLogging(self):
 
173
        # reset rpm bits from reording output
 
174
        rpm.setVerbosity(rpm.RPMLOG_NOTICE)
 
175
        rpm.setLogFile(sys.stderr)
 
176
        try:
 
177
            self._writepipe.close()
 
178
        except:
 
179
            pass
 
180
 
 
181
    def _scriptOutput(self):
 
182
        try:
 
183
            out = self._readpipe.read()
 
184
            return out
 
185
        except IOError:
 
186
            pass
 
187
 
 
188
    def __del__(self):
 
189
        self._shutdownOutputLogging()
 
190
        
 
191
    def _dopkgtup(self, hdr):
 
192
        tmpepoch = hdr['epoch']
 
193
        if tmpepoch is None: epoch = '0'
 
194
        else: epoch = str(tmpepoch)
 
195
 
 
196
        return (hdr['name'], hdr['arch'], epoch, hdr['version'], hdr['release'])
 
197
 
 
198
    def _makeHandle(self, hdr):
 
199
        handle = '%s:%s.%s-%s-%s' % (hdr['epoch'], hdr['name'], hdr['version'],
 
200
          hdr['release'], hdr['arch'])
 
201
 
 
202
        return handle
 
203
    
 
204
    def ts_done(self, package, action):
 
205
        """writes out the portions of the transaction which have completed"""
 
206
        
 
207
        if self.test: return
 
208
    
 
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
 
213
            try:
 
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)
 
217
                return
 
218
        
 
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
 
222
        
 
223
        # make sure we have a list to work from - rpm seems to be throwing us
 
224
        # some curveballs
 
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)
 
228
            return
 
229
 
 
230
        (t,e,n,v,r,a) = self._te_tuples[0] # what we should be on
 
231
 
 
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:
 
235
            if t != 'erase':
 
236
                self.display.filelog(package, msg)
 
237
        if action in TS_INSTALL_STATES:
 
238
            if t != 'install':
 
239
                self.display.filelog(package, msg)
 
240
                
 
241
        # check the pkg name out to make sure it matches
 
242
        if type(package) in types.StringTypes:
 
243
            name = package
 
244
        else:
 
245
            name = package.name
 
246
        
 
247
        if n != name:
 
248
            msg = 'ts_done name in te is %s should be %s' % (n, package)
 
249
            self.display.filelog(package, msg)
 
250
 
 
251
        # hope springs eternal that this isn't wrong
 
252
        msg = '%s %s:%s-%s-%s.%s\n' % (t,e,n,v,r,a)
 
253
 
 
254
        self._ts_done.write(msg)
 
255
        self._ts_done.flush()
 
256
        self._te_tuples.pop(0)
 
257
    
 
258
    def ts_all(self):
 
259
        """write out what our transaction will do"""
 
260
        
 
261
        # save the transaction elements into a list so we can run across them
 
262
        if not hasattr(self, '_te_tuples'):
 
263
            self._te_tuples = []
 
264
 
 
265
        for te in self.base.ts:
 
266
            n = te.N()
 
267
            a = te.A()
 
268
            v = te.V()
 
269
            r = te.R()
 
270
            e = te.E()
 
271
            if e is None:
 
272
                e = '0'
 
273
            if te.Type() == 1:
 
274
                t = 'install'
 
275
            elif te.Type() == 2:
 
276
                t = 'erase'
 
277
            else:
 
278
                t = te.Type()
 
279
            
 
280
            # save this in a list            
 
281
            self._te_tuples.append((t,e,n,v,r,a))
 
282
 
 
283
        # write to a file
 
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
 
287
        try:
 
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
 
292
            
 
293
            fo = open(tsfn, 'w')
 
294
        except (IOError, OSError), e:
 
295
            self.display.errorlog('could not open ts_all file: %s' % e)
 
296
            return
 
297
        
 
298
 
 
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)
 
301
            fo.write(msg)
 
302
        fo.flush()
 
303
        fo.close()
 
304
    
 
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)
 
334
    
 
335
    
 
336
    def _transStart(self, bytes, total, h):
 
337
        if bytes == 6:
 
338
            self.total_actions = total
 
339
            if self.test: return
 
340
            self.trans_running = True
 
341
            self.ts_all() # write out what transaction will do
 
342
 
 
343
    def _transProgress(self, bytes, total, h):
 
344
        pass
 
345
        
 
346
    def _transStop(self, bytes, total, h):
 
347
        pass
 
348
 
 
349
    def _instOpenFile(self, bytes, total, h):
 
350
        self.lastmsg = None
 
351
        hdr = None
 
352
        if h is not None:
 
353
            hdr, rpmloc = 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'])
 
361
            return fd
 
362
        else:
 
363
            self.display.errorlog("Error: No Header to INST_OPEN_FILE")
 
364
            
 
365
    def _instCloseFile(self, bytes, total, h):
 
366
        hdr = None
 
367
        if h is not None:
 
368
            hdr, rpmloc = h
 
369
            handle = self._makeHandle(hdr)
 
370
            os.close(self.filehandles[handle])
 
371
            fd = 0
 
372
            if self.test: return
 
373
            if self.trans_running:
 
374
                pkgtup = self._dopkgtup(hdr)
 
375
                txmbrs = self.base.tsInfo.getMembers(pkgtup=pkgtup)
 
376
                for txmbr in txmbrs:
 
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)
 
380
                
 
381
                
 
382
    
 
383
    def _instProgress(self, bytes, total, h):
 
384
        if h is not None:
 
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)
 
390
 
 
391
            else:
 
392
                hdr, rpmloc = h
 
393
                pkgtup = self._dopkgtup(hdr)
 
394
                txmbrs = self.base.tsInfo.getMembers(pkgtup=pkgtup)
 
395
                for txmbr in txmbrs:
 
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):
 
400
        pass
 
401
        
 
402
    def _unInstProgress(self, bytes, total, h):
 
403
        pass
 
404
    
 
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)
 
410
            action = TS_ERASE
 
411
        else:
 
412
            action = TS_UPDATED                    
 
413
        
 
414
        self.display.event(h, action, 100, 100, self.complete_actions,
 
415
                            self.total_actions)
 
416
        self.display.scriptout(h, self._scriptOutput())
 
417
        
 
418
        if self.test: return # and we're done
 
419
        self.ts_done(h, action)
 
420
        
 
421
        
 
422
    def _rePackageStart(self, bytes, total, h):
 
423
        pass
 
424
        
 
425
    def _rePackageStop(self, bytes, total, h):
 
426
        pass
 
427
        
 
428
    def _rePackageProgress(self, bytes, total, h):
 
429
        pass
 
430
        
 
431
    def _cpioError(self, bytes, total, h):
 
432
        (hdr, rpmloc) = h
 
433
        pkgtup = self._dopkgtup(hdr)
 
434
        txmbrs = self.base.tsInfo.getMembers(pkgtup=pkgtup)
 
435
        for txmbr in txmbrs:
 
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?
 
439
    
 
440
    def _unpackError(self, bytes, total, h):
 
441
        (hdr, rpmloc) = h
 
442
        pkgtup = self._dopkgtup(hdr)
 
443
        txmbrs = self.base.tsInfo.getMembers(pkgtup=pkgtup)
 
444
        for txmbr in txmbrs:
 
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
 
449