gboolean _ostree_repo_write_ref (OstreeRepo *self, const char *remote, const char *ref, const char *rev, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; glnx_fd_close int dfd = -1; if (remote == NULL) { if (!glnx_opendirat (self->repo_dir_fd, "refs/heads", TRUE, &dfd, error)) goto out; } else { glnx_fd_close int refs_remotes_dfd = -1; if (!glnx_opendirat (self->repo_dir_fd, "refs/remotes", TRUE, &refs_remotes_dfd, error)) goto out; if (rev != NULL) { /* Ensure we have a dir for the remote */ if (!glnx_shutil_mkdir_p_at (refs_remotes_dfd, remote, 0777, cancellable, error)) goto out; } if (!glnx_opendirat (refs_remotes_dfd, remote, TRUE, &dfd, error)) goto out; } if (rev == NULL) { if (unlinkat (dfd, ref, 0) != 0) { if (errno != ENOENT) { glnx_set_error_from_errno (error); goto out; } } } else { if (!write_checksum_file_at (self, dfd, ref, rev, cancellable, error)) goto out; } if (!_ostree_repo_update_mtime (self, error)) goto out; ret = TRUE; out: return ret; }
/** * ot_gfile_replace_contents_fsync: * * Like g_file_replace_contents(), except always uses fdatasync(). */ gboolean ot_gfile_replace_contents_fsync (GFile *path, GBytes *contents, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; int parent_dfd; const char *target_basename = gs_file_get_basename_cached (path); g_autoptr(GFile) parent = NULL; parent = g_file_get_parent (path); if (!glnx_opendirat (AT_FDCWD, gs_file_get_path_cached (parent), TRUE, &parent_dfd, error)) goto out; if (!ot_file_replace_contents_at (parent_dfd, target_basename, contents, TRUE, cancellable, error)) goto out; ret = TRUE; out: if (parent_dfd != -1) (void) close (parent_dfd); return ret; }
static gboolean ensure_sysroot_fd (OstreeSysroot *self, GError **error) { if (self->sysroot_fd == -1) { if (!glnx_opendirat (AT_FDCWD, gs_file_get_path_cached (self->path), TRUE, &self->sysroot_fd, error)) return FALSE; } return TRUE; }
int rpmostree_internals_builtin_unpack (int argc, char **argv, GCancellable *cancellable, GError **error) { int exit_status = EXIT_FAILURE; GOptionContext *context = g_option_context_new ("ROOT RPM"); RpmOstreeUnpackerFlags flags = 0; glnx_unref_object RpmOstreeUnpacker *unpacker = NULL; const char *rpmpath; glnx_fd_close int rootfs_fd = -1; if (!rpmostree_option_context_parse (context, option_entries, &argc, &argv, RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD, cancellable, NULL, error)) goto out; if (argc < 3) { rpmostree_usage_error (context, "ROOT and RPM must be specified", error); goto out; } if (!glnx_opendirat (AT_FDCWD, argv[1], TRUE, &rootfs_fd, error)) goto out; rpmpath = argv[2]; /* suid implies owner too...anything else is dangerous, as we might write * a setuid binary for the caller. */ if (opt_owner || opt_suid_fcaps) flags |= RPMOSTREE_UNPACKER_FLAGS_OWNER; if (opt_suid_fcaps) flags |= RPMOSTREE_UNPACKER_FLAGS_SUID_FSCAPS; unpacker = rpmostree_unpacker_new_at (AT_FDCWD, rpmpath, flags, error); if (!unpacker) goto out; if (!rpmostree_unpacker_unpack_to_dfd (unpacker, rootfs_fd, cancellable, error)) goto out; exit_status = EXIT_SUCCESS; out: return exit_status; }
static gboolean roc_context_init (ROContainerContext *rocctx, GError **error) { gboolean ret = FALSE; if (!roc_context_init_core (rocctx, error)) goto out; if (!glnx_opendirat (rocctx->userroot_dfd, "roots", TRUE, &rocctx->roots_dfd, error)) goto out; if (!ostree_repo_open (rocctx->repo, NULL, error)) goto out; if (!glnx_opendirat (rocctx->userroot_dfd, "cache/rpm-md", FALSE, &rocctx->rpmmd_dfd, error)) goto out; ret = TRUE; out: return ret; }
static gboolean roc_context_init_core (ROContainerContext *rocctx, GError **error) { gboolean ret = FALSE; rocctx->userroot_base = get_current_dir_name (); if (!glnx_opendirat (AT_FDCWD, rocctx->userroot_base, TRUE, &rocctx->userroot_dfd, error)) goto out; { g_autofree char *repo_pathstr = g_strconcat (rocctx->userroot_base, "/repo", NULL); g_autoptr(GFile) repo_path = g_file_new_for_path (repo_pathstr); rocctx->repo = ostree_repo_new (repo_path); } ret = TRUE; out: return ret; }
/** * glnx_dirfd_iterator_init_at: * @dfd: File descriptor, may be AT_FDCWD or -1 * @path: Path, may be relative to @df * @follow: If %TRUE and the last component of @path is a symlink, follow it * @out_dfd_iter: (out caller-allocates): A directory iterator, will be initialized * @error: Error * * Initialize @out_dfd_iter from @dfd and @path. */ gboolean glnx_dirfd_iterator_init_at (int dfd, const char *path, gboolean follow, GLnxDirFdIterator *out_dfd_iter, GError **error) { gboolean ret = FALSE; glnx_fd_close int fd = -1; if (!glnx_opendirat (dfd, path, follow, &fd, error)) goto out; if (!glnx_dirfd_iterator_init_take_fd (fd, out_dfd_iter, error)) goto out; fd = -1; /* Transfer ownership */ ret = TRUE; out: return ret; }
static gboolean find_ref_in_remotes (OstreeRepo *self, const char *rev, int *out_fd, GError **error) { g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; glnx_fd_close int ret_fd = -1; if (!glnx_dirfd_iterator_init_at (self->repo_dir_fd, "refs/remotes", TRUE, &dfd_iter, error)) return FALSE; while (TRUE) { struct dirent *dent = NULL; glnx_fd_close int remote_dfd = -1; if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, NULL, error)) return FALSE; if (dent == NULL) break; if (dent->d_type != DT_DIR) continue; if (!glnx_opendirat (dfd_iter.fd, dent->d_name, TRUE, &remote_dfd, error)) return FALSE; if (!ot_openat_ignore_enoent (remote_dfd, rev, &ret_fd, error)) return FALSE; if (ret_fd != -1) break; } *out_fd = ret_fd; ret_fd = -1; return TRUE; }
/* Execute /bin/true inside a bwrap container on the host */ gboolean rpmostree_bwrap_selftest (GError **error) { glnx_fd_close int host_root_dfd = -1; g_autoptr(RpmOstreeBwrap) bwrap = NULL; if (!glnx_opendirat (AT_FDCWD, "/", TRUE, &host_root_dfd, error)) return FALSE; bwrap = rpmostree_bwrap_new (host_root_dfd, RPMOSTREE_BWRAP_IMMUTABLE, error, NULL); if (!bwrap) return FALSE; rpmostree_bwrap_append_child_argv (bwrap, "true", NULL); if (!rpmostree_bwrap_run (bwrap, error)) { g_prefix_error (error, "bwrap test failed, see <https://github.com/projectatomic/rpm-ostree/pull/429>: "); return FALSE; } return TRUE; }
/** * ot_util_fsync_directory: * @dir: Path to a directory * @cancellable: Cancellable * @error: Error * * Ensure that all entries in directory @dir are on disk. */ gboolean ot_util_fsync_directory (GFile *dir, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; int dfd = -1; if (!glnx_opendirat (AT_FDCWD, gs_file_get_path_cached (dir), TRUE, &dfd, error)) goto out; if (fsync (dfd) != 0) { glnx_set_error_from_errno (error); goto out; } ret = TRUE; out: if (dfd != -1) (void) close (dfd); return ret; }
static gboolean export_dir (int source_parent_fd, const char *source_name, const char *source_relpath, int destination_parent_fd, const char *destination_name, const char *required_prefix, GCancellable *cancellable, GError **error) { int res; g_auto(GLnxDirFdIterator) source_iter = {0}; glnx_fd_close int destination_dfd = -1; struct dirent *dent; if (!glnx_dirfd_iterator_init_at (source_parent_fd, source_name, FALSE, &source_iter, error)) return FALSE; do res = mkdirat (destination_parent_fd, destination_name, 0755); while (G_UNLIKELY (res == -1 && errno == EINTR)); if (res == -1) { if (errno != EEXIST) { glnx_set_error_from_errno (error); return FALSE; } } if (!glnx_opendirat (destination_parent_fd, destination_name, TRUE, &destination_dfd, error)) return FALSE; while (TRUE) { struct stat stbuf; g_autofree char *source_printable = NULL; if (!glnx_dirfd_iterator_next_dent (&source_iter, &dent, cancellable, error)) return FALSE; if (dent == NULL) break; if (fstatat (source_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) == -1) { if (errno == ENOENT) { continue; } else { glnx_set_error_from_errno (error); return FALSE; } } /* Don't export any hidden files or backups */ if (g_str_has_prefix (dent->d_name, ".") || g_str_has_suffix (dent->d_name, "~")) continue; if (S_ISDIR (stbuf.st_mode)) { g_autofree gchar *child_relpath = g_build_filename (source_relpath, dent->d_name, NULL); if (!export_dir (source_iter.fd, dent->d_name, child_relpath, destination_dfd, dent->d_name, required_prefix, cancellable, error)) return FALSE; } else if (S_ISREG (stbuf.st_mode)) { source_printable = g_build_filename (source_relpath, dent->d_name, NULL); if (!flatpak_has_name_prefix (dent->d_name, required_prefix)) { g_print ("Not exporting %s, wrong prefix\n", source_printable); continue; } g_print ("Exporting %s\n", source_printable); if (!glnx_file_copy_at (source_iter.fd, dent->d_name, &stbuf, destination_dfd, dent->d_name, GLNX_FILE_COPY_NOXATTRS, cancellable, error)) return FALSE; } else { source_printable = g_build_filename (source_relpath, dent->d_name, NULL); g_debug ("Not exporting non-regular file %s\n", source_printable); } } /* Try to remove the directory, as we don't want to export empty directories. * However, don't fail if the unlink fails due to the directory not being empty */ do res = unlinkat (destination_parent_fd, destination_name, AT_REMOVEDIR); while (G_UNLIKELY (res == -1 && errno == EINTR)); if (res == -1) { if (errno != ENOTEMPTY) { glnx_set_error_from_errno (error); return FALSE; } } return TRUE; }
/* 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; }
/** * ostree_repo_static_delta_execute_offline: * @self: Repo * @dir_or_file: Path to a directory containing static delta data, or directly to the superblock * @skip_validation: If %TRUE, assume data integrity * @cancellable: Cancellable * @error: Error * * Given a directory representing an already-downloaded static delta * on disk, apply it, generating a new commit. The directory must be * named with the form "FROM-TO", where both are checksums, and it * must contain a file named "superblock", along with at least one part. */ gboolean ostree_repo_static_delta_execute_offline (OstreeRepo *self, GFile *dir_or_file, gboolean skip_validation, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; guint i, n; const char *dir_or_file_path = NULL; glnx_fd_close int meta_fd = -1; glnx_fd_close int dfd = -1; g_autoptr(GVariant) meta = NULL; g_autoptr(GVariant) headers = NULL; g_autoptr(GVariant) metadata = NULL; g_autoptr(GVariant) fallback = NULL; g_autofree char *to_checksum = NULL; g_autofree char *from_checksum = NULL; g_autofree char *basename = NULL; dir_or_file_path = gs_file_get_path_cached (dir_or_file); /* First, try opening it as a directory */ dfd = glnx_opendirat_with_errno (AT_FDCWD, dir_or_file_path, TRUE); if (dfd < 0) { if (errno != ENOTDIR) { glnx_set_error_from_errno (error); goto out; } else { g_autofree char *dir = dirname (g_strdup (dir_or_file_path)); basename = g_path_get_basename (dir_or_file_path); if (!glnx_opendirat (AT_FDCWD, dir, TRUE, &dfd, error)) goto out; } } else basename = g_strdup ("superblock"); meta_fd = openat (dfd, basename, O_RDONLY | O_CLOEXEC); if (meta_fd < 0) { glnx_set_error_from_errno (error); goto out; } if (!ot_util_variant_map_fd (meta_fd, 0, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT), FALSE, &meta, error)) goto out; /* Parsing OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT */ metadata = g_variant_get_child_value (meta, 0); /* Write the to-commit object */ { g_autoptr(GVariant) to_csum_v = NULL; g_autoptr(GVariant) from_csum_v = NULL; g_autoptr(GVariant) to_commit = NULL; gboolean have_to_commit; gboolean have_from_commit; to_csum_v = g_variant_get_child_value (meta, 3); if (!ostree_validate_structureof_csum_v (to_csum_v, error)) goto out; to_checksum = ostree_checksum_from_bytes_v (to_csum_v); from_csum_v = g_variant_get_child_value (meta, 2); if (g_variant_n_children (from_csum_v) > 0) { if (!ostree_validate_structureof_csum_v (from_csum_v, error)) goto out; from_checksum = ostree_checksum_from_bytes_v (from_csum_v); if (!ostree_repo_has_object (self, OSTREE_OBJECT_TYPE_COMMIT, from_checksum, &have_from_commit, cancellable, error)) goto out; if (!have_from_commit) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Commit %s, which is the delta source, is not in repository", from_checksum); goto out; } } if (!ostree_repo_has_object (self, OSTREE_OBJECT_TYPE_COMMIT, to_checksum, &have_to_commit, cancellable, error)) goto out; if (!have_to_commit) { g_autofree char *detached_path = _ostree_get_relative_static_delta_path (from_checksum, to_checksum, "commitmeta"); g_autoptr(GVariant) detached_data = NULL; detached_data = g_variant_lookup_value (metadata, detached_path, G_VARIANT_TYPE("a{sv}")); if (detached_data && !ostree_repo_write_commit_detached_metadata (self, to_checksum, detached_data, cancellable, error)) goto out; to_commit = g_variant_get_child_value (meta, 4); if (!ostree_repo_write_metadata (self, OSTREE_OBJECT_TYPE_COMMIT, to_checksum, to_commit, NULL, cancellable, error)) goto out; } } fallback = g_variant_get_child_value (meta, 7); if (g_variant_n_children (fallback) > 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Cannot execute delta offline: contains nonempty http fallback entries"); goto out; } headers = g_variant_get_child_value (meta, 6); n = g_variant_n_children (headers); for (i = 0; i < n; i++) { guint32 version; guint64 size; guint64 usize; const guchar *csum; char checksum[OSTREE_SHA256_STRING_LEN+1]; gboolean have_all; g_autoptr(GInputStream) part_in = NULL; g_autoptr(GVariant) inline_part_data = NULL; g_autoptr(GVariant) header = NULL; g_autoptr(GVariant) csum_v = NULL; g_autoptr(GVariant) objects = NULL; g_autoptr(GVariant) part = NULL; g_autofree char *deltapart_path = NULL; OstreeStaticDeltaOpenFlags delta_open_flags = skip_validation ? OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM : 0; header = g_variant_get_child_value (headers, i); g_variant_get (header, "(u@aytt@ay)", &version, &csum_v, &size, &usize, &objects); if (version > OSTREE_DELTAPART_VERSION) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Delta part has too new version %u", version); goto out; } if (!_ostree_repo_static_delta_part_have_all_objects (self, objects, &have_all, cancellable, error)) goto out; /* If we already have these objects, don't bother executing the * static delta. */ if (have_all) continue; csum = ostree_checksum_bytes_peek_validate (csum_v, error); if (!csum) goto out; ostree_checksum_inplace_from_bytes (csum, checksum); deltapart_path = _ostree_get_relative_static_delta_part_path (from_checksum, to_checksum, i); inline_part_data = g_variant_lookup_value (metadata, deltapart_path, G_VARIANT_TYPE("(yay)")); if (inline_part_data) { g_autoptr(GBytes) inline_part_bytes = g_variant_get_data_as_bytes (inline_part_data); part_in = g_memory_input_stream_new_from_bytes (inline_part_bytes); /* For inline parts, we don't checksum, because it's * included with the metadata, so we're not trying to * protect against MITM or such. Non-security related * checksums should be done at the underlying storage layer. */ delta_open_flags |= OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM; if (!_ostree_static_delta_part_open (part_in, inline_part_bytes, delta_open_flags, NULL, &part, cancellable, error)) goto out; } else { g_autofree char *relpath = g_strdup_printf ("%u", i); /* TODO avoid malloc here */ glnx_fd_close int part_fd = openat (dfd, relpath, O_RDONLY | O_CLOEXEC); if (part_fd < 0) { glnx_set_error_from_errno (error); g_prefix_error (error, "Opening deltapart '%s': ", deltapart_path); goto out; } part_in = g_unix_input_stream_new (part_fd, FALSE); if (!_ostree_static_delta_part_open (part_in, NULL, delta_open_flags, checksum, &part, cancellable, error)) goto out; } if (!_ostree_static_delta_part_execute (self, objects, part, skip_validation, NULL, cancellable, error)) { g_prefix_error (error, "Executing delta part %i: ", i); goto out; } } ret = TRUE; out: return ret; }
static gboolean run (int argc, char **argv, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; g_autoptr(GOptionContext) context = NULL; const char *dirpath; OtTrivialHttpd appstruct = { 0, }; OtTrivialHttpd *app = &appstruct; glnx_unref_object SoupServer *server = NULL; g_autoptr(GFileMonitor) dirmon = NULL; context = g_option_context_new ("[DIR] - Simple webserver"); g_option_context_add_main_entries (context, options, NULL); app->root_dfd = -1; if (!g_option_context_parse (context, &argc, &argv, error)) goto out; if (argc > 1) dirpath = argv[1]; else dirpath = "."; if (!glnx_opendirat (AT_FDCWD, dirpath, TRUE, &app->root_dfd, error)) goto out; if (!(opt_random_500s_percentage >= 0 && opt_random_500s_percentage <= 99)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid --random-500s=%u", opt_random_500s_percentage); goto out; } if (opt_log) { GOutputStream *stream = NULL; if (g_strcmp0 (opt_log, "-") == 0) { if (opt_daemonize) { ot_util_usage_error (context, "Cannot use --log-file=- and --daemonize at the same time", error); goto out; } stream = G_OUTPUT_STREAM (g_unix_output_stream_new (STDOUT_FILENO, FALSE)); } else { g_autoptr(GFile) log_file = NULL; GFileOutputStream* log_stream; log_file = g_file_new_for_path (opt_log); log_stream = g_file_create (log_file, G_FILE_CREATE_PRIVATE, cancellable, error); if (!log_stream) goto out; stream = G_OUTPUT_STREAM (log_stream); } app->log = stream; } #if SOUP_CHECK_VERSION(2, 48, 0) server = soup_server_new (SOUP_SERVER_SERVER_HEADER, "ostree-httpd ", NULL); if (!soup_server_listen_all (server, opt_port, 0, error)) goto out; #else server = soup_server_new (SOUP_SERVER_PORT, opt_port, SOUP_SERVER_SERVER_HEADER, "ostree-httpd ", NULL); #endif soup_server_add_handler (server, NULL, httpd_callback, app, NULL); if (opt_port_file) { g_autofree char *portstr = NULL; #if SOUP_CHECK_VERSION(2, 48, 0) GSList *listeners = soup_server_get_listeners (server); g_autoptr(GSocket) listener = NULL; g_autoptr(GSocketAddress) addr = NULL; g_assert (listeners); listener = g_object_ref (listeners->data); g_slist_free (listeners); listeners = NULL; addr = g_socket_get_local_address (listener, error); if (!addr) goto out; g_assert (G_IS_INET_SOCKET_ADDRESS (addr)); portstr = g_strdup_printf ("%u\n", g_inet_socket_address_get_port ((GInetSocketAddress*)addr)); #else portstr = g_strdup_printf ("%u\n", soup_server_get_port (server)); #endif if (g_strcmp0 ("-", opt_port_file) == 0) { fputs (portstr, stdout); // not g_print - this must go to stdout, not a handler fflush (stdout); } else if (!g_file_set_contents (opt_port_file, portstr, strlen (portstr), error)) goto out; } #if !SOUP_CHECK_VERSION(2, 48, 0) soup_server_run_async (server); #endif if (opt_daemonize) { pid_t pid = fork(); if (pid == -1) { int errsv = errno; g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv), g_strerror (errsv)); goto out; } else if (pid > 0) { ret = TRUE; goto out; } /* Child, continue */ if (setsid () < 0) err (1, "setsid"); /* Daemonising: close stdout/stderr so $() et al work on us */ if (freopen("/dev/null", "r", stdin) == NULL) err (1, "freopen"); if (freopen("/dev/null", "w", stdout) == NULL) err (1, "freopen"); if (freopen("/dev/null", "w", stderr) == NULL) err (1, "freopen"); } else { /* Since we're used for testing purposes, let's just do this by * default. This ensures we exit when our parent does. */ if (prctl (PR_SET_PDEATHSIG, SIGTERM) != 0) { if (errno != ENOSYS) { glnx_set_error_from_errno (error); goto out; } } } app->running = TRUE; if (opt_autoexit) { gboolean is_symlink = FALSE; g_autoptr(GFile) root = NULL; g_autoptr(GFileInfo) info = NULL; root = g_file_new_for_path (dirpath); info = g_file_query_info (root, G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, cancellable, error); if (!info) goto out; is_symlink = g_file_info_get_is_symlink (info); if (is_symlink) dirmon = g_file_monitor_file (root, 0, cancellable, error); else dirmon = g_file_monitor_directory (root, 0, cancellable, error); if (!dirmon) goto out; g_signal_connect (dirmon, "changed", G_CALLBACK (on_dir_changed), app); } httpd_log (app, "serving at root %s\n", dirpath); while (app->running) g_main_context_iteration (NULL, TRUE); ret = TRUE; out: if (app->root_dfd != -1) (void) close (app->root_dfd); g_clear_object (&app->log); return ret; }
static gboolean _ostree_repo_list_refs_internal (OstreeRepo *self, gboolean cut_prefix, const char *refspec_prefix, GHashTable **out_all_refs, GCancellable *cancellable, GError **error) { g_autoptr(GHashTable) ret_all_refs = NULL; g_autofree char *remote = NULL; g_autofree char *ref_prefix = NULL; ret_all_refs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); if (refspec_prefix) { struct stat stbuf; const char *prefix_path; const char *path; if (!ostree_parse_refspec (refspec_prefix, &remote, &ref_prefix, error)) return FALSE; if (remote) { prefix_path = glnx_strjoina ("refs/remotes/", remote, "/"); path = glnx_strjoina (prefix_path, ref_prefix); } else { prefix_path = "refs/heads/"; path = glnx_strjoina (prefix_path, ref_prefix); } if (fstatat (self->repo_dir_fd, path, &stbuf, 0) < 0) { if (errno != ENOENT) return glnx_throw_errno (error); } else { if (S_ISDIR (stbuf.st_mode)) { glnx_fd_close int base_fd = -1; g_autoptr(GString) base_path = g_string_new (""); if (!cut_prefix) g_string_printf (base_path, "%s/", ref_prefix); if (!glnx_opendirat (self->repo_dir_fd, cut_prefix ? path : prefix_path, TRUE, &base_fd, error)) return FALSE; if (!enumerate_refs_recurse (self, remote, base_fd, base_path, base_fd, cut_prefix ? "." : ref_prefix, ret_all_refs, cancellable, error)) return FALSE; } else { glnx_fd_close int prefix_dfd = -1; if (!glnx_opendirat (self->repo_dir_fd, prefix_path, TRUE, &prefix_dfd, error)) return FALSE; if (!add_ref_to_set (remote, prefix_dfd, ref_prefix, ret_all_refs, cancellable, error)) return FALSE; } } } else { g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; g_autoptr(GString) base_path = g_string_new (""); glnx_fd_close int refs_heads_dfd = -1; if (!glnx_opendirat (self->repo_dir_fd, "refs/heads", TRUE, &refs_heads_dfd, error)) return FALSE; if (!enumerate_refs_recurse (self, NULL, refs_heads_dfd, base_path, refs_heads_dfd, ".", ret_all_refs, cancellable, error)) return FALSE; g_string_truncate (base_path, 0); if (!glnx_dirfd_iterator_init_at (self->repo_dir_fd, "refs/remotes", TRUE, &dfd_iter, error)) return FALSE; while (TRUE) { struct dirent *dent; glnx_fd_close int remote_dfd = -1; if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error)) return FALSE; if (!dent) break; if (dent->d_type != DT_DIR) continue; if (!glnx_opendirat (dfd_iter.fd, dent->d_name, TRUE, &remote_dfd, error)) return FALSE; if (!enumerate_refs_recurse (self, dent->d_name, remote_dfd, base_path, remote_dfd, ".", ret_all_refs, cancellable, error)) return FALSE; } } ot_transfer_out_value (out_all_refs, &ret_all_refs); return TRUE; }
gboolean _ostree_repo_write_ref (OstreeRepo *self, const char *remote, const char *ref, const char *rev, GCancellable *cancellable, GError **error) { glnx_fd_close int dfd = -1; if (remote == NULL) { if (!glnx_opendirat (self->repo_dir_fd, "refs/heads", TRUE, &dfd, error)) { g_prefix_error (error, "Opening %s: ", "refs/heads"); return FALSE; } } else { glnx_fd_close int refs_remotes_dfd = -1; if (!glnx_opendirat (self->repo_dir_fd, "refs/remotes", TRUE, &refs_remotes_dfd, error)) { g_prefix_error (error, "Opening %s: ", "refs/remotes"); return FALSE; } if (rev != NULL) { /* Ensure we have a dir for the remote */ if (!glnx_shutil_mkdir_p_at (refs_remotes_dfd, remote, 0777, cancellable, error)) return FALSE; } dfd = glnx_opendirat_with_errno (refs_remotes_dfd, remote, TRUE); if (dfd < 0 && (errno != ENOENT || rev != NULL)) return glnx_throw_errno_prefix (error, "Opening remotes/ dir %s", remote); } if (rev == NULL) { if (dfd >= 0) { if (unlinkat (dfd, ref, 0) != 0) { if (errno != ENOENT) return glnx_throw_errno (error); } } } else { if (!write_checksum_file_at (self, dfd, ref, rev, cancellable, error)) return FALSE; } if (!_ostree_repo_update_mtime (self, error)) return FALSE; return TRUE; }
gboolean rpmostree_container_builtin_upgrade (int argc, char **argv, GCancellable *cancellable, GError **error) { int exit_status = EXIT_FAILURE; GOptionContext *context = g_option_context_new ("NAME"); g_auto(ROContainerContext) rocctx_data = RO_CONTAINER_CONTEXT_INIT; ROContainerContext *rocctx = &rocctx_data; g_autoptr(RpmOstreeInstall) install = NULL; const char *name; g_autofree char *commit_checksum = NULL; g_autofree char *new_commit_checksum = NULL; g_autoptr(GVariant) commit = NULL; g_autoptr(GVariant) metadata = NULL; g_autoptr(GVariant) input_packages_v = NULL; g_autoptr(RpmOstreeTreespec) treespec = NULL; guint current_version; guint new_version; g_autofree char *previous_state_sha512 = NULL; const char *target_current_root; const char *target_new_root; 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, "NAME must be specified", error); goto out; } name = argv[1]; if (!roc_context_init (rocctx, error)) goto out; target_current_root = glnx_readlinkat_malloc (rocctx->roots_dfd, name, cancellable, error); if (!target_current_root) { g_prefix_error (error, "Reading app link %s: ", name); goto out; } if (!parse_app_version (target_current_root, ¤t_version, error)) goto out; { g_autoptr(GVariantDict) metadata_dict = NULL; g_autoptr(GVariant) spec_v = NULL; g_autoptr(GVariant) previous_sha512_v = NULL; if (!ostree_repo_resolve_rev (rocctx->repo, name, FALSE, &commit_checksum, error)) goto out; if (!ostree_repo_load_variant (rocctx->repo, OSTREE_OBJECT_TYPE_COMMIT, commit_checksum, &commit, error)) goto out; metadata = g_variant_get_child_value (commit, 0); metadata_dict = g_variant_dict_new (metadata); spec_v = _rpmostree_vardict_lookup_value_required (metadata_dict, "rpmostree.spec", (GVariantType*)"a{sv}", error); if (!spec_v) goto out; treespec = rpmostree_treespec_new (spec_v); previous_sha512_v = _rpmostree_vardict_lookup_value_required (metadata_dict, "rpmostree.state-sha512", (GVariantType*)"s", error); if (!previous_sha512_v) goto out; previous_state_sha512 = g_variant_dup_string (previous_sha512_v, NULL); } new_version = current_version == 0 ? 1 : 0; if (new_version == 0) target_new_root = glnx_strjoina (name, ".0"); else target_new_root = glnx_strjoina (name, ".1"); if (!roc_context_prepare_for_root (rocctx, name, 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; { g_autofree char *new_state_sha512 = rpmostree_context_get_state_sha512 (rocctx->ctx); if (strcmp (new_state_sha512, previous_state_sha512) == 0) { g_print ("No changes in inputs to %s (%s)\n", name, commit_checksum); exit_status = EXIT_SUCCESS; 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, &new_commit_checksum, cancellable, error)) goto out; } g_print ("Checking out %s @ %s...\n", name, new_commit_checksum); { 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; if (!ostree_repo_checkout_tree_at (rocctx->repo, &opts, rocctx->roots_dfd, target_new_root, new_commit_checksum, cancellable, error)) goto out; } g_print ("Checking out %s @ %s...done\n", name, new_commit_checksum); if (!symlink_at_replace (target_new_root, rocctx->roots_dfd, name, cancellable, error)) goto out; g_print ("Creating current symlink...done\n"); exit_status = EXIT_SUCCESS; out: return exit_status; }
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; }
/* * checkout_tree_at: * @self: Repo * @mode: Options controlling all files * @overwrite_mode: Whether or not to overwrite files * @destination_parent_fd: Place tree here * @destination_name: Use this name for tree * @source: Source tree * @source_info: Source info * @cancellable: Cancellable * @error: Error * * Like ostree_repo_checkout_tree(), but check out @source into the * relative @destination_name, located by @destination_parent_fd. */ static gboolean checkout_tree_at (OstreeRepo *self, OstreeRepoCheckoutOptions *options, int destination_parent_fd, const char *destination_name, OstreeRepoFile *source, GFileInfo *source_info, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; gboolean did_exist = FALSE; glnx_fd_close int destination_dfd = -1; int res; g_autoptr(GVariant) xattrs = NULL; g_autoptr(GFileEnumerator) dir_enum = NULL; /* Create initially with mode 0700, then chown/chmod only when we're * done. This avoids anyone else being able to operate on partially * constructed dirs. */ do res = mkdirat (destination_parent_fd, destination_name, 0700); while (G_UNLIKELY (res == -1 && errno == EINTR)); if (res == -1) { if (errno == EEXIST && options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES) did_exist = TRUE; else { glnx_set_error_from_errno (error); goto out; } } if (!glnx_opendirat (destination_parent_fd, destination_name, TRUE, &destination_dfd, error)) goto out; /* Set the xattrs now, so any derived labeling works */ if (!did_exist && options->mode != OSTREE_REPO_CHECKOUT_MODE_USER) { if (!ostree_repo_file_get_xattrs (source, &xattrs, NULL, error)) goto out; if (xattrs) { if (!glnx_fd_set_all_xattrs (destination_dfd, xattrs, cancellable, error)) goto out; } } if (g_file_info_get_file_type (source_info) != G_FILE_TYPE_DIRECTORY) { ret = checkout_one_file_at (self, options, (GFile *) source, source_info, destination_dfd, g_file_info_get_name (source_info), cancellable, error); goto out; } dir_enum = g_file_enumerate_children ((GFile*)source, OSTREE_GIO_FAST_QUERYINFO, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, cancellable, error); if (!dir_enum) goto out; while (TRUE) { GFileInfo *file_info; GFile *src_child; const char *name; if (!g_file_enumerator_iterate (dir_enum, &file_info, &src_child, cancellable, error)) goto out; if (file_info == NULL) break; name = g_file_info_get_name (file_info); if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY) { if (!checkout_tree_at (self, options, destination_dfd, name, (OstreeRepoFile*)src_child, file_info, cancellable, error)) goto out; } else { if (!checkout_one_file_at (self, options, src_child, file_info, destination_dfd, name, cancellable, error)) goto out; } } /* We do fchmod/fchown last so that no one else could access the * partially created directory and change content we're laying out. */ if (!did_exist) { do res = fchmod (destination_dfd, g_file_info_get_attribute_uint32 (source_info, "unix::mode")); while (G_UNLIKELY (res == -1 && errno == EINTR)); if (G_UNLIKELY (res == -1)) { glnx_set_error_from_errno (error); goto out; } } if (!did_exist && options->mode != OSTREE_REPO_CHECKOUT_MODE_USER) { do res = fchown (destination_dfd, g_file_info_get_attribute_uint32 (source_info, "unix::uid"), g_file_info_get_attribute_uint32 (source_info, "unix::gid")); while (G_UNLIKELY (res == -1 && errno == EINTR)); if (G_UNLIKELY (res == -1)) { glnx_set_error_from_errno (error); goto out; } } /* Set directory mtime to OSTREE_TIMESTAMP, so that it is constant for all checkouts. * Must be done after setting permissions and creating all children. */ if (!did_exist) { const struct timespec times[2] = { { OSTREE_TIMESTAMP, UTIME_OMIT }, { OSTREE_TIMESTAMP, 0} }; do res = futimens (destination_dfd, times); while (G_UNLIKELY (res == -1 && errno == EINTR)); if (G_UNLIKELY (res == -1)) { glnx_set_error_from_errno (error); goto out; } } if (fsync_is_enabled (self, options)) { if (fsync (destination_dfd) == -1) { glnx_set_error_from_errno (error); goto out; } } ret = TRUE; out: return ret; }
int rpmostree_internals_builtin_unpack (int argc, char **argv, GCancellable *cancellable, GError **error) { int exit_status = EXIT_FAILURE; GOptionContext *context = g_option_context_new ("ROOT RPM"); RpmOstreeUnpackerFlags flags = 0; glnx_unref_object RpmOstreeUnpacker *unpacker = NULL; const char *target; const char *rpmpath; glnx_fd_close int rootfs_fd = -1; glnx_unref_object OstreeRepo *ostree_repo = NULL; if (!rpmostree_option_context_parse (context, option_entries, &argc, &argv, RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD, cancellable, NULL, error)) goto out; if (argc < 3) { rpmostree_usage_error (context, "TARGET and RPM must be specified", error); goto out; } target = argv[1]; rpmpath = argv[2]; if (opt_to_ostree_repo) { g_autoptr(GFile) to_ostree_repo_file = g_file_new_for_path (target); ostree_repo = ostree_repo_new (to_ostree_repo_file); if (!ostree_repo_open (ostree_repo, cancellable, error)) goto out; } else { if (!glnx_opendirat (AT_FDCWD, argv[1], TRUE, &rootfs_fd, error)) goto out; } /* suid implies owner too...anything else is dangerous, as we might write * a setuid binary for the caller. */ if (opt_owner || opt_suid_fcaps) flags |= RPMOSTREE_UNPACKER_FLAGS_OWNER; if (opt_suid_fcaps) flags |= RPMOSTREE_UNPACKER_FLAGS_SUID_FSCAPS; unpacker = rpmostree_unpacker_new_at (AT_FDCWD, rpmpath, flags, error); if (!unpacker) goto out; if (opt_to_ostree_repo) { const char *branch = rpmostree_unpacker_get_ostree_branch (unpacker); g_autofree char *checksum = NULL; if (!rpmostree_unpacker_unpack_to_ostree (unpacker, ostree_repo, NULL, &checksum, cancellable, error)) goto out; g_print ("Imported %s to %s -> %s\n", rpmpath, branch, checksum); } else { if (!rpmostree_unpacker_unpack_to_dfd (unpacker, rootfs_fd, cancellable, error)) goto out; } exit_status = EXIT_SUCCESS; out: return exit_status; }
static gboolean parse_deployment (OstreeSysroot *self, const char *boot_link, OstreeDeployment **out_deployment, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; const char *relative_boot_link; glnx_unref_object OstreeDeployment *ret_deployment = NULL; int entry_boot_version; int treebootserial = -1; int deployserial = -1; g_autofree char *osname = NULL; g_autofree char *bootcsum = NULL; g_autofree char *treecsum = NULL; glnx_fd_close int deployment_dfd = -1; const char *deploy_basename; g_autofree char *treebootserial_target = NULL; g_autofree char *deploy_dir = NULL; GKeyFile *origin = NULL; if (!ensure_sysroot_fd (self, error)) goto out; if (!parse_bootlink (boot_link, &entry_boot_version, &osname, &bootcsum, &treebootserial, error)) goto out; relative_boot_link = boot_link; if (*relative_boot_link == '/') relative_boot_link++; treebootserial_target = glnx_readlinkat_malloc (self->sysroot_fd, relative_boot_link, cancellable, error); if (!treebootserial_target) goto out; deploy_basename = glnx_basename (treebootserial_target); if (!_ostree_sysroot_parse_deploy_path_name (deploy_basename, &treecsum, &deployserial, error)) goto out; if (!glnx_opendirat (self->sysroot_fd, relative_boot_link, TRUE, &deployment_dfd, error)) goto out; if (!parse_origin (self, deployment_dfd, deploy_basename, &origin, cancellable, error)) goto out; ret_deployment = ostree_deployment_new (-1, osname, treecsum, deployserial, bootcsum, treebootserial); if (origin) ostree_deployment_set_origin (ret_deployment, origin); ret = TRUE; gs_transfer_out_value (out_deployment, &ret_deployment); out: if (origin) g_key_file_unref (origin); return ret; }
/* TODO: Add a man page. */ gboolean ostree_builtin_create_usb (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) { g_autoptr(GOptionContext) context = NULL; g_autoptr(OstreeAsyncProgress) progress = NULL; g_auto(GLnxConsoleRef) console = { 0, }; context = g_option_context_new ("MOUNT-PATH COLLECTION-ID REF [COLLECTION-ID REF...]"); /* Parse options. */ g_autoptr(OstreeRepo) src_repo = NULL; if (!ostree_option_context_parse (context, options, &argc, &argv, invocation, &src_repo, cancellable, error)) return FALSE; if (argc < 2) { ot_util_usage_error (context, "A MOUNT-PATH must be specified", error); return FALSE; } if (argc < 4) { ot_util_usage_error (context, "At least one COLLECTION-ID REF pair must be specified", error); return FALSE; } if (argc % 2 == 1) { ot_util_usage_error (context, "Only complete COLLECTION-ID REF pairs may be specified", error); return FALSE; } /* Open the USB stick, which must exist. Allow automounting and following symlinks. */ const char *mount_root_path = argv[1]; struct stat mount_root_stbuf; glnx_autofd int mount_root_dfd = -1; if (!glnx_opendirat (AT_FDCWD, mount_root_path, TRUE, &mount_root_dfd, error)) return FALSE; if (!glnx_fstat (mount_root_dfd, &mount_root_stbuf, error)) return FALSE; /* Read in the refs to add to the USB stick. */ g_autoptr(GPtrArray) refs = g_ptr_array_new_full (argc, (GDestroyNotify) ostree_collection_ref_free); for (gsize i = 2; i < argc; i += 2) { if (!ostree_validate_collection_id (argv[i], error) || !ostree_validate_rev (argv[i + 1], error)) return FALSE; g_ptr_array_add (refs, ostree_collection_ref_new (argv[i], argv[i + 1])); } /* Open the destination repository on the USB stick or create it if it doesn’t exist. * Check it’s below @mount_root_path, and that it’s not the same as the source * repository. * * If the destination file system supports xattrs (for example, ext4), we use * a BARE_USER repository; if it doesn’t (for example, FAT), we use ARCHIVE. * In either case, we want a lossless repository. */ const char *dest_repo_path = (opt_destination_repo != NULL) ? opt_destination_repo : ".ostree/repo"; if (!glnx_shutil_mkdir_p_at (mount_root_dfd, dest_repo_path, 0755, cancellable, error)) return FALSE; OstreeRepoMode mode = OSTREE_REPO_MODE_BARE_USER; if (TEMP_FAILURE_RETRY (fgetxattr (mount_root_dfd, "user.test", NULL, 0)) < 0 && (errno == ENOTSUP || errno == EOPNOTSUPP)) mode = OSTREE_REPO_MODE_ARCHIVE; g_debug ("%s: Creating repository in mode %u", G_STRFUNC, mode); g_autoptr(OstreeRepo) dest_repo = ostree_repo_create_at (mount_root_dfd, dest_repo_path, mode, NULL, cancellable, error); if (dest_repo == NULL) return FALSE; struct stat dest_repo_stbuf; if (!glnx_fstat (ostree_repo_get_dfd (dest_repo), &dest_repo_stbuf, error)) return FALSE; if (dest_repo_stbuf.st_dev != mount_root_stbuf.st_dev) { ot_util_usage_error (context, "--destination-repo must be a descendent of MOUNT-PATH", error); return FALSE; } if (ostree_repo_equal (src_repo, dest_repo)) { ot_util_usage_error (context, "--destination-repo must not be the source repository", error); return FALSE; } if (!ostree_ensure_repo_writable (dest_repo, error)) return FALSE; if (opt_disable_fsync) ostree_repo_set_disable_fsync (dest_repo, TRUE); /* Copy across all of the collection–refs to the destination repo. */ GVariantBuilder refs_builder; g_variant_builder_init (&refs_builder, G_VARIANT_TYPE ("a(sss)")); for (gsize i = 0; i < refs->len; i++) { const OstreeCollectionRef *ref = g_ptr_array_index (refs, i); g_variant_builder_add (&refs_builder, "(sss)", ref->collection_id, ref->ref_name, ""); } { GVariantBuilder builder; g_autoptr(GVariant) opts = NULL; OstreeRepoPullFlags flags = OSTREE_REPO_PULL_FLAGS_MIRROR; glnx_console_lock (&console); if (console.is_tty) progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, &console); g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add (&builder, "{s@v}", "collection-refs", g_variant_new_variant (g_variant_builder_end (&refs_builder))); g_variant_builder_add (&builder, "{s@v}", "flags", g_variant_new_variant (g_variant_new_int32 (flags))); g_variant_builder_add (&builder, "{s@v}", "depth", g_variant_new_variant (g_variant_new_int32 (0))); opts = g_variant_ref_sink (g_variant_builder_end (&builder)); g_autofree char *src_repo_uri = g_file_get_uri (ostree_repo_get_path (src_repo)); if (!ostree_repo_pull_with_options (dest_repo, src_repo_uri, opts, progress, cancellable, error)) { ostree_repo_abort_transaction (dest_repo, cancellable, NULL); return FALSE; } if (progress != NULL) ostree_async_progress_finish (progress); } /* Ensure a summary file is present to make it easier to look up commit checksums. */ /* FIXME: It should be possible to work without this, but find_remotes_cb() in * ostree-repo-pull.c currently assumes a summary file (signed or unsigned) is * present. */ struct stat stbuf; if (!glnx_fstatat_allow_noent (ostree_repo_get_dfd (dest_repo), "summary", &stbuf, 0, error)) return FALSE; if (errno == ENOENT && !ostree_repo_regenerate_summary (dest_repo, NULL, cancellable, error)) return FALSE; /* Add the symlinks .ostree/repos.d/@symlink_name → @dest_repo_path, unless * the @dest_repo_path is a well-known one like ostree/repo, in which case no * symlink is necessary; #OstreeRepoFinderMount always looks there. */ if (!g_str_equal (dest_repo_path, "ostree/repo") && !g_str_equal (dest_repo_path, ".ostree/repo")) { if (!glnx_shutil_mkdir_p_at (mount_root_dfd, ".ostree/repos.d", 0755, cancellable, error)) return FALSE; /* Find a unique name for the symlink. If a symlink already targets * @dest_repo_path, use that and don’t create a new one. */ GLnxDirFdIterator repos_iter; gboolean need_symlink = TRUE; if (!glnx_dirfd_iterator_init_at (mount_root_dfd, ".ostree/repos.d", TRUE, &repos_iter, error)) return FALSE; while (TRUE) { struct dirent *repo_dent; if (!glnx_dirfd_iterator_next_dent (&repos_iter, &repo_dent, cancellable, error)) return FALSE; if (repo_dent == NULL) break; /* Does the symlink already point to this repository? (Or is the * repository itself present in repos.d?) We already guarantee that * they’re on the same device. */ if (repo_dent->d_ino == dest_repo_stbuf.st_ino) { need_symlink = FALSE; break; } } /* If we need a symlink, find a unique name for it and create it. */ if (need_symlink) { /* Relative to .ostree/repos.d. */ g_autofree char *relative_dest_repo_path = g_build_filename ("..", "..", dest_repo_path, NULL); guint i; const guint max_attempts = 100; for (i = 0; i < max_attempts; i++) { g_autofree char *symlink_path = g_strdup_printf (".ostree/repos.d/%02u-generated", i); int ret = TEMP_FAILURE_RETRY (symlinkat (relative_dest_repo_path, mount_root_dfd, symlink_path)); if (ret < 0 && errno != EEXIST) return glnx_throw_errno_prefix (error, "symlinkat(%s → %s)", symlink_path, relative_dest_repo_path); else if (ret >= 0) break; } if (i == max_attempts) return glnx_throw (error, "Could not find an unused symlink name for the repository"); } } /* Report success to the user. */ g_autofree char *src_repo_path = g_file_get_path (ostree_repo_get_path (src_repo)); g_print ("Copied %u/%u refs successfully from ‘%s’ to ‘%s’ repository in ‘%s’.\n", refs->len, refs->len, src_repo_path, dest_repo_path, mount_root_path); return TRUE; }
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; }