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, &timestamp_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