From e8dbaa3c077a712cfe064e968433be2afb0f568f Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sun, 22 Jan 2012 13:27:38 -0500 Subject: [PATCH] core: Add generic "commit filter" API, use it to implement statoverride The builder wants the ability to mark a given file as e.g. setuid. To implement this, the repo now has a callback-based API when importing a directory to modify or remove items. The commit tool accepts a "statoverride" file as input which looks like: +mode /path/to/file --- src/libostree/ostree-repo.c | 260 +++++++++++++++++++++------------ src/libostree/ostree-repo.h | 16 +- src/ostree/ot-builtin-commit.c | 118 ++++++++++++++- tests/t0000-basic.sh | 10 +- 4 files changed, 303 insertions(+), 101 deletions(-) diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index c0863656..d7c3ff2b 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -1465,21 +1465,58 @@ create_modified_file_info (GFileInfo *info, ret = g_file_info_dup (info); - if (modifier->uid >= 0) - g_file_info_set_attribute_uint32 (ret, "unix::uid", modifier->uid); - if (modifier->gid >= 0) - g_file_info_set_attribute_uint32 (ret, "unix::gid", modifier->gid); - return ret; } -gboolean -ostree_repo_stage_directory_to_mtree (OstreeRepo *self, - GFile *dir, - OstreeMutableTree *mtree, - OstreeRepoCommitModifier *modifier, - GCancellable *cancellable, - GError **error) +static OstreeRepoCommitFilterResult +apply_commit_filter (OstreeRepo *self, + OstreeRepoCommitModifier *modifier, + GPtrArray *path, + GFileInfo *file_info, + GFileInfo **out_modified_info) +{ + GString *path_buf; + guint i; + OstreeRepoCommitFilterResult result; + GFileInfo *modified_info; + + if (modifier == NULL || modifier->filter == NULL) + { + *out_modified_info = g_object_ref (file_info); + return OSTREE_REPO_COMMIT_FILTER_ALLOW; + } + + path_buf = g_string_new (""); + + if (path->len == 0) + g_string_append_c (path_buf, '/'); + else + { + for (i = 0; i < path->len; i++) + { + const char *elt = path->pdata[i]; + + g_string_append_c (path_buf, '/'); + g_string_append (path_buf, elt); + } + } + + modified_info = g_file_info_dup (file_info); + result = modifier->filter (self, path_buf->str, modified_info, modifier->user_data); + *out_modified_info = modified_info; + + g_string_free (path_buf, TRUE); + return result; +} + +static gboolean +stage_directory_to_mtree_internal (OstreeRepo *self, + GFile *dir, + OstreeMutableTree *mtree, + OstreeRepoCommitModifier *modifier, + GPtrArray *path, + GCancellable *cancellable, + GError **error) { gboolean ret = FALSE; OstreeRepoFile *repo_dir = NULL; @@ -1493,6 +1530,7 @@ ostree_repo_stage_directory_to_mtree (OstreeRepo *self, GVariant *xattrs = NULL; GInputStream *file_input = NULL; gboolean repo_dir_was_empty = FALSE; + OstreeRepoCommitFilterResult filter_result; /* We can only reuse checksums directly if there's no modifier */ if (OSTREE_IS_REPO_FILE (dir) && modifier == NULL) @@ -1507,6 +1545,8 @@ ostree_repo_stage_directory_to_mtree (OstreeRepo *self, repo_dir_was_empty = g_hash_table_size (ostree_mutable_tree_get_files (mtree)) == 0 && g_hash_table_size (ostree_mutable_tree_get_subdirs (mtree)) == 0; + + filter_result = OSTREE_REPO_COMMIT_FILTER_ALLOW; } else { @@ -1516,96 +1556,108 @@ ostree_repo_stage_directory_to_mtree (OstreeRepo *self, if (!child_info) goto out; - modified_info = create_modified_file_info (child_info, modifier); - - if (!(modifier && modifier->skip_xattrs)) + filter_result = apply_commit_filter (self, modifier, path, child_info, &modified_info); + + if (filter_result == OSTREE_REPO_COMMIT_FILTER_ALLOW) { - xattrs = ostree_get_xattrs_for_file (dir, error); - if (!xattrs) - goto out; - } - - if (!stage_directory_meta (self, modified_info, xattrs, &child_file_checksum, - cancellable, error)) - goto out; - - ostree_mutable_tree_set_metadata_checksum (mtree, g_checksum_get_string (child_file_checksum)); - - g_clear_object (&child_info); - g_clear_object (&modified_info); - } - - dir_enum = g_file_enumerate_children ((GFile*)dir, OSTREE_GIO_FAST_QUERYINFO, - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - cancellable, - error); - if (!dir_enum) - goto out; - - while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL) - { - const char *name = g_file_info_get_name (child_info); - - g_clear_object (&modified_info); - modified_info = create_modified_file_info (child_info, modifier); - - g_clear_object (&child); - child = g_file_get_child (dir, name); - - if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY) - { - g_clear_object (&child_mtree); - if (!ostree_mutable_tree_ensure_dir (mtree, name, &child_mtree, error)) - goto out; - - if (!ostree_repo_stage_directory_to_mtree (self, child, child_mtree, - modifier, cancellable, error)) - goto out; - } - else if (repo_dir) - { - if (!ostree_mutable_tree_replace_file (mtree, name, - ostree_repo_file_get_checksum ((OstreeRepoFile*) child), - error)) - goto out; - } - else - { - ot_clear_checksum (&child_file_checksum); - ot_clear_gvariant (&xattrs); - g_clear_object (&file_input); - - if (g_file_info_get_file_type (modified_info) == G_FILE_TYPE_REGULAR) - { - file_input = (GInputStream*)g_file_read (child, cancellable, error); - if (!file_input) - goto out; - } - if (!(modifier && modifier->skip_xattrs)) { - xattrs = ostree_get_xattrs_for_file (child, error); + xattrs = ostree_get_xattrs_for_file (dir, error); if (!xattrs) goto out; } - - if (!stage_object_impl (self, OSTREE_OBJECT_TYPE_RAW_FILE, - modified_info, xattrs, file_input, NULL, - &child_file_checksum, cancellable, error)) - goto out; - - if (!ostree_mutable_tree_replace_file (mtree, name, - g_checksum_get_string (child_file_checksum), - error)) + + if (!stage_directory_meta (self, modified_info, xattrs, &child_file_checksum, + cancellable, error)) goto out; + + ostree_mutable_tree_set_metadata_checksum (mtree, g_checksum_get_string (child_file_checksum)); + + g_clear_object (&child_info); + g_clear_object (&modified_info); } - - g_clear_object (&child_info); } - if (temp_error != NULL) + + if (filter_result == OSTREE_REPO_COMMIT_FILTER_ALLOW) { - g_propagate_error (error, temp_error); - goto out; + dir_enum = g_file_enumerate_children ((GFile*)dir, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, + error); + if (!dir_enum) + goto out; + + while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL) + { + const char *name = g_file_info_get_name (child_info); + + g_clear_object (&modified_info); + g_ptr_array_add (path, (char*)name); + filter_result = apply_commit_filter (self, modifier, path, child_info, &modified_info); + + if (filter_result == OSTREE_REPO_COMMIT_FILTER_ALLOW) + { + g_clear_object (&child); + child = g_file_get_child (dir, name); + + if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY) + { + g_clear_object (&child_mtree); + if (!ostree_mutable_tree_ensure_dir (mtree, name, &child_mtree, error)) + goto out; + + if (!stage_directory_to_mtree_internal (self, child, child_mtree, + modifier, path, cancellable, error)) + goto out; + } + else if (repo_dir) + { + if (!ostree_mutable_tree_replace_file (mtree, name, + ostree_repo_file_get_checksum ((OstreeRepoFile*) child), + error)) + goto out; + } + else + { + ot_clear_checksum (&child_file_checksum); + ot_clear_gvariant (&xattrs); + g_clear_object (&file_input); + + if (g_file_info_get_file_type (modified_info) == G_FILE_TYPE_REGULAR) + { + file_input = (GInputStream*)g_file_read (child, cancellable, error); + if (!file_input) + goto out; + } + + if (!(modifier && modifier->skip_xattrs)) + { + xattrs = ostree_get_xattrs_for_file (child, error); + if (!xattrs) + goto out; + } + + if (!stage_object_impl (self, OSTREE_OBJECT_TYPE_RAW_FILE, + modified_info, xattrs, file_input, NULL, + &child_file_checksum, cancellable, error)) + goto out; + + if (!ostree_mutable_tree_replace_file (mtree, name, + g_checksum_get_string (child_file_checksum), + error)) + goto out; + } + + g_ptr_array_remove_index (path, path->len - 1); + } + + g_clear_object (&child_info); + } + if (temp_error != NULL) + { + g_propagate_error (error, temp_error); + goto out; + } } if (repo_dir && repo_dir_was_empty) @@ -1624,6 +1676,28 @@ ostree_repo_stage_directory_to_mtree (OstreeRepo *self, return ret; } +gboolean +ostree_repo_stage_directory_to_mtree (OstreeRepo *self, + GFile *dir, + OstreeMutableTree *mtree, + OstreeRepoCommitModifier *modifier, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + GPtrArray *path = NULL; + + path = g_ptr_array_new (); + if (!stage_directory_to_mtree_internal (self, dir, mtree, modifier, path, cancellable, error)) + goto out; + + ret = TRUE; + out: + if (path) + g_ptr_array_free (path, TRUE); + return ret; +} + gboolean ostree_repo_stage_mtree (OstreeRepo *self, OstreeMutableTree *mtree, @@ -2020,8 +2094,6 @@ OstreeRepoCommitModifier * ostree_repo_commit_modifier_new (void) { OstreeRepoCommitModifier *modifier = g_new0 (OstreeRepoCommitModifier, 1); - modifier->uid = -1; - modifier->gid = -1; modifier->refcount = 1; diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h index c1a4ebbb..d80e12b4 100644 --- a/src/libostree/ostree-repo.h +++ b/src/libostree/ostree-repo.h @@ -135,15 +135,25 @@ gboolean ostree_repo_load_variant (OstreeRepo *self, GVariant **out_variant, GError **error); +typedef enum { + OSTREE_REPO_COMMIT_FILTER_ALLOW, + OSTREE_REPO_COMMIT_FILTER_SKIP +} OstreeRepoCommitFilterResult; + +typedef OstreeRepoCommitFilterResult (*OstreeRepoCommitFilter) (OstreeRepo *repo, + const char *path, + GFileInfo *file_info, + gpointer user_data); + typedef struct { volatile gint refcount; - gint uid; - gint gid; - guint reserved_flags : 31; guint skip_xattrs : 1; + OstreeRepoCommitFilter filter; + gpointer user_data; + gpointer reserved[3]; } OstreeRepoCommitModifier; diff --git a/src/ostree/ot-builtin-commit.c b/src/ostree/ot-builtin-commit.c index be58f195..d6a327fc 100644 --- a/src/ostree/ot-builtin-commit.c +++ b/src/ostree/ot-builtin-commit.c @@ -36,6 +36,7 @@ static char *body; static char *parent; static char *branch; static char **metadata_strings; +static char *statoverride_file; static gboolean skip_if_unchanged; static gboolean tar_autocreate_parents; static gboolean no_xattrs; @@ -57,9 +58,93 @@ static GOptionEntry options[] = { { "no-xattrs", 0, 0, G_OPTION_ARG_NONE, &no_xattrs, "Do not import extended attributes", NULL }, { "tar-autocreate-parents", 0, 0, G_OPTION_ARG_NONE, &tar_autocreate_parents, "When loading tar archives, automatically create parent directories as needed", NULL }, { "skip-if-unchanged", 0, 0, G_OPTION_ARG_NONE, &skip_if_unchanged, "If the contents are unchanged from previous commit, do nothing", NULL }, + { "statoverride", 0, 0, G_OPTION_ARG_FILENAME, &statoverride_file, "File containing list of modifications to make to permissions", "path" }, { NULL } }; +static gboolean +parse_statoverride_file (GHashTable **out_mode_add, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + GFile *path = NULL; + char *contents = NULL; + gsize len; + GHashTable *ret_hash = NULL; + char **lines = NULL; + char **iter = NULL; + + path = ot_gfile_new_for_path (statoverride_file); + + if (!g_file_load_contents (path, cancellable, &contents, &len, NULL, + error)) + goto out; + + ret_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + lines = g_strsplit (contents, "\n", -1); + + for (iter = lines; iter && *iter; iter++) + { + const char *line = *iter; + + if (*line == '+') + { + const char *spc; + guint mode_add; + + spc = strchr (line + 1, ' '); + if (!spc) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Malformed statoverride file"); + goto out; + } + + mode_add = (guint32)(gint32)g_ascii_strtod (line + 1, NULL); + g_hash_table_insert (ret_hash, + g_strdup (spc + 1), + GUINT_TO_POINTER((gint32)mode_add)); + } + } + + ret = TRUE; + ot_transfer_out_value (out_mode_add, &ret_hash); + out: + if (ret_hash) + g_hash_table_unref (ret_hash); + g_free (contents); + g_strfreev (lines); + g_clear_object (&path); + return ret; +} + +static OstreeRepoCommitFilterResult +commit_filter (OstreeRepo *self, + const char *path, + GFileInfo *file_info, + gpointer user_data) +{ + GHashTable *mode_adds = user_data; + gpointer value; + + if (owner_uid >= 0) + g_file_info_set_attribute_uint32 (file_info, "unix::uid", owner_uid); + if (owner_gid >= 0) + g_file_info_set_attribute_uint32 (file_info, "unix::gid", owner_gid); + + if (mode_adds && g_hash_table_lookup_extended (mode_adds, path, NULL, &value)) + { + guint current_mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode"); + guint mode_add = GPOINTER_TO_UINT (value); + g_file_info_set_attribute_uint32 (file_info, "unix::mode", + current_mode | mode_add); + g_hash_table_remove (mode_adds, path); + } + + return OSTREE_REPO_COMMIT_FILTER_ALLOW; +} + gboolean ostree_builtin_commit (int argc, char **argv, GFile *repo_path, GError **error) { @@ -82,6 +167,7 @@ ostree_builtin_commit (int argc, char **argv, GFile *repo_path, GError **error) gboolean metadata_builder_initialized = FALSE; gboolean skip_commit = FALSE; gboolean in_transaction = FALSE; + GHashTable *mode_adds = NULL; context = g_option_context_new ("[ARG] - Commit a new revision"); g_option_context_add_main_entries (context, options, NULL); @@ -143,6 +229,12 @@ ostree_builtin_commit (int argc, char **argv, GFile *repo_path, GError **error) metadata_builder_initialized = FALSE; } + if (statoverride_file) + { + if (!parse_statoverride_file (&mode_adds, cancellable, error)) + goto out; + } + repo = ostree_repo_new (repo_path); if (!ostree_repo_check (repo, error)) goto out; @@ -161,11 +253,13 @@ ostree_builtin_commit (int argc, char **argv, GFile *repo_path, GError **error) goto out; } - if (owner_uid >= 0 || owner_gid >= 0) + if (owner_uid >= 0 || owner_gid >= 0 || statoverride_file != NULL + || no_xattrs) { modifier = ostree_repo_commit_modifier_new (); - modifier->uid = owner_uid; - modifier->gid = owner_gid; + modifier->skip_xattrs = no_xattrs; + modifier->filter = commit_filter; + modifier->user_data = mode_adds; } if (!ostree_repo_resolve_rev (repo, branch, TRUE, &parent, error)) @@ -249,6 +343,22 @@ ostree_builtin_commit (int argc, char **argv, GFile *repo_path, GError **error) } } } + + if (mode_adds && g_hash_table_size (mode_adds) > 0) + { + GHashTableIter hash_iter; + gpointer key, value; + + g_hash_table_iter_init (&hash_iter, mode_adds); + + while (g_hash_table_iter_next (&hash_iter, &key, &value)) + { + g_printerr ("Unmatched statoverride path: %s\n", (char*)key); + } + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Unmatched statoverride paths"); + goto out; + } if (!ostree_repo_stage_mtree (repo, mtree, &contents_checksum, cancellable, error)) goto out; @@ -316,6 +426,8 @@ ostree_builtin_commit (int argc, char **argv, GFile *repo_path, GError **error) g_free (contents_checksum); g_free (parent); ot_clear_gvariant(&parent_commit); + if (mode_adds) + g_hash_table_unref (mode_adds); g_free (tree_type); if (metadata_mappedf) g_mapped_file_unref (metadata_mappedf); diff --git a/tests/t0000-basic.sh b/tests/t0000-basic.sh index 1514f344..2a00b91a 100755 --- a/tests/t0000-basic.sh +++ b/tests/t0000-basic.sh @@ -19,7 +19,7 @@ set -e -echo "1..23" +echo "1..24" . libtest.sh @@ -173,3 +173,11 @@ echo "ok metadata commit with strings" cd ${test_tmpdir}/checkout-test2-4 $OSTREE commit -b test2 -s "no xattrs" --no-xattrs echo "ok commit with no xattrs" + +cd ${test_tmpdir} +cat > test-statoverride.txt <