1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 *
3 * Copyright (C) 2013-2015 Richard Hughes <richard@hughsie.com>
4 *
5 * Licensed under the GNU Lesser General Public License Version 2.1
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or(at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22 /**
23 * SECTION:dnf-repo-loader
24 * @short_description: An object for loading several #DnfRepo objects.
25 * @include: libdnf.h
26 * @stability: Stable
27 *
28 * This object can create #DnfRepo objects from either DVD media or from .repo
29 * files in the repodir.
30 *
31 * See also: #DnfRepo
32 */
33
34 #include <strings.h>
35
36 #include <gio/gunixmounts.h>
37 #include <librepo/util.h>
38 #include <string.h>
39
40 #include "catch-error.hpp"
41 #include "dnf-package.h"
42 #include "dnf-repo-loader.h"
43 #include "dnf-utils.h"
44
45 typedef struct
46 {
47 GPtrArray *monitor_repos;
48 DnfContext *context; /* weak reference */
49 GPtrArray *repos;
50 GVolumeMonitor *volume_monitor;
51 gboolean loaded;
52 } DnfRepoLoaderPrivate;
53
54 enum {
55 SIGNAL_CHANGED,
56 SIGNAL_LAST
57 };
58
59 static guint signals[SIGNAL_LAST] = { 0 };
60
G_DEFINE_TYPE_WITH_PRIVATE(DnfRepoLoader,dnf_repo_loader,G_TYPE_OBJECT)61 G_DEFINE_TYPE_WITH_PRIVATE(DnfRepoLoader, dnf_repo_loader, G_TYPE_OBJECT)
62 #define GET_PRIVATE(o) (static_cast<DnfRepoLoaderPrivate *>(dnf_repo_loader_get_instance_private (o)))
63
64 /**
65 * dnf_repo_loader_finalize:
66 **/
67 static void
68 dnf_repo_loader_finalize(GObject *object)
69 {
70 DnfRepoLoader *self = DNF_REPO_LOADER(object);
71 DnfRepoLoaderPrivate *priv = GET_PRIVATE(self);
72
73 if (priv->context != NULL)
74 g_object_remove_weak_pointer(G_OBJECT(priv->context),
75 (void **) &priv->context);
76 g_ptr_array_unref(priv->monitor_repos);
77 g_object_unref(priv->volume_monitor);
78 g_ptr_array_unref(priv->repos);
79
80 G_OBJECT_CLASS(dnf_repo_loader_parent_class)->finalize(object);
81 }
82
83 /**
84 * dnf_repo_loader_invalidate:
85 */
86 static void
dnf_repo_loader_invalidate(DnfRepoLoader * self)87 dnf_repo_loader_invalidate(DnfRepoLoader *self)
88 {
89 DnfRepoLoaderPrivate *priv = GET_PRIVATE(self);
90 priv->loaded = FALSE;
91 dnf_context_invalidate_full(priv->context, "repos.d invalidated",
92 DNF_CONTEXT_INVALIDATE_FLAG_ENROLLMENT);
93 }
94
95 /**
96 * dnf_repo_loader_mount_changed_cb:
97 */
98 static void
dnf_repo_loader_mount_changed_cb(GVolumeMonitor * vm,GMount * mount,DnfRepoLoader * self)99 dnf_repo_loader_mount_changed_cb(GVolumeMonitor *vm, GMount *mount, DnfRepoLoader *self)
100 {
101 /* invalidate all repos if a CD is inserted / removed */
102 g_debug("emit changed(mounts changed)");
103 g_signal_emit(self, signals[SIGNAL_CHANGED], 0);
104 dnf_repo_loader_invalidate(self);
105 }
106
107 /**
108 * dnf_repo_loader_init:
109 **/
110 static void
dnf_repo_loader_init(DnfRepoLoader * self)111 dnf_repo_loader_init(DnfRepoLoader *self)
112 {
113 DnfRepoLoaderPrivate *priv = GET_PRIVATE(self);
114 priv->monitor_repos = g_ptr_array_new_with_free_func((GDestroyNotify) g_object_unref);
115 priv->repos = g_ptr_array_new_with_free_func((GDestroyNotify) g_object_unref);
116 priv->volume_monitor = g_volume_monitor_get();
117 g_signal_connect(priv->volume_monitor, "mount-added",
118 G_CALLBACK(dnf_repo_loader_mount_changed_cb), self);
119 g_signal_connect(priv->volume_monitor, "mount-removed",
120 G_CALLBACK(dnf_repo_loader_mount_changed_cb), self);
121 }
122
123 /**
124 * dnf_repo_loader_class_init:
125 **/
126 static void
dnf_repo_loader_class_init(DnfRepoLoaderClass * klass)127 dnf_repo_loader_class_init(DnfRepoLoaderClass *klass)
128 {
129 GObjectClass *object_class = G_OBJECT_CLASS(klass);
130
131 /**
132 * DnfRepoLoader::changed:
133 **/
134 signals[SIGNAL_CHANGED] =
135 g_signal_new("changed",
136 G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST,
137 G_STRUCT_OFFSET(DnfRepoLoaderClass, changed),
138 NULL, NULL, g_cclosure_marshal_VOID__VOID,
139 G_TYPE_NONE, 0);
140
141 object_class->finalize = dnf_repo_loader_finalize;
142 }
143
144 /**
145 * dnf_repo_loader_add_media:
146 **/
147 static gboolean
dnf_repo_loader_add_media(DnfRepoLoader * self,const gchar * mount_point,guint idx,GError ** error)148 dnf_repo_loader_add_media(DnfRepoLoader *self,
149 const gchar *mount_point,
150 guint idx,
151 GError **error)
152 {
153 DnfRepoLoaderPrivate *priv = GET_PRIVATE(self);
154 DnfRepo *repo;
155 g_autofree gchar *treeinfo_fn = NULL;
156 g_autoptr(GKeyFile) treeinfo = NULL;
157
158 /* get common things */
159 treeinfo_fn = g_build_filename(mount_point, ".treeinfo", NULL);
160 treeinfo = g_key_file_new();
161 if (!g_key_file_load_from_file(treeinfo, treeinfo_fn, G_KEY_FILE_NONE, error))
162 return FALSE;
163
164 /* create read-only location */
165 repo = dnf_repo_new(priv->context);
166 dnf_repo_set_enabled(repo, DNF_REPO_ENABLED_PACKAGES);
167 dnf_repo_set_gpgcheck(repo, TRUE);
168 dnf_repo_set_kind(repo, DNF_REPO_KIND_MEDIA);
169 dnf_repo_set_skip_if_unavailable(repo, TRUE);
170 dnf_repo_set_cost(repo, 100);
171 dnf_repo_set_keyfile(repo, treeinfo);
172 if (idx == 0) {
173 dnf_repo_set_id(repo, "media");
174 } else {
175 g_autofree gchar *tmp = NULL;
176 tmp = g_strdup_printf("media-%i", idx);
177 dnf_repo_set_id(repo, tmp);
178 }
179 dnf_repo_set_location(repo, mount_point);
180 if (!dnf_repo_setup(repo, error))
181 return FALSE;
182
183 g_debug("added repo %s", dnf_repo_get_id(repo));
184 g_ptr_array_add(priv->repos, repo);
185 return TRUE;
186 }
187
188 /**
189 * dnf_repo_loader_add_sack_from_mount_point:
190 */
191 static gboolean
dnf_repo_loader_add_sack_from_mount_point(DnfRepoLoader * self,const gchar * root,guint * idx,GError ** error)192 dnf_repo_loader_add_sack_from_mount_point(DnfRepoLoader *self,
193 const gchar *root,
194 guint *idx,
195 GError **error)
196 {
197 const gchar *id = ".treeinfo";
198 gboolean exists;
199 g_autofree gchar *treeinfo_fn = NULL;
200
201 /* check if any installed media is an install disk */
202 treeinfo_fn = g_build_filename(root, id, NULL);
203 exists = g_file_test(treeinfo_fn, G_FILE_TEST_EXISTS);
204 g_debug("checking %s for %s: %s", root, id, exists ? "yes" : "no");
205 if (!exists)
206 return TRUE;
207
208 /* add the repodata/repomd.xml as a repo */
209 if (!dnf_repo_loader_add_media(self, root, *idx, error))
210 return FALSE;
211 (*idx)++;
212 return TRUE;
213 }
214
215 /**
216 * dnf_repo_loader_get_repos_removable:
217 */
218 static gboolean
dnf_repo_loader_get_repos_removable(DnfRepoLoader * self,GError ** error)219 dnf_repo_loader_get_repos_removable(DnfRepoLoader *self, GError **error)
220 {
221 GList *mounts;
222 GList *l;
223 gboolean ret = TRUE;
224 guint idx = 0;
225
226 /* coldplug the mounts */
227 mounts = g_unix_mounts_get(NULL);
228 for (l = mounts; l != NULL; l = l->next) {
229 GUnixMountEntry *e =(GUnixMountEntry *) l->data;
230 if (!g_unix_mount_is_readonly(e))
231 continue;
232 if (g_strcmp0(g_unix_mount_get_fs_type(e), "iso9660") != 0)
233 continue;
234 ret = dnf_repo_loader_add_sack_from_mount_point(self,
235 g_unix_mount_get_mount_path(e),
236 &idx,
237 error);
238 if (!ret)
239 goto out;
240 }
241 out:
242 g_list_foreach(mounts,(GFunc) g_unix_mount_free, NULL);
243 g_list_free(mounts);
244 return ret;
245 }
246
247 /**
248 * dnf_repo_loader_repo_cost_fn:
249 */
250 static gint
dnf_repo_loader_repo_cost_fn(gconstpointer a,gconstpointer b)251 dnf_repo_loader_repo_cost_fn(gconstpointer a, gconstpointer b)
252 {
253 DnfRepo *repo_a = *((DnfRepo **) a);
254 DnfRepo *repo_b = *((DnfRepo **) b);
255 if (dnf_repo_get_cost(repo_a) < dnf_repo_get_cost(repo_b))
256 return -1;
257 if (dnf_repo_get_cost(repo_a) > dnf_repo_get_cost(repo_b))
258 return 1;
259 return 0;
260 }
261
262 /**
263 * dnf_repo_loader_load_multiline_key_file:
264 **/
265 static GKeyFile *
dnf_repo_loader_load_multiline_key_file(const gchar * filename,GError ** error)266 dnf_repo_loader_load_multiline_key_file(const gchar *filename, GError **error)
267 {
268 GKeyFile *file = NULL;
269 gboolean ret;
270 gsize len;
271 guint i;
272 g_autofree gchar *data = NULL;
273 g_autoptr(GString) string = NULL;
274 g_auto(GStrv) lines = NULL;
275
276 /* load file */
277 if (!g_file_get_contents(filename, &data, &len, error))
278 return NULL;
279
280 /* split into lines */
281 string = g_string_new("");
282 lines = g_strsplit(data, "\n", -1);
283 for (i = 0; lines[i] != NULL; i++) {
284
285 /* convert tabs to spaces */
286 g_strdelimit(lines[i], "\t", ' ');
287
288 /* if a line starts with whitespace, then append it on
289 * the previous line */
290 if (lines[i][0] == ' ' && string->len > 0) {
291
292 /* whitespace strip this new line */
293 g_strstrip(lines[i]);
294
295 /* skip over the line if it was only whitespace */
296 if (strlen(lines[i]) == 0)
297 continue;
298
299 /* remove old newline from previous line */
300 g_string_set_size(string, string->len - 1);
301
302 /* only add a ';' if we have anything after the '=' */
303 if (string->str[string->len - 1] == '=') {
304 g_string_append_printf(string, "%s\n", lines[i]);
305 } else {
306 g_string_append_printf(string, ";%s\n", lines[i]);
307 }
308 } else {
309 g_string_append_printf(string, "%s\n", lines[i]);
310 }
311 }
312
313 /* remove final newline */
314 if (string->len > 0)
315 g_string_set_size(string, string->len - 1);
316
317 /* load modified lines */
318 file = g_key_file_new();
319 ret = g_key_file_load_from_data(file,
320 string->str,
321 -1,
322 G_KEY_FILE_KEEP_COMMENTS,
323 error);
324 if (!ret) {
325 g_key_file_free(file);
326 return NULL;
327 }
328 return file;
329 }
330
331 /**
332 * dnf_repo_loader_repo_parse_id:
333 **/
334 static gboolean
dnf_repo_loader_repo_parse_id(DnfRepoLoader * self,const gchar * id,const gchar * filename,GKeyFile * keyfile,GError ** error)335 dnf_repo_loader_repo_parse_id(DnfRepoLoader *self,
336 const gchar *id,
337 const gchar *filename,
338 GKeyFile *keyfile,
339 GError **error)
340 {
341 DnfRepoLoaderPrivate *priv = GET_PRIVATE(self);
342 g_autoptr(DnfRepo) repo = NULL;
343
344 repo = dnf_repo_new(priv->context);
345 dnf_repo_set_kind(repo, DNF_REPO_KIND_REMOTE);
346 dnf_repo_set_keyfile(repo, keyfile);
347 dnf_repo_set_filename(repo, filename);
348 dnf_repo_set_id(repo, id);
349
350 /* set up the repo ready for use */
351 if (!dnf_repo_setup(repo, error))
352 return FALSE;
353
354 g_debug("added repo %s\t%s", filename, id);
355 g_ptr_array_add(priv->repos, g_object_ref(repo));
356 return TRUE;
357 }
358
359 /**
360 * dnf_repo_loader_repo_parse:
361 **/
362 static gboolean
dnf_repo_loader_repo_parse(DnfRepoLoader * self,const gchar * filename,GError ** error)363 dnf_repo_loader_repo_parse(DnfRepoLoader *self,
364 const gchar *filename,
365 GError **error)
366 {
367 gboolean ret = TRUE;
368 guint i;
369 g_auto(GStrv) groups = NULL;
370 g_autoptr(GKeyFile) keyfile = NULL;
371
372 /* load non-standard keyfile */
373 keyfile = dnf_repo_loader_load_multiline_key_file(filename, error);
374 if (keyfile == NULL) {
375 g_prefix_error(error, "Failed to load %s: ", filename);
376 return FALSE;
377 }
378
379 /* save all the repos listed in the file, "main" section is skipped - repoid can't be "main" */
380 groups = g_key_file_get_groups(keyfile, NULL);
381 for (i = 0; groups[i] != NULL; i++) {
382 if (strcmp(groups[i], "main") == 0) {
383 continue;
384 }
385 ret = dnf_repo_loader_repo_parse_id(self, groups[i], filename, keyfile, error);
386 if (!ret)
387 return FALSE;
388 }
389 return TRUE;
390 }
391
392 /**
393 * dnf_repo_loader_refresh:
394 */
395 static gboolean
dnf_repo_loader_refresh(DnfRepoLoader * self,GError ** error)396 dnf_repo_loader_refresh(DnfRepoLoader *self, GError **error)
397 {
398 DnfRepoLoaderPrivate *priv = GET_PRIVATE(self);
399
400 if (!dnf_context_plugin_hook(priv->context, PLUGIN_HOOK_ID_CONTEXT_PRE_REPOS_RELOAD, nullptr, nullptr))
401 return FALSE;
402
403 /* no longer loaded */
404 dnf_repo_loader_invalidate(self);
405 g_ptr_array_set_size(priv->repos, 0);
406
407 /* re-populate redhat.repo */
408 if (!dnf_context_setup_enrollments(priv->context, error))
409 return FALSE;
410
411 /* load repos defined in main configuration */
412 auto cfg_file_path = dnf_context_get_config_file_path();
413 if (cfg_file_path[0] != '\0' &&
414 (dnf_context_is_set_config_file_path() || g_file_test(cfg_file_path, G_FILE_TEST_IS_REGULAR))) {
415 if (!dnf_repo_loader_repo_parse(self, cfg_file_path, error)) {
416 return FALSE;
417 }
418 }
419
420 /* open dir */
421 auto repos_dir = dnf_context_get_repos_dir(priv->context);
422 for (auto item = repos_dir; *item; ++item) {
423 auto repo_path = *item;
424 g_autoptr(GDir) dir = g_dir_open(repo_path, 0, NULL);
425 // existence of repos directories is not mandatory
426 if (dir == NULL)
427 continue;
428
429 /* find all the .repo files */
430 while (auto file = g_dir_read_name(dir)) {
431 g_autofree gchar *path_tmp = NULL;
432 if (!g_str_has_suffix(file, ".repo"))
433 continue;
434 path_tmp = g_build_filename(repo_path, file, NULL);
435 if (!dnf_repo_loader_repo_parse(self, path_tmp, error))
436 return FALSE;
437 }
438 }
439
440 /* add any DVD repos */
441 if (!dnf_repo_loader_get_repos_removable(self, error))
442 return FALSE;
443
444 /* all okay */
445 priv->loaded = TRUE;
446
447 /* sort these in order of cost */
448 g_ptr_array_sort(priv->repos, dnf_repo_loader_repo_cost_fn);
449 return TRUE;
450 }
451
452 /**
453 * dnf_repo_loader_has_removable_repos:
454 */
455 gboolean
dnf_repo_loader_has_removable_repos(DnfRepoLoader * self)456 dnf_repo_loader_has_removable_repos(DnfRepoLoader *self)
457 {
458 DnfRepoLoaderPrivate *priv = GET_PRIVATE(self);
459 guint i;
460
461 g_return_val_if_fail(DNF_IS_REPO_LOADER(self), FALSE);
462
463 /* are there any media repos */
464 for (i = 0; i < priv->repos->len; i++) {
465 auto repo = static_cast<DnfRepo *>(g_ptr_array_index(priv->repos, i));
466 if (dnf_repo_get_kind(repo) == DNF_REPO_KIND_MEDIA)
467 return TRUE;
468 }
469 return FALSE;
470 }
471
472 /**
473 * dnf_repo_loader_get_repos:
474 *
475 * Returns:(transfer container)(element-type DnfRepo): Array of repos
476 */
477 GPtrArray *
dnf_repo_loader_get_repos(DnfRepoLoader * self,GError ** error)478 dnf_repo_loader_get_repos(DnfRepoLoader *self, GError **error) try
479 {
480 DnfRepoLoaderPrivate *priv = GET_PRIVATE(self);
481
482 g_return_val_if_fail(DNF_IS_REPO_LOADER(self), NULL);
483 g_return_val_if_fail(error == NULL || *error == NULL, NULL);
484
485 /* nothing set yet */
486 if (!priv->loaded) {
487 if (!dnf_repo_loader_refresh(self, error))
488 return NULL;
489 }
490
491 /* all okay */
492 return g_ptr_array_ref(priv->repos);
493 } CATCH_TO_GERROR(NULL)
494
495 /**
496 * dnf_repo_loader_get_repo_by_id:
497 */
498 DnfRepo *
499 dnf_repo_loader_get_repo_by_id(DnfRepoLoader *self, const gchar *id, GError **error) try
500 {
501 DnfRepoLoaderPrivate *priv = GET_PRIVATE(self);
502 guint i;
503
504 g_return_val_if_fail(DNF_IS_REPO_LOADER(self), NULL);
505 g_return_val_if_fail(id != NULL, NULL);
506 g_return_val_if_fail(error == NULL || *error == NULL, NULL);
507
508 /* nothing set yet */
509 if (!priv->loaded) {
510 if (!dnf_repo_loader_refresh(self, error))
511 return NULL;
512 }
513
514 for (i = 0; i < priv->repos->len; i++) {
515 auto tmp = static_cast<DnfRepo *>(g_ptr_array_index(priv->repos, i));
516 if (g_strcmp0(dnf_repo_get_id(tmp), id) == 0)
517 return tmp;
518 }
519
520 /* we didn't find anything */
521 g_set_error(error,
522 DNF_ERROR,
523 DNF_ERROR_REPO_NOT_FOUND,
524 "failed to find %s", id);
525 return NULL;
CATCH_TO_GERROR(NULL)526 } CATCH_TO_GERROR(NULL)
527
528 /**
529 * dnf_repo_loader_directory_changed_cb:
530 **/
531 static void
532 dnf_repo_loader_directory_changed_cb(GFileMonitor *monitor_,
533 GFile *file, GFile *other_file,
534 GFileMonitorEvent event_type,
535 DnfRepoLoader *self)
536 {
537 g_debug("emit changed(ReposDir changed)");
538 g_signal_emit(self, signals[SIGNAL_CHANGED], 0);
539 dnf_repo_loader_invalidate(self);
540 }
541
542 /**
543 * dnf_repo_loader_setup_monitor:
544 */
545 static void
dnf_repo_loader_setup_monitor(DnfRepoLoader * self,const char * path,bool is_dir)546 dnf_repo_loader_setup_monitor(DnfRepoLoader *self, const char * path, bool is_dir)
547 {
548 DnfRepoLoaderPrivate *priv = GET_PRIVATE(self);
549 g_autoptr(GFile) file_path = g_file_new_for_path(path);
550 g_autoptr(GError) error = NULL;
551 GFileMonitor * monitor = NULL;
552 if (is_dir) {
553 monitor = g_file_monitor_directory(file_path, G_FILE_MONITOR_NONE, NULL, &error);
554 } else {
555 monitor = g_file_monitor_file(file_path, G_FILE_MONITOR_NONE, NULL, &error);
556 }
557 if (monitor) {
558 g_ptr_array_add(priv->monitor_repos, monitor);
559 g_signal_connect(monitor, "changed",
560 G_CALLBACK(dnf_repo_loader_directory_changed_cb), self);
561 } else {
562 g_warning("failed to setup monitor: %s",
563 error->message);
564 }
565 }
566
567 /**
568 * dnf_repo_loader_setup_watch:
569 */
570 static void
dnf_repo_loader_setup_watch(DnfRepoLoader * self)571 dnf_repo_loader_setup_watch(DnfRepoLoader *self)
572 {
573 DnfRepoLoaderPrivate *priv = GET_PRIVATE(self);
574
575 /* setup a file monitor on the main configuration file */
576 auto cfg_file_path = dnf_context_get_config_file_path();
577 if (cfg_file_path[0] != '\0' && g_file_test(cfg_file_path, G_FILE_TEST_IS_REGULAR)) {
578 dnf_repo_loader_setup_monitor(self, cfg_file_path, false);
579 }
580
581 /* setup a file monitor on the repos directory */
582 auto repos_dir = dnf_context_get_repos_dir(priv->context);
583 if (!repos_dir[0]) {
584 g_warning("no repodir set");
585 return;
586 }
587 for (auto iter = repos_dir; *iter; ++iter) {
588 auto repo_dir = *iter;
589 dnf_repo_loader_setup_monitor(self, repo_dir, true);
590 }
591 }
592
593 /**
594 * dnf_repo_loader_new:
595 * @context: A #DnfContext instance
596 *
597 * Creates a new #DnfRepoLoader.
598 *
599 * Returns:(transfer full): a #DnfRepoLoader
600 *
601 * Since: 0.1.0
602 **/
603 DnfRepoLoader *
dnf_repo_loader_new(DnfContext * context)604 dnf_repo_loader_new(DnfContext *context)
605 {
606 DnfRepoLoaderPrivate *priv;
607 auto self = DNF_REPO_LOADER(g_object_new(DNF_TYPE_REPO_LOADER, NULL));
608 priv = GET_PRIVATE(self);
609 priv->context = context;
610 g_object_add_weak_pointer(G_OBJECT(priv->context),(void **) &priv->context);
611 dnf_repo_loader_setup_watch(self);
612 return DNF_REPO_LOADER(self);
613 }
614