From 17b9e399b8b70266a3023a0671f73901976510e2 Mon Sep 17 00:00:00 2001 From: Matthew Barnes Date: Wed, 3 Dec 2014 18:58:16 -0500 Subject: [PATCH] repo: Add an internal struct to manage remotes OstreeRemote is a reference-counted struct that encompasses data about a remote, whether read from a configuration file or created explicitly via ostree_repo_remote_add(). OstreeRemotes are held in an internal table indexed by remote name. This solves some problems caused by merging system-wide remote data into the OstreeRepo's internal config key file. Also fixes https://bugzilla.gnome.org/show_bug.cgi?id=740911 --- src/libostree/ostree-repo-private.h | 2 + src/libostree/ostree-repo.c | 371 +++++++++++++++++++++------- 2 files changed, 278 insertions(+), 95 deletions(-) diff --git a/src/libostree/ostree-repo-private.h b/src/libostree/ostree-repo-private.h index cce24f55..d14d3027 100644 --- a/src/libostree/ostree-repo-private.h +++ b/src/libostree/ostree-repo-private.h @@ -68,6 +68,8 @@ struct OstreeRepo { gid_t target_owner_gid; GKeyFile *config; + GHashTable *remotes; + GMutex remotes_lock; OstreeRepoMode mode; gboolean enable_uncompressed_cache; gboolean generate_sizes; diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index a3e0ef66..26a257db 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -85,6 +85,152 @@ enum { G_DEFINE_TYPE (OstreeRepo, ostree_repo, G_TYPE_OBJECT) +GS_DEFINE_CLEANUP_FUNCTION0(GKeyFile*, local_keyfile_unref, g_key_file_unref) +#define local_cleanup_keyfile __attribute__ ((cleanup(local_keyfile_unref))) + +typedef struct { + volatile int ref_count; + char *name; + char *group; /* group name in options */ + GFile *file; /* NULL if remote defined in repo/config */ + GKeyFile *options; +} OstreeRemote; + +static OstreeRemote * +ost_remote_new (void) +{ + OstreeRemote *remote; + + remote = g_slice_new0 (OstreeRemote); + remote->ref_count = 1; + remote->options = g_key_file_new (); + + return remote; +} + +static OstreeRemote * +ost_remote_new_from_keyfile (GKeyFile *keyfile, + const gchar *group) +{ + GMatchInfo *match = NULL; + OstreeRemote *remote; + + static gsize regex_initialized; + static GRegex *regex; + + if (g_once_init_enter (®ex_initialized)) + { + regex = g_regex_new ("^remote \"(.+)\"$", 0, 0, NULL); + g_assert (regex); + g_once_init_leave (®ex_initialized, 1); + } + + /* Sanity check */ + g_return_val_if_fail (g_key_file_has_group (keyfile, group), NULL); + + /* If group name doesn't fit the pattern, fail. */ + if (!g_regex_match (regex, group, 0, &match)) + return NULL; + + remote = ost_remote_new (); + remote->name = g_match_info_fetch (match, 1); + remote->group = g_strdup (group); + + ot_keyfile_copy_group (keyfile, remote->options, group); + + g_match_info_unref (match); + + return remote; +} + +static OstreeRemote * +ost_remote_ref (OstreeRemote *remote) +{ + g_return_val_if_fail (remote != NULL, NULL); + g_return_val_if_fail (remote->ref_count > 0, NULL); + + g_atomic_int_inc (&remote->ref_count); + + return remote; +} + +static void +ost_remote_unref (OstreeRemote *remote) +{ + g_return_if_fail (remote != NULL); + g_return_if_fail (remote->ref_count > 0); + + if (g_atomic_int_dec_and_test (&remote->ref_count)) + { + g_clear_pointer (&remote->name, g_free); + g_clear_pointer (&remote->group, g_free); + g_clear_object (&remote->file); + g_clear_pointer (&remote->options, g_key_file_free); + g_slice_free (OstreeRemote, remote); + } +} + +GS_DEFINE_CLEANUP_FUNCTION0(OstreeRemote*, local_remote_unref, ost_remote_unref) +#define local_cleanup_remote __attribute__ ((cleanup(local_remote_unref))) + +static OstreeRemote * +ost_repo_get_remote (OstreeRepo *self, + const char *name, + GError **error) +{ + OstreeRemote *remote = NULL; + + g_return_val_if_fail (name != NULL, NULL); + + g_mutex_lock (&self->remotes_lock); + + remote = g_hash_table_lookup (self->remotes, name); + + if (remote != NULL) + ost_remote_ref (remote); + else + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Remote \"%s\" not found", name); + + g_mutex_unlock (&self->remotes_lock); + + return remote; +} + +static void +ost_repo_add_remote (OstreeRepo *self, + OstreeRemote *remote) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (remote != NULL); + g_return_if_fail (remote->name != NULL); + + g_mutex_lock (&self->remotes_lock); + + g_hash_table_replace (self->remotes, remote->name, ost_remote_ref (remote)); + + g_mutex_unlock (&self->remotes_lock); +} + +static gboolean +ost_repo_remove_remote (OstreeRepo *self, + OstreeRemote *remote) +{ + gboolean removed; + + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (remote != NULL, FALSE); + g_return_val_if_fail (remote->name != NULL, FALSE); + + g_mutex_lock (&self->remotes_lock); + + removed = g_hash_table_remove (self->remotes, remote->name); + + g_mutex_unlock (&self->remotes_lock); + + return removed; +} + static void ostree_repo_finalize (GObject *object) { @@ -123,6 +269,9 @@ ostree_repo_finalize (GObject *object) g_mutex_clear (&self->cache_lock); g_mutex_clear (&self->txn_stats_lock); + g_clear_pointer (&self->remotes, g_hash_table_destroy); + g_mutex_clear (&self->remotes_lock); + G_OBJECT_CLASS (ostree_repo_parent_class)->finalize (object); } @@ -209,6 +358,12 @@ ostree_repo_init (OstreeRepo *self) { g_mutex_init (&self->cache_lock); g_mutex_init (&self->txn_stats_lock); + + self->remotes = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) NULL, + (GDestroyNotify) ost_remote_unref); + g_mutex_init (&self->remotes_lock); + self->objects_dir_fd = -1; self->uncompressed_objects_dir_fd = -1; } @@ -373,9 +528,6 @@ keyfile_set_from_vardict (GKeyFile *keyfile, } } -GS_DEFINE_CLEANUP_FUNCTION0(GKeyFile*, local_keyfile_unref, g_key_file_unref) -#define local_cleanup_keyfile __attribute__ ((cleanup(local_keyfile_unref))) - /** * ostree_repo_remote_add: * @self: Repo @@ -402,13 +554,9 @@ ostree_repo_remote_add (OstreeRepo *self, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; - gboolean is_system; - gs_free char *section = NULL; gs_unref_object GFile *etc_ostree_remotes_d = g_file_new_for_path (SYSCONFDIR "/ostree/remotes.d"); - local_cleanup_keyfile GKeyFile *target_keyfile = NULL; - gs_free char *target_name = NULL; - gs_unref_object GFile *target_conf = NULL; + local_cleanup_remote OstreeRemote *remote = NULL; + gboolean ret = FALSE; g_return_val_if_fail (name != NULL, FALSE); g_return_val_if_fail (url != NULL, FALSE); @@ -422,53 +570,70 @@ ostree_repo_remote_add (OstreeRepo *self, goto out; } - section = g_strdup_printf ("remote \"%s\"", name); + remote = ost_repo_get_remote (self, name, NULL); - is_system = ostree_repo_is_system (self); - if (is_system) + if (remote != NULL) { - target_keyfile = g_key_file_new (); + GFile *file; - target_name = g_strconcat (name, ".conf", NULL); - target_conf = g_file_get_child (etc_ostree_remotes_d, target_name); - - if (g_file_query_exists (target_conf, NULL)) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Remote configuration already exists: %s", - gs_file_get_path_cached (target_conf)); - goto out; - } + if (remote->file != NULL) + file = remote->file; + else + file = self->config_file; + + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Remote configuration for \"%s\" already exists: %s", + name, gs_file_get_path_cached (file)); + + goto out; } - else + + remote = ost_remote_new (); + remote->name = g_strdup (name); + remote->group = g_strdup_printf ("remote \"%s\"", name); + + if (ostree_repo_is_system (self)) { - target_keyfile = ostree_repo_copy_config (self); + gs_free char *basename = g_strconcat (name, ".conf", NULL); + remote->file = g_file_get_child (etc_ostree_remotes_d, basename); } if (g_str_has_prefix (url, "metalink=")) - g_key_file_set_string (target_keyfile, section, "metalink", url + strlen ("metalink=")); + g_key_file_set_string (remote->options, remote->group, "metalink", url + strlen ("metalink=")); else - g_key_file_set_string (target_keyfile, section, "url", url); + g_key_file_set_string (remote->options, remote->group, "url", url); if (options) - keyfile_set_from_vardict (target_keyfile, section, options); + keyfile_set_from_vardict (remote->options, remote->group, options); - if (is_system) + if (remote->file != NULL) { - gsize len; - gs_free char *data = g_key_file_to_data (target_keyfile, &len, error); - if (!g_file_replace_contents (target_conf, data, len, + gs_free char *data = NULL; + gsize length; + + data = g_key_file_to_data (remote->options, &length, NULL); + + if (!g_file_replace_contents (remote->file, + data, length, NULL, FALSE, 0, NULL, cancellable, error)) goto out; } else { - if (!ostree_repo_write_config (self, target_keyfile, error)) + local_cleanup_keyfile GKeyFile *config = NULL; + + config = ostree_repo_copy_config (self); + ot_keyfile_copy_group (remote->options, config, remote->group); + + if (!ostree_repo_write_config (self, config, error)) goto out; } + ost_repo_add_remote (self, remote); + ret = TRUE; + out: return ret; } @@ -490,12 +655,8 @@ ostree_repo_remote_delete (OstreeRepo *self, GCancellable *cancellable, GError **error) { + local_cleanup_remote OstreeRemote *remote = NULL; gboolean ret = FALSE; - gs_unref_object GFile *etc_ostree_remotes_d = g_file_new_for_path (SYSCONFDIR "/ostree/remotes.d"); - local_cleanup_keyfile GKeyFile *target_keyfile = NULL; - gs_free char *section = NULL; - gs_unref_object GFile *target_conf = NULL; - gboolean is_system; g_return_val_if_fail (name != NULL, FALSE); @@ -507,44 +668,35 @@ ostree_repo_remote_delete (OstreeRepo *self, goto out; } - section = g_strdup_printf ("remote \"%s\"", name); + remote = ost_repo_get_remote (self, name, error); - /* Note we prefer deleting from the config if it exists there */ - if (g_key_file_has_group (self->config, section)) - is_system = FALSE; - else - is_system = ostree_repo_is_system (self); + if (remote == NULL) + goto out; - if (is_system) + if (remote->file != NULL) { - gs_free char *target_name = NULL; - - target_name = g_strconcat (name, ".conf", NULL); - target_conf = g_file_get_child (etc_ostree_remotes_d, target_name); - - if (!gs_file_unlink (target_conf, cancellable, error)) + if (!gs_file_unlink (remote->file, cancellable, error)) goto out; } else { - gsize len; - gs_free char *data = NULL; + local_cleanup_keyfile GKeyFile *config = NULL; - target_conf = g_object_ref (self->config_file); + config = ostree_repo_copy_config (self); - target_keyfile = ostree_repo_copy_config (self); - - if (!g_key_file_remove_group (target_keyfile, section, error)) - goto out; - - data = g_key_file_to_data (target_keyfile, &len, NULL); - if (!g_file_replace_contents (target_conf, data, len, - NULL, FALSE, 0, NULL, - cancellable, error)) - goto out; + /* XXX Not sure it's worth failing if the group to remove + * isn't found. It's the end result we want, after all. */ + if (g_key_file_remove_group (config, remote->group, NULL)) + { + if (!ostree_repo_write_config (self, config, error)) + goto out; + } } + ost_repo_remove_remote (self, remote); + ret = TRUE; + out: return ret; } @@ -718,6 +870,63 @@ enumerate_directory_allow_noent (GFile *dirpath, return ret; } +static gboolean +add_remotes_from_keyfile (OstreeRepo *self, + GKeyFile *keyfile, + GFile *file, + GError **error) +{ + GQueue queue = G_QUEUE_INIT; + gs_strfreev char **groups = NULL; + gsize length, ii; + gboolean ret = FALSE; + + g_mutex_lock (&self->remotes_lock); + + groups = g_key_file_get_groups (keyfile, &length); + + for (ii = 0; ii < length; ii++) + { + OstreeRemote *remote; + + remote = ost_remote_new_from_keyfile (keyfile, groups[ii]); + + if (remote != NULL) + { + /* Make sure all the remotes in the key file are + * acceptable before adding any to the OstreeRepo. */ + g_queue_push_tail (&queue, remote); + + if (g_hash_table_contains (self->remotes, remote->name)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Multiple specifications found for remote \"%s\"", + remote->name); + goto out; + } + + if (file != NULL) + remote->file = g_object_ref (file); + } + } + + while (!g_queue_is_empty (&queue)) + { + OstreeRemote *remote = g_queue_pop_head (&queue); + g_hash_table_replace (self->remotes, remote->name, remote); + } + + ret = TRUE; + + out: + while (!g_queue_is_empty (&queue)) + ost_remote_unref (g_queue_pop_head (&queue)); + + g_mutex_unlock (&self->remotes_lock); + + return ret; +} + static gboolean append_one_remote_config (OstreeRepo *self, GFile *path, @@ -726,43 +935,13 @@ append_one_remote_config (OstreeRepo *self, { gboolean ret = FALSE; local_cleanup_keyfile GKeyFile *remotedata = g_key_file_new (); - gs_strfreev char **groups = NULL; - char **iter; if (!g_key_file_load_from_file (remotedata, gs_file_get_path_cached (path), 0, error)) goto out; - groups = g_key_file_get_groups (remotedata, NULL); - for (iter = groups; iter && *iter; iter++) - { - const char *group = *iter; - char **subiter; - gs_strfreev char **keys = NULL; - - /* Whitelist of allowed groups for now */ - if (!g_str_has_prefix (group, "remote \"")) - continue; + ret = add_remotes_from_keyfile (self, remotedata, path, error); - if (g_key_file_has_group (self->config, group)) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Multiple specifications found for %s", group); - goto out; - } - - keys = g_key_file_get_keys (remotedata, group, NULL, NULL); - g_assert (keys); - for (subiter = keys; subiter && *subiter; subiter++) - { - const char *key = *subiter; - gs_free char *value = g_key_file_get_value (remotedata, group, key, NULL); - g_assert (value); - g_key_file_set_value (self->config, group, key, value); - } - } - - ret = TRUE; out: return ret; } @@ -860,6 +1039,8 @@ ostree_repo_open (OstreeRepo *self, g_prefix_error (error, "Couldn't parse config file: "); goto out; } + if (!add_remotes_from_keyfile (self, self->config, NULL, error)) + goto out; version = g_key_file_get_value (self->config, "core", "repo_version", error); if (!version)