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; }
static gboolean write_checksum_file_at (OstreeRepo *self, int dfd, const char *name, const char *sha256, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; const char *lastslash; if (!ostree_validate_checksum_string (sha256, error)) goto out; if (ostree_validate_checksum_string (name, NULL)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Rev name '%s' looks like a checksum", name); goto out; } if (!*name) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid empty ref name"); goto out; } lastslash = strrchr (name, '/'); if (lastslash) { char *parent = strdupa (name); parent[lastslash - name] = '\0'; if (!glnx_shutil_mkdir_p_at (dfd, parent, 0777, cancellable, error)) goto out; } { size_t l = strlen (sha256); char *bufnl = alloca (l + 2); memcpy (bufnl, sha256, l); bufnl[l] = '\n'; bufnl[l+1] = '\0'; if (!_ostree_repo_file_replace_contents (self, dfd, name, (guint8*)bufnl, l + 1, cancellable, error)) goto out; } ret = TRUE; out: return ret; }
/** * gs_file_ensure_directory: * @dir: Path to create as directory * @with_parents: Also create parent directories * @cancellable: a #GCancellable * @error: a #GError * * Like g_file_make_directory(), except does not throw an error if the * directory already exists. */ gboolean gs_file_ensure_directory (GFile *dir, gboolean with_parents, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; GError *temp_error = NULL; GFile *parent = NULL; if (!g_file_make_directory (dir, cancellable, &temp_error)) { if (with_parents && g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { parent = g_file_get_parent (dir); if (parent) { g_clear_error (&temp_error); if (!glnx_shutil_mkdir_p_at (AT_FDCWD, gs_file_get_path_cached (parent), 0777, cancellable, error)) goto out; } if (!gs_file_ensure_directory (dir, FALSE, cancellable, error)) goto out; } else if (!g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_EXISTS)) { g_propagate_error (error, temp_error); goto out; } else g_clear_error (&temp_error); } ret = TRUE; out: g_clear_object (&parent); return ret; }
int rpmostree_container_builtin_init (int argc, char **argv, GCancellable *cancellable, GError **error) { int exit_status = EXIT_FAILURE; g_auto(ROContainerContext) rocctx_data = RO_CONTAINER_CONTEXT_INIT; ROContainerContext *rocctx = &rocctx_data; GOptionContext *context = g_option_context_new (""); static const char* const directories[] = { "repo", "rpmmd.repos.d", "cache/rpm-md", "roots", "tmp" }; guint i; if (!rpmostree_option_context_parse (context, init_option_entries, &argc, &argv, RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD, cancellable, NULL, error)) goto out; if (!roc_context_init_core (rocctx, error)) goto out; for (i = 0; i < G_N_ELEMENTS (directories); i++) { if (!glnx_shutil_mkdir_p_at (rocctx->userroot_dfd, directories[i], 0755, cancellable, error)) goto out; } if (!ostree_repo_create (rocctx->repo, OSTREE_REPO_MODE_BARE_USER, cancellable, error)) goto out; exit_status = EXIT_SUCCESS; out: return exit_status; }
static gboolean rewrite_delta (OstreeRepo *src_repo, const char *src_commit, OstreeRepo *dst_repo, const char *dst_commit, GVariant *dst_commitv, const char *from, GError **error) { g_autoptr(GFile) src_delta_file = NULL; g_autoptr(GFile) dst_delta_file = NULL; g_autofree char *src_detached_key = _ostree_get_relative_static_delta_path (from, src_commit, "commitmeta"); g_autofree char *dst_detached_key = _ostree_get_relative_static_delta_path (from, dst_commit, "commitmeta"); g_autofree char *src_delta_dir = _ostree_get_relative_static_delta_path (from, src_commit, NULL); g_autofree char *dst_delta_dir = _ostree_get_relative_static_delta_path (from, dst_commit, NULL); g_autofree char *src_superblock_path = _ostree_get_relative_static_delta_path (from, src_commit, "superblock"); g_autofree char *dst_superblock_path = _ostree_get_relative_static_delta_path (from, dst_commit, "superblock"); GMappedFile *mfile = NULL; g_auto(GVariantBuilder) superblock_builder = FLATPAK_VARIANT_BUILDER_INITIALIZER; g_autoptr(GVariant) src_superblock = NULL; g_autoptr(GVariant) dst_superblock = NULL; g_autoptr(GBytes) bytes = NULL; g_autoptr(GVariant) dst_detached = NULL; g_autoptr(GVariant) src_metadata = NULL; g_autoptr(GVariant) src_recurse = NULL; g_autoptr(GVariant) src_parts = NULL; g_auto(GVariantDict) dst_metadata_dict = FLATPAK_VARIANT_DICT_INITIALIZER; int i; src_delta_file = g_file_resolve_relative_path (ostree_repo_get_path (src_repo), src_superblock_path); mfile = g_mapped_file_new (flatpak_file_get_path_cached (src_delta_file), FALSE, NULL); if (mfile == NULL) return TRUE; /* No superblock, not an error */ bytes = g_mapped_file_get_bytes (mfile); g_mapped_file_unref (mfile); src_superblock = g_variant_ref_sink (g_variant_new_from_bytes (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT), bytes, FALSE)); src_metadata = g_variant_get_child_value (src_superblock, 0); src_recurse = g_variant_get_child_value (src_superblock, 5); src_parts = g_variant_get_child_value (src_superblock, 6); if (g_variant_n_children (src_recurse) != 0) return flatpak_fail (error, "Recursive deltas not supported, ignoring"); g_variant_builder_init (&superblock_builder, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT)); g_variant_dict_init (&dst_metadata_dict, src_metadata); g_variant_dict_remove (&dst_metadata_dict, src_detached_key); if (ostree_repo_read_commit_detached_metadata (dst_repo, dst_commit, &dst_detached, NULL, NULL) && dst_detached != NULL) g_variant_dict_insert_value (&dst_metadata_dict, dst_detached_key, dst_detached); g_variant_builder_add_value (&superblock_builder, g_variant_dict_end (&dst_metadata_dict)); g_variant_builder_add_value (&superblock_builder, g_variant_get_child_value (src_superblock, 1)); /* timestamp */ g_variant_builder_add_value (&superblock_builder, from ? ostree_checksum_to_bytes_v (from) : new_bytearray ((guchar *) "", 0)); g_variant_builder_add_value (&superblock_builder, ostree_checksum_to_bytes_v (dst_commit)); g_variant_builder_add_value (&superblock_builder, dst_commitv); g_variant_builder_add_value (&superblock_builder, src_recurse); g_variant_builder_add_value (&superblock_builder, src_parts); g_variant_builder_add_value (&superblock_builder, g_variant_get_child_value (src_superblock, 7)); /* fallback */ dst_superblock = g_variant_ref_sink (g_variant_builder_end (&superblock_builder)); if (!glnx_shutil_mkdir_p_at (ostree_repo_get_dfd (dst_repo), dst_delta_dir, 0755, NULL, error)) return FALSE; for (i = 0; i < g_variant_n_children (src_parts); i++) { g_autofree char *src_part_path = g_strdup_printf ("%s/%d", src_delta_dir, i); g_autofree char *dst_part_path = g_strdup_printf ("%s/%d", dst_delta_dir, i); if (!glnx_file_copy_at (ostree_repo_get_dfd (src_repo), src_part_path, NULL, ostree_repo_get_dfd (dst_repo), dst_part_path, GLNX_FILE_COPY_OVERWRITE | GLNX_FILE_COPY_NOXATTRS, NULL, error)) return FALSE; } dst_delta_file = g_file_resolve_relative_path (ostree_repo_get_path (dst_repo), dst_superblock_path); if (!flatpak_variant_save (dst_delta_file, dst_superblock, NULL, error)) return FALSE; return TRUE; }
/* 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; }
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; }
static gboolean write_checksum_file_at (OstreeRepo *self, int dfd, const char *name, const char *sha256, GCancellable *cancellable, GError **error) { if (!ostree_validate_checksum_string (sha256, error)) return FALSE; if (ostree_validate_checksum_string (name, NULL)) return glnx_throw (error, "Rev name '%s' looks like a checksum", name); if (!*name) return glnx_throw (error, "Invalid empty ref name"); const char *lastslash = strrchr (name, '/'); if (lastslash) { char *parent = strdupa (name); parent[lastslash - name] = '\0'; if (!glnx_shutil_mkdir_p_at (dfd, parent, 0777, cancellable, error)) return FALSE; } { size_t l = strlen (sha256); char *bufnl = alloca (l + 2); g_autoptr(GError) temp_error = NULL; memcpy (bufnl, sha256, l); bufnl[l] = '\n'; bufnl[l+1] = '\0'; if (!_ostree_repo_file_replace_contents (self, dfd, name, (guint8*)bufnl, l + 1, cancellable, &temp_error)) { if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY)) { g_autoptr(GHashTable) refs = NULL; GHashTableIter hashiter; gpointer hashkey, hashvalue; g_clear_error (&temp_error); if (!ostree_repo_list_refs (self, name, &refs, cancellable, error)) return FALSE; g_hash_table_iter_init (&hashiter, refs); while ((g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue))) { if (strcmp (name, (char *)hashkey) != 0) return glnx_throw (error, "Conflict: %s exists under %s when attempting write", (char*)hashkey, name); } if (!glnx_shutil_rm_rf_at (dfd, name, cancellable, error)) return FALSE; if (!_ostree_repo_file_replace_contents (self, dfd, name, (guint8*)bufnl, l + 1, cancellable, error)) return FALSE; } else { g_propagate_error (error, g_steal_pointer (&temp_error)); return FALSE; } } } return TRUE; }