/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- * * Copyright (C) 2011 Colin Walters * * 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. * * Author: Colin Walters */ #include "config.h" #include "ostree-repo-private.h" static gboolean add_ref_to_set (const char *remote, GFile *base, GFile *child, GHashTable *refs, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; char *contents; char *relpath; gsize len; GString *refname; if (!g_file_load_contents (child, cancellable, &contents, &len, NULL, error)) goto out; g_strchomp (contents); refname = g_string_new (""); if (remote) { g_string_append (refname, remote); g_string_append_c (refname, ':'); } relpath = g_file_get_relative_path (base, child); g_string_append (refname, relpath); g_free (relpath); g_hash_table_insert (refs, g_string_free (refname, FALSE), contents); ret = TRUE; out: return ret; } static gboolean write_checksum_file (GFile *parentdir, const char *name, const char *sha256, GError **error) { gboolean ret = FALSE; gsize bytes_written; int i; gs_unref_object GFile *parent = NULL; gs_unref_object GFile *child = NULL; gs_unref_object GOutputStream *out = NULL; gs_unref_ptrarray GPtrArray *components = NULL; if (!ostree_validate_checksum_string (sha256, error)) goto out; if (ostree_validate_checksum_string (name, NULL)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Rev name '%s' looks like a checksum", name); goto out; } if (!ot_util_path_split_validate (name, &components, error)) goto out; if (components->len == 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid empty ref name"); goto out; } parent = g_object_ref (parentdir); for (i = 0; i+1 < components->len; i++) { child = g_file_get_child (parent, (char*)components->pdata[i]); if (!gs_file_ensure_directory (child, FALSE, NULL, error)) goto out; g_clear_object (&parent); parent = child; child = NULL; } child = g_file_get_child (parent, components->pdata[components->len - 1]); if ((out = (GOutputStream*)g_file_replace (child, NULL, FALSE, 0, NULL, error)) == NULL) goto out; if (!g_output_stream_write_all (out, sha256, strlen (sha256), &bytes_written, NULL, error)) goto out; if (!g_output_stream_write_all (out, "\n", 1, &bytes_written, NULL, error)) goto out; if (!g_output_stream_close (out, NULL, error)) goto out; ret = TRUE; out: return ret; } static gboolean parse_rev_file (OstreeRepo *self, GFile *f, char **sha256, GError **error) G_GNUC_UNUSED; static gboolean parse_rev_file (OstreeRepo *self, GFile *f, char **sha256, GError **error) { gboolean ret = FALSE; GError *temp_error = NULL; gs_free char *rev = NULL; if ((rev = gs_file_load_contents_utf8 (f, NULL, &temp_error)) == NULL) goto out; if (rev == NULL) { if (g_error_matches (temp_error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) { g_clear_error (&temp_error); } else { g_propagate_error (error, temp_error); goto out; } } else { g_strchomp (rev); } if (g_str_has_prefix (rev, "ref: ")) { gs_unref_object GFile *ref = NULL; char *ref_sha256; gboolean subret; ref = g_file_resolve_relative_path (self->local_heads_dir, rev + 5); subret = parse_rev_file (self, ref, &ref_sha256, error); if (!subret) { g_free (ref_sha256); goto out; } g_free (rev); rev = ref_sha256; } else { if (!ostree_validate_checksum_string (rev, error)) goto out; } ot_transfer_out_value(sha256, &rev); ret = TRUE; out: return ret; } static gboolean find_ref_in_remotes (OstreeRepo *self, const char *rev, GFile **out_file, GError **error) { gboolean ret = FALSE; gs_unref_object GFileEnumerator *dir_enum = NULL; gs_unref_object GFile *ret_file = NULL; dir_enum = g_file_enumerate_children (self->remote_heads_dir, OSTREE_GIO_FAST_QUERYINFO, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, error); if (!dir_enum) goto out; while (TRUE) { GFileInfo *file_info; GFile *child; if (!gs_file_enumerator_iterate (dir_enum, &file_info, &child, NULL, error)) goto out; if (file_info == NULL) break; if (g_file_info_get_file_type (file_info) != G_FILE_TYPE_DIRECTORY) continue; g_clear_object (&ret_file); ret_file = g_file_resolve_relative_path (child, rev); if (!g_file_query_exists (ret_file, NULL)) g_clear_object (&ret_file); else break; } ret = TRUE; ot_transfer_out_value (out_file, &ret_file); out: return ret; } static gboolean resolve_refspec (OstreeRepo *self, const char *remote, const char *ref, gboolean allow_noent, char **out_rev, GError **error); static gboolean resolve_refspec_fallback (OstreeRepo *self, const char *remote, const char *ref, gboolean allow_noent, char **out_rev, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; gs_free char *ret_rev = NULL; if (self->parent_repo) { if (!resolve_refspec (self->parent_repo, remote, ref, allow_noent, &ret_rev, error)) goto out; } else if (!allow_noent) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Refspec '%s%s%s' not found", remote ? remote : "", remote ? ":" : "", ref); goto out; } ret = TRUE; ot_transfer_out_value (out_rev, &ret_rev); out: return ret; } static gboolean resolve_refspec (OstreeRepo *self, const char *remote, const char *ref, gboolean allow_noent, char **out_rev, GError **error) { gboolean ret = FALSE; __attribute__((unused)) GCancellable *cancellable = NULL; GError *temp_error = NULL; gs_free char *tmp = NULL; gs_free char *tmp2 = NULL; gs_free char *ret_rev = NULL; gs_unref_object GFile *child = NULL; gs_unref_object GFile *origindir = NULL; 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 (remote != NULL) { child = ot_gfile_resolve_path_printf (self->remote_heads_dir, "%s/%s", remote, ref); if (!g_file_query_exists (child, NULL)) g_clear_object (&child); } else { child = g_file_resolve_relative_path (self->local_heads_dir, ref); if (!g_file_query_exists (child, NULL)) { g_clear_object (&child); child = g_file_resolve_relative_path (self->remote_heads_dir, ref); if (!g_file_query_exists (child, NULL)) { g_clear_object (&child); if (!find_ref_in_remotes (self, ref, &child, error)) goto out; } } } if (child) { if ((ret_rev = gs_file_load_contents_utf8 (child, NULL, &temp_error)) == NULL) { g_propagate_error (error, temp_error); g_prefix_error (error, "Couldn't open ref '%s': ", gs_file_get_path_cached (child)); goto out; } g_strchomp (ret_rev); if (!ostree_validate_checksum_string (ret_rev, error)) goto out; } else { if (!resolve_refspec_fallback (self, remote, ref, allow_noent, &ret_rev, cancellable, error)) goto out; } ot_transfer_out_value (out_rev, &ret_rev); ret = TRUE; out: return ret; } gboolean ostree_repo_resolve_rev (OstreeRepo *self, const char *refspec, gboolean allow_noent, char **out_rev, GError **error) { gboolean ret = FALSE; gs_free 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 (g_str_has_suffix (refspec, "^")) { gs_free char *parent_refspec = NULL; gs_free char *parent_rev = NULL; gs_unref_variant GVariant *commit = NULL; gs_unref_variant GVariant *parent_csum_v = 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)) goto out; if (!ostree_repo_load_variant (self, OSTREE_OBJECT_TYPE_COMMIT, parent_rev, &commit, error)) goto out; g_variant_get_child (commit, 1, "@ay", &parent_csum_v); if (g_variant_n_children (parent_csum_v) == 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Commit %s has no parent", parent_rev); goto out; } ret_rev = ostree_checksum_from_bytes_v (parent_csum_v); } else { gs_free char *remote = NULL; gs_free char *ref = NULL; if (!ostree_parse_refspec (refspec, &remote, &ref, error)) goto out; if (!resolve_refspec (self, remote, ref, allow_noent, &ret_rev, error)) goto out; } } ret = TRUE; ot_transfer_out_value (out_rev, &ret_rev); out: return ret; } static gboolean enumerate_refs_recurse (OstreeRepo *repo, const char *remote, GFile *base, GFile *dir, GHashTable *refs, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; gs_unref_object GFileEnumerator *enumerator = NULL; enumerator = g_file_enumerate_children (dir, OSTREE_GIO_FAST_QUERYINFO, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, cancellable, error); if (!enumerator) goto out; while (TRUE) { GFileInfo *file_info = NULL; GFile *child = NULL; if (!gs_file_enumerator_iterate (enumerator, &file_info, &child, NULL, error)) goto out; if (file_info == NULL) break; if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY) { if (!enumerate_refs_recurse (repo, remote, base, child, refs, cancellable, error)) goto out; } else if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR) { if (!add_ref_to_set (remote, base, child, refs, cancellable, error)) goto out; } } ret = TRUE; out: return ret; } gboolean ostree_repo_list_refs (OstreeRepo *repo, const char *refspec_prefix, GHashTable **out_all_refs, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; gs_unref_hashtable GHashTable *ret_all_refs = NULL; gs_free char *remote = NULL; gs_free char *ref_prefix = NULL; ret_all_refs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); if (refspec_prefix) { gs_unref_object GFile *dir = NULL; gs_unref_object GFile *child = NULL; gs_unref_object GFileInfo *info = NULL; if (!ostree_parse_refspec (refspec_prefix, &remote, &ref_prefix, error)) goto out; if (remote) dir = g_file_get_child (repo->remote_heads_dir, remote); else dir = g_object_ref (repo->local_heads_dir); child = g_file_resolve_relative_path (dir, ref_prefix); if (!ot_gfile_query_info_allow_noent (child, OSTREE_GIO_FAST_QUERYINFO, 0, &info, cancellable, error)) goto out; if (info) { if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) { if (!enumerate_refs_recurse (repo, remote, child, child, ret_all_refs, cancellable, error)) goto out; } else { if (!add_ref_to_set (remote, dir, child, ret_all_refs, cancellable, error)) goto out; } } } else { gs_unref_object GFileEnumerator *remote_enumerator = NULL; if (!enumerate_refs_recurse (repo, NULL, repo->local_heads_dir, repo->local_heads_dir, ret_all_refs, cancellable, error)) goto out; remote_enumerator = g_file_enumerate_children (repo->remote_heads_dir, OSTREE_GIO_FAST_QUERYINFO, 0, cancellable, error); while (TRUE) { GFileInfo *info; GFile *child; const char *name; if (!gs_file_enumerator_iterate (remote_enumerator, &info, &child, cancellable, error)) goto out; if (!info) break; name = g_file_info_get_name (info); if (!enumerate_refs_recurse (repo, name, child, child, ret_all_refs, cancellable, error)) goto out; } } ret = TRUE; ot_transfer_out_value (out_all_refs, &ret_all_refs); out: return ret; } static gboolean write_ref_summary (OstreeRepo *self, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; GHashTableIter hash_iter; gpointer key, value; gsize bytes_written; gs_unref_hashtable GHashTable *all_refs = NULL; gs_unref_object GFile *summary_path = NULL; gs_unref_object GOutputStream *out = NULL; gs_free char *buf = NULL; if (!ostree_repo_list_refs (self, NULL, &all_refs, cancellable, error)) goto out; summary_path = g_file_resolve_relative_path (ostree_repo_get_path (self), "refs/summary"); out = (GOutputStream*) g_file_replace (summary_path, NULL, FALSE, 0, cancellable, error); if (!out) goto out; g_hash_table_iter_init (&hash_iter, all_refs); while (g_hash_table_iter_next (&hash_iter, &key, &value)) { const char *name = key; const char *sha256 = value; g_free (buf); buf = g_strdup_printf ("%s %s\n", sha256, name); if (!g_output_stream_write_all (out, buf, strlen (buf), &bytes_written, cancellable, error)) goto out; } if (!g_output_stream_close (out, cancellable, error)) goto out; ret = TRUE; out: return ret; } gboolean ostree_repo_write_ref (OstreeRepo *self, const char *remote, const char *name, const char *rev, GError **error) { gboolean ret = FALSE; gs_unref_object GFile *dir = NULL; if (remote == NULL) dir = g_object_ref (self->local_heads_dir); else { dir = g_file_get_child (self->remote_heads_dir, remote); if (rev != NULL) { if (!gs_file_ensure_directory (dir, FALSE, NULL, error)) goto out; } } if (rev == NULL) { gs_unref_object GFile *child = g_file_resolve_relative_path (dir, name); if (g_file_query_exists (child, NULL)) { if (!gs_file_unlink (child, NULL, error)) goto out; } } else { if (!write_checksum_file (dir, name, rev, error)) goto out; if (rev != NULL) { if (self->mode == OSTREE_REPO_MODE_ARCHIVE || self->mode == OSTREE_REPO_MODE_ARCHIVE_Z2) { if (!write_ref_summary (self, NULL, error)) goto out; } } } ret = TRUE; out: return ret; } gboolean ostree_repo_write_refspec (OstreeRepo *self, const char *refspec, const char *rev, GError **error) { gboolean ret = FALSE; gs_free char *remote = NULL; gs_free char *ref = NULL; if (!ostree_parse_refspec (refspec, &remote, &ref, error)) goto out; if (!ostree_repo_write_ref (self, remote, ref, rev, error)) goto out; ret = TRUE; out: return ret; }