1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  *
3  * Copyright (C) 2014-2015 Richard Hughes <richard@hughsie.com>
4  *
5  * Licensed under the GNU Lesser General Public License Version 2.1
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or(at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
20  */
21 
22 /**
23  * SECTION:dnf-transaction
24  * @short_description: High level interface to librpm.
25  * @include: libdnf.h
26  * @stability: Stable
27  *
28  * This object represents an RPM transaction.
29  */
30 
31 #include <rpm/rpmlib.h>
32 #include <rpm/rpmlog.h>
33 #include <rpm/rpmts.h>
34 
35 #include "catch-error.hpp"
36 #include "log.hpp"
37 #include "tinyformat/tinyformat.hpp"
38 #include "dnf-context.hpp"
39 #include "dnf-goal.h"
40 #include "dnf-keyring.h"
41 #include "dnf-package.h"
42 #include "dnf-rpmts-private.hpp"
43 #include "dnf-sack.h"
44 #include "dnf-sack-private.hpp"
45 #include "dnf-transaction.h"
46 #include "dnf-types.h"
47 #include "dnf-utils.h"
48 #include "hy-query.h"
49 #include "hy-util-private.hpp"
50 #include "plugin/plugin-private.hpp"
51 
52 #include "module/ModulePackageContainer.hpp"
53 #include "transaction/Swdb.hpp"
54 #include "transaction/Transformer.hpp"
55 #include "utils/bgettext/bgettext-lib.h"
56 
57 typedef enum {
58     DNF_TRANSACTION_STEP_STARTED,
59     DNF_TRANSACTION_STEP_PREPARING,
60     DNF_TRANSACTION_STEP_WRITING,
61     DNF_TRANSACTION_STEP_IGNORE
62 } DnfTransactionStep;
63 
64 typedef struct {
65     rpmKeyring keyring;
66     rpmts ts;
67     DnfContext *context; /* weak reference */
68     GPtrArray *repos;
69     guint uid;
70 
71     /* previously in the helper */
72     DnfState *state;
73     DnfState *child;
74     FD_t fd;
75     DnfTransactionStep step;
76     GTimer *timer;
77     guint last_progress;
78     GPtrArray *remove;
79     GPtrArray *remove_helper;
80     GPtrArray *install;
81     GPtrArray *pkgs_to_download;
82     GHashTable *erased_by_package_hash;
83     guint64 flags;
84     gboolean dont_solve_goal;
85     libdnf::Swdb *swdb;
86 } DnfTransactionPrivate;
87 
88 G_DEFINE_TYPE_WITH_PRIVATE(DnfTransaction, dnf_transaction, G_TYPE_OBJECT)
89 #define GET_PRIVATE(o)                                                                             \
90     (static_cast< DnfTransactionPrivate * >(dnf_transaction_get_instance_private(o)))
91 
92 /**
93 * @brief Information about running transaction
94 *
95 * The structure is passed to pluginHook() as hookData, when id of hook
96 * is PLUGIN_HOOK_ID_CONTEXT_PRE_TRANSACTION or PLUGIN_HOOK_ID_CONTEXT_TRANSACTION.
97 */
98 struct PluginHookContextTransactionData : public libdnf::PluginHookData {
PluginHookContextTransactionDataPluginHookContextTransactionData99     PluginHookContextTransactionData(PluginHookId hookId, DnfTransaction * transaction,
100                                      HyGoal goal, DnfState * state)
101     : PluginHookData(hookId), transaction(transaction), goal(goal), state(state) {}
102 
103     DnfTransaction * transaction;
104     HyGoal goal;
105     DnfState * state;
106 };
107 
108 /**
109  * dnf_transaction_finalize:
110  **/
111 static void
dnf_transaction_finalize(GObject * object)112 dnf_transaction_finalize(GObject *object)
113 {
114     DnfTransaction *transaction = DNF_TRANSACTION(object);
115     DnfTransactionPrivate *priv = GET_PRIVATE(transaction);
116 
117     g_ptr_array_unref(priv->pkgs_to_download);
118     g_timer_destroy(priv->timer);
119     rpmKeyringFree(priv->keyring);
120     rpmtsFree(priv->ts);
121 
122     if (priv->swdb != NULL)
123         delete priv->swdb;
124     if (priv->repos != NULL)
125         g_ptr_array_unref(priv->repos);
126     if (priv->install != NULL)
127         g_ptr_array_unref(priv->install);
128     if (priv->remove != NULL)
129         g_ptr_array_unref(priv->remove);
130     if (priv->remove_helper != NULL)
131         g_ptr_array_unref(priv->remove_helper);
132     if (priv->erased_by_package_hash != NULL)
133         g_hash_table_unref(priv->erased_by_package_hash);
134     if (priv->context != NULL)
135         g_object_remove_weak_pointer(G_OBJECT(priv->context), (void **)&priv->context);
136 
137     G_OBJECT_CLASS(dnf_transaction_parent_class)->finalize(object);
138 }
139 
140 /**
141  * dnf_transaction_init:
142  **/
143 static void
dnf_transaction_init(DnfTransaction * transaction)144 dnf_transaction_init(DnfTransaction *transaction)
145 {
146     DnfTransactionPrivate *priv = GET_PRIVATE(transaction);
147     priv->timer = g_timer_new();
148     priv->pkgs_to_download = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
149 }
150 
151 /**
152  * dnf_transaction_class_init:
153  **/
154 static void
dnf_transaction_class_init(DnfTransactionClass * klass)155 dnf_transaction_class_init(DnfTransactionClass *klass)
156 {
157     GObjectClass *object_class = G_OBJECT_CLASS(klass);
158     object_class->finalize = dnf_transaction_finalize;
159 }
160 
161 /**
162  * dnf_transaction_get_flags:
163  * @transaction: a #DnfTransaction instance.
164  *
165  * Gets the transaction flags.
166  *
167  * Returns: the transaction flags used for this transaction
168  *
169  * Since: 0.1.0
170  **/
171 guint64
dnf_transaction_get_flags(DnfTransaction * transaction)172 dnf_transaction_get_flags(DnfTransaction *transaction)
173 {
174     DnfTransactionPrivate *priv = GET_PRIVATE(transaction);
175     return priv->flags;
176 }
177 
178 /**
179  * dnf_transaction_get_remote_pkgs:
180  * @transaction: a #DnfTransaction instance.
181  *
182  * Gets the packages that will be downloaded in dnf_transaction_download().
183  *
184  * The dnf_transaction_depsolve() function must have been called before this
185  * function will return sensible results.
186  *
187  * Returns:(transfer none): the list of packages
188  *
189  * Since: 0.1.0
190  **/
191 GPtrArray *
dnf_transaction_get_remote_pkgs(DnfTransaction * transaction)192 dnf_transaction_get_remote_pkgs(DnfTransaction *transaction)
193 {
194     DnfTransactionPrivate *priv = GET_PRIVATE(transaction);
195     return priv->pkgs_to_download;
196 }
197 
198 DnfDb *
dnf_transaction_get_db(DnfTransaction * transaction)199 dnf_transaction_get_db(DnfTransaction *transaction)
200 {
201     DnfTransactionPrivate *priv = GET_PRIVATE(transaction);
202     return priv->swdb;
203 }
204 
205 /**
206  * dnf_transaction_set_repos:
207  * @transaction: a #DnfTransaction instance.
208  * @repos: the repos to use with the transaction
209  *
210  * Sets the list of repos.
211  *
212  * Since: 0.1.0
213  **/
214 void
dnf_transaction_set_repos(DnfTransaction * transaction,GPtrArray * repos)215 dnf_transaction_set_repos(DnfTransaction *transaction, GPtrArray *repos)
216 {
217     DnfTransactionPrivate *priv = GET_PRIVATE(transaction);
218     if (priv->repos != NULL)
219         g_ptr_array_unref(priv->repos);
220     priv->repos = g_ptr_array_ref(repos);
221 }
222 
223 /**
224  * dnf_transaction_set_uid:
225  * @transaction: a #DnfTransaction instance.
226  * @uid: the uid
227  *
228  * Sets the user ID for the person who started this transaction.
229  *
230  * Since: 0.1.0
231  **/
232 void
dnf_transaction_set_uid(DnfTransaction * transaction,guint uid)233 dnf_transaction_set_uid(DnfTransaction *transaction, guint uid)
234 {
235     DnfTransactionPrivate *priv = GET_PRIVATE(transaction);
236     priv->uid = uid;
237 }
238 
239 /**
240  * dnf_transaction_set_flags:
241  * @transaction: a #DnfTransaction instance.
242  * @flags: the flags, e.g. %DNF_TRANSACTION_FLAG_ONLY_TRUSTED
243  *
244  * Sets the flags used for this transaction.
245  *
246  * Since: 0.1.0
247  **/
248 void
dnf_transaction_set_flags(DnfTransaction * transaction,guint64 flags)249 dnf_transaction_set_flags(DnfTransaction *transaction, guint64 flags)
250 {
251     DnfTransactionPrivate *priv = GET_PRIVATE(transaction);
252     priv->flags = flags;
253 }
254 
255 /**
256  * dnf_transaction_set_dont_solve_goal:
257  * @transaction: a #DnfTransaction instance.
258  * @dont_solve_goal: disable/enable calling dnf_goal_depsolve
259  *
260  * Enable/disable calling of dnf_goal_depsolve() in the dnf_transaction_depsolve().
261  *
262  * This function fixes the API problem. The dnf_transaction_depsolve() was allways calling
263  * dnf_goal_depsolve() with hardcoded DnfGoalActions = DNF_ALLOW_UNINSTALL. So, solution
264  * prepared by user was allways replaced.
265  * Default behaviour of existing functions was not changed to be sure of non breaking
266  * of existing API users.
267  *
268  * Since: 0.42.0
269  **/
270 void
dnf_transaction_set_dont_solve_goal(DnfTransaction * transaction,gboolean dont_solve_goal)271 dnf_transaction_set_dont_solve_goal(DnfTransaction *transaction, gboolean dont_solve_goal)
272 {
273     DnfTransactionPrivate *priv = GET_PRIVATE(transaction);
274     priv->dont_solve_goal = dont_solve_goal;
275 }
276 
277 /**
278  * dnf_transaction_ensure_repo:
279  * @transaction: a #DnfTransaction instance.
280  * @pkg: A #DnfPackage
281  * @error: A #GError or %NULL
282  *
283  * Ensures the #DnfRepo is set on the #DnfPackage *if not already set.
284  *
285  * Returns: %TRUE for success, %FALSE otherwise
286  *
287  * Since: 0.1.0
288  */
289 gboolean
dnf_transaction_ensure_repo(DnfTransaction * transaction,DnfPackage * pkg,GError ** error)290 dnf_transaction_ensure_repo(DnfTransaction *transaction, DnfPackage *pkg, GError **error) try
291 {
292     DnfTransactionPrivate *priv = GET_PRIVATE(transaction);
293     guint i;
294 
295     /* not set yet */
296     if (priv->repos == NULL) {
297         g_set_error(error,
298                     DNF_ERROR,
299                     DNF_ERROR_INTERNAL_ERROR,
300                     _("Sources not set when trying to ensure package %s"),
301                     dnf_package_get_name(pkg));
302         return FALSE;
303     }
304 
305     /* this is a local file */
306     if (g_strcmp0(dnf_package_get_reponame(pkg), HY_CMDLINE_REPO_NAME) == 0) {
307         dnf_package_set_filename(pkg, dnf_package_get_location(pkg));
308         return TRUE;
309     }
310 
311     /* get repo */
312     if (dnf_package_installed(pkg))
313         return TRUE;
314     for (i = 0; i < priv->repos->len; i++) {
315         auto repo = static_cast< DnfRepo * >(g_ptr_array_index(priv->repos, i));
316         if (g_strcmp0(dnf_package_get_reponame(pkg), dnf_repo_get_id(repo)) == 0) {
317             dnf_package_set_repo(pkg, repo);
318             return TRUE;
319         }
320     }
321 
322     /* not found */
323     g_set_error(error,
324                 DNF_ERROR,
325                 DNF_ERROR_INTERNAL_ERROR,
326                 _("Failed to ensure %1$s as repo %2$s not "
327                   "found(%3$i repos loaded)"),
328                 dnf_package_get_name(pkg),
329                 dnf_package_get_reponame(pkg),
330                 priv->repos->len);
331     return FALSE;
332 } CATCH_TO_GERROR(FALSE)
333 
334 /**
335  * dnf_transaction_ensure_repo_list:
336  * @transaction: a #DnfTransaction instance.
337  * @pkglist: A #GPtrArray *
338  * @error: A #GError or %NULL
339  *
340  * Ensures the #DnfRepo is set on the #GPtrArray *if not already set.
341  *
342  * Returns: %TRUE for success, %FALSE otherwise
343  *
344  * Since: 0.1.0
345  */
346 gboolean
347 dnf_transaction_ensure_repo_list(DnfTransaction *transaction, GPtrArray *pkglist, GError **error) try
348 {
349     for (guint i = 0; i < pkglist->len; i++) {
350         auto pkg = static_cast< DnfPackage * >(g_ptr_array_index(pkglist, i));
351         if (!dnf_transaction_ensure_repo(transaction, pkg, error))
352             return FALSE;
353     }
354     return TRUE;
CATCH_TO_GERROR(FALSE)355 } CATCH_TO_GERROR(FALSE)
356 
357 gboolean
358 dnf_transaction_gpgcheck_package(DnfTransaction *transaction, DnfPackage *pkg, GError **error) try
359 {
360     DnfTransactionPrivate *priv = GET_PRIVATE(transaction);
361     GError *error_local = NULL;
362     DnfRepo *repo;
363     const gchar *fn;
364 
365     /* ensure the filename is set */
366     if (!dnf_transaction_ensure_repo(transaction, pkg, error)) {
367         g_prefix_error(error, _("Failed to check untrusted: "));
368         return FALSE;
369     }
370 
371     /* find the location of the local file */
372     fn = dnf_package_get_filename(pkg);
373     if (fn == NULL) {
374         g_set_error(error,
375                     DNF_ERROR,
376                     DNF_ERROR_FILE_NOT_FOUND,
377                     _("Downloaded file for %s not found"),
378                     dnf_package_get_name(pkg));
379         return FALSE;
380     }
381 
382     /* check file */
383     if (!dnf_keyring_check_untrusted_file(priv->keyring, fn, &error_local)) {
384 
385         /* probably an i/o error */
386         if (!g_error_matches(error_local, DNF_ERROR, DNF_ERROR_GPG_SIGNATURE_INVALID)) {
387             g_propagate_error(error, error_local);
388             return FALSE;
389         }
390 
391         /* if the repo is signed this is ALWAYS an error */
392         repo = dnf_package_get_repo(pkg);
393         if (repo != NULL && dnf_repo_get_gpgcheck(repo)) {
394             g_set_error(error,
395                         DNF_ERROR,
396                         DNF_ERROR_FILE_INVALID,
397                         _("package %1$s cannot be verified "
398                           "and repo %2$s is GPG enabled: %3$s"),
399                         dnf_package_get_nevra(pkg),
400                         dnf_repo_get_id(repo),
401                         error_local->message);
402             g_error_free(error_local);
403             return FALSE;
404         }
405 
406         /* we can only install signed packages in this mode */
407         if ((priv->flags & DNF_TRANSACTION_FLAG_ONLY_TRUSTED) > 0) {
408             g_propagate_error(error, error_local);
409             return FALSE;
410         } else {
411             g_clear_error(&error_local);
412         }
413     }
414 
415     return TRUE;
416 } CATCH_TO_GERROR(FALSE)
417 
418 /**
419  * dnf_transaction_check_untrusted:
420  * @transaction: Transaction
421  * @goal: Target goal
422  * @error: Error
423  *
424  * Verify GPG signatures for all pending packages to be changed as part
425  * of @goal.
426  */
427 gboolean
428 dnf_transaction_check_untrusted(DnfTransaction *transaction, HyGoal goal, GError **error) try
429 {
430     guint i;
431     g_autoptr(GPtrArray) install = NULL;
432 
433     /* find a list of all the packages we might have to download */
434     install = dnf_goal_get_packages(goal,
435                                     DNF_PACKAGE_INFO_INSTALL,
436                                     DNF_PACKAGE_INFO_REINSTALL,
437                                     DNF_PACKAGE_INFO_DOWNGRADE,
438                                     DNF_PACKAGE_INFO_UPDATE,
439                                     -1);
440     if (install->len == 0)
441         return TRUE;
442 
443     /* find any packages in untrusted repos */
444     for (i = 0; i < install->len; i++) {
445         auto pkg = static_cast< DnfPackage * >(g_ptr_array_index(install, i));
446 
447         if (!dnf_transaction_gpgcheck_package(transaction, pkg, error))
448             return FALSE;
449     }
450     return TRUE;
CATCH_TO_GERROR(FALSE)451 } CATCH_TO_GERROR(FALSE)
452 
453 /**
454  * dnf_find_pkg_from_header:
455  **/
456 static DnfPackage *
457 dnf_find_pkg_from_header(GPtrArray *array, Header hdr)
458 {
459     const gchar *arch;
460     const gchar *name;
461     const gchar *release;
462     const gchar *version;
463     guint epoch;
464     guint i;
465 
466     /* get details */
467     name = headerGetString(hdr, RPMTAG_NAME);
468     epoch = headerGetNumber(hdr, RPMTAG_EPOCH);
469     version = headerGetString(hdr, RPMTAG_VERSION);
470     release = headerGetString(hdr, RPMTAG_RELEASE);
471     arch = headerGetString(hdr, RPMTAG_ARCH);
472 
473     /* find in array */
474     for (i = 0; i < array->len; i++) {
475         auto pkg = static_cast< DnfPackage * >(g_ptr_array_index(array, i));
476         if (g_strcmp0(name, dnf_package_get_name(pkg)) != 0)
477             continue;
478         if (g_strcmp0(version, dnf_package_get_version(pkg)) != 0)
479             continue;
480         if (g_strcmp0(release, dnf_package_get_release(pkg)) != 0)
481             continue;
482         if (g_strcmp0(arch, dnf_package_get_arch(pkg)) != 0)
483             continue;
484         if (epoch != dnf_package_get_epoch(pkg))
485             continue;
486         return pkg;
487     }
488     return NULL;
489 }
490 
491 /**
492  * dnf_find_pkg_from_filename_suffix:
493  **/
494 static DnfPackage *
dnf_find_pkg_from_filename_suffix(GPtrArray * array,const gchar * filename_suffix)495 dnf_find_pkg_from_filename_suffix(GPtrArray *array, const gchar *filename_suffix)
496 {
497     /* find in array */
498     for (guint i = 0; i < array->len; i++) {
499         auto pkg = static_cast< DnfPackage * >(g_ptr_array_index(array, i));
500         auto filename = dnf_package_get_filename(pkg);
501         if (filename == NULL)
502             continue;
503         if (g_str_has_suffix(filename, filename_suffix))
504             return pkg;
505     }
506     return NULL;
507 }
508 
509 /**
510  * dnf_find_pkg_from_name:
511  **/
512 static DnfPackage *
dnf_find_pkg_from_name(GPtrArray * array,const gchar * pkgname)513 dnf_find_pkg_from_name(GPtrArray *array, const gchar *pkgname)
514 {
515     guint i;
516 
517     /* find in array */
518     for (i = 0; i < array->len; i++) {
519         auto pkg = static_cast< DnfPackage * >(g_ptr_array_index(array, i));
520         if (g_strcmp0(dnf_package_get_name(pkg), pkgname) == 0)
521             return pkg;
522     }
523     return NULL;
524 }
525 
526 static void
_swdb_transaction_item_progress(libdnf::Swdb * swdb,DnfPackage * pkg)527 _swdb_transaction_item_progress(libdnf::Swdb *swdb, DnfPackage *pkg)
528 {
529     if (pkg == NULL) {
530         return;
531     }
532     const char *nevra = dnf_package_get_nevra(pkg);
533     if (nevra == NULL) {
534         return;
535     }
536     swdb->setItemDone(nevra);
537 }
538 
539 /**
540  * dnf_transaction_ts_progress_cb:
541  **/
542 static void *
dnf_transaction_ts_progress_cb(const void * arg,const rpmCallbackType what,const rpm_loff_t amount,const rpm_loff_t total,fnpyKey key,void * data)543 dnf_transaction_ts_progress_cb(const void *arg,
544                                const rpmCallbackType what,
545                                const rpm_loff_t amount,
546                                const rpm_loff_t total,
547                                fnpyKey key,
548                                void *data)
549 {
550     DnfTransaction *transaction = DNF_TRANSACTION(data);
551     DnfTransactionPrivate *priv = GET_PRIVATE(transaction);
552     const char *filename = (const char *)key;
553     const gchar *name = NULL;
554     gboolean ret;
555     guint percentage;
556     guint speed;
557     Header hdr = (Header)arg;
558     DnfPackage *pkg = NULL;
559     DnfStateAction action;
560     void *rc = NULL;
561     libdnf::Swdb *swdb = priv->swdb;
562     g_autoptr(GError) error_local = NULL;
563 
564     if (hdr != NULL)
565         name = headerGetString(hdr, RPMTAG_NAME);
566     g_debug("phase: %u(%i/%i, %s/%s)",
567             (uint)what,
568             (gint32)amount,
569             (gint32)total,
570             (const gchar *)key,
571             name);
572 
573     switch (what) {
574         case RPMCALLBACK_INST_OPEN_FILE:
575 
576             /* valid? */
577             if (filename == NULL || filename[0] == '\0')
578                 return NULL;
579 
580             /* open the file and return file descriptor */
581             priv->fd = Fopen(filename, "r.ufdio");
582             return (void *)priv->fd;
583             break;
584 
585         case RPMCALLBACK_INST_CLOSE_FILE:
586 
587             /* just close the file */
588             if (priv->fd != NULL) {
589                 Fclose(priv->fd);
590                 priv->fd = NULL;
591             }
592             break;
593 
594         case RPMCALLBACK_INST_START:
595 
596             /* find pkg */
597             pkg = dnf_find_pkg_from_filename_suffix(priv->install, filename);
598             if (pkg == NULL)
599                 g_assert_not_reached();
600 
601             /* map to correct action code */
602             action = dnf_package_get_action(pkg);
603             if (action == DNF_STATE_ACTION_UNKNOWN)
604                 action = DNF_STATE_ACTION_INSTALL;
605 
606             /* set the pkgid if not already set */
607             if (dnf_package_get_pkgid(pkg) == NULL) {
608                 const gchar *pkgid;
609                 pkgid = headerGetString(hdr, RPMTAG_SHA1HEADER);
610                 if (pkgid != NULL) {
611                     g_debug("setting %s pkgid %s", name, pkgid);
612                     dnf_package_set_pkgid(pkg, pkgid);
613                 }
614             }
615 
616             /* install start */
617             priv->step = DNF_TRANSACTION_STEP_WRITING;
618             priv->child = dnf_state_get_child(priv->state);
619             dnf_state_action_start(priv->child, action, dnf_package_get_package_id(pkg));
620             g_debug("install start: %s size=%i", filename, (gint32)total);
621             break;
622 
623         case RPMCALLBACK_UNINST_START:
624 
625             /* find pkg */
626             pkg = dnf_find_pkg_from_header(priv->remove, hdr);
627             if (pkg == NULL && filename != NULL) {
628                 pkg = dnf_find_pkg_from_filename_suffix(priv->remove, filename);
629             }
630             if (pkg == NULL && name != NULL)
631                 pkg = dnf_find_pkg_from_name(priv->remove, name);
632             if (pkg == NULL && name != NULL)
633                 pkg = dnf_find_pkg_from_name(priv->remove_helper, name);
634             if (pkg == NULL) {
635                 g_warning("cannot find %s in uninst-start", name);
636                 priv->step = DNF_TRANSACTION_STEP_WRITING;
637                 break;
638             }
639 
640             /* map to correct action code */
641             action = dnf_package_get_action(pkg);
642             if (action == DNF_STATE_ACTION_UNKNOWN)
643                 action = DNF_STATE_ACTION_REMOVE;
644 
645             /* remove start */
646             priv->step = DNF_TRANSACTION_STEP_WRITING;
647             priv->child = dnf_state_get_child(priv->state);
648             dnf_state_action_start(priv->child, action, dnf_package_get_package_id(pkg));
649             g_debug("remove start: %s size=%i", filename, (gint32)total);
650             break;
651 
652         case RPMCALLBACK_TRANS_PROGRESS:
653         case RPMCALLBACK_INST_PROGRESS:
654 
655             /* we're preparing the transaction */
656             if (priv->step == DNF_TRANSACTION_STEP_PREPARING ||
657                 priv->step == DNF_TRANSACTION_STEP_IGNORE) {
658                 g_debug("ignoring preparing %i / %i", (gint32)amount, (gint32)total);
659                 break;
660             }
661 
662             /* work out speed */
663             speed = (amount - priv->last_progress) / g_timer_elapsed(priv->timer, NULL);
664             dnf_state_set_speed(priv->state, speed);
665             priv->last_progress = amount;
666             g_timer_reset(priv->timer);
667 
668             /* progress */
669             percentage = (100.0f / (gfloat)total) * (gfloat)amount;
670             if (priv->child != NULL)
671                 dnf_state_set_percentage(priv->child, percentage);
672 
673             /* update UI */
674             pkg = dnf_find_pkg_from_header(priv->install, hdr);
675             if (pkg == NULL) {
676                 pkg = dnf_find_pkg_from_filename_suffix(priv->install, filename);
677             }
678             if (pkg == NULL) {
679                 g_debug("cannot find %s(%s)", filename, name);
680                 break;
681             }
682 
683             dnf_state_set_package_progress(
684                 priv->state, dnf_package_get_package_id(pkg), DNF_STATE_ACTION_INSTALL, percentage);
685             break;
686 
687         case RPMCALLBACK_UNINST_PROGRESS:
688 
689             /* we're preparing the transaction */
690             if (priv->step == DNF_TRANSACTION_STEP_PREPARING ||
691                 priv->step == DNF_TRANSACTION_STEP_IGNORE) {
692                 g_debug("ignoring preparing %i / %i", (gint32)amount, (gint32)total);
693                 break;
694             }
695 
696             /* progress */
697             percentage = (100.0f / (gfloat)total) * (gfloat)amount;
698             if (priv->child != NULL)
699                 dnf_state_set_percentage(priv->child, percentage);
700 
701             /* update UI */
702             pkg = dnf_find_pkg_from_header(priv->remove, hdr);
703             if (pkg == NULL && filename != NULL) {
704                 pkg = dnf_find_pkg_from_filename_suffix(priv->remove, filename);
705             }
706             if (pkg == NULL && name != NULL)
707                 pkg = dnf_find_pkg_from_name(priv->remove, name);
708             if (pkg == NULL && name != NULL)
709                 pkg = dnf_find_pkg_from_name(priv->remove_helper, name);
710             if (pkg == NULL) {
711                 g_warning("cannot find %s in uninst-progress", name);
712                 break;
713             }
714 
715             /* map to correct action code */
716             action = dnf_package_get_action(pkg);
717             if (action == DNF_STATE_ACTION_UNKNOWN)
718                 action = DNF_STATE_ACTION_REMOVE;
719 
720             dnf_state_set_package_progress(
721                 priv->state, dnf_package_get_package_id(pkg), action, percentage);
722             break;
723 
724         case RPMCALLBACK_TRANS_START:
725 
726             /* we setup the state */
727             g_debug("preparing transaction with %i items", (gint32)total);
728             if (priv->step == DNF_TRANSACTION_STEP_IGNORE)
729                 break;
730 
731             dnf_state_set_number_steps(priv->state, total);
732             priv->step = DNF_TRANSACTION_STEP_PREPARING;
733             break;
734 
735         case RPMCALLBACK_TRANS_STOP:
736 
737             /* don't do anything */
738             break;
739 
740         case RPMCALLBACK_INST_STOP:
741             pkg = dnf_find_pkg_from_header(priv->install, hdr);
742             if (pkg == NULL && filename != NULL) {
743                 pkg = dnf_find_pkg_from_filename_suffix(priv->install, filename);
744             }
745 
746             // transaction item install complete
747             _swdb_transaction_item_progress(swdb, pkg);
748 
749             /* phase complete */
750             ret = dnf_state_done(priv->state, &error_local);
751             if (!ret) {
752                 g_warning("state increment failed: %s", error_local->message);
753             }
754             break;
755 
756         case RPMCALLBACK_UNINST_STOP:
757 
758             pkg = dnf_find_pkg_from_header(priv->remove, hdr);
759             if (pkg == NULL) {
760                 pkg = dnf_find_pkg_from_header(priv->remove_helper, hdr);
761             }
762             if (pkg == NULL && filename != NULL) {
763                 pkg = dnf_find_pkg_from_filename_suffix(priv->remove, filename);
764             }
765             if (pkg == NULL && name != NULL) {
766                 pkg = dnf_find_pkg_from_name(priv->remove, name);
767             }
768             if (pkg == NULL && name != NULL) {
769                 pkg = dnf_find_pkg_from_name(priv->remove_helper, name);
770             }
771 
772             // transaction item remove complete
773             _swdb_transaction_item_progress(swdb, pkg);
774 
775             /* phase complete */
776             ret = dnf_state_done(priv->state, &error_local);
777             if (!ret) {
778                 g_warning("state increment failed: %s", error_local->message);
779             }
780             break;
781 
782         default:
783             break;
784     }
785     return rc;
786 }
787 
788 /**
789  * dnf_rpm_verbosity_string_to_value:
790  **/
791 static gint
dnf_rpm_verbosity_string_to_value(const gchar * value)792 dnf_rpm_verbosity_string_to_value(const gchar *value)
793 {
794     if (g_strcmp0(value, "critical") == 0)
795         return RPMLOG_CRIT;
796     if (g_strcmp0(value, "emergency") == 0)
797         return RPMLOG_EMERG;
798     if (g_strcmp0(value, "error") == 0)
799         return RPMLOG_ERR;
800     if (g_strcmp0(value, "warn") == 0)
801         return RPMLOG_WARNING;
802     if (g_strcmp0(value, "debug") == 0)
803         return RPMLOG_DEBUG;
804     if (g_strcmp0(value, "info") == 0)
805         return RPMLOG_INFO;
806     return RPMLOG_EMERG;
807 }
808 
809 /**
810  * dnf_transaction_delete_packages:
811  **/
812 static gboolean
dnf_transaction_delete_packages(DnfTransaction * transaction,DnfState * state,GError ** error)813 dnf_transaction_delete_packages(DnfTransaction *transaction, DnfState *state, GError **error)
814 {
815     DnfTransactionPrivate *priv = GET_PRIVATE(transaction);
816     DnfState *state_local;
817     const gchar *cachedir;
818     guint i;
819 
820     /* nothing to delete? */
821     if (priv->install->len == 0)
822         return TRUE;
823 
824     /* get the cachedir so we only delete packages in the actual
825      * cache, not local-install packages */
826     cachedir = dnf_context_get_cache_dir(priv->context);
827     if (cachedir == NULL) {
828         g_set_error_literal(error,
829                             DNF_ERROR,
830                             DNF_ERROR_FAILED_CONFIG_PARSING,
831                             _("Failed to get value for CacheDir"));
832         return FALSE;
833     }
834 
835     /* delete each downloaded file */
836     state_local = dnf_state_get_child(state);
837     dnf_state_set_number_steps(state_local, priv->install->len);
838     for (i = 0; i < priv->install->len; i++) {
839         auto pkg = static_cast< DnfPackage * >(g_ptr_array_index(priv->install, i));
840 
841         /* don't delete files not in the repo */
842         auto filename = dnf_package_get_filename(pkg);
843         if (g_str_has_prefix(filename, cachedir)) {
844             g_autoptr(GFile) file = NULL;
845             file = g_file_new_for_path(filename);
846             if (!g_file_delete(file, NULL, error))
847                 return FALSE;
848         }
849 
850         /* done */
851         if (!dnf_state_done(state_local, error))
852             return FALSE;
853     }
854     return TRUE;
855 }
856 
857 static int64_t
_get_current_time()858 _get_current_time()
859 {
860     g_autoptr(GDateTime) t_struct = g_date_time_new_now_utc();
861     return g_date_time_to_unix(t_struct);
862 }
863 
864 /**
865  * We've used dnf_package_set_pkgid() when running the transaction so we can
866  * avoid the lookup in the rpmdb.
867  **/
868 static void
_history_write_item(DnfPackage * pkg,libdnf::Swdb * swdb,libdnf::TransactionItemAction action)869 _history_write_item(DnfPackage *pkg, libdnf::Swdb *swdb, libdnf::TransactionItemAction action)
870 {
871     auto rpm = swdb->createRPMItem();
872     rpm->setName(dnf_package_get_name(pkg));
873     rpm->setEpoch(dnf_package_get_epoch(pkg));
874     rpm->setVersion(dnf_package_get_version(pkg));
875     rpm->setRelease(dnf_package_get_release(pkg));
876     rpm->setArch(dnf_package_get_arch(pkg));
877     rpm->save();
878 
879     libdnf::TransactionItemReason reason =
880         swdb->resolveRPMTransactionItemReason(rpm->getName(), rpm->getArch(), -2);
881 
882     auto transItem = swdb->addItem(
883         std::dynamic_pointer_cast< libdnf::Item >(rpm), dnf_package_get_reponame(pkg), action, reason);
884 }
885 
886 static gboolean
dnf_transaction_check_free_space(DnfTransaction * transaction,GError ** error)887 dnf_transaction_check_free_space(DnfTransaction *transaction, GError **error)
888 {
889     DnfTransactionPrivate *priv = GET_PRIVATE(transaction);
890     const gchar *cachedir;
891     guint64 download_size;
892     guint64 free_space;
893     g_autoptr(GFile) file = NULL;
894     g_autoptr(GFileInfo) filesystem_info = NULL;
895 
896     download_size = dnf_package_array_get_download_size(priv->pkgs_to_download);
897 
898     cachedir = dnf_context_get_cache_dir(priv->context);
899     if (cachedir == NULL) {
900         g_set_error_literal(error,
901                             DNF_ERROR,
902                             DNF_ERROR_FAILED_CONFIG_PARSING,
903                             _("Failed to get value for CacheDir"));
904         return FALSE;
905     }
906 
907     file = g_file_new_for_path(cachedir);
908     filesystem_info =
909         g_file_query_filesystem_info(file, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, NULL, error);
910     if (filesystem_info == NULL) {
911         g_prefix_error(error, _("Failed to get filesystem free size for %s: "), cachedir);
912         return FALSE;
913     }
914 
915     if (!g_file_info_has_attribute(filesystem_info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE)) {
916         g_set_error(error,
917                     DNF_ERROR,
918                     DNF_ERROR_FAILED,
919                     _("Failed to get filesystem free size for %s"),
920                     cachedir);
921         return FALSE;
922     }
923 
924     free_space =
925         g_file_info_get_attribute_uint64(filesystem_info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
926     if (free_space < download_size) {
927         g_autofree gchar *formatted_download_size = NULL;
928         g_autofree gchar *formatted_free_size = NULL;
929 
930         formatted_download_size = g_format_size(download_size);
931         formatted_free_size = g_format_size(free_space);
932         g_set_error(error,
933                     DNF_ERROR,
934                     DNF_ERROR_NO_SPACE,
935                     _("Not enough free space in %1$s: needed %2$s, available %3$s"),
936                     cachedir,
937                     formatted_download_size,
938                     formatted_free_size);
939         return FALSE;
940     }
941 
942     return TRUE;
943 }
944 
945 /**
946  * dnf_transaction_download:
947  * @transaction: a #DnfTransaction instance.
948  * @state: A #DnfState
949  * @error: A #GError or %NULL
950  *
951  * Downloads all the packages needed for a transaction.
952  *
953  * Returns: %TRUE for success, %FALSE otherwise
954  *
955  * Since: 0.1.0
956  **/
957 gboolean
dnf_transaction_download(DnfTransaction * transaction,DnfState * state,GError ** error)958 dnf_transaction_download(DnfTransaction *transaction, DnfState *state, GError **error) try
959 {
960     DnfTransactionPrivate *priv = GET_PRIVATE(transaction);
961 
962     /* check that we have enough free space */
963     if (!dnf_transaction_check_free_space(transaction, error))
964         return FALSE;
965 
966     /* just download the list */
967     return dnf_package_array_download(priv->pkgs_to_download, NULL, state, error);
968 } CATCH_TO_GERROR(FALSE)
969 
970 /**
971  * dnf_transaction_depsolve:
972  * @transaction: a #DnfTransaction instance.
973  * @goal: A #HyGoal
974  * @state: A #DnfState
975  * @error: A #GError or %NULL
976  *
977  * Depsolves the transaction.
978  *
979  * Returns: %TRUE for success, %FALSE otherwise
980  *
981  * Since: 0.1.0
982  **/
983 gboolean
984 dnf_transaction_depsolve(DnfTransaction *transaction, HyGoal goal, DnfState *state, GError **error) try
985 {
986     DnfTransactionPrivate *priv = GET_PRIVATE(transaction);
987     gboolean valid;
988     g_autoptr(GPtrArray) packages = NULL;
989 
990     /* depsolve */
991     if (!priv->dont_solve_goal) {
992         if (!dnf_goal_depsolve(goal, DNF_ALLOW_UNINSTALL, error)) {
993             return FALSE;
994         }
995     }
996 
997     /* find a list of all the packages we have to download */
998     g_ptr_array_set_size(priv->pkgs_to_download, 0);
999     packages = dnf_goal_get_packages(goal,
1000                                      DNF_PACKAGE_INFO_INSTALL,
1001                                      DNF_PACKAGE_INFO_REINSTALL,
1002                                      DNF_PACKAGE_INFO_DOWNGRADE,
1003                                      DNF_PACKAGE_INFO_UPDATE,
1004                                      -1);
1005     g_debug("Goal has %u packages", packages->len);
1006     for (guint i = 0; i < packages->len; i++) {
1007         auto pkg = static_cast< DnfPackage * >(g_ptr_array_index(packages, i));
1008 
1009         /* get correct package repo */
1010         if (!dnf_transaction_ensure_repo(transaction, pkg, error))
1011             return FALSE;
1012 
1013         /* this is a local file */
1014         if (g_strcmp0(dnf_package_get_reponame(pkg), HY_CMDLINE_REPO_NAME) == 0) {
1015             continue;
1016         }
1017 
1018         /* check package exists and checksum is okay */
1019         if (!dnf_package_check_filename(pkg, &valid, error))
1020             return FALSE;
1021 
1022         /* package needs to be downloaded */
1023         if (!valid) {
1024             g_ptr_array_add(priv->pkgs_to_download, g_object_ref(pkg));
1025         }
1026     }
1027     return TRUE;
CATCH_TO_GERROR(FALSE)1028 } CATCH_TO_GERROR(FALSE)
1029 
1030 /**
1031  * dnf_transaction_reset:
1032  */
1033 static void
1034 dnf_transaction_reset(DnfTransaction *transaction)
1035 {
1036     DnfTransactionPrivate *priv = GET_PRIVATE(transaction);
1037 
1038     /* reset */
1039     priv->child = NULL;
1040     g_ptr_array_set_size(priv->pkgs_to_download, 0);
1041     rpmtsEmpty(priv->ts);
1042     rpmtsSetNotifyCallback(priv->ts, NULL, NULL);
1043 
1044     /* clear */
1045     if (priv->install != NULL) {
1046         g_ptr_array_unref(priv->install);
1047         priv->install = NULL;
1048     }
1049     if (priv->remove != NULL) {
1050         g_ptr_array_unref(priv->remove);
1051         priv->remove = NULL;
1052     }
1053     if (priv->remove_helper != NULL) {
1054         g_ptr_array_unref(priv->remove_helper);
1055         priv->remove_helper = NULL;
1056     }
1057     if (priv->erased_by_package_hash != NULL) {
1058         g_hash_table_unref(priv->erased_by_package_hash);
1059         priv->erased_by_package_hash = NULL;
1060     }
1061 }
1062 
1063 /**
1064  * dnf_transaction_import_keys:
1065  * @transaction: a #DnfTransaction instance.
1066  * @error: A #GError or %NULL
1067  *
1068  * Imports all keys from /etc/pki/rpm-gpg as well as any
1069  * downloaded per-repo keys.  Note this is called automatically
1070  * by dnf_transaction_commit().
1071  **/
1072 gboolean
dnf_transaction_import_keys(DnfTransaction * transaction,GError ** error)1073 dnf_transaction_import_keys(DnfTransaction *transaction, GError **error) try
1074 {
1075     guint i;
1076 
1077     DnfTransactionPrivate *priv = GET_PRIVATE(transaction);
1078     /* import all system wide GPG keys */
1079     if (!dnf_keyring_add_public_keys(priv->keyring, error))
1080         return FALSE;
1081 
1082     /* import downloaded repo GPG keys */
1083     for (i = 0; i < priv->repos->len; i++) {
1084         auto repo = static_cast< DnfRepo * >(g_ptr_array_index(priv->repos, i));
1085         g_auto(GStrv) pubkeys = dnf_repo_get_public_keys(repo);
1086 
1087         /* does this file actually exist */
1088         for (char **iter = pubkeys; iter && *iter; iter++) {
1089             const char *pubkey = *iter;
1090             if (g_file_test(pubkey, G_FILE_TEST_EXISTS)) {
1091                 /* import */
1092                 if (!dnf_keyring_add_public_key(priv->keyring, pubkey, error))
1093                     return FALSE;
1094             }
1095         }
1096     }
1097 
1098     return TRUE;
1099 } CATCH_TO_GERROR(FALSE)
1100 
1101 /**
1102  * dnf_transaction_commit:
1103  * @transaction: a #DnfTransaction instance.
1104  * @goal: A #HyGoal
1105  * @state: A #DnfState
1106  * @error: A #GError or %NULL
1107  *
1108  * Commits a transaction by installing and removing packages.
1109  *
1110  * NOTE: If this fails, you need to call dnf_transaction_depsolve() again.
1111  *
1112  * Returns: %TRUE for success, %FALSE otherwise
1113  *
1114  * Since: 0.1.0
1115  **/
1116 gboolean
1117 dnf_transaction_commit(DnfTransaction *transaction, HyGoal goal, DnfState *state, GError **error) try
1118 {
1119     const gchar *filename;
1120     const gchar *tmp;
1121     gboolean allow_untrusted;
1122     gboolean is_update;
1123     gboolean ret = FALSE;
1124     gint rc;
1125     gint verbosity;
1126     guint i;
1127     guint j;
1128     DnfState *state_local;
1129     GPtrArray *all_obsoleted;
1130     GPtrArray *pkglist;
1131     DnfPackage *pkg;
1132     DnfPackage *pkg_tmp;
1133     rpmprobFilterFlags problems_filter = 0;
1134     rpmtransFlags rpmts_flags = RPMTRANS_FLAG_NONE;
1135     DnfTransactionPrivate *priv = GET_PRIVATE(transaction);
1136     libdnf::Swdb *swdb = priv->swdb;
1137     PluginHookContextTransactionData data{PLUGIN_HOOK_ID_CONTEXT_PRE_TRANSACTION, transaction, goal, state};
1138     DnfSack * sack = hy_goal_get_sack(goal);
1139     DnfSack * rpmdb_version_sack = NULL;
1140     std::string rpmdb_begin;
1141     std::string rpmdb_end;
1142 
1143     /* take lock */
1144     ret = dnf_state_take_lock(state, DNF_LOCK_TYPE_RPMDB, DNF_LOCK_MODE_PROCESS, error);
1145     if (!ret)
1146         goto out;
1147 
1148     /* set state */
1149     if (priv->flags & DNF_TRANSACTION_FLAG_TEST) {
1150         ret = dnf_state_set_steps(state,
1151                                   error,
1152                                   2,  /* install */
1153                                   2,  /* remove */
1154                                   10, /* test-commit */
1155                                   86, /* commit */
1156                                   -1);
1157     } else {
1158         ret = dnf_state_set_steps(state,
1159                                   error,
1160                                   2,  /* install */
1161                                   2,  /* remove */
1162                                   10, /* test-commit */
1163                                   83, /* commit */
1164                                   1,  /* write yumDB */
1165                                   2,  /* delete files */
1166                                   -1);
1167     }
1168     if (!ret)
1169         goto out;
1170 
1171     ret = dnf_transaction_import_keys(transaction, error);
1172     if (!ret)
1173         goto out;
1174 
1175     /* find any packages without valid GPG signatures */
1176     ret = dnf_transaction_check_untrusted(transaction, goal, error);
1177     if (!ret)
1178         goto out;
1179 
1180     // initialize SWDB transaction
1181     swdb->initTransaction();
1182 
1183     dnf_state_action_start(state, DNF_STATE_ACTION_REQUEST, NULL);
1184 
1185     /* get verbosity from the config file */
1186     tmp = dnf_context_get_rpm_verbosity(priv->context);
1187     verbosity = dnf_rpm_verbosity_string_to_value(tmp);
1188     rpmSetVerbosity(verbosity);
1189 
1190     /* setup the transaction */
1191     tmp = dnf_context_get_install_root(priv->context);
1192     rc = rpmtsSetRootDir(priv->ts, tmp);
1193     if (rc < 0) {
1194         ret = FALSE;
1195         g_set_error_literal(error, DNF_ERROR, DNF_ERROR_INTERNAL_ERROR, _("failed to set root"));
1196         goto out;
1197     }
1198     rpmtsSetNotifyCallback(priv->ts, dnf_transaction_ts_progress_cb, transaction);
1199 
1200     /* add things to install */
1201     state_local = dnf_state_get_child(state);
1202     priv->install = dnf_goal_get_packages(goal,
1203                                           DNF_PACKAGE_INFO_INSTALL,
1204                                           DNF_PACKAGE_INFO_REINSTALL,
1205                                           DNF_PACKAGE_INFO_DOWNGRADE,
1206                                           DNF_PACKAGE_INFO_UPDATE,
1207                                           -1);
1208     if (priv->install->len > 0)
1209         dnf_state_set_number_steps(state_local, priv->install->len);
1210     for (i = 0; i < priv->install->len; i++) {
1211 
1212         pkg = static_cast< DnfPackage * >(g_ptr_array_index(priv->install, i));
1213         ret = dnf_transaction_ensure_repo(transaction, pkg, error);
1214         if (!ret)
1215             goto out;
1216 
1217         DnfStateAction action = dnf_package_get_action(pkg);
1218 
1219         /* add the install */
1220         filename = dnf_package_get_filename(pkg);
1221         allow_untrusted = (priv->flags & DNF_TRANSACTION_FLAG_ONLY_TRUSTED) == 0;
1222         is_update = action == DNF_STATE_ACTION_UPDATE || action == DNF_STATE_ACTION_DOWNGRADE;
1223         ret = dnf_rpmts_add_install_filename2(
1224             priv->ts, filename, allow_untrusted, is_update, pkg, error);
1225         if (!ret)
1226             goto out;
1227 
1228         // resolve swdb reason
1229         libdnf::TransactionItemAction swdbAction = libdnf::TransactionItemAction::INSTALL;
1230         if (action == DNF_STATE_ACTION_UPDATE) {
1231             swdbAction = libdnf::TransactionItemAction::UPGRADE;
1232         } else if (action == DNF_STATE_ACTION_DOWNGRADE) {
1233             swdbAction = libdnf::TransactionItemAction::DOWNGRADE;
1234         } else if (action == DNF_STATE_ACTION_REINSTALL) {
1235             swdbAction = libdnf::TransactionItemAction::REINSTALL;
1236         }
1237 
1238         // add item to swdb transaction
1239         _history_write_item(pkg, swdb, swdbAction);
1240 
1241         /* this section done */
1242         ret = dnf_state_done(state_local, error);
1243         if (!ret)
1244             goto out;
1245     }
1246 
1247     /* this section done */
1248     ret = dnf_state_done(state, error);
1249     if (!ret)
1250         goto out;
1251 
1252     /* add things to remove */
1253     priv->remove =
1254         dnf_goal_get_packages(goal, DNF_PACKAGE_INFO_OBSOLETE, DNF_PACKAGE_INFO_REMOVE, -1);
1255     for (i = 0; i < priv->remove->len; i++) {
1256         pkg = static_cast< DnfPackage * >(g_ptr_array_index(priv->remove, i));
1257         ret = dnf_rpmts_add_remove_pkg(priv->ts, pkg, error);
1258         if (!ret)
1259             goto out;
1260 
1261         /* pre-get the pkgid, as this isn't possible to get after
1262          * the sack is invalidated */
1263         if (dnf_package_get_pkgid(pkg) == NULL) {
1264             g_warning("failed to pre-get pkgid for %s", dnf_package_get_package_id(pkg));
1265         }
1266 
1267         libdnf::TransactionItemAction swdbAction = libdnf::TransactionItemAction::REMOVE;
1268 
1269         /* are the things being removed actually being upgraded */
1270         pkg_tmp = dnf_find_pkg_from_name(priv->install, dnf_package_get_name(pkg));
1271         if (pkg_tmp != NULL) {
1272             dnf_package_set_action(pkg, DNF_STATE_ACTION_CLEANUP);
1273             if (dnf_package_evr_cmp(pkg, pkg_tmp)) {
1274                 swdbAction = libdnf::TransactionItemAction::UPGRADED;
1275             } else {
1276                 swdbAction = libdnf::TransactionItemAction::DOWNGRADED;
1277             }
1278         }
1279         _history_write_item(pkg, swdb, swdbAction);
1280     }
1281 
1282     /* add anything that gets obsoleted to a helper array which is used to
1283      * map removed packages auto-added by rpm to actual DnfPackage's */
1284     priv->remove_helper = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
1285     for (i = 0; i < priv->install->len; i++) {
1286         pkg = static_cast< DnfPackage * >(g_ptr_array_index(priv->install, i));
1287         is_update = dnf_package_get_action(pkg) == DNF_STATE_ACTION_UPDATE ||
1288                     dnf_package_get_action(pkg) == DNF_STATE_ACTION_DOWNGRADE;
1289         if (!is_update)
1290             continue;
1291         pkglist = hy_goal_list_obsoleted_by_package(goal, pkg);
1292 
1293         const char *pkg_name = dnf_package_get_name(pkg);
1294 
1295         for (j = 0; j < pkglist->len; j++) {
1296             pkg_tmp = static_cast< DnfPackage * >(g_ptr_array_index(pkglist, j));
1297             g_ptr_array_add(priv->remove_helper, g_object_ref(pkg_tmp));
1298             dnf_package_set_action(pkg_tmp, DNF_STATE_ACTION_CLEANUP);
1299 
1300             const char *pkg_tmp_name = dnf_package_get_name(pkg_tmp);
1301 
1302             if (dnf_find_pkg_from_name(priv->remove, pkg_tmp_name) != NULL) {
1303                 // package is already in remove set - skip resolution
1304                 continue;
1305             }
1306 
1307             libdnf::TransactionItemAction swdbAction = libdnf::TransactionItemAction::OBSOLETED;
1308             if (g_strcmp0(pkg_name, pkg_tmp_name) == 0) {
1309                 // names are identical - package is upgraded/downgraded
1310                 if (dnf_package_evr_cmp(pkg, pkg_tmp)) {
1311                     swdbAction = libdnf::TransactionItemAction::UPGRADED;
1312                 } else {
1313                     swdbAction = libdnf::TransactionItemAction::DOWNGRADED;
1314                 }
1315             }
1316 
1317             if (swdbAction == libdnf::TransactionItemAction::OBSOLETED
1318                 && dnf_find_pkg_from_name(priv->install, pkg_tmp_name) != NULL
1319                 && g_strcmp0(pkg_name, pkg_tmp_name) != 0) {
1320                     // If a package is obsoleted and there's a package with the same name
1321                     // in the install set, skip recording the obsolete in the history db
1322                     // because the package upgrade prevails over the obsolete.
1323                     //
1324                     // Example:
1325                     // grub2-tools-efi obsoletes grub2-tools  # skip as grub2-tools is also upgraded
1326                     // grub2-tools upgrades grub2-tools
1327                     continue;
1328             }
1329 
1330             // TODO SWDB add pkg_tmp replaced_by pkg
1331             _history_write_item(pkg_tmp, swdb, swdbAction);
1332         }
1333         g_ptr_array_unref(pkglist);
1334     }
1335 
1336     /* this section done */
1337     ret = dnf_state_done(state, error);
1338     if (!ret)
1339         goto out;
1340 
1341     /* map updated packages to their previous versions */
1342     priv->erased_by_package_hash =
1343         g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_object_unref);
1344     all_obsoleted = hy_goal_list_obsoleted(goal, NULL);
1345     for (i = 0; i < priv->install->len; i++) {
1346         pkg = static_cast< DnfPackage * >(g_ptr_array_index(priv->install, i));
1347         if (dnf_package_get_action(pkg) != DNF_STATE_ACTION_UPDATE &&
1348             dnf_package_get_action(pkg) != DNF_STATE_ACTION_DOWNGRADE &&
1349             dnf_package_get_action(pkg) != DNF_STATE_ACTION_REINSTALL)
1350             continue;
1351 
1352         pkglist = hy_goal_list_obsoleted_by_package(goal, pkg);
1353         for (j = 0; j < pkglist->len; j++) {
1354             pkg_tmp = static_cast< DnfPackage * >(g_ptr_array_index(pkglist, j));
1355             if (!hy_packagelist_has(all_obsoleted, pkg_tmp)) {
1356                 g_hash_table_insert(priv->erased_by_package_hash,
1357                                     g_strdup(dnf_package_get_package_id(pkg)),
1358                                     g_object_ref(pkg_tmp));
1359             }
1360         }
1361         g_ptr_array_unref(pkglist);
1362     }
1363     g_ptr_array_unref(all_obsoleted);
1364 
1365     /* generate ordering for the transaction */
1366     rpmtsOrder(priv->ts);
1367 
1368     /* run the test transaction */
1369     if (dnf_context_get_check_transaction(priv->context)) {
1370         g_debug("running test transaction");
1371         dnf_state_action_start(state, DNF_STATE_ACTION_TEST_COMMIT, NULL);
1372         priv->state = dnf_state_get_child(state);
1373         priv->step = DNF_TRANSACTION_STEP_IGNORE;
1374         /* the output value of rpmtsCheck is not meaningful */
1375         rpmtsCheck(priv->ts);
1376         dnf_state_action_stop(state);
1377         ret = dnf_rpmts_look_for_problems(priv->ts, error);
1378         if (!ret)
1379             goto out;
1380     }
1381 
1382     /* this section done */
1383     ret = dnf_state_done(state, error);
1384     if (!ret)
1385         goto out;
1386 
1387     /* no signature checking, we've handled that already */
1388     rpmtsSetVSFlags(priv->ts, _RPMVSF_NOSIGNATURES | _RPMVSF_NODIGESTS);
1389 
1390     /* filter diskspace */
1391     if (!dnf_context_get_check_disk_space(priv->context))
1392         problems_filter |= RPMPROB_FILTER_DISKSPACE;
1393     if (priv->flags & DNF_TRANSACTION_FLAG_ALLOW_REINSTALL)
1394         problems_filter |= RPMPROB_FILTER_REPLACEPKG;
1395     if (priv->flags & DNF_TRANSACTION_FLAG_ALLOW_DOWNGRADE)
1396         problems_filter |= RPMPROB_FILTER_OLDPACKAGE;
1397 
1398     if (priv->flags & DNF_TRANSACTION_FLAG_NODOCS)
1399         rpmts_flags |= RPMTRANS_FLAG_NODOCS;
1400 
1401     if (priv->flags & DNF_TRANSACTION_FLAG_TEST) {
1402         /* run the transaction in test mode */
1403         rpmts_flags |= RPMTRANS_FLAG_TEST;
1404 
1405         priv->state = dnf_state_get_child(state);
1406         priv->step = DNF_TRANSACTION_STEP_IGNORE;
1407         rpmtsSetFlags(priv->ts, rpmts_flags);
1408         g_debug("Running transaction in test mode");
1409         dnf_state_set_allow_cancel(state, FALSE);
1410         rc = rpmtsRun(priv->ts, NULL, problems_filter);
1411         if (rc < 0) {
1412             ret = FALSE;
1413             g_set_error(error,
1414                         DNF_ERROR,
1415                         DNF_ERROR_INTERNAL_ERROR,
1416                         _("Error %i running transaction test"),
1417                         rc);
1418             goto out;
1419         }
1420         if (rc > 0) {
1421             ret = dnf_rpmts_look_for_problems(priv->ts, error);
1422             if (!ret)
1423                 goto out;
1424         }
1425 
1426         /* transaction test done; return */
1427         ret = dnf_state_done(state, error);
1428         goto out;
1429     }
1430 
1431     if (!dnf_context_plugin_hook(priv->context, PLUGIN_HOOK_ID_CONTEXT_PRE_TRANSACTION, &data, nullptr))
1432         goto out;
1433 
1434     // FIXME get commandline
1435     if (sack) {
1436         rpmdb_begin = dnf_sack_get_rpmdb_version(sack);
1437     } else {
1438         // if sack is not available, create a custom instance
1439         rpmdb_version_sack = dnf_sack_new();
1440         dnf_sack_load_system_repo(rpmdb_version_sack, nullptr, DNF_SACK_LOAD_FLAG_NONE, nullptr);
1441         rpmdb_begin = dnf_sack_get_rpmdb_version(rpmdb_version_sack);
1442         g_object_unref(rpmdb_version_sack);
1443     }
1444     swdb->beginTransaction(_get_current_time(), rpmdb_begin, "", priv->uid);
1445 
1446     /* run the transaction */
1447     priv->state = dnf_state_get_child(state);
1448     priv->step = DNF_TRANSACTION_STEP_STARTED;
1449     rpmtsSetFlags(priv->ts, rpmts_flags);
1450     g_debug("Running actual transaction");
1451     dnf_state_set_allow_cancel(state, FALSE);
1452     rc = rpmtsRun(priv->ts, NULL, problems_filter);
1453     if (rc < 0) {
1454         ret = FALSE;
1455         g_set_error(
1456             error, DNF_ERROR, DNF_ERROR_INTERNAL_ERROR, _("Error %i running transaction"), rc);
1457         goto out;
1458     }
1459     if (rc > 0) {
1460         ret = dnf_rpmts_look_for_problems(priv->ts, error);
1461         if (!ret)
1462             goto out;
1463     }
1464 
1465     /* hmm, nothing was done... */
1466     if (priv->step != DNF_TRANSACTION_STEP_WRITING) {
1467         if (priv->install->len > 0 || priv->remove->len > 0) {
1468             ret = FALSE;
1469             g_set_error(error,
1470                         DNF_ERROR,
1471                         DNF_ERROR_INTERNAL_ERROR,
1472                         _("Transaction did not go to writing phase, "
1473                         "but returned no error(%i)"),
1474                         priv->step);
1475             goto out;
1476         }
1477     }
1478 
1479     /* this section done */
1480     ret = dnf_state_done(state, error);
1481     if (!ret)
1482         goto out;
1483 
1484     // finalize swdb transaction
1485     // always load a new sack with rpmdb state after the transaction
1486     rpmdb_version_sack = dnf_sack_new();
1487     dnf_sack_load_system_repo(rpmdb_version_sack, nullptr, DNF_SACK_LOAD_FLAG_NONE, nullptr);
1488     rpmdb_end = dnf_sack_get_rpmdb_version(rpmdb_version_sack);
1489     g_object_unref(rpmdb_version_sack);
1490 
1491     swdb->endTransaction(_get_current_time(), rpmdb_end.c_str(), libdnf::TransactionState::DONE);
1492     swdb->closeTransaction();
1493 
1494     data.hookId = PLUGIN_HOOK_ID_CONTEXT_TRANSACTION;
1495     if (!dnf_context_plugin_hook(priv->context, PLUGIN_HOOK_ID_CONTEXT_TRANSACTION, &data, nullptr))
1496         goto out;
1497 
1498     /* this section done */
1499     ret = dnf_state_done(state, error);
1500     if (!ret)
1501         goto out;
1502 
1503     /* remove the files we downloaded */
1504     if (!dnf_context_get_keep_cache(priv->context)) {
1505         state_local = dnf_state_get_child(state);
1506         ret = dnf_transaction_delete_packages(transaction, state_local, error);
1507         if (!ret)
1508             goto out;
1509     }
1510 
1511     if (sack) {
1512         if (auto moduleContainer = dnf_sack_get_module_container(sack)) {
1513             moduleContainer->save();
1514             moduleContainer->updateFailSafeData();
1515         }
1516     }
1517 
1518     /* all sacks are invalid now */
1519     dnf_context_invalidate_full(priv->context,
1520                                 "transaction performed",
1521                                 DNF_CONTEXT_INVALIDATE_FLAG_RPMDB |
1522                                     DNF_CONTEXT_INVALIDATE_FLAG_ENROLLMENT);
1523 
1524     /* this section done */
1525     ret = dnf_state_done(state, error);
1526 out:
1527     dnf_transaction_reset(transaction);
1528     dnf_state_release_locks(state);
1529     return ret;
CATCH_TO_GERROR(FALSE)1530 } CATCH_TO_GERROR(FALSE)
1531 
1532 /**
1533  * dnf_transaction_new:
1534  * @context: a #DnfContext instance.
1535  *
1536  * Creates a new #DnfTransaction.
1537  *
1538  * Returns:(transfer full): a #DnfTransaction
1539  *
1540  * Since: 0.1.0
1541  **/
1542 DnfTransaction *
1543 dnf_transaction_new(DnfContext *context)
1544 {
1545     auto transaction = DNF_TRANSACTION(g_object_new(DNF_TYPE_TRANSACTION, NULL));
1546     auto priv = GET_PRIVATE(transaction);
1547     auto install_root = dnf_context_get_install_root(context);
1548     std::string dbPath;
1549     if (dnf_context_get_write_history(context)) {
1550         gchar *tmp_path = g_build_filename(install_root, libdnf::Swdb::defaultPath, NULL);
1551         dbPath = std::string(tmp_path);
1552         g_free(tmp_path);
1553     } else {
1554         dbPath = ":memory:";
1555     }
1556     priv->swdb = new libdnf::Swdb(dbPath);
1557     priv->context = context;
1558     g_object_add_weak_pointer(G_OBJECT(priv->context), (void **)&priv->context);
1559     priv->ts = rpmtsCreate();
1560     rpmtsSetRootDir(priv->ts, install_root);
1561     priv->keyring = rpmtsGetKeyring(priv->ts, 1);
1562     return transaction;
1563 }
1564 
1565 DnfTransaction *
hookContextTransactionGetTransaction(DnfPluginHookData * data)1566 hookContextTransactionGetTransaction(DnfPluginHookData * data)
1567 {
1568     if (!data) {
1569         auto logger(libdnf::Log::getLogger());
1570         logger->error(tfm::format("%s: was called with data == nullptr", __func__));
1571         return nullptr;
1572     }
1573     if (data->hookId != PLUGIN_HOOK_ID_CONTEXT_PRE_TRANSACTION &&
1574         data->hookId != PLUGIN_HOOK_ID_CONTEXT_TRANSACTION) {
1575         auto logger(libdnf::Log::getLogger());
1576         logger->error(tfm::format("%s: was called with hookId == %i", __func__, data->hookId));
1577         return nullptr;
1578     }
1579     return (static_cast<PluginHookContextTransactionData *>(data))->transaction;
1580 }
1581 
1582 HyGoal
hookContextTransactionGetGoal(DnfPluginHookData * data)1583 hookContextTransactionGetGoal(DnfPluginHookData * data)
1584 {
1585     if (!data) {
1586         auto logger(libdnf::Log::getLogger());
1587         logger->error(tfm::format("%s: was called with data == nullptr", __func__));
1588         return nullptr;
1589     }
1590     if (data->hookId != PLUGIN_HOOK_ID_CONTEXT_PRE_TRANSACTION &&
1591         data->hookId != PLUGIN_HOOK_ID_CONTEXT_TRANSACTION) {
1592         auto logger(libdnf::Log::getLogger());
1593         logger->error(tfm::format("%s: was called with hookId == %i", __func__, data->hookId));
1594         return nullptr;
1595     }
1596     return (static_cast<PluginHookContextTransactionData *>(data))->goal;
1597 }
1598 
1599 DnfState *
hookContextTransactionGetState(DnfPluginHookData * data)1600 hookContextTransactionGetState(DnfPluginHookData * data)
1601 {
1602     if (!data) {
1603         auto logger(libdnf::Log::getLogger());
1604         logger->error(tfm::format("%s: was called with data == nullptr", __func__));
1605         return nullptr;
1606     }
1607     if (data->hookId != PLUGIN_HOOK_ID_CONTEXT_PRE_TRANSACTION &&
1608         data->hookId != PLUGIN_HOOK_ID_CONTEXT_TRANSACTION) {
1609         auto logger(libdnf::Log::getLogger());
1610         logger->error(tfm::format("%s: was called with hookId == %i", __func__, data->hookId));
1611         return nullptr;
1612     }
1613     return (static_cast<PluginHookContextTransactionData *>(data))->state;
1614 }
1615