Merge release-0.28.17 from 'tools/mic'
[platform/upstream/mic.git] / plugins / backend / zypppkgmgr.py
1 #!/usr/bin/python -tt
2 #
3 # Copyright (c) 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 shutil
20 import urlparse
21 import rpm
22 import glob
23
24 import zypp #pylint: disable=import-error
25 if not hasattr(zypp, 'PoolQuery') or \
26    not hasattr(zypp.RepoManager, 'loadSolvFile'):
27     raise ImportError("python-zypp in host system cannot support PoolQuery or "
28                       "loadSolvFile interface, please update it to enhanced "
29                       "version which can be found in download.tizen.org/tools")
30
31 from mic import msger
32 from mic.kickstart import ksparser
33 from mic.utils import misc, rpmmisc, runner, fs_related
34 from mic.utils.grabber import myurlgrab, TextProgress
35 from mic.utils.proxy import get_proxy_for
36 from mic.utils.errors import CreatorError, RepoError, RpmError
37 from mic.conf import configmgr
38
39 class RepositoryStub:
40     def __init__(self):
41         self.name = None
42         self.baseurl = []
43         self.mirrorlist = None
44         self.proxy = None
45         self.proxy_username = None
46         self.proxy_password = None
47         self.nocache = False
48
49         self.enabled = True
50         self.autorefresh = True
51         self.keeppackages = True
52         self.priority = None
53
54 from mic.pluginbase import BackendPlugin
55 class Zypp(BackendPlugin):
56     name = 'zypp'
57
58     def __init__(self, target_arch, instroot, cachedir, strict_mode = False):
59         self.cachedir = cachedir
60         self.instroot  = instroot
61         self.target_arch = target_arch
62         self.strict_mode = strict_mode
63
64         self.__pkgs_license = {}
65         self.__pkgs_content = {}
66         self.__pkgs_vcsinfo = {}
67         self.repos = []
68         self.to_deselect = []
69         self.localpkgs = {}
70         self.repo_manager = None
71         self.repo_manager_options = None
72         self.Z = None
73         self.ts = None
74         self.ts_pre = None
75         self.incpkgs = {}
76         self.excpkgs = {}
77         self.pre_pkgs = []
78         self.check_pkgs = []
79         self.probFilterFlags = [ rpm.RPMPROB_FILTER_OLDPACKAGE,
80                                  rpm.RPMPROB_FILTER_REPLACEPKG ]
81
82         self.has_prov_query = True
83         self.install_debuginfo = False
84         # this can't be changed, it is used by zypp
85         self.tmp_file_path = '/var/tmp'
86
87     def doFileLogSetup(self, uid, logfile):
88         # don't do the file log for the livecd as it can lead to open fds
89         # being left and an inability to clean up after ourself
90         pass
91
92     def closeRpmDB(self):
93         pass
94
95     def close(self):
96         if self.ts:
97             self.ts.closeDB()
98             self.ts = None
99
100         if self.ts_pre:
101             self.ts_pre.closeDB()
102             self.ts = None
103
104         self.closeRpmDB()
105
106     def __del__(self):
107         self.close()
108
109     def _cleanupRpmdbLocks(self, installroot):
110         # cleans up temporary files left by bdb so that differing
111         # versions of rpm don't cause problems
112         for f in glob.glob(installroot + "/var/lib/rpm/__db*"):
113             os.unlink(f)
114
115     def _cleanupZyppJunk(self, installroot):
116         try:
117             shutil.rmtree(os.path.join(installroot, '.zypp'))
118         except:
119             pass
120
121     def setup(self):
122         self._cleanupRpmdbLocks(self.instroot)
123         # '/var/tmp' is used by zypp to build cache, so make sure
124         # if it exists
125         if not os.path.exists(self.tmp_file_path ):
126             os.makedirs(self.tmp_file_path)
127
128     def whatObsolete(self, pkg):
129         query = zypp.PoolQuery()
130         query.addKind(zypp.ResKind.package)
131         query.addDependency(zypp.SolvAttr.obsoletes, pkg.name(), pkg.edition())
132         query.setMatchExact()
133         for pi in query.queryResults(self.Z.pool()):
134             return pi
135         return None
136
137     def _zyppQueryPackage(self, pkg):
138         query = zypp.PoolQuery()
139         query.addKind(zypp.ResKind.package)
140         query.addAttribute(zypp.SolvAttr.name, pkg)
141         query.setMatchExact()
142         for pi in query.queryResults(self.Z.pool()):
143             return pi
144         return None
145
146     def _splitPkgString(self, pkg):
147         sp = pkg.rsplit(".", 1)
148         name = sp[0]
149         arch = None
150         if len(sp) == 2:
151             arch = sp[1]
152             sysarch = zypp.Arch(self.target_arch)
153             if not zypp.Arch(arch).compatible_with (sysarch):
154                 arch = None
155                 name = ".".join(sp)
156         return name, arch
157
158     def selectPackage(self, pkg):
159         """Select a given package or package pattern, can be specified
160         with name.arch or name* or *name
161         """
162
163         if not self.Z:
164             self.__initialize_zypp()
165
166         def markPoolItem(obs, pi):
167             if obs == None:
168                 pi.status().setToBeInstalled (zypp.ResStatus.USER)
169             else:
170                 obs.status().setToBeInstalled (zypp.ResStatus.USER)
171
172         def cmpEVR(p1, p2):
173             # compare criterion: arch compatibility first, then repo
174             # priority, and version last
175             a1 = p1.arch()
176             a2 = p2.arch()
177             if str(a1) != str(a2):
178                 if a1.compatible_with(a2):
179                     return -1
180                 else:
181                     return 1
182             # Priority of a repository is an integer value between 0 (the
183             # highest priority) and 99 (the lowest priority)
184             pr1 = int(p1.repoInfo().priority())
185             pr2 = int(p2.repoInfo().priority())
186             if pr1 > pr2:
187                 return -1
188             elif pr1 < pr2:
189                 return 1
190
191             ed1 = p1.edition()
192             ed2 = p2.edition()
193             (e1, v1, r1) = map(str, [ed1.epoch(), ed1.version(), ed1.release()])
194             (e2, v2, r2) = map(str, [ed2.epoch(), ed2.version(), ed2.release()])
195             return rpm.labelCompare((e1, v1, r1), (e2, v2, r2))
196
197         found = False
198         startx = pkg.startswith("*")
199         endx = pkg.endswith("*")
200         ispattern = startx or endx
201         name, arch = self._splitPkgString(pkg)
202
203         q = zypp.PoolQuery()
204         q.addKind(zypp.ResKind.package)
205
206         if ispattern:
207             if startx and not endx:
208                 pattern = '%s$' % (pkg[1:])
209             if endx and not startx:
210                 pattern = '^%s' % (pkg[0:-1])
211             if endx and startx:
212                 pattern = '%s' % (pkg[1:-1])
213             q.setMatchRegex()
214             q.addAttribute(zypp.SolvAttr.name, pattern)
215
216         elif arch:
217             q.setMatchExact()
218             q.addAttribute(zypp.SolvAttr.name, name)
219
220         else:
221             q.setMatchExact()
222             q.addAttribute(zypp.SolvAttr.name, pkg)
223
224         for pitem in sorted(
225                         q.queryResults(self.Z.pool()),
226                         cmp=lambda x,y: cmpEVR(zypp.asKindPackage(x), zypp.asKindPackage(y)),
227                         reverse=True):
228             item = zypp.asKindPackage(pitem)
229             if item.name() in self.excpkgs.keys() and \
230                self.excpkgs[item.name()] == item.repoInfo().name():
231                 continue
232             if item.name() in self.incpkgs.keys() and \
233                self.incpkgs[item.name()] != item.repoInfo().name():
234                 continue
235
236             found = True
237             obspkg = self.whatObsolete(item)
238             if arch:
239                 if arch == str(item.arch()):
240                     pitem.status().setToBeInstalled (zypp.ResStatus.USER)
241             else:
242                 markPoolItem(obspkg, pitem)
243             if not ispattern:
244                 break
245
246         # Can't match using package name, then search from packge
247         # provides infomation
248         if found == False and not ispattern:
249             q.addAttribute(zypp.SolvAttr.provides, pkg)
250             q.addAttribute(zypp.SolvAttr.name,'')
251
252             for pitem in sorted(
253                             q.queryResults(self.Z.pool()),
254                             cmp=lambda x,y: cmpEVR(zypp.asKindPackage(x), zypp.asKindPackage(y)),
255                             reverse=True):
256                 item = zypp.asKindPackage(pitem)
257                 if item.name() in self.excpkgs.keys() and \
258                    self.excpkgs[item.name()] == item.repoInfo().name():
259                     continue
260                 if item.name() in self.incpkgs.keys() and \
261                    self.incpkgs[item.name()] != item.repoInfo().name():
262                     continue
263
264                 found = True
265                 obspkg = self.whatObsolete(item)
266                 markPoolItem(obspkg, pitem)
267                 break
268
269         if found:
270             return None
271         else:
272             raise CreatorError("Unable to find package: %s" % (pkg,))
273
274     def inDeselectPackages(self, pitem):
275         """check if specified pacakges are in the list of inDeselectPackages
276         """
277         item = zypp.asKindPackage(pitem)
278         name = item.name()
279         for pkg in self.to_deselect:
280             startx = pkg.startswith("*")
281             endx = pkg.endswith("*")
282             ispattern = startx or endx
283             pkgname, pkgarch = self._splitPkgString(pkg)
284             if not ispattern:
285                 if pkgarch:
286                     if name == pkgname and str(item.arch()) == pkgarch:
287                         return True
288                 else:
289                     if name == pkgname:
290                         return True
291             else:
292                 if startx and name.endswith(pkg[1:]):
293                     return True
294                 if endx and name.startswith(pkg[:-1]):
295                     return True
296
297         return False
298
299     def deselectPackage(self, pkg):
300         """collect packages should not be installed"""
301         self.to_deselect.append(pkg)
302
303     def selectGroup(self, grp, include = ksparser.GROUP_DEFAULT):
304         def compareGroup(pitem):
305             item = zypp.asKindPattern(pitem)
306             return item.repoInfo().priority()
307         if not self.Z:
308             self.__initialize_zypp()
309         found = False
310         q = zypp.PoolQuery()
311         q.addKind(zypp.ResKind.pattern)
312         for pitem in sorted(q.queryResults(self.Z.pool()), key=compareGroup):
313             item = zypp.asKindPattern(pitem)
314             summary = "%s" % item.summary()
315             name = "%s" % item.name()
316             if name == grp or summary == grp:
317                 found = True
318                 pitem.status().setToBeInstalled (zypp.ResStatus.USER)
319                 break
320
321         if found:
322             if include == ksparser.GROUP_REQUIRED:
323                 map(
324                     lambda p: self.deselectPackage(p),
325                     grp.default_packages.keys())
326
327             return None
328         else:
329             raise CreatorError("Unable to find pattern: %s" % (grp,))
330
331     def addRepository(self, name,
332                             url = None,
333                             mirrorlist = None,
334                             proxy = None,
335                             proxy_username = None,
336                             proxy_password = None,
337                             inc = None,
338                             exc = None,
339                             ssl_verify = True,
340                             nocache = False,
341                             cost=None,
342                             priority=None):
343         # TODO: Handle cost attribute for repos
344
345         if not self.repo_manager:
346             self.__initialize_repo_manager()
347
348         if not proxy and url:
349             proxy = get_proxy_for(url)
350
351         repo = RepositoryStub()
352         repo.name = name
353         repo.id = name
354         repo.proxy = proxy
355         repo.proxy_username = proxy_username
356         repo.proxy_password = proxy_password
357         repo.ssl_verify = ssl_verify
358         repo.nocache = nocache
359         repo.baseurl.append(url)
360         if inc:
361             for pkg in inc:
362                 self.incpkgs[pkg] = name
363         if exc:
364             for pkg in exc:
365                 self.excpkgs[pkg] = name
366
367         if mirrorlist:
368             repo.mirrorlist = mirrorlist
369
370         # Enable gpg check for verifying corrupt packages
371         repo.gpgcheck = 1
372         if priority is not None:
373             # priority 0 has issue in RepoInfo.setPriority
374             repo.priority = priority + 1
375
376         try:
377             repo_info = zypp.RepoInfo()
378             repo_info.setAlias(repo.name)
379             repo_info.setName(repo.name)
380             repo_info.setEnabled(repo.enabled)
381             repo_info.setAutorefresh(repo.autorefresh)
382             repo_info.setKeepPackages(repo.keeppackages)
383             baseurl = zypp.Url(repo.baseurl[0].full)
384             if not ssl_verify:
385                 baseurl.setQueryParam("ssl_verify", "no")
386             if proxy:
387                 host = urlparse.urlparse(proxy)[1]
388                 # scheme, host, path, parm, query, frag = urlparse.urlparse(proxy)
389
390                 proxyinfo = host.rsplit(":", 1)
391                 host = proxyinfo[0]
392
393                 port = "80"
394                 if len(proxyinfo) > 1:
395                     port = proxyinfo[1]
396
397                 if proxy.startswith("socks") and len(proxy.rsplit(':', 1)) == 2:
398                     host = proxy.rsplit(':', 1)[0]
399                     port = proxy.rsplit(':', 1)[1]
400
401                 # parse user/pass from proxy host
402                 proxyinfo = host.rsplit("@", 1)
403                 if len(proxyinfo) == 2:
404                     host = proxyinfo[1]
405                     # Known Issue: If password contains ":", which should be
406                     # quoted, for example, use '123%3Aabc' instead of 123:abc
407                     userpassinfo = proxyinfo[0].rsplit(":", 1)
408                     if len(userpassinfo) == 2:
409                         proxy_username = userpassinfo[0]
410                         proxy_password = userpassinfo[1]
411                     elif len(userpassinfo) == 1:
412                         proxy_username = userpassinfo[0]
413
414                 baseurl.setQueryParam ("proxy", host)
415                 baseurl.setQueryParam ("proxyport", port)
416                 if proxy_username:
417                     baseurl.setQueryParam ("proxyuser", proxy_username)
418                 if proxy_password:
419                     baseurl.setQueryParam ("proxypass", proxy_password)
420             else:
421                 baseurl.setQueryParam ("proxy", "_none_")
422
423             self.repos.append(repo)
424
425             repo_info.addBaseUrl(baseurl)
426
427             if repo.priority is not None:
428                 repo_info.setPriority(repo.priority)
429
430             # this hack is used to change zypp credential file location
431             # the default one is $HOME/.zypp, which cause conflicts when
432             # installing some basic packages, and the location doesn't
433             # have any interface actually, so use a tricky way anyway
434             homedir = None
435             if 'HOME' in os.environ:
436                 homedir = os.environ['HOME']
437                 os.environ['HOME'] = '/'
438             else:
439                 os.environ['HOME'] = '/'
440
441             self.repo_manager.addRepository(repo_info)
442
443             # save back the $HOME env
444             if homedir:
445                 os.environ['HOME'] = homedir
446             else:
447                 del os.environ['HOME']
448
449             self.__build_repo_cache(name)
450
451         except RuntimeError as e:
452             raise CreatorError(str(e))
453
454         msger.verbose('repo: %s was added' % name)
455         return repo
456
457     def installHasFile(self, file):
458         return False
459
460     def preInstall(self, pkg):
461         self.pre_pkgs.append(pkg)
462
463     def checkPackage(self, pkg):
464         self.check_pkgs.append(pkg)
465
466     def _get_local_packages(self):
467         """Return a list of rpm path to be local installed.
468         This is the hook where subclasses may specify a set of rpms which
469         it requires to be installed locally.
470         This returns an empty list by default.
471         Note, subclasses should usually chain up to the base class
472         implementation of this hook.
473         """
474         cropts = configmgr.create
475         if cropts['local_pkgs_path']:
476             if os.path.isdir(cropts['local_pkgs_path']):
477                 return glob.glob(
478                         os.path.join(cropts['local_pkgs_path'], '*.rpm'))
479             elif os.path.splitext(cropts['local_pkgs_path'])[-1] == '.rpm':
480                 return [cropts['local_pkgs_path']]
481         return []
482     def __localinst_packages(self):
483         for rpm_path in self._get_local_packages():
484             self.installLocal(rpm_path)
485     def runInstall(self, checksize = 0):
486         os.environ["HOME"] = "/"
487         os.environ["LD_PRELOAD"] = ""
488         self.buildTransaction()
489         self.__localinst_packages()
490
491         todo = zypp.GetResolvablesToInsDel(self.Z.pool())
492         installed_pkgs = todo._toInstall
493         dlpkgs = []
494
495         for pitem in installed_pkgs:
496             if not zypp.isKindPattern(pitem) and \
497               not self.inDeselectPackages(pitem):
498                 item = zypp.asKindPackage(pitem)
499                 dlpkgs.append(item)
500
501                 if item.name() in self.check_pkgs:
502                     self.check_pkgs.remove(item.name())
503
504                 if not self.install_debuginfo or str(item.arch()) == "noarch":
505                     continue
506
507                 dipkg = self._zyppQueryPackage("%s-debuginfo" % item.name())
508                 if dipkg:
509                     ditem = zypp.asKindPackage(dipkg)
510                     dlpkgs.append(ditem)
511                 else:
512                     msger.warning("No debuginfo rpm found for: %s" \
513                                   % item.name())
514
515         if self.check_pkgs:
516             raise CreatorError('Packages absent in image: %s' % ','.join(self.check_pkgs))
517
518         # record all pkg and the content
519         localpkgs = self.localpkgs.keys()
520         for pkg in dlpkgs:
521             license = ''
522             if pkg.name() in localpkgs:
523                 hdr = rpmmisc.readRpmHeader(self.ts, self.localpkgs[pkg.name()])
524                 pkg_long_name = misc.RPM_FMT % {
525                                     'name': hdr['name'],
526                                     'arch': hdr['arch'],
527                                     'version': hdr['version'],
528                                     'release': hdr['release']
529                                 }
530                 license = hdr['license']
531
532             else:
533                 pkg_long_name = misc.RPM_FMT % {
534                                     'name': pkg.name(),
535                                     'arch': pkg.arch(),
536                                     'version': pkg.edition().version(),
537                                     'release': pkg.edition().release()
538                                 }
539
540                 license = pkg.license()
541
542             if license in self.__pkgs_license.keys():
543                 self.__pkgs_license[license].append(pkg_long_name)
544             else:
545                 self.__pkgs_license[license] = [pkg_long_name]
546
547         total_count = len(dlpkgs)
548         cached_count = 0
549         download_total_size = sum(map(lambda x: int(x.downloadSize()), dlpkgs))
550         localpkgs = self.localpkgs.keys()
551
552         msger.info("Checking packages cached ...")
553         for po in dlpkgs:
554             # Check if it is cached locally
555             if po.name() in localpkgs:
556                 cached_count += 1
557             else:
558                 local = self.getLocalPkgPath(po)
559                 name = str(po.repoInfo().name())
560                 try:
561                     repo = filter(lambda r: r.name == name, self.repos)[0]
562                 except IndexError:
563                     repo = None
564                 nocache = repo.nocache if repo else False
565
566                 if os.path.exists(local):
567                     if nocache or self.checkPkg(local) !=0:
568                         os.unlink(local)
569                     else:
570                         download_total_size -= int(po.downloadSize())
571                         cached_count += 1
572         cache_avail_size = misc.get_filesystem_avail(self.cachedir)
573         if cache_avail_size < download_total_size:
574             raise CreatorError("No enough space used for downloading.")
575
576         # record the total size of installed pkgs
577         install_total_size = sum(map(lambda x: int(x.installSize()), dlpkgs))
578         # check needed size before actually download and install
579
580         # FIXME: for multiple partitions for loop type, check fails
581         #        skip the check temporarily
582         #if checksize and install_total_size > checksize:
583         #    raise CreatorError("No enough space used for installing, "
584         #                       "please resize partition size in ks file")
585
586         download_count =  total_count - cached_count
587         msger.info("Packages: %d Total, %d Cached, %d Missed" \
588                    % (total_count, cached_count, download_count))
589
590         try:
591             if download_count > 0:
592                 msger.info("Downloading packages ...")
593             self.downloadPkgs(dlpkgs, download_count)
594         except CreatorError as e:
595             raise CreatorError("Package download failed: %s" %(e,))
596
597         try:
598             self.installPkgs(dlpkgs)
599         except (RepoError, RpmError):
600             raise
601         except Exception as e:
602             raise CreatorError("Package installation failed: %s" % (e,))
603
604     def getVcsInfo(self):
605         if self.__pkgs_vcsinfo:
606             return
607
608         if not self.ts:
609             self.__initialize_transaction()
610
611         mi = self.ts.dbMatch()
612         for hdr in mi:
613             lname = misc.RPM_FMT % {
614                         'name': hdr['name'],
615                         'arch': hdr['arch'],
616                         'version': hdr['version'],
617                         'release': hdr['release']
618                     }
619             try:
620                 self.__pkgs_vcsinfo[lname] = hdr['VCS']
621             except ValueError:
622                 # if rpm not support VCS, set to None
623                 self.__pkgs_vcsinfo[lname] = None
624
625         return self.__pkgs_vcsinfo
626
627     def getAllContent(self):
628         if self.__pkgs_content:
629             return self.__pkgs_content
630
631         if not self.ts:
632             self.__initialize_transaction()
633
634         mi = self.ts.dbMatch()
635         for hdr in mi:
636             lname = misc.RPM_FMT % {
637                         'name': hdr['name'],
638                         'arch': hdr['arch'],
639                         'version': hdr['version'],
640                         'release': hdr['release']
641                     }
642             self.__pkgs_content[lname] = hdr['FILENAMES']
643
644         return self.__pkgs_content
645
646     def getPkgsLicense(self):
647         return self.__pkgs_license
648
649     def getFilelist(self, pkgname):
650         if not pkgname:
651             return None
652
653         if not self.ts:
654             self.__initialize_transaction()
655
656         mi = self.ts.dbMatch('name', pkgname)
657         for header in mi:
658             return header['FILENAMES']
659
660     def __initialize_repo_manager(self):
661         if self.repo_manager:
662             return
663
664         # Clean up repo metadata
665         shutil.rmtree(self.cachedir + "/etc", ignore_errors = True)
666         shutil.rmtree(self.cachedir + "/solv", ignore_errors = True)
667         shutil.rmtree(self.cachedir + "/raw", ignore_errors = True)
668
669         zypp.KeyRing.setDefaultAccept( zypp.KeyRing.ACCEPT_UNSIGNED_FILE
670                                      | zypp.KeyRing.ACCEPT_VERIFICATION_FAILED
671                                      | zypp.KeyRing.ACCEPT_UNKNOWNKEY
672                                      | zypp.KeyRing.TRUST_KEY_TEMPORARILY
673                                      )
674
675         self.repo_manager_options = \
676                 zypp.RepoManagerOptions(zypp.Pathname(self.instroot))
677
678         self.repo_manager_options.knownReposPath = \
679                 zypp.Pathname(self.cachedir + "/etc/zypp/repos.d")
680
681         self.repo_manager_options.repoCachePath = \
682                 zypp.Pathname(self.cachedir)
683
684         self.repo_manager_options.repoRawCachePath = \
685                 zypp.Pathname(self.cachedir + "/raw")
686
687         self.repo_manager_options.repoSolvCachePath = \
688                 zypp.Pathname(self.cachedir + "/solv")
689
690         self.repo_manager_options.repoPackagesCachePath = \
691                 zypp.Pathname(self.cachedir + "/packages")
692
693         self.repo_manager = zypp.RepoManager(self.repo_manager_options)
694
695     def __build_repo_cache(self, name):
696         repo = self.repo_manager.getRepositoryInfo(name)
697         if self.repo_manager.isCached(repo) or not repo.enabled():
698             return
699
700         msger.info('Refreshing repository: %s ...' % name)
701         self.repo_manager.buildCache(repo, zypp.RepoManager.BuildIfNeeded)
702
703     def __initialize_zypp(self):
704         if self.Z:
705             return
706
707         zconfig = zypp.ZConfig_instance()
708
709         # Set system architecture
710         if self.target_arch:
711             zconfig.setSystemArchitecture(zypp.Arch(self.target_arch))
712
713         msger.info("zypp architecture is <%s>" % zconfig.systemArchitecture())
714
715         # repoPackagesCachePath is corrected by this
716         self.repo_manager = zypp.RepoManager(self.repo_manager_options)
717         repos = self.repo_manager.knownRepositories()
718         for repo in repos:
719             if not repo.enabled():
720                 continue
721             self.repo_manager.loadFromCache(repo)
722
723         self.Z = zypp.ZYppFactory_instance().getZYpp()
724         if configmgr.create['block_recommends']:
725             msger.info("zypp not install recommend packages")
726             self.Z.resolver().setOnlyRequires(True)
727         self.Z.initializeTarget(zypp.Pathname(self.instroot))
728         self.Z.target().load()
729
730     def buildTransaction(self):
731         if not self.Z.resolver().resolvePool():
732             probs = self.Z.resolver().problems()
733
734             for problem in probs:
735                 msger.warning("repo problem: %s, %s" \
736                               % (problem.description().decode("utf-8"),
737                                  problem.details().decode("utf-8")))
738
739             raise RepoError("found %d resolver problem, abort!" \
740                             % len(probs))
741
742     def getLocalPkgPath(self, po):
743         repoinfo = po.repoInfo()
744         cacheroot = repoinfo.packagesPath()
745         location = po.location()
746         rpmpath = str(location.filename())
747         pkgpath = "%s/%s" % (cacheroot, os.path.basename(rpmpath))
748         return pkgpath
749
750     def installLocal(self, pkg, po=None, updateonly=False):
751         if not self.ts:
752             self.__initialize_transaction()
753
754         solvfile = "%s/.solv" % (self.cachedir)
755
756         rc, out = runner.runtool([fs_related.find_binary_path("rpms2solv"),
757                                   pkg])
758         if rc == 0:
759             f = open(solvfile, "w+")
760             f.write(out)
761             f.close()
762
763             warnmsg = self.repo_manager.loadSolvFile(solvfile,
764                                                      os.path.basename(pkg))
765             if warnmsg:
766                 msger.warning(warnmsg)
767
768             os.unlink(solvfile)
769         else:
770             msger.warning('Can not get %s solv data.' % pkg)
771
772         hdr = rpmmisc.readRpmHeader(self.ts, pkg)
773         arch = zypp.Arch(hdr['arch'])
774         sysarch = zypp.Arch(self.target_arch)
775
776         if arch.compatible_with (sysarch):
777             pkgname = hdr['name']
778             self.localpkgs[pkgname] = pkg
779             self.selectPackage(pkgname)
780             msger.info("Marking %s to be installed" % (pkg))
781
782         else:
783             msger.warning("Cannot add package %s to transaction. "
784                           "Not a compatible architecture: %s" \
785                           % (pkg, hdr['arch']))
786
787     def downloadPkgs(self, package_objects, count):
788         localpkgs = self.localpkgs.keys()
789         progress_obj = TextProgress(count)
790
791         for po in package_objects:
792             if po.name() in localpkgs:
793                 continue
794
795             filename = self.getLocalPkgPath(po)
796             if os.path.exists(filename):
797                 if self.checkPkg(filename) == 0:
798                     continue
799
800             dirn = os.path.dirname(filename)
801             if not os.path.exists(dirn):
802                 os.makedirs(dirn)
803
804             url = self.get_url(po)
805             proxies = self.get_proxies(po)
806
807             try:
808                 filename = myurlgrab(url.full, filename, proxies, progress_obj)
809             except CreatorError:
810                 self.close()
811                 raise
812
813     def preinstallPkgs(self):
814         if not self.ts_pre:
815             self.__initialize_transaction()
816
817         self.ts_pre.order()
818         cb = rpmmisc.RPMInstallCallback(self.ts_pre)
819         cb.headmsg = "Preinstall"
820         installlogfile = "%s/__catched_stderr.buf" % (self.instroot)
821
822         # start to catch stderr output from librpm
823         msger.enable_logstderr(installlogfile)
824
825         errors = self.ts_pre.run(cb.callback, '')
826         # stop catch
827         msger.disable_logstderr()
828         self.ts_pre.closeDB()
829         self.ts_pre = None
830
831         if errors is not None:
832             if len(errors) == 0:
833                 msger.warning('scriptlet or other non-fatal errors occurred '
834                               'during transaction.')
835
836             else:
837                 for e in errors:
838                     msger.warning(e[0])
839                 raise RepoError('Could not run transaction.')
840     def show_unresolved_dependencies_msg(self, unresolved_dependencies):
841         for pkg, need, needflags, sense, key in unresolved_dependencies:
842
843             package = '-'.join(pkg)
844
845             if needflags == rpm.RPMSENSE_LESS:
846                 deppkg = ' < '.join(need)
847             elif needflags == rpm.RPMSENSE_EQUAL:
848                 deppkg = ' = '.join(need)
849             elif needflags == rpm.RPMSENSE_GREATER:
850                 deppkg = ' > '.join(need)
851             else:
852                 deppkg = '-'.join(need)
853
854             if sense == rpm.RPMDEP_SENSE_REQUIRES:
855                 msger.warning("[%s] Requires [%s], which is not provided" \
856                               % (package, deppkg))
857
858             elif sense == rpm.RPMDEP_SENSE_CONFLICTS:
859                 msger.warning("[%s] Conflicts with [%s]" % (package, deppkg))
860
861     def installPkgs(self, package_objects):
862         if not self.ts:
863             self.__initialize_transaction()
864
865         # clean rpm lock
866         self._cleanupRpmdbLocks(self.instroot)
867         self._cleanupZyppJunk(self.instroot)
868         # Set filters
869         probfilter = 0
870         for flag in self.probFilterFlags:
871             probfilter |= flag
872         self.ts.setProbFilter(probfilter)
873         self.ts_pre.setProbFilter(probfilter)
874
875         localpkgs = self.localpkgs.keys()
876
877         for po in package_objects:
878             pkgname = po.name()
879             if pkgname in localpkgs:
880                 rpmpath = self.localpkgs[pkgname]
881             else:
882                 rpmpath = self.getLocalPkgPath(po)
883
884             if not os.path.exists(rpmpath):
885                 # Maybe it is a local repo
886                 rpmuri = self.get_url(po)
887                 if rpmuri.startswith("file:/"):
888                     rpmpath = rpmuri[5:]
889
890             if not os.path.exists(rpmpath):
891                 raise RpmError("Error: %s doesn't exist" % rpmpath)
892
893             h = rpmmisc.readRpmHeader(self.ts, rpmpath)
894
895             if pkgname in self.pre_pkgs:
896                 msger.verbose("pre-install package added: %s" % pkgname)
897                 self.ts_pre.addInstall(h, rpmpath, 'u')
898
899             self.ts.addInstall(h, rpmpath, 'u')
900
901         unresolved_dependencies = self.ts.check()
902         if not unresolved_dependencies:
903             if self.pre_pkgs:
904                 self.preinstallPkgs()
905
906             self.ts.order()
907             cb = rpmmisc.RPMInstallCallback(self.ts)
908             installlogfile = "%s/__catched_stderr.buf" % (self.instroot)
909
910             # start to catch stderr output from librpm
911             msger.enable_logstderr(installlogfile)
912
913             errors = self.ts.run(cb.callback, '')
914             # stop catch
915             msger.disable_logstderr()
916             self.ts.closeDB()
917             self.ts = None
918
919             if errors is not None:
920                 if len(errors) == 0:
921                     msger.warning('scriptlet or other non-fatal errors occurred '
922                                   'during transaction.')
923                     if self.strict_mode:
924                         raise CreatorError("mic failes to install some packages")
925                 else:
926                     for e in errors:
927                         msger.warning(e[0])
928                     raise RepoError('Could not run transaction.')
929
930         else:
931             self.show_unresolved_dependencies_msg(unresolved_dependencies)
932             raise RepoError("Unresolved dependencies, transaction failed.")
933
934     def __initialize_transaction(self):
935         if not self.ts:
936             self.ts = rpm.TransactionSet(self.instroot)
937             # Set to not verify DSA signatures.
938             self.ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES|rpm._RPMVSF_NODIGESTS)
939
940         if not self.ts_pre:
941             self.ts_pre = rpm.TransactionSet(self.instroot)
942             # Just unpack the files, don't run scripts
943             self.ts_pre.setFlags(rpm.RPMTRANS_FLAG_ALLFILES | rpm.RPMTRANS_FLAG_NOSCRIPTS)
944             # Set to not verify DSA signatures.
945             self.ts_pre.setVSFlags(rpm._RPMVSF_NOSIGNATURES|rpm._RPMVSF_NODIGESTS)
946
947     def checkPkg(self, pkg):
948         ret = 1
949         if not os.path.exists(pkg):
950             return ret
951         ret = rpmmisc.checkRpmIntegrity('rpm', pkg)
952         if ret != 0:
953             msger.warning("package %s is damaged: %s" \
954                           % (os.path.basename(pkg), pkg))
955
956         return ret
957
958     def _add_prob_flags(self, *flags):
959         for flag in flags:
960             if flag not in self.probFilterFlags:
961                 self.probFilterFlags.append(flag)
962
963     def get_proxies(self, pobj):
964         if not pobj:
965             return None
966
967         proxy = None
968         proxies = None
969         repoinfo = pobj.repoInfo()
970         reponame = "%s" % repoinfo.name()
971         repos = filter(lambda r: r.name == reponame, self.repos)
972         repourl = str(repoinfo.baseUrls()[0])
973
974         if repos:
975             proxy = repos[0].proxy
976         if not proxy:
977             proxy = get_proxy_for(repourl)
978         if proxy:
979             proxies = {str(repourl.split(':')[0]): str(proxy)}
980
981         return proxies
982
983     def get_url(self, pobj):
984         if not pobj:
985             return None
986
987         name = str(pobj.repoInfo().name())
988         try:
989             repo = filter(lambda r: r.name == name, self.repos)[0]
990         except IndexError:
991             return None
992
993         location = pobj.location()
994         location = str(location.filename())
995         if location.startswith("./"):
996             location = location[2:]
997
998         return repo.baseurl[0].join(location)
999
1000     def package_url(self, pkgname):
1001
1002         def cmpEVR(p1, p2):
1003             ed1 = p1.edition()
1004             ed2 = p2.edition()
1005             (e1, v1, r1) = map(str, [ed1.epoch(), ed1.version(), ed1.release()])
1006             (e2, v2, r2) = map(str, [ed2.epoch(), ed2.version(), ed2.release()])
1007             return rpm.labelCompare((e1, v1, r1), (e2, v2, r2))
1008
1009         if not self.Z:
1010             self.__initialize_zypp()
1011
1012         q = zypp.PoolQuery()
1013         q.addKind(zypp.ResKind.package)
1014         q.setMatchExact()
1015         q.addAttribute(zypp.SolvAttr.name, pkgname)
1016         items = sorted(q.queryResults(self.Z.pool()),
1017                        cmp=lambda x,y: cmpEVR(zypp.asKindPackage(x), zypp.asKindPackage(y)),
1018                        reverse=True)
1019
1020         if items:
1021             item = zypp.asKindPackage(items[0])
1022             url = self.get_url(item)
1023             proxies = self.get_proxies(item)
1024             return (url, proxies)
1025
1026         return (None, None)