diff --git a/doc/treefile.md b/doc/treefile.md index b2efa1aa43..71bfaec83c 100644 --- a/doc/treefile.md +++ b/doc/treefile.md @@ -66,11 +66,12 @@ 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. + and `check-passwd` has a type other than file, copy the `/etc/passwd` (and + `/usr/lib/passwd`) files from the previous commit if they exist. If + check-passwd has the file type, then the data is preserved from that file to + `/usr/lib/passwd`. + This helps ensure consistent uid/gid allocations across builds. However, it + does mean that removed users will exist in the `passwd` database forever. * `check-passwd`: Object, optional: Checks to run against the new passwd file before accepting the tree. All the entries specified should exist (unless @@ -78,6 +79,8 @@ Treefile types: none (for no checking), previous (to check against the passwd file in the previous commit), file (to check against another passwd file), and data to specify the relevant passwd data in the json itself. + Note that if you choose file, and preserve-passwd is true then the data will + be copied from the referenced file and not the previous commit. Example: `check-passwd: { "type": "none" }` Example: `check-passwd: { "type": "previous" }` @@ -91,6 +94,8 @@ Treefile types: none (for no checking), previous (to check against the group file in the previous commit), file (to check against another group file), and data to specify the relevant group data in the json itself. + Note that if you choose file, and preserve-passwd is true then the data will + be copied from the referenced file and not the previous commit. Example: `check-groups: { "type": "none" }` Example: `check-groups: { "type": "previous" }` diff --git a/src/rpmostree-compose-builtin-tree.c b/src/rpmostree-compose-builtin-tree.c index e2aeef1529..fc1220f4b4 100644 --- a/src/rpmostree-compose-builtin-tree.c +++ b/src/rpmostree-compose-builtin-tree.c @@ -786,6 +786,7 @@ rpmostree_compose_builtin_tree (int argc, gs_unref_ptrarray GPtrArray *bootstrap_packages = NULL; gs_unref_ptrarray GPtrArray *packages = NULL; gs_unref_object GFile *treefile_path = NULL; + gs_unref_object GFile *treefile_dirpath = NULL; gs_unref_object GFile *repo_path = NULL; gs_unref_object JsonParser *treefile_parser = NULL; gs_unref_variant_builder GVariantBuilder *metadata_builder = @@ -1022,7 +1023,8 @@ rpmostree_compose_builtin_tree (int argc, self->serialized_treefile = g_bytes_new_take (treefile_buf, len); } - if (previous_root != NULL) + treefile_dirpath = g_file_get_parent (treefile_path); + if (TRUE) { gboolean generate_from_previous = TRUE; @@ -1034,7 +1036,9 @@ rpmostree_compose_builtin_tree (int argc, if (generate_from_previous) { - if (!rpmostree_generate_passwd_from_previous (repo, yumroot, previous_root, + if (!rpmostree_generate_passwd_from_previous (repo, yumroot, + treefile_dirpath, + previous_root, treefile, cancellable, error)) goto out; } @@ -1076,11 +1080,11 @@ rpmostree_compose_builtin_tree (int argc, if (!rpmostree_prepare_rootfs_for_commit (yumroot, treefile, cancellable, error)) goto out; - if (!rpmostree_check_passwd (repo, yumroot, treefile_path, treefile, + if (!rpmostree_check_passwd (repo, yumroot, treefile_dirpath, treefile, cancellable, error)) goto out; - if (!rpmostree_check_groups (repo, yumroot, treefile_path, treefile, + if (!rpmostree_check_groups (repo, yumroot, treefile_dirpath, treefile, cancellable, error)) goto out; diff --git a/src/rpmostree-passwd-util.c b/src/rpmostree-passwd-util.c index 56896ebb8b..129bb0f9a3 100644 --- a/src/rpmostree-passwd-util.c +++ b/src/rpmostree-passwd-util.c @@ -40,6 +40,10 @@ #include "libgsystem.h" +GS_DEFINE_CLEANUP_FUNCTION0(FILE*, _cleanup_stdio_file, fclose); +#define _cleanup_stdio_file_ __attribute__((cleanup(_cleanup_stdio_file))) + + static gboolean ptrarray_contains_str (GPtrArray *haystack, const char *needle) { @@ -184,7 +188,7 @@ static GPtrArray * data2passwdents (const char *data) { struct passwd *ent = NULL; - FILE *mf = NULL; + _cleanup_stdio_file_ FILE *mf = NULL; GPtrArray *ret = g_ptr_array_new_with_free_func (conv_passwd_ent_free); mf = fmemopen ((void *)data, strlen (data), "r"); @@ -232,7 +236,7 @@ static GPtrArray * data2groupents (const char *data) { struct group *ent = NULL; - FILE *mf = NULL; + _cleanup_stdio_file_ FILE *mf = NULL; GPtrArray *ret = g_ptr_array_new_with_free_func (conv_group_ent_free); mf = fmemopen ((void *)data, strlen (data), "r"); @@ -718,9 +722,9 @@ rpmostree_passwd_migrate_except_root (GFile *rootfs, gs_free char *src_path = g_strconcat (gs_file_get_path_cached (rootfs), "/etc/", name, NULL); gs_free char *etctmp_path = g_strconcat (gs_file_get_path_cached (rootfs), "/etc/", name, ".tmp", NULL); gs_free char *usrdest_path = g_strconcat (gs_file_get_path_cached (rootfs), "/usr/lib/", name, NULL); - FILE *src_stream = NULL; - FILE *etcdest_stream = NULL; - FILE *usrdest_stream = NULL; + _cleanup_stdio_file_ FILE *src_stream = NULL; + _cleanup_stdio_file_ FILE *etcdest_stream = NULL; + _cleanup_stdio_file_ FILE *usrdest_stream = NULL; src_stream = gfopen (src_path, "r", cancellable, error); if (!src_stream) @@ -820,9 +824,121 @@ rpmostree_passwd_migrate_except_root (GFile *rootfs, ret = TRUE; out: - if (src_stream) (void) fclose (src_stream); - if (etcdest_stream) (void) fclose (etcdest_stream); - if (usrdest_stream) (void) fclose (usrdest_stream); + return ret; +} + +static FILE * +target_etc_filename (GFile *yumroot, + const char *filename, + GCancellable *cancellable, + GError **error) +{ + FILE *ret = NULL; + gs_free char *etc_subpath = g_strconcat ("etc/", filename, NULL); + gs_free char *target_etc = + g_build_filename (gs_file_get_path_cached (yumroot), etc_subpath, NULL); + + ret = gfopen (target_etc, "w", cancellable, error); + if (!ret) + goto out; + + out: + return ret; +} + +static gboolean +_rpmostree_gfile2stdio (GFile *source, + char **storage_buf, + FILE **ret_src_stream, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + gsize len; + FILE *src_stream = NULL; + + /* We read the file into memory using Gio (which talks + * to libostree), then memopen it, which works with libc. + */ + if (!g_file_load_contents (source, cancellable, + storage_buf, &len, NULL, error)) + goto out; + + if (len == 0) + goto done; + + src_stream = fmemopen (*storage_buf, len, "r"); + if (!src_stream) + { + gs_set_error_from_errno (error, errno); + goto out; + } + + done: + ret = TRUE; + out: + *ret_src_stream = src_stream; + return ret; +} + + +static gboolean +concat_entries (FILE *src_stream, + FILE *dest_stream, + RpmOstreePasswdMigrateKind kind, + GHashTable *seen_names, + GError **error) +{ + gboolean ret = FALSE; + + errno = 0; + while (TRUE) + { + struct passwd *pw = NULL; + struct group *gr = NULL; + int r; + const char *name; + + if (kind == RPM_OSTREE_PASSWD_MIGRATE_PASSWD) + pw = fgetpwent (src_stream); + else + gr = fgetgrent (src_stream); + + if (!(pw || gr)) + { + if (errno != 0 && errno != ENOENT) + { + _rpmostree_set_prefix_error_from_errno (error, errno, "fgetpwent: "); + goto out; + } + else + break; + } + + if (pw) + name = pw->pw_name; + else + name = gr->gr_name; + + /* Deduplicate */ + if (g_hash_table_lookup (seen_names, name)) + continue; + g_hash_table_add (seen_names, g_strdup (name)); + + if (pw) + r = putpwent (pw, dest_stream); + else + r = putgrent (gr, dest_stream); + + if (r == -1) + { + _rpmostree_set_prefix_error_from_errno (error, errno, "putpwent: "); + goto out; + } + } + + ret = TRUE; + out: return ret; } @@ -835,10 +951,8 @@ concat_passwd_file (GFile *yumroot, { gboolean ret = FALSE; const char *filename = kind == RPM_OSTREE_PASSWD_MIGRATE_PASSWD ? "passwd" : "group"; - gs_free char *etc_subpath = g_strconcat ("etc/", filename, NULL); gs_free char *usretc_subpath = g_strconcat ("usr/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 *orig_etc_content = g_file_resolve_relative_path (previous_commit, usretc_subpath); gs_unref_object GFile *orig_usrlib_content = @@ -849,18 +963,8 @@ concat_passwd_file (GFile *yumroot, gs_free char *contents = NULL; GFile *sources[] = { orig_etc_content, orig_usrlib_content }; guint i; - gsize len; gboolean have_etc, have_usr; - FILE *src_stream = NULL; - FILE *dest_stream = NULL; - - /* Create /etc in the target root; FIXME - should ensure we're using - * the right permissions from the filesystem RPM. Doing this right - * is really hard because filesystem depends on setup which installs - * the files... - */ - if (!gs_file_ensure_directory (yumroot_etc, TRUE, cancellable, error)) - goto out; + _cleanup_stdio_file_ FILE *dest_stream = NULL; have_etc = g_file_query_exists (orig_etc_content, NULL); have_usr = g_file_query_exists (orig_usrlib_content, NULL); @@ -875,111 +979,175 @@ concat_passwd_file (GFile *yumroot, goto out; } - { gs_free char *target_etc = - g_build_filename (gs_file_get_path_cached (yumroot), etc_subpath, NULL); - dest_stream = gfopen (target_etc, "w", cancellable, error); - if (!dest_stream) + if (!(dest_stream = target_etc_filename (yumroot, filename, + cancellable, error))) goto out; - } for (i = 0; i < G_N_ELEMENTS (sources); i++) { GFile *source = sources[i]; + _cleanup_stdio_file_ FILE *src_stream = NULL; - /* We read the file into memory using Gio (which talks - * to libostree), then memopen it, which works with libc. - */ - if (!g_file_load_contents (source, cancellable, - &contents, &len, NULL, error)) + if (!_rpmostree_gfile2stdio (source, &contents, &src_stream, + cancellable, error)) goto out; - if (len == 0) - continue; - - if (src_stream) (void) fclose (src_stream); - src_stream = fmemopen (contents, len, "r"); if (!src_stream) - { - gs_set_error_from_errno (error, errno); - goto out; - } - - errno = 0; - while (TRUE) - { - struct passwd *pw = NULL; - struct group *gr = NULL; - int r; - const char *name; - - if (kind == RPM_OSTREE_PASSWD_MIGRATE_PASSWD) - pw = fgetpwent (src_stream); - else - gr = fgetgrent (src_stream); + continue; - if (!(pw || gr)) - { - if (errno != 0 && errno != ENOENT) - { - _rpmostree_set_prefix_error_from_errno (error, errno, "fgetpwent: "); - goto out; - } - else - break; - } + if (!concat_entries (src_stream, dest_stream, kind, + seen_names, error)) + goto out; + } - if (pw) - name = pw->pw_name; - else - name = gr->gr_name; + if (!gfflush (dest_stream, cancellable, error)) + goto out; - /* Deduplicate */ - if (g_hash_table_lookup (seen_names, name)) - continue; - g_hash_table_add (seen_names, g_strdup (name)); + ret = TRUE; + out: + return ret; +} - if (pw) - r = putpwent (pw, dest_stream); - else - r = putgrent (gr, dest_stream); - if (r == -1) - { - _rpmostree_set_prefix_error_from_errno (error, errno, "putpwent: "); - goto out; - } - } - } +static gboolean +_data_from_json (GFile *yumroot, + GFile *treefile_dirpath, + JsonObject *treedata, + RpmOstreePasswdMigrateKind kind, + gboolean *out_found, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + const gboolean passwd = kind == RPM_OSTREE_PASSWD_MIGRATE_PASSWD; + const char *json_conf_name = passwd ? "check-passwd" : "check-groups"; + const char *filebasename = passwd ? "passwd" : "group"; + JsonObject *chk = NULL; + const char *chk_type = NULL; + const char *filename = NULL; + gs_unref_object GFile *source = NULL; + gs_free char *contents = NULL; + gs_unref_hashtable GHashTable *seen_names = + g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free); + _cleanup_stdio_file_ FILE *src_stream = NULL; + _cleanup_stdio_file_ FILE *dest_stream = NULL; - if (!gfflush (dest_stream, cancellable, error)) + *out_found = FALSE; + if (!json_object_has_member (treedata, json_conf_name)) + return TRUE; + + chk = json_object_get_object_member (treedata,json_conf_name); + if (!chk) + return TRUE; + + chk_type = _rpmostree_jsonutil_object_require_string_member (chk, "type", + error); + if (!chk_type) + goto out; + + if (!g_str_equal (chk_type, "file")) + return TRUE; + + filename = _rpmostree_jsonutil_object_require_string_member (chk, + "filename", + error); + if (!filename) + goto out; + + source = g_file_resolve_relative_path (treefile_dirpath, filename); + if (!source) + goto out; + + /* migrate the check data from the specified file to /etc */ + if (!_rpmostree_gfile2stdio (source, &contents, &src_stream, + cancellable, error)) + goto out; + + if (!src_stream) + return TRUE; + + /* no matter what we've used the data now */ + *out_found = TRUE; + + if (!(dest_stream = target_etc_filename (yumroot, filebasename, + cancellable, error))) + goto out; + + if (!concat_entries (src_stream, dest_stream, kind, seen_names, error)) goto out; ret = TRUE; out: - if (src_stream) (void) fclose (src_stream); - if (dest_stream) (void) fclose (dest_stream); return ret; } gboolean rpmostree_generate_passwd_from_previous (OstreeRepo *repo, GFile *yumroot, + GFile *treefile_dirpath, GFile *previous_root, + JsonObject *treedata, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; - gs_unref_object GFile *out = NULL; + gboolean found_passwd_data = FALSE; + gboolean found_groups_data = FALSE; + gboolean perform_migrate = FALSE; + gs_unref_object GFile *yumroot_etc = g_file_resolve_relative_path (yumroot, "etc"); - if (!concat_passwd_file (yumroot, previous_root, - RPM_OSTREE_PASSWD_MIGRATE_PASSWD, - cancellable, error)) + /* Create /etc in the target root; FIXME - should ensure we're using + * the right permissions from the filesystem RPM. Doing this right + * is really hard because filesystem depends on setup which installs + * the files... + */ + if (!gs_file_ensure_directory (yumroot_etc, TRUE, cancellable, error)) + goto out; + + if (!_data_from_json (yumroot, treefile_dirpath, + treedata, RPM_OSTREE_PASSWD_MIGRATE_PASSWD, + &found_passwd_data, cancellable, error)) + goto out; + perform_migrate = !found_passwd_data; + + if (!previous_root) + perform_migrate = FALSE; + + if (perform_migrate && !concat_passwd_file (yumroot, previous_root, + RPM_OSTREE_PASSWD_MIGRATE_PASSWD, + cancellable, error)) goto out; - if (!concat_passwd_file (yumroot, previous_root, - RPM_OSTREE_PASSWD_MIGRATE_GROUP, - cancellable, error)) + if (!_data_from_json (yumroot, treefile_dirpath, + treedata, RPM_OSTREE_PASSWD_MIGRATE_GROUP, + &found_groups_data, cancellable, error)) goto out; + perform_migrate = !found_groups_data; + + if (!previous_root) + perform_migrate = FALSE; + + if (perform_migrate && !concat_passwd_file (yumroot, previous_root, + RPM_OSTREE_PASSWD_MIGRATE_GROUP, + cancellable, error)) + goto out; + + // We should error if we are getting passwd data from JSON and group from + // previous commit, or vice versa, as that'll confuse everyone when it goes + // wrong. + if ( found_passwd_data && !found_groups_data) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Configured to migrate passwd data from JSON, and group data from commit"); + goto out; + } + if (!found_passwd_data && found_groups_data) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Configured to migrate passwd data from commit, and group data from JSON"); + goto out; + } + ret = TRUE; out: return ret; diff --git a/src/rpmostree-passwd-util.h b/src/rpmostree-passwd-util.h index 5469024a7f..5f2b33fcb1 100644 --- a/src/rpmostree-passwd-util.h +++ b/src/rpmostree-passwd-util.h @@ -55,6 +55,8 @@ rpmostree_passwd_migrate_except_root (GFile *rootfs, gboolean rpmostree_generate_passwd_from_previous (OstreeRepo *repo, GFile *yumroot, + GFile *treefile_dirpath, GFile *previous_root, + JsonObject *treedata, GCancellable *cancellable, GError **error);