Skip to content

Commit

Permalink
lib/sysroot: Support top-level symlinks
Browse files Browse the repository at this point in the history
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: coreos/rpm-ostree#337
  • Loading branch information
jlebon committed Jul 29, 2022
1 parent a406f60 commit 746e342
Show file tree
Hide file tree
Showing 10 changed files with 133 additions and 0 deletions.
1 change: 1 addition & 0 deletions Makefile-ostree.am
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
1 change: 1 addition & 0 deletions src/libostree/ostree-cmd-private.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/libostree/ostree-cmd-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
32 changes: 32 additions & 0 deletions src/libostree/ostree-sysroot-deploy.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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;
}
Expand Down
6 changes: 6 additions & 0 deletions src/libostree/ostree-sysroot-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
22 changes: 22 additions & 0 deletions src/libotutil/ot-keyfile-utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
5 changes: 5 additions & 0 deletions src/libotutil/ot-keyfile-utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
61 changes: 61 additions & 0 deletions src/ostree/ot-admin-builtin-create-toplevel-user-links.c
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*/

#include "config.h"

#include <string.h>
#include <glib-unix.h>

#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;
}
1 change: 1 addition & 0 deletions src/ostree/ot-admin-builtins.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ BUILTINPROTO(diff);
BUILTINPROTO(switch);
BUILTINPROTO(upgrade);
BUILTINPROTO(kargs);
BUILTINPROTO(create_toplevel_user_links);

#undef BUILTINPROTO

Expand Down
3 changes: 3 additions & 0 deletions src/ostree/ot-builtin-admin.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
};

Expand Down

0 comments on commit 746e342

Please sign in to comment.