From 12e4c4a7a353e4dc9220e2d936d6aa0aff1ebb5b Mon Sep 17 00:00:00 2001 From: Fabio Erculiani Date: Wed, 4 Dec 2013 17:35:43 +0100 Subject: [PATCH] [entropy.client.package.actions] install: rewrite lock handling This is a complete rewrite of the PackageInstallAction class, due to potentially state data collected on setup() that should be rather collected with the lock held for the whole install transaction. --- .../interfaces/package/actions/_manage.py | 45 +- .../interfaces/package/actions/config.py | 12 +- .../interfaces/package/actions/install.py | 638 +++++++++--------- .../interfaces/package/actions/remove.py | 13 +- .../interfaces/portage_plugin/__init__.py | 29 +- lib/entropy/spm/plugins/skel.py | 5 +- 6 files changed, 397 insertions(+), 345 deletions(-) diff --git a/lib/entropy/client/interfaces/package/actions/_manage.py b/lib/entropy/client/interfaces/package/actions/_manage.py index 7cc5c14cc..e4619bb6e 100644 --- a/lib/entropy/client/interfaces/package/actions/_manage.py +++ b/lib/entropy/client/interfaces/package/actions/_manage.py @@ -411,7 +411,10 @@ class _PackageInstallRemoveAction(PackageAction): return in_mask, protected, tofile, do_continue - def _remove_content_from_system_loop(self, inst_repo, remove_content, + def _remove_content_from_system_loop(self, inst_repo, remove_atom, + remove_content, remove_config, + affected_directories, + affected_infofiles, directories, directories_cache, preserved_mgr, not_removed_due_to_collisions, @@ -423,7 +426,6 @@ class _PackageInstallRemoveAction(PackageAction): Body of the _remove_content_from_system() method. """ info_dirs = self._get_info_directories() - metadata = self.metadata() # collect all the library paths to be preserved # in the final removal loop. @@ -434,7 +436,7 @@ class _PackageInstallRemoveAction(PackageAction): # determine without sys_root paths = self._handle_preserved_lib( - item, metadata['removeatom'], preserved_mgr) + item, remove_atom, preserved_mgr) if paths is not None: preserved_lib_paths.update(paths) @@ -465,7 +467,7 @@ class _PackageInstallRemoveAction(PackageAction): protected = False in_mask = False - if not metadata['removeconfig']: + if not remove_config: protected_item_test = sys_root_item (in_mask, protected, _x, @@ -552,7 +554,7 @@ class _PackageInstallRemoveAction(PackageAction): # so using os.path.isdir valid directory symlink if sys_root_item not in directories_cache: # collect for Trigger - metadata['affected_directories'].add(item) + affected_directories.add(item) directories.add((sys_root_item, "link")) directories_cache.add(sys_root_item) continue @@ -561,7 +563,7 @@ class _PackageInstallRemoveAction(PackageAction): # plain directory if sys_root_item not in directories_cache: # collect for Trigger - metadata['affected_directories'].add(item) + affected_directories.add(item) directories.add((sys_root_item, "dir")) directories_cache.add(sys_root_item) continue @@ -592,13 +594,13 @@ class _PackageInstallRemoveAction(PackageAction): # collect for Trigger dir_name = os.path.dirname(item) - metadata['affected_directories'].add(dir_name) + affected_directories.add(dir_name) # account for info files, if any if dir_name in info_dirs: for _ext in self._INFO_EXTS: if item.endswith(_ext): - metadata['affected_infofiles'].add(item) + affected_infofiles.add(item) break # add its parent directory @@ -613,15 +615,17 @@ class _PackageInstallRemoveAction(PackageAction): directories_cache.add(dirobj) def _remove_content_from_system(self, installed_repository, - automerge_metadata, preserved_mgr): + remove_atom, remove_config, sys_root, + protect_mask, removecontent_file, + automerge_metadata, + affected_directories, affected_infofiles, + preserved_mgr): """ Remove installed package content (files/directories) from live system. @keyword automerge_metadata: Entropy "automerge metadata" @type automerge_metadata: dict """ - metadata = self.metadata() - sys_root = self._get_system_root(metadata) # load CONFIG_PROTECT and CONFIG_PROTECT_MASK misc_settings = self._entropy.ClientSettings()['misc'] col_protect = misc_settings['collisionprotect'] @@ -632,7 +636,6 @@ class _PackageInstallRemoveAction(PackageAction): not_removed_due_to_collisions = set() colliding_path_messages = set() - protect_mask = metadata['config_protect+mask'] if protect_mask is not None: protect, mask = protect_mask else: @@ -643,13 +646,16 @@ class _PackageInstallRemoveAction(PackageAction): try: # simulate a removecontent list/set object remove_content = [] - if metadata['removecontent_file'] is not None: + if removecontent_file is not None: remove_content = Content.FileContentReader( - metadata['removecontent_file']) + removecontent_file) self._remove_content_from_system_loop( - installed_repository, - remove_content, directories, directories_cache, + installed_repository, remove_atom, + remove_content, remove_config, + affected_directories, + affected_infofiles, + directories, directories_cache, preserved_mgr, not_removed_due_to_collisions, colliding_path_messages, automerge_metadata, col_protect, protect, mask, protectskip, @@ -690,8 +696,7 @@ class _PackageInstallRemoveAction(PackageAction): def _filter(_path): return _path not in not_removed_due_to_collisions Content.filter_content_file( - metadata['removecontent_file'], - _filter) + removecontent_file, _filter) # now handle directories directories = sorted(directories, reverse = True) @@ -725,7 +730,7 @@ class _PackageInstallRemoveAction(PackageAction): if not taint: break - def _spm_remove_package(self, atom): + def _spm_remove_package(self, atom, metadata): """ Call Source Package Manager interface and tell it to remove our just removed package. @@ -739,4 +744,4 @@ class _PackageInstallRemoveAction(PackageAction): etpConst['logging']['normal_loglevel_id'], "Removing from SPM: %s" % (atom,) ) - return spm.remove_installed_package(self.metadata()) + return spm.remove_installed_package(atom, metadata) diff --git a/lib/entropy/client/interfaces/package/actions/config.py b/lib/entropy/client/interfaces/package/actions/config.py index 4abcbe2a6..d854e3a33 100644 --- a/lib/entropy/client/interfaces/package/actions/config.py +++ b/lib/entropy/client/interfaces/package/actions/config.py @@ -55,11 +55,15 @@ class _PackageConfigAction(PackageAction): # already configured return - metadata = {} - splitdebug_metadata = self._get_splitdebug_metadata() - metadata.update(splitdebug_metadata) - inst_repo = self._entropy.open_repository(self._repository_id) + with inst_repo.shared(): + return self._action_setup_unlocked(inst_repo) + + def _action_setup_unlocked(self, inst_repo): + """ + Setup the PackageAction. Assume repository lock already held. + """ + metadata = {} metadata['atom'] = inst_repo.retrieveAtom(self._package_id) key, slot = inst_repo.retrieveKeySlot(self._package_id) diff --git a/lib/entropy/client/interfaces/package/actions/install.py b/lib/entropy/client/interfaces/package/actions/install.py index 0e561c998..2c54cba9d 100644 --- a/lib/entropy/client/interfaces/package/actions/install.py +++ b/lib/entropy/client/interfaces/package/actions/install.py @@ -61,6 +61,16 @@ class _PackageInstallAction(_PackageInstallRemoveAction): self._meta = None meta.clear() + def _get_remove_package_id_unlocked(self, inst_repo): + """ + Return the installed packages repository package id + that would be removed. + """ + repo = self._entropy.open_repository(self._repository_id) + key_slot = repo.retrieveKeySlotAggregated(self._package_id) + remove_package_id, _inst_rc = inst_repo.atomMatch(key_slot) + return remove_package_id + def setup(self): """ Setup the PackageAction. @@ -69,25 +79,19 @@ class _PackageInstallAction(_PackageInstallRemoveAction): # already configured return - inst_repo = self._entropy.installed_repository() - with inst_repo.shared(): - return self._action_setup_unlocked(inst_repo) - - def _action_setup_unlocked(self, inst_repo): - """ - Setup the PackageAction. Assume repository lock already held. - """ metadata = {} splitdebug_metadata = self._get_splitdebug_metadata() metadata.update(splitdebug_metadata) - repo = self._entropy.open_repository(self._repository_id) - misc_settings = self._entropy.ClientSettings()['misc'] metadata['edelta_support'] = misc_settings['edelta_support'] is_package_repo = self._entropy._is_package_repository( self._repository_id) + # These are used by Spm.entropy_install_unpack_hook() + metadata['package_id'] = self._package_id + metadata['repository_id'] = self._repository_id + # if splitdebug is enabled, check if it's also enabled # via package.splitdebug if metadata['splitdebug']: @@ -116,14 +120,18 @@ class _PackageInstallAction(_PackageInstallRemoveAction): metadata['already_protected_config_files'] = {} metadata['configprotect_data'] = [] - metadata['triggers'] = {} - metadata['atom'] = repo.retrieveAtom(self._package_id) - metadata['slot'] = repo.retrieveSlot(self._package_id) - ver, tag, rev = repo.getVersioningData(self._package_id) - metadata['version'] = ver - metadata['versiontag'] = tag - metadata['revision'] = rev + repo = self._entropy.open_repository(self._repository_id) + + metadata['atom'] = repo.retrieveAtom(self._package_id) + + # use by Spm.entropy_install_unpack_hook(), + # and remove_installed_package() + metadata['category'] = repo.retrieveCategory(self._package_id) + metadata['name'] = repo.retrieveName(self._package_id) + metadata['version'] = repo.retrieveVersion(self._package_id) + metadata['versiontag'] = repo.retrieveTag(self._package_id) + metadata['slot'] = repo.retrieveSlot(self._package_id) metadata['extra_download'] = [] metadata['splitdebug_pkgfile'] = True @@ -135,11 +143,7 @@ class _PackageInstallAction(_PackageInstallRemoveAction): x['type'] != "debug"] metadata['extra_download'] += extra_download - metadata['category'] = repo.retrieveCategory(self._package_id) metadata['download'] = repo.retrieveDownloadURL(self._package_id) - metadata['name'] = repo.retrieveName(self._package_id) - metadata['conflicts'] = self._get_package_conflicts_unlocked( - inst_repo, repo, self._package_id) description = repo.retrieveDescription(self._package_id) if description: @@ -148,11 +152,6 @@ class _PackageInstallAction(_PackageInstallRemoveAction): description += "..." metadata['description'] = description - # this is set by __install_package() and required by spm_install - # phase - metadata['installed_package_id'] = None - metadata['remove_package_id'] = -1 - metadata['remove_metaopts'] = { 'removeconfig': True, } @@ -165,26 +164,11 @@ class _PackageInstallAction(_PackageInstallRemoveAction): metadata['merge_from'] = const_convert_to_unicode(mf) metadata['removeconfig'] = self._opts.get('removeconfig', False) - remove_package_id, _inst_rc = inst_repo.atomMatch( - entropy.dep.dep_getkey(metadata['atom']), - matchSlot = metadata['slot']) - metadata['remove_package_id'] = remove_package_id - - # setup the list of provided libraries that we're going to remove - if metadata['remove_package_id'] != -1: - repo_libs = repo.retrieveProvidedLibraries(self._package_id) - inst_libs = inst_repo.retrieveProvidedLibraries( - metadata['remove_package_id']) - metadata['removed_libs'] = frozenset(inst_libs - repo_libs) - else: - metadata['removed_libs'] = frozenset() - # collects directories whose content has been modified # this information is then handed to the Trigger metadata['affected_directories'] = set() metadata['affected_infofiles'] = set() - # smartpackage ? metadata['smartpackage'] = False # set unpack dir and image dir if is_package_repo: @@ -217,93 +201,34 @@ class _PackageInstallAction(_PackageInstallRemoveAction): except OSError as err: if err.errno != errno.EEXIST: raise + metadata['unpackdir'] = const_mkdtemp(dir=unpack_dir) - metadata['imagedir'] = metadata['unpackdir'] + os.path.sep + \ - etpConst['entropyimagerelativepath'] + metadata['imagedir'] = os.path.join( + metadata['unpackdir'], + etpConst['entropyimagerelativepath']) metadata['pkgdbpath'] = os.path.join(metadata['unpackdir'], - "edb/pkg.db") - - if metadata['remove_package_id'] == -1: - # nothing to remove, fresh install - metadata['removecontent_file'] = None - else: - metadata['removeatom'] = inst_repo.retrieveAtom( - metadata['remove_package_id']) - - # generate content file - content = inst_repo.retrieveContentIter( - metadata['remove_package_id'], - order_by="file", reverse=True) - metadata['removecontent_file'] = \ - self._generate_content_file(content) - - remove_trigger = inst_repo.getTriggerData( - metadata['remove_package_id']) - metadata['triggers']['remove'] = remove_trigger - - remove_trigger['affected_directories'] = \ - metadata['affected_directories'] - remove_trigger['affected_infofiles'] = \ - metadata['affected_infofiles'] - - remove_trigger['spm_repository'] = inst_repo.retrieveSpmRepository( - metadata['remove_package_id']) - remove_trigger.update(splitdebug_metadata) - - remove_trigger['accept_license'] = self._get_licenses( - inst_repo, metadata['remove_package_id']) - - # setup config_protect and config_protect_mask metadata before it's - # too late. - protect = self._get_config_protect_metadata( - inst_repo, metadata['remove_package_id'], - _metadata = metadata) - metadata.update(protect) + "edb", "pkg.db") metadata['phases'] = [] - if metadata['conflicts']: - metadata['phases'].append(self._remove_conflicts_phase) + metadata['phases'].append(self._remove_conflicts_phase) if metadata['merge_from']: metadata['phases'].append(self._merge_phase) else: metadata['phases'].append(self._unpack_phase) - # preinstall placed before preremove in order - # to respect Spm order - metadata['phases'].append(self._setup_phase) + metadata['phases'].append(self._setup_package_phase) + metadata['phases'].append(self._tarball_ownership_fixup_phase) metadata['phases'].append(self._pre_install_phase) - metadata['phases'].append(self._install_phase) - if metadata['remove_package_id'] != -1: - metadata['phases'].append(self._pre_remove_phase) - metadata['phases'].append(self._install_clean_phase) - else: - metadata['phases'].append(self._preserved_libs_gc_phase) - - if metadata['remove_package_id'] != -1: - metadata['phases'].append(self._post_remove_phase) - metadata['phases'].append(self._post_remove_install_phase) - - metadata['phases'].append(self._install_spm_phase) metadata['phases'].append(self._post_install_phase) metadata['phases'].append(self._cleanup_phase) - install_trigger = repo.getTriggerData(self._package_id) - metadata['triggers']['install'] = install_trigger - - install_trigger['unpackdir'] = metadata['unpackdir'] - install_trigger['imagedir'] = metadata['imagedir'] - install_trigger['spm_repository'] = repo.retrieveSpmRepository( - self._package_id) - - metadata['accept_license'] = self._get_licenses( - repo, self._package_id) - install_trigger['accept_license'] = metadata['accept_license'] - - install_trigger.update(splitdebug_metadata) + # SPM can place metadata here if it should be copied to + # the install trigger + metadata['__install_trigger__'] = {} self._meta = metadata @@ -365,10 +290,11 @@ class _PackageInstallAction(_PackageInstallRemoveAction): Execute the package conflicts removal phase. """ inst_repo = self._entropy.installed_repository() - with inst_repo.shared(): - confl_package_ids = [x for x in self._meta['conflicts'] if \ - inst_repo.isPackageIdAvailable(x)] + + repo = self._entropy.open_repository(self._repository_id) + confl_package_ids = self._get_package_conflicts_unlocked( + inst_repo, repo, self._package_id) if not confl_package_ids: return 0 @@ -715,9 +641,9 @@ class _PackageInstallAction(_PackageInstallRemoveAction): return spm_class.entropy_install_unpack_hook(self._entropy, self._meta) - def _setup_phase(self): + def _setup_package_phase(self): """ - Execute the setup phase. + Execute the package setup phase. """ xterm_title = "%s %s: %s" % ( self._xterm_header, @@ -726,22 +652,55 @@ class _PackageInstallAction(_PackageInstallRemoveAction): ) self._entropy.set_title(xterm_title) - exit_st = 0 - data = self._meta['triggers'].get('install') - action_data = self._meta['triggers'].get('install') + data = self._get_install_trigger_data() + trigger = self._entropy.Triggers( + self.NAME, + "setup", + data, + data) - if data: - trigger = self._entropy.Triggers( - self.NAME, "setup", - data, action_data) - ack = trigger.prepare() - if ack: - exit_st = trigger.run() - trigger.kill() + exit_st = 0 + ack = trigger.prepare() + if ack: + exit_st = trigger.run() + trigger.kill() if exit_st != 0: return exit_st + return 0 + + def _pre_install_phase(self): + """ + Execute the pre-install phase. + """ + xterm_title = "%s %s: %s" % ( + self._xterm_header, + _("Pre-install"), + self._meta['atom'], + ) + self._entropy.set_title(xterm_title) + + data = self._get_install_trigger_data() + trigger = self._entropy.Triggers( + self.NAME, + "preinstall", + data, + data) + + exit_st = 0 + ack = trigger.prepare() + if ack: + exit_st = trigger.run() + trigger.kill() + + return exit_st + + def _tarball_ownership_fixup_phase(self): + """ + Execute the tarball file ownership fixup phase. + New uid or gids could have created after the setup phase. + """ # NOTE: fixup permissions in the image directory # the setup phase could have created additional users and groups package_paths = [self._meta['pkgpath']] @@ -760,7 +719,7 @@ class _PackageInstallAction(_PackageInstallRemoveAction): if not self._stat_path(package_path): const_debug_write( __name__, - "_setup_phase: %s vanished" % ( + "_tarball_ownership_fixup_phase: %s vanished" % ( package_path,)) self._entropy.output( @@ -797,33 +756,59 @@ class _PackageInstallAction(_PackageInstallRemoveAction): return 0 - def _pre_install_phase(self): + def _get_remove_trigger_data_unlocked(self, inst_repo, remove_package_id): """ - Execute the pre-install phase. + Get the metadata used during removal phases by Triggers. """ - xterm_title = "%s %s: %s" % ( - self._xterm_header, - _("Pre-install"), - self._meta['atom'], - ) - self._entropy.set_title(xterm_title) + data = {} + data.update(inst_repo.getTriggerData(remove_package_id)) - data = self._meta['triggers'].get('install') - action_data = self._meta['triggers'].get('install') - exit_st = 0 + splitdebug_metadata = self._get_splitdebug_metadata() + data.update(splitdebug_metadata) - if data: - trigger = self._entropy.Triggers( - self.NAME, "preinstall", - data, action_data) - ack = trigger.prepare() - if ack: - exit_st = trigger.run() - trigger.kill() + data['affected_directories'] = self._meta['affected_directories'] + data['affected_infofiles'] = self._meta['affected_infofiles'] + data['spm_repository'] = inst_repo.retrieveSpmRepository( + remove_package_id) - return exit_st + data['accept_license'] = self._get_licenses( + inst_repo, remove_package_id) - def _pre_remove_phase(self): + return data + + def _get_install_trigger_data(self): + """ + Get the metadata used during removal phases by Triggers. + """ + repo = self._entropy.open_repository(self._repository_id) + + data = {} + data.update(repo.getTriggerData(self._package_id)) + + splitdebug_metadata = self._get_splitdebug_metadata() + data.update(splitdebug_metadata) + + data['unpackdir'] = self._meta['unpackdir'] + data['imagedir'] = self._meta['imagedir'] + + data['affected_directories'] = self._meta['affected_directories'] + data['affected_infofiles'] = self._meta['affected_infofiles'] + data['spm_repository'] = repo.retrieveSpmRepository(self._package_id) + data['accept_license'] = self._get_licenses(repo, self._package_id) + + # replace current empty "content" metadata info + # content metadata is required by + # _spm_install_package() -> Spm.add_installed_package() + # in case of injected packages (SPM metadata might be + # incomplete). + data['content'] = self._meta.get('content', data['content']) + + # SPM hook + data.update(self._meta['__install_trigger__']) + + return data + + def _pre_remove_package_unlocked(self, data): """ Execute the pre-remove phase. """ @@ -834,88 +819,61 @@ class _PackageInstallAction(_PackageInstallRemoveAction): ) self._entropy.set_title(xterm_title) - data = self._meta['triggers'].get('remove') - action_data = self._meta['triggers'].get('install') - exit_st = 0 + trigger = self._entropy.Triggers( + self.NAME, + "preremove", + data, + self._get_install_trigger_data()) - if data: - trigger = self._entropy.Triggers( - self.NAME, "preremove", data, - action_data) - ack = trigger.prepare() - if ack: - exit_st = trigger.run() - trigger.kill() + exit_st = 0 + ack = trigger.prepare() + if ack: + exit_st = trigger.run() + trigger.kill() return exit_st - def _install_clean_phase(self): + def _install_clean_unlocked(self, inst_repo, installed_package_id, + clean_content, removecontent_file, + remove_atom, removed_libs, + config_protect_metadata): """ Cleanup package files not used anymore by newly installed version. This is part of the atomic install, which overwrites the live fs with new files and removes old afterwards. """ - inst_repo = self._entropy.installed_repository() - - with inst_repo.exclusive(): - installed_package_id = self._meta['installed_package_id'] - - if inst_repo.isPackageIdAvailable(installed_package_id): - return self._install_clean_unlocked( - inst_repo, installed_package_id) - - return 0 - - def _install_clean_unlocked(self, inst_repo, installed_package_id): - """ - _install_clean with no installed repository lock handling. - """ - self._entropy.output( - blue(_("Cleaning previously installed application data.")), - importance = 1, - level = "info", - header = red(" ## ") - ) + sys_root = self._get_system_root(self._meta) preserved_mgr = preservedlibs.PreservedLibraries( inst_repo, installed_package_id, - self._meta['removed_libs'], - root = self._get_system_root(self._meta)) + removed_libs, root = sys_root) - self._remove_content_from_system( - inst_repo, - self._meta['already_protected_config_files'], - preserved_mgr + if clean_content: + self._entropy.output( + blue(_("Cleaning previously installed application data.")), + importance = 1, + level = "info", + header = red(" ## ") ) + self._remove_content_from_system( + inst_repo, + remove_atom, + self._meta['removeconfig'], + sys_root, + config_protect_metadata['config_protect+mask'], + removecontent_file, + self._meta['already_protected_config_files'], + self._meta['affected_directories'], + self._meta['affected_infofiles'], + preserved_mgr) + # garbage collect preserved libraries that are no longer needed self._garbage_collect_preserved_libs(preserved_mgr) return 0 - def _preserved_libs_gc_phase(self): - """ - Execute the garbage collection of preserved libraries. - """ - inst_repo = self._entropy.installed_repository() - - with inst_repo.exclusive(): - - installed_package_id = self._meta['installed_package_id'] - if inst_repo.isPackageIdAvailable(installed_package_id): - - # NOTE: removed_libs is always empty because this phase is only - # called when remove_package_id == -1 - preserved_mgr = preservedlibs.PreservedLibraries( - inst_repo, installed_package_id, - self._meta['removed_libs'], - root = self._get_system_root(self._meta)) - - self._garbage_collect_preserved_libs(preserved_mgr) - - return 0 - - def _post_remove_phase(self): + def _post_remove_package_unlocked(self, data): """ Execute the post-remove phase. """ @@ -926,33 +884,33 @@ class _PackageInstallAction(_PackageInstallRemoveAction): ) self._entropy.set_title(xterm_title) - data = self._meta['triggers'].get('remove') - action_data = self._meta['triggers'].get('install') - exit_st = 0 + trigger = self._entropy.Triggers( + self.NAME, + "postremove", + data, + self._get_install_trigger_data()) - if data: - trigger = self._entropy.Triggers( - self.NAME, "postremove", data, - action_data) - ack = trigger.prepare() - if ack: - exit_st = trigger.run() - trigger.kill() + exit_st = 0 + ack = trigger.prepare() + if ack: + exit_st = trigger.run() + trigger.kill() return exit_st - def _post_remove_install_phase(self): + def _post_remove_install_package_unlocked(self, atom): """ Execute the post-remove SPM package metadata phase. """ self._entropy.logger.log( "[Package]", etpConst['logging']['normal_loglevel_id'], - "Remove old package (spm data): %s" % (self._meta['removeatom'],) + "Remove old package (spm data): %s" % (atom,) ) - return self._spm_remove_package(self._meta['removeatom']) - def _install_spm_phase(self): + return self._spm_remove_package(atom, self._meta) + + def _install_spm_package_unlocked(self, inst_repo, installed_package_id): """ Execute the installation of SPM package metadata. """ @@ -964,16 +922,9 @@ class _PackageInstallAction(_PackageInstallRemoveAction): "Installing new SPM entry: %s" % (self._meta['atom'],) ) - # this comes from _add_installed_package() - installed_package_id = self._meta['installed_package_id'] - spm_uid = spm.add_installed_package(self._meta) if spm_uid != -1: - inst_repo = self._entropy.installed_repository() - with inst_repo.exclusive(): - - if inst_repo.isPackageIdAvailable(installed_package_id): - inst_repo.insertSpmUid(installed_package_id, spm_uid) + inst_repo.insertSpmUid(installed_package_id, spm_uid) return 0 @@ -988,18 +939,18 @@ class _PackageInstallAction(_PackageInstallRemoveAction): ) self._entropy.set_title(xterm_title) - data = self._meta['triggers'].get('install') - action_data = self._meta['triggers'].get('install') - exit_st = 0 + data = self._get_install_trigger_data() + trigger = self._entropy.Triggers( + self.NAME, + "postinstall", + data, + data) - if data: - trigger = self._entropy.Triggers( - self.NAME, "postinstall", - data, action_data) - ack = trigger.prepare() - if ack: - exit_st = trigger.run() - trigger.kill() + exit_st = 0 + ack = trigger.prepare() + if ack: + exit_st = trigger.run() + trigger.kill() return exit_st @@ -1083,18 +1034,29 @@ class _PackageInstallAction(_PackageInstallRemoveAction): return _path not in second_pass_removal Content.filter_content_file(content_file, _filter) - def _add_installed_package_unlocked(self, inst_repo, items_installed, - items_not_installed): + def _add_installed_package_unlocked(self, inst_repo,removecontent_file, + items_installed, items_not_installed): """ For internal use only. Copy package from repository to installed packages one. """ + def _merge_removecontent(inst_repo, repo, _package_id): + + # nothing to do if there is no content to remove + if removecontent_file is None: + return + + # determine if there is a package to remove first + remove_package_id = self._get_remove_package_id_unlocked(inst_repo) + if remove_package_id == -1: + return + # NOTE: this could be a source of memory consumption # but generally, the difference between two contents # is really small content_diff = list(inst_repo.contentDiff( - self._meta['remove_package_id'], + remove_package_id, repo, _package_id, extended=True)) @@ -1114,7 +1076,7 @@ class _PackageInstallAction(_PackageInstallRemoveAction): content_diff.sort(reverse=True) Content.merge_content_file( - self._meta['removecontent_file'], + removecontent_file, content_diff, _cmp_func) smart_pkg = self._meta['smartpackage'] @@ -1144,10 +1106,7 @@ class _PackageInstallAction(_PackageInstallRemoveAction): content_safety_file = self._generate_content_safety_file( content_safety) - if self._meta['remove_package_id'] != -1 and \ - self._meta['removecontent_file'] is not None: - _merge_removecontent( - inst_repo, repo, self._package_id) + _merge_removecontent(inst_repo, repo, self._package_id) else: @@ -1183,9 +1142,7 @@ class _PackageInstallAction(_PackageInstallRemoveAction): content_safety_file = self._generate_content_safety_file( content_safety) - if self._meta['remove_package_id'] != -1 and \ - self._meta['removecontent_file'] is not None: - _merge_removecontent(inst_repo, pkg_repo, pkg_package_id) + _merge_removecontent(inst_repo, pkg_repo, pkg_package_id) pkg_repo.close() @@ -1199,10 +1156,9 @@ class _PackageInstallAction(_PackageInstallRemoveAction): # -- # fix removecontent, need to check if we just installed files # that resolves at the same directory path (different symlink) - if self._meta['removecontent_file'] is not None: + if removecontent_file is not None: self._filter_out_files_installed_on_diff_path( - self._meta['removecontent_file'], - items_installed) + removecontent_file, items_installed) # filter out files not installed from content metadata # these include splitdebug files, when splitdebug is @@ -1213,12 +1169,6 @@ class _PackageInstallAction(_PackageInstallRemoveAction): Content.filter_content_file( content_file, _filter) - # this is needed to make postinstall trigger work properly - self._meta['triggers']['install']['affected_directories'] = \ - self._meta['affected_directories'] - self._meta['triggers']['install']['affected_infofiles'] = \ - self._meta['affected_infofiles'] - # always set data['injected'] to False # installed packages database SHOULD never have more # than one package for scope (key+slot) @@ -1288,19 +1238,10 @@ class _PackageInstallAction(_PackageInstallRemoveAction): # _spm_install_package() -> Spm.add_installed_package() # in case of injected packages (SPM metadata might be # incomplete). - self._meta['triggers']['install']['content'] = \ - Content.FileContentReader(content_file) + self._meta['content'] = Content.FileContentReader(content_file) return package_id - def _install_package(self): - """ - Execute the package installation code. - """ - inst_repo = self._entropy.installed_repository() - with inst_repo.exclusive(): - return self._install_package_unlocked(inst_repo) - def _install_package_unlocked(self, inst_repo): """ Execute the package installation code. @@ -1313,20 +1254,37 @@ class _PackageInstallAction(_PackageInstallRemoveAction): "Installing package: %s" % (self._meta['atom'],) ) - if self._meta['remove_package_id'] != -1: + remove_package_id = self._get_remove_package_id_unlocked(inst_repo) + + if remove_package_id != -1: am_files = inst_repo.retrieveAutomergefiles( - self._meta['remove_package_id'], + remove_package_id, get_dict = True) - self._meta['already_protected_config_files'] = am_files + self._meta['already_protected_config_files'].clear() + self._meta['already_protected_config_files'].update(am_files) # items_*installed will be filled by _move_image_to_system # then passed to _add_installed_package() items_installed = set() items_not_installed = set() exit_st = self._move_image_to_system_unlocked( - inst_repo, items_installed, items_not_installed) + inst_repo, remove_package_id, + items_installed, items_not_installed) + if exit_st != 0: - return exit_st + txt = "%s. %s. %s: %s" % ( + red(_("An error occured while trying to install the package")), + red(_("Check if your system is healthy")), + blue(_("Error")), + exit_st, + ) + self._entropy.output( + txt, + importance = 1, + level = "error", + header = red(" ## ") + ) + return exit_st, None, None txt = "%s: %s" % ( blue(_("Updating installed packages repository")), @@ -1338,11 +1296,22 @@ class _PackageInstallAction(_PackageInstallRemoveAction): level = "info", header = red(" ## ") ) - package_id = self._add_installed_package_unlocked( - inst_repo, items_installed, items_not_installed) - self._meta['installed_package_id'] = package_id - return 0 + # generate the files and directories that would be removed + removecontent_file = None + if remove_package_id != -1: + removecontent_file = self._generate_content_file( + inst_repo.retrieveContentIter( + remove_package_id, + order_by="file", + reverse=True) + ) + + package_id = self._add_installed_package_unlocked( + inst_repo, removecontent_file, + items_installed, items_not_installed) + + return 0, package_id, removecontent_file def _install_phase(self): """ @@ -1391,23 +1360,84 @@ class _PackageInstallAction(_PackageInstallRemoveAction): header = red(" ## ") ) - exit_st = self._install_package() - if exit_st != 0: - txt = "%s. %s. %s: %s" % ( - red(_("An error occured while trying to install the package")), - red(_("Check if your system is healthy")), - blue(_("Error")), - exit_st, - ) - self._entropy.output( - txt, - importance = 1, - level = "error", - header = red(" ## ") - ) - return exit_st + inst_repo = self._entropy.installed_repository() + with inst_repo.exclusive(): + return self._install_phase_unlocked(inst_repo) - def _handle_install_collision_protect_unlocked(self, inst_repo, tofile, + def _install_phase_unlocked(self, inst_repo): + """ + _install_phase(), assuming that the installed packages repository + lock is held in exclusive mode. + """ + remove_package_id = self._get_remove_package_id_unlocked(inst_repo) + + remove_atom = None + if remove_package_id != -1: + remove_atom = inst_repo.retrieveAtom(remove_package_id) + + # save trigger data + remove_trigger_data = None + if remove_package_id != -1: + remove_trigger_data = self._get_remove_trigger_data_unlocked( + inst_repo, remove_package_id) + + if remove_package_id == -1: + removed_libs = frozenset() + else: + repo = self._entropy.open_repository(self._repository_id) + repo_libs = repo.retrieveProvidedLibraries(self._package_id) + inst_libs = inst_repo.retrieveProvidedLibraries( + remove_package_id) + removed_libs = frozenset(inst_libs - repo_libs) + + config_protect_metadata = None + if remove_package_id != -1: + config_protect_metadata = self._get_config_protect_metadata( + inst_repo, remove_package_id, _metadata = self._meta) + + # after this point, old package metadata is no longer available + + (exit_st, installed_package_id, + removecontent_file) = self._install_package_unlocked(inst_repo) + if exit_st != 0: + return exit_st + + if remove_trigger_data: + exit_st = self._pre_remove_package_unlocked(remove_trigger_data) + if exit_st != 0: + return exit_st + + clean_content = remove_package_id != -1 + exit_st = self._install_clean_unlocked( + inst_repo, installed_package_id, + clean_content, removecontent_file, + remove_atom, removed_libs, + config_protect_metadata) + if exit_st != 0: + return exit_st + + if remove_trigger_data: + exit_st = self._post_remove_package_unlocked( + remove_trigger_data) + if exit_st != 0: + return exit_st + + if remove_package_id != -1: + exit_st = self._post_remove_install_package_unlocked( + remove_atom) + if exit_st != 0: + return exit_st + + exit_st = self._install_spm_package_unlocked( + inst_repo, installed_package_id) + if exit_st != 0: + return exit_st + + return 0 + + def _handle_install_collision_protect_unlocked(self, inst_repo, + remove_package_id, + tofile, todbfile): """ Handle files collition protection for the install phase. @@ -1417,7 +1447,7 @@ class _PackageInstallAction(_PackageInstallRemoveAction): const_convert_to_unicode(todbfile), get_id = True) - if (self._meta['remove_package_id'] not in avail) and avail: + if (remove_package_id not in avail) and avail: mytxt = darkred(_("Collision found during install for")) mytxt += " %s - %s" % ( blue(tofile), @@ -1439,8 +1469,8 @@ class _PackageInstallAction(_PackageInstallRemoveAction): return True - def _move_image_to_system_unlocked(self, inst_repo, items_installed, - items_not_installed): + def _move_image_to_system_unlocked(self, inst_repo, remove_package_id, + items_installed, items_not_installed): """ Internal method that moves the package image directory to the live filesystem. @@ -1692,7 +1722,7 @@ class _PackageInstallAction(_PackageInstallRemoveAction): if col_protect > 1: todbfile = fromfile[len(image_dir):] myrc = self._handle_install_collision_protect_unlocked( - inst_repo, tofile, todbfile) + inst_repo, remove_package_id, tofile, todbfile) if not myrc: return 0 diff --git a/lib/entropy/client/interfaces/package/actions/remove.py b/lib/entropy/client/interfaces/package/actions/remove.py index 2c0273369..4b4a6fee8 100644 --- a/lib/entropy/client/interfaces/package/actions/remove.py +++ b/lib/entropy/client/interfaces/package/actions/remove.py @@ -182,7 +182,16 @@ class _PackageRemoveAction(_PackageInstallRemoveAction): root = self._get_system_root(self._meta)) self._remove_content_from_system( - inst_repo, automerge_metadata, preserved_mgr) + inst_repo, + self._meta['atom'], + self._meta['removeconfig'], + self._get_system_root(self._meta), + self._meta['config_protect+mask'], + self._meta['removecontent_file'], + automerge_metadata, + self._meta['affected_directories'], + self._meta['affected_infofiles'], + preserved_mgr) # garbage collect preserved libraries that are no longer needed self._garbage_collect_preserved_libs(preserved_mgr) @@ -273,7 +282,7 @@ class _PackageRemoveAction(_PackageInstallRemoveAction): spm_atom = spm.convert_from_entropy_package_name(atom) if not installed_package_ids: - exit_st = self._spm_remove_package(spm_atom) + exit_st = self._spm_remove_package(spm_atom, self._meta) if exit_st != 0: return exit_st diff --git a/lib/entropy/spm/plugins/interfaces/portage_plugin/__init__.py b/lib/entropy/spm/plugins/interfaces/portage_plugin/__init__.py index 04614d963..46e7087de 100644 --- a/lib/entropy/spm/plugins/interfaces/portage_plugin/__init__.py +++ b/lib/entropy/spm/plugins/interfaces/portage_plugin/__init__.py @@ -3211,7 +3211,7 @@ class PortagePlugin(SpmPlugin): # Packages emerged with -B don't contain CONTENTS file # in their metadata, so we have to create one self._create_contents_file_if_not_available(pkg_dir, - package_metadata['triggers']['install']) + package_metadata) try: counter = self.assign_uid_to_installed_package( @@ -3296,7 +3296,7 @@ class PortagePlugin(SpmPlugin): return counter - def remove_installed_package(self, package_metadata): + def remove_installed_package(self, atom, package_metadata): """ Reimplemented from SpmPlugin class. """ @@ -3304,14 +3304,14 @@ class PortagePlugin(SpmPlugin): with self._PortageVdbLocker(self, root = root): return self._remove_installed_package_unlocked( - root, package_metadata) + root, atom, package_metadata) - def _remove_installed_package_unlocked(self, root, package_metadata): + def _remove_installed_package_unlocked(self, root, atom, package_metadata): """ remove_installed_package() body assuming that vdb lock has been already acquired. """ - atom = entropy.dep.remove_tag(package_metadata['removeatom']) + atom = self.convert_from_entropy_package_name(atom) remove_build = self.get_installed_package_build_script_path(atom) remove_path = os.path.dirname(remove_build) key = entropy.dep.dep_getkey(atom) @@ -3778,20 +3778,22 @@ class PortagePlugin(SpmPlugin): """ Reimplemented from SpmPlugin class. """ - package_metadata['xpakpath'] = os.path.join( + install_dict = package_metadata['__install_trigger__'] + + install_dict['xpakpath'] = os.path.join( package_metadata['unpackdir'], PortagePlugin._xpak_const['entropyxpakrelativepath']) if not package_metadata['merge_from']: - package_metadata['xpakstatus'] = None - package_metadata['xpakdir'] = os.path.join( - package_metadata['xpakpath'], + install_dict['xpakstatus'] = None + install_dict['xpakdir'] = os.path.join( + install_dict['xpakpath'], PortagePlugin._xpak_const['entropyxpakdatarelativepath']) else: - package_metadata['xpakstatus'] = True + install_dict['xpakstatus'] = True try: import portage.const as pc @@ -3803,10 +3805,11 @@ class PortagePlugin(SpmPlugin): portdbdir = os.path.join(portdbdir, PortagePlugin._pkg_compose_atom(package_metadata)) - package_metadata['xpakdir'] = portdbdir + install_dict['xpakdir'] = portdbdir - package_metadata['triggers']['install']['xpakdir'] = \ - package_metadata['xpakdir'] + package_metadata['xpakpath'] = install_dict['xpakpath'] + package_metadata['xpakdir'] = install_dict['xpakdir'] + package_metadata['xpakstatus'] = install_dict['xpakstatus'] return 0 diff --git a/lib/entropy/spm/plugins/skel.py b/lib/entropy/spm/plugins/skel.py index c83f71217..8d6c0d2d8 100644 --- a/lib/entropy/spm/plugins/skel.py +++ b/lib/entropy/spm/plugins/skel.py @@ -920,7 +920,7 @@ class SpmPlugin(Singleton): """ raise NotImplementedError() - def remove_installed_package(self, package_metadata): + def remove_installed_package(self, atom, package_metadata): """ Remove installed package from SPM database. "package_metadata" is a dictionary featuring the following (relevant) @@ -928,9 +928,10 @@ class SpmPlugin(Singleton): ['accept_license', 'imagedir', 'xpakpath', 'slot', 'pkgdbpath', 'versiontag', 'version', 'xpakstatus', 'unpackdir', 'revision', 'category', 'repository', 'xpakdir', 'name', 'install_source', - 'removeatom' ] + @param atom: the Entropy package atom + @type atom: string @param package_metadata: Entropy package metadata @type package_metadata: dict @return: execution status