/** * rpmostree_prepare_rootfs_for_commit: * * Walk over the root filesystem and perform some core conversions * from RPM conventions to OSTree conventions. For example: * * * Move /etc to /usr/etc * * Checksum the kernel in /boot * * Migrate content in /var to systemd-tmpfiles */ gboolean rpmostree_prepare_rootfs_for_commit (GFile *rootfs, JsonObject *treefile, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; gs_free char *dest_rootfs_path = NULL; dest_rootfs_path = g_strconcat (gs_file_get_path_cached (rootfs), ".post", NULL); if (!glnx_shutil_rm_rf_at (AT_FDCWD, dest_rootfs_path, cancellable, error)) goto out; { gs_unref_object GFile *dest_rootfs = g_file_new_for_path (dest_rootfs_path); if (!create_rootfs_from_yumroot_content (dest_rootfs, rootfs, treefile, cancellable, error)) goto out; } if (!glnx_shutil_rm_rf_at (AT_FDCWD, gs_file_get_path_cached (rootfs), cancellable, error)) goto out; if (TEMP_FAILURE_RETRY (renameat (AT_FDCWD, dest_rootfs_path, AT_FDCWD, gs_file_get_path_cached (rootfs))) != 0) { glnx_set_error_from_errno (error); goto out; } ret = TRUE; out: return ret; }
gboolean _ostree_repo_static_delta_delete (OstreeRepo *self, const char *delta_id, GCancellable *cancellable, GError **error) { g_autofree char *from = NULL; g_autofree char *to = NULL; if (!_ostree_parse_delta_name (delta_id, &from, &to, error)) return FALSE; g_autofree char *deltadir = _ostree_get_relative_static_delta_path (from, to, NULL); struct stat buf; if (fstatat (self->repo_dir_fd, deltadir, &buf, 0) != 0) { if (errno == ENOENT) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Can't find delta %s", delta_id); return FALSE; } else return glnx_throw_errno_prefix (error, "fstatat(%s)", deltadir); } if (!glnx_shutil_rm_rf_at (self->repo_dir_fd, deltadir, cancellable, error)) return FALSE; return TRUE; }
gboolean _ostree_repo_static_delta_delete (OstreeRepo *self, const char *delta_id, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; g_autofree char *from = NULL; g_autofree char *to = NULL; g_autofree char *deltadir = NULL; struct stat buf; _ostree_parse_delta_name (delta_id, &from, &to); deltadir = _ostree_get_relative_static_delta_path (from, to, NULL); if (fstatat (self->repo_dir_fd, deltadir, &buf, 0) != 0) { if (errno == ENOENT) g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Can't find delta %s", delta_id); else glnx_set_error_from_errno (error); goto out; } if (!glnx_shutil_rm_rf_at (self->repo_dir_fd, deltadir, cancellable, error)) goto out; ret = TRUE; out: return ret; }
/** * gs_shutil_rm_rf_at: * @dfd: A directory file descriptor, or -1 for current * @path: Path * @cancellable: Cancellable * @error: Error * * Recursively delete the filename referenced by the combination of * the directory fd@dfd and @path; it may be a file or directory. No * error is thrown if @path does not exist. */ gboolean gs_shutil_rm_rf_at (int dfd, const char *path, GCancellable *cancellable, GError **error) { return glnx_shutil_rm_rf_at (dfd, path, cancellable, error); }
/** * ostree_repo_prune_static_deltas: * @self: Repo * @commit: (allow-none): ASCII SHA256 checksum for commit, or %NULL for each * non existing commit * @cancellable: Cancellable * @error: Error * * Prune static deltas, if COMMIT is specified then delete static delta files only * targeting that commit; otherwise any static delta of non existing commits are * deleted. * * Locking: exclusive */ gboolean ostree_repo_prune_static_deltas (OstreeRepo *self, const char *commit, GCancellable *cancellable, GError **error) { g_autoptr(OstreeRepoAutoLock) lock = _ostree_repo_auto_lock_push (self, OSTREE_REPO_LOCK_EXCLUSIVE, cancellable, error); if (!lock) return FALSE; g_autoptr(GPtrArray) deltas = NULL; if (!ostree_repo_list_static_delta_names (self, &deltas, cancellable, error)) return FALSE; for (guint i = 0; i < deltas->len; i++) { const char *deltaname = deltas->pdata[i]; const char *dash = strchr (deltaname, '-'); const char *to = NULL; g_autofree char *from = NULL; if (!dash) { to = deltaname; } else { from = g_strndup (deltaname, dash - deltaname); to = dash + 1; } if (commit) { if (g_strcmp0 (to, commit)) continue; } else { gboolean have_commit; if (!ostree_repo_has_object (self, OSTREE_OBJECT_TYPE_COMMIT, to, &have_commit, cancellable, error)) return FALSE; if (have_commit) continue; } g_debug ("Trying to prune static delta %s", deltaname); g_autofree char *deltadir = _ostree_get_relative_static_delta_path (from, to, NULL); if (!glnx_shutil_rm_rf_at (self->repo_dir_fd, deltadir, cancellable, error)) return FALSE; } return TRUE; }
gboolean ot_gpgme_ctx_tmp_home_dir (gpgme_ctx_t gpgme_ctx, const char *tmp_dir, char **out_tmp_home_dir, GOutputStream **out_pubring_stream, GCancellable *cancellable, GError **error) { g_autoptr(GFile) pubring_file = NULL; g_autoptr(GOutputStream) target_stream = NULL; g_autofree char *pubring_path = NULL; g_autofree char *tmp_home_dir = NULL; gpgme_error_t gpg_error; gboolean ret = FALSE; g_return_val_if_fail (gpgme_ctx != NULL, FALSE); /* GPGME has no API for using multiple keyrings (aka, gpg --keyring), * so we create a temporary directory and tell GPGME to use it as the * home directory. Then (optionally) create a pubring.gpg file there * and hand the caller an open output stream to concatenate necessary * keyring files. */ if (tmp_dir == NULL) tmp_dir = g_get_tmp_dir (); tmp_home_dir = g_build_filename (tmp_dir, "ostree-gpg-XXXXXX", NULL); if (mkdtemp (tmp_home_dir) == NULL) { glnx_set_error_from_errno (error); goto out; } /* Not documented, but gpgme_ctx_set_engine_info() accepts NULL for * the executable file name, which leaves the old setting unchanged. */ gpg_error = gpgme_ctx_set_engine_info (gpgme_ctx, GPGME_PROTOCOL_OpenPGP, NULL, tmp_home_dir); if (gpg_error != GPG_ERR_NO_ERROR) { ot_gpgme_error_to_gio_error (gpg_error, error); goto out; } if (out_pubring_stream != NULL) { GFileOutputStream *pubring_stream; glnx_unref_object GFile *pubring_file = NULL; g_autofree char *pubring_path = NULL; pubring_path = g_build_filename (tmp_home_dir, "pubring.gpg", NULL); pubring_file = g_file_new_for_path (pubring_path); pubring_stream = g_file_create (pubring_file, G_FILE_CREATE_NONE, cancellable, error); if (pubring_stream == NULL) goto out; /* Sneaky cast from GFileOutputStream to GOutputStream. */ *out_pubring_stream = g_steal_pointer (&pubring_stream); } if (out_tmp_home_dir != NULL) *out_tmp_home_dir = g_steal_pointer (&tmp_home_dir); ret = TRUE; out: if (!ret) { /* Clean up our mess on error. */ (void) glnx_shutil_rm_rf_at (AT_FDCWD, tmp_home_dir, NULL, NULL); } return ret; }
int rpmostree_container_builtin_assemble (int argc, char **argv, GCancellable *cancellable, GError **error) { int exit_status = EXIT_FAILURE; GOptionContext *context = g_option_context_new ("NAME [PKGNAME PKGNAME...]"); g_auto(ROContainerContext) rocctx_data = RO_CONTAINER_CONTEXT_INIT; ROContainerContext *rocctx = &rocctx_data; g_autoptr(RpmOstreeInstall) install = {0,}; const char *specpath; struct stat stbuf; const char *name; g_autofree char *commit = NULL; const char *target_rootdir; g_autoptr(RpmOstreeTreespec) treespec = NULL; if (!rpmostree_option_context_parse (context, assemble_option_entries, &argc, &argv, RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD, cancellable, NULL, error)) goto out; if (argc < 1) { rpmostree_usage_error (context, "SPEC must be specified", error); goto out; } specpath = argv[1]; treespec = rpmostree_treespec_new_from_path (specpath, error); if (!treespec) goto out; name = rpmostree_treespec_get_ref (treespec); if (!roc_context_init (rocctx, error)) goto out; target_rootdir = glnx_strjoina (name, ".0"); if (fstatat (rocctx->roots_dfd, target_rootdir, &stbuf, AT_SYMLINK_NOFOLLOW) < 0) { if (errno != ENOENT) { glnx_set_error_from_errno (error); goto out; } } else { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Tree %s already exists", target_rootdir); goto out; } if (!roc_context_prepare_for_root (rocctx, target_rootdir, treespec, cancellable, error)) goto out; /* --- Downloading metadata --- */ if (!rpmostree_context_download_metadata (rocctx->ctx, cancellable, error)) goto out; /* --- Resolving dependencies --- */ if (!rpmostree_context_prepare_install (rocctx->ctx, &install, cancellable, error)) goto out; /* --- Download and import as necessary --- */ if (!rpmostree_context_download_import (rocctx->ctx, install, cancellable, error)) goto out; { glnx_fd_close int tmpdir_dfd = -1; if (!glnx_opendirat (rocctx->userroot_dfd, "tmp", TRUE, &tmpdir_dfd, error)) goto out; if (!rpmostree_context_assemble_commit (rocctx->ctx, tmpdir_dfd, name, install, &commit, cancellable, error)) goto out; } g_print ("Checking out %s @ %s...\n", name, commit); { OstreeRepoCheckoutOptions opts = { OSTREE_REPO_CHECKOUT_MODE_USER, OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES, }; /* For now... to be crash safe we'd need to duplicate some of the * boot-uuid/fsync gating at a higher level. */ opts.disable_fsync = TRUE; /* Also, what we really want here is some sort of sane lifecycle * management with whatever is running in the root. */ if (!glnx_shutil_rm_rf_at (rocctx->roots_dfd, target_rootdir, cancellable, error)) goto out; if (!ostree_repo_checkout_tree_at (rocctx->repo, &opts, rocctx->roots_dfd, target_rootdir, commit, cancellable, error)) goto out; } g_print ("Checking out %s @ %s...done\n", name, commit); if (!symlink_at_replace (target_rootdir, rocctx->roots_dfd, name, cancellable, error)) goto out; g_print ("Creating current symlink...done\n"); exit_status = EXIT_SUCCESS; out: return exit_status; }
static gboolean checkout_one_file_at (OstreeRepo *repo, OstreeRepoCheckoutOptions *options, GFile *source, GFileInfo *source_info, int destination_dfd, const char *destination_name, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; const char *checksum; gboolean is_symlink; gboolean can_cache; gboolean need_copy = TRUE; char loose_path_buf[_OSTREE_LOOSE_PATH_MAX]; g_autoptr(GInputStream) input = NULL; g_autoptr(GVariant) xattrs = NULL; gboolean is_whiteout; is_symlink = g_file_info_get_file_type (source_info) == G_FILE_TYPE_SYMBOLIC_LINK; checksum = ostree_repo_file_get_checksum ((OstreeRepoFile*)source); is_whiteout = !is_symlink && options->process_whiteouts && g_str_has_prefix (destination_name, WHITEOUT_PREFIX); /* First, see if it's a Docker whiteout, * https://github.com/docker/docker/blob/1a714e76a2cb9008cd19609059e9988ff1660b78/pkg/archive/whiteouts.go */ if (is_whiteout) { const char *name = destination_name + (sizeof (WHITEOUT_PREFIX) - 1); if (!name[0]) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid empty whiteout '%s'", name); goto out; } g_assert (name[0] != '/'); /* Sanity */ if (!glnx_shutil_rm_rf_at (destination_dfd, name, cancellable, error)) goto out; need_copy = FALSE; } else if (!is_symlink) { gboolean did_hardlink = FALSE; /* Try to do a hardlink first, if it's a regular file. This also * traverses all parent repos. */ OstreeRepo *current_repo = repo; while (current_repo) { gboolean is_bare = ((current_repo->mode == OSTREE_REPO_MODE_BARE && options->mode == OSTREE_REPO_CHECKOUT_MODE_NONE) || (current_repo->mode == OSTREE_REPO_MODE_BARE_USER && options->mode == OSTREE_REPO_CHECKOUT_MODE_USER)); gboolean current_can_cache = (options->enable_uncompressed_cache && current_repo->enable_uncompressed_cache); gboolean is_archive_z2_with_cache = (current_repo->mode == OSTREE_REPO_MODE_ARCHIVE_Z2 && options->mode == OSTREE_REPO_CHECKOUT_MODE_USER && current_can_cache); /* But only under these conditions */ if (is_bare || is_archive_z2_with_cache) { /* Override repo mode; for archive-z2 we're looking in the cache, which is in "bare" form */ _ostree_loose_path (loose_path_buf, checksum, OSTREE_OBJECT_TYPE_FILE, OSTREE_REPO_MODE_BARE); if (!checkout_file_hardlink (current_repo, options, loose_path_buf, destination_dfd, destination_name, TRUE, &did_hardlink, cancellable, error)) goto out; if (did_hardlink && options->devino_to_csum_cache) { struct stat stbuf; OstreeDevIno *key; if (TEMP_FAILURE_RETRY (fstatat (destination_dfd, destination_name, &stbuf, AT_SYMLINK_NOFOLLOW)) != 0) { glnx_set_error_from_errno (error); goto out; } key = g_new (OstreeDevIno, 1); key->dev = stbuf.st_dev; key->ino = stbuf.st_ino; memcpy (key->checksum, checksum, OSTREE_SHA256_STRING_LEN+1); g_hash_table_add ((GHashTable*)options->devino_to_csum_cache, key); } if (did_hardlink) break; } current_repo = current_repo->parent_repo; } need_copy = !did_hardlink; } can_cache = (options->enable_uncompressed_cache && repo->enable_uncompressed_cache); /* Ok, if we're archive-z2 and we didn't find an object, uncompress * it now, stick it in the cache, and then hardlink to that. */ if (can_cache && !is_whiteout && !is_symlink && need_copy && repo->mode == OSTREE_REPO_MODE_ARCHIVE_Z2 && options->mode == OSTREE_REPO_CHECKOUT_MODE_USER) { gboolean did_hardlink; if (!ostree_repo_load_file (repo, checksum, &input, NULL, NULL, cancellable, error)) goto out; /* Overwrite any parent repo from earlier */ _ostree_loose_path (loose_path_buf, checksum, OSTREE_OBJECT_TYPE_FILE, OSTREE_REPO_MODE_BARE); if (!checkout_object_for_uncompressed_cache (repo, loose_path_buf, source_info, input, cancellable, error)) { g_prefix_error (error, "Unpacking loose object %s: ", checksum); goto out; } g_clear_object (&input); /* Store the 2-byte objdir prefix (e.g. e3) in a set. The basic * idea here is that if we had to unpack an object, it's very * likely we're replacing some other object, so we may need a GC. * * This model ensures that we do work roughly proportional to * the size of the changes. For example, we don't scan any * directories if we didn't modify anything, meaning you can * checkout the same tree multiple times very quickly. * * This is also scale independent; we don't hardcode e.g. looking * at 1000 objects. * * The downside is that if we're unlucky, we may not free * an object for quite some time. */ g_mutex_lock (&repo->cache_lock); { gpointer key = GUINT_TO_POINTER ((g_ascii_xdigit_value (checksum[0]) << 4) + g_ascii_xdigit_value (checksum[1])); if (repo->updated_uncompressed_dirs == NULL) repo->updated_uncompressed_dirs = g_hash_table_new (NULL, NULL); g_hash_table_insert (repo->updated_uncompressed_dirs, key, key); } g_mutex_unlock (&repo->cache_lock); if (!checkout_file_hardlink (repo, options, loose_path_buf, destination_dfd, destination_name, FALSE, &did_hardlink, cancellable, error)) { g_prefix_error (error, "Using new cached uncompressed hardlink of %s to %s: ", checksum, destination_name); goto out; } need_copy = !did_hardlink; } /* Fall back to copy if we couldn't hardlink */ if (need_copy) { if (!ostree_repo_load_file (repo, checksum, &input, NULL, &xattrs, cancellable, error)) goto out; if (options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES) { if (!checkout_file_unioning_from_input_at (repo, options, source_info, xattrs, input, destination_dfd, destination_name, cancellable, error)) { g_prefix_error (error, "Union checkout of %s to %s: ", checksum, destination_name); goto out; } } else { if (!checkout_file_from_input_at (repo, options, source_info, xattrs, input, destination_dfd, destination_name, cancellable, error)) { g_prefix_error (error, "Checkout of %s to %s: ", checksum, destination_name); goto out; } } if (input) { if (!g_input_stream_close (input, cancellable, error)) goto out; } } ret = TRUE; out: return ret; }
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; }
static gboolean write_checksum_file_at (OstreeRepo *self, int dfd, const char *name, const char *sha256, GCancellable *cancellable, GError **error) { if (!ostree_validate_checksum_string (sha256, error)) return FALSE; if (ostree_validate_checksum_string (name, NULL)) return glnx_throw (error, "Rev name '%s' looks like a checksum", name); if (!*name) return glnx_throw (error, "Invalid empty ref name"); const char *lastslash = strrchr (name, '/'); if (lastslash) { char *parent = strdupa (name); parent[lastslash - name] = '\0'; if (!glnx_shutil_mkdir_p_at (dfd, parent, 0777, cancellable, error)) return FALSE; } { size_t l = strlen (sha256); char *bufnl = alloca (l + 2); g_autoptr(GError) temp_error = NULL; memcpy (bufnl, sha256, l); bufnl[l] = '\n'; bufnl[l+1] = '\0'; if (!_ostree_repo_file_replace_contents (self, dfd, name, (guint8*)bufnl, l + 1, cancellable, &temp_error)) { if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY)) { g_autoptr(GHashTable) refs = NULL; GHashTableIter hashiter; gpointer hashkey, hashvalue; g_clear_error (&temp_error); if (!ostree_repo_list_refs (self, name, &refs, cancellable, error)) return FALSE; g_hash_table_iter_init (&hashiter, refs); while ((g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue))) { if (strcmp (name, (char *)hashkey) != 0) return glnx_throw (error, "Conflict: %s exists under %s when attempting write", (char*)hashkey, name); } if (!glnx_shutil_rm_rf_at (dfd, name, cancellable, error)) return FALSE; if (!_ostree_repo_file_replace_contents (self, dfd, name, (guint8*)bufnl, l + 1, cancellable, error)) return FALSE; } else { g_propagate_error (error, g_steal_pointer (&temp_error)); return FALSE; } } } return TRUE; }