1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  *
3  * Copyright (C) 2013-2015 Richard Hughes <richard@hughsie.com>
4  *
5  * Licensed under the GNU Lesser General Public License Version 2.1
6  *
7  * Most of this code was taken from Zif, libzif/zif-transaction.c
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or(at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
22  */
23 
24 /**
25  * SECTION:dnf-goal
26  * @short_description: Helper methods for dealing with rpm transactions.
27  * @include: libdnf.h
28  * @stability: Unstable
29  *
30  * These methods make it easier to deal with rpm transactions.
31  */
32 
33 
34 #include <glib.h>
35 #include <rpm/rpmlib.h>
36 #include <rpm/rpmlog.h>
37 #include <rpm/rpmdb.h>
38 
39 #include "catch-error.hpp"
40 #include "sack/packageset.hpp"
41 #include "hy-package-private.hpp"
42 #include "dnf-rpmts-private.hpp"
43 #include "dnf-types.h"
44 #include "dnf-utils.h"
45 
46 #include "utils/bgettext/bgettext-lib.h"
47 #include "dnf-package.h"
48 
49 // older RPM doesn't have RPMTAG_MODULARITYLABEL defined
50 #ifndef RPMTAG_MODULARITYLABEL
51 #define RPMTAG_MODULARITYLABEL 5096
52 #endif
53 
54 
55 static gboolean
test_fail_safe(Header * hdr,DnfPackage * pkg,GError ** error)56 test_fail_safe(Header * hdr, DnfPackage * pkg, GError **error)
57 {
58     if (dnf_package_installed(pkg)) {
59         return TRUE;
60     }
61     if (strcmp(dnf_package_get_reponame(pkg), HY_CMDLINE_REPO_NAME) == 0) {
62         return TRUE;
63     }
64     if (auto repo = dnf_package_get_repo(pkg)) {
65         if (dnf_repo_get_module_hotfixes(repo)) {
66             return TRUE;
67         }
68     } else {
69         return TRUE;
70     }
71     rpmtd td = rpmtdNew();
72     gboolean ret = TRUE;
73     if (headerGet(*hdr, RPMTAG_MODULARITYLABEL, td, HEADERGET_MINMEM)) {
74         if (rpmtdGetString(td)) {
75             DnfSack * sack = dnf_package_get_sack(pkg);
76             std::unique_ptr<libdnf::PackageSet> includes(dnf_sack_get_module_includes(sack));
77             if (!includes || !includes->has(dnf_package_get_id(pkg))) {
78                 g_set_error(error, DNF_ERROR, DNF_ERROR_INTERNAL_ERROR,
79                             _("No available modular metadata for modular package '%s'; "
80                               "cannot be installed on the system"),
81                             dnf_package_get_nevra(pkg));
82                 ret = FALSE;
83             }
84         }
85     }
86     rpmtdFreeData(td);
87     rpmtdFree(td);
88     return ret;
89 }
90 
91 gboolean
dnf_rpmts_add_install_filename2(rpmts ts,const gchar * filename,gboolean allow_untrusted,gboolean is_update,DnfPackage * pkg,GError ** error)92 dnf_rpmts_add_install_filename2(rpmts ts,
93                                 const gchar *filename,
94                                 gboolean allow_untrusted,
95                                 gboolean is_update,
96                                 DnfPackage * pkg,
97                                 GError **error) try
98 {
99     gboolean ret = TRUE;
100     gint res;
101     Header hdr;
102     FD_t fd;
103 
104     /* open this */
105     fd = Fopen(filename, "r.ufdio");
106     res = rpmReadPackageFile(ts, fd, filename, &hdr);
107 
108     /* be less strict when we're allowing untrusted transactions */
109     if (allow_untrusted) {
110         switch(res) {
111         case RPMRC_NOKEY:
112         case RPMRC_NOTFOUND:
113         case RPMRC_NOTTRUSTED:
114         case RPMRC_OK:
115             break;
116         case RPMRC_FAIL:
117             ret = FALSE;
118             g_set_error(error,
119                         DNF_ERROR,
120                         DNF_ERROR_INTERNAL_ERROR,
121                         _("signature does not verify for %s"),
122                         filename);
123             goto out;
124         default:
125             ret = FALSE;
126             g_set_error(error,
127                         DNF_ERROR,
128                         DNF_ERROR_INTERNAL_ERROR,
129                         _("failed to open(generic error): %s"),
130                         filename);
131             goto out;
132         }
133     } else {
134         switch(res) {
135         case RPMRC_OK:
136             break;
137         case RPMRC_NOTTRUSTED:
138             ret = FALSE;
139             g_set_error(error,
140                         DNF_ERROR,
141                         DNF_ERROR_INTERNAL_ERROR,
142                         _("failed to verify key for %s"),
143                         filename);
144             goto out;
145         case RPMRC_NOKEY:
146             ret = FALSE;
147             g_set_error(error,
148                         DNF_ERROR,
149                         DNF_ERROR_INTERNAL_ERROR,
150                         _("public key unavailable for %s"),
151                         filename);
152             goto out;
153         case RPMRC_NOTFOUND:
154             ret = FALSE;
155             g_set_error(error,
156                         DNF_ERROR,
157                         DNF_ERROR_INTERNAL_ERROR,
158                         _("signature not found for %s"),
159                         filename);
160             goto out;
161         case RPMRC_FAIL:
162             ret = FALSE;
163             g_set_error(error,
164                         DNF_ERROR,
165                         DNF_ERROR_INTERNAL_ERROR,
166                         _("signature does not verify for %s"),
167                         filename);
168             goto out;
169         default:
170             ret = FALSE;
171             g_set_error(error,
172                         DNF_ERROR,
173                         DNF_ERROR_INTERNAL_ERROR,
174                         _("failed to open(generic error): %s"),
175                         filename);
176             goto out;
177         }
178     }
179     if (pkg) {
180         if (!test_fail_safe(&hdr, pkg, error)) {
181             ret = FALSE;
182             goto out;
183         }
184     }
185 
186     /* add to the transaction */
187     res = rpmtsAddInstallElement(ts, hdr, (fnpyKey) filename, is_update, NULL);
188     if (res != 0) {
189         ret = FALSE;
190         g_set_error(error,
191                     DNF_ERROR,
192                     DNF_ERROR_INTERNAL_ERROR,
193                     _("failed to add install element: %1$s [%2$i]"),
194                     filename, res);
195         goto out;
196     }
197 out:
198     Fclose(fd);
199     headerFree(hdr);
200     return ret;
201 } CATCH_TO_GERROR(FALSE)
202 
203 /**
204  * dnf_rpmts_add_install_filename:
205  * @ts: a #rpmts instance.
206  * @filename: the package.
207  * @allow_untrusted: is we can add untrusted packages.
208  * @is_update: if the package is an update.
209  * @error: a #GError or %NULL..
210  *
211  * Add to the transaction a package to be installed.
212  *
213  * Returns: %TRUE for success, %FALSE otherwise
214  *
215  * Since: 0.1.0
216  **/
217 gboolean
218 dnf_rpmts_add_install_filename(rpmts ts,
219                                const gchar *filename,
220                                gboolean allow_untrusted,
221                                gboolean is_update,
222                                GError **error) try
223 {
224     return dnf_rpmts_add_install_filename2(ts, filename, allow_untrusted, is_update, NULL, error);
CATCH_TO_GERROR(FALSE)225 } CATCH_TO_GERROR(FALSE)
226 
227 
228 /**
229  * dnf_rpmts_look_for_problems:
230  * @ts: a #rpmts instance.
231  * @error: a #GError or %NULL..
232  *
233  * Look for problems in the transaction.
234  *
235  * Returns: %TRUE for success, %FALSE otherwise
236  *
237  * Since: 0.1.0
238  **/
239 gboolean
240 dnf_rpmts_look_for_problems(rpmts ts, GError **error) try
241 {
242     gboolean ret = TRUE;
243     rpmProblem prob;
244     rpmpsi psi;
245     rpmps probs = NULL;
246     g_autoptr(GString) string = NULL;
247 
248     /* get a list of problems */
249     probs = rpmtsProblems(ts);
250     if (rpmpsNumProblems(probs) == 0)
251         goto out;
252 
253     /* parse problems */
254     string = g_string_new("");
255     psi = rpmpsInitIterator(probs);
256     while (rpmpsNextIterator(psi) >= 0) {
257         g_autofree gchar *msg = NULL;
258         prob = rpmpsGetProblem(psi);
259         msg = rpmProblemString(prob);
260         g_string_append(string, msg);
261         g_string_append(string, "\n");
262     }
263     rpmpsFreeIterator(psi);
264 
265     /* set error */
266     ret = FALSE;
267 
268     /* we failed, and got a reason to report */
269     if (string->len > 0) {
270         g_string_set_size(string, string->len - 1);
271         g_set_error(error,
272                     DNF_ERROR,
273                     DNF_ERROR_INTERNAL_ERROR,
274                     _("Error running transaction: %s"),
275                     string->str);
276         goto out;
277     }
278 
279     /* we failed, and got no reason why */
280     g_set_error_literal(error,
281                         DNF_ERROR,
282                         DNF_ERROR_INTERNAL_ERROR,
283                         _("Error running transaction and no problems were reported!"));
284 out:
285     rpmpsFree(probs);
286     return ret;
287 } CATCH_TO_GERROR(FALSE)
288 
289 /**
290  * dnf_rpmts_log_handler_cb:
291  **/
292 static int
293 dnf_rpmts_log_handler_cb(rpmlogRec rec, rpmlogCallbackData data)
294 {
295     GString **string =(GString **) data;
296 
297     /* only log errors */
298     if (rpmlogRecPriority(rec) != RPMLOG_ERR)
299         return RPMLOG_DEFAULT;
300 
301     /* do not log internal BDB errors */
302     if (g_strstr_len(rpmlogRecMessage(rec), -1, "BDB") != NULL)
303         return 0;
304 
305     /* create string if required */
306     if (*string == NULL)
307         *string = g_string_new("");
308 
309     /* if text already exists, join them */
310     if ((*string)->len > 0)
311         g_string_append(*string, ": ");
312     g_string_append(*string, rpmlogRecMessage(rec));
313 
314     /* remove the trailing /n which rpm does */
315     if ((*string)->len > 0)
316         g_string_truncate(*string,(*string)->len - 1);
317     return 0;
318 }
319 
320 /**
321  * dnf_rpmts_find_package:
322  **/
323 static Header
dnf_rpmts_find_package(rpmts ts,DnfPackage * pkg,GError ** error)324 dnf_rpmts_find_package(rpmts ts, DnfPackage *pkg, GError **error)
325 {
326     Header hdr = NULL;
327     rpmdbMatchIterator iter;
328     unsigned int recOffset;
329     g_autoptr(GString) rpm_error = NULL;
330 
331     /* find package by db-id */
332     recOffset = dnf_package_get_rpmdbid(pkg);
333     rpmlogSetCallback(dnf_rpmts_log_handler_cb, &rpm_error);
334     iter = rpmtsInitIterator(ts, RPMDBI_PACKAGES,
335                  &recOffset, sizeof(recOffset));
336     if (iter == NULL) {
337         if (rpm_error != NULL) {
338             g_set_error_literal(error,
339                                 DNF_ERROR,
340                                 DNF_ERROR_UNFINISHED_TRANSACTION,
341                                 rpm_error->str);
342         } else {
343             g_set_error_literal(error,
344                                 DNF_ERROR,
345                                 DNF_ERROR_UNFINISHED_TRANSACTION,
346                                 _("Fatal error, run database recovery"));
347         }
348         goto out;
349     }
350     hdr = rpmdbNextIterator(iter);
351     if (hdr == NULL) {
352         g_set_error(error,
353                     DNF_ERROR,
354                     DNF_ERROR_FILE_NOT_FOUND,
355                     _("failed to find package %s"),
356                     dnf_package_get_name(pkg));
357         goto out;
358     }
359 
360     /* success */
361     headerLink(hdr);
362 out:
363     rpmlogSetCallback(NULL, NULL);
364     if (iter != NULL)
365         rpmdbFreeIterator(iter);
366     return hdr;
367 }
368 
369 /**
370  * dnf_rpmts_add_remove_pkg:
371  * @ts: a #rpmts instance.
372  * @pkg: a #DnfPackage *instance.
373  * @error: a #GError or %NULL..
374  *
375  * Adds to the transaction a package to be removed.
376  *
377  * Returns: %TRUE for success, %FALSE otherwise
378  *
379  * Since: 0.1.0
380  **/
381 gboolean
dnf_rpmts_add_remove_pkg(rpmts ts,DnfPackage * pkg,GError ** error)382 dnf_rpmts_add_remove_pkg(rpmts ts, DnfPackage *pkg, GError **error) try
383 {
384     gboolean ret = TRUE;
385     gint retval;
386     Header hdr;
387 
388     hdr = dnf_rpmts_find_package(ts, pkg, error);
389     if (hdr == NULL) {
390         ret = FALSE;
391         goto out;
392     }
393 
394     /* remove it */
395     retval = rpmtsAddEraseElement(ts, hdr, -1);
396     if (retval != 0) {
397         ret = FALSE;
398         g_set_error(error,
399                     DNF_ERROR,
400                     DNF_ERROR_INTERNAL_ERROR,
401                     _("could not add erase element %1$s(%2$i)"),
402                     dnf_package_get_name(pkg), retval);
403         goto out;
404     }
405 out:
406     if (hdr != NULL)
407         headerFree(hdr);
408     return ret;
409 } CATCH_TO_GERROR(FALSE)
410