From 746e342a4251fb0ab820ca6e75655efbe3ffec24 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Fri, 29 Jul 2022 18:16:53 -0400 Subject: [PATCH] lib/sysroot: Support top-level symlinks A semi-common topic that comes up with libostree is the lack of support for top-level entries. The main reason for this is that OSTree cycles deployments all the time and so we don't want users to store data there. But we do want to be nice to e.g. applications which expect a specific top-level path to exist or users who are used to mounting things at the root. Add support for user-configurable symlinks provided via the new user configs. The configuration looks like this: ``` [toplevel-links] foobar=/var/foobar ``` OSTree will create the symlinks everytime a new deployment is created. Add a hidden `ostree admin create-toplevel-user-links` command which can be used by provisioning code to create the symlinks on first boot so that it takes effect on the pre-existing deployment. I initially also supported configuring empty immutable directories (which would only be useful as mountpoints), but this becomes much harder to support in a provisioning flow via Ignition. We can still support it in libostree if wanted, and just not expose it in Ignition-based downstreams. The nice thing with symlinks is that it matches all the other default symlinks pointing into `/var` to reinforce that all user data must remain there. Closes: https://github.com/coreos/rpm-ostree/issues/337 --- Makefile-ostree.am | 1 + src/libostree/ostree-cmd-private.c | 1 + src/libostree/ostree-cmd-private.h | 1 + src/libostree/ostree-sysroot-deploy.c | 32 ++++++++++ src/libostree/ostree-sysroot-private.h | 6 ++ src/libotutil/ot-keyfile-utils.c | 22 +++++++ src/libotutil/ot-keyfile-utils.h | 5 ++ ...admin-builtin-create-toplevel-user-links.c | 61 +++++++++++++++++++ src/ostree/ot-admin-builtins.h | 1 + src/ostree/ot-builtin-admin.c | 3 + 10 files changed, 133 insertions(+) create mode 100644 src/ostree/ot-admin-builtin-create-toplevel-user-links.c 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-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 } };