gboolean _ostree_repo_static_delta_delete (OstreeRepo *self, const char *delta_id, GCancellable *cancellable, GError **error) { g_autofree char *from = NULL; g_autofree char *to = NULL; if (!_ostree_parse_delta_name (delta_id, &from, &to, error)) return FALSE; g_autofree char *deltadir = _ostree_get_relative_static_delta_path (from, to, NULL); struct stat buf; if (fstatat (self->repo_dir_fd, deltadir, &buf, 0) != 0) { if (errno == ENOENT) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Can't find delta %s", delta_id); return FALSE; } else return glnx_throw_errno_prefix (error, "fstatat(%s)", deltadir); } if (!glnx_shutil_rm_rf_at (self->repo_dir_fd, deltadir, cancellable, error)) return FALSE; return TRUE; }
gboolean _ostree_repo_static_delta_query_exists (OstreeRepo *self, const char *delta_id, gboolean *out_exists, GCancellable *cancellable, GError **error) { g_autofree char *from = NULL; g_autofree char *to = NULL; struct stat stbuf; if (!_ostree_parse_delta_name (delta_id, &from, &to, error)) return FALSE; g_autofree char *superblock_path = _ostree_get_relative_static_delta_superblock_path (from, to); if (fstatat (self->repo_dir_fd, superblock_path, &stbuf, 0) < 0) { if (errno == ENOENT) { *out_exists = FALSE; return TRUE; } else return glnx_throw_errno_prefix (error, "fstatat(%s)", superblock_path); } *out_exists = TRUE; return TRUE; }
static gboolean prune_commitpartial_file (OstreeRepo *repo, const char *checksum, GCancellable *cancellable, GError **error) { g_autofree char *path = _ostree_get_commitpartial_path (checksum); if (unlinkat (repo->repo_dir_fd, path, 0) != 0) { if (errno != ENOENT) return glnx_throw_errno_prefix (error, "unlinkat"); } return TRUE; }
static gboolean _ostree_repo_prune_tmp (OstreeRepo *self, GCancellable *cancellable, GError **error) { if (self->cache_dir_fd == -1) return TRUE; g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; gboolean exists; if (!ot_dfd_iter_init_allow_noent (self->cache_dir_fd, _OSTREE_SUMMARY_CACHE_DIR, &dfd_iter, &exists, error)) return FALSE; /* Note early return */ if (!exists) return TRUE; while (TRUE) { size_t len; gboolean has_sig_suffix = FALSE; struct dirent *dent; if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, cancellable, error)) return FALSE; if (dent == NULL) break; len = strlen (dent->d_name); if (len > 4 && g_strcmp0 (dent->d_name + len - 4, ".sig") == 0) { has_sig_suffix = TRUE; dent->d_name[len - 4] = '\0'; } if (!g_hash_table_contains (self->remotes, dent->d_name)) { /* Restore the previous value to get the file name. */ if (has_sig_suffix) dent->d_name[len - 4] = '.'; if (unlinkat (dfd_iter.fd, dent->d_name, 0) < 0) return glnx_throw_errno_prefix (error, "unlinkat"); } } return TRUE; }
static gboolean show_one_part (OstreeRepo *self, gboolean swap_endian, const char *from, const char *to, GVariant *meta_entries, guint i, guint64 *total_size_ref, guint64 *total_usize_ref, GCancellable *cancellable, GError **error) { g_autoptr(GVariant) part = NULL; g_autofree char *part_path = _ostree_get_relative_static_delta_part_path (from, to, i); guint32 version; guint64 size, usize; g_autoptr(GVariant) objects = NULL; g_variant_get_child (meta_entries, i, "(u@aytt@ay)", &version, NULL, &size, &usize, &objects); size = maybe_swap_endian_u64 (swap_endian, size); usize = maybe_swap_endian_u64 (swap_endian, usize); *total_size_ref += size; *total_usize_ref += usize; g_print ("PartMeta%u: nobjects=%u size=%" G_GUINT64_FORMAT " usize=%" G_GUINT64_FORMAT "\n", i, (guint)(g_variant_get_size (objects) / OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN), size, usize); glnx_fd_close gint part_fd = openat (self->repo_dir_fd, part_path, O_RDONLY | O_CLOEXEC); if (part_fd < 0) return glnx_throw_errno_prefix (error, "openat(%s)", part_path); g_autoptr(GInputStream) part_in = g_unix_input_stream_new (part_fd, FALSE); if (!_ostree_static_delta_part_open (part_in, NULL, OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM, NULL, &part, cancellable, error)) return FALSE; { g_autoptr(GVariant) modes = NULL; g_autoptr(GVariant) xattrs = NULL; g_autoptr(GVariant) blob = NULL; g_autoptr(GVariant) ops = NULL; OstreeDeltaExecuteStats stats = { { 0, }, }; g_variant_get (part, "(@a(uuu)@aa(ayay)@ay@ay)", &modes, &xattrs, &blob, &ops); g_print ("PartPayload%u: nmodes=%" G_GUINT64_FORMAT " nxattrs=%" G_GUINT64_FORMAT " blobsize=%" G_GUINT64_FORMAT " opsize=%" G_GUINT64_FORMAT "\n", i, (guint64)g_variant_n_children (modes), (guint64)g_variant_n_children (xattrs), (guint64)g_variant_n_children (blob), (guint64)g_variant_n_children (ops)); if (!_ostree_static_delta_part_execute (self, objects, part, TRUE, &stats, cancellable, error)) return FALSE; { const guint *n_ops = stats.n_ops_executed; g_print ("PartPayloadOps%u: openspliceclose=%u open=%u write=%u setread=%u " "unsetread=%u close=%u bspatch=%u\n", i, n_ops[0], n_ops[1], n_ops[2], n_ops[3], n_ops[4], n_ops[5], n_ops[6]); } } return TRUE; }
/** * 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) { g_autofree char *basename = NULL; const char *dir_or_file_path = gs_file_get_path_cached (dir_or_file); /* First, try opening it as a directory */ glnx_fd_close int dfd = glnx_opendirat_with_errno (AT_FDCWD, dir_or_file_path, TRUE); if (dfd < 0) { if (errno != ENOTDIR) return glnx_throw_errno_prefix (error, "openat(O_DIRECTORY)"); 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)) return FALSE; } } else basename = g_strdup ("superblock"); glnx_fd_close int meta_fd = openat (dfd, basename, O_RDONLY | O_CLOEXEC); if (meta_fd < 0) return glnx_throw_errno_prefix (error, "openat(%s)", basename); g_autoptr(GVariant) meta = NULL; if (!ot_util_variant_map_fd (meta_fd, 0, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT), FALSE, &meta, error)) return FALSE; /* Parsing OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT */ g_autoptr(GVariant) metadata = g_variant_get_child_value (meta, 0); g_autofree char *to_checksum = NULL; g_autofree char *from_checksum = NULL; /* 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)) return FALSE; 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)) return FALSE; 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)) return FALSE; if (!have_from_commit) return glnx_throw (error, "Commit %s, which is the delta source, is not in repository", from_checksum); } if (!ostree_repo_has_object (self, OSTREE_OBJECT_TYPE_COMMIT, to_checksum, &have_to_commit, cancellable, error)) return FALSE; 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)) return FALSE; 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)) return FALSE; } } g_autoptr(GVariant) fallback = g_variant_get_child_value (meta, 7); if (g_variant_n_children (fallback) > 0) return glnx_throw (error, "Cannot execute delta offline: contains nonempty http fallback entries"); g_autoptr(GVariant) headers = g_variant_get_child_value (meta, 6); const guint n = g_variant_n_children (headers); for (guint 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(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; g_autoptr(GVariant) 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) return glnx_throw (error, "Delta part has too new version %u", version); if (!_ostree_repo_static_delta_part_have_all_objects (self, objects, &have_all, cancellable, error)) return FALSE; /* 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) return FALSE; ostree_checksum_inplace_from_bytes (csum, checksum); deltapart_path = _ostree_get_relative_static_delta_part_path (from_checksum, to_checksum, i); g_autoptr(GInputStream) part_in = NULL; g_autoptr(GVariant) 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)) return FALSE; } 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) return glnx_throw_errno_prefix (error, "Opening deltapart '%s'", relpath); 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)) return FALSE; } if (!_ostree_static_delta_part_execute (self, objects, part, skip_validation, NULL, cancellable, error)) return glnx_prefix_error (error, "Executing delta part %i", i); } return TRUE; }
static gboolean maybe_prune_loose_object (OtPruneData *data, OstreeRepoPruneFlags flags, const char *checksum, OstreeObjectType objtype, GCancellable *cancellable, GError **error) { gboolean reachable = FALSE; g_autoptr(GVariant) key = NULL; key = ostree_object_name_serialize (checksum, objtype); if (g_hash_table_lookup_extended (data->reachable, key, NULL, NULL)) reachable = TRUE; else { guint64 storage_size = 0; g_debug ("Pruning unneeded object %s.%s", checksum, ostree_object_type_to_string (objtype)); if (!ostree_repo_query_object_storage_size (data->repo, objtype, checksum, &storage_size, cancellable, error)) return FALSE; data->freed_bytes += storage_size; if (!(flags & OSTREE_REPO_PRUNE_FLAGS_NO_PRUNE)) { if (objtype == OSTREE_OBJECT_TYPE_PAYLOAD_LINK) { ssize_t size; char loose_path_buf[_OSTREE_LOOSE_PATH_MAX]; char target_checksum[OSTREE_SHA256_STRING_LEN+1]; char target_buf[_OSTREE_LOOSE_PATH_MAX + _OSTREE_PAYLOAD_LINK_PREFIX_LEN]; _ostree_loose_path (loose_path_buf, checksum, OSTREE_OBJECT_TYPE_PAYLOAD_LINK, data->repo->mode); size = readlinkat (data->repo->objects_dir_fd, loose_path_buf, target_buf, sizeof (target_buf)); if (size < 0) return glnx_throw_errno_prefix (error, "readlinkat"); if (size < OSTREE_SHA256_STRING_LEN + _OSTREE_PAYLOAD_LINK_PREFIX_LEN) return glnx_throw (error, "invalid data size for %s", loose_path_buf); sprintf (target_checksum, "%.2s%.62s", target_buf + _OSTREE_PAYLOAD_LINK_PREFIX_LEN, target_buf + _OSTREE_PAYLOAD_LINK_PREFIX_LEN + 3); g_autoptr(GVariant) target_key = ostree_object_name_serialize (target_checksum, OSTREE_OBJECT_TYPE_FILE); if (g_hash_table_lookup_extended (data->reachable, target_key, NULL, NULL)) { guint64 target_storage_size = 0; if (!ostree_repo_query_object_storage_size (data->repo, OSTREE_OBJECT_TYPE_FILE, target_checksum, &target_storage_size, cancellable, error)) return FALSE; reachable = target_storage_size >= data->repo->payload_link_threshold; if (reachable) goto exit; } } else if (objtype == OSTREE_OBJECT_TYPE_COMMIT) { if (!ostree_repo_mark_commit_partial (data->repo, checksum, FALSE, error)) return FALSE; } if (!ostree_repo_delete_object (data->repo, objtype, checksum, cancellable, error)) return FALSE; } if (OSTREE_OBJECT_TYPE_IS_META (objtype)) data->n_unreachable_meta++; else data->n_unreachable_content++; } exit: if (reachable) { g_debug ("Keeping needed object %s.%s", checksum, ostree_object_type_to_string (objtype)); if (OSTREE_OBJECT_TYPE_IS_META (objtype)) data->n_reachable_meta++; else data->n_reachable_content++; } 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; }