ostree/src/libostree/ostree-repo-refs.c

1397 lines
48 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (C) 2011,2013 Colin Walters <walters@verbum.org>
*
* 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, see <https://www.gnu.org/licenses/>.
*/
#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)
{
if (remote != NULL && collection_id != NULL)
return glnx_throw (error, "Cannot process both a remote and a collection ID");
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 collectionrefs
* 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 collectionref to resolve
* @allow_noent: %TRUE to not throw an error if @ref doesnt 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 collectionref, 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,
"Collectionref (%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 "<remote>:" and "<remote>:.", 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 collectionref 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 collectionref 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;
}