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 * Most of this code was taken from Zif, libzif/zif-repos.c
6 *
7 * Licensed under the GNU Lesser General Public License Version 2.1
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 /**
26 * SECTION:dnf-repo
27 * @short_description: Object representing a remote repo.
28 * @include: libdnf.h
29 * @stability: Unstable
30 *
31 * Sources are remote repositories of packages.
32 *
33 * See also: #DnfRepo
34 */
35
36 #include "conf/OptionBool.hpp"
37
38 #include "dnf-context.hpp"
39 #include "hy-repo-private.hpp"
40 #include "hy-iutil-private.hpp"
41
42 #include <strings.h>
43 #include <fcntl.h>
44 #include <fnmatch.h>
45 #include <glib/gstdio.h>
46 #include "hy-util.h"
47 #include <librepo/librepo.h>
48 #include <rpm/rpmts.h>
49 #include <librepo/yum.h>
50
51 #include "catch-error.hpp"
52 #include "dnf-keyring.h"
53 #include "dnf-package.h"
54 #include "dnf-repo.hpp"
55 #include "dnf-types.h"
56 #include "dnf-utils.h"
57 #include "utils/File.hpp"
58 #include "utils/url-encode.hpp"
59 #include "utils/utils.hpp"
60
61 #include <set>
62 #include <string>
63 #include <vector>
64
65 typedef struct
66 {
67 DnfRepoEnabled enabled;
68 gchar **exclude_packages;
69 gchar *filename; /* /etc/yum.repos.d/updates.repo */
70 gchar *location; /* /var/cache/PackageKit/metadata/fedora */
71 gchar *location_tmp; /* /var/cache/PackageKit/metadata/fedora.tmp */
72 gchar *packages; /* /var/cache/PackageKit/metadata/fedora/packages */
73 gchar *packages_tmp; /* /var/cache/PackageKit/metadata/fedora.tmp/packages */
74 gchar *keyring; /* /var/cache/PackageKit/metadata/fedora/gpgdir */
75 gchar *keyring_tmp; /* /var/cache/PackageKit/metadata/fedora.tmp/gpgdir */
76 gint64 timestamp_generated; /* µs */
77 gint64 timestamp_modified; /* µs */
78 GError *last_check_error;
79 GKeyFile *keyfile;
80 DnfContext *context; /* weak reference */
81 DnfRepoKind kind;
82 libdnf::Repo *repo;
83 LrHandle *repo_handle;
84 LrResult *repo_result;
85 LrUrlVars *urlvars;
86 } DnfRepoPrivate;
87
G_DEFINE_TYPE_WITH_PRIVATE(DnfRepo,dnf_repo,G_TYPE_OBJECT)88 G_DEFINE_TYPE_WITH_PRIVATE(DnfRepo, dnf_repo, G_TYPE_OBJECT)
89 #define GET_PRIVATE(o) (static_cast<DnfRepoPrivate *>(dnf_repo_get_instance_private (o)))
90
91 /**
92 * dnf_repo_finalize:
93 **/
94 static void
95 dnf_repo_finalize(GObject *object)
96 {
97 DnfRepo *repo = DNF_REPO(object);
98 DnfRepoPrivate *priv = GET_PRIVATE(repo);
99
100 g_free(priv->filename);
101 g_strfreev(priv->exclude_packages);
102 g_free(priv->location_tmp);
103 g_free(priv->location);
104 g_free(priv->packages);
105 g_free(priv->packages_tmp);
106 g_free(priv->keyring);
107 g_free(priv->keyring_tmp);
108 g_clear_error(&priv->last_check_error);
109 if (priv->repo_result != NULL)
110 lr_result_free(priv->repo_result);
111 if (priv->repo_handle != NULL)
112 lr_handle_free(priv->repo_handle);
113 if (priv->repo != NULL)
114 hy_repo_free(priv->repo);
115 if (priv->keyfile != NULL)
116 g_key_file_unref(priv->keyfile);
117 if (priv->context != NULL)
118 g_object_remove_weak_pointer(G_OBJECT(priv->context),
119 (void **) &priv->context);
120
121 G_OBJECT_CLASS(dnf_repo_parent_class)->finalize(object);
122 }
123
124 /**
125 * dnf_repo_init:
126 **/
127 static void
dnf_repo_init(DnfRepo * repo)128 dnf_repo_init(DnfRepo *repo)
129 {
130 DnfRepoPrivate *priv = GET_PRIVATE(repo);
131 priv->repo = hy_repo_create("<preinit>");
132 priv->repo_handle = lr_handle_init();
133 priv->repo_result = lr_result_init();
134 }
135
136 /**
137 * dnf_repo_class_init:
138 **/
139 static void
dnf_repo_class_init(DnfRepoClass * klass)140 dnf_repo_class_init(DnfRepoClass *klass)
141 {
142 GObjectClass *object_class = G_OBJECT_CLASS(klass);
143 object_class->finalize = dnf_repo_finalize;
144 }
145
146 /**
147 * dnf_repo_get_id:
148 * @repo: a #DnfRepo instance.
149 *
150 * Gets the repo ID.
151 *
152 * Returns: the repo ID, e.g. "fedora-updates"
153 *
154 * Since: 0.1.0
155 **/
156 const gchar *
dnf_repo_get_id(DnfRepo * repo)157 dnf_repo_get_id(DnfRepo *repo)
158 {
159 DnfRepoPrivate *priv = GET_PRIVATE(repo);
160 return libdnf::repoGetImpl(priv->repo)->id.c_str();
161 }
162
163 /**
164 * dnf_repo_get_location:
165 * @repo: a #DnfRepo instance.
166 *
167 * Gets the repo location.
168 *
169 * Returns: the repo location, e.g. "/var/cache/PackageKit/metadata/fedora"
170 *
171 * Since: 0.1.0
172 **/
173 const gchar *
dnf_repo_get_location(DnfRepo * repo)174 dnf_repo_get_location(DnfRepo *repo)
175 {
176 DnfRepoPrivate *priv = GET_PRIVATE(repo);
177 return priv->location;
178 }
179
180 /**
181 * dnf_repo_get_filename:
182 * @repo: a #DnfRepo instance.
183 *
184 * Gets the repo filename.
185 *
186 * Returns: the repo filename, e.g. "/etc/yum.repos.d/updates.repo"
187 *
188 * Since: 0.1.0
189 **/
190 const gchar *
dnf_repo_get_filename(DnfRepo * repo)191 dnf_repo_get_filename(DnfRepo *repo)
192 {
193 DnfRepoPrivate *priv = GET_PRIVATE(repo);
194 return priv->filename;
195 }
196
197 /**
198 * dnf_repo_get_packages:
199 * @repo: a #DnfRepo instance.
200 *
201 * Gets the repo packages location.
202 *
203 * Returns: the repo packages location, e.g. "/var/cache/PackageKit/metadata/fedora/packages"
204 *
205 * Since: 0.1.0
206 **/
207 const gchar *
dnf_repo_get_packages(DnfRepo * repo)208 dnf_repo_get_packages(DnfRepo *repo)
209 {
210 DnfRepoPrivate *priv = GET_PRIVATE(repo);
211 return priv->packages;
212 }
213
214 /**
215 * dnf_repo_get_public_keys:
216 * @repo: a #DnfRepo instance.
217 *
218 * Gets the public key location.
219 *
220 * Returns: (transfer full)(array zero-terminated=1): The repo public key location, e.g. "/var/cache/PackageKit/metadata/fedora/repomd.pub"
221 *
222 * Since: 0.8.2
223 **/
224 gchar **
dnf_repo_get_public_keys(DnfRepo * repo)225 dnf_repo_get_public_keys(DnfRepo *repo)
226 {
227 DnfRepoPrivate *priv = GET_PRIVATE(repo);
228 const auto & keys = priv->repo->getConfig()->gpgkey().getValue();
229 gchar **ret = g_new0(gchar *, keys.size() + 1);
230 for (size_t i = 0; i < keys.size(); ++i) {
231 g_autofree gchar *key_bn = g_path_get_basename(keys[i].c_str());
232 ret[i] = g_build_filename(priv->location, key_bn, NULL);
233 }
234 return ret;
235 }
236
237 /**
238 * dnf_repo_substitute:
239 */
240 static gchar *
dnf_repo_substitute(DnfRepo * repo,const gchar * url)241 dnf_repo_substitute(DnfRepo *repo, const gchar *url)
242 {
243 DnfRepoPrivate *priv = GET_PRIVATE(repo);
244 char *tmp;
245 gchar *substituted;
246
247 /* do a little dance so we can use g_free() rather than lr_free() */
248 tmp = lr_url_substitute(url, priv->urlvars);
249 substituted = g_strdup(tmp);
250 lr_free(tmp);
251
252 return substituted;
253 }
254
255 /**
256 * dnf_repo_get_description:
257 * @repo: a #DnfRepo instance.
258 *
259 * Gets the repo description.
260 *
261 * Returns: the repo description, e.g. "Fedora 20 Updates"
262 *
263 * Since: 0.1.0
264 **/
265 gchar *
dnf_repo_get_description(DnfRepo * repo)266 dnf_repo_get_description(DnfRepo *repo)
267 {
268 DnfRepoPrivate *priv = GET_PRIVATE(repo);
269 g_autofree gchar *tmp = NULL;
270
271 /* is DVD */
272 if (priv->kind == DNF_REPO_KIND_MEDIA) {
273 tmp = g_key_file_get_string(priv->keyfile, "general", "name", NULL);
274 if (tmp == NULL)
275 return NULL;
276 } else {
277 tmp = g_key_file_get_string(priv->keyfile,
278 dnf_repo_get_id(repo),
279 "name",
280 NULL);
281 if (tmp == NULL)
282 return NULL;
283 }
284
285 /* have to substitute things like $releasever and $basearch */
286 return dnf_repo_substitute(repo, tmp);
287 }
288
289 /**
290 * dnf_repo_get_timestamp_generated:
291 * @repo: a #DnfRepo instance.
292 *
293 * Returns: Time in seconds since the epoch (UTC)
294 **/
295 guint64
dnf_repo_get_timestamp_generated(DnfRepo * repo)296 dnf_repo_get_timestamp_generated (DnfRepo *repo)
297 {
298 DnfRepoPrivate *priv = GET_PRIVATE(repo);
299 return priv->timestamp_generated;
300 }
301
302 /**
303 * dnf_repo_get_n_solvables:
304 * @repo: a #DnfRepo instance.
305 *
306 * Returns: Number of packages in the repo
307 **/
308 guint
dnf_repo_get_n_solvables(DnfRepo * repo)309 dnf_repo_get_n_solvables (DnfRepo *repo)
310 {
311 DnfRepoPrivate *priv = GET_PRIVATE(repo);
312 return libdnf::repoGetImpl(priv->repo)->libsolvRepo->nsolvables;
313 }
314
315 /**
316 * dnf_repo_get_enabled:
317 * @repo: a #DnfRepo instance.
318 *
319 * Gets if the repo is enabled.
320 *
321 * Returns: %DNF_REPO_ENABLED_PACKAGES if enabled
322 *
323 * Since: 0.1.0
324 **/
325 DnfRepoEnabled
dnf_repo_get_enabled(DnfRepo * repo)326 dnf_repo_get_enabled(DnfRepo *repo)
327 {
328 DnfRepoPrivate *priv = GET_PRIVATE(repo);
329 return priv->enabled;
330 }
331
332 /**
333 * dnf_repo_get_required:
334 * @repo: a #DnfRepo instance.
335 *
336 * Returns: %TRUE if failure fetching this repo is fatal
337 *
338 * Since: 0.2.1
339 **/
340 gboolean
dnf_repo_get_required(DnfRepo * repo)341 dnf_repo_get_required(DnfRepo *repo)
342 {
343 DnfRepoPrivate *priv = GET_PRIVATE(repo);
344 return !priv->repo->getConfig()->skip_if_unavailable().getValue();
345 }
346
347 /**
348 * dnf_repo_get_cost:
349 * @repo: a #DnfRepo instance.
350 *
351 * Gets the repo cost.
352 *
353 * Returns: the repo cost, where 1000 is default
354 *
355 * Since: 0.1.0
356 **/
357 guint
dnf_repo_get_cost(DnfRepo * repo)358 dnf_repo_get_cost(DnfRepo *repo)
359 {
360 DnfRepoPrivate *priv = GET_PRIVATE(repo);
361 return repoGetImpl(priv->repo)->conf->cost().getValue();
362 }
363
364 /**
365 * dnf_repo_get_module_hotfixes:
366 * @repo: a #DnfRepo instance.
367 *
368 * Gets the repo hotfixes flag for module RPMs filtering.
369 *
370 * Returns: the repo module_hotfixes flag, where false is default
371 *
372 * Since: 0.16.1
373 **/
374 gboolean
dnf_repo_get_module_hotfixes(DnfRepo * repo)375 dnf_repo_get_module_hotfixes(DnfRepo *repo)
376 {
377 DnfRepoPrivate *priv = GET_PRIVATE(repo);
378 return priv->repo->getConfig()->module_hotfixes().getValue();
379 }
380
381 /**
382 * dnf_repo_get_kind:
383 * @repo: a #DnfRepo instance.
384 *
385 * Gets the repo kind.
386 *
387 * Returns: the repo kind, e.g. %DNF_REPO_KIND_MEDIA
388 *
389 * Since: 0.1.0
390 **/
391 DnfRepoKind
dnf_repo_get_kind(DnfRepo * repo)392 dnf_repo_get_kind(DnfRepo *repo)
393 {
394 DnfRepoPrivate *priv = GET_PRIVATE(repo);
395 return priv->kind;
396 }
397
398 /**
399 * dnf_repo_get_exclude_packages:
400 * @repo: a #DnfRepo instance.
401 *
402 * Returns:(transfer none)(array zero-terminated=1): Packages which should be excluded
403 *
404 * Since: 0.1.9
405 **/
406 gchar **
dnf_repo_get_exclude_packages(DnfRepo * repo)407 dnf_repo_get_exclude_packages(DnfRepo *repo)
408 {
409 DnfRepoPrivate *priv = GET_PRIVATE(repo);
410 return priv->exclude_packages;
411 }
412
413 /**
414 * dnf_repo_get_gpgcheck:
415 * @repo: a #DnfRepo instance.
416 *
417 * Gets if the packages should be signed.
418 *
419 * Returns: %TRUE if packages should be signed
420 *
421 * Since: 0.1.0
422 **/
423 gboolean
dnf_repo_get_gpgcheck(DnfRepo * repo)424 dnf_repo_get_gpgcheck(DnfRepo *repo)
425 {
426 DnfRepoPrivate *priv = GET_PRIVATE(repo);
427 return priv->repo->getConfig()->gpgcheck().getValue();
428 }
429
430 /**
431 * dnf_repo_get_gpgcheck_md:
432 * @repo: a #DnfRepo instance.
433 *
434 * Gets if the metadata should be signed.
435 *
436 * Returns: %TRUE if metadata should be signed
437 *
438 * Since: 0.1.7
439 **/
440 gboolean
dnf_repo_get_gpgcheck_md(DnfRepo * repo)441 dnf_repo_get_gpgcheck_md(DnfRepo *repo)
442 {
443 DnfRepoPrivate *priv = GET_PRIVATE(repo);
444 return priv->repo->getConfig()->repo_gpgcheck().getValue();
445 }
446
447 /**
448 * dnf_repo_get_repo:
449 * @repo: a #DnfRepo instance.
450 *
451 * Gets the #HyRepo backing this repo.
452 *
453 * Returns: the #HyRepo
454 *
455 * Since: 0.1.0
456 **/
457 HyRepo
dnf_repo_get_repo(DnfRepo * repo)458 dnf_repo_get_repo(DnfRepo *repo)
459 {
460 DnfRepoPrivate *priv = GET_PRIVATE(repo);
461 return priv->repo;
462 }
463
464 /**
465 * dnf_repo_get_metadata_expire:
466 * @repo: a #DnfRepo instance.
467 *
468 * Gets the metadata_expire time for the repo
469 *
470 * Returns: the metadata_expire time in seconds
471 */
472 guint
dnf_repo_get_metadata_expire(DnfRepo * repo)473 dnf_repo_get_metadata_expire(DnfRepo *repo)
474 {
475 DnfRepoPrivate *priv = GET_PRIVATE(repo);
476 return priv->repo->getConfig()->metadata_expire().getValue();
477 }
478
479 /**
480 * dnf_repo_is_devel:
481 * @repo: a #DnfRepo instance.
482 *
483 * Returns: %TRUE if the repo is a development repo
484 *
485 * Since: 0.1.0
486 **/
487 gboolean
dnf_repo_is_devel(DnfRepo * repo)488 dnf_repo_is_devel(DnfRepo *repo)
489 {
490 DnfRepoPrivate *priv = GET_PRIVATE(repo);
491 auto repoId = priv->repo->getId().c_str();
492 if (g_str_has_suffix(repoId, "-debuginfo"))
493 return TRUE;
494 if (g_str_has_suffix(repoId, "-debug"))
495 return TRUE;
496 if (g_str_has_suffix(repoId, "-development"))
497 return TRUE;
498 return FALSE;
499 }
500
501 /**
502 * dnf_repo_is_local:
503 * @repo: a #DnfRepo instance.
504 *
505 * Returns: %TRUE if the repo is a repo on local or media filesystem
506 *
507 * Since: 0.1.6
508 **/
509 gboolean
dnf_repo_is_local(DnfRepo * repo)510 dnf_repo_is_local(DnfRepo *repo)
511 {
512 DnfRepoPrivate *priv = GET_PRIVATE(repo);
513
514 /* media or local */
515 if (priv->kind == DNF_REPO_KIND_MEDIA ||
516 priv->kind == DNF_REPO_KIND_LOCAL)
517 return TRUE;
518
519 return FALSE;
520 }
521
522 /**
523 * dnf_repo_is_source:
524 * @repo: a #DnfRepo instance.
525 *
526 * Returns: %TRUE if the repo is a source repo
527 *
528 * Since: 0.1.0
529 **/
530 gboolean
dnf_repo_is_source(DnfRepo * repo)531 dnf_repo_is_source(DnfRepo *repo)
532 {
533 DnfRepoPrivate *priv = GET_PRIVATE(repo);
534 if (g_str_has_suffix(priv->repo->getId().c_str(), "-source"))
535 return TRUE;
536 return FALSE;
537 }
538
539 /**
540 * dnf_repo_set_id:
541 * @repo: a #DnfRepo instance.
542 * @id: the ID, e.g. "fedora-updates"
543 *
544 * Sets the repo ID.
545 *
546 * Since: 0.1.0
547 **/
548 void
dnf_repo_set_id(DnfRepo * repo,const gchar * id)549 dnf_repo_set_id(DnfRepo *repo, const gchar *id)
550 {
551 DnfRepoPrivate *priv = GET_PRIVATE(repo);
552 libdnf::repoGetImpl(priv->repo)->id = id;
553 libdnf::repoGetImpl(priv->repo)->conf->name().set(libdnf::Option::Priority::RUNTIME, id);
554 }
555
556 /**
557 * dnf_repo_set_location:
558 * @repo: a #DnfRepo instance.
559 * @location: the path
560 *
561 * Sets the repo location and the default packages path.
562 *
563 * Since: 0.1.0
564 **/
565 void
dnf_repo_set_location(DnfRepo * repo,const gchar * location)566 dnf_repo_set_location(DnfRepo *repo, const gchar *location)
567 {
568 DnfRepoPrivate *priv = GET_PRIVATE(repo);
569 g_free(priv->location);
570 g_free(priv->packages);
571 g_free(priv->keyring);
572 priv->location = dnf_repo_substitute(repo, location);
573 priv->packages = g_build_filename(location, "packages", NULL);
574 priv->keyring = g_build_filename(location, "gpgdir", NULL);
575 }
576
577 /**
578 * dnf_repo_set_location_tmp:
579 * @repo: a #DnfRepo instance.
580 * @location_tmp: the temp path
581 *
582 * Sets the repo location for temporary files and the default packages path.
583 *
584 * Since: 0.1.0
585 **/
586 void
dnf_repo_set_location_tmp(DnfRepo * repo,const gchar * location_tmp)587 dnf_repo_set_location_tmp(DnfRepo *repo, const gchar *location_tmp)
588 {
589 DnfRepoPrivate *priv = GET_PRIVATE(repo);
590 g_free(priv->location_tmp);
591 g_free(priv->packages_tmp);
592 g_free(priv->keyring_tmp);
593 priv->location_tmp = dnf_repo_substitute(repo, location_tmp);
594 priv->packages_tmp = g_build_filename(location_tmp, "packages", NULL);
595 priv->keyring_tmp = g_build_filename(location_tmp, "gpgdir", NULL);
596 }
597
598 /**
599 * dnf_repo_set_filename:
600 * @repo: a #DnfRepo instance.
601 * @filename: the filename path
602 *
603 * Sets the repo backing filename.
604 *
605 * Since: 0.1.0
606 **/
607 void
dnf_repo_set_filename(DnfRepo * repo,const gchar * filename)608 dnf_repo_set_filename(DnfRepo *repo, const gchar *filename)
609 {
610 DnfRepoPrivate *priv = GET_PRIVATE(repo);
611
612 g_free(priv->filename);
613 priv->filename = g_strdup(filename);
614 }
615
616 /**
617 * dnf_repo_set_packages:
618 * @repo: a #DnfRepo instance.
619 * @packages: the path to the package files
620 *
621 * Sets the repo package location, typically ending in "Packages" or "packages".
622 *
623 * Since: 0.1.0
624 **/
625 void
dnf_repo_set_packages(DnfRepo * repo,const gchar * packages)626 dnf_repo_set_packages(DnfRepo *repo, const gchar *packages)
627 {
628 DnfRepoPrivate *priv = GET_PRIVATE(repo);
629 g_free(priv->packages);
630 priv->packages = g_strdup(packages);
631 }
632
633 /**
634 * dnf_repo_set_packages_tmp:
635 * @repo: a #DnfRepo instance.
636 * @packages_tmp: the path to the temporary package files
637 *
638 * Sets the repo package location for temporary files.
639 *
640 * Since: 0.1.0
641 **/
642 void
dnf_repo_set_packages_tmp(DnfRepo * repo,const gchar * packages_tmp)643 dnf_repo_set_packages_tmp(DnfRepo *repo, const gchar *packages_tmp)
644 {
645 DnfRepoPrivate *priv = GET_PRIVATE(repo);
646 g_free(priv->packages_tmp);
647 priv->packages_tmp = g_strdup(packages_tmp);
648 }
649
650 /**
651 * dnf_repo_set_enabled:
652 * @repo: a #DnfRepo instance.
653 * @enabled: if the repo is enabled
654 *
655 * Sets the repo enabled state.
656 *
657 * Since: 0.1.0
658 **/
659 void
dnf_repo_set_enabled(DnfRepo * repo,DnfRepoEnabled enabled)660 dnf_repo_set_enabled(DnfRepo *repo, DnfRepoEnabled enabled)
661 {
662 DnfRepoPrivate *priv = GET_PRIVATE(repo);
663
664 priv->enabled = enabled;
665
666 /* packages implies metadata */
667 if (priv->enabled & DNF_REPO_ENABLED_PACKAGES)
668 priv->enabled |= DNF_REPO_ENABLED_METADATA;
669 }
670
671 /**
672 * dnf_repo_set_required:
673 * @repo: a #DnfRepo instance.
674 * @required: if the repo is required
675 *
676 * Sets whether failure to retrieve the repo is fatal; by default it
677 * is not.
678 *
679 * Since: 0.2.1
680 **/
681 void
dnf_repo_set_required(DnfRepo * repo,gboolean required)682 dnf_repo_set_required(DnfRepo *repo, gboolean required)
683 {
684 DnfRepoPrivate *priv = GET_PRIVATE(repo);
685 priv->repo->getConfig()->skip_if_unavailable().set(libdnf::Option::Priority::RUNTIME, !required);
686 }
687
688 /**
689 * dnf_repo_set_cost:
690 * @repo: a #DnfRepo instance.
691 * @cost: the repo cost
692 *
693 * Sets the repo cost, where 1000 is the default.
694 *
695 * Since: 0.1.0
696 **/
697 void
dnf_repo_set_cost(DnfRepo * repo,guint cost)698 dnf_repo_set_cost(DnfRepo *repo, guint cost)
699 {
700 DnfRepoPrivate *priv = GET_PRIVATE(repo);
701 repoGetImpl(priv->repo)->conf->cost().set(libdnf::Option::Priority::RUNTIME, cost);
702 }
703
704 /**
705 * dnf_repo_set_module_hotfixes:
706 * @repo: a #DnfRepo instance.
707 * @module_hotfixes: hotfixes flag for module RPMs filtering
708 *
709 * Sets the repo hotfixes flag for module RPMs filtering.
710 *
711 * Since: 0.16.1
712 **/
713 void
dnf_repo_set_module_hotfixes(DnfRepo * repo,gboolean module_hotfixes)714 dnf_repo_set_module_hotfixes(DnfRepo *repo, gboolean module_hotfixes)
715 {
716 DnfRepoPrivate *priv = GET_PRIVATE(repo);
717 priv->repo->getConfig()->module_hotfixes().set(libdnf::Option::Priority::RUNTIME, module_hotfixes);
718 }
719
720 /**
721 * dnf_repo_set_kind:
722 * @repo: a #DnfRepo instance.
723 * @kind: the #DnfRepoKind
724 *
725 * Sets the repo kind.
726 *
727 * Since: 0.1.0
728 **/
729 void
dnf_repo_set_kind(DnfRepo * repo,DnfRepoKind kind)730 dnf_repo_set_kind(DnfRepo *repo, DnfRepoKind kind)
731 {
732 DnfRepoPrivate *priv = GET_PRIVATE(repo);
733 priv->kind = kind;
734 }
735
736 /**
737 * dnf_repo_set_gpgcheck:
738 * @repo: a #DnfRepo instance.
739 * @gpgcheck_pkgs: if the repo packages should be signed
740 *
741 * Sets the repo signed status.
742 *
743 * Since: 0.1.0
744 **/
745 void
dnf_repo_set_gpgcheck(DnfRepo * repo,gboolean gpgcheck_pkgs)746 dnf_repo_set_gpgcheck(DnfRepo *repo, gboolean gpgcheck_pkgs)
747 {
748 DnfRepoPrivate *priv = GET_PRIVATE(repo);
749 priv->repo->getConfig()->gpgcheck().set(libdnf::Option::Priority::RUNTIME, gpgcheck_pkgs);
750 }
751
752 /**
753 * dnf_repo_set_skip_if_unavailiable:
754 * @repo: a #DnfRepo instance.
755 * @skip_if_unavailable: whether the repo should be skipped if unavailable
756 *
757 * Sets the repo skip_if_unavailable status
758 *
759 * Since: 0.7.3
760 **/
761 void
dnf_repo_set_skip_if_unavailable(DnfRepo * repo,gboolean skip_if_unavailable)762 dnf_repo_set_skip_if_unavailable(DnfRepo *repo, gboolean skip_if_unavailable)
763 {
764 DnfRepoPrivate *priv = GET_PRIVATE(repo);
765 priv->repo->getConfig()->skip_if_unavailable().set(libdnf::Option::Priority::RUNTIME, skip_if_unavailable);
766 }
767
768 /**
769 * dnf_repo_set_gpgcheck_md:
770 * @repo: a #DnfRepo instance.
771 * @gpgcheck_md: if the repo metadata should be signed
772 *
773 * Sets the metadata signed status.
774 *
775 * Since: 0.1.7
776 **/
777 void
dnf_repo_set_gpgcheck_md(DnfRepo * repo,gboolean gpgcheck_md)778 dnf_repo_set_gpgcheck_md(DnfRepo *repo, gboolean gpgcheck_md)
779 {
780 DnfRepoPrivate *priv = GET_PRIVATE(repo);
781 priv->repo->getConfig()->repo_gpgcheck().set(libdnf::Option::Priority::RUNTIME, gpgcheck_md);
782 }
783
784 /**
785 * dnf_repo_set_keyfile:
786 * @repo: a #DnfRepo instance.
787 * @keyfile: the #GKeyFile
788 *
789 * Sets the repo keyfile used to create the repo.
790 *
791 * Since: 0.1.0
792 **/
793 void
dnf_repo_set_keyfile(DnfRepo * repo,GKeyFile * keyfile)794 dnf_repo_set_keyfile(DnfRepo *repo, GKeyFile *keyfile)
795 {
796 DnfRepoPrivate *priv = GET_PRIVATE(repo);
797 if (priv->keyfile != NULL)
798 g_key_file_unref(priv->keyfile);
799 priv->keyfile = g_key_file_ref(keyfile);
800 }
801
802 /**
803 * dnf_repo_set_metadata_expire:
804 * @repo: a #DnfRepo instance.
805 * @metadata_expire: the expected expiry time for metadata
806 *
807 * Sets the metadata_expire time, which defaults to 48h.
808 **/
809 void
dnf_repo_set_metadata_expire(DnfRepo * repo,guint metadata_expire)810 dnf_repo_set_metadata_expire(DnfRepo *repo, guint metadata_expire)
811 {
812 DnfRepoPrivate *priv = GET_PRIVATE(repo);
813 priv->repo->getConfig()->metadata_expire().set(libdnf::Option::Priority::RUNTIME, metadata_expire);
814 }
815
816 /**
817 * @brief Format user password string
818 *
819 * Returns user and password in user:password form. If encode is True,
820 * special characters in user and password are URL encoded.
821 *
822 * @param user Username
823 * @param passwd Password
824 * @param encode If quote is True, special characters in user and password are URL encoded.
825 * @return User and password in user:password form
826 */
formatUserPassString(const std::string & user,const std::string & passwd,bool encode)827 static std::string formatUserPassString(const std::string & user, const std::string & passwd, bool encode)
828 {
829 if (encode)
830 return libdnf::urlEncode(user) + ":" + libdnf::urlEncode(passwd);
831 else
832 return user + ":" + passwd;
833 }
834
835 /* Resets repository configuration options previously readed from repository
836 * configuration file to initial state. */
837 static void
dnf_repo_conf_reset(libdnf::ConfigRepo & config)838 dnf_repo_conf_reset(libdnf::ConfigRepo &config)
839 {
840 for (auto & item : config.optBinds()) {
841 auto & itemOption = item.second;
842 if (itemOption.getPriority() == libdnf::Option::Priority::REPOCONFIG) {
843 itemOption.getOption().reset();
844 }
845 }
846 }
847
848 /* Loads repository configuration from GKeyFile */
849 static void
dnf_repo_conf_from_gkeyfile(libdnf::ConfigRepo & config,const char * repoId,GKeyFile * gkeyFile)850 dnf_repo_conf_from_gkeyfile(libdnf::ConfigRepo &config, const char *repoId, GKeyFile *gkeyFile)
851 {
852 // Reset to the initial state before reloading the configuration.
853 dnf_repo_conf_reset(config);
854
855 g_autoptr(GError) error_local = NULL;
856 g_auto(GStrv) keys = g_key_file_get_keys(gkeyFile, repoId, NULL, &error_local);
857 if (keys == NULL) {
858 g_debug("Failed to load configuration for repo id \"%s\": %s", repoId, error_local->message);
859 return;
860 }
861
862 for (auto it = keys; *it != NULL; ++it) {
863 auto key = *it;
864 g_autofree gchar *str = g_key_file_get_value(gkeyFile, repoId, key, NULL);
865 if (str) {
866 auto value = libdnf::string::trim(str);
867 if (value.length() > 1 && value.front() == value.back() &&
868 (value.front() == '"' || value.front() == '\'')) {
869 value.erase(--value.end());
870 value.erase(value.begin());
871 }
872 try {
873 auto & optionItem = config.optBinds().at(key);
874
875 if (dynamic_cast<libdnf::OptionStringList*>(&optionItem.getOption()) ||
876 dynamic_cast<libdnf::OptionChild<libdnf::OptionStringList>*>(&optionItem.getOption())
877 ) {
878
879 // reload list option from gKeyFile using g_key_file_get_string_list()
880 // g_key_file_get_value () is problematic for multiline lists
881 g_auto(GStrv) list = g_key_file_get_string_list(gkeyFile, repoId, key, NULL, NULL);
882 if (list) {
883 // list can be ['value1', 'value2, value3'] therefore we first join
884 // to have 'value1, value2, value3'
885 g_autofree gchar * tmp_strval = g_strjoinv(",", list);
886 try {
887 optionItem.newString(libdnf::Option::Priority::REPOCONFIG, tmp_strval);
888 } catch (const std::exception & ex) {
889 g_debug("Invalid configuration value: %s = %s in %s; %s", key, value.c_str(), repoId, ex.what());
890 }
891 }
892
893 } else {
894
895 // process other (non list) options
896 try {
897 optionItem.newString(libdnf::Option::Priority::REPOCONFIG, value);
898 } catch (const std::exception & ex) {
899 g_debug("Invalid configuration value: %s = %s in %s; %s", key, value.c_str(), repoId, ex.what());
900 }
901
902 }
903
904 } catch (const std::exception &) {
905 g_debug("Unknown configuration option: %s = %s in %s", key, value.c_str(), repoId);
906 }
907 }
908 }
909 }
910
911 static void
dnf_repo_apply_setopts(libdnf::ConfigRepo & config,const char * repoId)912 dnf_repo_apply_setopts(libdnf::ConfigRepo &config, const char *repoId)
913 {
914 // apply repository setopts
915 auto & optBinds = config.optBinds();
916 for (const auto & setopt : libdnf::getGlobalSetopts()) {
917 auto last_dot_pos = setopt.key.rfind('.');
918 if (last_dot_pos == std::string::npos) {
919 continue;
920 }
921 auto repo_pattern = setopt.key.substr(0, last_dot_pos);
922 if (fnmatch(repo_pattern.c_str(), repoId, FNM_CASEFOLD) == 0) {
923 auto key = setopt.key.substr(last_dot_pos+1);
924 try {
925 auto & optionItem = optBinds.at(key);
926 try {
927 optionItem.newString(setopt.priority, setopt.value);
928 } catch (const std::exception & ex) {
929 g_warning("dnf_repo_apply_setopt: Invalid configuration value: %s = %s; %s", setopt.key.c_str(), setopt.value.c_str(), ex.what());
930 }
931 } catch (const std::exception &) {
932 g_warning("dnf_repo_apply_setopt: Unknown configuration option: %s in %s = %s", key.c_str(), setopt.key.c_str(), setopt.value.c_str());
933 }
934 }
935 }
936 }
937
938 /* Initialize (or potentially reset) repo & LrHandle from keyfile values. */
939 static gboolean
dnf_repo_set_keyfile_data(DnfRepo * repo,gboolean reloadFromGKeyFile,GError ** error)940 dnf_repo_set_keyfile_data(DnfRepo *repo, gboolean reloadFromGKeyFile, GError **error)
941 {
942 DnfRepoPrivate *priv = GET_PRIVATE(repo);
943 std::string tmp_str;
944 const char *tmp_cstr;
945
946 auto repoId = priv->repo->getId().c_str();
947 g_debug("setting keyfile data for %s", repoId);
948
949 auto conf = priv->repo->getConfig();
950
951 // Reload repository configuration from keyfile.
952 if (reloadFromGKeyFile) {
953 dnf_repo_conf_from_gkeyfile(*conf, repoId, priv->keyfile);
954 dnf_repo_apply_setopts(*conf, repoId);
955 }
956
957 /* baseurl is optional; if missing, unset it */
958 g_auto(GStrv) baseurls = NULL;
959 auto & repoBaseurls = conf->baseurl().getValue();
960 if (!repoBaseurls.empty()){
961 auto len = repoBaseurls.size();
962 baseurls = g_new0(char *, len + 1);
963 for (size_t i = 0; i < len; ++i) {
964 baseurls[i] = g_strdup(repoBaseurls[i].c_str());
965 }
966 }
967 if (!lr_handle_setopt(priv->repo_handle, error, LRO_URLS, baseurls))
968 return FALSE;
969
970 const char *mirrorlisturl = NULL;
971 const char *metalinkurl = NULL;
972
973 /* the "mirrorlist" entry could be either a real mirrorlist, or a metalink entry */
974 tmp_cstr = conf->mirrorlist().empty() ? NULL : conf->mirrorlist().getValue().c_str();
975 if (tmp_cstr) {
976 if (strstr(tmp_cstr, "metalink"))
977 metalinkurl = tmp_cstr;
978 else /* it really is a mirrorlist */
979 mirrorlisturl = tmp_cstr;
980 }
981
982 /* let "metalink" entry override metalink-as-mirrorlist entry */
983 tmp_cstr = conf->metalink().empty() ? NULL : conf->metalink().getValue().c_str();
984 if (tmp_cstr)
985 metalinkurl = tmp_cstr;
986
987 /* now set the final values (or unset them) */
988 if (!lr_handle_setopt(priv->repo_handle, error, LRO_MIRRORLISTURL, mirrorlisturl))
989 return FALSE;
990 if (!lr_handle_setopt(priv->repo_handle, error, LRO_METALINKURL, metalinkurl))
991 return FALSE;
992
993 /* file:// */
994 if (baseurls != NULL && baseurls[0] != NULL &&
995 mirrorlisturl == NULL && metalinkurl == NULL) {
996 g_autofree gchar *url = NULL;
997 url = lr_prepend_url_protocol(baseurls[0]);
998 if (url != NULL && strncasecmp(url, "file://", 7) == 0) {
999 if (g_strstr_len(url, -1, "$testdatadir") == NULL)
1000 priv->kind = DNF_REPO_KIND_LOCAL;
1001 g_free(priv->location);
1002 g_free(priv->keyring);
1003 priv->location = dnf_repo_substitute(repo, url + 7);
1004 priv->keyring = g_build_filename(url + 7, "gpgdir", NULL);
1005 }
1006 }
1007
1008 /* set location if currently unset */
1009 if (!lr_handle_setopt(priv->repo_handle, error, LRO_LOCAL, 0L))
1010 return FALSE;
1011
1012 /* set repos cache dir */
1013 g_autofree gchar *cache_path = NULL;
1014 /* make each repo's cache directory name has releasever and basearch as its suffix */
1015 g_autofree gchar *cache_file_name = g_strjoin("-", repoId,
1016 dnf_context_get_release_ver(priv->context),
1017 dnf_context_get_base_arch(priv->context), NULL);
1018
1019 cache_path = g_build_filename(dnf_context_get_cache_dir(priv->context), cache_file_name, NULL);
1020 if (priv->packages == NULL) {
1021 g_autofree gchar *packages_path = g_build_filename(cache_path, "packages", NULL);
1022 dnf_repo_set_packages(repo, packages_path);
1023 }
1024 if (priv->location == NULL) {
1025 dnf_repo_set_location(repo, cache_path);
1026 }
1027
1028 /* set temp location used for updating remote repos */
1029 if (priv->kind == DNF_REPO_KIND_REMOTE) {
1030 g_autoptr(GString) tmp = NULL;
1031 tmp = g_string_new(priv->location);
1032 if (tmp->len > 0 && tmp->str[tmp->len - 1] == '/')
1033 g_string_truncate(tmp, tmp->len - 1);
1034 g_string_append(tmp, ".tmp");
1035 dnf_repo_set_location_tmp(repo, tmp->str);
1036 }
1037
1038 /* gpgkey is optional for gpgcheck=1, but required for repo_gpgcheck=1 */
1039 auto repo_gpgcheck = conf->repo_gpgcheck().getValue();
1040 if (repo_gpgcheck && conf->gpgkey().getValue().empty()) {
1041 g_set_error_literal(error,
1042 DNF_ERROR,
1043 DNF_ERROR_FILE_INVALID,
1044 "gpgkey not set, yet repo_gpgcheck=1");
1045 return FALSE;
1046 }
1047
1048 /* XXX: setopt() expects a long, so we need a long on the stack */
1049 if (!lr_handle_setopt(priv->repo_handle, error, LRO_GPGCHECK, (long)repo_gpgcheck))
1050 return FALSE;
1051
1052 // Sync priv->exclude_packages
1053 g_strfreev(priv->exclude_packages);
1054 auto & repoExcludepkgs = conf->excludepkgs().getValue();
1055 if (!repoExcludepkgs.empty()) {
1056 auto len = repoExcludepkgs.size();
1057 priv->exclude_packages = g_new0(char *, len + 1);
1058 for (size_t i = 0; i < len; ++i) {
1059 priv->exclude_packages[i] = g_strdup(repoExcludepkgs[i].c_str());
1060 }
1061 } else {
1062 priv->exclude_packages = NULL;
1063 }
1064
1065 auto minrate = conf->minrate().getValue();
1066 if (!lr_handle_setopt(priv->repo_handle, error, LRO_LOWSPEEDLIMIT, static_cast<long>(minrate)))
1067 return FALSE;
1068
1069 auto maxspeed = conf->throttle().getValue();
1070 if (maxspeed > 0 && maxspeed <= 1)
1071 maxspeed *= conf->bandwidth().getValue();
1072 if (maxspeed != 0 && maxspeed < minrate) {
1073 g_set_error_literal(error, DNF_ERROR, DNF_ERROR_FILE_INVALID,
1074 "Maximum download speed is lower than minimum. "
1075 "Please change configuration of minrate or throttle");
1076 return FALSE;
1077 }
1078 if (!lr_handle_setopt(priv->repo_handle, error, LRO_MAXSPEED, static_cast<int64_t>(maxspeed)))
1079 return FALSE;
1080
1081 long timeout = conf->timeout().getValue();
1082 if (timeout > 0) {
1083 if (!lr_handle_setopt(priv->repo_handle, error, LRO_CONNECTTIMEOUT, timeout))
1084 return FALSE;
1085 if (!lr_handle_setopt(priv->repo_handle, error, LRO_LOWSPEEDTIME, timeout))
1086 return FALSE;
1087 } else {
1088 if (!lr_handle_setopt(priv->repo_handle, error, LRO_CONNECTTIMEOUT, LRO_CONNECTTIMEOUT_DEFAULT))
1089 return FALSE;
1090 if (!lr_handle_setopt(priv->repo_handle, error, LRO_LOWSPEEDTIME, LRO_LOWSPEEDTIME_DEFAULT))
1091 return FALSE;
1092 }
1093
1094 tmp_str = conf->proxy().getValue();
1095 tmp_cstr = tmp_str.empty() ? dnf_context_get_http_proxy(priv->context) : tmp_str.c_str();
1096 if (!lr_handle_setopt(priv->repo_handle, error, LRO_PROXY, tmp_cstr))
1097 return FALSE;
1098
1099 // setup proxy authorization method
1100 auto proxyAuthMethods = libdnf::Repo::Impl::stringToProxyAuthMethods(conf->proxy_auth_method().getValue());
1101 if (!lr_handle_setopt(priv->repo_handle, error, LRO_PROXYAUTHMETHODS, static_cast<long>(proxyAuthMethods)))
1102 return FALSE;
1103
1104 // setup proxy username and password
1105 tmp_cstr = NULL;
1106 if (!conf->proxy_username().empty()) {
1107 tmp_str = conf->proxy_username().getValue();
1108 if (!tmp_str.empty()) {
1109 if (conf->proxy_password().empty()) {
1110 g_set_error(error, DNF_ERROR, DNF_ERROR_FILE_INVALID,
1111 "repo '%s': 'proxy_username' is set but not 'proxy_password'", repoId);
1112 return FALSE;
1113 }
1114 tmp_str = formatUserPassString(tmp_str, conf->proxy_password().getValue(), true);
1115 tmp_cstr = tmp_str.c_str();
1116 }
1117 }
1118 if (!lr_handle_setopt(priv->repo_handle, error, LRO_PROXYUSERPWD, tmp_cstr))
1119 return FALSE;
1120
1121 // setup username and password
1122 tmp_cstr = NULL;
1123 tmp_str = conf->username().getValue();
1124 if (!tmp_str.empty()) {
1125 // TODO Use URL encoded form, needs support in librepo
1126 tmp_str = formatUserPassString(tmp_str, conf->password().getValue(), false);
1127 tmp_cstr = tmp_str.c_str();
1128 }
1129 if (!lr_handle_setopt(priv->repo_handle, error, LRO_USERPWD, tmp_cstr))
1130 return FALSE;
1131
1132 auto proxy_sslverify = conf->proxy_sslverify().getValue();
1133 /* XXX: setopt() expects a long, so we need a long on the stack */
1134 if (!lr_handle_setopt(priv->repo_handle, error, LRO_PROXY_SSLVERIFYPEER, (long)proxy_sslverify))
1135 return FALSE;
1136 if (!lr_handle_setopt(priv->repo_handle, error, LRO_PROXY_SSLVERIFYHOST, (long)proxy_sslverify))
1137 return FALSE;
1138
1139 auto & proxy_sslcacert = conf->proxy_sslcacert().getValue();
1140 if (!proxy_sslcacert.empty()) {
1141 if (!lr_handle_setopt(priv->repo_handle, error, LRO_PROXY_SSLCACERT, proxy_sslcacert.c_str()))
1142 return FALSE;
1143 }
1144
1145 auto & proxy_sslclientcert = conf->proxy_sslclientcert().getValue();
1146 if (!proxy_sslclientcert.empty()) {
1147 if (!lr_handle_setopt(priv->repo_handle, error, LRO_PROXY_SSLCLIENTCERT, proxy_sslclientcert.c_str()))
1148 return FALSE;
1149 }
1150
1151 auto & proxy_sslclientkey = conf->proxy_sslclientkey().getValue();
1152 if (!proxy_sslclientkey.empty()) {
1153 if (!lr_handle_setopt(priv->repo_handle, error, LRO_PROXY_SSLCLIENTKEY, proxy_sslclientkey.c_str()))
1154 return FALSE;
1155 }
1156
1157 return TRUE;
1158 }
1159
1160 /**
1161 * dnf_repo_setup:
1162 * @repo: a #DnfRepo instance.
1163 * @error: a #GError or %NULL
1164 *
1165 * Sets up the repo ready for use.
1166 *
1167 * Returns: %TRUE for success
1168 *
1169 * Since: 0.1.0
1170 **/
1171 gboolean
dnf_repo_setup(DnfRepo * repo,GError ** error)1172 dnf_repo_setup(DnfRepo *repo, GError **error) try
1173 {
1174 DnfRepoPrivate *priv = GET_PRIVATE(repo);
1175 DnfRepoEnabled enabled = DNF_REPO_ENABLED_NONE;
1176 g_autofree gchar *basearch = NULL;
1177 g_autofree gchar *release = NULL;
1178 g_autofree gchar *testdatadir = NULL;
1179
1180 basearch = g_key_file_get_string(priv->keyfile, "general", "arch", NULL);
1181 if (basearch == NULL)
1182 basearch = g_strdup(dnf_context_get_base_arch(priv->context));
1183 if (basearch == NULL) {
1184 g_set_error_literal(error,
1185 DNF_ERROR,
1186 DNF_ERROR_INTERNAL_ERROR,
1187 "basearch not set");
1188 return FALSE;
1189 }
1190 release = g_key_file_get_string(priv->keyfile, "general", "version", NULL);
1191 if (release == NULL)
1192 release = g_strdup(dnf_context_get_release_ver(priv->context));
1193 if (release == NULL) {
1194 g_set_error_literal(error,
1195 DNF_ERROR,
1196 DNF_ERROR_INTERNAL_ERROR,
1197 "releasever not set");
1198 return FALSE;
1199 }
1200 if (!lr_handle_setopt(priv->repo_handle, error, LRO_USERAGENT, dnf_context_get_user_agent(priv->context)))
1201 return FALSE;
1202 if (!lr_handle_setopt(priv->repo_handle, error, LRO_REPOTYPE, LR_YUMREPO))
1203 return FALSE;
1204 if (!lr_handle_setopt(priv->repo_handle, error, LRO_INTERRUPTIBLE, 0L))
1205 return FALSE;
1206 priv->urlvars = lr_urlvars_set(priv->urlvars, "releasever", release);
1207 priv->urlvars = lr_urlvars_set(priv->urlvars, "basearch", basearch);
1208
1209 /* Call libdnf::dnf_context_load_vars(priv->context); only when values not in cache.
1210 * But what about if variables on disk change during long running programs (PackageKit daemon)?
1211 * if (!libdnf::dnf_context_get_vars_cached(priv->context))
1212 */
1213 libdnf::dnf_context_load_vars(priv->context);
1214 for (const auto & item : libdnf::dnf_context_get_vars(priv->context))
1215 priv->urlvars = lr_urlvars_set(priv->urlvars, item.first.c_str(), item.second.c_str());
1216
1217 testdatadir = dnf_realpath(TESTDATADIR);
1218 priv->urlvars = lr_urlvars_set(priv->urlvars, "testdatadir", testdatadir);
1219 if (!lr_handle_setopt(priv->repo_handle, error, LRO_VARSUB, priv->urlvars))
1220 return FALSE;
1221 if (!lr_handle_setopt(priv->repo_handle, error, LRO_GNUPGHOMEDIR, priv->keyring))
1222 return FALSE;
1223
1224 auto repoId = priv->repo->getId().c_str();
1225
1226 auto conf = priv->repo->getConfig();
1227 dnf_repo_conf_from_gkeyfile(*conf, repoId, priv->keyfile);
1228 dnf_repo_apply_setopts(*conf, repoId);
1229
1230 auto sslverify = conf->sslverify().getValue();
1231 /* XXX: setopt() expects a long, so we need a long on the stack */
1232 if (!lr_handle_setopt(priv->repo_handle, error, LRO_SSLVERIFYPEER, (long)sslverify))
1233 return FALSE;
1234 if (!lr_handle_setopt(priv->repo_handle, error, LRO_SSLVERIFYHOST, (long)sslverify))
1235 return FALSE;
1236
1237 auto & sslcacert = conf->sslcacert().getValue();
1238 if (!sslcacert.empty()) {
1239 if (!lr_handle_setopt(priv->repo_handle, error, LRO_SSLCACERT, sslcacert.c_str()))
1240 return FALSE;
1241 }
1242
1243 auto & sslclientcert = conf->sslclientcert().getValue();
1244 if (!sslclientcert.empty()) {
1245 if (!lr_handle_setopt(priv->repo_handle, error, LRO_SSLCLIENTCERT, sslclientcert.c_str()))
1246 return FALSE;
1247 }
1248
1249 auto & sslclientkey = conf->sslclientkey().getValue();
1250 if (!sslclientkey.empty()) {
1251 if (!lr_handle_setopt(priv->repo_handle, error, LRO_SSLCLIENTKEY, sslclientkey.c_str()))
1252 return FALSE;
1253 }
1254
1255 if (!lr_handle_setopt(priv->repo_handle, error, LRO_SSLVERIFYSTATUS, conf->sslverifystatus().getValue() ? 1L : 0L))
1256 return FALSE;
1257
1258 #ifdef LRO_SUPPORTS_CACHEDIR
1259 /* Set cache dir */
1260 if (dnf_context_get_zchunk(priv->context)) {
1261 if(!lr_handle_setopt(priv->repo_handle, error, LRO_CACHEDIR, dnf_context_get_cache_dir(priv->context)))
1262 return FALSE;
1263 }
1264 #endif
1265
1266 if (conf->enabled().getValue())
1267 enabled |= DNF_REPO_ENABLED_PACKAGES;
1268
1269 /* enabled_metadata is optional */
1270 if (conf->enabled_metadata().getPriority() != libdnf::Option::Priority::DEFAULT) {
1271 try {
1272 if (libdnf::OptionBool(false).fromString(conf->enabled_metadata().getValue()))
1273 enabled |= DNF_REPO_ENABLED_METADATA;
1274 } catch (const std::exception & ex) {
1275 g_warning("Config error in section \"%s\" key \"%s\": %s", repoId, "enabled_metadata", ex.what());
1276 }
1277 } else {
1278 g_autofree gchar *basename = g_path_get_basename(priv->filename);
1279 /* special case the satellite and subscription manager repo */
1280 if (g_strcmp0(basename, "redhat.repo") == 0)
1281 enabled |= DNF_REPO_ENABLED_METADATA;
1282 }
1283
1284 dnf_repo_set_enabled(repo, enabled);
1285
1286 return dnf_repo_set_keyfile_data(repo, FALSE, error);
1287 } CATCH_TO_GERROR(FALSE)
1288
1289 typedef struct
1290 {
1291 DnfState *state;
1292 gchar *last_mirror_url;
1293 gchar *last_mirror_failure_message;
1294 } RepoUpdateData;
1295
1296 /**
1297 * dnf_repo_update_state_cb:
1298 */
1299 static int
dnf_repo_update_state_cb(void * user_data,gdouble total_to_download,gdouble now_downloaded)1300 dnf_repo_update_state_cb(void *user_data,
1301 gdouble total_to_download,
1302 gdouble now_downloaded)
1303 {
1304 gboolean ret;
1305 gdouble percentage;
1306 auto updatedata = static_cast<RepoUpdateData *>(user_data);
1307 DnfState *state = updatedata->state;
1308
1309 /* abort */
1310 if (!dnf_state_check(state, NULL))
1311 return -1;
1312
1313 /* the number of files has changed */
1314 if (total_to_download <= 0.01 && now_downloaded <= 0.01) {
1315 dnf_state_reset(state);
1316 return 0;
1317 }
1318
1319 /* nothing sensible */
1320 if (total_to_download < 0)
1321 return 0;
1322
1323 /* set percentage */
1324 percentage = 100.0f * now_downloaded / total_to_download;
1325 ret = dnf_state_set_percentage(state, percentage);
1326 if (ret) {
1327 g_debug("update state %.0f/%.0f",
1328 now_downloaded,
1329 total_to_download);
1330 }
1331
1332 return 0;
1333 }
1334
1335 /**
1336 * dnf_repo_set_timestamp_modified:
1337 */
1338 static gboolean
dnf_repo_set_timestamp_modified(DnfRepo * repo,GError ** error)1339 dnf_repo_set_timestamp_modified(DnfRepo *repo, GError **error)
1340 {
1341 DnfRepoPrivate *priv = GET_PRIVATE(repo);
1342 g_autofree gchar *filename = NULL;
1343 g_autoptr(GFile) file = NULL;
1344 g_autoptr(GFileInfo) info = NULL;
1345
1346 filename = g_build_filename(priv->location, "repodata", "repomd.xml", NULL);
1347 file = g_file_new_for_path(filename);
1348 info = g_file_query_info(file,
1349 G_FILE_ATTRIBUTE_TIME_MODIFIED ","
1350 G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC,
1351 G_FILE_QUERY_INFO_NONE,
1352 NULL,
1353 error);
1354 if (info == NULL)
1355 return FALSE;
1356 priv->timestamp_modified = g_file_info_get_attribute_uint64(info,
1357 G_FILE_ATTRIBUTE_TIME_MODIFIED) * G_USEC_PER_SEC;
1358 priv->timestamp_modified += g_file_info_get_attribute_uint32(info,
1359 G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC);
1360 return TRUE;
1361 }
1362
1363 static gboolean
dnf_repo_check_internal(DnfRepo * repo,guint permissible_cache_age,DnfState * state,GError ** error)1364 dnf_repo_check_internal(DnfRepo *repo,
1365 guint permissible_cache_age,
1366 DnfState *state,
1367 GError **error)
1368 {
1369 DnfRepoPrivate *priv = GET_PRIVATE(repo);
1370 auto repoImpl = libdnf::repoGetImpl(priv->repo);
1371
1372 std::vector<const char *> download_list = {"primary", "group", "updateinfo", "appstream",
1373 "appstream-icons", "modules"};
1374 /* This one is huge, and at least rpm-ostree jigdo mode doesn't require it.
1375 * https://github.com/projectatomic/rpm-ostree/issues/1127
1376 */
1377 if (dnf_context_get_enable_filelists(priv->context))
1378 download_list.push_back("filelists");
1379 for (const auto & item : repoImpl->additionalMetadata) {
1380 download_list.push_back(item.c_str());
1381 }
1382 download_list.push_back(NULL);
1383 gboolean ret;
1384 LrYumRepo *yum_repo;
1385 const gchar *urls[] = { "", NULL };
1386 gint64 age_of_data; /* in seconds */
1387 g_autoptr(GError) error_local = NULL;
1388 guint metadata_expire;
1389 guint valid_time_allowed;
1390
1391 /* has the media repo vanished? */
1392 if (priv->kind == DNF_REPO_KIND_MEDIA &&
1393 !g_file_test(priv->location, G_FILE_TEST_EXISTS)) {
1394 if (!dnf_repo_get_required(repo))
1395 priv->enabled = DNF_REPO_ENABLED_NONE;
1396 g_set_error(error,
1397 DNF_ERROR,
1398 DNF_ERROR_REPO_NOT_AVAILABLE,
1399 "%s was not found", priv->location);
1400 return FALSE;
1401 }
1402
1403 /* has the local repo vanished? */
1404 if (priv->kind == DNF_REPO_KIND_LOCAL &&
1405 !g_file_test(priv->location, G_FILE_TEST_EXISTS)) {
1406 if (!dnf_repo_get_required(repo))
1407 priv->enabled = DNF_REPO_ENABLED_NONE;
1408 g_set_error(error,
1409 DNF_ERROR,
1410 DNF_ERROR_REPO_NOT_AVAILABLE,
1411 "%s was not found", priv->location);
1412 return FALSE;
1413 }
1414
1415 /* Yum metadata */
1416 dnf_state_action_start(state, DNF_STATE_ACTION_LOADING_CACHE, NULL);
1417 urls[0] = priv->location;
1418 if (!lr_handle_setopt(priv->repo_handle, error, LRO_URLS, urls))
1419 return FALSE;
1420 if (!lr_handle_setopt(priv->repo_handle, error, LRO_DESTDIR, priv->location))
1421 return FALSE;
1422 if (!lr_handle_setopt(priv->repo_handle, error, LRO_LOCAL, 1L))
1423 return FALSE;
1424 if (!lr_handle_setopt(priv->repo_handle, error, LRO_CHECKSUM, 1L))
1425 return FALSE;
1426 if (!lr_handle_setopt(priv->repo_handle, error, LRO_YUMDLIST, download_list.data()))
1427 return FALSE;
1428 if (!lr_handle_setopt(priv->repo_handle, error, LRO_MIRRORLISTURL, NULL))
1429 return FALSE;
1430 if (!lr_handle_setopt(priv->repo_handle, error, LRO_METALINKURL, NULL))
1431 return FALSE;
1432 if (!lr_handle_setopt(priv->repo_handle, error, LRO_GNUPGHOMEDIR, priv->keyring))
1433 return FALSE;
1434 lr_result_clear(priv->repo_result);
1435 if (!lr_handle_perform(priv->repo_handle, priv->repo_result, &error_local)) {
1436 g_set_error(error,
1437 DNF_ERROR,
1438 DNF_ERROR_REPO_NOT_AVAILABLE,
1439 "repodata %s was not complete: %s",
1440 priv->repo->getId().c_str(), error_local->message);
1441 return FALSE;
1442 }
1443
1444 /* get the metadata file locations */
1445 if (!lr_result_getinfo(priv->repo_result, &error_local, LRR_YUM_REPO, &yum_repo)) {
1446 g_set_error(error,
1447 DNF_ERROR,
1448 DNF_ERROR_INTERNAL_ERROR,
1449 "failed to get yum-repo: %s",
1450 error_local->message);
1451 return FALSE;
1452 }
1453
1454 /* get timestamp */
1455 ret = lr_result_getinfo(priv->repo_result, &error_local,
1456 LRR_YUM_TIMESTAMP, &priv->timestamp_generated);
1457 if (!ret) {
1458 g_set_error(error,
1459 DNF_ERROR,
1460 DNF_ERROR_INTERNAL_ERROR,
1461 "failed to get timestamp: %s",
1462 error_local->message);
1463 return FALSE;
1464 }
1465
1466 /* check metadata age for non-local repos */
1467 if (priv->kind != DNF_REPO_KIND_LOCAL && permissible_cache_age != G_MAXUINT) {
1468 if (!dnf_repo_set_timestamp_modified(repo, error))
1469 return FALSE;
1470 age_of_data =(g_get_real_time() - priv->timestamp_modified) / G_USEC_PER_SEC;
1471
1472 /* choose a lower value between cache and metadata_expire for expired checking */
1473 metadata_expire = dnf_repo_get_metadata_expire(repo);
1474 valid_time_allowed = (metadata_expire <= permissible_cache_age ? metadata_expire : permissible_cache_age);
1475
1476 if (age_of_data > valid_time_allowed) {
1477 g_set_error(error,
1478 DNF_ERROR,
1479 DNF_ERROR_INTERNAL_ERROR,
1480 "cache too old: %" G_GINT64_FORMAT" > %i",
1481 age_of_data, valid_time_allowed);
1482 return FALSE;
1483 }
1484 }
1485
1486 /* init newRepo */
1487 auto newRepo = hy_repo_create(priv->repo->getId().c_str());
1488 auto newRepoImpl = libdnf::repoGetImpl(newRepo);
1489
1490 // "additionalMetadata" are not part of the config. They are filled during runtime
1491 // (eg. by plugins) and must be kept.
1492 newRepoImpl->additionalMetadata = repoImpl->additionalMetadata;
1493
1494 hy_repo_free(priv->repo);
1495 priv->repo = newRepo;
1496 newRepoImpl->repomdFn = yum_repo->repomd;
1497 for (auto *elem = yum_repo->paths; elem; elem = g_slist_next(elem)) {
1498 if (elem->data) {
1499 auto yumrepopath = static_cast<LrYumRepoPath *>(elem->data);
1500 newRepoImpl->metadataPaths[yumrepopath->type] = yumrepopath->path;
1501 }
1502 }
1503 /* ensure we reset the values from the keyfile */
1504 if (!dnf_repo_set_keyfile_data(repo, TRUE, error))
1505 return FALSE;
1506
1507 return TRUE;
1508 }
1509
1510 /**
1511 * dnf_repo_check:
1512 * @repo: a #DnfRepo instance.
1513 * @permissible_cache_age: The oldest cache age allowed in seconds(wall clock time); Pass %G_MAXUINT to ignore
1514 * @state: a #DnfState instance.
1515 * @error: a #GError or %NULL.
1516 *
1517 * Checks the repo.
1518 *
1519 * Returns: %TRUE for success, %FALSE otherwise
1520 *
1521 * Since: 0.1.0
1522 **/
1523 gboolean
dnf_repo_check(DnfRepo * repo,guint permissible_cache_age,DnfState * state,GError ** error)1524 dnf_repo_check(DnfRepo *repo,
1525 guint permissible_cache_age,
1526 DnfState *state,
1527 GError **error) try
1528 {
1529 DnfRepoPrivate *priv = GET_PRIVATE(repo);
1530 g_clear_error(&priv->last_check_error);
1531 if (!dnf_repo_check_internal(repo, permissible_cache_age, state,
1532 &priv->last_check_error)) {
1533 if (error)
1534 *error = g_error_copy(priv->last_check_error);
1535 return FALSE;
1536 }
1537 return TRUE;
1538 } CATCH_TO_GERROR(FALSE)
1539
1540
1541 /**
1542 * dnf_repo_get_filename_md:
1543 * @repo: a #DnfRepo instance.
1544 * @md_kind: The file kind, e.g. "primary" or "updateinfo"
1545 *
1546 * Gets the filename used for a repo data kind.
1547 *
1548 * Returns: the full path to the data file, %NULL otherwise
1549 *
1550 * Since: 0.1.7
1551 **/
1552 const gchar *
1553 dnf_repo_get_filename_md(DnfRepo *repo, const gchar *md_kind)
1554 {
1555 DnfRepoPrivate *priv = GET_PRIVATE(repo);
1556 g_return_val_if_fail(md_kind != NULL, NULL);
1557 if (priv->repo) {
1558 auto repoImpl = libdnf::repoGetImpl(priv->repo);
1559 auto & filename = repoImpl->getMetadataPath(md_kind);
1560 return filename.empty() ? nullptr : filename.c_str();
1561 } else
1562 return nullptr;
1563 }
1564
1565 /**
1566 * dnf_repo_clean:
1567 * @repo: a #DnfRepo instance.
1568 * @error: a #GError or %NULL.
1569 *
1570 * Cleans the repo by deleting all the location contents.
1571 *
1572 * Returns: %TRUE for success, %FALSE otherwise
1573 *
1574 * Since: 0.1.0
1575 **/
1576 gboolean
dnf_repo_clean(DnfRepo * repo,GError ** error)1577 dnf_repo_clean(DnfRepo *repo, GError **error) try
1578 {
1579 DnfRepoPrivate *priv = GET_PRIVATE(repo);
1580
1581 /* do not clean media or local repos */
1582 if (priv->kind == DNF_REPO_KIND_MEDIA ||
1583 priv->kind == DNF_REPO_KIND_LOCAL)
1584 return TRUE;
1585
1586 if (!g_file_test(priv->location, G_FILE_TEST_EXISTS))
1587 return TRUE;
1588 if (!dnf_remove_recursive(priv->location, error))
1589 return FALSE;
1590 return TRUE;
1591 } CATCH_TO_GERROR(FALSE)
1592
1593 /**
1594 * dnf_repo_add_public_key:
1595 *
1596 * This imports a repodata public key into the default librpm keyring
1597 **/
1598 static gboolean
1599 dnf_repo_add_public_key(DnfRepo *repo,
1600 const char *tmp_path,
1601 GError **error)
1602 {
1603 gboolean ret;
1604 rpmKeyring keyring;
1605 rpmts ts;
1606
1607 /* then import to rpmdb */
1608 ts = rpmtsCreate();
1609 keyring = rpmtsGetKeyring(ts, 1);
1610 ret = dnf_keyring_add_public_key(keyring, tmp_path, error);
1611 rpmKeyringFree(keyring);
1612 rpmtsFree(ts);
1613 return ret;
1614 }
1615
1616 /**
1617 * dnf_repo_download_import_public_keys:
1618 **/
1619 static gboolean
dnf_repo_download_import_public_key(DnfRepo * repo,const char * gpgkey,const char * key_tmp,GError ** error)1620 dnf_repo_download_import_public_key(DnfRepo *repo,
1621 const char *gpgkey,
1622 const char *key_tmp,
1623 GError **error)
1624 {
1625 DnfRepoPrivate *priv = GET_PRIVATE(repo);
1626 int fd;
1627 g_autoptr(GError) error_local = NULL;
1628
1629 /* does this file already exist */
1630 if (g_file_test(key_tmp, G_FILE_TEST_EXISTS))
1631 return lr_gpg_import_key(key_tmp, priv->keyring_tmp, error);
1632
1633 /* create the gpgdir location */
1634 if (g_mkdir_with_parents(priv->keyring_tmp, 0755) != 0) {
1635 g_set_error(error,
1636 DNF_ERROR,
1637 DNF_ERROR_INTERNAL_ERROR,
1638 "Failed to create %s",
1639 priv->keyring_tmp);
1640 return FALSE;
1641 }
1642
1643 /* set the keyring location */
1644 if (!lr_handle_setopt(priv->repo_handle, error,
1645 LRO_GNUPGHOMEDIR, priv->keyring_tmp))
1646 return FALSE;
1647
1648 /* download to known location */
1649 g_debug("Downloading %s to %s", gpgkey, key_tmp);
1650 fd = g_open(key_tmp, O_CLOEXEC | O_CREAT | O_RDWR, 0774);
1651 if (fd < 0) {
1652 g_set_error(error,
1653 DNF_ERROR,
1654 DNF_ERROR_INTERNAL_ERROR,
1655 "Failed to open %s: %i",
1656 key_tmp, fd);
1657 return FALSE;
1658 }
1659 if (!lr_download_url(priv->repo_handle, gpgkey, fd, &error_local)) {
1660 g_set_error(error,
1661 DNF_ERROR,
1662 DNF_ERROR_CANNOT_FETCH_SOURCE,
1663 "Failed to download gpg key for repo '%s': %s",
1664 dnf_repo_get_id(repo),
1665 error_local->message);
1666 g_close(fd, NULL);
1667 return FALSE;
1668 }
1669 if (!g_close(fd, error))
1670 return FALSE;
1671
1672 /* import found key */
1673 return lr_gpg_import_key(key_tmp, priv->keyring_tmp, error);
1674 }
1675
1676 static int
repo_mirrorlist_failure_cb(void * user_data,const char * message,const char * url,const char * metadata)1677 repo_mirrorlist_failure_cb(void *user_data,
1678 const char *message,
1679 const char *url,
1680 const char *metadata)
1681 {
1682 auto data = static_cast<RepoUpdateData *>(user_data);
1683
1684 if (data->last_mirror_url)
1685 goto out;
1686
1687 data->last_mirror_url = g_strdup(url);
1688 data->last_mirror_failure_message = g_strdup(message);
1689 out:
1690 return LR_CB_OK;
1691 }
1692
1693 /**
1694 * dnf_repo_update:
1695 * @repo: a #DnfRepo instance.
1696 * @flags: #DnfRepoUpdateFlags, e.g. %DNF_REPO_UPDATE_FLAG_FORCE
1697 * @state: a #DnfState instance.
1698 * @error: a #%GError or %NULL.
1699 *
1700 * Updates the repo.
1701 *
1702 * Returns: %TRUE for success, %FALSE otherwise
1703 *
1704 * Since: 0.1.0
1705 **/
1706 gboolean
dnf_repo_update(DnfRepo * repo,DnfRepoUpdateFlags flags,DnfState * state,GError ** error)1707 dnf_repo_update(DnfRepo *repo,
1708 DnfRepoUpdateFlags flags,
1709 DnfState *state,
1710 GError **error) try
1711 {
1712 DnfRepoPrivate *priv = GET_PRIVATE(repo);
1713 DnfState *state_local;
1714 gboolean ret;
1715 gint rc;
1716 gint64 timestamp_new = 0;
1717 g_autoptr(GError) error_local = NULL;
1718 RepoUpdateData updatedata = { 0, };
1719
1720 /* cannot change DVD contents */
1721 if (priv->kind == DNF_REPO_KIND_MEDIA) {
1722 g_set_error_literal(error,
1723 DNF_ERROR,
1724 DNF_ERROR_REPO_NOT_AVAILABLE,
1725 "Cannot update read-only repo");
1726 return FALSE;
1727 }
1728
1729 /* Just verify existence for local */
1730 if (priv->kind == DNF_REPO_KIND_LOCAL) {
1731 if (priv->last_check_error) {
1732 if (error)
1733 *error = g_error_copy(priv->last_check_error);
1734 return FALSE;
1735 }
1736 /* If we didn't have an error in check, don't refresh
1737 local repos */
1738 return TRUE;
1739 }
1740
1741 /* this needs to be set */
1742 if (priv->location_tmp == NULL) {
1743 g_set_error(error,
1744 DNF_ERROR,
1745 DNF_ERROR_INTERNAL_ERROR,
1746 "location_tmp not set for %s",
1747 priv->repo->getId().c_str());
1748 return FALSE;
1749 }
1750
1751 /* ensure we set the values from the keyfile */
1752 if (!dnf_repo_set_keyfile_data(repo, TRUE, error))
1753 return FALSE;
1754
1755 /* countme support */
1756 libdnf::repoGetImpl(priv->repo)->addCountmeFlag(priv->repo_handle);
1757
1758 /* take lock */
1759 ret = dnf_state_take_lock(state,
1760 DNF_LOCK_TYPE_METADATA,
1761 DNF_LOCK_MODE_PROCESS,
1762 error);
1763 if (!ret)
1764 goto out;
1765
1766 /* set state */
1767 ret = dnf_state_set_steps(state, error,
1768 95, /* download */
1769 5, /* check */
1770 -1);
1771 if (!ret)
1772 goto out;
1773
1774 /* remove the temporary space if it already exists */
1775 if (g_file_test(priv->location_tmp, G_FILE_TEST_EXISTS)) {
1776 ret = dnf_remove_recursive(priv->location_tmp, error);
1777 if (!ret)
1778 goto out;
1779 }
1780
1781 /* ensure exists */
1782 rc = g_mkdir_with_parents(priv->location_tmp, 0755);
1783 if (rc != 0) {
1784 ret = FALSE;
1785 g_set_error(error,
1786 DNF_ERROR,
1787 DNF_ERROR_CANNOT_WRITE_REPO_CONFIG,
1788 "Failed to create %s", priv->location_tmp);
1789 goto out;
1790 }
1791
1792 {
1793 const auto & gpgkeys = priv->repo->getConfig()->gpgkey().getValue();
1794 if (!gpgkeys.empty() &&
1795 (priv->repo->getConfig()->repo_gpgcheck().getValue() || priv->repo->getConfig()->gpgcheck().getValue())) {
1796 for (const auto & key : gpgkeys) {
1797 const char *gpgkey = key.c_str();
1798 g_autofree char *gpgkey_name = g_path_get_basename(gpgkey);
1799 g_autofree char *key_tmp = g_build_filename(priv->location_tmp, gpgkey_name, NULL);
1800
1801 /* download and import public key */
1802 if ((g_str_has_prefix(gpgkey, "https://") ||
1803 g_str_has_prefix(gpgkey, "file://"))) {
1804 g_debug("importing public key %s", gpgkey);
1805
1806 ret = dnf_repo_download_import_public_key(repo, gpgkey, key_tmp, error);
1807 if (!ret)
1808 goto out;
1809 }
1810
1811 /* do we autoimport this into librpm? */
1812 if ((flags & DNF_REPO_UPDATE_FLAG_IMPORT_PUBKEY) > 0) {
1813 ret = dnf_repo_add_public_key(repo, key_tmp, error);
1814 if (!ret)
1815 goto out;
1816 }
1817 }
1818 }
1819 }
1820
1821 g_debug("Attempting to update %s", priv->repo->getId().c_str());
1822 ret = lr_handle_setopt(priv->repo_handle, error,
1823 LRO_LOCAL, 0L);
1824 if (!ret)
1825 goto out;
1826 ret = lr_handle_setopt(priv->repo_handle, error,
1827 LRO_DESTDIR, priv->location_tmp);
1828 if (!ret)
1829 goto out;
1830
1831 /* Callback to display progress of downloading */
1832 state_local = updatedata.state = dnf_state_get_child(state);
1833 ret = lr_handle_setopt(priv->repo_handle, error,
1834 LRO_PROGRESSDATA, &updatedata);
1835 if (!ret)
1836 goto out;
1837 ret = lr_handle_setopt(priv->repo_handle, error,
1838 LRO_PROGRESSCB, dnf_repo_update_state_cb);
1839 if (!ret)
1840 goto out;
1841 /* Note this uses the same user data as PROGRESSDATA */
1842 ret = lr_handle_setopt(priv->repo_handle, error,
1843 LRO_HMFCB, repo_mirrorlist_failure_cb);
1844 if (!ret)
1845 goto out;
1846
1847 /* see dnf_repo_check_internal */
1848 if (!dnf_context_get_enable_filelists(priv->context)) {
1849 const gchar *excluded_metadata_types[] = { "filelists", NULL };
1850 ret = lr_handle_setopt(priv->repo_handle, error,
1851 LRO_YUMBLIST, excluded_metadata_types);
1852 if (!ret)
1853 goto out;
1854 }
1855
1856 lr_result_clear(priv->repo_result);
1857 dnf_state_action_start(state_local,
1858 DNF_STATE_ACTION_DOWNLOAD_METADATA, NULL);
1859 ret = lr_handle_perform(priv->repo_handle,
1860 priv->repo_result,
1861 &error_local);
1862 if (!ret) {
1863 if (updatedata.last_mirror_failure_message) {
1864 g_autofree gchar *orig_message = error_local->message;
1865 error_local->message = g_strconcat(orig_message, "; Last error: ", updatedata.last_mirror_failure_message, NULL);
1866 }
1867
1868 g_set_error(error,
1869 DNF_ERROR,
1870 DNF_ERROR_CANNOT_FETCH_SOURCE,
1871 "cannot update repo '%s': %s",
1872 dnf_repo_get_id(repo),
1873 error_local->message);
1874 goto out;
1875 }
1876
1877 /* check the newer metadata is newer */
1878 ret = lr_result_getinfo(priv->repo_result, &error_local,
1879 LRR_YUM_TIMESTAMP, ×tamp_new);
1880 if (!ret) {
1881 g_set_error(error,
1882 DNF_ERROR,
1883 DNF_ERROR_INTERNAL_ERROR,
1884 "failed to get timestamp: %s",
1885 error_local->message);
1886 goto out;
1887 }
1888 if ((flags & DNF_REPO_UPDATE_FLAG_FORCE) == 0 &&
1889 timestamp_new < priv->timestamp_generated) {
1890 g_debug("fresh metadata was older than what we have, ignoring");
1891 if (!dnf_state_finished(state, error))
1892 return FALSE;
1893 goto out;
1894 }
1895
1896 /* only simulate */
1897 if (flags & DNF_REPO_UPDATE_FLAG_SIMULATE) {
1898 g_debug("simulating, so not switching to new metadata");
1899 ret = dnf_remove_recursive(priv->location_tmp, error);
1900 goto out;
1901 }
1902
1903 /* move the packages directory from the old cache to the new cache */
1904 if (g_file_test(priv->packages, G_FILE_TEST_EXISTS)) {
1905 ret = dnf_move_recursive(priv->packages, priv->packages_tmp, &error_local);
1906 if (!ret) {
1907 g_set_error(error,
1908 DNF_ERROR,
1909 DNF_ERROR_CANNOT_FETCH_SOURCE,
1910 "cannot move %s to %s: %s",
1911 priv->packages, priv->packages_tmp, error_local->message);
1912 goto out;
1913 }
1914 }
1915
1916 /* delete old /var/cache/PackageKit/metadata/$REPO/ */
1917 ret = dnf_repo_clean(repo, error);
1918 if (!ret)
1919 goto out;
1920
1921 /* rename .tmp actual name */
1922 ret = dnf_move_recursive(priv->location_tmp, priv->location, &error_local);
1923 if (!ret) {
1924 g_set_error(error,
1925 DNF_ERROR,
1926 DNF_ERROR_CANNOT_FETCH_SOURCE,
1927 "cannot move %s to %s: %s",
1928 priv->location_tmp, priv->location, error_local->message);
1929 goto out;
1930 }
1931 ret = lr_handle_setopt(priv->repo_handle, error,
1932 LRO_DESTDIR, priv->location);
1933 if (!ret)
1934 goto out;
1935 ret = lr_handle_setopt(priv->repo_handle, error,
1936 LRO_GNUPGHOMEDIR, priv->keyring);
1937 if (!ret)
1938 goto out;
1939
1940 /* done */
1941 ret = dnf_state_done(state, error);
1942 if (!ret)
1943 goto out;
1944
1945 /* now setup internal hawkey stuff */
1946 state_local = dnf_state_get_child(state);
1947 ret = dnf_repo_check(repo, G_MAXUINT, state_local, error);
1948 if (!ret)
1949 goto out;
1950
1951 /* signal that the vendor platform data is not resyned */
1952 dnf_context_invalidate_full(priv->context, "updated repo cache",
1953 DNF_CONTEXT_INVALIDATE_FLAG_ENROLLMENT);
1954
1955 /* done */
1956 ret = dnf_state_done(state, error);
1957 if (!ret)
1958 goto out;
1959 out:
1960 if (!ret) {
1961 /* remove the .tmp dir on failure */
1962 g_autoptr(GError) error_remove = NULL;
1963 if (!dnf_remove_recursive(priv->location_tmp, &error_remove))
1964 g_debug("Failed to remove %s: %s", priv->location_tmp, error_remove->message);
1965 }
1966 g_free(updatedata.last_mirror_failure_message);
1967 g_free(updatedata.last_mirror_url);
1968 dnf_state_release_locks(state);
1969 if (!lr_handle_setopt(priv->repo_handle, NULL, LRO_PROGRESSCB, NULL))
1970 g_debug("Failed to reset LRO_PROGRESSCB to NULL");
1971 if (!lr_handle_setopt(priv->repo_handle, NULL, LRO_HMFCB, NULL))
1972 g_debug("Failed to reset LRO_HMFCB to NULL");
1973 if (!lr_handle_setopt(priv->repo_handle, NULL, LRO_PROGRESSDATA, 0xdeadbeef))
1974 g_debug("Failed to set LRO_PROGRESSDATA to 0xdeadbeef");
1975 return ret;
1976 } CATCH_TO_GERROR(FALSE)
1977
1978 /**
1979 * dnf_repo_set_data:
1980 * @repo: a #DnfRepo instance.
1981 * @parameter: the UTF8 key.
1982 * @value: the UTF8 value.
1983 * @error: A #GError or %NULL
1984 *
1985 * Sets data on the repo.
1986 *
1987 * Returns: %TRUE for success, %FALSE otherwise
1988 *
1989 * Since: 0.1.0
1990 **/
1991 gboolean
1992 dnf_repo_set_data(DnfRepo *repo,
1993 const gchar *parameter,
1994 const gchar *value,
1995 GError **error) try
1996 {
1997 DnfRepoPrivate *priv = GET_PRIVATE(repo);
1998 g_key_file_set_string(priv->keyfile, priv->repo->getId().c_str(), parameter, value);
1999 return TRUE;
CATCH_TO_GERROR(FALSE)2000 } CATCH_TO_GERROR(FALSE)
2001
2002 /**
2003 * dnf_repo_commit:
2004 * @repo: a #DnfRepo instance.
2005 * @error: A #GError or %NULL
2006 *
2007 * Commits data on the repo, which involves saving a new .repo file.
2008 *
2009 * Returns: %TRUE for success, %FALSE otherwise
2010 *
2011 * Since: 0.1.4
2012 **/
2013 gboolean
2014 dnf_repo_commit(DnfRepo *repo, GError **error) try
2015 {
2016 DnfRepoPrivate *priv = GET_PRIVATE(repo);
2017 g_autofree gchar *data = NULL;
2018
2019 /* cannot change DVD contents */
2020 if (priv->kind == DNF_REPO_KIND_MEDIA) {
2021 g_set_error_literal(error,
2022 DNF_ERROR,
2023 DNF_ERROR_CANNOT_WRITE_REPO_CONFIG,
2024 "Cannot commit to read-only media");
2025 return FALSE;
2026 }
2027
2028 /* dump updated file to disk */
2029 data = g_key_file_to_data(priv->keyfile, NULL, error);
2030 if (data == NULL)
2031 return FALSE;
2032 return g_file_set_contents(priv->filename, data, -1, error);
2033 } CATCH_TO_GERROR(FALSE)
2034
2035 /**
2036 * dnf_repo_checksum_hy_to_lr:
2037 **/
2038 static LrChecksumType
2039 dnf_repo_checksum_hy_to_lr(int checksum_hy)
2040 {
2041 if (checksum_hy == G_CHECKSUM_MD5)
2042 return LR_CHECKSUM_MD5;
2043 if (checksum_hy == G_CHECKSUM_SHA1)
2044 return LR_CHECKSUM_SHA1;
2045 if (checksum_hy == G_CHECKSUM_SHA256)
2046 return LR_CHECKSUM_SHA256;
2047 return LR_CHECKSUM_UNKNOWN;
2048 }
2049
2050 typedef struct
2051 {
2052 gchar *last_mirror_url;
2053 gchar *last_mirror_failure_message;
2054 guint64 downloaded;
2055 guint64 download_size;
2056 } GlobalDownloadData;
2057
2058 typedef struct
2059 {
2060 DnfPackage *pkg;
2061 DnfState *state;
2062 guint64 downloaded;
2063 GlobalDownloadData *global_download_data;
2064 } PackageDownloadData;
2065
2066 static int
package_download_update_state_cb(void * user_data,gdouble total_to_download,gdouble now_downloaded)2067 package_download_update_state_cb(void *user_data,
2068 gdouble total_to_download,
2069 gdouble now_downloaded)
2070 {
2071 auto data = static_cast<PackageDownloadData *>(user_data);
2072 GlobalDownloadData *global_data = data->global_download_data;
2073 gboolean ret;
2074 gdouble percentage;
2075 guint64 previously_downloaded;
2076
2077 /* abort */
2078 if (!dnf_state_check(data->state, NULL))
2079 return -1;
2080
2081 /* nothing sensible */
2082 if (total_to_download < 0 || now_downloaded < 0)
2083 return 0;
2084
2085 dnf_state_action_start(data->state,
2086 DNF_STATE_ACTION_DOWNLOAD_PACKAGES,
2087 dnf_package_get_package_id(data->pkg));
2088
2089 previously_downloaded = data->downloaded;
2090 data->downloaded = now_downloaded;
2091
2092 global_data->downloaded += (now_downloaded - previously_downloaded);
2093
2094 /* set percentage */
2095 percentage = 100.0f * global_data->downloaded / global_data->download_size;
2096 ret = dnf_state_set_percentage(data->state, percentage);
2097 if (ret) {
2098 g_debug("update state %d/%d",
2099 (int)global_data->downloaded,
2100 (int)global_data->download_size);
2101 }
2102
2103 return 0;
2104 }
2105
2106 static int
package_download_end_cb(void * user_data,LrTransferStatus status,const char * msg)2107 package_download_end_cb(void *user_data,
2108 LrTransferStatus status,
2109 const char *msg)
2110 {
2111 auto data = static_cast<PackageDownloadData *>(user_data);
2112
2113 g_slice_free(PackageDownloadData, data);
2114
2115 return LR_CB_OK;
2116 }
2117
2118 static int
mirrorlist_failure_cb(void * user_data,const char * message,const char * url)2119 mirrorlist_failure_cb(void *user_data,
2120 const char *message,
2121 const char *url)
2122 {
2123 auto data = static_cast<PackageDownloadData *>(user_data);
2124 auto global_data = data->global_download_data;
2125
2126 if (global_data->last_mirror_url)
2127 goto out;
2128
2129 global_data->last_mirror_url = g_strdup(url);
2130 global_data->last_mirror_failure_message = g_strdup(message);
2131 out:
2132 return LR_CB_OK;
2133 }
2134
2135 LrHandle *
dnf_repo_get_lr_handle(DnfRepo * repo)2136 dnf_repo_get_lr_handle(DnfRepo *repo)
2137 {
2138 DnfRepoPrivate *priv = GET_PRIVATE (repo);
2139 return priv->repo_handle;
2140 }
2141
2142 LrResult *
dnf_repo_get_lr_result(DnfRepo * repo)2143 dnf_repo_get_lr_result(DnfRepo *repo)
2144 {
2145 DnfRepoPrivate *priv = GET_PRIVATE (repo);
2146 return priv->repo_result;
2147 }
2148
2149 /**
2150 * dnf_repo_download_package:
2151 * @repo: a #DnfRepo instance.
2152 * @pkg: a #DnfPackage *instance.
2153 * @directory: the destination directory.
2154 * @state: a #DnfState.
2155 * @error: a #GError or %NULL..
2156 *
2157 * Downloads a package from a repo.
2158 *
2159 * Returns: the complete filename of the downloaded file
2160 *
2161 * Since: 0.1.0
2162 **/
2163 gchar *
dnf_repo_download_package(DnfRepo * repo,DnfPackage * pkg,const gchar * directory,DnfState * state,GError ** error)2164 dnf_repo_download_package(DnfRepo *repo,
2165 DnfPackage *pkg,
2166 const gchar *directory,
2167 DnfState *state,
2168 GError **error) try
2169 {
2170 g_autoptr(GPtrArray) packages = g_ptr_array_new();
2171 g_autofree gchar *basename = NULL;
2172
2173 g_ptr_array_add(packages, pkg);
2174
2175 if (!dnf_repo_download_packages(repo, packages, directory, state, error))
2176 return NULL;
2177
2178 /* build return value */
2179 basename = g_path_get_basename(dnf_package_get_location(pkg));
2180 return g_build_filename(directory, basename, NULL);
2181 } CATCH_TO_GERROR(NULL)
2182
2183 /**
2184 * dnf_repo_download_packages:
2185 * @repo: a #DnfRepo instance.
2186 * @packages: (element-type DnfPackage): an array of packages, must be from this repo
2187 * @directory: the destination directory.
2188 * @state: a #DnfState.
2189 * @error: a #GError or %NULL.
2190 *
2191 * Downloads multiple packages from a repo. The target filename will be
2192 * equivalent to `g_path_get_basename (dnf_package_get_location (pkg))`.
2193 *
2194 * Returns: %TRUE for success, %FALSE otherwise
2195 *
2196 * Since: 0.2.3
2197 **/
2198 gboolean
2199 dnf_repo_download_packages(DnfRepo *repo,
2200 GPtrArray *packages,
2201 const gchar *directory,
2202 DnfState *state,
2203 GError **error) try
2204 {
2205 DnfRepoPrivate *priv = GET_PRIVATE(repo);
2206 gboolean ret = FALSE;
2207 guint i;
2208 GSList *package_targets = NULL;
2209 GlobalDownloadData global_data = { 0, };
2210 g_autoptr(GError) error_local = NULL;
2211 g_autofree gchar *directory_slash = NULL;
2212
2213 /* ensure we reset the values from the keyfile */
2214 if (!dnf_repo_set_keyfile_data(repo, TRUE, error))
2215 goto out;
2216
2217 /* if nothing specified then use cachedir */
2218 if (directory == NULL) {
2219 directory_slash = g_build_filename(priv->packages, "/", NULL);
2220 if (!g_file_test(directory_slash, G_FILE_TEST_EXISTS)) {
2221 if (g_mkdir_with_parents(directory_slash, 0755) != 0) {
2222 g_set_error(error,
2223 DNF_ERROR,
2224 DNF_ERROR_INTERNAL_ERROR,
2225 "Failed to create %s",
2226 directory_slash);
2227 goto out;
2228 }
2229 }
2230 } else {
2231 /* librepo uses the GNU basename() function to find out if the
2232 * output directory is fully specified as a filename, but
2233 * basename needs a trailing '/' to detect it's not a filename */
2234 directory_slash = g_build_filename(directory, "/", NULL);
2235 }
2236
2237 global_data.download_size = dnf_package_array_get_download_size(packages);
2238 for (i = 0; i < packages->len; i++) {
2239 auto pkg = static_cast<DnfPackage *>(packages->pdata[i]);
2240 PackageDownloadData *data;
2241 LrPackageTarget *target;
2242 const unsigned char *checksum;
2243 int checksum_type;
2244 g_autofree char *checksum_str = NULL;
2245
2246 g_debug("downloading %s to %s",
2247 dnf_package_get_location(pkg),
2248 directory_slash);
2249
2250 data = g_slice_new0(PackageDownloadData);
2251 data->pkg = pkg;
2252 data->state = state;
2253 data->global_download_data = &global_data;
2254
2255 checksum = dnf_package_get_chksum(pkg, &checksum_type);
2256 checksum_str = hy_chksum_str(checksum, checksum_type);
2257
2258 std::string encodedUrl = dnf_package_get_location(pkg);
2259 if (encodedUrl.find("://") == std::string::npos) {
2260 encodedUrl = libdnf::urlEncode(encodedUrl, "/");
2261 }
2262
2263 target = lr_packagetarget_new_v2(priv->repo_handle,
2264 encodedUrl.c_str(),
2265 directory_slash,
2266 dnf_repo_checksum_hy_to_lr(checksum_type),
2267 checksum_str,
2268 dnf_package_get_downloadsize(pkg),
2269 dnf_package_get_baseurl(pkg),
2270 TRUE,
2271 package_download_update_state_cb,
2272 data,
2273 package_download_end_cb,
2274 mirrorlist_failure_cb,
2275 error);
2276 if (target == NULL)
2277 goto out;
2278
2279 package_targets = g_slist_prepend(package_targets, target);
2280 }
2281
2282 ret = lr_download_packages(package_targets, LR_PACKAGEDOWNLOAD_FAILFAST, &error_local);
2283 if (!ret) {
2284 if (g_error_matches(error_local,
2285 LR_PACKAGE_DOWNLOADER_ERROR,
2286 LRE_ALREADYDOWNLOADED)) {
2287 /* ignore */
2288 g_clear_error(&error_local);
2289 } else {
2290 if (global_data.last_mirror_failure_message) {
2291 g_autofree gchar *orig_message = error_local->message;
2292 error_local->message = g_strconcat(orig_message, "; Last error: ", global_data.last_mirror_failure_message, NULL);
2293 }
2294 g_propagate_error(error, error_local);
2295 error_local = NULL;
2296 goto out;
2297 }
2298 }
2299
2300 ret = TRUE;
2301 out:
2302 if (!lr_handle_setopt(priv->repo_handle, NULL, LRO_PROGRESSCB, NULL))
2303 g_debug("Failed to reset LRO_PROGRESSCB to NULL");
2304 if (!lr_handle_setopt(priv->repo_handle, NULL, LRO_PROGRESSDATA, 0xdeadbeef))
2305 g_debug("Failed to set LRO_PROGRESSDATA to 0xdeadbeef");
2306 g_free(global_data.last_mirror_failure_message);
2307 g_free(global_data.last_mirror_url);
2308 g_slist_free_full(package_targets, (GDestroyNotify)lr_packagetarget_free);
2309 return ret;
CATCH_TO_GERROR(FALSE)2310 } CATCH_TO_GERROR(FALSE)
2311
2312 /**
2313 * dnf_repo_new:
2314 * @context: A #DnfContext instance
2315 *
2316 * Creates a new #DnfRepo.
2317 *
2318 * Returns:(transfer full): a #DnfRepo
2319 *
2320 * Since: 0.1.0
2321 **/
2322 DnfRepo *
2323 dnf_repo_new(DnfContext *context)
2324 {
2325 auto repo = DNF_REPO(g_object_new(DNF_TYPE_REPO, NULL));
2326 auto priv = GET_PRIVATE(repo);
2327 priv->context = context;
2328 g_object_add_weak_pointer(G_OBJECT(priv->context),(void **) &priv->context);
2329 return repo;
2330 }
2331
dnf_repo_get_hy_repo(DnfRepo * repo)2332 HyRepo dnf_repo_get_hy_repo(DnfRepo *repo)
2333 {
2334 auto priv = GET_PRIVATE(repo);
2335 return priv->repo;
2336 }
2337
2338 /**
2339 * dnf_repo_add_metadata_type_to_download:
2340 * @repo: a #DnfRepo instance.
2341 * @metadataType: a #gchar, e.g. %"filelist"
2342 *
2343 * Ask for additional repository metadata type to download.
2344 * Given metadata are appended to the default metadata set when repository is downloaded.
2345 *
2346 * Since: 0.24.0
2347 **/
2348 void
dnf_repo_add_metadata_type_to_download(DnfRepo * repo,const gchar * metadataType)2349 dnf_repo_add_metadata_type_to_download(DnfRepo * repo, const gchar * metadataType)
2350 {
2351 auto priv = GET_PRIVATE(repo);
2352 libdnf::repoGetImpl(priv->repo)->additionalMetadata.insert(metadataType);
2353 }
2354
2355 /**
2356 * dnf_repo_get_metadata_content:
2357 * @repo: a #DnfRepo instance.
2358 * @metadataType: a #gchar, e.g. %"filelist"
2359 * @content: a #gpointer, a pointer to allocated memory with content of metadata file
2360 * @length: a #gsize
2361 * @error: a #GError or %NULL.
2362 *
2363 * Return content of the particular downloaded repository metadata.
2364 * Content of compressed metadata file is returned uncompressed.
2365 *
2366 * Returns: %TRUE for success, %FALSE otherwise
2367 *
2368 * Since: 0.24.0
2369 **/
2370 gboolean
dnf_repo_get_metadata_content(DnfRepo * repo,const gchar * metadataType,gpointer * content,gsize * length,GError ** error)2371 dnf_repo_get_metadata_content(DnfRepo * repo, const gchar * metadataType, gpointer * content,
2372 gsize * length, GError ** error) try
2373 {
2374 auto path = dnf_repo_get_filename_md(repo, metadataType);
2375 if (!path) {
2376 g_set_error(error, DNF_ERROR, DNF_ERROR_FILE_NOT_FOUND,
2377 "Cannot found metadata type \"%s\" for repo \"%s\"",
2378 metadataType, dnf_repo_get_id(repo));
2379 return FALSE;
2380 }
2381
2382 try {
2383 auto mdfile = libdnf::File::newFile(path);
2384 mdfile->open("r");
2385 const auto &fcontent = mdfile->getContent();
2386 mdfile->close();
2387 auto data = g_malloc(fcontent.length());
2388 memcpy(data, fcontent.data(), fcontent.length());
2389 *content = data;
2390 *length = fcontent.length();
2391 return TRUE;
2392 } catch (std::runtime_error & ex) {
2393 g_set_error(error, DNF_ERROR, DNF_ERROR_FAILED,
2394 "Cannot load metadata type \"%s\" for repo \"%s\": %s",
2395 metadataType, dnf_repo_get_id(repo), ex.what());
2396 return FALSE;
2397 }
2398 } CATCH_TO_GERROR(FALSE)
2399