From f8f68bd091088495b4e56720a8f4e0b8a86bf341 Mon Sep 17 00:00:00 2001 From: Martin Schwan Date: Thu, 6 Aug 2026 14:05:44 +0200 Subject: [PATCH 01/13] WIP Signed-off-by: Martin Schwan --- src/pu-emmc.c | 6 ++++++ src/pu-utils.c | 14 +++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/pu-emmc.c b/src/pu-emmc.c index 1100c17..b053cd7 100644 --- a/src/pu-emmc.c +++ b/src/pu-emmc.c @@ -24,6 +24,8 @@ typedef struct _PuEmmcInput { gchar *filename; gchar *md5sum; gchar *sha256sum; + GList *exclude; + gchar *only; /* Internal members */ gsize _size; @@ -601,6 +603,8 @@ pu_emmc_class_finalize(GObject *object) g_free(in->filename); g_free(in->md5sum); g_free(in->sha256sum); + g_list_free(g_steal_pointer(&in->exclude)); + g_free(in->only); g_free(in); } g_list_free(g_steal_pointer(&part->input)); @@ -1058,6 +1062,8 @@ pu_emmc_parse_partitions(PuEmmc *emmc, input->filename = pu_hash_table_lookup_string(iv->data.mapping, "filename", ""); input->md5sum = pu_hash_table_lookup_string(iv->data.mapping, "md5sum", ""); input->sha256sum = pu_hash_table_lookup_string(iv->data.mapping, "sha256sum", ""); + input->exclude = pu_hash_table_lookup_list(iv->data.mapping, "exclude", NULL); + input->only = pu_hash_table_lookup_string(iv->data.mapping, "only", ""); part->input = g_list_prepend(part->input, input); g_debug("Parsed partition input: filename=%s md5sum=%s sha256sum=%s", diff --git a/src/pu-utils.c b/src/pu-utils.c index 863e3a3..80ab24d 100644 --- a/src/pu-utils.c +++ b/src/pu-utils.c @@ -59,9 +59,10 @@ pu_spawn_command_line_sync(const gchar *command_line, gboolean pu_archive_extract(const gchar *filename, const gchar *dest, + GList *exclude, GError **error) { - g_autofree gchar *cmd = NULL; + g_autoptr(GString) cmd = NULL; g_return_val_if_fail(filename != NULL, FALSE); g_return_val_if_fail(dest != NULL, FALSE); @@ -69,9 +70,16 @@ pu_archive_extract(const gchar *filename, g_debug("Extracting '%s' to '%s'", filename, dest); - cmd = g_strdup_printf("tar -xf %s -C %s", filename, dest); + cmd = g_string_new("tar "); - if (!pu_spawn_command_line_sync(cmd, error)) { + for (GList *e = exclude; e; e = e->next) { + g_string_append_printf(cmd, "--exclude=%s ", e->data); + } + + //cmd = g_strdup_printf("tar %s -xf %s -C %s", extra_args, filename, dest); + g_string_append_printf(cmd, "-xf %s -C %s", filename, dest); + + if (!pu_spawn_command_line_sync(cmd->str, error)) { g_prefix_error(error, "Failed extracting '%s' to '%s': ", filename, dest); return FALSE; } From 3feb22d8c819383cbc2f4df7055c0e47cb812a13 Mon Sep 17 00:00:00 2001 From: Martin Schwan Date: Thu, 6 Aug 2026 15:56:42 +0200 Subject: [PATCH 02/13] WIP Signed-off-by: Martin Schwan --- doc/layout-config-reference.rst | 22 ++++++++++++++++++++++ src/pu-emmc.c | 15 +++++++++++++-- src/pu-utils.c | 13 +++++++++---- src/pu-utils.h | 2 ++ 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/doc/layout-config-reference.rst b/doc/layout-config-reference.rst index 6be91eb..83c9692 100644 --- a/doc/layout-config-reference.rst +++ b/doc/layout-config-reference.rst @@ -348,6 +348,28 @@ at least a ``filename``. For verifying the checksum of the given input file by checked against the provided file before writing to the target partition or volume. +``exclude`` (sequence) + Paths to exclude for this partition. Paths to be excluded should be + provided as a sequence of strings. This only works on partitions with a valid + filesystem. + + If the input is a ``.tar`` archive, the specified paths are excluded from + extraction using tar's ``--exclude`` option: + https://manpages.debian.org/unstable/tar/tar.1.en.html#exclude + + For other input file types, the specified paths are deleted on the partition + after writing the input files. + +``only`` (sequence) + A list of paths to only extract/keep on the corresponding partition. This + only works on partitions with a valid filesystem. + + If the input is a ``.tar`` archive, only the specified members are extracted: + https://manpages.debian.org/unstable/tar/tar.1.en.html + + For other input file types, any other paths are deleted on the partition + after writing the input files, except the ones specified. + .. _supported-file-types: Supported File Types diff --git a/src/pu-emmc.c b/src/pu-emmc.c index b053cd7..70d33df 100644 --- a/src/pu-emmc.c +++ b/src/pu-emmc.c @@ -377,7 +377,7 @@ pu_emmc_write_data(PuFlash *flash, if (g_regex_match_simple(".tar", path, G_REGEX_CASELESS, 0)) { if (!pu_mount(part_path, part_mount, NULL, NULL, error)) return FALSE; - if (!pu_archive_extract(path, part_mount, error)) + if (!pu_archive_extract(path, part_mount, input->exclude, input->only, error)) return FALSE; if (!pu_umount(part_mount, error)) return FALSE; @@ -1062,7 +1062,18 @@ pu_emmc_parse_partitions(PuEmmc *emmc, input->filename = pu_hash_table_lookup_string(iv->data.mapping, "filename", ""); input->md5sum = pu_hash_table_lookup_string(iv->data.mapping, "md5sum", ""); input->sha256sum = pu_hash_table_lookup_string(iv->data.mapping, "sha256sum", ""); - input->exclude = pu_hash_table_lookup_list(iv->data.mapping, "exclude", NULL); + GList *exclude_list = pu_hash_table_lookup_list(iv->data.mapping, "exclude", NULL); + if (exclude_list) { + for (GList *e = exclude_list; e; e = e->next) { + PuConfigValue *ev = e->data; + if (ev->type != PU_CONFIG_VALUE_TYPE_STRING) { + g_set_error(error, PU_ERROR, PU_ERROR_EMMC_PARSE, + "'exclude' does not contain a sequence of strings"); + return FALSE; + } + input->exclude = g_list_prepend(input->exclude, ev->data.string); + } + } input->only = pu_hash_table_lookup_string(iv->data.mapping, "only", ""); part->input = g_list_prepend(part->input, input); diff --git a/src/pu-utils.c b/src/pu-utils.c index 80ab24d..7306ad9 100644 --- a/src/pu-utils.c +++ b/src/pu-utils.c @@ -60,6 +60,7 @@ gboolean pu_archive_extract(const gchar *filename, const gchar *dest, GList *exclude, + GList *only, GError **error) { g_autoptr(GString) cmd = NULL; @@ -70,14 +71,18 @@ pu_archive_extract(const gchar *filename, g_debug("Extracting '%s' to '%s'", filename, dest); - cmd = g_string_new("tar "); + cmd = g_string_new("tar"); + /* TODO: trailing slashes may cause problems here? */ for (GList *e = exclude; e; e = e->next) { - g_string_append_printf(cmd, "--exclude=%s ", e->data); + g_string_append_printf(cmd, " --exclude=%s", e->data); } - //cmd = g_strdup_printf("tar %s -xf %s -C %s", extra_args, filename, dest); - g_string_append_printf(cmd, "-xf %s -C %s", filename, dest); + g_string_append_printf(cmd, " -C %s -xf %s", dest, filename); + + for (GList *o = only; o; o = o->next) { + g_string_append_printf(cmd, " %s", o->data); + } if (!pu_spawn_command_line_sync(cmd->str, error)) { g_prefix_error(error, "Failed extracting '%s' to '%s': ", filename, dest); diff --git a/src/pu-utils.h b/src/pu-utils.h index 8b488a9..c980b30 100644 --- a/src/pu-utils.h +++ b/src/pu-utils.h @@ -13,6 +13,8 @@ gboolean pu_spawn_command_line_sync(const gchar *command_line, GError **error); gboolean pu_archive_extract(const gchar *filename, const gchar *dest, + GList *exclude, + GList *only, GError **error); gboolean pu_make_filesystem(const gchar *part, const gchar *type, From 163f77b9a1e5529cd04cf24e750f746214debe9a Mon Sep 17 00:00:00 2001 From: Martin Schwan Date: Fri, 7 Aug 2026 16:17:07 +0200 Subject: [PATCH 03/13] WIP Signed-off-by: Martin Schwan --- src/pu-emmc.c | 17 ++++++++++++++--- src/pu-utils.c | 6 ++++-- tests/utils.c | 2 +- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/pu-emmc.c b/src/pu-emmc.c index 70d33df..bf52cff 100644 --- a/src/pu-emmc.c +++ b/src/pu-emmc.c @@ -25,7 +25,7 @@ typedef struct _PuEmmcInput { gchar *md5sum; gchar *sha256sum; GList *exclude; - gchar *only; + GList *only; /* Internal members */ gsize _size; @@ -604,7 +604,7 @@ pu_emmc_class_finalize(GObject *object) g_free(in->md5sum); g_free(in->sha256sum); g_list_free(g_steal_pointer(&in->exclude)); - g_free(in->only); + g_list_free(g_steal_pointer(&in->only)); g_free(in); } g_list_free(g_steal_pointer(&part->input)); @@ -1074,7 +1074,18 @@ pu_emmc_parse_partitions(PuEmmc *emmc, input->exclude = g_list_prepend(input->exclude, ev->data.string); } } - input->only = pu_hash_table_lookup_string(iv->data.mapping, "only", ""); + GList *only_list = pu_hash_table_lookup_list(iv->data.mapping, "only", NULL); + if (only_list) { + for (GList *o = only_list; o; o = o->next) { + PuConfigValue *ov = o->data; + if (ov->type != PU_CONFIG_VALUE_TYPE_STRING) { + g_set_error(error, PU_ERROR, PU_ERROR_EMMC_PARSE, + "'only' does not contain a sequence of strings"); + return FALSE; + } + input->only = g_list_prepend(input->only, ov->data.string); + } + } part->input = g_list_prepend(part->input, input); g_debug("Parsed partition input: filename=%s md5sum=%s sha256sum=%s", diff --git a/src/pu-utils.c b/src/pu-utils.c index 7306ad9..5035b71 100644 --- a/src/pu-utils.c +++ b/src/pu-utils.c @@ -75,13 +75,15 @@ pu_archive_extract(const gchar *filename, /* TODO: trailing slashes may cause problems here? */ for (GList *e = exclude; e; e = e->next) { - g_string_append_printf(cmd, " --exclude=%s", e->data); + gchar *es = e->data; + g_string_append_printf(cmd, " --exclude=%s", es); } g_string_append_printf(cmd, " -C %s -xf %s", dest, filename); for (GList *o = only; o; o = o->next) { - g_string_append_printf(cmd, " %s", o->data); + gchar *os = o->data; + g_string_append_printf(cmd, " %s", os); } if (!pu_spawn_command_line_sync(cmd->str, error)) { diff --git a/tests/utils.c b/tests/utils.c index 3e4431b..4c37c62 100644 --- a/tests/utils.c +++ b/tests/utils.c @@ -24,7 +24,7 @@ test_archive_extract(void) out_file = g_build_filename(dest, "lorem.txt", NULL); - g_assert_true(pu_archive_extract(source, dest, &error)); + g_assert_true(pu_archive_extract(source, dest, NULL, NULL, &error)); g_assert_no_error(error); g_assert_true(g_file_test(out_file, G_FILE_TEST_IS_REGULAR)); From 692fa21c7a50115602d245d270424fc2e24ccdb0 Mon Sep 17 00:00:00 2001 From: Martin Schwan Date: Mon, 10 Aug 2026 11:39:46 +0200 Subject: [PATCH 04/13] WIP Signed-off-by: Martin Schwan --- src/pu-emmc.c | 3 +- src/pu-utils.c | 8 +++- tests/data/foobar.tar | Bin 0 -> 10240 bytes tests/data/lorem.tar | Bin 10240 -> 10240 bytes tests/utils.c | 101 +++++++++++++++++++++++++++++++++++++++--- 5 files changed, 103 insertions(+), 9 deletions(-) create mode 100644 tests/data/foobar.tar diff --git a/src/pu-emmc.c b/src/pu-emmc.c index bf52cff..fa316de 100644 --- a/src/pu-emmc.c +++ b/src/pu-emmc.c @@ -377,7 +377,8 @@ pu_emmc_write_data(PuFlash *flash, if (g_regex_match_simple(".tar", path, G_REGEX_CASELESS, 0)) { if (!pu_mount(part_path, part_mount, NULL, NULL, error)) return FALSE; - if (!pu_archive_extract(path, part_mount, input->exclude, input->only, error)) + if (!pu_archive_extract(path, part_mount, input->exclude, + input->only, error)) return FALSE; if (!pu_umount(part_mount, error)) return FALSE; diff --git a/src/pu-utils.c b/src/pu-utils.c index 5035b71..5b0b310 100644 --- a/src/pu-utils.c +++ b/src/pu-utils.c @@ -76,14 +76,18 @@ pu_archive_extract(const gchar *filename, /* TODO: trailing slashes may cause problems here? */ for (GList *e = exclude; e; e = e->next) { gchar *es = e->data; - g_string_append_printf(cmd, " --exclude=%s", es); + g_string_append_printf(cmd, " --exclude %s", es); } g_string_append_printf(cmd, " -C %s -xf %s", dest, filename); for (GList *o = only; o; o = o->next) { gchar *os = o->data; - g_string_append_printf(cmd, " %s", os); + if (g_regex_match_simple("[!^*?\\[\\]]", os, 0, 0)) { + g_string_append_printf(cmd, " --wildcards %s", os); + } else { + g_string_append_printf(cmd, " --no-wildcards %s", os); + } } if (!pu_spawn_command_line_sync(cmd->str, error)) { diff --git a/tests/data/foobar.tar b/tests/data/foobar.tar new file mode 100644 index 0000000000000000000000000000000000000000..61cc877dc1fe3a8ab84728b842cd6794b217b3cd GIT binary patch literal 10240 zcmeH}-3o#*7>0M;RqP6EbAQm?Xk;gaUy75WeDD8V?LQU*`5#J0XI^r@2Uj6EjmY1_MJw6Eiag1%t_k oj46{D8UM3^HE?YfWctO(nUbHAUxY)mnK4+i35sU02C!yX0E~qt2LJ#7 delta 16 WcmZn&Xb4!qvRQ%afh>?vU;_X%8wDZ& diff --git a/tests/utils.c b/tests/utils.c index 4c37c62..631d8bf 100644 --- a/tests/utils.c +++ b/tests/utils.c @@ -16,19 +16,108 @@ test_archive_extract(void) { g_autoptr(GError) error = NULL; g_autofree gchar *dest = NULL; - g_autofree gchar *out_file = NULL; - const gchar *source = "data/lorem.tar"; + g_autofree gchar *lorem_file = NULL; + g_autofree gchar *ipsum_file = NULL; + g_autofree gchar *dolor_file = NULL; + g_autofree gchar *foo_dir = NULL; + g_autofree gchar *foo_file = NULL; + g_autofree gchar *bar_dir = NULL; + g_autofree gchar *bar_file = NULL; + g_autofree gchar *baz_dir = NULL; + g_autofree gchar *baz_file = NULL; + g_autoptr(GList) exclude = NULL; + g_autoptr(GList) list_ba = NULL; + g_autoptr(GList) list_notfound = NULL; + g_autoptr(GList) only = NULL; dest = g_dir_make_tmp("partup-XXXXXX", &error); g_assert_no_error(error); - out_file = g_build_filename(dest, "lorem.txt", NULL); + lorem_file = g_build_filename(dest, "lorem.txt", NULL); + ipsum_file = g_build_filename(dest, "ipsum.txt", NULL); + dolor_file = g_build_filename(dest, "dolor.txt", NULL); - g_assert_true(pu_archive_extract(source, dest, NULL, NULL, &error)); + exclude = g_list_prepend(exclude, "lorem.txt"); + exclude = g_list_prepend(exclude, "ipsum.txt"); + + only = g_list_prepend(only, "ipsum.txt"); + only = g_list_prepend(only, "dolor.txt"); + + list_ba = g_list_prepend(list_ba, "ba*"); + list_notfound = g_list_prepend(list_notfound, "notfound*"); + + foo_dir = g_build_filename(dest, "foo", NULL); + foo_file = g_build_filename(dest, "foo/foo.txt", NULL); + bar_dir = g_build_filename(dest, "bar", NULL); + bar_file = g_build_filename(dest, "bar/bar.cfg", NULL); + baz_dir = g_build_filename(dest, "baz", NULL); + baz_file = g_build_filename(dest, "baz/baz.yaml", NULL); + + /* Extract all */ + g_assert_true(pu_archive_extract("data/lorem.tar", dest, NULL, NULL, &error)); + g_assert_no_error(error); + g_assert_true(g_file_test(lorem_file, G_FILE_TEST_IS_REGULAR)); + g_assert_true(g_file_test(ipsum_file, G_FILE_TEST_IS_REGULAR)); + g_assert_true(g_file_test(dolor_file, G_FILE_TEST_IS_REGULAR)); + g_assert_cmpint(g_remove(lorem_file), ==, 0); + g_assert_cmpint(g_remove(ipsum_file), ==, 0); + g_assert_cmpint(g_remove(dolor_file), ==, 0); + + /* Extract all excluding two */ + g_assert_true(pu_archive_extract("data/lorem.tar", dest, exclude, NULL, &error)); + g_assert_no_error(error); + g_assert_false(g_file_test(lorem_file, G_FILE_TEST_IS_REGULAR)); + g_assert_false(g_file_test(ipsum_file, G_FILE_TEST_IS_REGULAR)); + g_assert_true(g_file_test(dolor_file, G_FILE_TEST_IS_REGULAR)); + g_assert_cmpint(g_remove(dolor_file), ==, 0); + + /* Extract only two */ + g_assert_true(pu_archive_extract("data/lorem.tar", dest, NULL, only, &error)); + g_assert_no_error(error); + g_assert_false(g_file_test(lorem_file, G_FILE_TEST_IS_REGULAR)); + g_assert_true(g_file_test(ipsum_file, G_FILE_TEST_IS_REGULAR)); + g_assert_true(g_file_test(dolor_file, G_FILE_TEST_IS_REGULAR)); + g_assert_cmpint(g_remove(ipsum_file), ==, 0); + g_assert_cmpint(g_remove(dolor_file), ==, 0); + + /* Extract only two excluding two */ + g_assert_true(pu_archive_extract("data/lorem.tar", dest, exclude, only, &error)); + g_assert_no_error(error); + g_assert_false(g_file_test(lorem_file, G_FILE_TEST_IS_REGULAR)); + g_assert_false(g_file_test(ipsum_file, G_FILE_TEST_IS_REGULAR)); + g_assert_true(g_file_test(dolor_file, G_FILE_TEST_IS_REGULAR)); + g_assert_cmpint(g_remove(dolor_file), ==, 0); + + /* Extract all excluding "ba*" */ + g_assert_true(pu_archive_extract("data/foobar.tar", dest, list_ba, NULL, &error)); + g_assert_no_error(error); + g_assert_true(g_file_test(foo_dir, G_FILE_TEST_IS_DIR)); + g_assert_true(g_file_test(foo_file, G_FILE_TEST_IS_REGULAR)); + g_assert_false(g_file_test(bar_dir, G_FILE_TEST_IS_DIR)); + g_assert_false(g_file_test(bar_file, G_FILE_TEST_IS_REGULAR)); + g_assert_false(g_file_test(baz_dir, G_FILE_TEST_IS_DIR)); + g_assert_false(g_file_test(baz_file, G_FILE_TEST_IS_REGULAR)); + g_assert_cmpint(g_remove(foo_file), ==, 0); + g_assert_cmpint(g_rmdir(foo_dir), ==, 0); + + /* Extract only "ba*" (includes wildcards) */ + g_assert_true(pu_archive_extract("data/foobar.tar", dest, NULL, list_ba, &error)); g_assert_no_error(error); - g_assert_true(g_file_test(out_file, G_FILE_TEST_IS_REGULAR)); + g_assert_false(g_file_test(foo_dir, G_FILE_TEST_IS_DIR)); + g_assert_false(g_file_test(foo_file, G_FILE_TEST_IS_REGULAR)); + g_assert_true(g_file_test(bar_dir, G_FILE_TEST_IS_DIR)); + g_assert_true(g_file_test(bar_file, G_FILE_TEST_IS_REGULAR)); + g_assert_true(g_file_test(baz_dir, G_FILE_TEST_IS_DIR)); + g_assert_true(g_file_test(baz_file, G_FILE_TEST_IS_REGULAR)); + g_assert_cmpint(g_remove(bar_file), ==, 0); + g_assert_cmpint(g_rmdir(bar_dir), ==, 0); + g_assert_cmpint(g_remove(baz_file), ==, 0); + g_assert_cmpint(g_rmdir(baz_dir), ==, 0); + + /* Member not found in archive */ + g_assert_false(pu_archive_extract("data/foobar.tar", dest, NULL, list_notfound, &error)); + g_assert_error(error, G_SPAWN_EXIT_ERROR, 2); - g_assert_cmpint(g_remove(out_file), ==, 0); g_assert_cmpint(g_rmdir(dest), ==, 0); } From f5b61f64ff576c0638fef9c6be456996ec1a7828 Mon Sep 17 00:00:00 2001 From: Martin Schwan Date: Mon, 10 Aug 2026 16:30:21 +0200 Subject: [PATCH 05/13] WIP Signed-off-by: Martin Schwan --- src/pu-emmc.c | 11 +++++- src/pu-utils.c | 75 ++++++++++++++++++++++++++++++++++++++ src/pu-utils.h | 6 +++ tests/data/dir-struct.tar | Bin 0 -> 10240 bytes tests/utils.c | 45 +++++++++++++++++++++++ 5 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 tests/data/dir-struct.tar diff --git a/src/pu-emmc.c b/src/pu-emmc.c index fa316de..8fd1180 100644 --- a/src/pu-emmc.c +++ b/src/pu-emmc.c @@ -388,8 +388,17 @@ pu_emmc_write_data(PuFlash *flash, return FALSE; if (!pu_resize_filesystem(part_path, error)) return FALSE; - if (!pu_set_ext_label(part_path, part->label, error)) + if (part->label && !pu_set_ext_label(part_path, part->label, error)) return FALSE; + if (input->exclude || input->only) { + if (!pu_mount(part_path, part_mount, NULL, NULL, error)) + return FALSE; + if (!pu_remove_recursive_intersect(part_mount, input->exclude, + input->only, error)) + return FALSE; + if (!pu_umount(part_mount, error)) + return FALSE; + } } else if (!part->filesystem) { if (!pu_write_raw(path, part_path, self->device, 0, 0, 0, error)) return FALSE; diff --git a/src/pu-utils.c b/src/pu-utils.c index 5b0b310..26df682 100644 --- a/src/pu-utils.c +++ b/src/pu-utils.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -603,3 +604,77 @@ pu_str_pre_remove(gchar *string, return string; } + +GList * +pu_list_intersect(GList *list_a, + GList *list_b) +{ + GList *intersect = NULL; + + if (!list_a || !list_b) { + return NULL; + } + + for (GList *a = list_a; a; a = a->next) { + for (GList *b = list_b; b; b = b->next) { + gchar *as = a->data; + gchar *bs = b->data; + + /* TODO: faster way to get intersect list? Consider using GHashTable */ + if (g_strcmp0(as, bs) == 0) { + intersect = g_list_prepend(intersect, bs); + g_debug("Prending to intersect list: %s", bs); + } + } + } + + return intersect; +} + +gboolean +pu_remove_recursive_intersect(const gchar *path, + GList *exclude, + GList *only, + GError **error) +{ + GHashTable *keep = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + glob_t gl; + gboolean first = TRUE; + + g_return_val_if_fail(g_strcmp0(path, "") > 0, FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + + /* TODO: support wildcard paths, like "*foo.txt", which should match any + * directory containing foo.txt. */ + for (GList *o = only; o; o = o->next) { + const gchar *os = o->data; + gint flags = GLOB_NOSORT | GLOB_BRACE | (first ? 0 : GLOB_APPEND); + gint ret = glob(os, flags, NULL, &gl); + + if (ret == 0) { + first = FALSE; + } else if (ret == GLOB_NOMATCH) { + continue; + } else { + g_set_error(error, PU_ERROR, PU_ERROR_FAILED, + "glob() failed on '%s': %d", os, ret); + return FALSE; + } + } + + if (!first) { + for (gsize_t i = 0; i < gl.gl_pathc; i++) { + gchar *canon = g_canonicalize_filename(gl.gl_pathv[i], NULL); + g_hash_table_replace(keep, canon, GINT_TO_POINTER(1)); + } + globfree(&gl); + } + + /* Create a list of directories and files that match "exclude" */ + + /* Create a list of directories and files that match "only" */ + + /* Create intersection of real "exclude" and "only" file/dir list above */ + + return TRUE; +} diff --git a/src/pu-utils.h b/src/pu-utils.h index c980b30..3c7ec3b 100644 --- a/src/pu-utils.h +++ b/src/pu-utils.h @@ -67,5 +67,11 @@ gchar * pu_device_get_partition_pattern(const gchar *device, GError **error); gchar * pu_str_pre_remove(gchar *string, guint n); +GList * pu_list_intersect(GList *list_a, + GList *list_b); +gboolean pu_remove_recursive_intersect(const gchar *path, + GList *exclude, + GList *only, + GError **error); #endif /* PARTUP_UTILS_H */ diff --git a/tests/data/dir-struct.tar b/tests/data/dir-struct.tar new file mode 100644 index 0000000000000000000000000000000000000000..813319e702110caf57ee020c8d4901505172d515 GIT binary patch literal 10240 zcmeI0>u$m@42AP3eS$96_IbDjHffNl4I1?67iUx&2(lI>Y(GvUR5cb(bv`?GVs{_@ z^ume`9!6^nqh}b`&p0QHRE;u5>R=bV6)mew(XTo?KlFalRTb-;^QrZIv#0+1k8K<0 z3e#ti_f-B$HN;;T3I5Lr8{)rQbVXx;5_~d$BLe?06!<@r|MD;ti-9@(wV?f9v>f~Y zGw`!9|B3(F4TJ6K(_;6Yb#3C`XnkGx$9FQbvHpMa*Su|5#j}3J9cQTjasMa&;rI9a zIqm-;3PSw{|2r|8aeB=E-g^JG-}d!tv(4x}x|);!D%O9cCHUvYaMK_0_qO-->ZUo% z5(D&i~42od0tnPWrjYf8!2a z?T^X(@TAvEBk`Yl{+E%zk>MDC{_j#oyg>md00p1`6o3Ly017|>C;$bZ02FvwfiI4i B6np>x literal 0 HcmV?d00001 diff --git a/tests/utils.c b/tests/utils.c index 631d8bf..bbd0219 100644 --- a/tests/utils.c +++ b/tests/utils.c @@ -294,6 +294,50 @@ test_is_ext234_image(void) g_assert_false(pu_is_ext234_image("data/lorem.txt")); } +static void +test_list_intersect(void) +{ + GList *a = NULL; + GList *b = NULL; + GList *intersect = NULL; + + a = g_list_prepend(a, "foo"); + a = g_list_prepend(a, "bar"); + a = g_list_prepend(a, "baz"); + + b = g_list_prepend(b, "baz"); + b = g_list_prepend(b, "buzzer"); + + g_assert_null(pu_list_intersect(NULL, NULL)); + g_assert_null(pu_list_intersect(a, NULL)); + g_assert_null(pu_list_intersect(NULL, b)); + + intersect = pu_list_intersect(a, b); + g_assert_nonnull(intersect); + g_assert_nonnull(g_list_find(intersect, "baz")); +} + +static void +test_remove_recursive_intersect(void) +{ + g_autofree gchar *dest = NULL; + + dest = g_dir_make_tmp("partup-XXXXXX", &error); + g_assert_no_error(error); + + toplevel_file = g_build_filename(dest, "toplevel.txt", NULL); + empty_dir = g_build_filename(dest, "empty", NULL); + nested_dir = g_build_filename(dest, "nested", "one", "two", "three", NULL); + nested_file = g_build_filename(dest, "nested", "one", "two", "three", "four.txt", NULL); + foobarbuz_dir = g_build_filename(dest, "foo", "bar", "buz", NULL); + foo_file = g_build_filename(dest, "foo", "test.c", NULL); + foobar_file = g_build_filename(dest, "foo", "bar", "settings.cfg", NULL); + foobarbuz1_file = g_build_filename(dest, "foo", "bar", "buz", "buzzer.yaml", NULL); + foobarbuz2_file = g_build_filename(dest, "foo", "bar", "buz", "dozzer.yaml", NULL); + + +} + int main(int argc, char *argv[]) @@ -324,6 +368,7 @@ main(int argc, g_test_add_func("/utils/str_pre_remove", test_str_pre_remove); g_test_add_func("/utils/device_get_partition_pattern", test_device_get_partition_pattern); g_test_add_func("/utils/is_ext234_image", test_is_ext234_image); + g_test_add_func("/utils/list_intersect", test_list_intersect); return g_test_run(); } From 0a27b3e7ac0d94845b1c8088ad08a874dca78cf7 Mon Sep 17 00:00:00 2001 From: Martin Schwan Date: Tue, 11 Aug 2026 13:53:23 +0200 Subject: [PATCH 06/13] WIP Signed-off-by: Martin Schwan --- src/pu-emmc.c | 8 +-- src/pu-utils.c | 147 +++++++++++++++++++++++++++++++++++++++---------- src/pu-utils.h | 4 +- tests/utils.c | 107 +++++++++++++++++++++++++++++------ 4 files changed, 214 insertions(+), 52 deletions(-) diff --git a/src/pu-emmc.c b/src/pu-emmc.c index 8fd1180..bbf506b 100644 --- a/src/pu-emmc.c +++ b/src/pu-emmc.c @@ -388,7 +388,7 @@ pu_emmc_write_data(PuFlash *flash, return FALSE; if (!pu_resize_filesystem(part_path, error)) return FALSE; - if (part->label && !pu_set_ext_label(part_path, part->label, error)) + if (!pu_set_ext_label(part_path, part->label, error)) return FALSE; if (input->exclude || input->only) { if (!pu_mount(part_path, part_mount, NULL, NULL, error)) @@ -607,14 +607,14 @@ pu_emmc_class_finalize(GObject *object) g_free(part->partuuid); g_free(part->filesystem); g_free(part->mkfs_extra_args); - g_list_free(g_steal_pointer(&part->flags)); + g_list_free_full(g_steal_pointer(&part->flags), g_free); for (GList *i = part->input; i != NULL; i = i->next) { PuEmmcInput *in = i->data; g_free(in->filename); g_free(in->md5sum); g_free(in->sha256sum); - g_list_free(g_steal_pointer(&in->exclude)); - g_list_free(g_steal_pointer(&in->only)); + g_list_free_full(g_steal_pointer(&in->exclude), g_free); + g_list_free_full(g_steal_pointer(&in->only), g_free); g_free(in); } g_list_free(g_steal_pointer(&part->input)); diff --git a/src/pu-utils.c b/src/pu-utils.c index 26df682..e6b8950 100644 --- a/src/pu-utils.c +++ b/src/pu-utils.c @@ -605,26 +605,30 @@ pu_str_pre_remove(gchar *string, return string; } -GList * -pu_list_intersect(GList *list_a, - GList *list_b) +GHashTable * +pu_hash_table_intersect(GHashTable *set_a, + GHashTable *set_b) { - GList *intersect = NULL; + GHashTable *intersect = g_hash_table_new(g_str_hash, g_str_equal); - if (!list_a || !list_b) { - return NULL; + if (!set_a || !set_b) { + return intersect; } - for (GList *a = list_a; a; a = a->next) { - for (GList *b = list_b; b; b = b->next) { - gchar *as = a->data; - gchar *bs = b->data; + if (g_hash_table_size(set_b) < g_hash_table_size(set_a)) { + GHashTable *tmp = set_a; + set_a = set_b; + set_b = tmp; + } - /* TODO: faster way to get intersect list? Consider using GHashTable */ - if (g_strcmp0(as, bs) == 0) { - intersect = g_list_prepend(intersect, bs); - g_debug("Prending to intersect list: %s", bs); - } + GHashTableIter iter; + gpointer key; + + g_hash_table_iter_init(&iter, set_a); + while (g_hash_table_iter_next(&iter, &key, NULL)) { + if (g_hash_table_contains(set_b, key)) { + g_hash_table_add(intersect, key); + g_debug("Adding to intersect set: %s", (gchar *) key); } } @@ -632,49 +636,134 @@ pu_list_intersect(GList *list_a, } gboolean -pu_remove_recursive_intersect(const gchar *path, - GList *exclude, - GList *only, - GError **error) +pu_file_remove_recursive(GFile *file, + GError **error) +{ + g_autoptr(GFileEnumerator) dir_enum = NULL; + + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + + dir_enum = g_file_enumerate_children(file, G_FILE_ATTRIBUTE_STANDARD_NAME, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, NULL); + if (dir_enum) { + g_autoptr(GFileInfo) info = NULL; + while ((info = g_file_enumerator_next_file(dir_enum, NULL, NULL)) != NULL) { + g_autoptr(GFile) child = NULL; + child = g_file_enumerator_get_child(dir_enum, info); + if (!pu_file_remove_recursive(child, error)) { + g_set_error(error, PU_ERROR, PU_ERROR_FAILED, + "Failed recursive file removal"); + return FALSE; + } + } + } + + g_debug("removing %s", g_file_get_path(file)); + return g_file_delete(file, NULL, error); +} + +static GHashTable * +canonicalize_path_list(GList *paths, + GError **error) { - GHashTable *keep = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + g_autoptr(GHashTable) table = NULL; glob_t gl; gboolean first = TRUE; - g_return_val_if_fail(g_strcmp0(path, "") > 0, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + if (!paths) { + return NULL; + } + + table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + /* TODO: support wildcard paths, like "*foo.txt", which should match any * directory containing foo.txt. */ - for (GList *o = only; o; o = o->next) { - const gchar *os = o->data; - gint flags = GLOB_NOSORT | GLOB_BRACE | (first ? 0 : GLOB_APPEND); - gint ret = glob(os, flags, NULL, &gl); + for (GList *p = paths; p; p = p->next) { + const gchar *ps = p->data; + gint flags = GLOB_NOSORT | (first ? 0 : GLOB_APPEND); + gint ret = glob(ps, flags, NULL, &gl); + g_debug("ps: %s", ps); if (ret == 0) { first = FALSE; } else if (ret == GLOB_NOMATCH) { + g_debug("glob did not match anything, continue"); continue; } else { g_set_error(error, PU_ERROR, PU_ERROR_FAILED, - "glob() failed on '%s': %d", os, ret); - return FALSE; + "glob() failed on '%s': %d", ps, ret); + return NULL; } } + g_debug("gl_pathc: %ld", gl.gl_pathc); if (!first) { - for (gsize_t i = 0; i < gl.gl_pathc; i++) { + for (gsize i = 0; i < gl.gl_pathc; i++) { gchar *canon = g_canonicalize_filename(gl.gl_pathv[i], NULL); - g_hash_table_replace(keep, canon, GINT_TO_POINTER(1)); + g_debug("canon: %s", canon); + g_hash_table_replace(table, canon, NULL); } globfree(&gl); } + return g_steal_pointer(&table); +} + +/* TODO: Reconsider function name: Should be remove recursive exclusion set */ +gboolean +pu_remove_recursive_intersect(const gchar *path, + GList *exclude, + GList *only, + GError **error) +{ + g_autoptr(GHashTable) exclude_table = NULL; + g_autoptr(GHashTable) only_table = NULL; + g_autoptr(GHashTable) intersect = NULL; + g_autofree gchar *only_default = NULL; + + g_return_val_if_fail(g_strcmp0(path, "") > 0, FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + /* Create a list of directories and files that match "exclude" */ + g_debug("EXCLUDE"); + if (exclude) { + g_debug("EXCLUDE: true"); + exclude_table = canonicalize_path_list(exclude, error); + if (!exclude_table) { + g_prefix_error(error, "Failed parsing 'exclude' paths: "); + return FALSE; + } + } /* Create a list of directories and files that match "only" */ + g_debug("ONLY"); + if (!only) { + only_default = g_build_filename(path, "*", NULL); + only = g_list_prepend(only, only_default); + } + only_table = canonicalize_path_list(only, error); + if (!only_table) { + g_prefix_error(error, "Failed parsing 'only' paths: "); + return FALSE; + } /* Create intersection of real "exclude" and "only" file/dir list above */ + intersect = pu_hash_table_intersect(exclude_table, only_table); + + GHashTableIter iter; + gpointer key; + + g_hash_table_iter_init(&iter, intersect); + while (g_hash_table_iter_next(&iter, &key, NULL)) { + g_autoptr(GFile) file = NULL; + file = g_file_new_for_path(key); + if (!pu_file_remove_recursive(file, error)) { + return FALSE; + } + } return TRUE; } diff --git a/src/pu-utils.h b/src/pu-utils.h index 3c7ec3b..b493729 100644 --- a/src/pu-utils.h +++ b/src/pu-utils.h @@ -67,8 +67,8 @@ gchar * pu_device_get_partition_pattern(const gchar *device, GError **error); gchar * pu_str_pre_remove(gchar *string, guint n); -GList * pu_list_intersect(GList *list_a, - GList *list_b); +GHashTable * pu_hash_table_intersect(GHashTable *set_a, + GHashTable *set_b); gboolean pu_remove_recursive_intersect(const gchar *path, GList *exclude, GList *only, diff --git a/tests/utils.c b/tests/utils.c index bbd0219..ba4950c 100644 --- a/tests/utils.c +++ b/tests/utils.c @@ -295,47 +295,119 @@ test_is_ext234_image(void) } static void -test_list_intersect(void) +test_hash_table_intersect(void) { - GList *a = NULL; - GList *b = NULL; - GList *intersect = NULL; + g_autoptr(GHashTable) a = NULL; + g_autoptr(GHashTable) b = NULL; + g_autoptr(GHashTable) intersect = NULL; - a = g_list_prepend(a, "foo"); - a = g_list_prepend(a, "bar"); - a = g_list_prepend(a, "baz"); + a = g_hash_table_new(g_str_hash, g_str_equal); + b = g_hash_table_new(g_str_hash, g_str_equal); - b = g_list_prepend(b, "baz"); - b = g_list_prepend(b, "buzzer"); + g_hash_table_add(a, "foo"); + g_hash_table_add(a, "bar"); + g_hash_table_add(a, "baz"); + g_hash_table_add(b, "baz"); + g_hash_table_add(b, "buzzer"); - g_assert_null(pu_list_intersect(NULL, NULL)); - g_assert_null(pu_list_intersect(a, NULL)); - g_assert_null(pu_list_intersect(NULL, b)); + /* NULL inputs return an empty (non-NULL) set. */ + intersect = pu_hash_table_intersect(NULL, NULL); + g_assert_nonnull(intersect); + g_assert_cmpuint(g_hash_table_size(intersect), ==, 0); + g_hash_table_destroy(intersect); + + intersect = pu_hash_table_intersect(a, NULL); + g_assert_nonnull(intersect); + g_assert_cmpuint(g_hash_table_size(intersect), ==, 0); + g_hash_table_destroy(intersect); + + intersect = pu_hash_table_intersect(NULL, b); + g_assert_nonnull(intersect); + g_assert_cmpuint(g_hash_table_size(intersect), ==, 0); + g_hash_table_destroy(intersect); - intersect = pu_list_intersect(a, b); + /* Real intersection: only "baz" is common. */ + intersect = pu_hash_table_intersect(a, b); g_assert_nonnull(intersect); - g_assert_nonnull(g_list_find(intersect, "baz")); + g_assert_cmpuint(g_hash_table_size(intersect), ==, 1); + g_assert_true(g_hash_table_contains(intersect, "baz")); + g_assert_false(g_hash_table_contains(intersect, "foo")); + g_assert_false(g_hash_table_contains(intersect, "buzzer")); } static void test_remove_recursive_intersect(void) { + g_autoptr(GError) error = NULL; + g_autofree GList *exclude = NULL; + g_autofree GList *only = NULL; g_autofree gchar *dest = NULL; + g_autofree gchar *all_dir = NULL; + g_autofree gchar *empty_dir = NULL; + g_autofree gchar *nested_dir = NULL; + g_autofree gchar *nested_file = NULL; + g_autofree gchar *foo_file = NULL; + g_autofree gchar *foobar_file = NULL; + g_autofree gchar *foobarbuz1_file = NULL; + g_autofree gchar *foobarbuz2_file = NULL; dest = g_dir_make_tmp("partup-XXXXXX", &error); g_assert_no_error(error); - toplevel_file = g_build_filename(dest, "toplevel.txt", NULL); + all_dir = g_build_filename(dest, "*", NULL); empty_dir = g_build_filename(dest, "empty", NULL); nested_dir = g_build_filename(dest, "nested", "one", "two", "three", NULL); nested_file = g_build_filename(dest, "nested", "one", "two", "three", "four.txt", NULL); - foobarbuz_dir = g_build_filename(dest, "foo", "bar", "buz", NULL); foo_file = g_build_filename(dest, "foo", "test.c", NULL); foobar_file = g_build_filename(dest, "foo", "bar", "settings.cfg", NULL); foobarbuz1_file = g_build_filename(dest, "foo", "bar", "buz", "buzzer.yaml", NULL); foobarbuz2_file = g_build_filename(dest, "foo", "bar", "buz", "dozzer.yaml", NULL); + /* NULL checks */ + g_assert_true(pu_archive_extract("data/dir-struct.tar", dest, NULL, NULL, &error)); + g_assert_no_error(error); + exclude = g_list_prepend(exclude, all_dir); + g_assert_true(pu_remove_recursive_intersect(dest, NULL, NULL, &error)); + g_assert_false(g_file_test(empty_dir, G_FILE_TEST_IS_DIR)); + g_assert_false(g_file_test(nested_dir, G_FILE_TEST_IS_DIR)); + g_assert_false(g_file_test(nested_file, G_FILE_TEST_IS_REGULAR)); + g_assert_false(g_file_test(foo_file, G_FILE_TEST_IS_REGULAR)); + g_assert_false(g_file_test(foobar_file, G_FILE_TEST_IS_REGULAR)); + g_assert_false(g_file_test(foobarbuz1_file, G_FILE_TEST_IS_REGULAR)); + g_assert_false(g_file_test(foobarbuz2_file, G_FILE_TEST_IS_REGULAR)); + + /* Exclude all */ + g_assert_true(pu_archive_extract("data/dir-struct.tar", dest, NULL, NULL, &error)); + g_assert_no_error(error); + + exclude = g_list_prepend(exclude, all_dir); + g_assert_true(pu_remove_recursive_intersect(dest, exclude, only, &error)); + g_assert_false(g_file_test(empty_dir, G_FILE_TEST_IS_DIR)); + g_assert_false(g_file_test(nested_dir, G_FILE_TEST_IS_DIR)); + g_assert_false(g_file_test(nested_file, G_FILE_TEST_IS_REGULAR)); + g_assert_false(g_file_test(foo_file, G_FILE_TEST_IS_REGULAR)); + g_assert_false(g_file_test(foobar_file, G_FILE_TEST_IS_REGULAR)); + g_assert_false(g_file_test(foobarbuz1_file, G_FILE_TEST_IS_REGULAR)); + g_assert_false(g_file_test(foobarbuz2_file, G_FILE_TEST_IS_REGULAR)); + + /* Preserve all */ + g_assert_true(pu_archive_extract("data/dir-struct.tar", dest, NULL, NULL, &error)); + g_assert_no_error(error); + + g_list_free(exclude); + exclude = NULL; + g_list_free(only); + only = NULL; + only = g_list_prepend(only, all_dir); + g_assert_true(pu_remove_recursive_intersect(dest, exclude, only, &error)); + g_assert_true(g_file_test(empty_dir, G_FILE_TEST_IS_DIR)); + g_assert_true(g_file_test(nested_dir, G_FILE_TEST_IS_DIR)); + g_assert_true(g_file_test(nested_file, G_FILE_TEST_IS_REGULAR)); + g_assert_true(g_file_test(foo_file, G_FILE_TEST_IS_REGULAR)); + g_assert_true(g_file_test(foobar_file, G_FILE_TEST_IS_REGULAR)); + g_assert_true(g_file_test(foobarbuz1_file, G_FILE_TEST_IS_REGULAR)); + g_assert_true(g_file_test(foobarbuz2_file, G_FILE_TEST_IS_REGULAR)); } int @@ -368,7 +440,8 @@ main(int argc, g_test_add_func("/utils/str_pre_remove", test_str_pre_remove); g_test_add_func("/utils/device_get_partition_pattern", test_device_get_partition_pattern); g_test_add_func("/utils/is_ext234_image", test_is_ext234_image); - g_test_add_func("/utils/list_intersect", test_list_intersect); + g_test_add_func("/utils/hash_table_intersect", test_hash_table_intersect); + g_test_add_func("/utils/remove_recursive_intersect", test_remove_recursive_intersect); return g_test_run(); } From 6e57a07d4272e93842c1ff11e87130b169bf1beb Mon Sep 17 00:00:00 2001 From: Martin Schwan Date: Tue, 11 Aug 2026 16:02:16 +0200 Subject: [PATCH 07/13] WIP Signed-off-by: Martin Schwan --- doc/layout-config-reference.rst | 2 + src/pu-utils.c | 115 +++++++++++++++++++++----------- src/pu-utils.h | 4 +- tests/utils.c | 23 ++++--- 4 files changed, 93 insertions(+), 51 deletions(-) diff --git a/doc/layout-config-reference.rst b/doc/layout-config-reference.rst index 83c9692..e9fc81b 100644 --- a/doc/layout-config-reference.rst +++ b/doc/layout-config-reference.rst @@ -360,6 +360,8 @@ at least a ``filename``. For verifying the checksum of the given input file by For other input file types, the specified paths are deleted on the partition after writing the input files. + ``exclude`` takes precedence over ``only``. + ``only`` (sequence) A list of paths to only extract/keep on the corresponding partition. This only works on partitions with a valid filesystem. diff --git a/src/pu-utils.c b/src/pu-utils.c index e6b8950..02cdd10 100644 --- a/src/pu-utils.c +++ b/src/pu-utils.c @@ -605,20 +605,19 @@ pu_str_pre_remove(gchar *string, return string; } +#if 0 GHashTable * -pu_hash_table_intersect(GHashTable *set_a, +pu_hash_table_substract(GHashTable *set_a, GHashTable *set_b) { - GHashTable *intersect = g_hash_table_new(g_str_hash, g_str_equal); + GHashTable *substraction = g_hash_table_new(g_str_hash, g_str_equal); - if (!set_a || !set_b) { - return intersect; + if (!set_a) { + return set_b; } - if (g_hash_table_size(set_b) < g_hash_table_size(set_a)) { - GHashTable *tmp = set_a; - set_a = set_b; - set_b = tmp; + if (!set_b) { + return set_a; } GHashTableIter iter; @@ -626,23 +625,42 @@ pu_hash_table_intersect(GHashTable *set_a, g_hash_table_iter_init(&iter, set_a); while (g_hash_table_iter_next(&iter, &key, NULL)) { - if (g_hash_table_contains(set_b, key)) { - g_hash_table_add(intersect, key); - g_debug("Adding to intersect set: %s", (gchar *) key); + if (!g_hash_table_contains(set_b, key)) { + g_hash_table_add(substraction, key); + g_debug("Adding to substraction set: %s", (gchar *) key); } } - return intersect; + return substraction; } +#endif -gboolean +static gboolean pu_file_remove_recursive(GFile *file, + GHashTable *skip, GError **error) { g_autoptr(GFileEnumerator) dir_enum = NULL; + g_autofree gchar *file_path = NULL; g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + if (skip) { + GHashTableIter iter; + gpointer key; + + file_path = g_file_get_path(file); + + g_hash_table_iter_init(&iter, skip); + while (g_hash_table_iter_next(&iter, &key, NULL)) { + if (g_str_has_prefix(file_path, key)) { + g_debug("Skipping deletion of '%s', because '%s' gets retained", + file_path, (gchar *) key); + return FALSE; + } + } + } + dir_enum = g_file_enumerate_children(file, G_FILE_ATTRIBUTE_STANDARD_NAME, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, NULL); @@ -651,7 +669,7 @@ pu_file_remove_recursive(GFile *file, while ((info = g_file_enumerator_next_file(dir_enum, NULL, NULL)) != NULL) { g_autoptr(GFile) child = NULL; child = g_file_enumerator_get_child(dir_enum, info); - if (!pu_file_remove_recursive(child, error)) { + if (!pu_file_remove_recursive(child, skip, error)) { g_set_error(error, PU_ERROR, PU_ERROR_FAILED, "Failed recursive file removal"); return FALSE; @@ -721,48 +739,65 @@ pu_remove_recursive_intersect(const gchar *path, { g_autoptr(GHashTable) exclude_table = NULL; g_autoptr(GHashTable) only_table = NULL; - g_autoptr(GHashTable) intersect = NULL; - g_autofree gchar *only_default = NULL; + g_autoptr(GHashTable) all_table = NULL; + g_autofree GList *all = NULL; g_return_val_if_fail(g_strcmp0(path, "") > 0, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); - /* Create a list of directories and files that match "exclude" */ - g_debug("EXCLUDE"); if (exclude) { - g_debug("EXCLUDE: true"); + /* Create a list of directories and files that match "exclude" */ + g_debug("EXCLUDE"); exclude_table = canonicalize_path_list(exclude, error); if (!exclude_table) { g_prefix_error(error, "Failed parsing 'exclude' paths: "); return FALSE; } - } - /* Create a list of directories and files that match "only" */ - g_debug("ONLY"); - if (!only) { - only_default = g_build_filename(path, "*", NULL); - only = g_list_prepend(only, only_default); - } - only_table = canonicalize_path_list(only, error); - if (!only_table) { - g_prefix_error(error, "Failed parsing 'only' paths: "); - return FALSE; - } + /* Delete all entries in "exclude" */ + GHashTableIter iter; + gpointer key; - /* Create intersection of real "exclude" and "only" file/dir list above */ - intersect = pu_hash_table_intersect(exclude_table, only_table); + g_hash_table_iter_init(&iter, exclude_table); + while (g_hash_table_iter_next(&iter, &key, NULL)) { + g_autoptr(GFile) file = NULL; + file = g_file_new_for_path(key); + if (!pu_file_remove_recursive(file, NULL, error)) { + return FALSE; + } + } + } - GHashTableIter iter; - gpointer key; + if (only) { + /* Create a list of directories and files that match "only" */ + g_debug("ONLY"); + only_table = canonicalize_path_list(only, error); + if (!only_table) { + g_prefix_error(error, "Failed parsing 'only' paths: "); + return FALSE; + } - g_hash_table_iter_init(&iter, intersect); - while (g_hash_table_iter_next(&iter, &key, NULL)) { - g_autoptr(GFile) file = NULL; - file = g_file_new_for_path(key); - if (!pu_file_remove_recursive(file, error)) { + /* Create a list of directories and files that matches everything */ + g_debug("ALL"); + all = g_list_prepend(all, g_build_filename(path, "*", NULL)); + all_table = canonicalize_path_list(all, error); + if (!all_table) { + g_prefix_error(error, "Failed parsing 'all' paths: "); return FALSE; } + + /* Delete everything, except entries in "only" */ + GHashTableIter iter; + gpointer key; + + g_hash_table_iter_init(&iter, all_table); + while (g_hash_table_iter_next(&iter, &key, NULL)) { + g_autoptr(GFile) file = NULL; + file = g_file_new_for_path(key); + if (!pu_file_remove_recursive(file, only_table, error)) { + return FALSE; + } + } } return TRUE; diff --git a/src/pu-utils.h b/src/pu-utils.h index b493729..b0bb9b2 100644 --- a/src/pu-utils.h +++ b/src/pu-utils.h @@ -67,8 +67,8 @@ gchar * pu_device_get_partition_pattern(const gchar *device, GError **error); gchar * pu_str_pre_remove(gchar *string, guint n); -GHashTable * pu_hash_table_intersect(GHashTable *set_a, - GHashTable *set_b); +/*GHashTable * pu_hash_table_intersect(GHashTable *set_a, + GHashTable *set_b);*/ gboolean pu_remove_recursive_intersect(const gchar *path, GList *exclude, GList *only, diff --git a/tests/utils.c b/tests/utils.c index ba4950c..ec98cd3 100644 --- a/tests/utils.c +++ b/tests/utils.c @@ -294,6 +294,7 @@ test_is_ext234_image(void) g_assert_false(pu_is_ext234_image("data/lorem.txt")); } +#if 0 static void test_hash_table_intersect(void) { @@ -334,6 +335,7 @@ test_hash_table_intersect(void) g_assert_false(g_hash_table_contains(intersect, "foo")); g_assert_false(g_hash_table_contains(intersect, "buzzer")); } +#endif static void test_remove_recursive_intersect(void) @@ -367,21 +369,24 @@ test_remove_recursive_intersect(void) g_assert_true(pu_archive_extract("data/dir-struct.tar", dest, NULL, NULL, &error)); g_assert_no_error(error); - exclude = g_list_prepend(exclude, all_dir); g_assert_true(pu_remove_recursive_intersect(dest, NULL, NULL, &error)); - g_assert_false(g_file_test(empty_dir, G_FILE_TEST_IS_DIR)); - g_assert_false(g_file_test(nested_dir, G_FILE_TEST_IS_DIR)); - g_assert_false(g_file_test(nested_file, G_FILE_TEST_IS_REGULAR)); - g_assert_false(g_file_test(foo_file, G_FILE_TEST_IS_REGULAR)); - g_assert_false(g_file_test(foobar_file, G_FILE_TEST_IS_REGULAR)); - g_assert_false(g_file_test(foobarbuz1_file, G_FILE_TEST_IS_REGULAR)); - g_assert_false(g_file_test(foobarbuz2_file, G_FILE_TEST_IS_REGULAR)); + g_assert_true(g_file_test(empty_dir, G_FILE_TEST_IS_DIR)); + g_assert_true(g_file_test(nested_dir, G_FILE_TEST_IS_DIR)); + g_assert_true(g_file_test(nested_file, G_FILE_TEST_IS_REGULAR)); + g_assert_true(g_file_test(foo_file, G_FILE_TEST_IS_REGULAR)); + g_assert_true(g_file_test(foobar_file, G_FILE_TEST_IS_REGULAR)); + g_assert_true(g_file_test(foobarbuz1_file, G_FILE_TEST_IS_REGULAR)); + g_assert_true(g_file_test(foobarbuz2_file, G_FILE_TEST_IS_REGULAR)); /* Exclude all */ g_assert_true(pu_archive_extract("data/dir-struct.tar", dest, NULL, NULL, &error)); g_assert_no_error(error); + g_list_free(exclude); + exclude = NULL; exclude = g_list_prepend(exclude, all_dir); + g_list_free(only); + only = NULL; g_assert_true(pu_remove_recursive_intersect(dest, exclude, only, &error)); g_assert_false(g_file_test(empty_dir, G_FILE_TEST_IS_DIR)); g_assert_false(g_file_test(nested_dir, G_FILE_TEST_IS_DIR)); @@ -440,7 +445,7 @@ main(int argc, g_test_add_func("/utils/str_pre_remove", test_str_pre_remove); g_test_add_func("/utils/device_get_partition_pattern", test_device_get_partition_pattern); g_test_add_func("/utils/is_ext234_image", test_is_ext234_image); - g_test_add_func("/utils/hash_table_intersect", test_hash_table_intersect); + //g_test_add_func("/utils/hash_table_intersect", test_hash_table_intersect); g_test_add_func("/utils/remove_recursive_intersect", test_remove_recursive_intersect); return g_test_run(); From 303b5c888211d5663f445714024175e7fabedad2 Mon Sep 17 00:00:00 2001 From: Martin Schwan Date: Wed, 12 Aug 2026 14:20:48 +0200 Subject: [PATCH 08/13] WIP Signed-off-by: Martin Schwan --- src/pu-emmc.c | 4 +-- src/pu-utils.c | 44 +++++++++++++++++++++----------- src/pu-utils.h | 12 ++++++--- tests/utils.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 104 insertions(+), 24 deletions(-) diff --git a/src/pu-emmc.c b/src/pu-emmc.c index bbf506b..9d49922 100644 --- a/src/pu-emmc.c +++ b/src/pu-emmc.c @@ -393,8 +393,8 @@ pu_emmc_write_data(PuFlash *flash, if (input->exclude || input->only) { if (!pu_mount(part_path, part_mount, NULL, NULL, error)) return FALSE; - if (!pu_remove_recursive_intersect(part_mount, input->exclude, - input->only, error)) + if (!pu_path_remove_exclude_only(part_mount, input->exclude, + input->only, error)) return FALSE; if (!pu_umount(part_mount, error)) return FALSE; diff --git a/src/pu-utils.c b/src/pu-utils.c index 02cdd10..cb754af 100644 --- a/src/pu-utils.c +++ b/src/pu-utils.c @@ -635,28 +635,35 @@ pu_hash_table_substract(GHashTable *set_a, } #endif -static gboolean +gboolean pu_file_remove_recursive(GFile *file, GHashTable *skip, GError **error) { g_autoptr(GFileEnumerator) dir_enum = NULL; - g_autofree gchar *file_path = NULL; + g_autoptr(GFileInfo) info = NULL; + gboolean skip_delete = FALSE; g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (skip) { + g_autofree gchar *file_path = NULL; GHashTableIter iter; gpointer key; file_path = g_file_get_path(file); - g_hash_table_iter_init(&iter, skip); while (g_hash_table_iter_next(&iter, &key, NULL)) { - if (g_str_has_prefix(file_path, key)) { - g_debug("Skipping deletion of '%s', because '%s' gets retained", - file_path, (gchar *) key); - return FALSE; + if (g_str_equal(key, file_path)) { + /* Skip deletion of 'file_path' (and possible children), because + * it gets retained */ + return TRUE; + } + if (g_str_has_prefix(key, file_path)) { + /* Skip deletion of 'file_path', because prefix 'key' gets + * retained, but still evaluate possible children */ + skip_delete = TRUE; + break; } } } @@ -665,19 +672,25 @@ pu_file_remove_recursive(GFile *file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, NULL); if (dir_enum) { - g_autoptr(GFileInfo) info = NULL; while ((info = g_file_enumerator_next_file(dir_enum, NULL, NULL)) != NULL) { g_autoptr(GFile) child = NULL; child = g_file_enumerator_get_child(dir_enum, info); if (!pu_file_remove_recursive(child, skip, error)) { - g_set_error(error, PU_ERROR, PU_ERROR_FAILED, - "Failed recursive file removal"); + if (error) { + g_prefix_error(error, "Failed recursive file removal"); + } else { + g_set_error(error, PU_ERROR, PU_ERROR_FAILED, + "Failed recursive file removal"); + } return FALSE; } } } - g_debug("removing %s", g_file_get_path(file)); + if (skip_delete) { + return TRUE; + } + return g_file_delete(file, NULL, error); } @@ -732,10 +745,10 @@ canonicalize_path_list(GList *paths, /* TODO: Reconsider function name: Should be remove recursive exclusion set */ gboolean -pu_remove_recursive_intersect(const gchar *path, - GList *exclude, - GList *only, - GError **error) +pu_path_remove_exclude_only(const gchar *path, + GList *exclude, + GList *only, + GError **error) { g_autoptr(GHashTable) exclude_table = NULL; g_autoptr(GHashTable) only_table = NULL; @@ -787,6 +800,7 @@ pu_remove_recursive_intersect(const gchar *path, } /* Delete everything, except entries in "only" */ + g_debug("REMOVE"); GHashTableIter iter; gpointer key; diff --git a/src/pu-utils.h b/src/pu-utils.h index b0bb9b2..c206c23 100644 --- a/src/pu-utils.h +++ b/src/pu-utils.h @@ -7,6 +7,7 @@ #define PARTUP_UTILS_H #include +#include #include gboolean pu_spawn_command_line_sync(const gchar *command_line, @@ -69,9 +70,12 @@ gchar * pu_str_pre_remove(gchar *string, guint n); /*GHashTable * pu_hash_table_intersect(GHashTable *set_a, GHashTable *set_b);*/ -gboolean pu_remove_recursive_intersect(const gchar *path, - GList *exclude, - GList *only, - GError **error); +gboolean pu_file_remove_recursive(GFile *file, + GHashTable *skip, + GError **error); +gboolean pu_path_remove_exclude_only(const gchar *path, + GList *exclude, + GList *only, + GError **error); #endif /* PARTUP_UTILS_H */ diff --git a/tests/utils.c b/tests/utils.c index ec98cd3..3adb7bf 100644 --- a/tests/utils.c +++ b/tests/utils.c @@ -341,6 +341,7 @@ static void test_remove_recursive_intersect(void) { g_autoptr(GError) error = NULL; + g_autoptr(GFile) dest_file = NULL; g_autofree GList *exclude = NULL; g_autofree GList *only = NULL; g_autofree gchar *dest = NULL; @@ -352,6 +353,7 @@ test_remove_recursive_intersect(void) g_autofree gchar *foobar_file = NULL; g_autofree gchar *foobarbuz1_file = NULL; g_autofree gchar *foobarbuz2_file = NULL; + g_autofree gchar *yaml_file = NULL; dest = g_dir_make_tmp("partup-XXXXXX", &error); g_assert_no_error(error); @@ -364,12 +366,13 @@ test_remove_recursive_intersect(void) foobar_file = g_build_filename(dest, "foo", "bar", "settings.cfg", NULL); foobarbuz1_file = g_build_filename(dest, "foo", "bar", "buz", "buzzer.yaml", NULL); foobarbuz2_file = g_build_filename(dest, "foo", "bar", "buz", "dozzer.yaml", NULL); + yaml_file = g_build_filename(dest, "foo", "bar", "buz", "*.yaml", NULL); /* NULL checks */ g_assert_true(pu_archive_extract("data/dir-struct.tar", dest, NULL, NULL, &error)); g_assert_no_error(error); - g_assert_true(pu_remove_recursive_intersect(dest, NULL, NULL, &error)); + g_assert_true(pu_path_remove_exclude_only(dest, NULL, NULL, &error)); g_assert_true(g_file_test(empty_dir, G_FILE_TEST_IS_DIR)); g_assert_true(g_file_test(nested_dir, G_FILE_TEST_IS_DIR)); g_assert_true(g_file_test(nested_file, G_FILE_TEST_IS_REGULAR)); @@ -387,7 +390,7 @@ test_remove_recursive_intersect(void) exclude = g_list_prepend(exclude, all_dir); g_list_free(only); only = NULL; - g_assert_true(pu_remove_recursive_intersect(dest, exclude, only, &error)); + g_assert_true(pu_path_remove_exclude_only(dest, exclude, only, &error)); g_assert_false(g_file_test(empty_dir, G_FILE_TEST_IS_DIR)); g_assert_false(g_file_test(nested_dir, G_FILE_TEST_IS_DIR)); g_assert_false(g_file_test(nested_file, G_FILE_TEST_IS_REGULAR)); @@ -405,7 +408,7 @@ test_remove_recursive_intersect(void) g_list_free(only); only = NULL; only = g_list_prepend(only, all_dir); - g_assert_true(pu_remove_recursive_intersect(dest, exclude, only, &error)); + g_assert_true(pu_path_remove_exclude_only(dest, exclude, only, &error)); g_assert_true(g_file_test(empty_dir, G_FILE_TEST_IS_DIR)); g_assert_true(g_file_test(nested_dir, G_FILE_TEST_IS_DIR)); g_assert_true(g_file_test(nested_file, G_FILE_TEST_IS_REGULAR)); @@ -413,6 +416,65 @@ test_remove_recursive_intersect(void) g_assert_true(g_file_test(foobar_file, G_FILE_TEST_IS_REGULAR)); g_assert_true(g_file_test(foobarbuz1_file, G_FILE_TEST_IS_REGULAR)); g_assert_true(g_file_test(foobarbuz2_file, G_FILE_TEST_IS_REGULAR)); + + /* Preserve only nested file */ + g_assert_true(pu_archive_extract("data/dir-struct.tar", dest, NULL, NULL, &error)); + g_assert_no_error(error); + + g_list_free(exclude); + exclude = NULL; + g_list_free(only); + only = NULL; + only = g_list_prepend(only, foo_file); + only = g_list_prepend(only, foobarbuz1_file); + g_assert_true(pu_path_remove_exclude_only(dest, exclude, only, &error)); + g_assert_false(g_file_test(empty_dir, G_FILE_TEST_IS_DIR)); + g_assert_false(g_file_test(nested_dir, G_FILE_TEST_IS_DIR)); + g_assert_false(g_file_test(nested_file, G_FILE_TEST_IS_REGULAR)); + g_assert_true(g_file_test(foo_file, G_FILE_TEST_IS_REGULAR)); + g_assert_false(g_file_test(foobar_file, G_FILE_TEST_IS_REGULAR)); + g_assert_true(g_file_test(foobarbuz1_file, G_FILE_TEST_IS_REGULAR)); + g_assert_false(g_file_test(foobarbuz2_file, G_FILE_TEST_IS_REGULAR)); + + /* Preserve only *.yaml file */ + g_assert_true(pu_archive_extract("data/dir-struct.tar", dest, NULL, NULL, &error)); + g_assert_no_error(error); + + g_list_free(exclude); + exclude = NULL; + g_list_free(only); + only = NULL; + only = g_list_prepend(only, yaml_file); + g_assert_true(pu_path_remove_exclude_only(dest, exclude, only, &error)); + g_assert_false(g_file_test(empty_dir, G_FILE_TEST_IS_DIR)); + g_assert_false(g_file_test(nested_dir, G_FILE_TEST_IS_DIR)); + g_assert_false(g_file_test(nested_file, G_FILE_TEST_IS_REGULAR)); + g_assert_false(g_file_test(foo_file, G_FILE_TEST_IS_REGULAR)); + g_assert_false(g_file_test(foobar_file, G_FILE_TEST_IS_REGULAR)); + g_assert_true(g_file_test(foobarbuz1_file, G_FILE_TEST_IS_REGULAR)); + g_assert_true(g_file_test(foobarbuz2_file, G_FILE_TEST_IS_REGULAR)); + + /* Exclude *.yaml file */ + g_assert_true(pu_archive_extract("data/dir-struct.tar", dest, NULL, NULL, &error)); + g_assert_no_error(error); + + g_list_free(exclude); + exclude = NULL; + exclude = g_list_prepend(exclude, yaml_file); + g_list_free(only); + only = NULL; + g_assert_true(pu_path_remove_exclude_only(dest, exclude, only, &error)); + g_assert_true(g_file_test(empty_dir, G_FILE_TEST_IS_DIR)); + g_assert_true(g_file_test(nested_dir, G_FILE_TEST_IS_DIR)); + g_assert_true(g_file_test(nested_file, G_FILE_TEST_IS_REGULAR)); + g_assert_true(g_file_test(foo_file, G_FILE_TEST_IS_REGULAR)); + g_assert_true(g_file_test(foobar_file, G_FILE_TEST_IS_REGULAR)); + g_assert_false(g_file_test(foobarbuz1_file, G_FILE_TEST_IS_REGULAR)); + g_assert_false(g_file_test(foobarbuz2_file, G_FILE_TEST_IS_REGULAR)); + + /* Clean up */ + dest_file = g_file_new_for_path(dest); + g_assert_true(pu_file_remove_recursive(dest_file, NULL, &error)); } int From ebe1093ba59838b1348de2eda4ba8ebfa0633e4f Mon Sep 17 00:00:00 2001 From: Martin Schwan Date: Wed, 12 Aug 2026 14:21:38 +0200 Subject: [PATCH 09/13] WIP Signed-off-by: Martin Schwan --- tests/config/system-tests/exclude-only.yaml | 37 +++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tests/config/system-tests/exclude-only.yaml diff --git a/tests/config/system-tests/exclude-only.yaml b/tests/config/system-tests/exclude-only.yaml new file mode 100644 index 0000000..a444262 --- /dev/null +++ b/tests/config/system-tests/exclude-only.yaml @@ -0,0 +1,37 @@ +api-version: 4 +disklabel: gpt + +partitions: + - label: boot + filesystem: fat32 + size: 16MiB + offset: 1MiB + flags: + - boot + input: + - filename: lorem.txt + - label: root1 + filesystem: ext4 + expand: true + input: + - filename: dir-struct.tar + exclude: + - empty + - label: root2 + filesystem: ext4 + expand: true + input: + - filename: dir-struct.tar + only: + - foo + - label: root3 + filesystem: ext4 + expand: true + input: + - filename: dir-struct.tar + exclude: + - empty + - foo/tested.c + only: + - foo + - nested From 2d0c8ad059f679669e8ce63e2948be6c1864e2d7 Mon Sep 17 00:00:00 2001 From: Martin Schwan Date: Wed, 12 Aug 2026 14:25:28 +0200 Subject: [PATCH 10/13] WIP Signed-off-by: Martin Schwan --- src/pu-utils.c | 30 ------------------------------ tests/utils.c | 44 -------------------------------------------- 2 files changed, 74 deletions(-) diff --git a/src/pu-utils.c b/src/pu-utils.c index cb754af..420d209 100644 --- a/src/pu-utils.c +++ b/src/pu-utils.c @@ -605,36 +605,6 @@ pu_str_pre_remove(gchar *string, return string; } -#if 0 -GHashTable * -pu_hash_table_substract(GHashTable *set_a, - GHashTable *set_b) -{ - GHashTable *substraction = g_hash_table_new(g_str_hash, g_str_equal); - - if (!set_a) { - return set_b; - } - - if (!set_b) { - return set_a; - } - - GHashTableIter iter; - gpointer key; - - g_hash_table_iter_init(&iter, set_a); - while (g_hash_table_iter_next(&iter, &key, NULL)) { - if (!g_hash_table_contains(set_b, key)) { - g_hash_table_add(substraction, key); - g_debug("Adding to substraction set: %s", (gchar *) key); - } - } - - return substraction; -} -#endif - gboolean pu_file_remove_recursive(GFile *file, GHashTable *skip, diff --git a/tests/utils.c b/tests/utils.c index 3adb7bf..b648e94 100644 --- a/tests/utils.c +++ b/tests/utils.c @@ -294,49 +294,6 @@ test_is_ext234_image(void) g_assert_false(pu_is_ext234_image("data/lorem.txt")); } -#if 0 -static void -test_hash_table_intersect(void) -{ - g_autoptr(GHashTable) a = NULL; - g_autoptr(GHashTable) b = NULL; - g_autoptr(GHashTable) intersect = NULL; - - a = g_hash_table_new(g_str_hash, g_str_equal); - b = g_hash_table_new(g_str_hash, g_str_equal); - - g_hash_table_add(a, "foo"); - g_hash_table_add(a, "bar"); - g_hash_table_add(a, "baz"); - g_hash_table_add(b, "baz"); - g_hash_table_add(b, "buzzer"); - - /* NULL inputs return an empty (non-NULL) set. */ - intersect = pu_hash_table_intersect(NULL, NULL); - g_assert_nonnull(intersect); - g_assert_cmpuint(g_hash_table_size(intersect), ==, 0); - g_hash_table_destroy(intersect); - - intersect = pu_hash_table_intersect(a, NULL); - g_assert_nonnull(intersect); - g_assert_cmpuint(g_hash_table_size(intersect), ==, 0); - g_hash_table_destroy(intersect); - - intersect = pu_hash_table_intersect(NULL, b); - g_assert_nonnull(intersect); - g_assert_cmpuint(g_hash_table_size(intersect), ==, 0); - g_hash_table_destroy(intersect); - - /* Real intersection: only "baz" is common. */ - intersect = pu_hash_table_intersect(a, b); - g_assert_nonnull(intersect); - g_assert_cmpuint(g_hash_table_size(intersect), ==, 1); - g_assert_true(g_hash_table_contains(intersect, "baz")); - g_assert_false(g_hash_table_contains(intersect, "foo")); - g_assert_false(g_hash_table_contains(intersect, "buzzer")); -} -#endif - static void test_remove_recursive_intersect(void) { @@ -507,7 +464,6 @@ main(int argc, g_test_add_func("/utils/str_pre_remove", test_str_pre_remove); g_test_add_func("/utils/device_get_partition_pattern", test_device_get_partition_pattern); g_test_add_func("/utils/is_ext234_image", test_is_ext234_image); - //g_test_add_func("/utils/hash_table_intersect", test_hash_table_intersect); g_test_add_func("/utils/remove_recursive_intersect", test_remove_recursive_intersect); return g_test_run(); From dc58551f13c96796323137e27d68c0e21827b856 Mon Sep 17 00:00:00 2001 From: Martin Schwan Date: Wed, 12 Aug 2026 14:44:57 +0200 Subject: [PATCH 11/13] WIP Signed-off-by: Martin Schwan --- src/pu-emmc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pu-emmc.c b/src/pu-emmc.c index 9d49922..1080d70 100644 --- a/src/pu-emmc.c +++ b/src/pu-emmc.c @@ -607,14 +607,14 @@ pu_emmc_class_finalize(GObject *object) g_free(part->partuuid); g_free(part->filesystem); g_free(part->mkfs_extra_args); - g_list_free_full(g_steal_pointer(&part->flags), g_free); + g_list_free(g_steal_pointer(&part->flags)); for (GList *i = part->input; i != NULL; i = i->next) { PuEmmcInput *in = i->data; g_free(in->filename); g_free(in->md5sum); g_free(in->sha256sum); - g_list_free_full(g_steal_pointer(&in->exclude), g_free); - g_list_free_full(g_steal_pointer(&in->only), g_free); + g_list_free(g_steal_pointer(&in->exclude)); + g_list_free(g_steal_pointer(&in->only)); g_free(in); } g_list_free(g_steal_pointer(&part->input)); From 31a24464812b39af65d6e0c2b7de47769de1e932 Mon Sep 17 00:00:00 2001 From: Martin Schwan Date: Wed, 12 Aug 2026 14:48:02 +0200 Subject: [PATCH 12/13] WIP Signed-off-by: Martin Schwan --- .github/workflows/system-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index 1e94a84..3450ec0 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -33,7 +33,7 @@ jobs: echo "Create package" cp $f tests/data/layout.yaml G_DEBUG=fatal-warnings partup -d -C tests/data package pkg.partup \ - layout.yaml random.bin lorem.txt lorem.tar root.ext4 + layout.yaml random.bin lorem.txt lorem.tar dir-struct.tar root.ext4 echo "Show package content" sudo G_DEBUG=fatal-warnings partup -s show pkg.partup echo "Install package to loop device" From e5c2f95ffc28e68439d3780bd38ecb9436282fa2 Mon Sep 17 00:00:00 2001 From: Martin Schwan Date: Wed, 12 Aug 2026 15:15:51 +0200 Subject: [PATCH 13/13] WIP Signed-off-by: Martin Schwan --- src/pu-emmc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pu-emmc.c b/src/pu-emmc.c index 1080d70..376b700 100644 --- a/src/pu-emmc.c +++ b/src/pu-emmc.c @@ -407,6 +407,11 @@ pu_emmc_write_data(PuFlash *flash, return FALSE; if (!pu_file_copy(path, part_mount, error)) return FALSE; + if (input->exclude || input->only) { + if (!pu_path_remove_exclude_only(part_mount, input->exclude, + input->only, error)) + return FALSE; + } if (!pu_umount(part_mount, error)) return FALSE; }