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