1
2 """
3
4 @author: Fabio Erculiani <lxnay@sabayonlinux.org>
5 @contact: lxnay@sabayonlinux.org
6 @copyright: Fabio Erculiani
7 @license: GPL-2
8
9 B{Entropy Package Manager Client Core Interface}.
10
11 """
12
13 import os
14 import sys
15 from entropy.core import Singleton
16 from entropy.output import TextInterface, bold, red, darkred, blue
17 from entropy.db import dbapi2
18 from entropy.client.interfaces.loaders import LoadersMixin
19 from entropy.client.interfaces.cache import CacheMixin
20 from entropy.client.interfaces.dep import CalculatorsMixin
21 from entropy.client.interfaces.methods import RepositoryMixin, MiscMixin, \
22 MatchMixin
23 from entropy.client.interfaces.fetch import FetchersMixin
24 from entropy.client.interfaces.noticeboard import NoticeBoardMixin
25 from entropy.const import etpConst, etpCache, etpUi, const_debug_write, \
26 const_convert_to_unicode
27 from entropy.core.settings.base import SystemSettings
28 from entropy.core.settings.plugins.skel import SystemSettingsPlugin
29 from entropy.misc import LogFile
30 from entropy.exceptions import SystemDatabaseError, RepositoryError
31 from entropy.i18n import _
32
34
35 import entropy.tools as entropyTools
36
37 - def __init__(self, plugin_id, helper_interface):
41
43 """
44 This function collects available repositories configuration files
45 by filling internal dict() __repos_files and __repos_mtime.
46
47 @param system_settings: SystemSettings instance
48 @type system_settings: instance of SystemSettings
49 @return: None
50 @rtype: None
51 """
52
53 self.__repos_mtime = {
54 'repos_license_whitelist': {},
55 'repos_mask': {},
56 'repos_system_mask': {},
57 'repos_critical_updates': {},
58 'repos_keywords': {},
59 }
60 self.__repos_files = {
61 'repos_license_whitelist': {},
62 'repos_mask': {},
63 'repos_system_mask': {},
64 'conflicting_tagged_packages': {},
65 'repos_critical_updates': {},
66 'repos_keywords': {},
67 }
68
69 dmp_dir = etpConst['dumpstoragedir']
70 for repoid in system_settings['repositories']['order']:
71
72 repos_mask_setting = {}
73 repos_mask_mtime = {}
74 repos_lic_wl_setting = {}
75 repos_lic_wl_mtime = {}
76 repo_data = system_settings['repositories']['available'][repoid]
77 repos_sm_mask_setting = {}
78 repos_sm_mask_mtime = {}
79 confl_tagged = {}
80 repos_critical_updates_setting = {}
81 repos_critical_updates_mtime = {}
82 repos_keywords_setting = {}
83 repos_keywords_mtime = {}
84
85 maskpath = os.path.join(repo_data['dbpath'],
86 etpConst['etpdatabasemaskfile'])
87 wlpath = os.path.join(repo_data['dbpath'],
88 etpConst['etpdatabaselicwhitelistfile'])
89 sm_path = os.path.join(repo_data['dbpath'],
90 etpConst['etpdatabasesytemmaskfile'])
91 ct_path = os.path.join(repo_data['dbpath'],
92 etpConst['etpdatabaseconflictingtaggedfile'])
93 critical_path = os.path.join(repo_data['dbpath'],
94 etpConst['etpdatabasecriticalfile'])
95 keywords_path = os.path.join(repo_data['dbpath'],
96 etpConst['etpdatabasekeywordsfile'])
97
98 if os.access(maskpath, os.R_OK) and os.path.isfile(maskpath):
99 repos_mask_setting[repoid] = maskpath
100 repos_mask_mtime[repoid] = dmp_dir + "/repo_" + \
101 repoid + "_" + etpConst['etpdatabasemaskfile'] + ".mtime"
102
103 if os.access(wlpath, os.R_OK) and os.path.isfile(wlpath):
104 repos_lic_wl_setting[repoid] = wlpath
105 repos_lic_wl_mtime[repoid] = dmp_dir + "/repo_" + \
106 repoid + "_" + etpConst['etpdatabaselicwhitelistfile'] + \
107 ".mtime"
108
109 if os.access(sm_path, os.R_OK) and os.path.isfile(sm_path):
110 repos_sm_mask_setting[repoid] = sm_path
111 repos_sm_mask_mtime[repoid] = dmp_dir + "/repo_" + \
112 repoid + "_" + etpConst['etpdatabasesytemmaskfile'] + \
113 ".mtime"
114 if os.access(ct_path, os.R_OK) and os.path.isfile(ct_path):
115 confl_tagged[repoid] = ct_path
116
117 if os.access(critical_path, os.R_OK) and \
118 os.path.isfile(critical_path):
119 repos_critical_updates_setting[repoid] = critical_path
120 repos_critical_updates_mtime[repoid] = dmp_dir + "/repo_" + \
121 repoid + "_" + etpConst['etpdatabasecriticalfile'] + \
122 ".mtime"
123
124 if os.access(keywords_path, os.R_OK) and \
125 os.path.isfile(keywords_path):
126 repos_keywords_setting[repoid] = keywords_path
127 repos_keywords_mtime[repoid] = dmp_dir + "/repo_" + \
128 repoid + "_" + etpConst['etpdatabasekeywordsfile'] + \
129 ".mtime"
130
131 self.__repos_files['repos_mask'].update(repos_mask_setting)
132 self.__repos_mtime['repos_mask'].update(repos_mask_mtime)
133
134 self.__repos_files['repos_license_whitelist'].update(
135 repos_lic_wl_setting)
136 self.__repos_mtime['repos_license_whitelist'].update(
137 repos_lic_wl_mtime)
138
139 self.__repos_files['repos_system_mask'].update(
140 repos_sm_mask_setting)
141 self.__repos_mtime['repos_system_mask'].update(
142 repos_sm_mask_mtime)
143
144 self.__repos_files['conflicting_tagged_packages'].update(
145 confl_tagged)
146
147 self.__repos_files['repos_critical_updates'].update(
148 repos_critical_updates_setting)
149 self.__repos_mtime['repos_critical_updates'].update(
150 repos_critical_updates_mtime)
151
152 self.__repos_files['repos_keywords'].update(repos_keywords_setting)
153 self.__repos_mtime['repos_keywords'].update(repos_keywords_mtime)
154
155 - def __run_post_branch_migration_hooks(self, sys_settings_instance):
156
157
158 if os.getuid() != 0:
159 return
160
161 old_branch_path = etpConst['etp_previous_branch_file']
162 in_branch_upgrade_path = etpConst['etp_in_branch_upgrade_file']
163 current_branch = sys_settings_instance['repositories']['branch']
164
165 def write_current_branch(branch):
166 old_brf = open(old_branch_path, "w")
167 old_brf.write(branch)
168 old_brf.flush()
169 old_brf.close()
170
171 def write_in_branch_upgrade(branch):
172 brf = open(in_branch_upgrade_path, "w")
173 brf.write("in branch upgrade: %s" % (branch,))
174 brf.flush()
175 brf.close()
176
177 if not os.path.isfile(old_branch_path):
178 write_current_branch(current_branch)
179 return
180
181 old_f = open(old_branch_path, "r")
182 old_branch = old_f.readline().strip()
183 old_f.close()
184
185 if old_branch == current_branch:
186 return
187
188 repos, err = self._helper.run_repositories_post_branch_switch_hooks(
189 old_branch, current_branch)
190 if not err:
191 write_in_branch_upgrade(current_branch)
192 write_current_branch(current_branch)
193
194 - def __run_post_branch_upgrade_hooks(self, sys_settings_instance):
195
196
197 if os.getuid() != 0:
198 return
199
200 repos, errors = self._helper.run_repository_post_branch_upgrade_hooks(
201 pretend = True)
202 if not repos:
203
204 return
205
206
207
208
209 try:
210 update, remove, fine, spm_fine = \
211 self._helper.calculate_world_updates(
212 critical_updates = False)
213 except (ValueError, SystemDatabaseError, RepositoryError,):
214
215
216
217 update = 1
218
219 def delete_in_branch_upgrade():
220 br_path = etpConst['etp_in_branch_upgrade_file']
221 if os.access(br_path, os.W_OK) and os.path.isfile(br_path):
222 os.remove(br_path)
223
224
225
226 if not update:
227 self._helper.run_repository_post_branch_upgrade_hooks()
228 delete_in_branch_upgrade()
229
231
232 parser_data = {}
233
234 mask_installed = []
235 mask_installed_keys = {}
236 while (self._helper.clientDbconn != None):
237 try:
238 self._helper.clientDbconn.validateDatabase()
239 except SystemDatabaseError:
240 break
241 mc_cache = set()
242 repos_mask_list = self.__repositories_system_mask(
243 system_settings_instance)
244 m_list = repos_mask_list + system_settings_instance['system_mask']
245 for atom in m_list:
246 m_ids, m_r = self._helper.clientDbconn.atomMatch(atom,
247 multiMatch = True)
248 if m_r != 0:
249 continue
250 mykey = self.entropyTools.dep_getkey(atom)
251 if mykey not in mask_installed_keys:
252 mask_installed_keys[mykey] = set()
253 for m_id in m_ids:
254 if m_id in mc_cache:
255 continue
256 mc_cache.add(m_id)
257 mask_installed.append(m_id)
258 mask_installed_keys[mykey].add(m_id)
259 break
260
261 parser_data.update({
262 'repos_installed': mask_installed,
263 'repos_installed_keys': mask_installed_keys,
264 })
265 return parser_data
266
268 data = {
269 'cache': {},
270 }
271 return data
272
274 """
275 Parser returning system packages mask metadata read from
276 packages.db.keywords file inside the repository directory.
277 This file contains maintainer supplied per-repository extra
278 package keywords.
279 """
280 data = {
281
282
283 'universal': set(),
284
285
286 'packages': {},
287 'packages_ids': None,
288 }
289
290 entries = self.entropyTools.generic_file_content_parser(
291 repo_keywords_path)
292
293
294 for entry in entries:
295 entry = entry.split()
296 if len(entry) == 1:
297
298 item = entry[0]
299 if item == "**":
300 item = ''
301 data['universal'].add(item)
302
303 elif len(entry) > 1:
304
305 pkg = entry[0]
306 keywords = entry[1:]
307 obj = data['packages'].setdefault(pkg, set())
308 obj.update(keywords)
309
310 return data
311
312
314 """
315 Parser returning system packages mask metadata read from
316 packages.db.system_mask file inside the repository directory.
317 This file contains packages that should be always kept
318 installed, extending the already defined (in repository database)
319 set of atoms.
320 """
321 system_mask = []
322 for repoid in self.__repos_files['repos_system_mask']:
323 sys_settings_instance.validate_entropy_cache(
324 self.__repos_files['repos_system_mask'][repoid],
325 self.__repos_mtime['repos_system_mask'][repoid],
326 repoid = repoid)
327 system_mask += [x for x in \
328 self.entropyTools.generic_file_content_parser(
329 self.__repos_files['repos_system_mask'][repoid]) if x \
330 not in system_mask]
331 return system_mask
332
334 """
335 Parser that generates repository settings metadata.
336
337 @param sys_settings_instance: SystemSettings instance
338 @type sys_settings_instance: instance of SystemSettings
339 @return: parsed metadata
340 @rtype: dict
341 """
342
343
344 self.__setup_repos_files(sys_settings_instance)
345
346 data = {
347 'license_whitelist': {},
348 'mask': {},
349 'system_mask': [],
350 'critical_updates': {},
351 'conflicting_tagged_packages': {},
352 'repos_keywords': {},
353 }
354
355
356 """
357 Parser returning licenses considered accepted by default
358 (= GPL compatibles) read from package.lic_whitelist.
359 """
360 for repoid in self.__repos_files['repos_license_whitelist']:
361 sys_settings_instance.validate_entropy_cache(
362 self.__repos_files['repos_license_whitelist'][repoid],
363 self.__repos_mtime['repos_license_whitelist'][repoid],
364 repoid = repoid)
365
366 data['license_whitelist'][repoid] = \
367 self.entropyTools.generic_file_content_parser(
368 self.__repos_files['repos_license_whitelist'][repoid])
369
370
371 """
372 Parser returning packages masked at repository level read from
373 packages.db.mask inside the repository database directory.
374 """
375 for repoid in self.__repos_files['repos_mask']:
376 sys_settings_instance.validate_entropy_cache(
377 self.__repos_files['repos_mask'][repoid],
378 self.__repos_mtime['repos_mask'][repoid], repoid = repoid)
379
380 data['mask'][repoid] = \
381 self.entropyTools.generic_file_content_parser(
382 self.__repos_files['repos_mask'][repoid])
383
384
385 """
386 Parser returning packages masked at repository level read from
387 packages.db.keywords inside the repository database directory.
388 """
389 for repoid in self.__repos_files['repos_keywords']:
390 sys_settings_instance.validate_entropy_cache(
391 self.__repos_files['repos_keywords'][repoid],
392 self.__repos_mtime['repos_keywords'][repoid], repoid = repoid)
393
394 data['repos_keywords'][repoid] = \
395 self.__repositories_repos_keywords(
396 self.__repos_files['repos_keywords'][repoid])
397
398
399 data['system_mask'] = self.__repositories_system_mask(
400 sys_settings_instance)
401
402
403 """
404 Parser returning critical packages list metadata read from
405 packages.db.critical file inside the repository directory.
406 This file contains packages that should be always updated
407 before anything else.
408 """
409 for repoid in self.__repos_files['repos_critical_updates']:
410 sys_settings_instance.validate_entropy_cache(
411 self.__repos_files['repos_critical_updates'][repoid],
412 self.__repos_mtime['repos_critical_updates'][repoid],
413 repoid = repoid)
414 data['critical_updates'][repoid] = \
415 self.entropyTools.generic_file_content_parser(
416 self.__repos_files['repos_critical_updates'][repoid])
417
418
419
420 """
421 Parser returning packages that could have been installed because
422 they aren't in the same scope, but ending up creating critical
423 issues. You can see it as a configurable conflict map.
424 """
425
426 repoids = [x for x in sys_settings_instance['repositories']['order'] \
427 if x in self.__repos_files['conflicting_tagged_packages']]
428 for repoid in repoids:
429 filepath = self.__repos_files['conflicting_tagged_packages'].get(
430 repoid)
431 if os.access(filepath, os.R_OK) and os.path.isfile(filepath):
432 confl_f = open(filepath, "r")
433 content = confl_f.readlines()
434 confl_f.close()
435 content = [x.strip().rsplit("#", 1)[0].strip().split() for x \
436 in content if not x.startswith("#") and x.strip()]
437 for mydata in content:
438 if len(mydata) < 2:
439 continue
440 data['conflicting_tagged_packages'][mydata[0]] = mydata[1:]
441
442 return data
443
445
446 """
447 Parses Entropy client system configuration file.
448
449 @return dict data
450 """
451
452 data = {
453 'filesbackup': etpConst['filesbackup'],
454 'forcedupdates': etpConst['forcedupdates'],
455 'packagehashes': etpConst['packagehashes'],
456 'ignore_spm_downgrades': False,
457 'collisionprotect': etpConst['collisionprotect'],
458 'configprotect': etpConst['configprotect'][:],
459 'configprotectmask': etpConst['configprotectmask'][:],
460 'configprotectskip': etpConst['configprotectskip'][:],
461 }
462
463 cli_conf = etpConst['clientconf']
464 if not (os.path.isfile(cli_conf) and os.access(cli_conf, os.R_OK)):
465 return data
466
467 client_f = open(cli_conf, "r")
468 clientconf = [x.strip() for x in client_f.readlines() if \
469 x.strip() and not x.strip().startswith("#")]
470 client_f.close()
471 for line in clientconf:
472
473 split_line = line.split("|")
474 split_line_len = len(split_line)
475
476 if line.startswith("filesbackup|") and (split_line_len == 2):
477
478 compatopt = split_line[1].strip().lower()
479 if compatopt in ("disable", "disabled", "false", "0", "no",):
480 data['filesbackup'] = False
481
482 elif line.startswith("forcedupdates|") and (split_line_len == 2):
483
484 compatopt = split_line[1].strip().lower()
485 if compatopt in ("disable", "disabled", "false", "0", "no",):
486 data['forcedupdates'] = False
487 else:
488 data['forcedupdates'] = True
489
490 elif line.startswith("packagehashes|") and (split_line_len == 2):
491
492 opts = split_line[1].strip().lower().split()
493 hashes = []
494 for opt in opts:
495 if opt in etpConst['packagehashes']:
496 hashes.append(opt)
497 if hashes:
498 data['packagehashes'] = tuple(hashes)
499
500 elif line.startswith("ignore-spm-downgrades|") and \
501 (split_line_len == 2):
502
503 compatopt = split_line[1].strip().lower()
504 if compatopt in ("enable", "enabled", "true", "1", "yes"):
505 data['ignore_spm_downgrades'] = True
506
507 elif line.startswith("collisionprotect|") and (split_line_len == 2):
508
509 collopt = split_line[1].strip()
510 if collopt.lower() in ("0", "1", "2",):
511 data['collisionprotect'] = int(collopt)
512
513 elif line.startswith("configprotect|") and (split_line_len == 2):
514
515 configprotect = split_line[1].strip()
516 for myprot in configprotect.split():
517 data['configprotect'].append(
518 const_convert_to_unicode(myprot))
519
520 elif line.startswith("configprotectmask|") and \
521 (split_line_len == 2):
522
523 configprotect = split_line[1].strip()
524 for myprot in configprotect.split():
525 data['configprotectmask'].append(
526 const_convert_to_unicode(myprot))
527
528 elif line.startswith("configprotectskip|") and \
529 (split_line_len == 2):
530
531 configprotect = split_line[1].strip()
532 for myprot in configprotect.split():
533 data['configprotectskip'].append(
534 etpConst['systemroot'] + \
535 const_convert_to_unicode(myprot))
536
537 return data
538
539 - def post_setup(self, system_settings_instance):
540 """
541 Reimplemented from SystemSettingsPlugin.
542 """
543
544 if self._helper._can_run_sys_set_hooks:
545
546 self.__run_post_branch_migration_hooks(system_settings_instance)
547
548
549 self.__run_post_branch_upgrade_hooks(system_settings_instance)
550
551
552 -class Client(Singleton, TextInterface, LoadersMixin, CacheMixin, CalculatorsMixin, \
553 RepositoryMixin, MiscMixin, MatchMixin, FetchersMixin, NoticeBoardMixin):
554
555 - def init_singleton(self, indexing = True, noclientdb = 0,
556 xcache = True, user_xcache = False, repo_validation = True,
557 load_ugc = True, url_fetcher = None,
558 multiple_url_fetcher = None):
677
678
680 self.__instance_destroyed = True
681 if hasattr(self, 'clientDbconn'):
682 if self.clientDbconn != None:
683 self.clientDbconn.closeDB()
684 del self.clientDbconn
685 if hasattr(self, 'FileUpdates'):
686 del self.FileUpdates
687 if hasattr(self, 'clientLog'):
688 self.clientLog.close()
689 if hasattr(self, 'SystemSettings') and \
690 hasattr(self, 'sys_settings_client_plugin_id'):
691
692 if hasattr(self.SystemSettings, 'remove_plugin'):
693 try:
694 self.SystemSettings.remove_plugin(
695 self.sys_settings_client_plugin_id)
696 except KeyError:
697 pass
698
699 self.close_all_repositories(mask_clear = False)
700 self.closeAllSecurity()
701 self.closeAllQA()
702
705 """
706 Service method used to sync package names with Source Package Manager
707 via metadata stored in Repository dbs collected at server-time.
708 Source Package Manager can change package names, categories or slot
709 and Entropy repositories must be kept in sync.
710
711 In other words, it checks for /usr/portage/profiles/updates changes,
712 of course indirectly, since there is no way entropy.client can directly
713 depend on Portage.
714
715 @param repository_identifier: repository identifier which repo_db
716 parameter is bound
717 @type repository_identifier: string
718 @param repo_db: repository database instance
719 @type repo_db: entropy.db.EntropyRepository
720 @return: bool stating if changes have been made
721 @rtype: bool
722 """
723 if not self.clientDbconn:
724
725 return False
726
727 self._treeupdates_repos.add(repository_identifier)
728
729 do_rescan = False
730 shell_rescan = os.getenv("ETP_TREEUPDATES_RESCAN")
731 if shell_rescan:
732 do_rescan = True
733
734
735 stored_digest = repo_db.retrieveRepositoryUpdatesDigest(
736 repository_identifier)
737 if stored_digest == -1:
738 do_rescan = True
739
740
741 client_digest = "0"
742 if not do_rescan:
743 client_digest = self.clientDbconn.retrieveRepositoryUpdatesDigest(
744 repository_identifier)
745
746 if do_rescan or (str(stored_digest) != str(client_digest)) or force:
747
748
749 self.clientDbconn.clearTreeupdatesEntries(repository_identifier)
750
751
752 update_actions = repo_db.retrieveTreeUpdatesActions(
753 repository_identifier)
754
755 update_actions = self.clientDbconn.filterTreeUpdatesActions(
756 update_actions)
757
758 if update_actions:
759
760 mytxt = "%s: %s." % (
761 bold(_("ATTENTION")),
762 red(_("forcing packages metadata update")),
763 )
764 self.updateProgress(
765 mytxt,
766 importance = 1,
767 type = "info",
768 header = darkred(" * ")
769 )
770 mytxt = "%s %s." % (
771 red(_("Updating system database using repository")),
772 blue(repository_identifier),
773 )
774 self.updateProgress(
775 mytxt,
776 importance = 1,
777 type = "info",
778 header = darkred(" * ")
779 )
780
781 self.clientDbconn.runTreeUpdatesActions(update_actions)
782
783
784 self.clientDbconn.setRepositoryUpdatesDigest(repository_identifier,
785 stored_digest)
786
787 self.clientDbconn.addRepositoryUpdatesActions(etpConst['clientdbid'],
788 update_actions, self.SystemSettings['repositories']['branch'])
789 self.clientDbconn.commitChanges()
790
791 self.clientDbconn.clearCache()
792 return True
793
795 return self.__instance_destroyed
796
799