/* See "man 5 passwd" We just make sure the name and uid/gid match, and that none are missing. don't care about GECOS/dir/shell. */ static gboolean rpmostree_check_passwd_groups (gboolean passwd, OstreeRepo *repo, GFile *yumroot, GFile *treefile_dirpath, JsonObject *treedata, const char *previous_commit, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; const char *direct = NULL; const char *chk_type = "previous"; const char *commit_filepath = passwd ? "usr/lib/passwd" : "usr/lib/group"; const char *json_conf_name = passwd ? "check-passwd" : "check-groups"; const char *json_conf_ign = passwd ? "ignore-removed-users" : "ignore-removed-groups"; g_autoptr(GFile) old_path = NULL; g_autoptr(GFile) new_path = g_file_resolve_relative_path (yumroot, commit_filepath); g_autoptr(GPtrArray) ignore_removed_ents = NULL; gboolean ignore_all_removed = FALSE; g_autofree char *old_contents = NULL; g_autofree char *new_contents = NULL; g_autoptr(GPtrArray) old_ents = NULL; g_autoptr(GPtrArray) new_ents = NULL; unsigned int oiter = 0; unsigned int niter = 0; if (json_object_has_member (treedata, json_conf_name)) { JsonObject *chk = json_object_get_object_member (treedata,json_conf_name); if (!chk) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "%s is not an object", json_conf_name); goto out; } chk_type = _rpmostree_jsonutil_object_require_string_member (chk, "type", error); if (!chk_type) goto out; if (g_str_equal (chk_type, "none")) { ret = TRUE; goto out; } else if (g_str_equal (chk_type, "file")) { direct = _rpmostree_jsonutil_object_require_string_member (chk, "filename", error); if (!direct) goto out; } else if (g_str_equal (chk_type, "data")) { JsonNode *ents_node = json_object_get_member (chk, "entries"); JsonObject *ents_obj = NULL; GList *ents; GList *iter; if (!ents_node) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "No entries member for data in %s", json_conf_name); goto out; } ents_obj = json_node_get_object (ents_node); if (passwd) old_ents = g_ptr_array_new_with_free_func (conv_passwd_ent_free); else old_ents = g_ptr_array_new_with_free_func (conv_group_ent_free); ents = json_object_get_members (ents_obj); for (iter = ents; iter; iter = iter->next) if (passwd) { const char *name = iter->data; JsonNode *val = json_object_get_member (ents_obj, name); JsonNodeType child_type = json_node_get_node_type (val); gint64 uid = 0; gint64 gid = 0; struct conv_passwd_ent *convent = g_new (struct conv_passwd_ent, 1); if (child_type != JSON_NODE_ARRAY) { if (!_rpmostree_jsonutil_object_require_int_member (ents_obj, name, &uid, error)) goto out; gid = uid; } else { JsonArray *child_array = json_node_get_array (val); guint len = json_array_get_length (child_array); if (!len || (len > 2)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Array %s is only for uid and gid. Has length %u", name, len); goto out; } if (!_rpmostree_jsonutil_array_require_int_element (child_array, 0, &uid, error)) goto out; if (len == 1) gid = uid; else if (!_rpmostree_jsonutil_array_require_int_element (child_array, 1, &gid, error)) goto out; } convent->name = g_strdup (name); convent->uid = uid; convent->gid = gid; g_ptr_array_add (old_ents, convent); } else { const char *name = iter->data; gint64 gid = 0; struct conv_group_ent *convent = g_new (struct conv_group_ent, 1); if (!_rpmostree_jsonutil_object_require_int_member (ents_obj, name, &gid, error)) goto out; convent->name = g_strdup (name); convent->gid = gid; g_ptr_array_add (old_ents, convent); } }
int rpmostree_compose_builtin_tree (int argc, char **argv, GCancellable *cancellable, GError **error) { int exit_status = EXIT_FAILURE; GError *temp_error = NULL; GOptionContext *context = g_option_context_new ("TREEFILE - Run yum and commit the result to an OSTree repository"); RpmOstreeTreeComposeContext selfdata = { NULL, }; RpmOstreeTreeComposeContext *self = &selfdata; JsonNode *treefile_rootval = NULL; JsonObject *treefile = NULL; g_autofree char *cachekey = NULL; g_autofree char *new_inputhash = NULL; g_autoptr(GFile) previous_root = NULL; g_autofree char *previous_checksum = NULL; g_autoptr(GFile) yumroot = NULL; g_autoptr(GFile) yumroot_varcache = NULL; glnx_fd_close int rootfs_fd = -1; glnx_unref_object OstreeRepo *repo = NULL; g_autoptr(GPtrArray) bootstrap_packages = NULL; g_autoptr(GPtrArray) packages = NULL; g_autoptr(GFile) treefile_path = NULL; g_autoptr(GFile) treefile_dirpath = NULL; g_autoptr(GFile) repo_path = NULL; glnx_unref_object JsonParser *treefile_parser = NULL; gs_unref_variant_builder GVariantBuilder *metadata_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); g_autoptr(RpmOstreeContext) corectx = NULL; g_autoptr(GHashTable) varsubsts = NULL; gboolean workdir_is_tmp = FALSE; g_autofree char *next_version = NULL; self->treefile_context_dirs = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref); if (!rpmostree_option_context_parse (context, option_entries, &argc, &argv, RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD, cancellable, NULL, error)) goto out; if (argc < 2) { rpmostree_usage_error (context, "TREEFILE must be specified", error); goto out; } if (!opt_repo) { rpmostree_usage_error (context, "--repo must be specified", error); goto out; } if (getuid () != 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "compose tree must presently be run as uid 0 (root)"); goto out; } /* Test whether or not bwrap is going to work - we will fail inside e.g. a Docker * container without --privileged or userns exposed. */ if (!rpmostree_bwrap_selftest (error)) goto out; repo_path = g_file_new_for_path (opt_repo); repo = self->repo = ostree_repo_new (repo_path); if (!ostree_repo_open (repo, cancellable, error)) goto out; treefile_path = g_file_new_for_path (argv[1]); if (opt_workdir) { self->workdir = g_file_new_for_path (opt_workdir); } else { g_autofree char *tmpd = NULL; if (!rpmostree_mkdtemp ("/var/tmp/rpm-ostree.XXXXXX", &tmpd, NULL, error)) goto out; self->workdir = g_file_new_for_path (tmpd); workdir_is_tmp = TRUE; if (opt_workdir_tmpfs) { if (mount ("tmpfs", tmpd, "tmpfs", 0, (const void*)"mode=755") != 0) { _rpmostree_set_prefix_error_from_errno (error, errno, "mount(tmpfs): "); goto out; } } } if (!glnx_opendirat (AT_FDCWD, gs_file_get_path_cached (self->workdir), FALSE, &self->workdir_dfd, error)) goto out; if (opt_cachedir) { if (!glnx_opendirat (AT_FDCWD, opt_cachedir, TRUE, &self->cachedir_dfd, error)) { g_prefix_error (error, "Opening cachedir '%s': ", opt_cachedir); goto out; } } else { self->cachedir_dfd = fcntl (self->workdir_dfd, F_DUPFD_CLOEXEC, 3); if (self->cachedir_dfd < 0) { glnx_set_error_from_errno (error); goto out; } } if (opt_metadata_strings) { if (!parse_keyvalue_strings (opt_metadata_strings, metadata_builder, error)) goto out; } if (fchdir (self->workdir_dfd) != 0) { glnx_set_error_from_errno (error); goto out; } corectx = rpmostree_context_new_compose (self->cachedir_dfd, cancellable, error); if (!corectx) goto out; varsubsts = rpmostree_context_get_varsubsts (corectx); treefile_parser = json_parser_new (); if (!json_parser_load_from_file (treefile_parser, gs_file_get_path_cached (treefile_path), error)) goto out; treefile_rootval = json_parser_get_root (treefile_parser); if (!JSON_NODE_HOLDS_OBJECT (treefile_rootval)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Treefile root is not an object"); goto out; } treefile = json_node_get_object (treefile_rootval); if (!process_includes (self, treefile_path, 0, treefile, cancellable, error)) goto out; if (opt_print_only) { glnx_unref_object JsonGenerator *generator = json_generator_new (); g_autoptr(GOutputStream) stdout = g_unix_output_stream_new (1, FALSE); json_generator_set_pretty (generator, TRUE); json_generator_set_root (generator, treefile_rootval); (void) json_generator_to_stream (generator, stdout, NULL, NULL); exit_status = EXIT_SUCCESS; goto out; } { const char *input_ref = _rpmostree_jsonutil_object_require_string_member (treefile, "ref", error); if (!input_ref) goto out; self->ref = _rpmostree_varsubst_string (input_ref, varsubsts, error); if (!self->ref) goto out; } if (!ostree_repo_read_commit (repo, self->ref, &previous_root, &previous_checksum, cancellable, &temp_error)) { if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_clear_error (&temp_error); g_print ("No previous commit for %s\n", self->ref); } else { g_propagate_error (error, temp_error); goto out; } } else g_print ("Previous commit: %s\n", previous_checksum); self->previous_checksum = previous_checksum; yumroot = g_file_get_child (self->workdir, "rootfs.tmp"); if (!glnx_shutil_rm_rf_at (self->workdir_dfd, "rootfs.tmp", cancellable, error)) goto out; if (json_object_has_member (treefile, "automatic_version_prefix") && !compose_strv_contains_prefix (opt_metadata_strings, "version=")) { g_autoptr(GVariant) variant = NULL; g_autofree char *last_version = NULL; const char *ver_prefix; ver_prefix = _rpmostree_jsonutil_object_require_string_member (treefile, "automatic_version_prefix", error); if (!ver_prefix) goto out; if (previous_checksum) { if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, previous_checksum, &variant, error)) goto out; last_version = checksum_version (variant); } next_version = _rpmostree_util_next_version (ver_prefix, last_version); g_variant_builder_add (metadata_builder, "{sv}", "version", g_variant_new_string (next_version)); } bootstrap_packages = g_ptr_array_new (); packages = g_ptr_array_new (); if (json_object_has_member (treefile, "bootstrap_packages")) { if (!_rpmostree_jsonutil_append_string_array_to (treefile, "bootstrap_packages", packages, error)) goto out; } if (!_rpmostree_jsonutil_append_string_array_to (treefile, "packages", packages, error)) goto out; { g_autofree char *thisarch_packages = g_strconcat ("packages-", dnf_context_get_base_arch (rpmostree_context_get_hif (corectx)), NULL); if (json_object_has_member (treefile, thisarch_packages)) { if (!_rpmostree_jsonutil_append_string_array_to (treefile, thisarch_packages, packages, error)) goto out; } } g_ptr_array_add (packages, NULL); { glnx_unref_object JsonGenerator *generator = json_generator_new (); char *treefile_buf = NULL; gsize len; json_generator_set_root (generator, treefile_rootval); json_generator_set_pretty (generator, TRUE); treefile_buf = json_generator_to_data (generator, &len); self->serialized_treefile = g_bytes_new_take (treefile_buf, len); } treefile_dirpath = g_file_get_parent (treefile_path); if (TRUE) { 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, treefile_dirpath, previous_root, treefile, cancellable, error)) goto out; } } { gboolean unmodified = FALSE; if (!install_packages_in_root (self, corectx, treefile, yumroot, (char**)packages->pdata, opt_force_nocache ? NULL : &unmodified, &new_inputhash, cancellable, error)) goto out; if (unmodified) { g_print ("No apparent changes since previous commit; use --force-nocache to override\n"); exit_status = EXIT_SUCCESS; goto out; } else if (opt_dry_run) { g_print ("--dry-run complete, exiting\n"); exit_status = EXIT_SUCCESS; goto out; } } if (g_strcmp0 (g_getenv ("RPM_OSTREE_BREAK"), "post-yum") == 0) goto out; if (!glnx_opendirat (AT_FDCWD, gs_file_get_path_cached (yumroot), TRUE, &rootfs_fd, error)) goto out; if (!rpmostree_treefile_postprocessing (rootfs_fd, self->treefile_context_dirs->pdata[0], self->serialized_treefile, treefile, next_version, cancellable, error)) goto out; if (!rpmostree_prepare_rootfs_for_commit (yumroot, treefile, cancellable, error)) goto out; /* Reopen since the prepare renamed */ (void) close (rootfs_fd); if (!glnx_opendirat (AT_FDCWD, gs_file_get_path_cached (yumroot), TRUE, &rootfs_fd, error)) goto out; if (!rpmostree_copy_additional_files (yumroot, self->treefile_context_dirs->pdata[0], treefile, cancellable, error)) goto out; if (!rpmostree_check_passwd (repo, yumroot, treefile_dirpath, treefile, previous_checksum, cancellable, error)) goto out; if (!rpmostree_check_groups (repo, yumroot, treefile_dirpath, treefile, previous_checksum, cancellable, error)) goto out; { const char *gpgkey; gboolean selinux = TRUE; g_autoptr(GVariant) metadata = NULL; g_variant_builder_add (metadata_builder, "{sv}", "rpmostree.inputhash", g_variant_new_string (new_inputhash)); metadata = g_variant_ref_sink (g_variant_builder_end (metadata_builder)); if (!_rpmostree_jsonutil_object_get_optional_string_member (treefile, "gpg_key", &gpgkey, error)) goto out; if (!_rpmostree_jsonutil_object_get_optional_boolean_member (treefile, "selinux", &selinux, error)) goto out; { g_autofree char *new_revision = NULL; if (!rpmostree_commit (rootfs_fd, repo, self->ref, metadata, gpgkey, selinux, NULL, &new_revision, cancellable, error)) goto out; g_print ("%s => %s\n", self->ref, new_revision); } } if (opt_touch_if_changed) { gs_fd_close int fd = open (opt_touch_if_changed, O_CREAT|O_WRONLY|O_NOCTTY, 0644); if (fd == -1) { gs_set_error_from_errno (error, errno); g_prefix_error (error, "Updating '%s': ", opt_touch_if_changed); goto out; } if (futimens (fd, NULL) == -1) { gs_set_error_from_errno (error, errno); goto out; } } exit_status = EXIT_SUCCESS; out: /* Explicitly close this one now as it may have references to files * we delete below. */ g_clear_object (&corectx); /* Move back out of the workding directory to ensure unmount works */ (void )chdir ("/"); if (self->workdir_dfd != -1) (void) close (self->workdir_dfd); if (workdir_is_tmp) { if (opt_workdir_tmpfs) if (umount (gs_file_get_path_cached (self->workdir)) != 0) { fprintf (stderr, "warning: umount failed: %m\n"); } (void) gs_shutil_rm_rf (self->workdir, NULL, NULL); } if (self) { g_clear_object (&self->workdir); g_clear_pointer (&self->serialized_treefile, g_bytes_unref); g_ptr_array_unref (self->treefile_context_dirs); } return exit_status; }