1# -*- coding: utf-8 -*- 2 3# Copyright (C) 2009, 2012-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 20import calendar 21import os 22import time 23 24import libdnf.transaction 25import libdnf.utils 26 27from dnf.i18n import ucd 28from dnf.yum import misc 29from dnf.exceptions import DatabaseError 30 31from .group import GroupPersistor, EnvironmentPersistor, RPMTransaction 32 33 34class RPMTransactionItemWrapper(object): 35 def __init__(self, swdb, item): 36 assert item is not None 37 self._swdb = swdb 38 self._item = item 39 40 def __str__(self): 41 return self._item.getItem().toStr() 42 43 def __lt__(self, other): 44 return self._item < other._item 45 46 def __eq__(self, other): 47 return self._item == other._item 48 49 def __hash__(self): 50 return self._item.__hash__() 51 52 def match(self, pattern): 53 return True 54 55 def is_package(self): 56 return self._item.getRPMItem() is not None 57 58 def is_group(self): 59 return self._item.getCompsGroupItem() is not None 60 61 def is_environment(self): 62 return self._item.getCompsEnvironmentItem() is not None 63 64 def get_group(self): 65 return self._item.getCompsGroupItem() 66 67 def get_environment(self): 68 return self._item.getCompsEnvironmentItem() 69 70 @property 71 def name(self): 72 return self._item.getRPMItem().getName() 73 74 @property 75 def epoch(self): 76 return self._item.getRPMItem().getEpoch() 77 78 @property 79 def version(self): 80 return self._item.getRPMItem().getVersion() 81 82 @property 83 def release(self): 84 return self._item.getRPMItem().getRelease() 85 86 @property 87 def arch(self): 88 return self._item.getRPMItem().getArch() 89 90 @property 91 def evr(self): 92 if self.epoch: 93 return "{}:{}-{}".format(self.epoch, self.version, self.release) 94 return "{}-{}".format(self.version, self.release) 95 96 @property 97 def nevra(self): 98 return self._item.getRPMItem().getNEVRA() 99 100 @property 101 def action(self): 102 return self._item.getAction() 103 104 @action.setter 105 def action(self, value): 106 self._item.setAction(value) 107 108 @property 109 def reason(self): 110 return self._item.getReason() 111 112 @reason.setter 113 def reason(self, value): 114 return self._item.setReason(value) 115 116 @property 117 def action_name(self): 118 try: 119 return self._item.getActionName() 120 except AttributeError: 121 return "" 122 123 @property 124 def action_short(self): 125 try: 126 return self._item.getActionShort() 127 except AttributeError: 128 return "" 129 130 @property 131 def state(self): 132 return self._item.getState() 133 134 @state.setter 135 def state(self, value): 136 self._item.setState(value) 137 138 @property 139 def from_repo(self): 140 return self._item.getRepoid() 141 142 def ui_from_repo(self): 143 if not self._item.getRepoid(): 144 return "" 145 return "@" + self._item.getRepoid() 146 147 @property 148 def obsoleting(self): 149 return None 150 151 def get_reason(self): 152 # TODO: get_history_reason 153 return self._swdb.rpm.get_reason(self) 154 155 @property 156 def pkg(self): 157 return self._swdb.rpm._swdb_ti_pkg[self._item] 158 159 @property 160 def files(self): 161 return self.pkg.files 162 163 @property 164 def _active(self): 165 return self.pkg 166 167 168class TransactionWrapper(object): 169 170 altered_lt_rpmdb = False 171 altered_gt_rpmdb = False 172 173 def __init__(self, trans): 174 self._trans = trans 175 176 @property 177 def tid(self): 178 return self._trans.getId() 179 180 @property 181 def cmdline(self): 182 return self._trans.getCmdline() 183 184 @property 185 def releasever(self): 186 return self._trans.getReleasever() 187 188 @property 189 def beg_timestamp(self): 190 return self._trans.getDtBegin() 191 192 @property 193 def end_timestamp(self): 194 return self._trans.getDtEnd() 195 196 @property 197 def beg_rpmdb_version(self): 198 return self._trans.getRpmdbVersionBegin() 199 200 @property 201 def end_rpmdb_version(self): 202 return self._trans.getRpmdbVersionEnd() 203 204 @property 205 def return_code(self): 206 return int(self._trans.getState() != libdnf.transaction.TransactionItemState_DONE) 207 208 @property 209 def loginuid(self): 210 return self._trans.getUserId() 211 212 @property 213 def data(self): 214 return self.packages 215 216 @property 217 def is_output(self): 218 output = self._trans.getConsoleOutput() 219 return bool(output) 220 221 @property 222 def comment(self): 223 return self._trans.getComment() 224 225 def tids(self): 226 return [self._trans.getId()] 227 228 def performed_with(self): 229 return [] 230 231 def packages(self): 232 result = self._trans.getItems() 233 return [RPMTransactionItemWrapper(self, i) for i in result] 234 235 def output(self): 236 return [i[1] for i in self._trans.getConsoleOutput()] 237 238 def error(self): 239 return [] 240 241 def compare_rpmdbv(self, rpmdbv): 242 self.altered_gt_rpmdb = self._trans.getRpmdbVersionEnd() != rpmdbv 243 244 245class MergedTransactionWrapper(TransactionWrapper): 246 247 def __init__(self, trans): 248 self._trans = libdnf.transaction.MergedTransaction(trans._trans) 249 250 def merge(self, trans): 251 self._trans.merge(trans._trans) 252 253 @property 254 def loginuid(self): 255 return self._trans.listUserIds() 256 257 def tids(self): 258 return self._trans.listIds() 259 260 @property 261 def return_code(self): 262 return [int(i != libdnf.transaction.TransactionItemState_DONE) for i in self._trans.listStates()] 263 264 @property 265 def cmdline(self): 266 return self._trans.listCmdlines() 267 268 @property 269 def releasever(self): 270 return self._trans.listReleasevers() 271 272 @property 273 def comment(self): 274 return self._trans.listComments() 275 276 def output(self): 277 return [i[1] for i in self._trans.getConsoleOutput()] 278 279class SwdbInterface(object): 280 281 def __init__(self, db_dir, releasever=""): 282 # TODO: record all vars 283 # TODO: remove relreasever from options 284 self.releasever = str(releasever) 285 self._rpm = None 286 self._group = None 287 self._env = None 288 self._addon_data = None 289 self._swdb = None 290 self._db_dir = db_dir 291 self._output = [] 292 293 def __del__(self): 294 self.close() 295 296 @property 297 def rpm(self): 298 if self._rpm is None: 299 self._rpm = RPMTransaction(self) 300 return self._rpm 301 302 @property 303 def group(self): 304 if self._group is None: 305 self._group = GroupPersistor(self) 306 return self._group 307 308 @property 309 def env(self): 310 if self._env is None: 311 self._env = EnvironmentPersistor(self) 312 return self._env 313 314 @property 315 def dbpath(self): 316 return os.path.join(self._db_dir, libdnf.transaction.Swdb.defaultDatabaseName) 317 318 @property 319 def swdb(self): 320 """ Lazy initialize Swdb object """ 321 if not self._swdb: 322 # _db_dir == persistdir which is prepended with installroot already 323 try: 324 self._swdb = libdnf.transaction.Swdb(self.dbpath) 325 except RuntimeError as ex: 326 raise DatabaseError(str(ex)) 327 self._swdb.initTransaction() 328 # TODO: vars -> libdnf 329 return self._swdb 330 331 def transform(self, input_dir): 332 transformer = libdnf.transaction.Transformer(input_dir, self.dbpath) 333 transformer.transform() 334 335 def close(self): 336 try: 337 del self._tid 338 except AttributeError: 339 pass 340 self._rpm = None 341 self._group = None 342 self._env = None 343 if self._swdb: 344 self._swdb.closeTransaction() 345 self._swdb.closeDatabase() 346 self._swdb = None 347 self._output = [] 348 349 @property 350 def path(self): 351 return self.swdb.getPath() 352 353 def reset_db(self): 354 return self.swdb.resetDatabase() 355 356 # TODO: rename to get_last_transaction? 357 def last(self, complete_transactions_only=True): 358 # TODO: complete_transactions_only 359 t = self.swdb.getLastTransaction() 360 if not t: 361 return None 362 return TransactionWrapper(t) 363 364 # TODO: rename to: list_transactions? 365 def old(self, tids=None, limit=0, complete_transactions_only=False): 366 tids = tids or [] 367 tids = [int(i) for i in tids] 368 result = self.swdb.listTransactions() 369 result = [TransactionWrapper(i) for i in result] 370 # TODO: move to libdnf 371 if tids: 372 result = [i for i in result if i.tid in tids] 373 374 # populate altered_lt_rpmdb and altered_gt_rpmdb 375 for i, trans in enumerate(result): 376 if i == 0: 377 continue 378 prev_trans = result[i-1] 379 if trans._trans.getRpmdbVersionBegin() != prev_trans._trans.getRpmdbVersionEnd(): 380 trans.altered_lt_rpmdb = True 381 prev_trans.altered_gt_rpmdb = True 382 return result[::-1] 383 384 def get_current(self): 385 return TransactionWrapper(self.swdb.getCurrent()) 386 387 def set_reason(self, pkg, reason): 388 """Set reason for package""" 389 rpm_item = self.rpm._pkg_to_swdb_rpm_item(pkg) 390 repoid = self.repo(pkg) 391 action = libdnf.transaction.TransactionItemAction_REASON_CHANGE 392 ti = self.swdb.addItem(rpm_item, repoid, action, reason) 393 ti.setState(libdnf.transaction.TransactionItemState_DONE) 394 return ti 395 396 ''' 397 def package(self, pkg): 398 """Get SwdbPackage from package""" 399 return self.swdb.package(str(pkg)) 400 ''' 401 402 def repo(self, pkg): 403 """Get repository of package""" 404 return self.swdb.getRPMRepo(str(pkg)) 405 406 def package_data(self, pkg): 407 """Get package data for package""" 408 # trans item is returned 409 result = self.swdb.getRPMTransactionItem(str(pkg)) 410 if result is None: 411 return result 412 result = RPMTransactionItemWrapper(self, result) 413 return result 414 415# def reason(self, pkg): 416# """Get reason for package""" 417# result = self.swdb.resolveRPMTransactionItemReason(pkg.name, pkg.arch, -1) 418# return result 419 420 # TODO: rename to begin_transaction? 421 def beg(self, rpmdb_version, using_pkgs, tsis, cmdline=None, comment=""): 422 try: 423 self.swdb.initTransaction() 424 except: 425 pass 426 427 tid = self.swdb.beginTransaction( 428 int(calendar.timegm(time.gmtime())), 429 str(rpmdb_version), 430 cmdline or "", 431 int(misc.getloginuid()), 432 comment) 433 self.swdb.setReleasever(self.releasever) 434 self._tid = tid 435 436 return tid 437 438 def pkg_to_swdb_rpm_item(self, po): 439 rpm_item = self.swdb.createRPMItem() 440 rpm_item.setName(po.name) 441 rpm_item.setEpoch(po.epoch or 0) 442 rpm_item.setVersion(po.version) 443 rpm_item.setRelease(po.release) 444 rpm_item.setArch(po.arch) 445 return rpm_item 446 447 def log_scriptlet_output(self, msg): 448 if not hasattr(self, '_tid'): 449 return 450 if not msg: 451 return 452 for line in msg.splitlines(): 453 line = ucd(line) 454 # logging directly to database fails if transaction runs in a background process 455 self._output.append((1, line)) 456 457 ''' 458 def _log_errors(self, errors): 459 for error in errors: 460 error = ucd(error) 461 self.swdb.log_error(self._tid, error) 462 ''' 463 464 def end(self, end_rpmdb_version="", return_code=None, errors=None): 465 if not hasattr(self, '_tid'): 466 return # Failed at beg() time 467 468 if return_code is None: 469 # return_code/state auto-detection 470 return_code = libdnf.transaction.TransactionState_DONE 471 for tsi in self.rpm: 472 if tsi.state == libdnf.transaction.TransactionItemState_ERROR: 473 return_code = libdnf.transaction.TransactionState_ERROR 474 break 475 476 for file_descriptor, line in self._output: 477 self.swdb.addConsoleOutputLine(file_descriptor, line) 478 self._output = [] 479 480 self.swdb.endTransaction( 481 int(time.time()), 482 str(end_rpmdb_version), 483 return_code, 484 ) 485 486 # Closing and cleanup is done in the close() method. 487 # It is important to keep data around after the transaction ends 488 # because it's needed by plugins to report installed packages etc. 489 490 # TODO: ignore_case, more patterns 491 def search(self, patterns, ignore_case=True): 492 """ Search for history transactions which contain specified 493 packages al. la. "yum list". Returns transaction ids. """ 494 return self.swdb.searchTransactionsByRPM(patterns) 495 496 def user_installed(self, pkg): 497 """Returns True if package is user installed""" 498 reason = self.swdb.resolveRPMTransactionItemReason(pkg.name, pkg.arch, -1) 499 if reason == libdnf.transaction.TransactionItemReason_USER: 500 return True 501 # if reason is not known, consider a package user-installed 502 # because it was most likely installed via rpm 503 if reason == libdnf.transaction.TransactionItemReason_UNKNOWN: 504 return True 505 return False 506 507 def get_erased_reason(self, pkg, first_trans, rollback): 508 """Get reason of package before transaction being undone. If package 509 is already installed in the system, keep his reason. 510 511 :param pkg: package being installed 512 :param first_trans: id of first transaction being undone 513 :param rollback: True if transaction is performing a rollback""" 514 if rollback: 515 # return the reason at the point of rollback; we're setting that reason 516 result = self.swdb.resolveRPMTransactionItemReason(pkg.name, pkg.arch, first_trans) 517 else: 518 result = self.swdb.resolveRPMTransactionItemReason(pkg.name, pkg.arch, -1) 519 520 # consider unknown reason as user-installed 521 if result == libdnf.transaction.TransactionItemReason_UNKNOWN: 522 result = libdnf.transaction.TransactionItemReason_USER 523 return result 524