From f9e9c0664897dfe7331a009fe9b7420eb4a2b48c Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 23 Dec 2014 16:28:53 -0500 Subject: [PATCH] compose: Support "preserve-passwd" option (enabled by default) The checking code from #56 landed, and started triggering for me on the `dockerroot` user. It's nice to know it works. Then the issue is... "what now"? It turns out in the case of `dockerroot` it's actually unused, so we could fix this by deleting it. But in general we need to support dynamic uids/gids/. And we can't yet take a hard dep on #49. So this patch changes things so we take a copy of the passwd/group data from the previous commit. Any users subsequently added in the *new* commit will be additive. Closes: https://github.com/projectatomic/rpm-ostree/issues/78 --- doc/treefile.md | 7 ++ src/rpmostree-compose-builtin-tree.c | 17 +++++ src/rpmostree-json-parsing.c | 25 +++++++ src/rpmostree-json-parsing.h | 6 ++ src/rpmostree-passwd-util.c | 103 +++++++++++++++++++++++++++ src/rpmostree-passwd-util.h | 7 ++ 6 files changed, 165 insertions(+) diff --git a/doc/treefile.md b/doc/treefile.md index be1e74de1a..6e21aa18f8 100644 --- a/doc/treefile.md +++ b/doc/treefile.md @@ -61,6 +61,13 @@ Treefile Note this does not alter the RPM database, so `rpm -V` will complain. + * `preserve-passwd`: boolean, optional: Defaults to `true`. If enabled, + copy the `/etc/passwd` (and `/usr/lib/passwd`) files from the previous commit + if they exist. This helps ensure consistent uid/gid allocations across + builds. However, it does mean that removed users will exist in the `passwd` + database forever. It also does not help clients switch between unrelated + trees. + * `check-passwd`: Object, optional: Checks to run against the new passwd file before accepting the tree. All the entries specified should exist (unless ignored) and have the same values or the compose will fail. There are four diff --git a/src/rpmostree-compose-builtin-tree.c b/src/rpmostree-compose-builtin-tree.c index ef0d4cbe84..9f8540529d 100644 --- a/src/rpmostree-compose-builtin-tree.c +++ b/src/rpmostree-compose-builtin-tree.c @@ -940,6 +940,23 @@ rpmostree_compose_builtin_tree (int argc, self->serialized_treefile = g_bytes_new_take (treefile_buf, len); } + { + gboolean generate_from_previous = TRUE; + + if (!_rpmostree_jsonutil_object_get_optional_boolean_member (treefile, + "preserve-passwd", + &generate_from_previous, + error)) + goto out; + + if (generate_from_previous) + { + if (!rpmostree_generate_passwd_from_previous (repo, yumroot, ref, + cancellable, error)) + goto out; + } + } + if (!yuminstall (self, treefile, yumroot, (char**)packages->pdata, cancellable, error)) diff --git a/src/rpmostree-json-parsing.c b/src/rpmostree-json-parsing.c index 5e7b727e09..c6d5a75d30 100644 --- a/src/rpmostree-json-parsing.c +++ b/src/rpmostree-json-parsing.c @@ -133,6 +133,31 @@ _rpmostree_jsonutil_object_require_int_member (JsonObject *object, return TRUE; } +gboolean +_rpmostree_jsonutil_object_get_optional_boolean_member (JsonObject *object, + const char *member_name, + gboolean *out_value, + GError **error) +{ + gboolean ret = FALSE; + JsonNode *node = json_object_get_member (object, member_name); + + if (node != NULL) + { + if (json_node_get_value_type (node) != G_TYPE_BOOLEAN) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Member '%s' is not a boolean", member_name); + goto out; + } + *out_value = json_node_get_boolean (node); + } + + ret = TRUE; + out: + return ret; +} + const char * _rpmostree_jsonutil_array_require_string_element (JsonArray *array, guint i, diff --git a/src/rpmostree-json-parsing.h b/src/rpmostree-json-parsing.h index bced842259..0d187e1ec6 100644 --- a/src/rpmostree-json-parsing.h +++ b/src/rpmostree-json-parsing.h @@ -47,6 +47,12 @@ _rpmostree_jsonutil_object_require_int_member (JsonObject *object, gint64 *out_val, GError **error); +gboolean +_rpmostree_jsonutil_object_get_optional_boolean_member (JsonObject *object, + const char *member_name, + gboolean *out_value, + GError **error); + const char * _rpmostree_jsonutil_array_require_string_element (JsonArray *array, guint i, diff --git a/src/rpmostree-passwd-util.c b/src/rpmostree-passwd-util.c index 182268f617..aadc56f12e 100644 --- a/src/rpmostree-passwd-util.c +++ b/src/rpmostree-passwd-util.c @@ -667,3 +667,106 @@ rpmostree_check_groups (OstreeRepo *repo, return rpmostree_check_passwd_groups (TRUE, repo, yumroot, treefile_dirpath, treedata, cancellable, error); } + +static gboolean +concat_passwd_file (GFile *yumroot, + GFile *previous_commit, + const char *filename, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + gs_free char *etc_subpath = g_strconcat ("etc/", filename, NULL); + gs_free char *usrlib_subpath = g_strconcat ("usr/lib/", filename, NULL); + gs_unref_object GFile *yumroot_etc = g_file_resolve_relative_path (yumroot, "etc"); + gs_unref_object GFile *yumroot_dest = g_file_resolve_relative_path (yumroot, etc_subpath); + gs_unref_object GFile *orig_etc_content = g_file_resolve_relative_path (previous_commit, etc_subpath); + gs_unref_object GFile *orig_usrlib_content = g_file_resolve_relative_path (previous_commit, usrlib_subpath); + gs_unref_object GFileOutputStream *out = NULL; + gboolean have_etc, have_usr; + + if (!gs_file_ensure_directory (yumroot_etc, TRUE, cancellable, error)) + goto out; + + have_etc = g_file_query_exists (orig_etc_content, NULL); + have_usr = g_file_query_exists (orig_usrlib_content, NULL); + + if (!(have_etc || have_usr)) + { + ret = TRUE; + goto out; + } + + out = g_file_replace (yumroot_dest, NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION, + cancellable, error); + if (!out) + goto out; + + if (have_etc) + { + gs_unref_object GInputStream *src = + (GInputStream*)g_file_read (orig_etc_content, cancellable, error); + if (g_output_stream_splice ((GOutputStream*)out, src, + G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE, + cancellable, error ) < 0) + goto out; + } + + if (have_usr) + { + gs_unref_object GInputStream *src = + (GInputStream*)g_file_read (orig_usrlib_content, cancellable, error); + if (g_output_stream_splice ((GOutputStream*)out, src, + G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE, + cancellable, error ) < 0) + goto out; + } + + if (!g_output_stream_flush ((GOutputStream*)out, cancellable, error)) + goto out; + + ret = TRUE; + out: + return ret; +} + + +gboolean +rpmostree_generate_passwd_from_previous (OstreeRepo *repo, + GFile *yumroot, + const char *ref, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + GError *temp_error = NULL; + gs_unref_object GFile *previous_root = NULL; + gs_unref_object GFile *yumroot_etc_group = g_file_resolve_relative_path (yumroot, "etc/group"); + gs_unref_object GFile *out = NULL; + + if (!ostree_repo_read_commit (repo, ref, &previous_root, NULL, NULL, &temp_error)) + { + if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + { + g_clear_error (&temp_error); + ret = TRUE; + } + else + { + g_propagate_error (error, temp_error); + } + goto out; + } + + if (!concat_passwd_file (yumroot, previous_root, "passwd", + cancellable, error)) + goto out; + + if (!concat_passwd_file (yumroot, previous_root, "group", + cancellable, error)) + goto out; + + ret = TRUE; + out: + return ret; +} diff --git a/src/rpmostree-passwd-util.h b/src/rpmostree-passwd-util.h index 67a5c7f3a1..d688639109 100644 --- a/src/rpmostree-passwd-util.h +++ b/src/rpmostree-passwd-util.h @@ -39,3 +39,10 @@ rpmostree_check_groups (OstreeRepo *repo, JsonObject *treedata, GCancellable *cancellable, GError **error); + +gboolean +rpmostree_generate_passwd_from_previous (OstreeRepo *repo, + GFile *yumroot, + const char *ref, + GCancellable *cancellable, + GError **error);