3 # Copyright (c) 2008, 2009, 2010, 2011 Intel, Inc.
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
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
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.
24 from mic.utils.errors import CreatorError
25 from mic.utils.proxy import get_proxy_for
26 from mic.utils import runner
29 class RPMInstallCallback:
30 """ Command line callback class for callbacks from the RPM library.
33 def __init__(self, ts, output=1):
35 self.callbackfilehandles = {}
36 self.total_actions = 0
37 self.total_installed = 0
38 self.installed_pkg_names = []
39 self.total_removed = 0
43 self.tsInfo = None # this needs to be set for anything else to work
47 self.headmsg = "Installing"
49 def _dopkgtup(self, hdr):
50 tmpepoch = hdr['epoch']
51 if tmpepoch is None: epoch = '0'
52 else: epoch = str(tmpepoch)
54 return (hdr['name'], hdr['arch'], epoch, hdr['version'], hdr['release'])
56 def _makeHandle(self, hdr):
57 handle = '%s:%s.%s-%s-%s' % (hdr['epoch'], hdr['name'], hdr['version'],
58 hdr['release'], hdr['arch'])
62 def _localprint(self, msg):
66 def _makefmt(self, percent, progress = True):
67 l = len(str(self.total_actions))
68 size = "%s.%s" % (l, l)
69 fmt_done = "[%" + size + "s/%" + size + "s]"
70 done = fmt_done % (self.total_installed + self.total_removed,
72 marks = self.marks - (2 * l)
73 width = "%s.%s" % (marks, marks)
74 fmt_bar = "%-" + width + "s"
76 bar = fmt_bar % (self.mark * int(marks * (percent / 100.0)), )
77 fmt = "%-10.10s: %-20.20s " + bar + " " + done
79 bar = fmt_bar % (self.mark * marks, )
80 fmt = "%-10.10s: %-20.20s " + bar + " " + done
83 def _logPkgString(self, hdr):
84 """return nice representation of the package for the log"""
85 (n,a,e,v,r) = self._dopkgtup(hdr)
87 pkg = '%s.%s %s-%s' % (n, a, v, r)
89 pkg = '%s.%s %s:%s-%s' % (n, a, e, v, r)
93 def callback(self, what, bytes, total, h, user):
94 if what == rpm.RPMCALLBACK_TRANS_START:
96 self.total_actions = total
98 elif what == rpm.RPMCALLBACK_TRANS_PROGRESS:
101 elif what == rpm.RPMCALLBACK_TRANS_STOP:
104 elif what == rpm.RPMCALLBACK_INST_OPEN_FILE:
112 hdr = readRpmHeader(self.ts, h)
114 handle = self._makeHandle(hdr)
115 fd = os.open(rpmloc, os.O_RDONLY)
116 self.callbackfilehandles[handle]=fd
117 if hdr['name'] not in self.installed_pkg_names:
118 self.installed_pkg_names.append(hdr['name'])
119 self.total_installed += 1
122 self._localprint("No header - huh?")
124 elif what == rpm.RPMCALLBACK_INST_CLOSE_FILE:
131 hdr = readRpmHeader(self.ts, h)
133 handle = self._makeHandle(hdr)
134 os.close(self.callbackfilehandles[handle])
138 #pkgtup = self._dopkgtup(hdr)
139 self.logString.append(self._logPkgString(hdr))
141 elif what == rpm.RPMCALLBACK_INST_PROGRESS:
143 percent = (self.total_installed*100L)/self.total_actions
150 m = re.match("(.*)-(\d+.*)-(\d+\.\d+)\.(.+)\.rpm", os.path.basename(rpmloc))
154 pkgname = os.path.basename(rpmloc)
156 fmt = self._makefmt(percent)
157 msg = fmt % (self.headmsg, pkgname)
158 if msg != self.lastmsg:
163 if self.total_installed == self.total_actions:
165 msger.verbose('\n'.join(self.logString))
167 elif what == rpm.RPMCALLBACK_UNINST_START:
170 elif what == rpm.RPMCALLBACK_UNINST_PROGRESS:
173 elif what == rpm.RPMCALLBACK_UNINST_STOP:
174 self.total_removed += 1
176 elif what == rpm.RPMCALLBACK_REPACKAGE_START:
179 elif what == rpm.RPMCALLBACK_REPACKAGE_STOP:
182 elif what == rpm.RPMCALLBACK_REPACKAGE_PROGRESS:
185 def readRpmHeader(ts, filename):
186 """ Read an rpm header. """
188 fd = os.open(filename, os.O_RDONLY)
189 h = ts.hdrFromFdno(fd)
193 def splitFilename(filename):
194 """ Pass in a standard style rpm fullname
196 Return a name, version, release, epoch, arch, e.g.::
197 foo-1.0-1.i386.rpm returns foo, 1.0, 1, i386
198 1:bar-9-123a.ia64.rpm returns bar, 9, 123a, 1, ia64
201 if filename[-4:] == '.rpm':
202 filename = filename[:-4]
204 archIndex = filename.rfind('.')
205 arch = filename[archIndex+1:]
207 relIndex = filename[:archIndex].rfind('-')
208 rel = filename[relIndex+1:archIndex]
210 verIndex = filename[:relIndex].rfind('-')
211 ver = filename[verIndex+1:relIndex]
213 epochIndex = filename.find(':')
217 epoch = filename[:epochIndex]
219 name = filename[epochIndex + 1:verIndex]
220 return name, ver, rel, epoch, arch
222 def getCanonX86Arch(arch):
225 f = open("/proc/cpuinfo", "r")
226 lines = f.readlines()
229 if line.startswith("model name") and line.find("Geode(TM)") != -1:
232 # only athlon vs i686 isn't handled with uname currently
236 # if we're i686 and AuthenticAMD, then we should be an athlon
237 f = open("/proc/cpuinfo", "r")
238 lines = f.readlines()
241 if line.startswith("vendor") and line.find("AuthenticAMD") != -1:
243 # i686 doesn't guarantee cmov, but we depend on it
244 elif line.startswith("flags") and line.find("cmov") == -1:
249 def getCanonX86_64Arch(arch):
254 f = open("/proc/cpuinfo", "r")
255 lines = f.readlines()
258 if line.startswith("vendor_id"):
259 vendor = line.split(':')[1]
264 if vendor.find("Authentic AMD") != -1 or vendor.find("AuthenticAMD") != -1:
266 if vendor.find("GenuineIntel") != -1:
273 if (len(arch) == 4 and arch[0] == "i" and arch[2:4] == "86"):
274 return getCanonX86Arch(arch)
277 return getCanonX86_64Arch(arch)
281 # Copy from libsatsolver:poolarch.c, with cleanup
283 "x86_64": "x86_64:i686:i586:i486:i386",
284 "i686": "i686:i586:i486:i386",
285 "i586": "i586:i486:i386",
286 "ia64": "ia64:i686:i586:i486:i386",
287 "armv7tnhl": "armv7tnhl:armv7thl:armv7nhl:armv7hl",
288 "armv7thl": "armv7thl:armv7hl",
289 "armv7nhl": "armv7nhl:armv7hl",
290 "armv7hl": "armv7hl",
291 "armv7l": "armv7l:armv6l:armv5tejl:armv5tel:armv5l:armv4tl:armv4l:armv3l",
292 "armv6l": "armv6l:armv5tejl:armv5tel:armv5l:armv4tl:armv4l:armv3l",
293 "armv5tejl": "armv5tejl:armv5tel:armv5l:armv4tl:armv4l:armv3l",
294 "armv5tel": "armv5tel:armv5l:armv4tl:armv4l:armv3l",
295 "armv5l": "armv5l:armv4tl:armv4l:armv3l",
298 # dict mapping arch -> ( multicompat, best personality, biarch personality )
300 "x86_64": ( "athlon", "x86_64", "athlon" ),
319 "armv7tnhl": "armv7nhl",
320 "armv7nhl": "armv7hl",
323 "armv6l": "armv5tejl",
324 "armv5tejl": "armv5tel",
325 "armv5tel": "noarch",
331 def isMultiLibArch(arch=None):
332 """returns true if arch is a multilib arch, false if not"""
334 arch = getCanonArch()
336 if not arches.has_key(arch): # or we could check if it is noarch
339 if multilibArches.has_key(arch):
342 if multilibArches.has_key(arches[arch]):
348 myarch = getCanonArch()
349 if not arches.has_key(myarch):
352 if isMultiLibArch(arch=myarch):
353 if multilibArches.has_key(myarch):
356 return arches[myarch]
358 if arches.has_key(myarch):
360 value = arches[basearch]
361 while value != 'noarch':
363 value = arches[basearch]
367 def checkRpmIntegrity(bin_rpm, package):
368 return runner.quiet([bin_rpm, "-K", "--nosignature", package])
370 def checkSig(ts, package):
371 """ Takes a transaction set and a package, check it's sigs,
372 return 0 if they are all fine
373 return 1 if the gpg key can't be found
374 return 2 if the header is in someway damaged
375 return 3 if the key is not trusted
376 return 4 if the pkg is not gpg or pgp signed
380 currentflags = ts.setVSFlags(0)
381 fdno = os.open(package, os.O_RDONLY)
383 hdr = ts.hdrFromFdno(fdno)
386 if str(e) == "public key not availaiable":
388 if str(e) == "public key not available":
390 if str(e) == "public key not trusted":
392 if str(e) == "error reading package header":
395 error, siginfo = getSigInfo(hdr)
408 ts.setVSFlags(currentflags) # put things back like they were before
412 """ checks signature from an hdr hand back signature information and/or
417 locale.setlocale(locale.LC_ALL, 'C')
419 string = '%|DSAHEADER?{%{DSAHEADER:pgpsig}}:{%|RSAHEADER?{%{RSAHEADER:pgpsig}}:{%|SIGGPG?{%{SIGGPG:pgpsig}}:{%|SIGPGP?{%{SIGPGP:pgpsig}}:{(none)}|}|}|}|'
420 siginfo = hdr.sprintf(string)
421 if siginfo != '(none)':
423 sigtype, sigdate, sigid = siginfo.split(',')
430 infotuple = (sigtype, sigdate, sigid)
431 return error, infotuple
433 def checkRepositoryEULA(name, repo):
434 """ This function is to check the EULA file if provided.
435 return True: no EULA or accepted
436 return False: user declined the EULA
444 from mic.utils.errors import CreatorError
446 def _check_and_download_url(u2opener, url, savepath):
449 f = u2opener.open(url)
452 except u2.HTTPError, httperror:
453 if httperror.code in (404, 503):
456 raise CreatorError(httperror)
457 except OSError, oserr:
461 raise CreatorError(oserr)
462 except IOError, oserr:
463 if hasattr(oserr, "reason") and oserr.reason.errno == 2:
466 raise CreatorError(oserr)
467 except u2.URLError, err:
468 raise CreatorError(err)
469 except httplib.HTTPException, e:
470 raise CreatorError(e)
473 licf = open(savepath, "w")
480 def _pager_file(savepath):
482 if os.path.splitext(savepath)[1].upper() in ('.HTM', '.HTML'):
483 pagers = ('w3m', 'links', 'lynx', 'less', 'more')
485 pagers = ('less', 'more')
489 cmd = "%s %s" % (pager, savepath)
504 # when proxy needed, make urllib2 follow it
506 proxy_username = repo.proxy_username
507 proxy_password = repo.proxy_password
510 proxy = get_proxy_for(repo.baseurl[0])
513 auth_handler = u2.HTTPBasicAuthHandler(u2.HTTPPasswordMgrWithDefaultRealm())
517 proxy_netloc = urlparse.urlsplit(proxy).netloc
519 proxy_url = 'http://%s:%s@%s' % (proxy_username, proxy_password, proxy_netloc)
521 proxy_url = 'http://%s@%s' % (proxy_username, proxy_netloc)
525 proxy_support = u2.ProxyHandler({'http': proxy_url,
528 handlers.append(proxy_support)
530 # download all remote files to one temp dir
532 repo_lic_dir = tempfile.mkdtemp(prefix = 'repolic')
534 for url in repo.baseurl:
535 tmphandlers = handlers[:]
537 (scheme, host, path, parm, query, frag) = urlparse.urlparse(url.rstrip('/') + '/')
538 if scheme not in ("http", "https", "ftp", "ftps", "file"):
539 raise CreatorError("Error: invalid url %s" % url)
543 user_pass, host = host.split('@', 1)
545 user, password = user_pass.split(':', 1)
546 except ValueError, e:
547 raise CreatorError('Bad URL: %s' % url)
549 msger.verbose("adding HTTP auth: %s, XXXXXXXX" %(user))
550 auth_handler.add_password(None, host, user, password)
551 tmphandlers.append(auth_handler)
552 url = scheme + "://" + host + path + parm + query + frag
555 u2opener = u2.build_opener(*tmphandlers)
558 repo_eula_url = urlparse.urljoin(url, "LICENSE.txt")
559 repo_eula_path = _check_and_download_url(
562 os.path.join(repo_lic_dir, repo.id + '_LICENSE.txt'))
569 shutil.rmtree(repo_lic_dir) #cleanup
572 # show the license file
573 msger.info('For the software packages in this yum repo:')
574 msger.info(' %s: %s' % (name, baseurl))
575 msger.info('There is an "End User License Agreement" file that need to be checked.')
576 msger.info('Please read the terms and conditions outlined in it and answer the followed qustions.')
579 _pager_file(repo_eula_path)
581 # Asking for the "Accept/Decline"
582 if not msger.ask('Would you agree to the terms and conditions outlined in the above End User License Agreement?'):
583 msger.warning('Will not install pkgs from this repo.')
584 shutil.rmtree(repo_lic_dir) #cleanup
587 # try to find support_info.html for extra infomation
588 repo_info_url = urlparse.urljoin(baseurl, "support_info.html")
589 repo_info_path = _check_and_download_url(
592 os.path.join(repo_lic_dir, repo.id + '_support_info.html'))
594 msger.info('There is one more file in the repo for additional support information, please read it')
596 _pager_file(repo_info_path)
599 shutil.rmtree(repo_lic_dir)