1# -*- coding: utf-8 -*- 2 3# Copyright (C) 2017-2018 Red Hat, Inc. 4# 5# This program is free software; you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation; either version 2 of the License, or 8# (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU Library General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with this program; if not, write to the Free Software 17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18# 19 20 21import libdnf.transaction 22 23import dnf.db.history 24import dnf.transaction 25import dnf.exceptions 26from dnf.i18n import _ 27from dnf.util import logger 28 29import rpm 30 31class PersistorBase(object): 32 def __init__(self, history): 33 assert isinstance(history, dnf.db.history.SwdbInterface), str(type(history)) 34 self.history = history 35 self._installed = {} 36 self._removed = {} 37 self._upgraded = {} 38 39 def __len__(self): 40 return len(self._installed) + len(self._removed) + len(self._upgraded) 41 42 def clean(self): 43 self._installed = {} 44 self._removed = {} 45 self._upgraded = {} 46 47 def _get_obj_id(self, obj): 48 raise NotImplementedError 49 50 def _add_to_history(self, item, action): 51 ti = self.history.swdb.addItem(item, "", action, libdnf.transaction.TransactionItemReason_USER) 52 ti.setState(libdnf.transaction.TransactionItemState_DONE) 53 54 def install(self, obj): 55 self._installed[self._get_obj_id(obj)] = obj 56 self._add_to_history(obj, libdnf.transaction.TransactionItemAction_INSTALL) 57 58 def remove(self, obj): 59 self._removed[self._get_obj_id(obj)] = obj 60 self._add_to_history(obj, libdnf.transaction.TransactionItemAction_REMOVE) 61 62 def upgrade(self, obj): 63 self._upgraded[self._get_obj_id(obj)] = obj 64 self._add_to_history(obj, libdnf.transaction.TransactionItemAction_UPGRADE) 65 66 def new(self, obj_id, name, translated_name, pkg_types): 67 raise NotImplementedError 68 69 def get(self, obj_id): 70 raise NotImplementedError 71 72 def search_by_pattern(self, pattern): 73 raise NotImplementedError 74 75 76class GroupPersistor(PersistorBase): 77 78 def __iter__(self): 79 items = self.history.swdb.getItems() 80 items = [i for i in items if i.getCompsGroupItem()] 81 return iter(items) 82 83 def _get_obj_id(self, obj): 84 return obj.getGroupId() 85 86 def new(self, obj_id, name, translated_name, pkg_types): 87 swdb_group = self.history.swdb.createCompsGroupItem() 88 swdb_group.setGroupId(obj_id) 89 if name is not None: 90 swdb_group.setName(name) 91 if translated_name is not None: 92 swdb_group.setTranslatedName(translated_name) 93 swdb_group.setPackageTypes(pkg_types) 94 return swdb_group 95 96 def get(self, obj_id): 97 swdb_group = self.history.swdb.getCompsGroupItem(obj_id) 98 if not swdb_group: 99 return None 100 swdb_group = swdb_group.getCompsGroupItem() 101 return swdb_group 102 103 def search_by_pattern(self, pattern): 104 return self.history.swdb.getCompsGroupItemsByPattern(pattern) 105 106 def get_package_groups(self, pkg_name): 107 return self.history.swdb.getPackageCompsGroups(pkg_name) 108 109 def is_removable_pkg(self, pkg_name): 110 # for group removal and autoremove 111 reason = self.history.swdb.resolveRPMTransactionItemReason(pkg_name, "", -2) 112 if reason != libdnf.transaction.TransactionItemReason_GROUP: 113 return False 114 115 # TODO: implement lastTransId == -2 in libdnf 116 package_groups = set(self.get_package_groups(pkg_name)) 117 for group_id, group in self._removed.items(): 118 for pkg in group.getPackages(): 119 if pkg.getName() != pkg_name: 120 continue 121 if not pkg.getInstalled(): 122 continue 123 package_groups.remove(group_id) 124 for group_id, group in self._installed.items(): 125 for pkg in group.getPackages(): 126 if pkg.getName() != pkg_name: 127 continue 128 if not pkg.getInstalled(): 129 continue 130 package_groups.add(group_id) 131 if package_groups: 132 return False 133 return True 134 135 136class EnvironmentPersistor(PersistorBase): 137 138 def __iter__(self): 139 items = self.history.swdb.getItems() 140 items = [i for i in items if i.getCompsEnvironmentItem()] 141 return iter(items) 142 143 def _get_obj_id(self, obj): 144 return obj.getEnvironmentId() 145 146 def new(self, obj_id, name, translated_name, pkg_types): 147 swdb_env = self.history.swdb.createCompsEnvironmentItem() 148 swdb_env.setEnvironmentId(obj_id) 149 if name is not None: 150 swdb_env.setName(name) 151 if translated_name is not None: 152 swdb_env.setTranslatedName(translated_name) 153 swdb_env.setPackageTypes(pkg_types) 154 return swdb_env 155 156 def get(self, obj_id): 157 swdb_env = self.history.swdb.getCompsEnvironmentItem(obj_id) 158 if not swdb_env: 159 return None 160 swdb_env = swdb_env.getCompsEnvironmentItem() 161 return swdb_env 162 163 def search_by_pattern(self, pattern): 164 return self.history.swdb.getCompsEnvironmentItemsByPattern(pattern) 165 166 def get_group_environments(self, group_id): 167 return self.history.swdb.getCompsGroupEnvironments(group_id) 168 169 def is_removable_group(self, group_id): 170 # for environment removal 171 swdb_group = self.history.group.get(group_id) 172 if not swdb_group: 173 return False 174 175 # TODO: implement lastTransId == -2 in libdnf 176 group_environments = set(self.get_group_environments(group_id)) 177 for env_id, env in self._removed.items(): 178 for group in env.getGroups(): 179 if group.getGroupId() != group_id: 180 continue 181 if not group.getInstalled(): 182 continue 183 group_environments.remove(env_id) 184 for env_id, env in self._installed.items(): 185 for group in env.getGroups(): 186 if group.getGroupId() != group_id: 187 continue 188 if not group.getInstalled(): 189 continue 190 group_environments.add(env_id) 191 if group_environments: 192 return False 193 return True 194 195 196class RPMTransaction(object): 197 def __init__(self, history, transaction=None): 198 self.history = history 199 self.transaction = transaction 200 if not self.transaction: 201 try: 202 self.history.swdb.initTransaction() 203 except: 204 pass 205 self._swdb_ti_pkg = {} 206 207 # TODO: close trans if needed 208 209 def __iter__(self): 210 # :api 211 if self.transaction: 212 items = self.transaction.getItems() 213 else: 214 items = self.history.swdb.getItems() 215 items = [dnf.db.history.RPMTransactionItemWrapper(self.history, i) for i in items if i.getRPMItem()] 216 return iter(items) 217 218 def __len__(self): 219 if self.transaction: 220 items = self.transaction.getItems() 221 else: 222 items = self.history.swdb.getItems() 223 items = [dnf.db.history.RPMTransactionItemWrapper(self.history, i) for i in items if i.getRPMItem()] 224 return len(items) 225 226 def _pkg_to_swdb_rpm_item(self, pkg): 227 rpm_item = self.history.swdb.createRPMItem() 228 rpm_item.setName(pkg.name) 229 rpm_item.setEpoch(pkg.epoch or 0) 230 rpm_item.setVersion(pkg.version) 231 rpm_item.setRelease(pkg.release) 232 rpm_item.setArch(pkg.arch) 233 return rpm_item 234 235 def new(self, pkg, action, reason=None, replaced_by=None): 236 rpm_item = self._pkg_to_swdb_rpm_item(pkg) 237 repoid = self.get_repoid(pkg) 238 if reason is None: 239 reason = self.get_reason(pkg) 240 result = self.history.swdb.addItem(rpm_item, repoid, action, reason) 241 if replaced_by: 242 result.addReplacedBy(replaced_by) 243 self._swdb_ti_pkg[result] = pkg 244 return result 245 246 def get_repoid(self, pkg): 247 result = getattr(pkg, "_force_swdb_repoid", None) 248 if result: 249 return result 250 return pkg.reponame 251 252 def get_reason(self, pkg): 253 """Get reason for package""" 254 return self.history.swdb.resolveRPMTransactionItemReason(pkg.name, pkg.arch, -1) 255 256 def get_reason_name(self, pkg): 257 """Get reason for package""" 258 return libdnf.transaction.TransactionItemReasonToString(self.get_reason(pkg)) 259 260 def _add_obsoleted(self, obsoleted, replaced_by=None): 261 obsoleted = obsoleted or [] 262 for obs in obsoleted: 263 ti = self.new(obs, libdnf.transaction.TransactionItemAction_OBSOLETED) 264 if replaced_by: 265 ti.addReplacedBy(replaced_by) 266 267 def add_downgrade(self, new, old, obsoleted=None): 268 ti_new = self.new(new, libdnf.transaction.TransactionItemAction_DOWNGRADE) 269 ti_old = self.new(old, libdnf.transaction.TransactionItemAction_DOWNGRADED, replaced_by=ti_new) 270 self._add_obsoleted(obsoleted, replaced_by=ti_new) 271 272 def add_erase(self, old, reason=None): 273 self.add_remove(old, reason) 274 275 def add_install(self, new, obsoleted=None, reason=None): 276 if reason is None: 277 reason = libdnf.transaction.TransactionItemReason_USER 278 ti_new = self.new(new, libdnf.transaction.TransactionItemAction_INSTALL, reason) 279 self._add_obsoleted(obsoleted, replaced_by=ti_new) 280 281 def add_reinstall(self, new, old, obsoleted=None): 282 ti_new = self.new(new, libdnf.transaction.TransactionItemAction_REINSTALL) 283 ti_old = self.new(old, libdnf.transaction.TransactionItemAction_REINSTALLED, replaced_by=ti_new) 284 self._add_obsoleted(obsoleted, replaced_by=ti_new) 285 286 def add_remove(self, old, reason=None): 287 reason = reason or libdnf.transaction.TransactionItemReason_USER 288 ti_old = self.new(old, libdnf.transaction.TransactionItemAction_REMOVE, reason) 289 290 def add_upgrade(self, new, old, obsoleted=None): 291 ti_new = self.new(new, libdnf.transaction.TransactionItemAction_UPGRADE) 292 ti_old = self.new(old, libdnf.transaction.TransactionItemAction_UPGRADED, replaced_by=ti_new) 293 self._add_obsoleted(obsoleted, replaced_by=ti_new) 294 295 def _test_fail_safe(self, hdr, pkg): 296 if pkg._from_cmdline: 297 return 0 298 if pkg.repo.module_hotfixes: 299 return 0 300 try: 301 if hdr['modularitylabel'] and not pkg._is_in_active_module(): 302 logger.critical(_("No available modular metadata for modular package '{}', " 303 "it cannot be installed on the system").format(pkg)) 304 return 1 305 except ValueError: 306 return 0 307 return 0 308 309 def _populate_rpm_ts(self, ts): 310 """Populate the RPM transaction set.""" 311 modular_problems = 0 312 313 for tsi in self: 314 try: 315 if tsi.action == libdnf.transaction.TransactionItemAction_DOWNGRADE: 316 hdr = tsi.pkg._header 317 modular_problems += self._test_fail_safe(hdr, tsi.pkg) 318 ts.addInstall(hdr, tsi, 'u') 319 elif tsi.action == libdnf.transaction.TransactionItemAction_DOWNGRADED: 320 ts.addErase(tsi.pkg.idx) 321 elif tsi.action == libdnf.transaction.TransactionItemAction_INSTALL: 322 hdr = tsi.pkg._header 323 modular_problems += self._test_fail_safe(hdr, tsi.pkg) 324 ts.addInstall(hdr, tsi, 'i') 325 elif tsi.action == libdnf.transaction.TransactionItemAction_OBSOLETE: 326 hdr = tsi.pkg._header 327 modular_problems += self._test_fail_safe(hdr, tsi.pkg) 328 ts.addInstall(hdr, tsi, 'u') 329 elif tsi.action == libdnf.transaction.TransactionItemAction_OBSOLETED: 330 ts.addErase(tsi.pkg.idx) 331 elif tsi.action == libdnf.transaction.TransactionItemAction_REINSTALL: 332 # note: in rpm 4.12 there should not be set 333 # rpm.RPMPROB_FILTER_REPLACEPKG to work 334 hdr = tsi.pkg._header 335 modular_problems += self._test_fail_safe(hdr, tsi.pkg) 336 ts.addReinstall(hdr, tsi) 337 elif tsi.action == libdnf.transaction.TransactionItemAction_REINSTALLED: 338 # Required when multiple packages with the same NEVRA marked as installed 339 ts.addErase(tsi.pkg.idx) 340 elif tsi.action == libdnf.transaction.TransactionItemAction_REMOVE: 341 ts.addErase(tsi.pkg.idx) 342 elif tsi.action == libdnf.transaction.TransactionItemAction_UPGRADE: 343 hdr = tsi.pkg._header 344 modular_problems += self._test_fail_safe(hdr, tsi.pkg) 345 ts.addInstall(hdr, tsi, 'u') 346 elif tsi.action == libdnf.transaction.TransactionItemAction_UPGRADED: 347 ts.addErase(tsi.pkg.idx) 348 elif tsi.action == libdnf.transaction.TransactionItemAction_REASON_CHANGE: 349 pass 350 else: 351 raise RuntimeError("TransactionItemAction not handled: %s" % tsi.action) 352 except rpm.error as e: 353 raise dnf.exceptions.Error(_("An rpm exception occurred: %s" % e)) 354 if modular_problems: 355 raise dnf.exceptions.Error(_("No available modular metadata for modular package")) 356 357 return ts 358 359 @property 360 def install_set(self): 361 # :api 362 result = set() 363 for tsi in self: 364 if tsi.action in dnf.transaction.FORWARD_ACTIONS: 365 try: 366 result.add(tsi.pkg) 367 except KeyError: 368 raise RuntimeError("TransactionItem is has no RPM attached: %s" % tsi) 369 return result 370 371 @property 372 def remove_set(self): 373 # :api 374 result = set() 375 for tsi in self: 376 if tsi.action in dnf.transaction.BACKWARD_ACTIONS + [libdnf.transaction.TransactionItemAction_REINSTALLED]: 377 try: 378 result.add(tsi.pkg) 379 except KeyError: 380 raise RuntimeError("TransactionItem is has no RPM attached: %s" % tsi) 381 return result 382 383 def _rpm_limitations(self): 384 """ Ensures all the members can be passed to rpm as they are to perform 385 the transaction. 386 """ 387 src_installs = [pkg for pkg in self.install_set if pkg.arch == 'src'] 388 if len(src_installs): 389 return _("Will not install a source rpm package (%s).") % \ 390 src_installs[0] 391 return None 392 393 def _get_items(self, action): 394 return [tsi for tsi in self if tsi.action == action] 395