summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Larsson <alexl@redhat.com>2020-09-01 12:05:36 +0200
committerAlexander Larsson <alexl@redhat.com>2020-10-23 12:30:08 +0200
commiteffde3d513de1570488ddf13dac43c6413a44c68 (patch)
tree875716fe54ee78035e8505eeaa2259d803417caa
parent8e1f199dd4d035b5ff501aa37c6f1229ac3c0e61 (diff)
downloadostree-effde3d513de1570488ddf13dac43c6413a44c68.tar.gz
deltas: Update delta indexes when updating summary
When we update the summary file (and its list of deltas) we also update all delta indexes. The index format is a single `a{sv}` variant identical to the metadata-part of the summary with (currently) only the `ostree.static-deltas` key. Since we expect most delta indexes to change rarely, we avoid unnecessary writes when reindexing. New indexes are compared to existing ones and only the changed ones are written to disk. This avoids unnecessary write load and mtime changes on the repo server.
-rw-r--r--src/libostree/ostree-repo-static-delta-core.c165
-rw-r--r--src/libostree/ostree-repo-static-delta-private.h5
-rw-r--r--src/libostree/ostree-repo.c3
3 files changed, 173 insertions, 0 deletions
diff --git a/src/libostree/ostree-repo-static-delta-core.c b/src/libostree/ostree-repo-static-delta-core.c
index e3432aa5..c58e3683 100644
--- a/src/libostree/ostree-repo-static-delta-core.c
+++ b/src/libostree/ostree-repo-static-delta-core.c
@@ -1205,3 +1205,168 @@ ostree_repo_static_delta_verify_signature (OstreeRepo *self,
return _ostree_repo_static_delta_verify_signature (self, delta_fd, sign, out_success_message, error);
}
+
+static void
+null_or_ptr_array_unref (GPtrArray *array)
+{
+ if (array != NULL)
+ g_ptr_array_unref (array);
+}
+
+static gboolean
+file_has_content (OstreeRepo *repo,
+ const char *subpath,
+ GBytes *data,
+ GCancellable *cancellable)
+{
+ struct stat stbuf;
+ glnx_autofd int existing_fd = -1;
+
+ if (!glnx_fstatat (repo->repo_dir_fd, subpath, &stbuf, 0, NULL))
+ return FALSE;
+
+ if (stbuf.st_size != g_bytes_get_size (data))
+ return FALSE;
+
+ if (!glnx_openat_rdonly (repo->repo_dir_fd, subpath, TRUE, &existing_fd, NULL))
+ return FALSE;
+
+ g_autoptr(GBytes) existing_data = glnx_fd_readall_bytes (existing_fd, cancellable, NULL);
+ if (existing_data == NULL)
+ return FALSE;
+
+ return g_bytes_equal (existing_data, data);
+}
+
+gboolean
+_ostree_repo_static_delta_reindex (OstreeRepo *repo,
+ const char *opt_to_commit,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GPtrArray) all_deltas = NULL;
+ g_autoptr(GHashTable) deltas_to_commit_ht = NULL; /* map: to checksum -> ptrarray of from checksums (or NULL) */
+
+ deltas_to_commit_ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)null_or_ptr_array_unref);
+
+ if (opt_to_commit == NULL)
+ {
+ g_autoptr(GPtrArray) old_indexes = NULL;
+
+ /* To ensure all old index files either is regenerated, or
+ * removed, we initialize all existing indexes to NULL in the
+ * hashtable. */
+ if (!ostree_repo_list_static_delta_indexes (repo, &old_indexes, cancellable, error))
+ return FALSE;
+
+ for (int i = 0; i < old_indexes->len; i++)
+ {
+ const char *old_index = g_ptr_array_index (old_indexes, i);
+ g_hash_table_insert (deltas_to_commit_ht, g_strdup (old_index), NULL);
+ }
+ }
+ else
+ {
+ if (!ostree_validate_checksum_string (opt_to_commit, error))
+ return FALSE;
+
+ /* We ensure the specific old index either is regenerated, or removed */
+ g_hash_table_insert (deltas_to_commit_ht, g_strdup (opt_to_commit), NULL);
+ }
+
+ if (!ostree_repo_list_static_delta_names (repo, &all_deltas, cancellable, error))
+ return FALSE;
+
+ for (int i = 0; i < all_deltas->len; i++)
+ {
+ const char *delta_name = g_ptr_array_index (all_deltas, i);
+ g_autofree char *from = NULL;
+ g_autofree char *to = NULL;
+ GPtrArray *deltas_to_commit = NULL;
+
+ if (!_ostree_parse_delta_name (delta_name, &from, &to, error))
+ return FALSE;
+
+ if (opt_to_commit != NULL && strcmp (to, opt_to_commit) != 0)
+ continue;
+
+ deltas_to_commit = g_hash_table_lookup (deltas_to_commit_ht, to);
+ if (deltas_to_commit == NULL)
+ {
+ deltas_to_commit = g_ptr_array_new_with_free_func (g_free);
+ g_hash_table_insert (deltas_to_commit_ht, g_steal_pointer (&to), deltas_to_commit);
+ }
+
+ g_ptr_array_add (deltas_to_commit, g_steal_pointer (&from));
+ }
+
+ GLNX_HASH_TABLE_FOREACH_KV (deltas_to_commit_ht, const char*, to, GPtrArray*, froms)
+ {
+ g_autofree char *index_path = _ostree_get_relative_static_delta_index_path (to);
+
+ if (froms == NULL)
+ {
+ /* No index to this checksum seen, delete if it exists */
+
+ g_debug ("Removing delta index for %s", to);
+ if (!ot_ensure_unlinked_at (repo->repo_dir_fd, index_path, error))
+ return FALSE;
+ }
+ else
+ {
+ g_auto(GVariantDict) index_builder = OT_VARIANT_BUILDER_INITIALIZER;
+ g_auto(GVariantDict) deltas_builder = OT_VARIANT_BUILDER_INITIALIZER;
+ g_autoptr(GVariant) index_variant = NULL;
+ g_autoptr(GBytes) index = NULL;
+
+ /* We sort on from here so that the index file is reproducible */
+ g_ptr_array_sort (froms, (GCompareFunc)g_strcmp0);
+
+ g_variant_dict_init (&deltas_builder, NULL);
+
+ for (int i = 0; i < froms->len; i++)
+ {
+ const char *from = g_ptr_array_index (froms, i);
+ g_autofree char *delta_name = NULL;
+ GVariant *digest;
+
+ digest = _ostree_repo_static_delta_superblock_digest (repo, from, to, cancellable, error);
+ if (digest == NULL)
+ return FALSE;
+
+ if (from != NULL)
+ delta_name = g_strconcat (from, "-", to, NULL);
+ else
+ delta_name = g_strdup (to);
+
+ g_variant_dict_insert_value (&deltas_builder, delta_name, digest);
+ }
+
+ /* The toplevel of the index is an a{sv} for extensibility, and we use same key name (and format) as when
+ * storing deltas in the summary. */
+ g_variant_dict_init (&index_builder, NULL);
+
+ g_variant_dict_insert_value (&index_builder, OSTREE_SUMMARY_STATIC_DELTAS, g_variant_dict_end (&deltas_builder));
+
+ index_variant = g_variant_ref_sink (g_variant_dict_end (&index_builder));
+ index = g_variant_get_data_as_bytes (index_variant);
+
+ g_autofree char *index_dirname = g_path_get_dirname (index_path);
+ if (!glnx_shutil_mkdir_p_at (repo->repo_dir_fd, index_dirname, DEFAULT_DIRECTORY_MODE, cancellable, error))
+ return FALSE;
+
+ /* delta indexes are generally small and static, so reading it back and comparing is cheap, and it will
+ lower the write load (and particular sync-load) on the disk during reindexing (i.e. summary updates), */
+ if (file_has_content (repo, index_path, index, cancellable))
+ continue;
+
+ g_debug ("Updating delta index for %s", to);
+ if (!glnx_file_replace_contents_at (repo->repo_dir_fd, index_path,
+ g_bytes_get_data (index, NULL), g_bytes_get_size (index),
+ 0, cancellable, error))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
diff --git a/src/libostree/ostree-repo-static-delta-private.h b/src/libostree/ostree-repo-static-delta-private.h
index 5a2e6879..d6c706da 100644
--- a/src/libostree/ostree-repo-static-delta-private.h
+++ b/src/libostree/ostree-repo-static-delta-private.h
@@ -227,6 +227,11 @@ _ostree_repo_static_delta_delete (OstreeRepo *repo,
const char *delta_id,
GCancellable *cancellable,
GError **error);
+gboolean
+_ostree_repo_static_delta_reindex (OstreeRepo *repo,
+ const char *opt_to_commit,
+ GCancellable *cancellable,
+ GError **error);
/* Used for static deltas which due to a historical mistake are
* inconsistent endian.
diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c
index ba3e877f..3a331c90 100644
--- a/src/libostree/ostree-repo.c
+++ b/src/libostree/ostree-repo.c
@@ -5927,6 +5927,9 @@ ostree_repo_regenerate_summary (OstreeRepo *self,
g_variant_ref_sink (summary);
}
+ if (!_ostree_repo_static_delta_reindex (self, NULL, cancellable, error))
+ return FALSE;
+
if (!_ostree_repo_file_replace_contents (self,
self->repo_dir_fd,
"summary",