/* * Copyright (C) 2011,2013 Colin Walters * * SPDX-License-Identifier: LGPL-2.0+ * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "config.h" #include "ostree-core-private.h" #include "ostree-repo-private.h" #include "otutil.h" #include "ot-fs-utils.h" /* This is polymorphic in @collection_id: if non-%NULL, @refs will be treated as of * type OstreeCollectionRef ↦ checksum. Otherwise, it will be treated as of type * refspec ↦ checksum. */ static gboolean add_ref_to_set (const char *remote, const char *collection_id, int base_fd, const char *path, GHashTable *refs, GCancellable *cancellable, GError **error) { g_return_val_if_fail (remote == NULL || collection_id == NULL, FALSE); gsize len; char *contents = glnx_file_get_contents_utf8_at (base_fd, path, &len, cancellable, error); if (!contents) return FALSE; g_strchomp (contents); if (collection_id == NULL) { g_autoptr(GString) refname = g_string_new (""); if (remote) { g_string_append (refname, remote); g_string_append_c (refname, ':'); } g_string_append (refname, path); g_hash_table_insert (refs, g_string_free (g_steal_pointer (&refname), FALSE), contents); } else { g_hash_table_insert (refs, ostree_collection_ref_new (collection_id, path), contents); } 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); /* FIXME: Conflict detection needs to be extended to collection–refs * using ostree_repo_list_collection_refs(). */ 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; } static gboolean find_ref_in_remotes (OstreeRepo *self, const char *rev, int *out_fd, GError **error) { g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; glnx_autofd 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_autofd 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; } static gboolean resolve_refspec (OstreeRepo *self, const char *remote, const char *ref, gboolean allow_noent, gboolean fallback_remote, char **out_rev, GError **error); static gboolean resolve_refspec_fallback (OstreeRepo *self, const char *remote, const char *ref, gboolean allow_noent, gboolean fallback_remote, char **out_rev, GCancellable *cancellable, GError **error) { g_autofree char *ret_rev = NULL; if (self->parent_repo) { if (!resolve_refspec (self->parent_repo, remote, ref, allow_noent, fallback_remote, &ret_rev, error)) return FALSE; } else if (!allow_noent) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Refspec '%s%s%s' not found", remote ? remote : "", remote ? ":" : "", ref); return FALSE; } ot_transfer_out_value (out_rev, &ret_rev); return TRUE; } static gboolean resolve_refspec (OstreeRepo *self, const char *remote, const char *ref, gboolean allow_noent, gboolean fallback_remote, char **out_rev, GError **error) { __attribute__((unused)) GCancellable *cancellable = NULL; g_autofree char *ret_rev = NULL; glnx_autofd int target_fd = -1; g_return_val_if_fail (ref != NULL, FALSE); /* We intentionally don't allow a ref that looks like a checksum */ if (ostree_validate_checksum_string (ref, NULL)) { ret_rev = g_strdup (ref); } else if (self->in_transaction) { const char *refspec; if (remote != NULL) refspec = glnx_strjoina (remote, ":", ref); else refspec = ref; g_mutex_lock (&self->txn_lock); if (self->txn.refs) ret_rev = g_strdup (g_hash_table_lookup (self->txn.refs, refspec)); g_mutex_unlock (&self->txn_lock); } if (ret_rev != NULL) { ot_transfer_out_value (out_rev, &ret_rev); return TRUE; } if (remote != NULL) { const char *remote_ref = glnx_strjoina ("refs/remotes/", remote, "/", ref); if (!ot_openat_ignore_enoent (self->repo_dir_fd, remote_ref, &target_fd, error)) return FALSE; } else { const char *local_ref = glnx_strjoina ("refs/heads/", ref); if (!ot_openat_ignore_enoent (self->repo_dir_fd, local_ref, &target_fd, error)) return FALSE; if (target_fd == -1 && fallback_remote) { local_ref = glnx_strjoina ("refs/remotes/", ref); if (!ot_openat_ignore_enoent (self->repo_dir_fd, local_ref, &target_fd, error)) return FALSE; if (target_fd == -1) { if (!find_ref_in_remotes (self, ref, &target_fd, error)) return FALSE; } } } if (target_fd != -1) { ret_rev = glnx_fd_readall_utf8 (target_fd, NULL, NULL, error); if (!ret_rev) { g_prefix_error (error, "Couldn't open ref '%s': ", ref); return FALSE; } g_strchomp (ret_rev); if (!ostree_validate_checksum_string (ret_rev, error)) return FALSE; } else { if (!resolve_refspec_fallback (self, remote, ref, allow_noent, fallback_remote, &ret_rev, cancellable, error)) return FALSE; } ot_transfer_out_value (out_rev, &ret_rev); return TRUE; } /** * ostree_repo_resolve_partial_checksum: * @self: Repo * @refspec: A refspec * @full_checksum (out) (transfer full): A full checksum corresponding to the truncated ref given * @error: Error * * Look up the existing refspec checksums. If the given ref is a unique truncated beginning * of a valid checksum it will return that checksum in the parameter @full_checksum */ static gboolean ostree_repo_resolve_partial_checksum (OstreeRepo *self, const char *refspec, char **full_checksum, GError **error) { static const char hexchars[] = "0123456789abcdef"; g_autofree char *ret_rev = NULL; g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* If the input is longer than OSTREE_SHA256_STRING_LEN chars or contains non-hex chars, don't bother looking for it as an object */ const gsize off = strspn (refspec, hexchars); if (off > OSTREE_SHA256_STRING_LEN || refspec[off] != '\0') return TRUE; /* this looks through all objects and adds them to the ref_list if: a) they are a commit object AND b) the obj checksum starts with the partual checksum defined by "refspec" */ g_autoptr(GHashTable) ref_list = NULL; if (!ostree_repo_list_commit_objects_starting_with (self, refspec, &ref_list, NULL, error)) return FALSE; guint length = g_hash_table_size (ref_list); GHashTableIter hashiter; gpointer key, value; GVariant *first_commit = NULL; g_hash_table_iter_init (&hashiter, ref_list); if (g_hash_table_iter_next (&hashiter, &key, &value)) first_commit = (GVariant*) key; OstreeObjectType objtype; const char *checksum = NULL; if (first_commit) ostree_object_name_deserialize (first_commit, &checksum, &objtype); /* length more than one - multiple commits match partial refspec: is not unique */ if (length > 1) return glnx_throw (error, "Refspec %s not unique", refspec); /* length is 1 - a single matching commit gives us our revision */ else if (length == 1) ret_rev = g_strdup (checksum); /* Note: if length is 0, then code will return TRUE because there is no error, but it will return full_checksum = NULL to signal to continue parsing */ ot_transfer_out_value (full_checksum, &ret_rev); return TRUE; } static gboolean _ostree_repo_resolve_rev_internal (OstreeRepo *self, const char *refspec, gboolean allow_noent, gboolean fallback_remote, char **out_rev, GError **error) { g_autofree char *ret_rev = NULL; g_return_val_if_fail (refspec != NULL, FALSE); if (ostree_validate_checksum_string (refspec, NULL)) { ret_rev = g_strdup (refspec); } else if (!ostree_repo_resolve_partial_checksum (self, refspec, &ret_rev, error)) return FALSE; if (!ret_rev) { if (error != NULL && *error != NULL) return FALSE; if (g_str_has_suffix (refspec, "^")) { g_autofree char *parent_refspec = NULL; g_autofree char *parent_rev = NULL; g_autoptr(GVariant) commit = NULL; parent_refspec = g_strdup (refspec); parent_refspec[strlen(parent_refspec) - 1] = '\0'; if (!ostree_repo_resolve_rev (self, parent_refspec, allow_noent, &parent_rev, error)) return FALSE; if (!ostree_repo_load_variant (self, OSTREE_OBJECT_TYPE_COMMIT, parent_rev, &commit, error)) return FALSE; if (!(ret_rev = ostree_commit_get_parent (commit))) return glnx_throw (error, "Commit %s has no parent", parent_rev); } else { g_autofree char *remote = NULL; g_autofree char *ref = NULL; if (!ostree_parse_refspec (refspec, &remote, &ref, error)) return FALSE; if (!resolve_refspec (self, remote, ref, allow_noent, fallback_remote, &ret_rev, error)) return FALSE; } } ot_transfer_out_value (out_rev, &ret_rev); return TRUE; } /** * ostree_repo_resolve_rev: * @self: Repo * @refspec: A refspec * @allow_noent: Do not throw an error if refspec does not exist * @out_rev: (out) (nullable) (transfer full): A checksum,or %NULL if @allow_noent is true and it does not exist * @error: Error * * Look up the given refspec, returning the checksum it references in * the parameter @out_rev. Will fall back on remote directory if cannot * find the given refspec in local. */ gboolean ostree_repo_resolve_rev (OstreeRepo *self, const char *refspec, gboolean allow_noent, char **out_rev, GError **error) { return _ostree_repo_resolve_rev_internal (self, refspec, allow_noent, TRUE, out_rev, error); } /** * ostree_repo_resolve_rev_ext: * @self: Repo * @refspec: A refspec * @allow_noent: Do not throw an error if refspec does not exist * @flags: Options controlling behavior * @out_rev: (out) (nullable) (transfer full): A checksum,or %NULL if @allow_noent is true and it does not exist * @error: Error * * Look up the given refspec, returning the checksum it references in * the parameter @out_rev. Differently from ostree_repo_resolve_rev(), * this will not fall back to searching through remote repos if a * local ref is specified but not found. * * The flag %OSTREE_REPO_RESOLVE_REV_EXT_LOCAL_ONLY is implied so * using it has no effect. * * Since: 2016.7 */ gboolean ostree_repo_resolve_rev_ext (OstreeRepo *self, const char *refspec, gboolean allow_noent, OstreeRepoResolveRevExtFlags flags, char **out_rev, GError **error) { return _ostree_repo_resolve_rev_internal (self, refspec, allow_noent, FALSE, out_rev, error); } /** * ostree_repo_resolve_collection_ref: * @self: an #OstreeRepo * @ref: a collection–ref to resolve * @allow_noent: %TRUE to not throw an error if @ref doesn’t exist * @flags: options controlling behaviour * @out_rev: (out) (transfer full) (optional) (nullable): return location for * the checksum corresponding to @ref, or %NULL if @allow_noent is %TRUE and * the @ref could not be found * @cancellable: (nullable): a #GCancellable, or %NULL * @error: return location for a #GError, or %NULL * * Look up the checksum for the given collection–ref, returning it in @out_rev. * This will search through the mirrors and remote refs. * * If @allow_noent is %TRUE and the given @ref cannot be found, %TRUE will be * returned and @out_rev will be set to %NULL. If @allow_noent is %FALSE and * the given @ref cannot be found, a %G_IO_ERROR_NOT_FOUND error will be * returned. * * If you want to check only local refs, not remote or mirrored ones, use the * flag %OSTREE_REPO_RESOLVE_REV_EXT_LOCAL_ONLY. This is analogous to using * ostree_repo_resolve_rev_ext() but for collection-refs. * * Returns: %TRUE on success, %FALSE on failure * Since: 2018.6 */ gboolean ostree_repo_resolve_collection_ref (OstreeRepo *self, const OstreeCollectionRef *ref, gboolean allow_noent, OstreeRepoResolveRevExtFlags flags, char **out_rev, GCancellable *cancellable, GError **error) { g_autofree char *ret_contents = NULL; g_return_val_if_fail (OSTREE_IS_REPO (self), FALSE); g_return_val_if_fail (ref != NULL, FALSE); g_return_val_if_fail (ref->collection_id != NULL && ref->ref_name != NULL, FALSE); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* Check for the ref in the current transaction in case it hasn't been * written to disk, to match the behavior of ostree_repo_resolve_rev() */ if (self->in_transaction) { g_mutex_lock (&self->txn_lock); if (self->txn.collection_refs) { const char *repo_collection_id = ostree_repo_get_collection_id (self); /* If the collection ID doesn't match it's a remote ref */ if (!(flags & OSTREE_REPO_RESOLVE_REV_EXT_LOCAL_ONLY) || repo_collection_id == NULL || g_strcmp0 (repo_collection_id, ref->collection_id) == 0) { ret_contents = g_strdup (g_hash_table_lookup (self->txn.collection_refs, ref)); } } g_mutex_unlock (&self->txn_lock); } /* Check for the ref on disk in the repo */ if (ret_contents == NULL) { g_autoptr(GHashTable) refs = NULL; /* (element-type OstreeCollectionRef utf8) */ OstreeRepoListRefsExtFlags list_refs_flags; if (flags & OSTREE_REPO_RESOLVE_REV_EXT_LOCAL_ONLY) list_refs_flags = OSTREE_REPO_LIST_REFS_EXT_EXCLUDE_REMOTES | OSTREE_REPO_LIST_REFS_EXT_EXCLUDE_MIRRORS; else list_refs_flags = OSTREE_REPO_LIST_REFS_EXT_NONE; if (!ostree_repo_list_collection_refs (self, ref->collection_id, &refs, list_refs_flags, cancellable, error)) return FALSE; ret_contents = g_strdup (g_hash_table_lookup (refs, ref)); } /* Check for the ref in the parent repo */ if (ret_contents == NULL && self->parent_repo != NULL) { if (!ostree_repo_resolve_collection_ref (self->parent_repo, ref, TRUE, flags, &ret_contents, cancellable, error)) return FALSE; } if (ret_contents == NULL && !allow_noent) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Collection–ref (%s, %s) not found", ref->collection_id, ref->ref_name); return FALSE; } if (out_rev != NULL) *out_rev = g_steal_pointer (&ret_contents); return TRUE; } static gboolean enumerate_refs_recurse (OstreeRepo *repo, const char *remote, OstreeRepoListRefsExtFlags flags, const char *collection_id, int base_dfd, GString *base_path, int child_dfd, const char *path, GHashTable *refs, GCancellable *cancellable, GError **error) { g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; const gboolean aliases_only = (flags & OSTREE_REPO_LIST_REFS_EXT_ALIASES) > 0; if (!glnx_dirfd_iterator_init_at (child_dfd, path, FALSE, &dfd_iter, error)) return FALSE; while (TRUE) { guint len = base_path->len; struct dirent *dent = NULL; if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error)) return FALSE; if (dent == NULL) break; /* https://github.com/ostreedev/ostree/issues/1285 * Ignore any files that don't appear to be valid fragments; e.g. * Red Hat has a tool that drops .rsync_info files into each * directory it syncs. **/ if (!_ostree_validate_ref_fragment (dent->d_name, NULL)) continue; g_string_append (base_path, dent->d_name); if (dent->d_type == DT_DIR) { g_string_append_c (base_path, '/'); if (!enumerate_refs_recurse (repo, remote, flags, collection_id, base_dfd, base_path, dfd_iter.fd, dent->d_name, refs, cancellable, error)) return FALSE; } else { if (aliases_only && dent->d_type == DT_LNK) { g_autofree char *target = glnx_readlinkat_malloc (base_dfd, base_path->str, cancellable, error); const char *resolved_target = target; if (!target) return FALSE; while (g_str_has_prefix (resolved_target, "../")) resolved_target += 3; g_hash_table_insert (refs, g_strdup (base_path->str), g_strdup (resolved_target)); } else if ((!aliases_only && dent->d_type == DT_REG) || dent->d_type == DT_LNK) { if (!add_ref_to_set (remote, collection_id, base_dfd, base_path->str, refs, cancellable, error)) return FALSE; } } g_string_truncate (base_path, len); } return TRUE; } static gboolean _ostree_repo_list_refs_internal (OstreeRepo *self, gboolean cut_prefix, OstreeRepoListRefsExtFlags flags, const char *refspec_prefix, GHashTable **out_all_refs, GCancellable *cancellable, GError **error) { GLNX_AUTO_PREFIX_ERROR ("Listing refs", error); g_autofree char *remote = NULL; g_autofree char *ref_prefix = NULL; g_autoptr(GHashTable) 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; /* special-case ":" and ":.", which ostree_parse_refspec won't like */ if (g_str_has_suffix (refspec_prefix, ":") || g_str_has_suffix (refspec_prefix, ":.")) { const char *colon = strrchr (refspec_prefix, ':'); g_autofree char *r = g_strndup (refspec_prefix, colon - refspec_prefix); if (ostree_validate_remote_name (r, NULL)) { remote = g_steal_pointer (&r); ref_prefix = g_strdup ("."); } } if (!ref_prefix) { if (!ostree_parse_refspec (refspec_prefix, &remote, &ref_prefix, error)) return FALSE; } if (!(flags & OSTREE_REPO_LIST_REFS_EXT_EXCLUDE_REMOTES) && 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 (!glnx_fstatat_allow_noent (self->repo_dir_fd, path, &stbuf, 0, error)) return FALSE; if (errno == 0) { if (S_ISDIR (stbuf.st_mode)) { glnx_autofd 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, flags, NULL, base_fd, base_path, base_fd, cut_prefix ? "." : ref_prefix, ret_all_refs, cancellable, error)) return FALSE; } else { glnx_autofd 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, NULL, 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_autofd 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, flags, NULL, refs_heads_dfd, base_path, refs_heads_dfd, ".", ret_all_refs, cancellable, error)) return FALSE; if (!(flags & OSTREE_REPO_LIST_REFS_EXT_EXCLUDE_REMOTES)) { 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_autofd 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, flags, NULL, 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; } /** * ostree_repo_list_refs: * @self: Repo * @refspec_prefix: (allow-none): Only list refs which match this prefix * @out_all_refs: (out) (element-type utf8 utf8) (transfer container): * Mapping from refspec to checksum * @cancellable: Cancellable * @error: Error * * If @refspec_prefix is %NULL, list all local and remote refspecs, * with their current values in @out_all_refs. Otherwise, only list * refspecs which have @refspec_prefix as a prefix. * * @out_all_refs will be returned as a mapping from refspecs (including the * remote name) to checksums. If @refspec_prefix is non-%NULL, it will be * removed as a prefix from the hash table keys. */ gboolean ostree_repo_list_refs (OstreeRepo *self, const char *refspec_prefix, GHashTable **out_all_refs, GCancellable *cancellable, GError **error) { return _ostree_repo_list_refs_internal (self, TRUE, OSTREE_REPO_LIST_REFS_EXT_NONE, refspec_prefix, out_all_refs, cancellable, error); } /** * ostree_repo_list_refs_ext: * @self: Repo * @refspec_prefix: (allow-none): Only list refs which match this prefix * @out_all_refs: (out) (element-type utf8 utf8) (transfer container): * Mapping from refspec to checksum * @flags: Options controlling listing behavior * @cancellable: Cancellable * @error: Error * * If @refspec_prefix is %NULL, list all local and remote refspecs, * with their current values in @out_all_refs. Otherwise, only list * refspecs which have @refspec_prefix as a prefix. * * @out_all_refs will be returned as a mapping from refspecs (including the * remote name) to checksums. Differently from ostree_repo_list_refs(), the * @refspec_prefix will not be removed from the refspecs in the hash table. * * Since: 2016.4 */ gboolean ostree_repo_list_refs_ext (OstreeRepo *self, const char *refspec_prefix, GHashTable **out_all_refs, OstreeRepoListRefsExtFlags flags, GCancellable *cancellable, GError **error) { return _ostree_repo_list_refs_internal (self, FALSE, flags, refspec_prefix, out_all_refs, cancellable, error); } /** * ostree_repo_remote_list_refs: * @self: Repo * @remote_name: Name of the remote. * @out_all_refs: (out) (element-type utf8 utf8) (transfer container): * Mapping from ref to checksum * @cancellable: Cancellable * @error: Error * */ gboolean ostree_repo_remote_list_refs (OstreeRepo *self, const char *remote_name, GHashTable **out_all_refs, GCancellable *cancellable, GError **error) { g_autoptr(GBytes) summary_bytes = NULL; g_autoptr(GHashTable) ret_all_refs = NULL; if (!ostree_repo_remote_fetch_summary (self, remote_name, &summary_bytes, NULL, cancellable, error)) return FALSE; if (summary_bytes == NULL) { return glnx_throw (error, "Remote refs not available; server has no summary file"); } else { g_autoptr(GVariant) summary = NULL; g_autoptr(GVariant) ref_map = NULL; GVariantIter iter; GVariant *child; ret_all_refs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); summary = g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, summary_bytes, FALSE); ref_map = g_variant_get_child_value (summary, 0); g_variant_iter_init (&iter, ref_map); while ((child = g_variant_iter_next_value (&iter)) != NULL) { const char *ref_name = NULL; g_autoptr(GVariant) csum_v = NULL; char tmp_checksum[OSTREE_SHA256_STRING_LEN+1]; g_variant_get_child (child, 0, "&s", &ref_name); if (ref_name != NULL) { g_variant_get_child (child, 1, "(t@aya{sv})", NULL, &csum_v, NULL); const guchar *csum_bytes = ostree_checksum_bytes_peek_validate (csum_v, error); if (csum_bytes == NULL) return FALSE; ostree_checksum_inplace_from_bytes (csum_bytes, tmp_checksum); g_hash_table_insert (ret_all_refs, g_strdup (ref_name), g_strdup (tmp_checksum)); } g_variant_unref (child); } } ot_transfer_out_value (out_all_refs, &ret_all_refs); return TRUE; } static gboolean remote_list_collection_refs_process_refs (OstreeRepo *self, const gchar *remote_name, const gchar *summary_collection_id, GVariant *summary_refs, GHashTable *ret_all_refs, GError **error) { gsize j, n; for (j = 0, n = g_variant_n_children (summary_refs); j < n; j++) { const guchar *csum_bytes; g_autoptr(GVariant) ref_v = NULL, csum_v = NULL; gchar tmp_checksum[OSTREE_SHA256_STRING_LEN + 1]; const gchar *ref_name; /* Check the ref name. */ ref_v = g_variant_get_child_value (summary_refs, j); g_variant_get_child (ref_v, 0, "&s", &ref_name); if (!ostree_validate_rev (ref_name, error)) return FALSE; /* Check the commit checksum. */ g_variant_get_child (ref_v, 1, "(t@ay@a{sv})", NULL, &csum_v, NULL); csum_bytes = ostree_checksum_bytes_peek_validate (csum_v, error); if (csum_bytes == NULL) return FALSE; ostree_checksum_inplace_from_bytes (csum_bytes, tmp_checksum); g_hash_table_insert (ret_all_refs, ostree_collection_ref_new (summary_collection_id, ref_name), g_strdup (tmp_checksum)); } return TRUE; } /** * ostree_repo_remote_list_collection_refs: * @self: Repo * @remote_name: Name of the remote. * @out_all_refs: (out) (element-type OstreeCollectionRef utf8) (transfer container): * Mapping from collection–ref to checksum * @cancellable: Cancellable * @error: Error * * List refs advertised by @remote_name, including refs which are part of * collections. If the repository at @remote_name has a collection ID set, its * refs will be returned with that collection ID; otherwise, they will be returned * with a %NULL collection ID in each #OstreeCollectionRef key in @out_all_refs. * Any refs for other collections stored in the repository will also be returned. * No filtering is performed. * * Since: 2018.6 */ gboolean ostree_repo_remote_list_collection_refs (OstreeRepo *self, const char *remote_name, GHashTable **out_all_refs, GCancellable *cancellable, GError **error) { g_autoptr(GBytes) summary_bytes = NULL; g_autoptr(GHashTable) ret_all_refs = NULL; /* (element-type OstreeCollectionRef utf8) */ g_autoptr(GVariant) summary_v = NULL; g_autoptr(GVariant) additional_metadata_v = NULL; g_autoptr(GVariant) summary_refs = NULL; const char *summary_collection_id; g_autoptr(GVariantIter) summary_collection_map = NULL; if (!ostree_repo_remote_fetch_summary (self, remote_name, &summary_bytes, NULL, cancellable, error)) return FALSE; if (summary_bytes == NULL) return glnx_throw (error, "Remote refs not available; server has no summary file"); ret_all_refs = g_hash_table_new_full (ostree_collection_ref_hash, ostree_collection_ref_equal, (GDestroyNotify) ostree_collection_ref_free, g_free); summary_v = g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, summary_bytes, FALSE); additional_metadata_v = g_variant_get_child_value (summary_v, 1); /* List the refs in the main map. */ if (!g_variant_lookup (additional_metadata_v, OSTREE_SUMMARY_COLLECTION_ID, "&s", &summary_collection_id)) summary_collection_id = NULL; summary_refs = g_variant_get_child_value (summary_v, 0); if (!remote_list_collection_refs_process_refs (self, remote_name, summary_collection_id, summary_refs, ret_all_refs, error)) return FALSE; /* List the refs in the collection map. */ if (!g_variant_lookup (additional_metadata_v, OSTREE_SUMMARY_COLLECTION_MAP, "a{sa(s(taya{sv}))}", &summary_collection_map)) summary_collection_map = NULL; while (summary_collection_map != NULL && g_variant_iter_loop (summary_collection_map, "{&s@a(s(taya{sv}))}", &summary_collection_id, &summary_refs)) { if (!remote_list_collection_refs_process_refs (self, remote_name, summary_collection_id, summary_refs, ret_all_refs, error)) return FALSE; } ot_transfer_out_value (out_all_refs, &ret_all_refs); return TRUE; } static char * relative_symlink_to (const char *relpath, const char *target) { g_assert (*relpath); g_assert (*target && *target != '/'); g_autoptr(GString) buf = g_string_new (""); while (TRUE) { const char *slash = strchr (relpath, '/'); if (!slash) break; relpath = slash + 1; g_string_append (buf, "../"); } g_string_append (buf, target); return g_string_free (g_steal_pointer (&buf), FALSE); } /* May specify @rev or @alias */ gboolean _ostree_repo_write_ref (OstreeRepo *self, const char *remote, const OstreeCollectionRef *ref, const char *rev, const char *alias, GCancellable *cancellable, GError **error) { glnx_autofd int dfd = -1; g_return_val_if_fail (remote == NULL || ref->collection_id == NULL, FALSE); g_return_val_if_fail (!(rev != NULL && alias != NULL), FALSE); if (remote != NULL && !ostree_validate_remote_name (remote, error)) return FALSE; if (ref->collection_id != NULL && !ostree_validate_collection_id (ref->collection_id, error)) return FALSE; if (!ostree_validate_rev (ref->ref_name, error)) return FALSE; if (remote == NULL && (ref->collection_id == NULL || g_strcmp0 (ref->collection_id, ostree_repo_get_collection_id (self)) == 0)) { if (!glnx_opendirat (self->repo_dir_fd, "refs/heads", TRUE, &dfd, error)) return FALSE; } else if (remote == NULL && ref->collection_id != NULL) { glnx_autofd int refs_mirrors_dfd = -1; /* refs/mirrors might not exist in older repositories, so create it. */ if (!glnx_shutil_mkdir_p_at_open (self->repo_dir_fd, "refs/mirrors", 0777, &refs_mirrors_dfd, cancellable, error)) return FALSE; if (rev != NULL) { /* Ensure we have a dir for the collection */ if (!glnx_shutil_mkdir_p_at (refs_mirrors_dfd, ref->collection_id, 0777, cancellable, error)) return FALSE; } dfd = glnx_opendirat_with_errno (refs_mirrors_dfd, ref->collection_id, TRUE); if (dfd < 0 && (errno != ENOENT || rev != NULL)) return glnx_throw_errno_prefix (error, "Opening mirrors/ dir %s", ref->collection_id); } else { glnx_autofd int refs_remotes_dfd = -1; if (!glnx_opendirat (self->repo_dir_fd, "refs/remotes", TRUE, &refs_remotes_dfd, error)) 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 && alias == NULL) { if (dfd >= 0) { if (!ot_ensure_unlinked_at (dfd, ref->ref_name, error)) return FALSE; } } else if (rev != NULL) { if (!write_checksum_file_at (self, dfd, ref->ref_name, rev, cancellable, error)) return FALSE; } else if (alias != NULL) { const char *lastslash = strrchr (ref->ref_name, '/'); if (lastslash) { char *parent = strdupa (ref->ref_name); parent[lastslash - ref->ref_name] = '\0'; if (!glnx_shutil_mkdir_p_at (dfd, parent, DEFAULT_DIRECTORY_MODE, cancellable, error)) return FALSE; } g_autofree char *reltarget = relative_symlink_to (ref->ref_name, alias); g_autofree char *tmplink = NULL; if (!_ostree_make_temporary_symlink_at (self->tmp_dir_fd, reltarget, &tmplink, cancellable, error)) return FALSE; if (!glnx_renameat (self->tmp_dir_fd, tmplink, dfd, ref->ref_name, error)) return FALSE; } if (!_ostree_repo_update_mtime (self, error)) return FALSE; /* Update the summary after updating the mtime so the summary doesn't look * out of date */ if (!self->in_transaction && !_ostree_repo_maybe_regenerate_summary (self, cancellable, error)) return FALSE; return TRUE; } gboolean _ostree_repo_update_refs (OstreeRepo *self, GHashTable *refs, /* (element-type utf8 utf8) */ GCancellable *cancellable, GError **error) { GLNX_HASH_TABLE_FOREACH_KV (refs, const char*, refspec, const char*, rev) { g_autofree char *remote = NULL; g_autofree char *ref_name = NULL; if (!ostree_parse_refspec (refspec, &remote, &ref_name, error)) return FALSE; const OstreeCollectionRef ref = { NULL, ref_name }; if (!_ostree_repo_write_ref (self, remote, &ref, rev, NULL, cancellable, error)) return FALSE; } return TRUE; } gboolean _ostree_repo_update_collection_refs (OstreeRepo *self, GHashTable *refs, /* (element-type OstreeCollectionRef utf8) */ GCancellable *cancellable, GError **error) { GHashTableIter hash_iter; gpointer key, value; g_hash_table_iter_init (&hash_iter, refs); while (g_hash_table_iter_next (&hash_iter, &key, &value)) { const OstreeCollectionRef *ref = key; const char *rev = value; if (!_ostree_repo_write_ref (self, NULL, ref, rev, NULL, cancellable, error)) return FALSE; } return TRUE; } /** * ostree_repo_list_collection_refs: * @self: Repo * @match_collection_id: (nullable): If non-%NULL, only list refs from this collection * @out_all_refs: (out) (element-type OstreeCollectionRef utf8) (transfer container): * Mapping from collection–ref to checksum * @flags: Options controlling listing behavior * @cancellable: Cancellable * @error: Error * * List all local, mirrored, and remote refs, mapping them to the commit * checksums they currently point to in @out_all_refs. If @match_collection_id * is specified, the results will be limited to those with an equal collection * ID. * * #OstreeCollectionRefs are guaranteed to be returned with their collection ID * set to a non-%NULL value; so no refs from `refs/heads` will be listed if no * collection ID is configured for the repository * (ostree_repo_get_collection_id()). * * If you want to exclude refs from `refs/remotes`, use * %OSTREE_REPO_LIST_REFS_EXT_EXCLUDE_REMOTES in @flags. Similarly use * %OSTREE_REPO_LIST_REFS_EXT_EXCLUDE_MIRRORS to exclude refs from * `refs/mirrors`. * * Returns: %TRUE on success, %FALSE otherwise * Since: 2018.6 */ gboolean ostree_repo_list_collection_refs (OstreeRepo *self, const char *match_collection_id, GHashTable **out_all_refs, OstreeRepoListRefsExtFlags flags, GCancellable *cancellable, GError **error) { GLNX_AUTO_PREFIX_ERROR ("Listing refs", error); g_return_val_if_fail (OSTREE_IS_REPO (self), FALSE); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); if (match_collection_id != NULL && !ostree_validate_collection_id (match_collection_id, error)) return FALSE; g_autoptr(GPtrArray) refs_dirs = g_ptr_array_new (); if (!(flags & OSTREE_REPO_LIST_REFS_EXT_EXCLUDE_MIRRORS)) g_ptr_array_add (refs_dirs, "refs/mirrors"); if (!(flags & OSTREE_REPO_LIST_REFS_EXT_EXCLUDE_REMOTES)) g_ptr_array_add (refs_dirs, "refs/remotes"); g_ptr_array_add (refs_dirs, NULL); g_autoptr(GHashTable) ret_all_refs = NULL; ret_all_refs = g_hash_table_new_full (ostree_collection_ref_hash, ostree_collection_ref_equal, (GDestroyNotify) ostree_collection_ref_free, g_free); g_autoptr(GString) base_path = g_string_new (""); const gchar *main_collection_id = ostree_repo_get_collection_id (self); if (main_collection_id != NULL && (match_collection_id == NULL || g_strcmp0 (match_collection_id, main_collection_id) == 0)) { glnx_autofd 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, flags, main_collection_id, refs_heads_dfd, base_path, refs_heads_dfd, ".", ret_all_refs, cancellable, error)) return FALSE; } g_string_truncate (base_path, 0); for (const char **iter = (const char **)refs_dirs->pdata; iter && *iter; iter++) { const char *refs_dir = *iter; gboolean refs_dir_exists = FALSE; g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; if (!ot_dfd_iter_init_allow_noent (self->repo_dir_fd, refs_dir, &dfd_iter, &refs_dir_exists, error)) return FALSE; while (refs_dir_exists) { struct dirent *dent; glnx_autofd int subdir_fd = -1; const gchar *current_collection_id; g_autofree gchar *remote_collection_id = NULL; 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 (g_strcmp0 (refs_dir, "refs/mirrors") == 0) { if (match_collection_id != NULL && g_strcmp0 (match_collection_id, dent->d_name) != 0) continue; else current_collection_id = dent->d_name; } else /* refs_dir = "refs/remotes" */ { g_autoptr(GError) local_error = NULL; if (!ostree_repo_get_remote_option (self, dent->d_name, "collection-id", NULL, &remote_collection_id, &local_error) || !ostree_validate_collection_id (remote_collection_id, &local_error)) { g_debug ("Ignoring remote ‘%s’ due to no valid collection ID being configured for it: %s", dent->d_name, local_error->message); g_clear_error (&local_error); continue; } if (match_collection_id != NULL && g_strcmp0 (match_collection_id, remote_collection_id) != 0) continue; else current_collection_id = remote_collection_id; } if (!glnx_opendirat (dfd_iter.fd, dent->d_name, TRUE, &subdir_fd, error)) return FALSE; if (!enumerate_refs_recurse (self, NULL, flags, current_collection_id, subdir_fd, base_path, subdir_fd, ".", ret_all_refs, cancellable, error)) return FALSE; } } ot_transfer_out_value (out_all_refs, &ret_all_refs); return TRUE; }