Merge release-0.28.17 from 'tools/mic'
[platform/upstream/mic.git] / mic / utils / rpmmisc.py
1 #!/usr/bin/python -tt
2 #
3 # Copyright (c) 2008, 2009, 2010, 2011 Intel, Inc.
4 #
5 # This program is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the Free
7 # Software Foundation; version 2 of the License
8 #
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 # for more details.
13 #
14 # You should have received a copy of the GNU General Public License along
15 # with this program; if not, write to the Free Software Foundation, Inc., 59
16 # Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
18 import os
19 import sys
20 import re
21 import rpm
22
23 from mic import msger
24 from mic.utils.errors import CreatorError
25 from mic.utils.proxy import get_proxy_for
26 from mic.utils import runner
27
28
29 class RPMInstallCallback:
30     """ Command line callback class for callbacks from the RPM library.
31     """
32
33     def __init__(self, ts, output=1):
34         self.output = output
35         self.callbackfilehandles = {}
36         self.total_actions = 0
37         self.total_installed = 0
38         self.total_installing = 0
39         self.installed_pkg_names = []
40         self.total_removed = 0
41         self.mark = "+"
42         self.marks = 40
43         self.lastmsg = None
44         self.tsInfo = None # this needs to be set for anything else to work
45         self.ts = ts
46         self.filelog = False
47         self.logString = []
48         self.headmsg = "Installing"
49
50     def _dopkgtup(self, hdr):
51         tmpepoch = hdr['epoch']
52         if tmpepoch is None: epoch = '0'
53         else: epoch = str(tmpepoch)
54
55         return (hdr['name'], hdr['arch'], epoch, hdr['version'], hdr['release'])
56
57     def _makeHandle(self, hdr):
58         handle = '%s:%s.%s-%s-%s' % (hdr['epoch'], hdr['name'], hdr['version'],
59           hdr['release'], hdr['arch'])
60
61         return handle
62
63     def _localprint(self, msg):
64         if self.output:
65             msger.info(msg)
66
67     def _makefmt(self, percent, progress = True):
68         l = len(str(self.total_actions))
69         size = "%s.%s" % (l, l)
70         fmt_done = "[%" + size + "s/%" + size + "s]"
71         done = fmt_done % (self.total_installing,
72                            self.total_actions)
73         marks = self.marks - (2 * l)
74         width = "%s.%s" % (marks, marks)
75         fmt_bar = "%-" + width + "s"
76         if progress:
77             bar = fmt_bar % (self.mark * int(marks * (percent / 100.0)), )
78             fmt = "%-10.10s: %-50.50s " + bar + " " + done
79         else:
80             bar = fmt_bar % (self.mark * marks, )
81             fmt = "%-10.10s: %-50.50s "  + bar + " " + done
82         return fmt
83
84     def _logPkgString(self, hdr):
85         """return nice representation of the package for the log"""
86         (n,a,e,v,r) = self._dopkgtup(hdr)
87         if e == '0':
88             pkg = '%s.%s %s-%s' % (n, a, v, r)
89         else:
90             pkg = '%s.%s %s:%s-%s' % (n, a, e, v, r)
91
92         return pkg
93
94     def callback(self, what, bytes, total, h, user):
95         if what == rpm.RPMCALLBACK_TRANS_START:
96             if bytes == 6:
97                 self.total_actions = total
98
99         elif what == rpm.RPMCALLBACK_TRANS_PROGRESS:
100             pass
101
102         elif what == rpm.RPMCALLBACK_TRANS_STOP:
103             pass
104
105         elif what == rpm.RPMCALLBACK_INST_OPEN_FILE:
106             self.lastmsg = None
107             hdr = None
108             if h is not None:
109                 try:
110                     hdr, rpmloc = h
111                 except:
112                     rpmloc = h
113                     hdr = readRpmHeader(self.ts, h)
114
115                 m = re.match("(.*)-(\d+.*)-(\d+\.\d+)\.(.+)\.rpm", os.path.basename(rpmloc))
116                 if m:
117                     pkgname = m.group(1)
118                 else:
119                     pkgname = os.path.basename(rpmloc)
120                 msger.info("Next install: %s " % pkgname)
121
122                 handle = self._makeHandle(hdr)
123                 fd = os.open(rpmloc, os.O_RDONLY)
124                 self.callbackfilehandles[handle]=fd
125                 if hdr['name'] not in self.installed_pkg_names:
126                     self.installed_pkg_names.append(hdr['name'])
127                     self.total_installed += 1
128                 return fd
129             else:
130                 self._localprint("No header - huh?")
131
132         elif what == rpm.RPMCALLBACK_INST_CLOSE_FILE:
133             hdr = None
134             if h is not None:
135                 try:
136                     hdr, rpmloc = h
137                 except:
138                     rpmloc = h
139                     hdr = readRpmHeader(self.ts, h)
140
141                 handle = self._makeHandle(hdr)
142                 os.close(self.callbackfilehandles[handle])
143                 fd = 0
144
145                 # log stuff
146                 #pkgtup = self._dopkgtup(hdr)
147                 self.logString.append(self._logPkgString(hdr))
148
149         elif what == rpm.RPMCALLBACK_INST_START:
150             self.total_installing += 1
151
152         elif what == rpm.RPMCALLBACK_UNINST_STOP:
153             pass
154
155         elif what == rpm.RPMCALLBACK_INST_PROGRESS:
156             if h is not None:
157                 percent = (self.total_installed*100)/self.total_actions
158                 if total > 0:
159                     try:
160                         hdr, rpmloc = h
161                     except:
162                         rpmloc = h
163
164                     m = re.match("(.*)-(\d+.*)-(\d+\.\d+)\.(.+)\.rpm", os.path.basename(rpmloc))
165                     if m:
166                         pkgname = m.group(1)
167                     else:
168                         pkgname = os.path.basename(rpmloc)
169                 if self.output:
170                     fmt = self._makefmt(percent)
171                     msg = fmt % (self.headmsg, pkgname)
172                     if msg != self.lastmsg:
173                         self.lastmsg = msg
174
175                         msger.info(msg)
176
177                         if self.total_installed == self.total_actions:
178                             msger.raw('')
179                             msger.verbose('\n'.join(self.logString))
180
181         elif what == rpm.RPMCALLBACK_UNINST_START:
182             pass
183
184         elif what == rpm.RPMCALLBACK_UNINST_PROGRESS:
185             pass
186
187         elif what == rpm.RPMCALLBACK_UNINST_STOP:
188             self.total_removed += 1
189
190         elif what == rpm.RPMCALLBACK_REPACKAGE_START:
191             pass
192
193         elif what == rpm.RPMCALLBACK_REPACKAGE_STOP:
194             pass
195
196         elif what == rpm.RPMCALLBACK_REPACKAGE_PROGRESS:
197             pass
198         elif what == rpm.RPMCALLBACK_SCRIPT_ERROR:
199             if h is not None:
200                 try:
201                     hdr, rpmloc = h
202                 except:
203                     rpmloc = h
204
205                 m = re.match("(.*)-(\d+.*)-(\d+\.\d+)\.(.+)\.rpm", os.path.basename(rpmloc))
206                 if m:
207                     pkgname = m.group(1)
208                 else:
209                     pkgname = os.path.basename(rpmloc)
210
211                 msger.warning('(%s) Post script failed' % pkgname)
212
213 def readRpmHeader(ts, filename):
214     """ Read an rpm header. """
215
216     fd = os.open(filename, os.O_RDONLY)
217     h = ts.hdrFromFdno(fd)
218     os.close(fd)
219     return h
220
221 def splitFilename(filename):
222     """ Pass in a standard style rpm fullname
223
224         Return a name, version, release, epoch, arch, e.g.::
225             foo-1.0-1.i386.rpm returns foo, 1.0, 1, i386
226             1:bar-9-123a.ia64.rpm returns bar, 9, 123a, 1, ia64
227     """
228
229     if filename[-4:] == '.rpm':
230         filename = filename[:-4]
231
232     archIndex = filename.rfind('.')
233     arch = filename[archIndex+1:]
234
235     relIndex = filename[:archIndex].rfind('-')
236     rel = filename[relIndex+1:archIndex]
237
238     verIndex = filename[:relIndex].rfind('-')
239     ver = filename[verIndex+1:relIndex]
240
241     epochIndex = filename.find(':')
242     if epochIndex == -1:
243         epoch = ''
244     else:
245         epoch = filename[:epochIndex]
246
247     name = filename[epochIndex + 1:verIndex]
248     return name, ver, rel, epoch, arch
249
250 def getCanonX86Arch(arch):
251     #
252     if arch == "i586":
253         f = open("/proc/cpuinfo", "r")
254         lines = f.readlines()
255         f.close()
256         for line in lines:
257             if line.startswith("model name") and line.find("Geode(TM)") != -1:
258                 return "geode"
259         return arch
260     # only athlon vs i686 isn't handled with uname currently
261     if arch != "i686":
262         return arch
263
264     # if we're i686 and AuthenticAMD, then we should be an athlon
265     f = open("/proc/cpuinfo", "r")
266     lines = f.readlines()
267     f.close()
268     for line in lines:
269         if line.startswith("vendor") and line.find("AuthenticAMD") != -1:
270             return "athlon"
271         # i686 doesn't guarantee cmov, but we depend on it
272         elif line.startswith("flags") and line.find("cmov") == -1:
273             return "i586"
274
275     return arch
276
277 def getCanonX86_64Arch(arch):
278     if arch != "x86_64":
279         return arch
280
281     vendor = None
282     f = open("/proc/cpuinfo", "r")
283     lines = f.readlines()
284     f.close()
285     for line in lines:
286         if line.startswith("vendor_id"):
287             vendor = line.split(':')[1]
288             break
289     if vendor is None:
290         return arch
291
292     if vendor.find("Authentic AMD") != -1 or vendor.find("AuthenticAMD") != -1:
293         return "amd64"
294     if vendor.find("GenuineIntel") != -1:
295         return "ia32e"
296     return arch
297
298 def getCanonArch():
299     arch = os.uname()[4]
300
301     if (len(arch) == 4 and arch[0] == "i" and arch[2:4] == "86"):
302         return getCanonX86Arch(arch)
303
304     if arch == "x86_64":
305         return getCanonX86_64Arch(arch)
306
307     return arch
308
309 # Copy from libsatsolver:poolarch.c, with cleanup
310 archPolicies = {
311     "x86_64":       "x86_64:i686:i586:i486:i386",
312     "i686":         "i686:i586:i486:i386",
313     "i586":         "i586:i486:i386",
314     "ia64":         "ia64:i686:i586:i486:i386",
315     "aarch64":      "aarch64",
316     "armv7tnhl":    "armv7tnhl:armv7thl:armv7nhl:armv7hl",
317     "armv7thl":     "armv7thl:armv7hl",
318     "armv7nhl":     "armv7nhl:armv7hl",
319     "armv7hl":      "armv7hl",
320     "armv7l":       "armv7l:armv6l:armv5tejl:armv5tel:armv5l:armv4tl:armv4l:armv3l",
321     "armv6l":       "armv6l:armv5tejl:armv5tel:armv5l:armv4tl:armv4l:armv3l",
322     "armv5tejl":    "armv5tejl:armv5tel:armv5l:armv4tl:armv4l:armv3l",
323     "armv5tel":     "armv5tel:armv5l:armv4tl:armv4l:armv3l",
324     "armv5l":       "armv5l:armv4tl:armv4l:armv3l",
325     "mipsel":       "mipsel",
326 }
327
328 # dict mapping arch -> ( multicompat, best personality, biarch personality )
329 multilibArches = {
330     "x86_64":  ( "athlon", "x86_64", "athlon" ),
331 }
332
333 # from yumUtils.py
334 arches = {
335     # ia32
336     "athlon": "i686",
337     "i686": "i586",
338     "geode": "i586",
339     "i586": "i486",
340     "i486": "i386",
341     "i386": "noarch",
342
343     # amd64
344     "x86_64": "athlon",
345     "amd64": "x86_64",
346     "ia32e": "x86_64",
347
348     # arm
349     "armv7tnhl": "armv7nhl",
350     "armv7nhl": "armv7hl",
351     "armv7hl": "noarch",
352     "armv7l": "armv6l",
353     "armv6l": "armv5tejl",
354     "armv5tejl": "armv5tel",
355     "armv5tel": "noarch",
356
357     #itanium
358     "ia64": "noarch",
359
360     "mipsel": "mipsel",
361 }
362
363 def isMultiLibArch(arch=None):
364     """returns true if arch is a multilib arch, false if not"""
365     if arch is None:
366         arch = getCanonArch()
367
368     if not arches.has_key(arch): # or we could check if it is noarch
369         return False
370
371     if multilibArches.has_key(arch):
372         return True
373
374     if multilibArches.has_key(arches[arch]):
375         return True
376
377     return False
378
379 def getBaseArch():
380     myarch = getCanonArch()
381     if not arches.has_key(myarch):
382         return myarch
383
384     if isMultiLibArch(arch=myarch):
385         if multilibArches.has_key(myarch):
386             return myarch
387         else:
388             return arches[myarch]
389
390     if arches.has_key(myarch):
391         basearch = myarch
392         value = arches[basearch]
393         while value != 'noarch':
394             basearch = value
395             value = arches[basearch]
396
397         return basearch
398
399 def checkRpmIntegrity(bin_rpm, package):
400     return runner.quiet([bin_rpm, "-K", "--nosignature", package])
401
402 def checkSig(ts, package):
403     """ Takes a transaction set and a package, check it's sigs,
404         return 0 if they are all fine
405         return 1 if the gpg key can't be found
406         return 2 if the header is in someway damaged
407         return 3 if the key is not trusted
408         return 4 if the pkg is not gpg or pgp signed
409     """
410
411     value = 0
412     currentflags = ts.setVSFlags(0)
413     fdno = os.open(package, os.O_RDONLY)
414     try:
415         hdr = ts.hdrFromFdno(fdno)
416
417     except rpm.error as e:
418         if str(e) == "public key not availaiable":
419             value = 1
420         if str(e) == "public key not available":
421             value = 1
422         if str(e) == "public key not trusted":
423             value = 3
424         if str(e) == "error reading package header":
425             value = 2
426     else:
427         error, siginfo = getSigInfo(hdr)
428         if error == 101:
429             os.close(fdno)
430             del hdr
431             value = 4
432         else:
433             del hdr
434
435     try:
436         os.close(fdno)
437     except OSError:
438         pass
439
440     ts.setVSFlags(currentflags) # put things back like they were before
441     return value
442
443 def getSigInfo(hdr):
444     """ checks signature from an hdr hand back signature information and/or
445         an error code
446     """
447
448     import locale
449     locale.setlocale(locale.LC_ALL, 'C')
450
451     string = '%|DSAHEADER?{%{DSAHEADER:pgpsig}}:{%|RSAHEADER?{%{RSAHEADER:pgpsig}}:{%|SIGGPG?{%{SIGGPG:pgpsig}}:{%|SIGPGP?{%{SIGPGP:pgpsig}}:{(none)}|}|}|}|'
452     siginfo = hdr.sprintf(string)
453     if siginfo != '(none)':
454         error = 0
455         sigtype, sigdate, sigid = siginfo.split(',')
456     else:
457         error = 101
458         sigtype = 'MD5'
459         sigdate = 'None'
460         sigid = 'None'
461
462     infotuple = (sigtype, sigdate, sigid)
463     return error, infotuple
464