From 827e711eb74135f075f02f5c44685e4e35c1a8b1 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Thu, 10 Jul 2014 18:28:22 -0400 Subject: [PATCH] compose: Migrate content of /etc/{passwd,group} to /usr/lib more sanely I had an epiphany today while working on https://bugzilla.redhat.com/show_bug.cgi?id=1098304 - I realized that I can just do an install, and then copy over everything except the root entries from /etc/passwd into /usr/lib/passwd. No need for a patched shadow-utils. No need to modify the /etc/nsswitch.conf before doing the install root. It totally works. I have no idea why I originally overcomplicated this. The thing that sucks a bit about this code is that I have to drop to the FILE * APIs so that I can use the glibc APIs for processing group/shadow. Also, the way I deduplicated the code paths for processing passwd/group is crappy, but I think it's better than duplicating them (as systemd-sysusers does). The good: We don't need a two-step RPM transaction, we don't need a patch for shadow-utils, it's just saner The bad: Code is not the most beautiful? Not really bad. The ugly: I didn't think of this in the first place and spent months beating my head against the wall of shadow-utils... --- src/rpmostree-compose-builtin-tree.c | 83 +---------- src/rpmostree-postprocess.c | 203 +++++++++++++++++++++++++++ 2 files changed, 205 insertions(+), 81 deletions(-) diff --git a/src/rpmostree-compose-builtin-tree.c b/src/rpmostree-compose-builtin-tree.c index 61b7078a00..a8643d73e8 100644 --- a/src/rpmostree-compose-builtin-tree.c +++ b/src/rpmostree-compose-builtin-tree.c @@ -158,50 +158,6 @@ append_string_array_to (JsonObject *object, return TRUE; } -static gboolean -replace_nsswitch (GFile *target_usretc, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - gs_unref_object GFile *nsswitch_conf = - g_file_get_child (target_usretc, "nsswitch.conf"); - gs_free char *nsswitch_contents = NULL; - gs_free char *new_nsswitch_contents = NULL; - - static gsize regex_initialized; - static GRegex *passwd_regex; - - if (g_once_init_enter (®ex_initialized)) - { - passwd_regex = g_regex_new ("^(passwd|group):\\s+files(.*)$", - G_REGEX_MULTILINE, 0, NULL); - g_assert (passwd_regex); - g_once_init_leave (®ex_initialized, 1); - } - - nsswitch_contents = gs_file_load_contents_utf8 (nsswitch_conf, cancellable, error); - if (!nsswitch_contents) - goto out; - - new_nsswitch_contents = g_regex_replace (passwd_regex, - nsswitch_contents, -1, 0, - "\\1: files altfiles\\2", - 0, error); - if (!new_nsswitch_contents) - goto out; - - if (!g_file_replace_contents (nsswitch_conf, new_nsswitch_contents, - strlen (new_nsswitch_contents), - NULL, FALSE, 0, NULL, - cancellable, error)) - goto out; - - ret = TRUE; - out: - return ret; -} - typedef struct { GSSubprocess *process; GFile *reposdir_path; @@ -452,8 +408,6 @@ yum_context_new (RpmOstreeTreeComposeContext *self, duped_environ = g_environ_setenv (duped_environ, "OSTREE_KERNEL_INSTALL_NOOP", "1", TRUE); /* See fedora's kernel.spec */ duped_environ = g_environ_setenv (duped_environ, "HARDLINK", "no", TRUE); - /* See https://bugzilla.redhat.com/show_bug.cgi?id=1098304 */ - duped_environ = g_environ_setenv (duped_environ, "SHADOW_USE_USRLIB", "1", TRUE); gs_subprocess_context_set_environment (context, duped_environ); } @@ -990,50 +944,17 @@ rpmostree_compose_builtin_tree (int argc, bootstrap_packages = g_ptr_array_new (); packages = g_ptr_array_new (); - if (!append_string_array_to (treefile, "bootstrap_packages", bootstrap_packages, error)) + if (!append_string_array_to (treefile, "bootstrap_packages", packages, error)) goto out; - g_ptr_array_add (bootstrap_packages, NULL); - if (!append_string_array_to (treefile, "packages", packages, error)) goto out; g_ptr_array_add (packages, NULL); - - /* Ensure we have enough to modify NSS */ if (!yuminstall (self, treefile, yumroot, workdir, - (char**)bootstrap_packages->pdata, + (char**)packages->pdata, cancellable, error)) goto out; - /* Prepare NSS configuration; this needs to be done - before any invocations of "useradd" in %post */ - - { - gs_unref_object GFile *yumroot_passwd = - g_file_resolve_relative_path (yumroot, "usr/lib/passwd"); - gs_unref_object GFile *yumroot_group = - g_file_resolve_relative_path (yumroot, "usr/lib/group"); - gs_unref_object GFile *yumroot_etc = - g_file_resolve_relative_path (yumroot, "etc"); - - if (!g_file_replace_contents (yumroot_passwd, "", 0, NULL, FALSE, 0, - NULL, cancellable, error)) - goto out; - if (!g_file_replace_contents (yumroot_group, "", 0, NULL, FALSE, 0, - NULL, cancellable, error)) - goto out; - - if (!replace_nsswitch (yumroot_etc, cancellable, error)) - goto out; - } - - { - if (!yuminstall (self, treefile, yumroot, workdir, - (char**)packages->pdata, - cancellable, error)) - goto out; - } - cachekey = g_strconcat ("treecompose/", ref, NULL); if (!cachedir_lookup_string (cachedir, cachekey, &cached_compose_checksum, diff --git a/src/rpmostree-postprocess.c b/src/rpmostree-postprocess.c index be40ff7a6e..3c65126540 100644 --- a/src/rpmostree-postprocess.c +++ b/src/rpmostree-postprocess.c @@ -24,8 +24,11 @@ #include #include +#include #include #include +#include +#include #include #include @@ -534,6 +537,190 @@ workaround_selinux_cross_labeling (GFile *rootfs, return ret; } +static FILE * +gfopen (const char *path, + const char *mode, + GCancellable *cancellable, + GError **error) +{ + FILE *ret = NULL; + + ret = fopen (path, mode); + if (!ret) + { + int errsv = errno; + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "fopen(%s): %s", path, g_strerror (errsv)); + return NULL; + } + return ret; +} + +static gboolean +gfflush (FILE *f, + GCancellable *cancellable, + GError **error) +{ + if (fflush (f) != 0) + { + int errsv = errno; + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "fflush: %s", g_strerror (errsv)); + return FALSE; + } + return TRUE; +} + +typedef enum { + MIGRATE_PASSWD, + MIGRATE_GROUP +} MigrateKind; + +/* + * This function is taking the /etc/passwd generated in the install + * root, and splitting it into two streams: a new /etc/passwd that + * just contains the root entry, and /usr/lib/passwd which contains + * everything else. + * + * The implementation is kind of horrible because I wanted to avoid + * duplicating the user/group code. + */ +static gboolean +migrate_passwd_file_except_root (GFile *rootfs, + MigrateKind kind, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + const char *name = kind == MIGRATE_PASSWD ? "passwd" : "group"; + 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; + + src_stream = gfopen (src_path, "r", cancellable, error); + if (!src_stream) + goto out; + + etcdest_stream = gfopen (etctmp_path, "w", cancellable, error); + if (!etcdest_stream) + goto out; + + usrdest_stream = gfopen (usrdest_path, "a", cancellable, error); + if (!usrdest_stream) + goto out; + + errno = 0; + while (TRUE) + { + struct passwd *pw = NULL; + struct group *gr = NULL; + FILE *deststream; + int r; + + if (kind == MIGRATE_PASSWD) + pw = fgetpwent (src_stream); + else + gr = fgetgrent (src_stream); + + if (!(pw || gr)) + { + if (errno != 0 && errno != ENOENT) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "fgetpwent: %s", g_strerror (errno)); + goto out; + } + else + break; + } + + if ((pw && pw->pw_uid == 0) || + (gr && gr->gr_gid == 0)) + deststream = etcdest_stream; + else + deststream = usrdest_stream; + + if (pw) + r = putpwent (pw, deststream); + else + r = putgrent (gr, deststream); + + if (r == -1) + { + int errsv = errno; + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "putpwent: %s", g_strerror (errsv)); + goto out; + } + } + + if (!gfflush (etcdest_stream, cancellable, error)) + goto out; + if (!gfflush (usrdest_stream, cancellable, error)) + goto out; + + if (rename (etctmp_path, src_path) != 0) + { + int errsv = errno; + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "rename(%s, %s): %s", etctmp_path, src_path, g_strerror (errsv)); + goto out; + } + + 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 gboolean +replace_nsswitch (GFile *target_usretc, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + gs_unref_object GFile *nsswitch_conf = + g_file_get_child (target_usretc, "nsswitch.conf"); + gs_free char *nsswitch_contents = NULL; + gs_free char *new_nsswitch_contents = NULL; + + static gsize regex_initialized; + static GRegex *passwd_regex; + + if (g_once_init_enter (®ex_initialized)) + { + passwd_regex = g_regex_new ("^(passwd|group):\\s+files(.*)$", + G_REGEX_MULTILINE, 0, NULL); + g_assert (passwd_regex); + g_once_init_leave (®ex_initialized, 1); + } + + nsswitch_contents = gs_file_load_contents_utf8 (nsswitch_conf, cancellable, error); + if (!nsswitch_contents) + goto out; + + new_nsswitch_contents = g_regex_replace (passwd_regex, + nsswitch_contents, -1, 0, + "\\1: files altfiles\\2", + 0, error); + if (!new_nsswitch_contents) + goto out; + + if (!g_file_replace_contents (nsswitch_conf, new_nsswitch_contents, + strlen (new_nsswitch_contents), + NULL, FALSE, 0, NULL, + cancellable, error)) + goto out; + + ret = TRUE; + out: + return ret; +} /* Prepare a root filesystem, taking mainly the contents of /usr from yumroot */ static gboolean @@ -554,6 +741,22 @@ create_rootfs_from_yumroot_content (GFile *targetroot, if (!init_rootfs (targetroot, cancellable, error)) goto out; + g_print ("Migrating /etc/passwd to /usr/lib/\n"); + if (!migrate_passwd_file_except_root (yumroot, MIGRATE_PASSWD, cancellable, error)) + goto out; + g_print ("Migrating /etc/group to /usr/lib/\n"); + if (!migrate_passwd_file_except_root (yumroot, MIGRATE_GROUP, cancellable, error)) + goto out; + + /* NSS configuration to look at the new files */ + { + gs_unref_object GFile *yumroot_etc = + g_file_resolve_relative_path (yumroot, "etc"); + + if (!replace_nsswitch (yumroot_etc, cancellable, error)) + goto out; + } + /* We take /usr from the yum content */ g_print ("Moving /usr to target\n"); {