/** * 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_unref_object GFile *rootfs_tmp = NULL; gs_free char *rootfs_tmp_path = NULL; rootfs_tmp_path = g_strconcat (gs_file_get_path_cached (rootfs), ".tmp", NULL); rootfs_tmp = g_file_new_for_path (rootfs_tmp_path); if (!gs_shutil_rm_rf (rootfs_tmp, cancellable, error)) goto out; if (!create_rootfs_from_yumroot_content (rootfs_tmp, rootfs, treefile, cancellable, error)) goto out; if (!gs_shutil_rm_rf (rootfs, cancellable, error)) goto out; if (!gs_file_rename (rootfs_tmp, rootfs, cancellable, error)) goto out; ret = TRUE; out: return ret; }
static gboolean cleanup_other_bootversions (OstreeSysroot *self, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; int cleanup_bootversion; int cleanup_subbootversion; gs_unref_object GFile *cleanup_boot_dir = NULL; cleanup_bootversion = self->bootversion == 0 ? 1 : 0; cleanup_subbootversion = self->subbootversion == 0 ? 1 : 0; cleanup_boot_dir = ot_gfile_resolve_path_printf (self->path, "boot/loader.%d", cleanup_bootversion); if (!gs_shutil_rm_rf (cleanup_boot_dir, cancellable, error)) goto out; g_clear_object (&cleanup_boot_dir); cleanup_boot_dir = ot_gfile_resolve_path_printf (self->path, "ostree/boot.%d", cleanup_bootversion); if (!gs_shutil_rm_rf (cleanup_boot_dir, cancellable, error)) goto out; g_clear_object (&cleanup_boot_dir); cleanup_boot_dir = ot_gfile_resolve_path_printf (self->path, "ostree/boot.%d.0", cleanup_bootversion); if (!gs_shutil_rm_rf (cleanup_boot_dir, cancellable, error)) goto out; g_clear_object (&cleanup_boot_dir); cleanup_boot_dir = ot_gfile_resolve_path_printf (self->path, "ostree/boot.%d.1", cleanup_bootversion); if (!gs_shutil_rm_rf (cleanup_boot_dir, cancellable, error)) goto out; g_clear_object (&cleanup_boot_dir); cleanup_boot_dir = ot_gfile_resolve_path_printf (self->path, "ostree/boot.%d.%d", self->bootversion, cleanup_subbootversion); if (!gs_shutil_rm_rf (cleanup_boot_dir, cancellable, error)) goto out; g_clear_object (&cleanup_boot_dir); ret = TRUE; out: return ret; }
static gboolean do_kernel_prep (GFile *yumroot, JsonObject *treefile, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; gs_unref_object GFile *bootdir = g_file_get_child (yumroot, "boot"); gs_unref_object GFile *kernel_path = NULL; gs_unref_object GFile *initramfs_path = NULL; const char *boot_checksum_str = NULL; GChecksum *boot_checksum = NULL; g_autofree char *kver = NULL; if (!find_kernel_and_initramfs_in_bootdir (bootdir, &kernel_path, &initramfs_path, cancellable, error)) goto out; if (kernel_path == NULL) { gs_unref_object GFile *mod_dir = g_file_resolve_relative_path (yumroot, "usr/lib/modules"); gs_unref_object GFile *modversion_dir = NULL; if (!find_ensure_one_subdirectory (mod_dir, &modversion_dir, cancellable, error)) goto out; if (modversion_dir) { kver = g_file_get_basename (modversion_dir); if (!find_kernel_and_initramfs_in_bootdir (modversion_dir, &kernel_path, &initramfs_path, cancellable, error)) goto out; } } if (kernel_path == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unable to find kernel (vmlinuz) in /boot or /usr/lib/modules"); goto out; } if (initramfs_path) { g_print ("Removing RPM-generated '%s'\n", gs_file_get_path_cached (initramfs_path)); if (!gs_shutil_rm_rf (initramfs_path, cancellable, error)) goto out; } if (!kver) { const char *kname = gs_file_get_basename_cached (kernel_path); const char *kver_p; kver_p = strchr (kname, '-'); g_assert (kver_p); kver = g_strdup (kver_p + 1); } /* OSTree needs to own this */ { gs_unref_object GFile *loaderdir = g_file_get_child (bootdir, "loader"); if (!gs_shutil_rm_rf (loaderdir, cancellable, error)) goto out; } { char *child_argv[] = { "depmod", (char*)kver, NULL }; if (!run_sync_in_root (yumroot, "depmod", child_argv, error)) goto out; } /* Ensure the /etc/machine-id file is present and empty. Apparently systemd doesn't work when the file is missing (as of systemd-219-9.fc22) but it is correctly populated if the file is there. */ g_print ("Creating empty machine-id\n"); { const char *hardcoded_machine_id = ""; gs_unref_object GFile *machineid_path = g_file_resolve_relative_path (yumroot, "etc/machine-id"); if (!g_file_replace_contents (machineid_path, hardcoded_machine_id, strlen (hardcoded_machine_id), NULL, FALSE, 0, NULL, cancellable, error)) goto out; } { gboolean reproducible; gs_unref_ptrarray GPtrArray *dracut_argv = g_ptr_array_new (); if (!dracut_supports_reproducible (yumroot, &reproducible, cancellable, error)) goto out; g_ptr_array_add (dracut_argv, "dracut"); g_ptr_array_add (dracut_argv, "-v"); if (reproducible) { g_ptr_array_add (dracut_argv, "--reproducible"); g_ptr_array_add (dracut_argv, "--gzip"); } g_ptr_array_add (dracut_argv, "--tmpdir=/tmp"); g_ptr_array_add (dracut_argv, "-f"); g_ptr_array_add (dracut_argv, "/var/tmp/initramfs.img"); g_ptr_array_add (dracut_argv, (char*)kver); if (json_object_has_member (treefile, "initramfs-args")) { guint i, len; JsonArray *initramfs_args; initramfs_args = json_object_get_array_member (treefile, "initramfs-args"); len = json_array_get_length (initramfs_args); for (i = 0; i < len; i++) { const char *arg = _rpmostree_jsonutil_array_require_string_element (initramfs_args, i, error); if (!arg) goto out; g_ptr_array_add (dracut_argv, (char*)arg); } } g_ptr_array_add (dracut_argv, NULL); if (!run_sync_in_root (yumroot, "dracut", (char**)dracut_argv->pdata, error)) goto out; } initramfs_path = g_file_resolve_relative_path (yumroot, "var/tmp/initramfs.img"); if (!g_file_query_exists (initramfs_path, NULL)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Dracut failed to generate '%s'", gs_file_get_path_cached (initramfs_path)); goto out; } { gs_free char *initramfs_name = g_strconcat ("initramfs-", kver, ".img", NULL); gs_unref_object GFile *initramfs_dest = g_file_get_child (bootdir, initramfs_name); if (!gs_file_rename (initramfs_path, initramfs_dest, cancellable, error)) goto out; /* Transfer ownership */ g_object_unref (initramfs_path); initramfs_path = initramfs_dest; initramfs_dest = NULL; } boot_checksum = g_checksum_new (G_CHECKSUM_SHA256); if (!_rpmostree_util_update_checksum_from_file (boot_checksum, kernel_path, cancellable, error)) goto out; if (!_rpmostree_util_update_checksum_from_file (boot_checksum, initramfs_path, cancellable, error)) goto out; boot_checksum_str = g_checksum_get_string (boot_checksum); { gs_free char *new_kernel_name = g_strconcat (gs_file_get_basename_cached (kernel_path), "-", boot_checksum_str, NULL); gs_unref_object GFile *new_kernel_path = g_file_get_child (bootdir, new_kernel_name); gs_free char *new_initramfs_name = g_strconcat (gs_file_get_basename_cached (initramfs_path), "-", boot_checksum_str, NULL); gs_unref_object GFile *new_initramfs_path = g_file_get_child (bootdir, new_initramfs_name); if (!gs_file_rename (kernel_path, new_kernel_path, cancellable, error)) goto out; if (!gs_file_rename (initramfs_path, new_initramfs_path, cancellable, error)) goto out; } ret = TRUE; out: if (boot_checksum) g_checksum_free (boot_checksum); return ret; }
gboolean rpmostree_commit (GFile *rootfs, OstreeRepo *repo, const char *refname, GVariant *metadata, const char *gpg_keyid, gboolean enable_selinux, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; gs_unref_object OstreeMutableTree *mtree = NULL; OstreeRepoCommitModifier *commit_modifier = NULL; gs_free char *parent_revision = NULL; gs_free char *new_revision = NULL; gs_unref_object GFile *root_tree = NULL; gs_unref_object OstreeSePolicy *sepolicy = NULL; gs_fd_close int rootfs_fd = -1; if (!gs_file_open_dir_fd (rootfs, &rootfs_fd, cancellable, error)) goto out; /* hardcode targeted policy for now */ if (enable_selinux) { if (!rpmostree_prepare_rootfs_get_sepolicy (rootfs_fd, ".", &sepolicy, cancellable, error)) goto out; } g_print ("Committing '%s' ...\n", gs_file_get_path_cached (rootfs)); if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error)) goto out; mtree = ostree_mutable_tree_new (); commit_modifier = ostree_repo_commit_modifier_new (0, NULL, NULL, NULL); ostree_repo_commit_modifier_set_xattr_callback (commit_modifier, read_xattrs_cb, NULL, GINT_TO_POINTER (rootfs_fd)); if (sepolicy) { const char *policy_name = ostree_sepolicy_get_name (sepolicy); g_print ("Labeling with SELinux policy '%s'\n", policy_name); ostree_repo_commit_modifier_set_sepolicy (commit_modifier, sepolicy); } if (!ostree_repo_write_directory_to_mtree (repo, rootfs, mtree, commit_modifier, cancellable, error)) goto out; if (!ostree_repo_write_mtree (repo, mtree, &root_tree, cancellable, error)) goto out; if (!ostree_repo_resolve_rev (repo, refname, TRUE, &parent_revision, error)) goto out; if (!ostree_repo_write_commit (repo, parent_revision, "", "", metadata, (OstreeRepoFile*)root_tree, &new_revision, cancellable, error)) goto out; if (gpg_keyid) { g_print ("Signing commit %s with key %s\n", new_revision, gpg_keyid); if (!ostree_repo_sign_commit (repo, new_revision, gpg_keyid, NULL, cancellable, error)) goto out; } ostree_repo_transaction_set_ref (repo, NULL, refname, new_revision); if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error)) goto out; g_print ("%s => %s\n", refname, new_revision); if (!g_getenv ("RPM_OSTREE_PRESERVE_ROOTFS")) (void) gs_shutil_rm_rf (rootfs, NULL, NULL); else g_print ("Preserved %s\n", gs_file_get_path_cached (rootfs)); ret = TRUE; out: return ret; }
gboolean rpmostree_treefile_postprocessing (GFile *yumroot, GFile *context_directory, GBytes *serialized_treefile, JsonObject *treefile, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; guint i, len; JsonArray *units = NULL; JsonArray *remove = NULL; const char *default_target = NULL; const char *postprocess_script = NULL; if (json_object_has_member (treefile, "units")) units = json_object_get_array_member (treefile, "units"); if (units) len = json_array_get_length (units); else len = 0; { gs_unref_object GFile *multiuser_wants_dir = g_file_resolve_relative_path (yumroot, "etc/systemd/system/multi-user.target.wants"); if (!gs_file_ensure_directory (multiuser_wants_dir, TRUE, cancellable, error)) goto out; for (i = 0; i < len; i++) { const char *unitname = _rpmostree_jsonutil_array_require_string_element (units, i, error); gs_unref_object GFile *unit_link_target = NULL; gs_free char *symlink_target = NULL; if (!unitname) goto out; symlink_target = g_strconcat ("/usr/lib/systemd/system/", unitname, NULL); unit_link_target = g_file_get_child (multiuser_wants_dir, unitname); if (g_file_query_file_type (unit_link_target, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) == G_FILE_TYPE_SYMBOLIC_LINK) continue; g_print ("Adding %s to multi-user.target.wants\n", unitname); if (!g_file_make_symbolic_link (unit_link_target, symlink_target, cancellable, error)) goto out; } } { gs_unref_object GFile *target_treefile_dir_path = g_file_resolve_relative_path (yumroot, "usr/share/rpm-ostree"); gs_unref_object GFile *target_treefile_path = g_file_get_child (target_treefile_dir_path, "treefile.json"); const guint8 *buf; gsize len; if (!gs_file_ensure_directory (target_treefile_dir_path, TRUE, cancellable, error)) goto out; g_print ("Writing '%s'\n", gs_file_get_path_cached (target_treefile_path)); buf = g_bytes_get_data (serialized_treefile, &len); if (!g_file_replace_contents (target_treefile_path, (char*)buf, len, NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION, NULL, cancellable, error)) goto out; } if (!_rpmostree_jsonutil_object_get_optional_string_member (treefile, "default_target", &default_target, error)) goto out; if (default_target != NULL) { gs_unref_object GFile *default_target_path = g_file_resolve_relative_path (yumroot, "etc/systemd/system/default.target"); gs_free char *dest_default_target_path = g_strconcat ("/usr/lib/systemd/system/", default_target, NULL); (void) gs_file_unlink (default_target_path, NULL, NULL); if (!g_file_make_symbolic_link (default_target_path, dest_default_target_path, cancellable, error)) goto out; } if (json_object_has_member (treefile, "remove-files")) { remove = json_object_get_array_member (treefile, "remove-files"); len = json_array_get_length (remove); } else len = 0; for (i = 0; i < len; i++) { const char *val = _rpmostree_jsonutil_array_require_string_element (remove, i, error); gs_unref_object GFile *child = NULL; if (!val) return FALSE; if (g_path_is_absolute (val)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "'remove' elements must be relative"); goto out; } child = g_file_resolve_relative_path (yumroot, val); if (g_file_query_exists (child, NULL)) { g_print ("Removing '%s'\n", val); if (!gs_shutil_rm_rf (child, cancellable, error)) goto out; } else { g_printerr ("warning: Targeted path for remove-files does not exist: %s\n", gs_file_get_path_cached (child)); } } if (json_object_has_member (treefile, "remove-from-packages")) { g_autoptr(RpmOstreeRefSack) refsack = NULL; _cleanup_hypackagelist_ HyPackageList pkglist = NULL; guint i; remove = json_object_get_array_member (treefile, "remove-from-packages"); len = json_array_get_length (remove); if (!rpmostree_get_pkglist_for_root (AT_FDCWD, gs_file_get_path_cached (yumroot), &refsack, &pkglist, cancellable, error)) { g_prefix_error (error, "Reading package set: "); goto out; } for (i = 0; i < len; i++) { JsonArray *elt = json_array_get_array_element (remove, i); if (!handle_remove_files_from_package (yumroot, refsack, elt, cancellable, error)) goto out; } } if (!_rpmostree_jsonutil_object_get_optional_string_member (treefile, "postprocess-script", &postprocess_script, error)) goto out; if (postprocess_script) { const char *yumroot_path = gs_file_get_path_cached (yumroot); gs_unref_object GFile *src = g_file_resolve_relative_path (context_directory, postprocess_script); const char *bn = gs_file_get_basename_cached (src); gs_free char *binpath = g_strconcat ("/usr/bin/rpmostree-postprocess-", bn, NULL); gs_free char *destpath = g_strconcat (yumroot_path, binpath, NULL); gs_unref_object GFile *dest = g_file_new_for_path (destpath); /* Clone all the things */ if (!g_file_copy (src, dest, 0, cancellable, NULL, NULL, error)) { g_prefix_error (error, "Copying postprocess-script '%s' into target: ", bn); goto out; } g_print ("Executing postprocessing script '%s'\n", bn); { char *child_argv[] = { binpath, NULL }; if (!run_sync_in_root (yumroot, binpath, child_argv, error)) { g_prefix_error (error, "While executing postprocessing script '%s': ", bn); goto out; } } g_print ("Finished postprocessing script '%s'\n", bn); } ret = TRUE; out: return ret; }
static gboolean handle_remove_files_from_package (GFile *yumroot, RpmOstreeRefSack *refsack, JsonArray *removespec, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; const char *pkg = json_array_get_string_element (removespec, 0); guint i, j, npackages; guint len = json_array_get_length (removespec); HyPackage hypkg; _cleanup_hyquery_ HyQuery query = NULL; _cleanup_hypackagelist_ HyPackageList pkglist = NULL; query = hy_query_create (refsack->sack); hy_query_filter (query, HY_PKG_NAME, HY_EQ, pkg); pkglist = hy_query_run (query); npackages = hy_packagelist_count (pkglist); if (npackages == 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unable to find package '%s' specified in remove-files-from", pkg); goto out; } for (j = 0; j < npackages; j++) { _cleanup_hystringarray_ HyStringArray pkg_files = NULL; hypkg = hy_packagelist_get (pkglist, 0); pkg_files = hy_package_get_files (hypkg); for (i = 1; i < len; i++) { const char *remove_regex_pattern = json_array_get_string_element (removespec, i); GRegex *regex; char **strviter; regex = g_regex_new (remove_regex_pattern, G_REGEX_JAVASCRIPT_COMPAT, 0, error); if (!regex) goto out; for (strviter = pkg_files; strviter && strviter[0]; strviter++) { const char *file = *strviter; if (g_regex_match (regex, file, 0, NULL)) { gs_unref_object GFile *child = NULL; if (file[0] == '/') file++; child = g_file_resolve_relative_path (yumroot, file); g_print ("Deleting: %s\n", file); if (!gs_shutil_rm_rf (child, cancellable, error)) goto out; } } } } ret = TRUE; out: return ret; }
static gboolean cleanup_old_deployments (OstreeSysroot *self, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; guint32 root_device; guint64 root_inode; guint i; gs_unref_object GFile *active_root = g_file_new_for_path ("/"); gs_unref_hashtable GHashTable *active_deployment_dirs = NULL; gs_unref_hashtable GHashTable *active_boot_checksums = NULL; gs_unref_ptrarray GPtrArray *all_deployment_dirs = NULL; gs_unref_ptrarray GPtrArray *all_boot_dirs = NULL; if (!_ostree_sysroot_get_devino (active_root, &root_device, &root_inode, cancellable, error)) goto out; active_deployment_dirs = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL); active_boot_checksums = g_hash_table_new_full (g_str_hash, (GEqualFunc)g_str_equal, g_free, NULL); for (i = 0; i < self->deployments->len; i++) { OstreeDeployment *deployment = self->deployments->pdata[i]; GFile *deployment_path = ostree_sysroot_get_deployment_directory (self, deployment); char *bootcsum = g_strdup (ostree_deployment_get_bootcsum (deployment)); /* Transfer ownership */ g_hash_table_replace (active_deployment_dirs, deployment_path, deployment_path); g_hash_table_replace (active_boot_checksums, bootcsum, bootcsum); } if (!list_all_deployment_directories (self, &all_deployment_dirs, cancellable, error)) goto out; for (i = 0; i < all_deployment_dirs->len; i++) { OstreeDeployment *deployment = all_deployment_dirs->pdata[i]; gs_unref_object GFile *deployment_path = ostree_sysroot_get_deployment_directory (self, deployment); gs_unref_object GFile *origin_path = ostree_sysroot_get_deployment_origin_path (deployment_path); if (!g_hash_table_lookup (active_deployment_dirs, deployment_path)) { guint32 device; guint64 inode; if (!_ostree_sysroot_get_devino (deployment_path, &device, &inode, cancellable, error)) goto out; /* This shouldn't happen, because higher levels should * disallow having the booted deployment not in the active * deployment list, but let's be extra safe. */ if (device == root_device && inode == root_inode) continue; if (!gs_shutil_rm_rf (deployment_path, cancellable, error)) goto out; if (!gs_shutil_rm_rf (origin_path, cancellable, error)) goto out; } } if (!list_all_boot_directories (self, &all_boot_dirs, cancellable, error)) goto out; for (i = 0; i < all_boot_dirs->len; i++) { GFile *bootdir = all_boot_dirs->pdata[i]; gs_free char *osname = NULL; gs_free char *bootcsum = NULL; if (!parse_bootdir_name (gs_file_get_basename_cached (bootdir), &osname, &bootcsum)) g_assert_not_reached (); if (g_hash_table_lookup (active_boot_checksums, bootcsum)) continue; if (!gs_shutil_rm_rf (bootdir, 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 do_kernel_prep (GFile *yumroot, JsonObject *treefile, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; gs_unref_object GFile *bootdir = g_file_get_child (yumroot, "boot"); gs_unref_object GFile *kernel_path = NULL; gs_unref_object GFile *initramfs_path = NULL; const char *boot_checksum_str = NULL; GChecksum *boot_checksum = NULL; const char *kname; const char *kver; if (!find_kernel_and_initramfs_in_bootdir (bootdir, &kernel_path, &initramfs_path, cancellable, error)) goto out; if (initramfs_path) { g_print ("Removing RPM-generated '%s'\n", gs_file_get_path_cached (initramfs_path)); if (!gs_shutil_rm_rf (initramfs_path, cancellable, error)) goto out; } kname = gs_file_get_basename_cached (kernel_path); kver = strchr (kname, '-'); g_assert (kver); kver += 1; /* OSTree needs to own this */ { gs_unref_object GFile *loaderdir = g_file_get_child (bootdir, "loader"); if (!gs_shutil_rm_rf (loaderdir, cancellable, error)) goto out; } { char *child_argv[] = { "depmod", (char*)kver, NULL }; if (!run_sync_in_root (yumroot, "/usr/sbin/depmod", child_argv, error)) goto out; } /* Copy of code from gnome-continuous; yes, we hardcode the machine id for now, because distributing pre-generated initramfs images with dracut/systemd at the moment effectively requires this. http://lists.freedesktop.org/archives/systemd-devel/2013-July/011770.html */ g_print ("Hardcoding machine-id\n"); { const char *hardcoded_machine_id = "45bb3b96146aa94f299b9eb43646eb35\n"; gs_unref_object GFile *machineid_path = g_file_resolve_relative_path (yumroot, "etc/machine-id"); if (!g_file_replace_contents (machineid_path, hardcoded_machine_id, strlen (hardcoded_machine_id), NULL, FALSE, 0, NULL, cancellable, error)) goto out; } { gs_unref_ptrarray GPtrArray *dracut_argv = g_ptr_array_new (); g_ptr_array_add (dracut_argv, "dracut"); g_ptr_array_add (dracut_argv, "-v"); g_ptr_array_add (dracut_argv, "--tmpdir=/tmp"); g_ptr_array_add (dracut_argv, "-f"); g_ptr_array_add (dracut_argv, "/var/tmp/initramfs.img"); g_ptr_array_add (dracut_argv, (char*)kver); if (json_object_has_member (treefile, "initramfs-args")) { guint i, len; JsonArray *initramfs_args; initramfs_args = json_object_get_array_member (treefile, "initramfs-args"); len = json_array_get_length (initramfs_args); for (i = 0; i < len; i++) { const char *arg = _rpmostree_jsonutil_array_require_string_element (initramfs_args, i, error); if (!arg) goto out; g_ptr_array_add (dracut_argv, (char*)arg); } } g_ptr_array_add (dracut_argv, NULL); if (!run_sync_in_root (yumroot, "/usr/sbin/dracut", (char**)dracut_argv->pdata, error)) goto out; } initramfs_path = g_file_resolve_relative_path (yumroot, "var/tmp/initramfs.img"); if (!g_file_query_exists (initramfs_path, NULL)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Dracut failed to generate '%s'", gs_file_get_path_cached (initramfs_path)); goto out; } { gs_free char *initramfs_name = g_strconcat ("initramfs-", kver, ".img", NULL); gs_unref_object GFile *initramfs_dest = g_file_get_child (bootdir, initramfs_name); if (!gs_file_rename (initramfs_path, initramfs_dest, cancellable, error)) goto out; /* Transfer ownership */ g_object_unref (initramfs_path); initramfs_path = initramfs_dest; initramfs_dest = NULL; } boot_checksum = g_checksum_new (G_CHECKSUM_SHA256); if (!_rpmostree_util_update_checksum_from_file (boot_checksum, kernel_path, cancellable, error)) goto out; if (!_rpmostree_util_update_checksum_from_file (boot_checksum, initramfs_path, cancellable, error)) goto out; boot_checksum_str = g_checksum_get_string (boot_checksum); { gs_free char *new_kernel_name = g_strconcat (gs_file_get_basename_cached (kernel_path), "-", boot_checksum_str, NULL); gs_unref_object GFile *new_kernel_path = g_file_get_child (bootdir, new_kernel_name); gs_free char *new_initramfs_name = g_strconcat (gs_file_get_basename_cached (initramfs_path), "-", boot_checksum_str, NULL); gs_unref_object GFile *new_initramfs_path = g_file_get_child (bootdir, new_initramfs_name); if (!gs_file_rename (kernel_path, new_kernel_path, cancellable, error)) goto out; if (!gs_file_rename (initramfs_path, new_initramfs_path, cancellable, error)) goto out; } ret = TRUE; out: if (boot_checksum) g_checksum_free (boot_checksum); return ret; }
static gboolean git_mirror_submodules (const char *repo_location, gboolean update, GFile *mirror_dir, const char *revision, BuilderContext *context, GError **error) { g_autofree char *mirror_dir_path = NULL; g_autoptr(GFile) checkout_dir_template = NULL; g_autoptr(GFile) checkout_dir = NULL; g_autofree char *checkout_dir_path = NULL; g_autofree char *submodule_status = NULL; mirror_dir_path = g_file_get_path (mirror_dir); checkout_dir_template = g_file_get_child (builder_context_get_state_dir (context), "tmp-checkout-XXXXXX"); checkout_dir_path = g_file_get_path (checkout_dir_template); if (g_mkdtemp (checkout_dir_path) == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Can't create temporary checkout directory"); return FALSE; } checkout_dir = g_file_new_for_path (checkout_dir_path); if (!git (NULL, NULL, error, "clone", "-q", "--no-checkout", mirror_dir_path, checkout_dir_path, NULL)) return FALSE; if (!git (checkout_dir, NULL, error, "checkout", "-q", "-f", revision, NULL)) return FALSE; if (!git (checkout_dir, &submodule_status, error, "submodule", "status", NULL)) return FALSE; if (submodule_status) { int i; g_auto(GStrv) lines = g_strsplit (submodule_status, "\n", -1); for (i = 0; lines[i] != NULL; i++) { g_autofree char *url = NULL; g_autofree char *option = NULL; g_autofree char *old = NULL; g_auto(GStrv) words = NULL; if (*lines[i] == 0) continue; words = g_strsplit (lines[i] + 1, " ", 3); option = g_strdup_printf ("submodule.%s.url", words[1]); if (!git (checkout_dir, &url, error, "config", "-f", ".gitmodules", option, NULL)) return FALSE; /* Trim trailing whitespace */ g_strchomp (url); old = url; url = make_absolute (repo_location, old, error); if (url == NULL) return FALSE; if (!git_mirror_repo (url, update, words[0], context, error)) return FALSE; } } if (!gs_shutil_rm_rf (checkout_dir, NULL, error)) return FALSE; return TRUE; }
gboolean flatpak_builtin_build_init (int argc, char **argv, GCancellable *cancellable, GError **error) { g_autoptr(GOptionContext) context = NULL; g_autoptr(GFile) var_deploy_base = NULL; g_autoptr(GFile) var_deploy_files = NULL; g_autoptr(GFile) base = NULL; g_autoptr(GFile) files_dir = NULL; g_autoptr(GFile) usr_dir = NULL; g_autoptr(GFile) var_dir = NULL; g_autoptr(GFile) var_tmp_dir = NULL; g_autoptr(GFile) var_run_dir = NULL; g_autoptr(GFile) metadata_file = NULL; g_autoptr(GString) metadata_contents = NULL; const char *app_id; const char *directory; const char *sdk; const char *runtime; const char *branch = "master"; g_autofree char *runtime_ref = NULL; g_autofree char *var_ref = NULL; g_autofree char *sdk_ref = NULL; int i; context = g_option_context_new ("DIRECTORY APPNAME SDK RUNTIME [BRANCH] - Initialize a directory for building"); if (!flatpak_option_context_parse (context, options, &argc, &argv, FLATPAK_BUILTIN_FLAG_NO_DIR, NULL, cancellable, error)) return FALSE; if (argc < 5) return usage_error (context, "RUNTIME must be specified", error); directory = argv[1]; app_id = argv[2]; sdk = argv[3]; runtime = argv[4]; if (argc >= 6) branch = argv[5]; if (!flatpak_is_valid_name (app_id)) return flatpak_fail (error, "'%s' is not a valid application name", app_id); if (!flatpak_is_valid_name (runtime)) return flatpak_fail (error, "'%s' is not a valid runtime name", runtime); if (!flatpak_is_valid_name (sdk)) return flatpak_fail (error, "'%s' is not a valid sdk name", sdk); if (!flatpak_is_valid_branch (branch)) return flatpak_fail (error, "'%s' is not a valid branch name", branch); runtime_ref = flatpak_build_untyped_ref (runtime, branch, opt_arch); sdk_ref = flatpak_build_untyped_ref (sdk, branch, opt_arch); base = g_file_new_for_commandline_arg (directory); if (!gs_file_ensure_directory (base, TRUE, cancellable, error)) return FALSE; files_dir = g_file_get_child (base, "files"); if (opt_sdk_dir) usr_dir = g_file_get_child (base, opt_sdk_dir); else usr_dir = g_file_get_child (base, "usr"); var_dir = g_file_get_child (base, "var"); var_tmp_dir = g_file_get_child (var_dir, "tmp"); var_run_dir = g_file_get_child (var_dir, "run"); metadata_file = g_file_get_child (base, "metadata"); if (!opt_update && g_file_query_exists (files_dir, cancellable)) return flatpak_fail (error, "Build directory %s already initialized", directory); if (opt_writable_sdk) { g_autofree char *full_sdk_ref = g_strconcat ("runtime/", sdk_ref, NULL); g_autoptr(GError) my_error = NULL; g_autoptr(GFile) sdk_deploy_files = NULL; g_autoptr(FlatpakDeploy) sdk_deploy = NULL; sdk_deploy = flatpak_find_deploy_for_ref (full_sdk_ref, cancellable, error); if (sdk_deploy == NULL) return FALSE; if (!gs_shutil_rm_rf (usr_dir, NULL, &my_error)) { if (!g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_propagate_error (error, g_steal_pointer (&my_error)); return FALSE; } g_clear_error (&my_error); } sdk_deploy_files = flatpak_deploy_get_files (sdk_deploy); if (!flatpak_cp_a (sdk_deploy_files, usr_dir, FLATPAK_CP_FLAGS_NO_CHOWN, cancellable, error)) return FALSE; if (opt_sdk_extensions) { g_autoptr(GKeyFile) metakey = flatpak_deploy_get_metadata (sdk_deploy); GList *extensions = NULL, *l; /* We leak this on failure, as we have no autoptr for deep lists.. */ extensions = flatpak_list_extensions (metakey, opt_arch, branch); for (i = 0; opt_sdk_extensions[i] != NULL; i++) { const char *requested_extension = opt_sdk_extensions[i]; gboolean found = FALSE; for (l = extensions; l != NULL; l = l->next) { FlatpakExtension *ext = l->data; if (strcmp (ext->installed_id, requested_extension) == 0 || strcmp (ext->id, requested_extension) == 0) { g_autoptr(GFile) ext_deploy_dir = flatpak_find_deploy_dir_for_ref (ext->ref, cancellable, NULL); if (ext_deploy_dir != NULL) { g_autoptr(GFile) ext_deploy_files = g_file_get_child (ext_deploy_dir, "files"); g_autoptr(GFile) target = g_file_resolve_relative_path (usr_dir, ext->directory); g_autoptr(GFile) target_parent = g_file_get_parent (target); if (!gs_file_ensure_directory (target_parent, TRUE, cancellable, error)) return FALSE; /* An extension overrides whatever is there before, so we clean up first */ if (!gs_shutil_rm_rf (target, cancellable, error)) return FALSE; if (!flatpak_cp_a (ext_deploy_files, target, FLATPAK_CP_FLAGS_NO_CHOWN, cancellable, error)) return FALSE; found = TRUE; } else { g_list_free_full (extensions, (GDestroyNotify) flatpak_extension_free); return flatpak_fail (error, "Requested extension %s not installed\n", requested_extension); } } } if (!found) return flatpak_fail (error, "No extension %s in sdk\n", requested_extension); } g_list_free_full (extensions, (GDestroyNotify) flatpak_extension_free); } } if (opt_var) { var_ref = flatpak_build_runtime_ref (opt_var, branch, opt_arch); var_deploy_base = flatpak_find_deploy_dir_for_ref (var_ref, cancellable, error); if (var_deploy_base == NULL) return FALSE; var_deploy_files = g_file_get_child (var_deploy_base, "files"); } if (opt_update) return TRUE; if (!g_file_make_directory (files_dir, cancellable, error)) return FALSE; if (var_deploy_files) { if (!gs_shutil_cp_a (var_deploy_files, var_dir, cancellable, error)) return FALSE; } else { if (!g_file_make_directory (var_dir, cancellable, error)) return FALSE; } if (!gs_file_ensure_directory (var_tmp_dir, FALSE, cancellable, error)) return FALSE; if (!g_file_query_exists (var_run_dir, cancellable) && !g_file_make_symbolic_link (var_run_dir, "/run", cancellable, error)) return FALSE; metadata_contents = g_string_new ("[Application]\n"); g_string_append_printf (metadata_contents, "name=%s\n" "runtime=%s\n" "sdk=%s\n", app_id, runtime_ref, sdk_ref); if (opt_tags != NULL) { g_string_append (metadata_contents, "tags="); for (i = 0; opt_tags[i] != NULL; i++) { g_string_append (metadata_contents, opt_tags[i]); g_string_append_c (metadata_contents, ';'); } g_string_append_c (metadata_contents, '\n'); } if (!g_file_replace_contents (metadata_file, metadata_contents->str, metadata_contents->len, NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION, NULL, cancellable, error)) return FALSE; return TRUE; }
gboolean install_bundle (XdgAppDir *dir, GOptionContext *context, int argc, char **argv, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; g_autoptr(GFile) deploy_base = NULL; g_autoptr(GFile) file = NULL; const char *filename; g_autofree char *ref = NULL; g_autofree char *origin = NULL; gboolean created_deploy_base = FALSE; gboolean added_remote = FALSE; g_autofree char *to_checksum = NULL; g_auto(GStrv) parts = NULL; g_autoptr(GBytes) gpg_data = NULL; g_autofree char *remote = NULL; OstreeRepo *repo; g_auto(GLnxLockFile) lock = GLNX_LOCK_FILE_INIT; g_autoptr(GVariant) metadata = NULL; g_autofree char *basename = NULL; if (argc < 2) return usage_error (context, "bundle filename must be specified", error); filename = argv[1]; repo = xdg_app_dir_get_repo (dir); file = g_file_new_for_commandline_arg (filename); metadata = xdg_app_bundle_load (file, &to_checksum, &ref, &origin, NULL, &gpg_data, error); if (metadata == NULL) return FALSE; if (opt_gpg_file != NULL) { /* Override gpg_data from file */ gpg_data = read_gpg_data (cancellable, error); if (gpg_data == NULL) return FALSE; } parts = xdg_app_decompose_ref (ref, error); if (parts == NULL) return FALSE; deploy_base = xdg_app_dir_get_deploy_dir (dir, ref); if (g_file_query_exists (deploy_base, cancellable)) return xdg_app_fail (error, "%s branch %s already installed", parts[1], parts[3]); /* Add a remote for later updates */ basename = g_file_get_basename (file); remote = xdg_app_dir_create_origin_remote (dir, origin, parts[1], basename, gpg_data, cancellable, error); if (remote == NULL) return FALSE; /* From here we need to goto out on error, to clean up */ added_remote = TRUE; if (!xdg_app_dir_pull_from_bundle (dir, file, remote, ref, gpg_data != NULL, cancellable, error)) goto out; if (!xdg_app_dir_lock (dir, &lock, cancellable, error)) return FALSE; if (!g_file_make_directory_with_parents (deploy_base, cancellable, error)) goto out; created_deploy_base = TRUE; if (!xdg_app_dir_set_origin (dir, ref, remote, cancellable, error)) goto out; if (!xdg_app_dir_deploy (dir, ref, to_checksum, cancellable, error)) goto out; if (strcmp (parts[0], "app") == 0) { if (!xdg_app_dir_make_current_ref (dir, ref, cancellable, error)) goto out; if (!xdg_app_dir_update_exports (dir, parts[1], cancellable, error)) goto out; } glnx_release_lock_file (&lock); xdg_app_dir_cleanup_removed (dir, cancellable, NULL); if (!xdg_app_dir_mark_changed (dir, error)) goto out; ret = TRUE; out: if (created_deploy_base && !ret) gs_shutil_rm_rf (deploy_base, cancellable, NULL); if (added_remote && !ret) ostree_repo_remote_delete (repo, remote, NULL, NULL); return ret; }
gboolean xdg_app_builtin_install (int argc, char **argv, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; g_autoptr(GOptionContext) context = NULL; g_autoptr(XdgAppDir) dir = NULL; g_autoptr(GFile) deploy_base = NULL; const char *repository; const char *name; const char *branch = NULL; g_autofree char *ref = NULL; g_autofree char *installed_ref = NULL; gboolean is_app; gboolean created_deploy_base = FALSE; g_auto(GLnxLockFile) lock = GLNX_LOCK_FILE_INIT; g_autoptr(GError) my_error = NULL; context = g_option_context_new ("REPOSITORY NAME [BRANCH] - Install an application or runtime"); if (!xdg_app_option_context_parse (context, options, &argc, &argv, 0, &dir, cancellable, error)) return FALSE; if (opt_bundle) return install_bundle (dir, context, argc, argv, cancellable, error); if (argc < 3) return usage_error (context, "REPOSITORY and NAME must be specified", error); repository = argv[1]; name = argv[2]; if (argc >= 4) branch = argv[3]; if (!opt_app && !opt_runtime) opt_app = opt_runtime = TRUE; installed_ref = xdg_app_dir_find_installed_ref (dir, name, branch, opt_arch, opt_app, opt_runtime, &is_app, &my_error); if (installed_ref != NULL) { return xdg_app_fail (error, "%s %s, branch %s is already installed", is_app ? "App" : "Runtime", name, branch ? branch : "master"); } if (!g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_propagate_error (error, g_steal_pointer (&my_error)); return FALSE; } ref = xdg_app_dir_find_remote_ref (dir, repository, name, branch, opt_arch, opt_app, opt_runtime, &is_app, cancellable, error); if (ref == NULL) return FALSE; deploy_base = xdg_app_dir_get_deploy_dir (dir, ref); if (g_file_query_exists (deploy_base, cancellable)) return xdg_app_fail (error, "Ref %s already deployed", ref); if (!opt_no_pull) { if (!xdg_app_dir_pull (dir, repository, ref, NULL, cancellable, error)) return FALSE; } /* After we create the deploy base we must goto out on errors */ if (!opt_no_deploy) { if (!xdg_app_dir_lock (dir, &lock, cancellable, error)) goto out; if (!g_file_make_directory_with_parents (deploy_base, cancellable, error)) goto out; created_deploy_base = TRUE; if (!xdg_app_dir_set_origin (dir, ref, repository, cancellable, error)) goto out; if (!xdg_app_dir_deploy (dir, ref, NULL, cancellable, error)) goto out; if (is_app) { if (!xdg_app_dir_make_current_ref (dir, ref, cancellable, error)) goto out; if (!xdg_app_dir_update_exports (dir, name, cancellable, error)) goto out; } glnx_release_lock_file (&lock); } xdg_app_dir_cleanup_removed (dir, cancellable, NULL); if (!xdg_app_dir_mark_changed (dir, error)) goto out; ret = TRUE; out: if (created_deploy_base && !ret) gs_shutil_rm_rf (deploy_base, cancellable, NULL); return ret; }
static gboolean migrate_rpm_and_yumdb (GFile *targetroot, GFile *yumroot, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; gs_unref_object GFile *usrbin_rpm = g_file_resolve_relative_path (targetroot, "usr/bin/rpm"); gs_unref_object GFile *legacyrpm_path = g_file_resolve_relative_path (yumroot, "var/lib/rpm"); gs_unref_object GFile *newrpm_path = g_file_resolve_relative_path (targetroot, "usr/share/rpm"); gs_unref_object GFile *src_yum_rpmdb_indexes = g_file_resolve_relative_path (yumroot, "var/lib/yum"); gs_unref_object GFile *target_yum_rpmdb_indexes = g_file_resolve_relative_path (targetroot, "usr/share/yumdb"); gs_unref_object GFile *yumroot_yumlib = g_file_get_child (yumroot, "var/lib/yum"); gs_unref_object GFileEnumerator *direnum = NULL; direnum = g_file_enumerate_children (legacyrpm_path, "standard::name", G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, cancellable, error); if (!direnum) goto out; while (TRUE) { const char *name; GFileInfo *file_info; GFile *child; if (!gs_file_enumerator_iterate (direnum, &file_info, &child, cancellable, error)) goto out; if (!file_info) break; name = g_file_info_get_name (file_info); if (g_str_has_prefix (name, "__db.") || strcmp (name, ".dbenv.lock") == 0 || strcmp (name, ".rpm.lock") == 0) { if (!gs_file_unlink (child, cancellable, error)) goto out; } } (void) g_file_enumerator_close (direnum, cancellable, error); g_print ("Placing RPM db in /usr/share/rpm\n"); if (!gs_file_rename (legacyrpm_path, newrpm_path, cancellable, error)) goto out; /* Remove /var/lib/yum; we don't want it here. */ if (!gs_shutil_rm_rf (yumroot_yumlib, cancellable, error)) goto out; ret = TRUE; out: return ret; }
static gboolean migrate_rpm_and_yumdb (GFile *targetroot, GFile *yumroot, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; gs_unref_object GFile *usrbin_rpm = g_file_resolve_relative_path (targetroot, "usr/bin/rpm"); gs_unref_object GFile *legacyrpm_path = g_file_resolve_relative_path (yumroot, "var/lib/rpm"); gs_unref_object GFile *newrpm_path = g_file_resolve_relative_path (targetroot, "usr/share/rpm"); gs_unref_object GFile *src_yum_rpmdb_indexes = g_file_resolve_relative_path (yumroot, "var/lib/yum"); gs_unref_object GFile *target_yum_rpmdb_indexes = g_file_resolve_relative_path (targetroot, "usr/share/yumdb"); gs_unref_object GFile *yumroot_yumlib = g_file_get_child (yumroot, "var/lib/yum"); gs_unref_object GFileEnumerator *direnum = NULL; direnum = g_file_enumerate_children (legacyrpm_path, "standard::name", G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, cancellable, error); if (!direnum) goto out; while (TRUE) { const char *name; GFileInfo *file_info; GFile *child; if (!gs_file_enumerator_iterate (direnum, &file_info, &child, cancellable, error)) goto out; if (!file_info) break; name = g_file_info_get_name (file_info); if (g_str_has_prefix (name, "__db.") || strcmp (name, ".dbenv.lock") == 0 || strcmp (name, ".rpm.lock") == 0) { if (!gs_file_unlink (child, cancellable, error)) goto out; } } (void) g_file_enumerator_close (direnum, cancellable, error); g_print ("Placing RPM db in /usr/share/rpm\n"); if (!gs_file_rename (legacyrpm_path, newrpm_path, cancellable, error)) goto out; /* Move the yum database to usr/share/yumdb; disabled for now due * to bad conflict with OSTree's current * one-http-request-per-file. */ #if 0 if (g_file_query_exists (src_yum_rpmdb_indexes, NULL)) { g_print ("Moving %s to %s\n", gs_file_get_path_cached (src_yum_rpmdb_indexes), gs_file_get_path_cached (target_yum_rpmdb_indexes)); if (!gs_file_rename (src_yum_rpmdb_indexes, target_yum_rpmdb_indexes, cancellable, error)) goto out; if (!clean_yumdb_extraneous_files (target_yum_rpmdb_indexes, cancellable, error)) goto out; } #endif /* Remove /var/lib/yum; we don't want it here. */ if (!gs_shutil_rm_rf (yumroot_yumlib, cancellable, error)) goto out; ret = TRUE; out: return ret; }
/* Prepare a root filesystem, taking mainly the contents of /usr from yumroot */ static gboolean create_rootfs_from_yumroot_content (GFile *targetroot, GFile *yumroot, JsonObject *treefile, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; glnx_fd_close int src_rootfs_fd = -1; glnx_fd_close int target_root_dfd = -1; gs_unref_object GFile *kernel_path = NULL; gs_unref_object GFile *initramfs_path = NULL; gs_unref_hashtable GHashTable *preserve_groups_set = NULL; gboolean container = FALSE; if (!glnx_opendirat (AT_FDCWD, gs_file_get_path_cached (yumroot), TRUE, &src_rootfs_fd, error)) goto out; if (!_rpmostree_jsonutil_object_get_optional_boolean_member (treefile, "container", &container, error)) goto out; g_print ("Preparing kernel\n"); if (!container && !do_kernel_prep (yumroot, treefile, cancellable, error)) goto out; g_print ("Initializing rootfs\n"); if (!init_rootfs (targetroot, cancellable, error)) goto out; if (!glnx_opendirat (AT_FDCWD, gs_file_get_path_cached (targetroot), TRUE, &target_root_dfd, error)) goto out; g_print ("Migrating /etc/passwd to /usr/lib/\n"); if (!rpmostree_passwd_migrate_except_root (yumroot, RPM_OSTREE_PASSWD_MIGRATE_PASSWD, NULL, cancellable, error)) goto out; if (json_object_has_member (treefile, "etc-group-members")) { JsonArray *etc_group_members = json_object_get_array_member (treefile, "etc-group-members"); preserve_groups_set = _rpmostree_jsonutil_jsarray_strings_to_set (etc_group_members); } g_print ("Migrating /etc/group to /usr/lib/\n"); if (!rpmostree_passwd_migrate_except_root (yumroot, RPM_OSTREE_PASSWD_MIGRATE_GROUP, preserve_groups_set, 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"); { gs_unref_object GFile *usr = g_file_get_child (yumroot, "usr"); if (!move_to_dir (usr, targetroot, cancellable, error)) goto out; } /* Except /usr/local -> ../var/usrlocal */ g_print ("Linking /usr/local -> ../var/usrlocal\n"); { gs_unref_object GFile *target_usrlocal = g_file_resolve_relative_path (targetroot, "usr/local"); if (!gs_shutil_rm_rf (target_usrlocal, cancellable, error)) goto out; if (!g_file_make_symbolic_link (target_usrlocal, "../var/usrlocal", cancellable, error)) goto out; } /* And now we take the contents of /etc and put them in /usr/etc */ g_print ("Moving /etc to /usr/etc\n"); { gs_unref_object GFile *yumroot_etc = g_file_get_child (yumroot, "etc"); gs_unref_object GFile *target_usretc = g_file_resolve_relative_path (targetroot, "usr/etc"); if (!gs_file_rename (yumroot_etc, target_usretc, cancellable, error)) goto out; } if (!migrate_rpm_and_yumdb (targetroot, yumroot, cancellable, error)) goto out; if (!convert_var_to_tmpfiles_d (src_rootfs_fd, target_root_dfd, cancellable, error)) goto out; /* Move boot, but rename the kernel/initramfs to have a checksum */ if (!container) { gs_unref_object GFile *yumroot_boot = g_file_get_child (yumroot, "boot"); gs_unref_object GFile *target_boot = g_file_get_child (targetroot, "boot"); gs_unref_object GFile *target_usrlib = g_file_resolve_relative_path (targetroot, "usr/lib"); gs_unref_object GFile *target_usrlib_ostree_boot = g_file_resolve_relative_path (target_usrlib, "ostree-boot"); RpmOstreePostprocessBootLocation boot_location = RPMOSTREE_POSTPROCESS_BOOT_LOCATION_BOTH; const char *boot_location_str = NULL; g_print ("Moving /boot\n"); if (!_rpmostree_jsonutil_object_get_optional_string_member (treefile, "boot_location", &boot_location_str, error)) goto out; if (boot_location_str != NULL) { if (strcmp (boot_location_str, "legacy") == 0) boot_location = RPMOSTREE_POSTPROCESS_BOOT_LOCATION_LEGACY; else if (strcmp (boot_location_str, "both") == 0) boot_location = RPMOSTREE_POSTPROCESS_BOOT_LOCATION_BOTH; else if (strcmp (boot_location_str, "new") == 0) boot_location = RPMOSTREE_POSTPROCESS_BOOT_LOCATION_NEW; else { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid boot location '%s'", boot_location_str); goto out; } } if (!gs_file_ensure_directory (target_usrlib, TRUE, cancellable, error)) goto out; switch (boot_location) { case RPMOSTREE_POSTPROCESS_BOOT_LOCATION_LEGACY: { g_print ("Using boot location: legacy\n"); if (!gs_file_rename (yumroot_boot, target_boot, cancellable, error)) goto out; } break; case RPMOSTREE_POSTPROCESS_BOOT_LOCATION_BOTH: { g_print ("Using boot location: both\n"); if (!gs_file_rename (yumroot_boot, target_boot, cancellable, error)) goto out; /* Hardlink the existing content, only a little ugly as * we'll end up sha256'ing it twice, but oh well. */ if (!gs_shutil_cp_al_or_fallback (target_boot, target_usrlib_ostree_boot, cancellable, error)) goto out; } break; case RPMOSTREE_POSTPROCESS_BOOT_LOCATION_NEW: { g_print ("Using boot location: new\n"); if (!gs_file_rename (yumroot_boot, target_usrlib_ostree_boot, cancellable, error)) goto out; } break; } } /* Also carry along toplevel compat links */ g_print ("Copying toplevel compat symlinks\n"); { guint i; const char *toplevel_links[] = { "lib", "lib64", "lib32", "bin", "sbin" }; for (i = 0; i < G_N_ELEMENTS (toplevel_links); i++) { gs_unref_object GFile *srcpath = g_file_get_child (yumroot, toplevel_links[i]); if (g_file_query_file_type (srcpath, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) == G_FILE_TYPE_SYMBOLIC_LINK) { if (!move_to_dir (srcpath, targetroot, cancellable, error)) goto out; } } } g_print ("Adding tmpfiles-ostree-integration.conf\n"); { gs_unref_object GFile *src_pkglibdir = g_file_new_for_path (PKGLIBDIR); gs_unref_object GFile *src_tmpfilesd = g_file_get_child (src_pkglibdir, "tmpfiles-ostree-integration.conf"); gs_unref_object GFile *target_tmpfilesd = g_file_resolve_relative_path (targetroot, "usr/lib/tmpfiles.d/tmpfiles-ostree-integration.conf"); gs_unref_object GFile *target_tmpfilesd_parent = g_file_get_parent (target_tmpfilesd); if (!gs_file_ensure_directory (target_tmpfilesd_parent, TRUE, cancellable, error)) goto out; if (!g_file_copy (src_tmpfilesd, target_tmpfilesd, 0, cancellable, NULL, NULL, error)) goto out; } ret = TRUE; out: return ret; }
gboolean install_bundle (XdgAppDir *dir, GOptionContext *context, int argc, char **argv, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; g_autoptr(GFile) deploy_base = NULL; g_autoptr(GFile) file = NULL; g_autoptr(GFile) gpg_tmp_file = NULL; const char *filename; g_autofree char *ref = NULL; g_autofree char *origin = NULL; gboolean created_deploy_base = FALSE; gboolean added_remote = FALSE; g_autofree char *to_checksum = NULL; g_auto(GStrv) parts = NULL; g_autoptr(GBytes) gpg_data = NULL; g_autofree char *remote = NULL; OstreeRepo *repo; g_autoptr(OstreeGpgVerifyResult) gpg_result = NULL; g_autoptr(GError) my_error = NULL; g_auto(GLnxLockFile) lock = GLNX_LOCK_FILE_INIT; if (argc < 2) return usage_error (context, "bundle filename must be specified", error); filename = argv[1]; repo = xdg_app_dir_get_repo (dir); if (!xdg_app_supports_bundles (repo)) return xdg_app_fail (error, "Your version of ostree is too old to support single-file bundles"); if (!xdg_app_dir_lock (dir, &lock, cancellable, error)) goto out; file = g_file_new_for_commandline_arg (filename); { g_autoptr(GVariant) delta = NULL; g_autoptr(GVariant) metadata = NULL; g_autoptr(GBytes) bytes = NULL; g_autoptr(GVariant) to_csum_v = NULL; g_autoptr(GVariant) gpg_value = NULL; GMappedFile *mfile = g_mapped_file_new (gs_file_get_path_cached (file), FALSE, error); if (mfile == NULL) return FALSE; bytes = g_mapped_file_get_bytes (mfile); g_mapped_file_unref (mfile); delta = g_variant_new_from_bytes (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT), bytes, FALSE); g_variant_ref_sink (delta); to_csum_v = g_variant_get_child_value (delta, 3); if (!ostree_validate_structureof_csum_v (to_csum_v, error)) return FALSE; to_checksum = ostree_checksum_from_bytes_v (to_csum_v); metadata = g_variant_get_child_value (delta, 0); if (!g_variant_lookup (metadata, "ref", "s", &ref)) return xdg_app_fail (error, "Invalid bundle, no ref in metadata"); if (!g_variant_lookup (metadata, "origin", "s", &origin)) origin = NULL; gpg_value = g_variant_lookup_value (metadata, "gpg-keys", G_VARIANT_TYPE("ay")); if (gpg_value) { gsize n_elements; const char *data = g_variant_get_fixed_array (gpg_value, &n_elements, 1); gpg_data = g_bytes_new (data, n_elements); } } parts = xdg_app_decompose_ref (ref, error); if (parts == NULL) return FALSE; deploy_base = xdg_app_dir_get_deploy_dir (dir, ref); if (g_file_query_exists (deploy_base, cancellable)) return xdg_app_fail (error, "%s branch %s already installed", parts[1], parts[3]); if (opt_gpg_file != NULL) { /* Override gpg_data from file */ gpg_data = read_gpg_data (cancellable, error); if (gpg_data == NULL) return FALSE; } /* Add a remote for later updates */ if (origin != NULL) { g_auto(GStrv) remotes = ostree_repo_remote_list (repo, NULL); int version = 0; do { g_autofree char *name = NULL; if (version == 0) name = g_strdup_printf ("%s-origin", parts[1]); else name = g_strdup_printf ("%s-%d-origin", parts[1], version); version++; if (remotes == NULL || !g_strv_contains ((const char * const *) remotes, name)) remote = g_steal_pointer (&name); } while (remote == NULL); } if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error)) return FALSE; ostree_repo_transaction_set_ref (repo, remote, ref, to_checksum); if (!ostree_repo_static_delta_execute_offline (repo, file, FALSE, cancellable, error)) return FALSE; if (gpg_data) { g_autoptr(GFileIOStream) stream; GOutputStream *o; gpg_tmp_file = g_file_new_tmp (".xdg-app-XXXXXX", &stream, error); if (gpg_tmp_file == NULL) return FALSE; o = g_io_stream_get_output_stream (G_IO_STREAM (stream)); if (!g_output_stream_write_all (o, g_bytes_get_data (gpg_data, NULL), g_bytes_get_size (gpg_data), NULL, cancellable, error)) return FALSE; } gpg_result = ostree_repo_verify_commit_ext (repo, to_checksum, NULL, gpg_tmp_file, cancellable, &my_error); if (gpg_tmp_file) g_file_delete (gpg_tmp_file, cancellable, NULL); if (gpg_result == NULL) { /* NOT_FOUND means no gpg signature, we ignore this *if* there * is no gpg key specified in the bundle or by the user */ if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) && gpg_data == NULL) g_clear_error (&my_error); else { g_propagate_error (error, g_steal_pointer (&my_error)); return FALSE; } } else { /* If there is no valid gpg signature we fail, unless there is no gpg key specified (on the command line or in the file) because then we trust the source bundle. */ if (ostree_gpg_verify_result_count_valid (gpg_result) == 0 && gpg_data != NULL) return xdg_app_fail (error, "GPG signatures found, but none are in trusted keyring"); } if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error)) return FALSE; if (!g_file_make_directory_with_parents (deploy_base, cancellable, error)) return FALSE; /* From here we need to goto out on error, to clean up */ created_deploy_base = TRUE; if (remote) { g_autoptr(GVariantBuilder) optbuilder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); g_autofree char *basename = g_file_get_basename (file); g_variant_builder_add (optbuilder, "{s@v}", "xa.title", g_variant_new_variant (g_variant_new_string (basename))); g_variant_builder_add (optbuilder, "{s@v}", "xa.noenumerate", g_variant_new_variant (g_variant_new_boolean (TRUE))); g_variant_builder_add (optbuilder, "{s@v}", "xa.prio", g_variant_new_variant (g_variant_new_string ("0"))); if (!ostree_repo_remote_add (repo, remote, origin, g_variant_builder_end (optbuilder), cancellable, error)) goto out; added_remote = TRUE; if (gpg_data) { g_autoptr(GInputStream) gpg_data_as_stream = g_memory_input_stream_new_from_bytes (gpg_data); if (!ostree_repo_remote_gpg_import (repo, remote, gpg_data_as_stream, NULL, NULL, cancellable, error)) goto out; } if (!xdg_app_dir_set_origin (dir, ref, remote, cancellable, error)) goto out; } if (!xdg_app_dir_deploy (dir, ref, to_checksum, cancellable, error)) goto out; if (!xdg_app_dir_make_current_ref (dir, ref, cancellable, error)) goto out; if (strcmp (parts[0], "app") == 0) { if (!xdg_app_dir_update_exports (dir, parts[1], cancellable, error)) goto out; } glnx_release_lock_file (&lock); xdg_app_dir_cleanup_removed (dir, cancellable, NULL); if (!xdg_app_dir_mark_changed (dir, error)) goto out; ret = TRUE; out: if (created_deploy_base && !ret) gs_shutil_rm_rf (deploy_base, cancellable, NULL); if (added_remote && !ret) ostree_repo_remote_delete (repo, remote, NULL, NULL); return ret; }