diff --git a/Makefile-ostree.am b/Makefile-ostree.am index fb377075eb..f78f7d7571 100644 --- a/Makefile-ostree.am +++ b/Makefile-ostree.am @@ -82,6 +82,7 @@ ostree_SOURCES += \ src/ostree/ot-admin-builtin-pin.c \ src/ostree/ot-admin-builtin-upgrade.c \ src/ostree/ot-admin-builtin-unlock.c \ + src/ostree/ot-admin-builtin-create-toplevel-user-links.c \ src/ostree/ot-admin-builtins.h \ src/ostree/ot-admin-instutil-builtin-selinux-ensure-labeled.c \ src/ostree/ot-admin-instutil-builtin-set-kargs.c \ diff --git a/src/libostree/ostree-cmd-private.c b/src/libostree/ostree-cmd-private.c index ad820fdeac..e1a9290473 100644 --- a/src/libostree/ostree-cmd-private.c +++ b/src/libostree/ostree-cmd-private.c @@ -52,6 +52,7 @@ ostree_cmd__private__ (void) _ostree_repo_verify_bindings, _ostree_sysroot_finalize_staged, _ostree_sysroot_boot_complete, + _ostree_sysroot_create_toplevel_user_links, }; return &table; diff --git a/src/libostree/ostree-cmd-private.h b/src/libostree/ostree-cmd-private.h index 17f943c8b2..1c9c2f873d 100644 --- a/src/libostree/ostree-cmd-private.h +++ b/src/libostree/ostree-cmd-private.h @@ -34,6 +34,7 @@ typedef struct { gboolean (* ostree_repo_verify_bindings) (const char *collection_id, const char *ref_name, GVariant *commit, GError **error); gboolean (* ostree_finalize_staged) (OstreeSysroot *sysroot, GCancellable *cancellable, GError **error); gboolean (* ostree_boot_complete) (OstreeSysroot *sysroot, GCancellable *cancellable, GError **error); + gboolean (* ostree_create_toplevel_user_links) (OstreeSysroot *sysroot, int deployment_dfd, GCancellable *cancellable, GError **error); } OstreeCmdPrivateVTable; /* Note this not really "public", we just export the symbol, but not the header */ diff --git a/src/libostree/ostree-repo-private.h b/src/libostree/ostree-repo-private.h index 0d33f7c2d0..132b34968d 100644 --- a/src/libostree/ostree-repo-private.h +++ b/src/libostree/ostree-repo-private.h @@ -211,6 +211,7 @@ struct OstreeRepo { guint test_error_flags; /* OstreeRepoTestErrorFlags */ GKeyFile *config; + GKeyFile *user_config; GHashTable *remotes; GMutex remotes_lock; OstreeRepoMode mode; diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index 90cde65139..d275a3d934 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -3195,6 +3195,97 @@ min_free_space_size_validate_and_convert (OstreeRepo *self, return TRUE; } +static int +compare_strings (gconstpointer a, gconstpointer b) +{ + const char **sa = (const char **)a; + const char **sb = (const char **)b; + + return strcmp (*sa, *sb); +} + +#define SYSCONF_CONFIGS SHORTENED_SYSCONFDIR "/ostree/config.d" + + +static gboolean +load_user_configs (OstreeRepo *self, + GCancellable *cancellable, + GError **error) +{ + g_clear_pointer (&self->user_config, g_key_file_unref); + g_autoptr(OstreeSysroot) sysroot_ref = (OstreeSysroot*)g_weak_ref_get (&self->sysroot); + + /* Only read from /etc/ostree/config.d if we are pointed at a deployment */ + if (sysroot_ref == NULL || sysroot_ref->is_physical) + return TRUE; + + g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; + gboolean exists; + if (!ot_dfd_iter_init_allow_noent (sysroot_ref->sysroot_fd, SYSCONF_CONFIGS, &dfd_iter, &exists, error)) + return FALSE; + /* Note early return */ + if (!exists) + return TRUE; + + g_autoptr(GPtrArray) configs = g_ptr_array_new_with_free_func (g_free); + + while (TRUE) + { + struct dirent *dent; + if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, cancellable, error)) + return FALSE; + if (dent == NULL) + break; + + /* match remotes.d semantics */ + if (dent->d_type != DT_REG || !g_str_has_suffix (dent->d_name, ".conf")) + continue; + + /* parse it just to validate it */ + g_autoptr(GKeyFile) k = g_key_file_new (); + g_autofree char *path = g_strdup_printf ("/proc/self/fd/%d/%s", dfd_iter.fd, dent->d_name); + if (!g_key_file_load_from_file (k, path, G_KEY_FILE_NONE, error)) + return glnx_prefix_error (error, "parsing config file %s", path); + + g_ptr_array_add (configs, g_strdup (dent->d_name)); + } + + if (configs->len == 0) + return TRUE; /* Note early return; no user configs. */ + + g_ptr_array_sort (configs, compare_strings); + + g_auto(GLnxTmpfile) user_keyfile = { 0, }; + if (!glnx_open_anonymous_tmpfile (O_RDWR | O_CLOEXEC, &user_keyfile, error)) + return glnx_prefix_error (error, "allocating tmpfile for user config"); + + for (guint i = 0; i < configs->len; i++) + { + const char *config = configs->pdata[i]; + + glnx_autofd int fd = -1; + if (!glnx_openat_rdonly (dfd_iter.fd, config, FALSE, &fd, error)) + return glnx_prefix_error (error, "opening %s/%s", SYSCONF_CONFIGS, config); + + if (glnx_regfile_copy_bytes (fd, user_keyfile.fd, -1) < 0) + return glnx_throw_errno_prefix (error, "copying %s/%s", SYSCONF_CONFIGS, config); + + /* make sure there's a newline */ + if (glnx_loop_write (user_keyfile.fd, "\n", 1) < 0) + return glnx_throw_errno_prefix (error, "writing newline to tmpfile"); + } + + self->user_config = g_key_file_new (); + + /* we want g_key_file_load_from_fd() but that's private */ + g_autofree char *tmpfile_path = g_strdup_printf ("/proc/self/fd/%d", user_keyfile.fd); + if (!g_key_file_load_from_file (self->user_config, tmpfile_path, G_KEY_FILE_NONE, error)) + return glnx_prefix_error (error, "parsing final config file"); + + return TRUE; +} + + static gboolean reload_core_config (OstreeRepo *self, GCancellable *cancellable, @@ -3442,6 +3533,12 @@ reload_core_config (OstreeRepo *self, self->repo_finders = g_steal_pointer (&configured_finders); } + if (ostree_repo_is_system (self)) + { + if (!load_user_configs (self, cancellable, error)) + return FALSE; + } + return TRUE; } diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index 456b0c041d..41aef83d61 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -2742,6 +2742,35 @@ lint_deployment_fs (OstreeSysroot *self, return TRUE; } +/* This is called when deployments are created, but also + * via `ostree admin create-toplevel-user-links`. */ +gboolean +_ostree_sysroot_create_toplevel_user_links (OstreeSysroot *self, + int deployment_dfd, + GCancellable *cancellable, + GError **error) +{ + OstreeRepo *repo = ostree_sysroot_repo (self); + g_autoptr(GHashTable) toplevel_links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + ot_keyfile_get_keys_in_hashtable (repo->config, toplevel_links, "toplevel-links"); + /* toplevel-links is supported in user configs; read after to support overriding */ + if (repo->user_config) + ot_keyfile_get_keys_in_hashtable (repo->user_config, toplevel_links, "toplevel-links"); + + GLNX_HASH_TABLE_FOREACH_KV (toplevel_links, const char *, dir, const char *, symlink) + { + /* sanity-check the user isn't trying to do silly things */ + if (g_str_equal (dir, ".") || g_str_equal (dir, "..") || strchr (dir, '/') != NULL) + return glnx_throw (error, "Invalid top-level dir: '%s'", dir); + + if (TEMP_FAILURE_RETRY (symlinkat (symlink, deployment_dfd, dir)) < 0) + return glnx_throw_errno_prefix (error, "symlinkat(/%s)", dir); + } + + return TRUE; +} + /* The first part of writing a deployment. This primarily means doing the * hardlink farm checkout, but we also compute some initial state. */ @@ -2793,6 +2822,9 @@ sysroot_initialize_deployment (OstreeSysroot *self, if (!lint_deployment_fs (self, new_deployment, deployment_dfd, cancellable, error)) return FALSE; + if (!_ostree_sysroot_create_toplevel_user_links (self, deployment_dfd, cancellable, error)) + return FALSE; + ot_transfer_out_value (out_new_deployment, &new_deployment); return TRUE; } diff --git a/src/libostree/ostree-sysroot-private.h b/src/libostree/ostree-sysroot-private.h index a49a406cf3..1b95d3ac64 100644 --- a/src/libostree/ostree-sysroot-private.h +++ b/src/libostree/ostree-sysroot-private.h @@ -187,4 +187,10 @@ gboolean _ostree_sysroot_cleanup_internal (OstreeSysroot *sysroot, GCancellable *cancellable, GError **error); +gboolean +_ostree_sysroot_create_toplevel_user_links (OstreeSysroot *self, + int deployment_dfd, + GCancellable *cancellable, + GError **error); + G_END_DECLS diff --git a/src/libotutil/ot-keyfile-utils.c b/src/libotutil/ot-keyfile-utils.c index de8abd2bea..b0ab2e8bc1 100644 --- a/src/libotutil/ot-keyfile-utils.c +++ b/src/libotutil/ot-keyfile-utils.c @@ -258,3 +258,25 @@ ot_keyfile_copy_group (GKeyFile *source_keyfile, out: return ret; } + +void +ot_keyfile_get_keys_in_hashtable (GKeyFile *keyfile, + GHashTable *table, + const char *group_name) +{ + g_assert (keyfile != NULL); + g_assert (table != NULL); + g_assert (group_name != NULL); + + gsize length; + g_auto(GStrv) keys = g_key_file_get_keys (keyfile, group_name, &length, NULL); + if (keys == NULL) + return; + + for (gsize i = 0; i < length; i++) + { + const char *key = keys[i]; + g_autofree char *val = g_key_file_get_value (keyfile, group_name, key, NULL); + g_hash_table_insert (table, g_strdup (key), g_strdup (val)); + } +} diff --git a/src/libotutil/ot-keyfile-utils.h b/src/libotutil/ot-keyfile-utils.h index 3b4f6560f7..713b74fb6a 100644 --- a/src/libotutil/ot-keyfile-utils.h +++ b/src/libotutil/ot-keyfile-utils.h @@ -72,4 +72,9 @@ ot_keyfile_copy_group (GKeyFile *source_keyfile, GKeyFile *target_keyfile, const char *group_name); +void +ot_keyfile_get_keys_in_hashtable (GKeyFile *keyfile, + GHashTable *table, + const char *group_name); + G_END_DECLS diff --git a/src/ostree/ot-admin-builtin-create-toplevel-user-links.c b/src/ostree/ot-admin-builtin-create-toplevel-user-links.c new file mode 100644 index 0000000000..143ff11bd8 --- /dev/null +++ b/src/ostree/ot-admin-builtin-create-toplevel-user-links.c @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2022 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2 of the licence or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "ot-main.h" +#include "ot-admin-builtins.h" +#include "ostree-cmd-private.h" + +static GOptionEntry options[] = { + { NULL } +}; + +gboolean +ot_admin_builtin_create_toplevel_user_links (int argc, char **argv, + OstreeCommandInvocation *invocation, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(OstreeSysroot) sysroot = NULL; + g_autoptr(GOptionContext) context = g_option_context_new (""); + if (!ostree_admin_option_context_parse (context, options, &argc, &argv, + OSTREE_ADMIN_BUILTIN_FLAG_SUPERUSER, + invocation, &sysroot, cancellable, error)) + return glnx_prefix_error (error, "parsing options"); + + OstreeDeployment *deployment = ostree_sysroot_get_booted_deployment (sysroot); + g_autofree char *deployment_path = ostree_sysroot_get_deployment_dirpath (sysroot, deployment); + + glnx_autofd int deployment_dfd = -1; + if (!glnx_opendirat (ostree_sysroot_get_fd (sysroot), deployment_path, TRUE, &deployment_dfd, error)) + return glnx_prefix_error (error, "open(%s)", deployment_path); + + if (!ostree_sysroot_deployment_set_mutable (sysroot, deployment, TRUE, cancellable, error)) + return glnx_prefix_error (error, "setting deployment mutable"); + + if (!ostree_cmd__private__()->ostree_create_toplevel_user_links (sysroot, deployment_dfd, cancellable, error)) + return glnx_prefix_error (error, "creating toplevel user links"); + + if (!ostree_sysroot_deployment_set_mutable (sysroot, deployment, FALSE, cancellable, error)) + return glnx_prefix_error (error, "setting deployment immutable"); + + return TRUE; +} diff --git a/src/ostree/ot-admin-builtins.h b/src/ostree/ot-admin-builtins.h index 8bac1c5625..a180521360 100644 --- a/src/ostree/ot-admin-builtins.h +++ b/src/ostree/ot-admin-builtins.h @@ -47,6 +47,7 @@ BUILTINPROTO(diff); BUILTINPROTO(switch); BUILTINPROTO(upgrade); BUILTINPROTO(kargs); +BUILTINPROTO(create_toplevel_user_links); #undef BUILTINPROTO diff --git a/src/ostree/ot-builtin-admin.c b/src/ostree/ot-builtin-admin.c index 503fb9a7be..29fecdd249 100644 --- a/src/ostree/ot-builtin-admin.c +++ b/src/ostree/ot-builtin-admin.c @@ -79,6 +79,9 @@ static OstreeCommand admin_subcommands[] = { { "kargs", OSTREE_BUILTIN_FLAG_NO_REPO, ot_admin_builtin_kargs, "Change kernel arguments" }, + { "create-toplevel-user-links", OSTREE_BUILTIN_FLAG_NO_REPO | OSTREE_BUILTIN_FLAG_HIDDEN, + ot_admin_builtin_create_toplevel_user_links, + "Create user-configured top-level symlinks" }, { NULL, 0, NULL, NULL } };