Add block-recommends option to choose if install recommmended packages
[tools/mic.git] / plugins / backend / zypppkgmgr.py
1 #!/usr/bin/python3 -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 urllib.parse
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) = list(map(str, [ed1.epoch(), ed1.version(), ed1.release()]))
194             (e2, v2, r2) = list(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 list(self.excpkgs.keys()) and \
230                self.excpkgs[item.name()] == item.repoInfo().name():
231                 continue
232             if item.name() in list(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 list(self.excpkgs.keys()) and \
258                    self.excpkgs[item.name()] == item.repoInfo().name():
259                     continue
260                 if item.name() in list(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                 list([self.deselectPackage(p) for p in list(grp.default_packages.keys())])
324
325             return None
326         else:
327             raise CreatorError("Unable to find pattern: %s" % (grp,))
328
329     def addRepository(self, name,
330                             url = None,
331                             mirrorlist = None,
332                             proxy = None,
333                             proxy_username = None,
334                             proxy_password = None,
335                             inc = None,
336                             exc = None,
337                             ssl_verify = True,
338                             nocache = False,
339                             cost=None,
340                             priority=None):
341         # TODO: Handle cost attribute for repos
342
343         if not self.repo_manager:
344             self.__initialize_repo_manager()
345
346         if not proxy and url:
347             proxy = get_proxy_for(url)
348
349         repo = RepositoryStub()
350         repo.name = name
351         repo.id = name
352         repo.proxy = proxy
353         repo.proxy_username = proxy_username
354         repo.proxy_password = proxy_password
355         repo.ssl_verify = ssl_verify
356         repo.nocache = nocache
357         repo.baseurl.append(url)
358         if inc:
359             for pkg in inc:
360                 self.incpkgs[pkg] = name
361         if exc:
362             for pkg in exc:
363                 self.excpkgs[pkg] = name
364
365         if mirrorlist:
366             repo.mirrorlist = mirrorlist
367
368         # Enable gpg check for verifying corrupt packages
369         repo.gpgcheck = 1
370         if priority is not None:
371             # priority 0 has issue in RepoInfo.setPriority
372             repo.priority = priority + 1
373
374         try:
375             repo_info = zypp.RepoInfo()
376             repo_info.setAlias(repo.name)
377             repo_info.setName(repo.name)
378             repo_info.setEnabled(repo.enabled)
379             repo_info.setAutorefresh(repo.autorefresh)
380             repo_info.setKeepPackages(repo.keeppackages)
381             baseurl = zypp.Url(repo.baseurl[0].full)
382             if not ssl_verify:
383                 baseurl.setQueryParam("ssl_verify", "no")
384             if proxy:
385                 host = urllib.parse.urlparse(proxy)[1]
386                 # scheme, host, path, parm, query, frag = urlparse.urlparse(proxy)
387
388                 proxyinfo = host.rsplit(":", 1)
389                 host = proxyinfo[0]
390
391                 port = "80"
392                 if len(proxyinfo) > 1:
393                     port = proxyinfo[1]
394
395                 if proxy.startswith("socks") and len(proxy.rsplit(':', 1)) == 2:
396                     host = proxy.rsplit(':', 1)[0]
397                     port = proxy.rsplit(':', 1)[1]
398
399                 # parse user/pass from proxy host
400                 proxyinfo = host.rsplit("@", 1)
401                 if len(proxyinfo) == 2:
402                     host = proxyinfo[1]
403                     # Known Issue: If password contains ":", which should be
404                     # quoted, for example, use '123%3Aabc' instead of 123:abc
405                     userpassinfo = proxyinfo[0].rsplit(":", 1)
406                     if len(userpassinfo) == 2:
407                         proxy_username = userpassinfo[0]
408                         proxy_password = userpassinfo[1]
409                     elif len(userpassinfo) == 1:
410                         proxy_username = userpassinfo[0]
411
412                 baseurl.setQueryParam ("proxy", host)
413                 baseurl.setQueryParam ("proxyport", port)
414                 if proxy_username:
415                     baseurl.setQueryParam ("proxyuser", proxy_username)
416                 if proxy_password:
417                     baseurl.setQueryParam ("proxypass", proxy_password)
418             else:
419                 baseurl.setQueryParam ("proxy", "_none_")
420
421             self.repos.append(repo)
422
423             repo_info.addBaseUrl(baseurl)
424
425             if repo.priority is not None:
426                 repo_info.setPriority(repo.priority)
427
428             # this hack is used to change zypp credential file location
429             # the default one is $HOME/.zypp, which cause conflicts when
430             # installing some basic packages, and the location doesn't
431             # have any interface actually, so use a tricky way anyway
432             homedir = None
433             if 'HOME' in os.environ:
434                 homedir = os.environ['HOME']
435                 os.environ['HOME'] = '/'
436             else:
437                 os.environ['HOME'] = '/'
438
439             self.repo_manager.addRepository(repo_info)
440
441             # save back the $HOME env
442             if homedir:
443                 os.environ['HOME'] = homedir
444             else:
445                 del os.environ['HOME']
446
447             self.__build_repo_cache(name)
448
449         except RuntimeError as e:
450             raise CreatorError(str(e))
451
452         msger.verbose('repo: %s was added' % name)
453         return repo
454
455     def installHasFile(self, file):
456         return False
457
458     def preInstall(self, pkg):
459         self.pre_pkgs.append(pkg)
460
461     def checkPackage(self, pkg):
462         self.check_pkgs.append(pkg)
463
464     def _get_local_packages(self):
465         """Return a list of rpm path to be local installed.
466         This is the hook where subclasses may specify a set of rpms which
467         it requires to be installed locally.
468         This returns an empty list by default.
469         Note, subclasses should usually chain up to the base class
470         implementation of this hook.
471         """
472         cropts = configmgr.create
473         if cropts['local_pkgs_path']:
474             if os.path.isdir(cropts['local_pkgs_path']):
475                 return glob.glob(
476                         os.path.join(cropts['local_pkgs_path'], '*.rpm'))
477             elif os.path.splitext(cropts['local_pkgs_path'])[-1] == '.rpm':
478                 return [cropts['local_pkgs_path']]
479         return []
480     def __localinst_packages(self):
481         for rpm_path in self._get_local_packages():
482             self.installLocal(rpm_path)
483     def runInstall(self, checksize = 0):
484         os.environ["HOME"] = "/"
485         os.environ["LD_PRELOAD"] = ""
486         self.buildTransaction()
487         self.__localinst_packages()
488
489         todo = zypp.GetResolvablesToInsDel(self.Z.pool())
490         installed_pkgs = todo._toInstall
491         dlpkgs = []
492
493         for pitem in installed_pkgs:
494             if not zypp.isKindPattern(pitem) and \
495               not self.inDeselectPackages(pitem):
496                 item = zypp.asKindPackage(pitem)
497                 dlpkgs.append(item)
498
499                 if item.name() in self.check_pkgs:
500                     self.check_pkgs.remove(item.name())
501
502                 if not self.install_debuginfo or str(item.arch()) == "noarch":
503                     continue
504
505                 dipkg = self._zyppQueryPackage("%s-debuginfo" % item.name())
506                 if dipkg:
507                     ditem = zypp.asKindPackage(dipkg)
508                     dlpkgs.append(ditem)
509                 else:
510                     msger.warning("No debuginfo rpm found for: %s" \
511                                   % item.name())
512
513         if self.check_pkgs:
514             raise CreatorError('Packages absent in image: %s' % ','.join(self.check_pkgs))
515
516         # record all pkg and the content
517         localpkgs = list(self.localpkgs.keys())
518         for pkg in dlpkgs:
519             license = ''
520             if pkg.name() in localpkgs:
521                 hdr = rpmmisc.readRpmHeader(self.ts, self.localpkgs[pkg.name()])
522                 pkg_long_name = misc.RPM_FMT % {
523                                     'name': hdr['name'],
524                                     'arch': hdr['arch'],
525                                     'version': hdr['version'],
526                                     'release': hdr['release']
527                                 }
528                 license = hdr['license']
529
530             else:
531                 pkg_long_name = misc.RPM_FMT % {
532                                     'name': pkg.name(),
533                                     'arch': pkg.arch(),
534                                     'version': pkg.edition().version(),
535                                     'release': pkg.edition().release()
536                                 }
537
538                 license = pkg.license()
539
540             if license in list(self.__pkgs_license.keys()):
541                 self.__pkgs_license[license].append(pkg_long_name)
542             else:
543                 self.__pkgs_license[license] = [pkg_long_name]
544
545         total_count = len(dlpkgs)
546         cached_count = 0
547         download_total_size = sum([int(x.downloadSize()) for x in dlpkgs])
548         localpkgs = list(self.localpkgs.keys())
549
550         msger.info("Checking packages cached ...")
551         for po in dlpkgs:
552             # Check if it is cached locally
553             if po.name() in localpkgs:
554                 cached_count += 1
555             else:
556                 local = self.getLocalPkgPath(po)
557                 name = str(po.repoInfo().name())
558                 try:
559                     repo = filter(lambda r: r.name == name, self.repos)[0]
560                 except IndexError:
561                     repo = None
562                 nocache = repo.nocache if repo else False
563
564                 if os.path.exists(local):
565                     if nocache or self.checkPkg(local) !=0:
566                         os.unlink(local)
567                     else:
568                         download_total_size -= int(po.downloadSize())
569                         cached_count += 1
570         cache_avail_size = misc.get_filesystem_avail(self.cachedir)
571         if cache_avail_size < download_total_size:
572             raise CreatorError("No enough space used for downloading.")
573
574         # record the total size of installed pkgs
575         install_total_size = sum([int(x.installSize()) for x in dlpkgs])
576         # check needed size before actually download and install
577
578         # FIXME: for multiple partitions for loop type, check fails
579         #        skip the check temporarily
580         #if checksize and install_total_size > checksize:
581         #    raise CreatorError("No enough space used for installing, "
582         #                       "please resize partition size in ks file")
583
584         download_count =  total_count - cached_count
585         msger.info("Packages: %d Total, %d Cached, %d Missed" \
586                    % (total_count, cached_count, download_count))
587
588         try:
589             if download_count > 0:
590                 msger.info("Downloading packages ...")
591             self.downloadPkgs(dlpkgs, download_count)
592         except CreatorError as e:
593             raise CreatorError("Package download failed: %s" %(e,))
594
595         try:
596             self.installPkgs(dlpkgs)
597         except (RepoError, RpmError):
598             raise
599         except Exception as e:
600             raise CreatorError("Package installation failed: %s" % (e,))
601
602     def getVcsInfo(self):
603         if self.__pkgs_vcsinfo:
604             return
605
606         if not self.ts:
607             self.__initialize_transaction()
608
609         mi = self.ts.dbMatch()
610         for hdr in mi:
611             lname = misc.RPM_FMT % {
612                         'name': hdr['name'],
613                         'arch': hdr['arch'],
614                         'version': hdr['version'],
615                         'release': hdr['release']
616                     }
617             try:
618                 self.__pkgs_vcsinfo[lname] = hdr['VCS']
619             except ValueError:
620                 # if rpm not support VCS, set to None
621                 self.__pkgs_vcsinfo[lname] = None
622
623         return self.__pkgs_vcsinfo
624
625     def getAllContent(self):
626         if self.__pkgs_content:
627             return self.__pkgs_content
628
629         if not self.ts:
630             self.__initialize_transaction()
631
632         mi = self.ts.dbMatch()
633         for hdr in mi:
634             lname = misc.RPM_FMT % {
635                         'name': hdr['name'],
636                         'arch': hdr['arch'],
637                         'version': hdr['version'],
638                         'release': hdr['release']
639                     }
640             self.__pkgs_content[lname] = hdr['FILENAMES']
641
642         return self.__pkgs_content
643
644     def getPkgsLicense(self):
645         return self.__pkgs_license
646
647     def getFilelist(self, pkgname):
648         if not pkgname:
649             return None
650
651         if not self.ts:
652             self.__initialize_transaction()
653
654         mi = self.ts.dbMatch('name', pkgname)
655         for header in mi:
656             return header['FILENAMES']
657
658     def __initialize_repo_manager(self):
659         if self.repo_manager:
660             return
661
662         # Clean up repo metadata
663         shutil.rmtree(self.cachedir + "/etc", ignore_errors = True)
664         shutil.rmtree(self.cachedir + "/solv", ignore_errors = True)
665         shutil.rmtree(self.cachedir + "/raw", ignore_errors = True)
666
667         zypp.KeyRing.setDefaultAccept( zypp.KeyRing.ACCEPT_UNSIGNED_FILE
668                                      | zypp.KeyRing.ACCEPT_VERIFICATION_FAILED
669                                      | zypp.KeyRing.ACCEPT_UNKNOWNKEY
670                                      | zypp.KeyRing.TRUST_KEY_TEMPORARILY
671                                      )
672
673         self.repo_manager_options = \
674                 zypp.RepoManagerOptions(zypp.Pathname(self.instroot))
675
676         self.repo_manager_options.knownReposPath = \
677                 zypp.Pathname(self.cachedir + "/etc/zypp/repos.d")
678
679         self.repo_manager_options.repoCachePath = \
680                 zypp.Pathname(self.cachedir)
681
682         self.repo_manager_options.repoRawCachePath = \
683                 zypp.Pathname(self.cachedir + "/raw")
684
685         self.repo_manager_options.repoSolvCachePath = \
686                 zypp.Pathname(self.cachedir + "/solv")
687
688         self.repo_manager_options.repoPackagesCachePath = \
689                 zypp.Pathname(self.cachedir + "/packages")
690
691         self.repo_manager = zypp.RepoManager(self.repo_manager_options)
692
693     def __build_repo_cache(self, name):
694         repo = self.repo_manager.getRepositoryInfo(name)
695         if self.repo_manager.isCached(repo) or not repo.enabled():
696             return
697
698         msger.info('Refreshing repository: %s ...' % name)
699         self.repo_manager.buildCache(repo, zypp.RepoManager.BuildIfNeeded)
700
701     def __initialize_zypp(self):
702         if self.Z:
703             return
704
705         zconfig = zypp.ZConfig_instance()
706
707         # Set system architecture
708         if self.target_arch:
709             zconfig.setSystemArchitecture(zypp.Arch(self.target_arch))
710
711         msger.info("zypp architecture is <%s>" % zconfig.systemArchitecture())
712
713         # repoPackagesCachePath is corrected by this
714         self.repo_manager = zypp.RepoManager(self.repo_manager_options)
715         repos = self.repo_manager.knownRepositories()
716         for repo in repos:
717             if not repo.enabled():
718                 continue
719             self.repo_manager.loadFromCache(repo)
720
721         self.Z = zypp.ZYppFactory_instance().getZYpp()
722         if configmgr.create['block_recommends']:
723             msger.info("zypp not install recommend packages")
724             self.Z.resolver().setOnlyRequires(True)
725         self.Z.initializeTarget(zypp.Pathname(self.instroot))
726         self.Z.target().load()
727
728     def buildTransaction(self):
729         if not self.Z.resolver().resolvePool():
730             probs = self.Z.resolver().problems()
731
732             for problem in probs:
733                 msger.warning("repo problem: %s, %s" \
734                               % (problem.description().decode("utf-8"),
735                                  problem.details().decode("utf-8")))
736
737             raise RepoError("found %d resolver problem, abort!" \
738                             % len(probs))
739
740     def getLocalPkgPath(self, po):
741         repoinfo = po.repoInfo()
742         cacheroot = repoinfo.packagesPath()
743         location = po.location()
744         rpmpath = str(location.filename())
745         pkgpath = "%s/%s" % (cacheroot, os.path.basename(rpmpath))
746         return pkgpath
747
748     def installLocal(self, pkg, po=None, updateonly=False):
749         if not self.ts:
750             self.__initialize_transaction()
751
752         solvfile = "%s/.solv" % (self.cachedir)
753
754         rc, out = runner.runtool([fs_related.find_binary_path("rpms2solv"),
755                                   pkg])
756         if rc == 0:
757             f = open(solvfile, "w+")
758             f.write(out)
759             f.close()
760
761             warnmsg = self.repo_manager.loadSolvFile(solvfile,
762                                                      os.path.basename(pkg))
763             if warnmsg:
764                 msger.warning(warnmsg)
765
766             os.unlink(solvfile)
767         else:
768             msger.warning('Can not get %s solv data.' % pkg)
769
770         hdr = rpmmisc.readRpmHeader(self.ts, pkg)
771         arch = zypp.Arch(hdr['arch'])
772         sysarch = zypp.Arch(self.target_arch)
773
774         if arch.compatible_with (sysarch):
775             pkgname = hdr['name']
776             self.localpkgs[pkgname] = pkg
777             self.selectPackage(pkgname)
778             msger.info("Marking %s to be installed" % (pkg))
779
780         else:
781             msger.warning("Cannot add package %s to transaction. "
782                           "Not a compatible architecture: %s" \
783                           % (pkg, hdr['arch']))
784
785     def downloadPkgs(self, package_objects, count):
786         localpkgs = list(self.localpkgs.keys())
787         progress_obj = TextProgress(count)
788
789         for po in package_objects:
790             if po.name() in localpkgs:
791                 continue
792
793             filename = self.getLocalPkgPath(po)
794             if os.path.exists(filename):
795                 if self.checkPkg(filename) == 0:
796                     continue
797
798             dirn = os.path.dirname(filename)
799             if not os.path.exists(dirn):
800                 os.makedirs(dirn)
801
802             url = self.get_url(po)
803             proxies = self.get_proxies(po)
804
805             try:
806                 filename = myurlgrab(url.full, filename, proxies, progress_obj)
807             except CreatorError:
808                 self.close()
809                 raise
810
811     def preinstallPkgs(self):
812         if not self.ts_pre:
813             self.__initialize_transaction()
814
815         self.ts_pre.order()
816         cb = rpmmisc.RPMInstallCallback(self.ts_pre)
817         cb.headmsg = "Preinstall"
818         installlogfile = "%s/__catched_stderr.buf" % (self.instroot)
819
820         # start to catch stderr output from librpm
821         msger.enable_logstderr(installlogfile)
822
823         errors = self.ts_pre.run(cb.callback, '')
824         # stop catch
825         msger.disable_logstderr()
826         self.ts_pre.closeDB()
827         self.ts_pre = None
828
829         if errors is not None:
830             if len(errors) == 0:
831                 msger.warning('scriptlet or other non-fatal errors occurred '
832                               'during transaction.')
833
834             else:
835                 for e in errors:
836                     msger.warning(e[0])
837                 raise RepoError('Could not run transaction.')
838     def show_unresolved_dependencies_msg(self, unresolved_dependencies):
839         for pkg, need, needflags, sense, key in unresolved_dependencies:
840
841             package = '-'.join(pkg)
842
843             if needflags == rpm.RPMSENSE_LESS:
844                 deppkg = ' < '.join(need)
845             elif needflags == rpm.RPMSENSE_EQUAL:
846                 deppkg = ' = '.join(need)
847             elif needflags == rpm.RPMSENSE_GREATER:
848                 deppkg = ' > '.join(need)
849             else:
850                 deppkg = '-'.join(need)
851
852             if sense == rpm.RPMDEP_SENSE_REQUIRES:
853                 msger.warning("[%s] Requires [%s], which is not provided" \
854                               % (package, deppkg))
855
856             elif sense == rpm.RPMDEP_SENSE_CONFLICTS:
857                 msger.warning("[%s] Conflicts with [%s]" % (package, deppkg))
858
859     def installPkgs(self, package_objects):
860         if not self.ts:
861             self.__initialize_transaction()
862
863         # clean rpm lock
864         self._cleanupRpmdbLocks(self.instroot)
865         self._cleanupZyppJunk(self.instroot)
866         # Set filters
867         probfilter = 0
868         for flag in self.probFilterFlags:
869             probfilter |= flag
870         self.ts.setProbFilter(probfilter)
871         self.ts_pre.setProbFilter(probfilter)
872
873         localpkgs = list(self.localpkgs.keys())
874
875         for po in package_objects:
876             pkgname = po.name()
877             if pkgname in localpkgs:
878                 rpmpath = self.localpkgs[pkgname]
879             else:
880                 rpmpath = self.getLocalPkgPath(po)
881
882             if not os.path.exists(rpmpath):
883                 # Maybe it is a local repo
884                 rpmuri = self.get_url(po)
885                 if rpmuri.startswith("file:/"):
886                     rpmpath = rpmuri[5:]
887
888             if not os.path.exists(rpmpath):
889                 raise RpmError("Error: %s doesn't exist" % rpmpath)
890
891             h = rpmmisc.readRpmHeader(self.ts, rpmpath)
892
893             if pkgname in self.pre_pkgs:
894                 msger.verbose("pre-install package added: %s" % pkgname)
895                 self.ts_pre.addInstall(h, rpmpath, 'u')
896
897             self.ts.addInstall(h, rpmpath, 'u')
898
899         unresolved_dependencies = self.ts.check()
900         if not unresolved_dependencies:
901             if self.pre_pkgs:
902                 self.preinstallPkgs()
903
904             self.ts.order()
905             cb = rpmmisc.RPMInstallCallback(self.ts)
906             installlogfile = "%s/__catched_stderr.buf" % (self.instroot)
907
908             # start to catch stderr output from librpm
909             msger.enable_logstderr(installlogfile)
910
911             errors = self.ts.run(cb.callback, '')
912             # stop catch
913             msger.disable_logstderr()
914             self.ts.closeDB()
915             self.ts = None
916
917             if errors is not None:
918                 if len(errors) == 0:
919                     msger.warning('scriptlet or other non-fatal errors occurred '
920                                   'during transaction.')
921                     if self.strict_mode:
922                         raise CreatorError("mic failes to install some packages")
923                 else:
924                     for e in errors:
925                         msger.warning(e[0])
926                     raise RepoError('Could not run transaction.')
927
928         else:
929             self.show_unresolved_dependencies_msg(unresolved_dependencies)
930             raise RepoError("Unresolved dependencies, transaction failed.")
931
932     def __initialize_transaction(self):
933         if not self.ts:
934             self.ts = rpm.TransactionSet(self.instroot)
935             # Set to not verify DSA signatures.
936             self.ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES|rpm._RPMVSF_NODIGESTS)
937
938         if not self.ts_pre:
939             self.ts_pre = rpm.TransactionSet(self.instroot)
940             # Just unpack the files, don't run scripts
941             self.ts_pre.setFlags(rpm.RPMTRANS_FLAG_ALLFILES | rpm.RPMTRANS_FLAG_NOSCRIPTS)
942             # Set to not verify DSA signatures.
943             self.ts_pre.setVSFlags(rpm._RPMVSF_NOSIGNATURES|rpm._RPMVSF_NODIGESTS)
944
945     def checkPkg(self, pkg):
946         ret = 1
947         if not os.path.exists(pkg):
948             return ret
949         ret = rpmmisc.checkRpmIntegrity('rpm', pkg)
950         if ret != 0:
951             msger.warning("package %s is damaged: %s" \
952                           % (os.path.basename(pkg), pkg))
953
954         return ret
955
956     def _add_prob_flags(self, *flags):
957         for flag in flags:
958             if flag not in self.probFilterFlags:
959                 self.probFilterFlags.append(flag)
960
961     def get_proxies(self, pobj):
962         if not pobj:
963             return None
964
965         proxy = None
966         proxies = None
967         repoinfo = pobj.repoInfo()
968         reponame = "%s" % repoinfo.name()
969         repos = [r for r in self.repos if r.name == reponame]
970         repourl = str(repoinfo.baseUrls()[0])
971
972         if repos:
973             proxy = repos[0].proxy
974         if not proxy:
975             proxy = get_proxy_for(repourl)
976         if proxy:
977             proxies = {str(repourl.split(':')[0]): str(proxy)}
978
979         return proxies
980
981     def get_url(self, pobj):
982         if not pobj:
983             return None
984
985         name = str(pobj.repoInfo().name())
986         try:
987             repo = filter(lambda r: r.name == name, self.repos)[0]
988         except IndexError:
989             return None
990
991         location = pobj.location()
992         location = str(location.filename())
993         if location.startswith("./"):
994             location = location[2:]
995
996         return repo.baseurl[0].join(location)
997
998     def package_url(self, pkgname):
999
1000         def cmpEVR(p1, p2):
1001             ed1 = p1.edition()
1002             ed2 = p2.edition()
1003             (e1, v1, r1) = list(map(str, [ed1.epoch(), ed1.version(), ed1.release()]))
1004             (e2, v2, r2) = list(map(str, [ed2.epoch(), ed2.version(), ed2.release()]))
1005             return rpm.labelCompare((e1, v1, r1), (e2, v2, r2))
1006
1007         if not self.Z:
1008             self.__initialize_zypp()
1009
1010         q = zypp.PoolQuery()
1011         q.addKind(zypp.ResKind.package)
1012         q.setMatchExact()
1013         q.addAttribute(zypp.SolvAttr.name, pkgname)
1014         items = sorted(q.queryResults(self.Z.pool()),
1015                        cmp=lambda x,y: cmpEVR(zypp.asKindPackage(x), zypp.asKindPackage(y)),
1016                        reverse=True)
1017
1018         if items:
1019             item = zypp.asKindPackage(items[0])
1020             url = self.get_url(item)
1021             proxies = self.get_proxies(item)
1022             return (url, proxies)
1023
1024         return (None, None)