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  * 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  * SECTION:dnf-goal
23  * @short_description: Helper methods for dealing with hawkey packages.
24  * @include: libdnf.h
25  * @stability: Unstable
26  *
27  * These methods make it easier to get and set extra data on a package.
28  */
29 
30 
31 #include <stdlib.h>
32 #include <string.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <fcntl.h>
36 #include <glib.h>
37 #include <glib/gstdio.h>
38 #include <assert.h>
39 
40 #include <librepo/librepo.h>
41 #include <memory>
42 
43 #include "catch-error.hpp"
44 #include "dnf-context.hpp"
45 #include "dnf-package.h"
46 #include "dnf-types.h"
47 #include "dnf-utils.h"
48 #include "hy-util.h"
49 #include "repo/solvable/Dependency.hpp"
50 #include "repo/solvable/DependencyContainer.hpp"
51 #include "utils/url-encode.hpp"
52 
53 typedef struct {
54     char            *checksum_str;
55     gboolean         user_action;
56     gchar           *filename;
57     gchar           *origin;
58     gchar           *package_id;
59     DnfPackageInfo   info;
60     DnfStateAction   action;
61     DnfRepo         *repo;
62 } DnfPackagePrivate;
63 
64 /**
65  * dnf_package_destroy_func:
66  **/
67 static void
dnf_package_destroy_func(void * userdata)68 dnf_package_destroy_func(void *userdata)
69 {
70     DnfPackagePrivate *priv =(DnfPackagePrivate *) userdata;
71     g_free(priv->filename);
72     g_free(priv->origin);
73     g_free(priv->package_id);
74     g_free(priv->checksum_str);
75     g_slice_free(DnfPackagePrivate, priv);
76 }
77 
78 /**
79  * dnf_package_get_priv:
80  **/
81 static DnfPackagePrivate *
dnf_package_get_priv(DnfPackage * pkg)82 dnf_package_get_priv(DnfPackage *pkg)
83 {
84     DnfPackagePrivate *priv;
85 
86     /* create private area */
87     priv = (DnfPackagePrivate*)g_object_get_data(G_OBJECT(pkg), "DnfPackagePrivate");
88     if (priv != NULL)
89         return priv;
90 
91     priv = g_slice_new0(DnfPackagePrivate);
92     g_object_set_data_full(G_OBJECT(pkg), "DnfPackagePrivate", priv, dnf_package_destroy_func);
93     return priv;
94 }
95 
96 /**
97  * dnf_package_is_local:
98  * @pkg: a #DnfPackage *instance.
99  *
100  * Returns: %TRUE if the pkg is a pkg on local or media filesystem
101  *
102  * Since: 0.38.2
103  **/
104 gboolean
dnf_package_is_local(DnfPackage * pkg)105 dnf_package_is_local(DnfPackage *pkg)
106 {
107     DnfPackagePrivate *priv;
108     priv = dnf_package_get_priv(pkg);
109 
110     assert(priv->repo);
111 
112     if (!dnf_repo_is_local(priv->repo))
113         return FALSE;
114 
115     const gchar *url_location = dnf_package_get_baseurl(pkg);
116     return (!url_location  || (url_location && g_str_has_prefix(url_location, "file:/")));
117 }
118 
119 /**
120  * dnf_package_get_local_baseurl:
121  * @pkg: a #DnfPackage *instance.
122  *
123  * Returns package baseurl converted to a local filesystem path (i.e. "file://"
124  * is stripped and the URL is decoded). In case the URL is not local, returns
125  * %NULL.
126  *
127  * The returned string is newly allocated and ownership is transferred to the
128  * caller.
129  *
130  * Returns: local filesystem baseurl or %NULL
131  *
132  * Since: 0.54.0
133  **/
134 gchar *
dnf_package_get_local_baseurl(DnfPackage * pkg,GError ** error)135 dnf_package_get_local_baseurl(DnfPackage *pkg, GError **error)
136 {
137     const gchar *baseurl = dnf_package_get_baseurl(pkg);
138 
139     if (!baseurl || !g_str_has_prefix(baseurl, "file://")) {
140         return nullptr;
141     }
142 
143     return g_strdup(libdnf::urlDecode(baseurl + 7).c_str());
144 }
145 
146 /**
147  * dnf_package_get_filename:
148  * @pkg: a #DnfPackage *instance.
149  *
150  * Gets the package filename.
151  *
152  * Returns: absolute filename, or %NULL
153  *
154  * Since: 0.1.0
155  **/
156 const gchar *
dnf_package_get_filename(DnfPackage * pkg)157 dnf_package_get_filename(DnfPackage *pkg)
158 {
159     DnfPackagePrivate *priv;
160 
161     priv = dnf_package_get_priv(pkg);
162     if (!priv)
163         return NULL;
164     if (dnf_package_installed(pkg))
165         return NULL;
166     if (!priv->filename && !priv->repo)
167         return NULL;
168 
169     /* default cache filename location */
170     if (!priv->filename) {
171         if (dnf_package_is_local(pkg)) {
172             const gchar *url_location = dnf_package_get_baseurl(pkg);
173             if (!url_location){
174                 url_location = dnf_repo_get_location(priv->repo);
175             }
176             priv->filename = g_build_filename(url_location,
177                                               dnf_package_get_location(pkg),
178                                               NULL);
179         } else {
180             /* set the filename to cachedir for non-local repos */
181             g_autofree gchar *basename = NULL;
182             basename = g_path_get_basename(dnf_package_get_location(pkg));
183             priv->filename = g_build_filename(dnf_repo_get_packages(priv->repo),
184                                basename,
185                                NULL);
186         }
187         g_assert (priv->filename); /* Pacify static analysis */
188     }
189 
190     /* remove file:// from filename */
191     if (g_str_has_prefix(priv->filename, "file:///")){
192         gchar *tmp = priv->filename;
193         priv->filename = g_strdup(tmp + 7);
194         g_free(tmp);
195         goto out;
196     }
197 
198     /* remove file: from filename */
199     if (strlen(priv->filename) >= 7){
200         if (g_str_has_prefix(priv->filename, "file:/")){
201             if (priv->filename[6] != '/'){
202                 gchar *tmp = priv->filename;
203                 priv->filename = g_strdup(tmp + 5);
204                 g_free(tmp);
205             }
206         }
207     }
208 out:
209     return priv->filename;
210 }
211 
212 /**
213  * dnf_package_get_origin:
214  * @pkg: a #DnfPackage *instance.
215  *
216  * Gets the package origin.
217  *
218  * Returns: the package origin, or %NULL
219  *
220  * Since: 0.1.0
221  **/
222 const gchar *
dnf_package_get_origin(DnfPackage * pkg)223 dnf_package_get_origin(DnfPackage *pkg)
224 {
225     DnfPackagePrivate *priv;
226     priv = dnf_package_get_priv(pkg);
227     if (priv == NULL)
228         return NULL;
229     if (!dnf_package_installed(pkg))
230         return NULL;
231     return priv->origin;
232 }
233 
234 /**
235  * dnf_package_get_pkgid:
236  * @pkg: a #DnfPackage *instance.
237  *
238  * Gets the pkgid, which is the SHA hash of the package header.
239  *
240  * Returns: pkgid string, or NULL
241  *
242  * Since: 0.1.0
243  **/
244 const gchar *
dnf_package_get_pkgid(DnfPackage * pkg)245 dnf_package_get_pkgid(DnfPackage *pkg)
246 {
247     const unsigned char *checksum;
248     DnfPackagePrivate *priv;
249     int checksum_type;
250 
251     priv = dnf_package_get_priv(pkg);
252     if (priv == NULL)
253         return NULL;
254     if (priv->checksum_str != NULL)
255         goto out;
256 
257     /* calculate and cache */
258     checksum = dnf_package_get_hdr_chksum(pkg, &checksum_type);
259     if (checksum == NULL)
260         goto out;
261     priv->checksum_str = hy_chksum_str(checksum, checksum_type);
262 out:
263     return priv->checksum_str;
264 }
265 
266 /**
267  * dnf_package_set_pkgid:
268  * @pkg: a #DnfPackage *instance.
269  * @pkgid: pkgid, e.g. "e6e3b2b10c1ef1033769147dbd1bf851c7de7699"
270  *
271  * Sets the package pkgid, which is the SHA hash of the package header.
272  *
273  * Since: 0.1.8
274  **/
275 void
dnf_package_set_pkgid(DnfPackage * pkg,const gchar * pkgid)276 dnf_package_set_pkgid(DnfPackage *pkg, const gchar *pkgid)
277 {
278     DnfPackagePrivate *priv;
279     g_return_if_fail(pkgid != NULL);
280     priv = dnf_package_get_priv(pkg);
281     if (priv == NULL)
282         return;
283     g_free(priv->checksum_str);
284     priv->checksum_str = strdup(pkgid);
285 }
286 
287 /**
288  * dnf_package_id_build:
289  **/
290 static gchar *
dnf_package_id_build(const gchar * name,const gchar * version,const gchar * arch,const gchar * data)291 dnf_package_id_build(const gchar *name,
292               const gchar *version,
293               const gchar *arch,
294               const gchar *data)
295 {
296     return g_strjoin(";", name,
297               version != NULL ? version : "",
298               arch != NULL ? arch : "",
299               data != NULL ? data : "",
300               NULL);
301 }
302 
303 /**
304  * dnf_package_get_package_id:
305  * @pkg: a #DnfPackage *instance.
306  *
307  * Gets the package-id as used by PackageKit.
308  *
309  * Returns: the package_id string, or %NULL, e.g. "hal;2:0.3.4;i386;installed:fedora"
310  *
311  * Since: 0.1.0
312  **/
313 const gchar *
dnf_package_get_package_id(DnfPackage * pkg)314 dnf_package_get_package_id(DnfPackage *pkg)
315 {
316     DnfPackagePrivate *priv;
317     const gchar *reponame;
318     g_autofree gchar *reponame_tmp = NULL;
319 
320     priv = dnf_package_get_priv(pkg);
321     if (priv == NULL)
322         return NULL;
323     if (priv->package_id != NULL)
324         goto out;
325 
326     /* calculate and cache */
327     reponame = dnf_package_get_reponame(pkg);
328     if (g_strcmp0(reponame, HY_SYSTEM_REPO_NAME) == 0) {
329         /* origin data to munge into the package_id data field */
330         if (priv->origin != NULL) {
331             reponame_tmp = g_strdup_printf("installed:%s", priv->origin);
332             reponame = reponame_tmp;
333         } else {
334             reponame = "installed";
335         }
336     } else if (g_strcmp0(reponame, HY_CMDLINE_REPO_NAME) == 0) {
337         reponame = "local";
338     }
339     priv->package_id = dnf_package_id_build(dnf_package_get_name(pkg),
340                         dnf_package_get_evr(pkg),
341                         dnf_package_get_arch(pkg),
342                         reponame);
343 out:
344     return priv->package_id;
345 }
346 
347 /**
348  * dnf_package_get_cost:
349  * @pkg: a #DnfPackage *instance.
350  *
351  * Returns the cost of the repo that provided the package.
352  *
353  * Returns: the cost, where higher is more expensive, default 1000
354  *
355  * Since: 0.1.0
356  **/
357 guint
dnf_package_get_cost(DnfPackage * pkg)358 dnf_package_get_cost(DnfPackage *pkg)
359 {
360     DnfPackagePrivate *priv;
361     priv = dnf_package_get_priv(pkg);
362     if (priv->repo == NULL) {
363         g_warning("no repo for %s", dnf_package_get_package_id(pkg));
364         return G_MAXUINT;
365     }
366     return dnf_repo_get_cost(priv->repo);
367 }
368 
369 /**
370  * dnf_package_set_filename:
371  * @pkg: a #DnfPackage *instance.
372  * @filename: absolute filename.
373  *
374  * Sets the file on disk that matches the package repo.
375  *
376  * Since: 0.1.0
377  **/
378 void
dnf_package_set_filename(DnfPackage * pkg,const gchar * filename)379 dnf_package_set_filename(DnfPackage *pkg, const gchar *filename)
380 {
381     DnfPackagePrivate *priv;
382 
383     /* replace contents */
384     priv = dnf_package_get_priv(pkg);
385     if (priv == NULL)
386         return;
387     g_free(priv->filename);
388     priv->filename = g_strdup(filename);
389 }
390 
391 /**
392  * dnf_package_set_origin:
393  * @pkg: a #DnfPackage *instance.
394  * @origin: origin, e.g. "fedora"
395  *
396  * Sets the package origin repo.
397  *
398  * Since: 0.1.0
399  **/
400 void
dnf_package_set_origin(DnfPackage * pkg,const gchar * origin)401 dnf_package_set_origin(DnfPackage *pkg, const gchar *origin)
402 {
403     DnfPackagePrivate *priv;
404     priv = dnf_package_get_priv(pkg);
405     if (priv == NULL)
406         return;
407     g_free(priv->origin);
408     priv->origin = g_strdup(origin);
409 }
410 
411 /**
412  * dnf_package_set_repo:
413  * @pkg: a #DnfPackage *instance.
414  * @repo: a #DnfRepo.
415  *
416  * Sets the repo the package was created from.
417  *
418  * Since: 0.1.0
419  **/
420 void
dnf_package_set_repo(DnfPackage * pkg,DnfRepo * repo)421 dnf_package_set_repo(DnfPackage *pkg, DnfRepo *repo)
422 {
423     DnfPackagePrivate *priv;
424     priv = dnf_package_get_priv(pkg);
425     if (priv == NULL)
426         return;
427     priv->repo = repo;
428 }
429 
430 /**
431  * dnf_package_get_repo:
432  * @pkg: a #DnfPackage *instance.
433  *
434  * Gets the repo the package was created from.
435  *
436  * Returns: a #DnfRepo or %NULL
437  *
438  * Since: 0.1.0
439  **/
440 DnfRepo *
dnf_package_get_repo(DnfPackage * pkg)441 dnf_package_get_repo(DnfPackage *pkg)
442 {
443     DnfPackagePrivate *priv;
444     priv = dnf_package_get_priv(pkg);
445     if (priv == NULL)
446         return NULL;
447     return priv->repo;
448 }
449 
450 /**
451  * dnf_package_get_info:
452  * @pkg: a #DnfPackage *instance.
453  *
454  * Gets the info enum assigned to the package.
455  *
456  * Returns: #DnfPackageInfo value
457  *
458  * Since: 0.1.0
459  **/
460 DnfPackageInfo
dnf_package_get_info(DnfPackage * pkg)461 dnf_package_get_info(DnfPackage *pkg)
462 {
463     DnfPackagePrivate *priv;
464     priv = dnf_package_get_priv(pkg);
465     if (priv == NULL)
466         return DNF_PACKAGE_INFO_UNKNOWN;
467     return priv->info;
468 }
469 
470 /**
471  * dnf_package_get_action:
472  * @pkg: a #DnfPackage *instance.
473  *
474  * Gets the action assigned to the package, i.e. what is going to be performed.
475  *
476  * Returns: a #DnfStateAction
477  *
478  * Since: 0.1.0
479  */
480 DnfStateAction
dnf_package_get_action(DnfPackage * pkg)481 dnf_package_get_action(DnfPackage *pkg)
482 {
483     DnfPackagePrivate *priv;
484     priv = dnf_package_get_priv(pkg);
485     if (priv == NULL)
486         return DNF_STATE_ACTION_UNKNOWN;
487     return priv->action;
488 }
489 
490 /**
491  * dnf_package_set_info:
492  * @pkg: a #DnfPackage *instance.
493  * @info: the info flags.
494  *
495  * Sets the info flags for the package.
496  *
497  * Since: 0.1.0
498  **/
499 void
dnf_package_set_info(DnfPackage * pkg,DnfPackageInfo info)500 dnf_package_set_info(DnfPackage *pkg, DnfPackageInfo info)
501 {
502     DnfPackagePrivate *priv;
503     priv = dnf_package_get_priv(pkg);
504     if (priv == NULL)
505         return;
506     priv->info = info;
507 }
508 
509 /**
510  * dnf_package_set_action:
511  * @pkg: a #DnfPackage *instance.
512  * @action: the #DnfStateAction for the package.
513  *
514  * Sets the action for the package, i.e. what is going to be performed.
515  *
516  * Since: 0.1.0
517  */
518 void
dnf_package_set_action(DnfPackage * pkg,DnfStateAction action)519 dnf_package_set_action(DnfPackage *pkg, DnfStateAction action)
520 {
521     DnfPackagePrivate *priv;
522     priv = dnf_package_get_priv(pkg);
523     if (priv == NULL)
524         return;
525     priv->action = action;
526 }
527 
528 /**
529  * dnf_package_get_user_action:
530  * @pkg: a #DnfPackage *instance.
531  *
532  * Gets if the package was installed or removed as the user action.
533  *
534  * Returns: %TRUE if the package was explicitly requested
535  *
536  * Since: 0.1.0
537  **/
538 gboolean
dnf_package_get_user_action(DnfPackage * pkg)539 dnf_package_get_user_action(DnfPackage *pkg)
540 {
541     DnfPackagePrivate *priv;
542     priv = dnf_package_get_priv(pkg);
543     if (priv == NULL)
544         return FALSE;
545     return priv->user_action;
546 }
547 
548 /**
549  * dnf_package_set_user_action:
550  * @pkg: a #DnfPackage *instance.
551  * @user_action: %TRUE if the package was explicitly requested.
552  *
553  * Sets if the package was installed or removed as the user action.
554  *
555  * Since: 0.1.0
556  **/
557 void
dnf_package_set_user_action(DnfPackage * pkg,gboolean user_action)558 dnf_package_set_user_action(DnfPackage *pkg, gboolean user_action)
559 {
560     DnfPackagePrivate *priv;
561     priv = dnf_package_get_priv(pkg);
562     if (priv == NULL)
563         return;
564     priv->user_action = user_action;
565 }
566 
567 /**
568  * dnf_package_is_gui:
569  * @pkg: a #DnfPackage *instance.
570  *
571  * Returns: %TRUE if the package is a GUI package
572  *
573  * Since: 0.1.0
574  **/
575 gboolean
dnf_package_is_gui(DnfPackage * pkg)576 dnf_package_is_gui(DnfPackage *pkg)
577 {
578     gboolean ret = FALSE;
579     const gchar *tmp;
580     gint idx;
581     gint size;
582 
583     /* find if the package depends on GTK or KDE */
584     std::unique_ptr<DnfReldepList> reldep_list(dnf_package_get_requires(pkg));
585     size = reldep_list->count();
586     for (idx = 0; idx < size && !ret; idx++) {
587         auto reldep = reldep_list->get(idx);
588         tmp = reldep->toString();
589         if (g_strstr_len(tmp, -1, "libgtk") != NULL ||
590             g_strstr_len(tmp, -1, "libQt5Gui.so") != NULL ||
591             g_strstr_len(tmp, -1, "libQtGui.so") != NULL ||
592             g_strstr_len(tmp, -1, "libqt-mt.so") != NULL) {
593             ret = TRUE;
594         }
595     }
596 
597     return ret;
598 }
599 
600 /**
601  * dnf_package_is_devel:
602  * @pkg: a #DnfPackage *instance.
603  *
604  * Returns: %TRUE if the package is a development package
605  *
606  * Since: 0.1.0
607  **/
608 gboolean
dnf_package_is_devel(DnfPackage * pkg)609 dnf_package_is_devel(DnfPackage *pkg)
610 {
611     const gchar *name;
612     name = dnf_package_get_name(pkg);
613     if (g_str_has_suffix(name, "-debuginfo"))
614         return TRUE;
615     if (g_str_has_suffix(name, "-devel"))
616         return TRUE;
617     if (g_str_has_suffix(name, "-static"))
618         return TRUE;
619     if (g_str_has_suffix(name, "-libs"))
620         return TRUE;
621     return FALSE;
622 }
623 
624 /**
625  * dnf_package_is_downloaded:
626  * @pkg: a #DnfPackage *instance.
627  *
628  * Returns: %TRUE if the package is already downloaded
629  *
630  * Since: 0.1.0
631  **/
632 gboolean
dnf_package_is_downloaded(DnfPackage * pkg)633 dnf_package_is_downloaded(DnfPackage *pkg)
634 {
635     const gchar *filename;
636 
637     if (dnf_package_installed(pkg))
638         return FALSE;
639     filename = dnf_package_get_filename(pkg);
640     if (filename == NULL) {
641         g_warning("Failed to get cache filename for %s",
642                dnf_package_get_name(pkg));
643         return FALSE;
644     }
645     return g_file_test(filename, G_FILE_TEST_EXISTS);
646 }
647 
648 /**
649  * dnf_package_is_installonly:
650  * @pkg: a #DnfPackage *instance.
651  *
652  * Returns: %TRUE if the package can be installed more than once
653  *
654  * Since: 0.1.0
655  */
656 gboolean
dnf_package_is_installonly(DnfPackage * pkg)657 dnf_package_is_installonly(DnfPackage *pkg)
658 {
659     if (auto * pkg_name = dnf_package_get_name(pkg)) {
660         auto & mainConf = libdnf::getGlobalMainConfig();
661         for (auto & inst_only_pkg_name : mainConf.installonlypkgs().getValue()) {
662             if (inst_only_pkg_name == pkg_name) {
663                 return TRUE;
664             }
665         }
666     }
667     return FALSE;
668 }
669 
670 /**
671  * dnf_repo_checksum_hy_to_lr:
672  **/
673 static LrChecksumType
dnf_repo_checksum_hy_to_lr(GChecksumType checksum)674 dnf_repo_checksum_hy_to_lr(GChecksumType checksum)
675 {
676     if (checksum == G_CHECKSUM_MD5)
677         return LR_CHECKSUM_MD5;
678     if (checksum == G_CHECKSUM_SHA1)
679         return LR_CHECKSUM_SHA1;
680     if (checksum == G_CHECKSUM_SHA256)
681         return LR_CHECKSUM_SHA256;
682     if (checksum == G_CHECKSUM_SHA384)
683         return LR_CHECKSUM_SHA384;
684     return LR_CHECKSUM_SHA512;
685 }
686 
687 /**
688  * dnf_package_check_filename:
689  * @pkg: a #DnfPackage *instance.
690  * @valid: Set to %TRUE if the package is valid.
691  * @error: a #GError or %NULL..
692  *
693  * Checks the package is already downloaded and valid.
694  *
695  * Returns: %TRUE if the package was checked successfully
696  *
697  * Since: 0.1.0
698  **/
699 gboolean
dnf_package_check_filename(DnfPackage * pkg,gboolean * valid,GError ** error)700 dnf_package_check_filename(DnfPackage *pkg, gboolean *valid, GError **error) try
701 {
702     LrChecksumType checksum_type_lr;
703     char *checksum_valid = NULL;
704     const gchar *path;
705     const unsigned char *checksum;
706     gboolean ret = TRUE;
707     int checksum_type_hy;
708     int fd;
709 
710     /* check if the file does not exist */
711     path = dnf_package_get_filename(pkg);
712     g_debug("checking if %s already exists...", path);
713     if (!g_file_test(path, G_FILE_TEST_EXISTS)) {
714         *valid = FALSE;
715 
716         /* a missing file in a local repo is an error, unless it is remote via base:url,
717          * since we can't download it */
718         if (dnf_package_is_local(pkg)) {
719             ret = FALSE;
720             g_set_error(error,
721                         DNF_ERROR,
722                         DNF_ERROR_INTERNAL_ERROR,
723                         "File missing in local repository %s", path);
724         }
725 
726         goto out;
727     }
728 
729     /* check the checksum */
730     checksum = dnf_package_get_chksum(pkg, &checksum_type_hy);
731     checksum_valid = hy_chksum_str(checksum, checksum_type_hy);
732     checksum_type_lr = dnf_repo_checksum_hy_to_lr((GChecksumType)checksum_type_hy);
733     fd = g_open(path, O_RDONLY, 0);
734     if (fd < 0) {
735         ret = FALSE;
736         g_set_error(error,
737                  DNF_ERROR,
738                  DNF_ERROR_INTERNAL_ERROR,
739                  "Failed to open %s", path);
740         goto out;
741     }
742     ret = lr_checksum_fd_cmp(checksum_type_lr,
743                  fd,
744                  checksum_valid,
745                  TRUE, /* use xattr value */
746                  valid,
747                  error);
748     if (!ret) {
749         g_close(fd, NULL);
750         goto out;
751     }
752     ret = g_close(fd, error);
753     if (!ret)
754         goto out;
755 
756     /* A checksum mismatch for a package in a local repository is an
757        error.  We can't repair it by downloading a corrected version,
758        so let's fail here. */
759     if (!*valid && dnf_repo_is_local (dnf_package_get_repo (pkg))) {
760         ret = FALSE;
761         g_set_error(error,
762                     DNF_ERROR,
763                     DNF_ERROR_INTERNAL_ERROR,
764                     "Checksum mismatch in local repository %s", path);
765         goto out;
766     }
767 
768 out:
769     g_free(checksum_valid);
770     return ret;
771 } CATCH_TO_GERROR(FALSE)
772 
773 /**
774  * dnf_package_download:
775  * @pkg: a #DnfPackage *instance.
776  * @directory: destination directory, or %NULL for the cachedir.
777  * @state: the #DnfState.
778  * @error: a #GError or %NULL..
779  *
780  * Downloads the package.
781  *
782  * Returns: the complete filename of the downloaded file
783  *
784  * Since: 0.1.0
785  **/
786 gchar *
787 dnf_package_download(DnfPackage *pkg,
788               const gchar *directory,
789               DnfState *state,
790               GError **error) try
791 {
792     DnfRepo *repo;
793     repo = dnf_package_get_repo(pkg);
794     if (repo == NULL) {
795         g_set_error_literal(error,
796                      DNF_ERROR,
797                      DNF_ERROR_INTERNAL_ERROR,
798                      "package repo is unset");
799         return NULL;
800     }
801     return dnf_repo_download_package(repo, pkg, directory, state, error);
CATCH_TO_GERROR(NULL)802 } CATCH_TO_GERROR(NULL)
803 
804 /**
805  * dnf_package_array_download:
806  * @packages: an array of packages.
807  * @directory: destination directory, or %NULL for the cachedir.
808  * @state: the #DnfState.
809  * @error: a #GError or %NULL..
810  *
811  * Downloads an array of packages.
812  *
813  * Returns: %TRUE for success
814  *
815  * Since: 0.1.0
816  */
817 gboolean
818 dnf_package_array_download(GPtrArray *packages,
819                 const gchar *directory,
820                 DnfState *state,
821                 GError **error) try
822 {
823     DnfState *state_local;
824     GHashTableIter hiter;
825     gpointer key, value;
826     guint i;
827     g_autoptr(GHashTable) repo_to_packages = NULL;
828 
829     /* map packages to repos */
830     repo_to_packages = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)g_ptr_array_unref);
831     for (i = 0; i < packages->len; i++) {
832         DnfPackage *pkg = (DnfPackage*)g_ptr_array_index(packages, i);
833         DnfRepo *repo;
834         GPtrArray *repo_packages;
835 
836         repo = dnf_package_get_repo(pkg);
837         if (repo == NULL) {
838             g_set_error_literal(error,
839                                 DNF_ERROR,
840                                 DNF_ERROR_INTERNAL_ERROR,
841                                 "package repo is unset");
842             return FALSE;
843         }
844         repo_packages = (GPtrArray*)g_hash_table_lookup(repo_to_packages, repo);
845         if (repo_packages == NULL) {
846             repo_packages = g_ptr_array_new();
847             g_hash_table_insert(repo_to_packages, repo, repo_packages);
848         }
849         g_ptr_array_add(repo_packages, pkg);
850     }
851 
852     /* set steps according to the number of repos we are going to download from */
853     dnf_state_set_number_steps(state, g_hash_table_size(repo_to_packages));
854 
855     /* download all packages from each repo in one go */
856     g_hash_table_iter_init(&hiter, repo_to_packages);
857     while (g_hash_table_iter_next(&hiter, &key, &value)) {
858         DnfRepo *repo = (DnfRepo*)key;
859         GPtrArray *repo_packages = (GPtrArray*)value;
860 
861         state_local = dnf_state_get_child(state);
862         if (!dnf_repo_download_packages(repo, repo_packages, directory, state_local, error))
863             return FALSE;
864 
865         /* done */
866         if (!dnf_state_done(state, error))
867             return FALSE;
868     }
869     return TRUE;
870 } CATCH_TO_GERROR(FALSE)
871 
872 /**
873  * dnf_package_array_get_download_size:
874  * @packages: an array of packages.
875  *
876  * Gets the download size for an array of packages.
877  *
878  * Returns: the download size
879  *
880  * Since: 0.2.3
881  */
882 guint64
883 dnf_package_array_get_download_size(GPtrArray *packages)
884 {
885     guint i;
886     guint64 download_size = 0;
887 
888     for (i = 0; i < packages->len; i++) {
889         DnfPackage *pkg = (DnfPackage*)g_ptr_array_index(packages, i);
890 
891         download_size += dnf_package_get_downloadsize(pkg);
892     }
893 
894     return download_size;
895 }
896