From a406f60ba1eacc4e051d3e33e47c7081f0ba5ce0 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Fri, 29 Jul 2022 18:00:57 -0400 Subject: [PATCH] lib/repo: Support `/etc/ostree/config.d/` The sysroot handling of libostree supports multiple knobs specified in the system repo keyfile. E.g. `sysroot.bootloader`, `sysroot.readonly`, etc... One issue is that all the knobs so far have been "vendor-oriented". I.e. they're not necessarily something we expect users to directly modify. I'd like to add more sysroot configurable knobs which we *do* expect users to modify, but I don't want them to modify the main config directly and mix with knobs they really shouldn't touch. It's also not convenient to modify in a provisioning flow. Add a new concept of a "user config" which is a config obtained by merging all the configs in `/etc/ostree/config.d`. Now, there's no official API for merging `GKeyFile` objects, but the parser has the right semantics if we simply concatenate all the configs and parse the resulting final config: keys are merged into the same group and later keys override earlier keys. Appending to a string list is not supported, but we can design our knobs so that this is not an issue. We don't want user configs to be able to override e.g. `core.mode`. We only want to support specific keys in those configs (but for consistency, also in the primary config). So keep it as a separate `user_config` object. --- src/libostree/ostree-repo-private.h | 1 + src/libostree/ostree-repo.c | 97 +++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) 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; }