From 9d033af78217085af3f4242ebfed9da2a75f4423 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Thu, 5 Feb 2026 11:21:44 +0100 Subject: [PATCH 1/4] Include and exclude paths should apply after strip --- pkgutil.c | 33 +++++++++++++++-------------- tests/BUILD.bazel | 54 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 17 deletions(-) diff --git a/pkgutil.c b/pkgutil.c index ec8d5f3..c4da5ba 100644 --- a/pkgutil.c +++ b/pkgutil.c @@ -308,6 +308,12 @@ static void extract_nested_archive_from_stream(struct astream *in, char *rel = normalize_rel_path(p); archive_entry_set_pathname(e, rel); + if (apply_strip_components(e, strip_components)) { + archive_read_data_skip(a); + free(rel); + continue; + } + if (match != NULL) { r = match_excluded_with_prefix(match, e, prefix); if (r != 0) { @@ -317,12 +323,6 @@ static void extract_nested_archive_from_stream(struct astream *in, } } - if (apply_strip_components(e, strip_components)) { - archive_read_data_skip(a); - free(rel); - continue; - } - r = archive_read_extract2(a, e, disk); if (r != ARCHIVE_OK) { free(rel); @@ -462,7 +462,8 @@ static int match_excluded_with_prefix(struct archive *match, if (match == NULL) { return (0); } - if (prefix == NULL || prefix[0] == '\0') { + if (prefix == NULL || prefix[0] == '\0' || + (prefix[0] == '.' && prefix[1] == '\0')) { return archive_match_excluded(match, e); } const char *orig = archive_entry_pathname(e); @@ -711,14 +712,6 @@ int main(int argc, char **argv) { const char *p = archive_entry_pathname(e); char *rel = normalize_rel_path(p); archive_entry_set_pathname(e, rel); - if (match != NULL) { - r = archive_match_excluded(match, e); - if (r != 0) { - archive_read_data_skip(xar); - free(rel); - continue; - } - } int is_nested = should_be_treated_as_nested_archive(rel); if (do_expand_full && is_nested) { char *nested_outdir = strip_components_path(rel, strip_components); @@ -750,7 +743,7 @@ int main(int argc, char **argv) { }; extract_nested_archive_from_stream(&in, nested_outdir, flags, match, - nested_strip, rel); + nested_strip, nested_outdir); } free(nested_outdir); free(rel); @@ -760,6 +753,14 @@ int main(int argc, char **argv) { free(rel); continue; } + if (match != NULL) { + r = archive_match_excluded(match, e); + if (r != 0) { + archive_read_data_skip(xar); + free(rel); + continue; + } + } r = archive_read_extract2(xar, e, disk); if (r != ARCHIVE_OK) { free(rel); diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 85cc5e4..0fda226 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -52,7 +52,7 @@ run_binary( ], args = [ "--exclude", - "Payload/Library/Developer/CommandLineTools/SDKs/MacOSX15.5.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.6/Headers/ruby/ruby", + "System/Library/Frameworks/Ruby.framework/Versions/2.6/Headers/ruby/ruby", "--expand-full", "--strip-components", "6", @@ -64,6 +64,32 @@ run_binary( tags = ["manual"], ) +run_binary( + name = "pkgutil_component_expand_full_strip_6_include_exclude_action", + tool = "//:pkgutil", + srcs = [ + "@component_pkg//file", + ], + args = [ + "--include", + "usr/*", + "--exclude", + "usr/include/*", + "--exclude", + "usr/lib/*", + "--exclude", + "usr/share/*", + "--expand-full", + "--strip-components", + "6", + "$(location @component_pkg//file)", + "$@", + ], + out_dirs = ["pkgutil-component-expand-full-strip-6-include-exclude"], + testonly = True, + tags = ["manual"], +) + run_binary( name = "pkgutil_product_expand_action", tool = "//:pkgutil", @@ -169,6 +195,32 @@ exec_test( ], ) +exec_test( + native_test, + name = "pkgutil_component_expand_full_strip_6_include_exclude_test", + src = ":test", + args = [ + "-ne", + "$(location :pkgutil_component_expand_full_strip_6_include_exclude_action)/usr/share/man/mann/bignum.n", + ], + data = [ + ":pkgutil_component_expand_full_strip_6_include_exclude_action", + ], +) + +exec_test( + native_test, + name = "pkgutil_component_expand_full_strip_6_include_exclude_exists_test", + src = ":test", + args = [ + "-e", + "$(location :pkgutil_component_expand_full_strip_6_include_exclude_action)/usr/bin/cups-config", + ], + data = [ + ":pkgutil_component_expand_full_strip_6_include_exclude_action", + ], +) + exec_test( native_test, name = "pkgutil_product_expand_test", From d218c5b9c96e8b0f5fbb8ae2351df8720f1153ed Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Thu, 5 Feb 2026 12:23:44 +0100 Subject: [PATCH 2/4] Revert "Include and exclude paths should apply after strip" This reverts commit 9d033af78217085af3f4242ebfed9da2a75f4423. --- pkgutil.c | 33 ++++++++++++++--------------- tests/BUILD.bazel | 54 +---------------------------------------------- 2 files changed, 17 insertions(+), 70 deletions(-) diff --git a/pkgutil.c b/pkgutil.c index c4da5ba..ec8d5f3 100644 --- a/pkgutil.c +++ b/pkgutil.c @@ -308,12 +308,6 @@ static void extract_nested_archive_from_stream(struct astream *in, char *rel = normalize_rel_path(p); archive_entry_set_pathname(e, rel); - if (apply_strip_components(e, strip_components)) { - archive_read_data_skip(a); - free(rel); - continue; - } - if (match != NULL) { r = match_excluded_with_prefix(match, e, prefix); if (r != 0) { @@ -323,6 +317,12 @@ static void extract_nested_archive_from_stream(struct astream *in, } } + if (apply_strip_components(e, strip_components)) { + archive_read_data_skip(a); + free(rel); + continue; + } + r = archive_read_extract2(a, e, disk); if (r != ARCHIVE_OK) { free(rel); @@ -462,8 +462,7 @@ static int match_excluded_with_prefix(struct archive *match, if (match == NULL) { return (0); } - if (prefix == NULL || prefix[0] == '\0' || - (prefix[0] == '.' && prefix[1] == '\0')) { + if (prefix == NULL || prefix[0] == '\0') { return archive_match_excluded(match, e); } const char *orig = archive_entry_pathname(e); @@ -712,6 +711,14 @@ int main(int argc, char **argv) { const char *p = archive_entry_pathname(e); char *rel = normalize_rel_path(p); archive_entry_set_pathname(e, rel); + if (match != NULL) { + r = archive_match_excluded(match, e); + if (r != 0) { + archive_read_data_skip(xar); + free(rel); + continue; + } + } int is_nested = should_be_treated_as_nested_archive(rel); if (do_expand_full && is_nested) { char *nested_outdir = strip_components_path(rel, strip_components); @@ -743,7 +750,7 @@ int main(int argc, char **argv) { }; extract_nested_archive_from_stream(&in, nested_outdir, flags, match, - nested_strip, nested_outdir); + nested_strip, rel); } free(nested_outdir); free(rel); @@ -753,14 +760,6 @@ int main(int argc, char **argv) { free(rel); continue; } - if (match != NULL) { - r = archive_match_excluded(match, e); - if (r != 0) { - archive_read_data_skip(xar); - free(rel); - continue; - } - } r = archive_read_extract2(xar, e, disk); if (r != ARCHIVE_OK) { free(rel); diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 0fda226..85cc5e4 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -52,7 +52,7 @@ run_binary( ], args = [ "--exclude", - "System/Library/Frameworks/Ruby.framework/Versions/2.6/Headers/ruby/ruby", + "Payload/Library/Developer/CommandLineTools/SDKs/MacOSX15.5.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.6/Headers/ruby/ruby", "--expand-full", "--strip-components", "6", @@ -64,32 +64,6 @@ run_binary( tags = ["manual"], ) -run_binary( - name = "pkgutil_component_expand_full_strip_6_include_exclude_action", - tool = "//:pkgutil", - srcs = [ - "@component_pkg//file", - ], - args = [ - "--include", - "usr/*", - "--exclude", - "usr/include/*", - "--exclude", - "usr/lib/*", - "--exclude", - "usr/share/*", - "--expand-full", - "--strip-components", - "6", - "$(location @component_pkg//file)", - "$@", - ], - out_dirs = ["pkgutil-component-expand-full-strip-6-include-exclude"], - testonly = True, - tags = ["manual"], -) - run_binary( name = "pkgutil_product_expand_action", tool = "//:pkgutil", @@ -195,32 +169,6 @@ exec_test( ], ) -exec_test( - native_test, - name = "pkgutil_component_expand_full_strip_6_include_exclude_test", - src = ":test", - args = [ - "-ne", - "$(location :pkgutil_component_expand_full_strip_6_include_exclude_action)/usr/share/man/mann/bignum.n", - ], - data = [ - ":pkgutil_component_expand_full_strip_6_include_exclude_action", - ], -) - -exec_test( - native_test, - name = "pkgutil_component_expand_full_strip_6_include_exclude_exists_test", - src = ":test", - args = [ - "-e", - "$(location :pkgutil_component_expand_full_strip_6_include_exclude_action)/usr/bin/cups-config", - ], - data = [ - ":pkgutil_component_expand_full_strip_6_include_exclude_action", - ], -) - exec_test( native_test, name = "pkgutil_product_expand_test", From 4f91cfb3a2dd2320a36b0365a8056d3659cafd2d Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Thu, 5 Feb 2026 13:01:03 +0100 Subject: [PATCH 3/4] wip --- pkgutil.c | 226 ++++++++++++++++++++++++++++++++-------------- tests/BUILD.bazel | 54 +++++++++++ 2 files changed, 210 insertions(+), 70 deletions(-) diff --git a/pkgutil.c b/pkgutil.c index ec8d5f3..b91fe1e 100644 --- a/pkgutil.c +++ b/pkgutil.c @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -79,9 +80,22 @@ static char *strip_components_path(const char *path, int strip); static int apply_strip_components(struct archive_entry *e, int strip); static int path_component_count(const char *path); static char *normalize_rel_path(const char *path); -static int match_excluded_with_prefix(struct archive *match, - struct archive_entry *e, - const char *prefix); +struct pattern_list { + char **items; + size_t len; + size_t cap; +}; +static void pattern_list_add(struct pattern_list *list, const char *pattern); +static void pattern_list_free(struct pattern_list *list); +static int matches_any_pattern(const struct pattern_list *list, const char *path); +static int matches_any_pattern_with_recursion(const struct pattern_list *list, + const char *path); +static int should_extract_path(const struct pattern_list *includes, + const struct pattern_list *excludes, + const char *path); +static char *join_prefix_path(const char *prefix, const char *path); +static int has_include_descendant(const struct pattern_list *includes, + const char *path); static int pkg_getopt(int *argc, char ***argv, const char **arg) { enum { state_start = 0, state_next_word, state_short, state_long }; @@ -263,7 +277,8 @@ static int astream_close_cb(struct archive *a, void *client_data) { static void extract_nested_archive_from_stream(struct astream *in, const char *outdir, int flags, - struct archive *match, + const struct pattern_list *includes, + const struct pattern_list *excludes, int strip_components, const char *prefix) { struct archive *a = archive_read_new(); @@ -308,14 +323,14 @@ static void extract_nested_archive_from_stream(struct astream *in, char *rel = normalize_rel_path(p); archive_entry_set_pathname(e, rel); - if (match != NULL) { - r = match_excluded_with_prefix(match, e, prefix); - if (r != 0) { - archive_read_data_skip(a); - free(rel); - continue; - } + char *logical_path = join_prefix_path(prefix, rel); + if (!should_extract_path(includes, excludes, logical_path)) { + archive_read_data_skip(a); + free(logical_path); + free(rel); + continue; } + free(logical_path); if (apply_strip_components(e, strip_components)) { archive_read_data_skip(a); @@ -456,41 +471,121 @@ static int path_component_count(const char *path) { return (count); } -static int match_excluded_with_prefix(struct archive *match, - struct archive_entry *e, - const char *prefix) { - if (match == NULL) { - return (0); +static void pattern_list_add(struct pattern_list *list, const char *pattern) { + if (list->len == list->cap) { + size_t new_cap = list->cap == 0 ? 8 : list->cap * 2; + char **new_items = realloc(list->items, new_cap * sizeof(*new_items)); + if (new_items == NULL) { + fail_errno("realloc"); + } + list->items = new_items; + list->cap = new_cap; + } + char *dup = strdup(pattern); + if (dup == NULL) { + fail_errno("strdup"); } - if (prefix == NULL || prefix[0] == '\0') { - return archive_match_excluded(match, e); + list->items[list->len++] = dup; +} + +static void pattern_list_free(struct pattern_list *list) { + for (size_t i = 0; i < list->len; i++) { + free(list->items[i]); } - const char *orig = archive_entry_pathname(e); - if (orig == NULL) { - return archive_match_excluded(match, e); + free(list->items); + list->items = NULL; + list->len = 0; + list->cap = 0; +} + +static int matches_any_pattern(const struct pattern_list *list, + const char *path) { + for (size_t i = 0; i < list->len; i++) { + const char *pat = list->items[i]; + if (fnmatch(pat, path, FNM_PATHNAME) == 0) { + return (1); + } + size_t pat_len = strlen(pat); + if (pat_len >= 2 && pat[pat_len - 2] == '/' && pat[pat_len - 1] == '*') { + size_t dir_len = pat_len - 2; + if (strlen(path) == dir_len && strncmp(path, pat, dir_len) == 0) { + return (1); + } + } } - char *orig_copy = strdup(orig); - if (orig_copy == NULL) { + return (0); +} + +static int matches_any_pattern_with_recursion(const struct pattern_list *list, + const char *path) { + if (matches_any_pattern(list, path)) { + return (1); + } + char *tmp = strdup(path); + if (tmp == NULL) { fail_errno("strdup"); } + for (;;) { + char *slash = strrchr(tmp, '/'); + if (slash == NULL) { + break; + } + *slash = '\0'; + if (matches_any_pattern(list, tmp)) { + free(tmp); + return (1); + } + } + free(tmp); + return (0); +} + +static int should_extract_path(const struct pattern_list *includes, + const struct pattern_list *excludes, + const char *path) { + if (includes->len > 0 && + !matches_any_pattern_with_recursion(includes, path)) { + return (0); + } + if (excludes->len > 0 && + matches_any_pattern_with_recursion(excludes, path)) { + return (0); + } + return (1); +} + +static int has_include_descendant(const struct pattern_list *includes, + const char *path) { + size_t plen = strlen(path); + for (size_t i = 0; i < includes->len; i++) { + const char *pat = includes->items[i]; + if (strncmp(pat, path, plen) == 0 && pat[plen] == '/') { + return (1); + } + } + return (0); +} + +static char *join_prefix_path(const char *prefix, const char *path) { + if (prefix == NULL || prefix[0] == '\0' || + (prefix[0] == '.' && prefix[1] == '\0')) { + char *dup = strdup(path); + if (dup == NULL) { + fail_errno("strdup"); + } + return (dup); + } size_t plen = strlen(prefix); - size_t olen = strlen(orig_copy); - size_t total = plen + 1 + olen + 1; + size_t path_len = strlen(path); + size_t total = plen + 1 + path_len + 1; char *buf = malloc(total); if (buf == NULL) { - free(orig_copy); fail_errno("malloc"); } memcpy(buf, prefix, plen); buf[plen] = '/'; - memcpy(buf + plen + 1, orig_copy, olen + 1); - - archive_entry_set_pathname(e, buf); - int r = archive_match_excluded(match, e); - archive_entry_set_pathname(e, orig_copy); - free(buf); - free(orig_copy); - return (r); + memcpy(buf + plen + 1, path, path_len + 1); + return (buf); } static int contains_dotdot_segment(const char *path) { @@ -582,7 +677,8 @@ int main(int argc, char **argv) { const char *xar_path = NULL; const char *outdir = NULL; struct archive *xar; - struct archive *match = NULL; + struct pattern_list includes = {0}; + struct pattern_list excludes = {0}; struct archive *disk; struct archive_entry *e; int r; @@ -611,28 +707,10 @@ int main(int argc, char **argv) { do_expand_full = 1; break; case opt_include: - if (match == NULL) { - match = archive_match_new(); - if (match == NULL) { - fail_errno("archive_match_new"); - } - } - r = archive_match_include_pattern(match, arg); - if (r != ARCHIVE_OK) { - fail_archive(match, "include pattern"); - } + pattern_list_add(&includes, arg); break; case opt_exclude: - if (match == NULL) { - match = archive_match_new(); - if (match == NULL) { - fail_errno("archive_match_new"); - } - } - r = archive_match_exclude_pattern(match, arg); - if (r != ARCHIVE_OK) { - fail_archive(match, "exclude pattern"); - } + pattern_list_add(&excludes, arg); break; case opt_strip_components: strip_components = atoi(arg); @@ -667,10 +745,6 @@ int main(int argc, char **argv) { fail_errno("archive_read_new"); } - if (match != NULL) { - archive_match_set_inclusion_recursion(match, 1); - } - disk = archive_write_disk_new(); if (disk == NULL) { fail_errno("archive_write_disk_new"); @@ -711,16 +785,21 @@ int main(int argc, char **argv) { const char *p = archive_entry_pathname(e); char *rel = normalize_rel_path(p); archive_entry_set_pathname(e, rel); - if (match != NULL) { - r = archive_match_excluded(match, e); - if (r != 0) { + int is_nested = should_be_treated_as_nested_archive(rel); + if (do_expand_full && is_nested) { + char *logical_path = join_prefix_path(NULL, rel); + int include_nested = should_extract_path(&includes, &excludes, logical_path); + if (!include_nested && includes.len > 0 && + has_include_descendant(&includes, logical_path)) { + include_nested = 1; + } + free(logical_path); + if (!include_nested) { archive_read_data_skip(xar); free(rel); continue; } - } - int is_nested = should_be_treated_as_nested_archive(rel); - if (do_expand_full && is_nested) { + char *nested_outdir = strip_components_path(rel, strip_components); int nested_strip = strip_components; int rel_components = path_component_count(rel); @@ -749,12 +828,20 @@ int main(int argc, char **argv) { .eof = 0, }; - extract_nested_archive_from_stream(&in, nested_outdir, flags, match, - nested_strip, rel); + extract_nested_archive_from_stream(&in, nested_outdir, flags, &includes, + &excludes, nested_strip, rel); } free(nested_outdir); free(rel); } else { + char *logical_path = join_prefix_path(NULL, rel); + if (!should_extract_path(&includes, &excludes, logical_path)) { + archive_read_data_skip(xar); + free(logical_path); + free(rel); + continue; + } + free(logical_path); if (apply_strip_components(e, strip_components)) { archive_read_data_skip(xar); free(rel); @@ -771,8 +858,7 @@ int main(int argc, char **argv) { archive_write_free(disk); archive_read_free(xar); - if (match != NULL) { - archive_match_free(match); - } + pattern_list_free(&includes); + pattern_list_free(&excludes); return (0); } diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 85cc5e4..daecae2 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -64,6 +64,32 @@ run_binary( tags = ["manual"], ) +run_binary( + name = "pkgutil_component_expand_full_strip_6_usr_filter_action", + tool = "//:pkgutil", + srcs = [ + "@component_pkg//file", + ], + args = [ + "--include", + "Payload/Library/Developer/CommandLineTools/SDKs/MacOSX15.5.sdk/usr/*", + "--exclude", + "Payload/Library/Developer/CommandLineTools/SDKs/MacOSX15.5.sdk/usr/include/tkPlatDecls.h", + "--exclude", + "Payload/Library/Developer/CommandLineTools/SDKs/MacOSX15.5.sdk/usr/lib/libMTLCapture.tbd", + "--exclude", + "Payload/Library/Developer/CommandLineTools/SDKs/MacOSX15.5.sdk/usr/share/*", + "--expand-full", + "--strip-components", + "6", + "$(location @component_pkg//file)", + "$@", + ], + out_dirs = ["pkgutil-component-expand-full-strip-6-usr-filter"], + testonly = True, + tags = ["manual"], +) + run_binary( name = "pkgutil_product_expand_action", tool = "//:pkgutil", @@ -169,6 +195,34 @@ exec_test( ], ) +exec_test( + native_test, + name = "pkgutil_component_expand_full_strip_6_usr_filter_test", + src = ":test", + args = [ + "-e", + "$(location :pkgutil_component_expand_full_strip_6_usr_filter_action)/usr", + "$(location :pkgutil_component_expand_full_strip_6_usr_filter_action)/usr/lib/libNFC_HAL.tbd", + ], + data = [ + ":pkgutil_component_expand_full_strip_6_usr_filter_action", + ], +) + +exec_test( + native_test, + name = "pkgutil_component_expand_full_strip_6_usr_filter_missing_test", + src = ":test", + args = [ + "-ne", + "$(location :pkgutil_component_expand_full_strip_6_usr_filter_action)/System", + "$(location :pkgutil_component_expand_full_strip_6_usr_filter_action)/usr/share", + ], + data = [ + ":pkgutil_component_expand_full_strip_6_usr_filter_action", + ], +) + exec_test( native_test, name = "pkgutil_product_expand_test", From b14b47b62c80ffc4f623937a6fc32ce64ad00284 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Thu, 5 Feb 2026 13:30:24 +0100 Subject: [PATCH 4/4] wip --- pkgutil.c | 103 +++++++++++++++------------------------------- tests/BUILD.bazel | 8 ++-- 2 files changed, 38 insertions(+), 73 deletions(-) diff --git a/pkgutil.c b/pkgutil.c index b91fe1e..c3c7309 100644 --- a/pkgutil.c +++ b/pkgutil.c @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -87,12 +86,7 @@ struct pattern_list { }; static void pattern_list_add(struct pattern_list *list, const char *pattern); static void pattern_list_free(struct pattern_list *list); -static int matches_any_pattern(const struct pattern_list *list, const char *path); -static int matches_any_pattern_with_recursion(const struct pattern_list *list, - const char *path); -static int should_extract_path(const struct pattern_list *includes, - const struct pattern_list *excludes, - const char *path); +static int should_extract_path(struct archive *matching, const char *path); static char *join_prefix_path(const char *prefix, const char *path); static int has_include_descendant(const struct pattern_list *includes, const char *path); @@ -277,8 +271,7 @@ static int astream_close_cb(struct archive *a, void *client_data) { static void extract_nested_archive_from_stream(struct astream *in, const char *outdir, int flags, - const struct pattern_list *includes, - const struct pattern_list *excludes, + struct archive *matching, int strip_components, const char *prefix) { struct archive *a = archive_read_new(); @@ -324,7 +317,7 @@ static void extract_nested_archive_from_stream(struct astream *in, archive_entry_set_pathname(e, rel); char *logical_path = join_prefix_path(prefix, rel); - if (!should_extract_path(includes, excludes, logical_path)) { + if (!should_extract_path(matching, logical_path)) { archive_read_data_skip(a); free(logical_path); free(rel); @@ -498,60 +491,18 @@ static void pattern_list_free(struct pattern_list *list) { list->cap = 0; } -static int matches_any_pattern(const struct pattern_list *list, - const char *path) { - for (size_t i = 0; i < list->len; i++) { - const char *pat = list->items[i]; - if (fnmatch(pat, path, FNM_PATHNAME) == 0) { - return (1); - } - size_t pat_len = strlen(pat); - if (pat_len >= 2 && pat[pat_len - 2] == '/' && pat[pat_len - 1] == '*') { - size_t dir_len = pat_len - 2; - if (strlen(path) == dir_len && strncmp(path, pat, dir_len) == 0) { - return (1); - } - } +static int should_extract_path(struct archive *matching, const char *path) { + struct archive_entry *entry = archive_entry_new(); + if (entry == NULL) { + fail_errno("archive_entry_new"); } - return (0); -} - -static int matches_any_pattern_with_recursion(const struct pattern_list *list, - const char *path) { - if (matches_any_pattern(list, path)) { - return (1); + archive_entry_set_pathname(entry, path); + int excluded = archive_match_excluded(matching, entry); + archive_entry_free(entry); + if (excluded < 0) { + fail_archive(matching, "archive_match_excluded"); } - char *tmp = strdup(path); - if (tmp == NULL) { - fail_errno("strdup"); - } - for (;;) { - char *slash = strrchr(tmp, '/'); - if (slash == NULL) { - break; - } - *slash = '\0'; - if (matches_any_pattern(list, tmp)) { - free(tmp); - return (1); - } - } - free(tmp); - return (0); -} - -static int should_extract_path(const struct pattern_list *includes, - const struct pattern_list *excludes, - const char *path) { - if (includes->len > 0 && - !matches_any_pattern_with_recursion(includes, path)) { - return (0); - } - if (excludes->len > 0 && - matches_any_pattern_with_recursion(excludes, path)) { - return (0); - } - return (1); + return (excluded == 0); } static int has_include_descendant(const struct pattern_list *includes, @@ -677,8 +628,8 @@ int main(int argc, char **argv) { const char *xar_path = NULL; const char *outdir = NULL; struct archive *xar; + struct archive *matching; struct pattern_list includes = {0}; - struct pattern_list excludes = {0}; struct archive *disk; struct archive_entry *e; int r; @@ -690,6 +641,11 @@ int main(int argc, char **argv) { int strip_components = 0; int flags; + matching = archive_match_new(); + if (matching == NULL) { + fail_errno("archive_match_new"); + } + while ((opt = pkg_getopt(&argc, &argv, &arg)) != -1) { switch (opt) { case 'f': @@ -708,9 +664,14 @@ int main(int argc, char **argv) { break; case opt_include: pattern_list_add(&includes, arg); + if (archive_match_include_pattern(matching, arg) != ARCHIVE_OK) { + fail_archive(matching, "archive_match_include_pattern"); + } break; case opt_exclude: - pattern_list_add(&excludes, arg); + if (archive_match_exclude_pattern(matching, arg) != ARCHIVE_OK) { + fail_archive(matching, "archive_match_exclude_pattern"); + } break; case opt_strip_components: strip_components = atoi(arg); @@ -781,6 +742,10 @@ int main(int argc, char **argv) { fail_errno("chdir(outdir)"); } + if (archive_match_set_inclusion_recursion(matching, 1) != ARCHIVE_OK) { + fail_archive(matching, "archive_match_set_inclusion_recursion"); + } + while ((r = archive_read_next_header(xar, &e)) == ARCHIVE_OK) { const char *p = archive_entry_pathname(e); char *rel = normalize_rel_path(p); @@ -788,7 +753,7 @@ int main(int argc, char **argv) { int is_nested = should_be_treated_as_nested_archive(rel); if (do_expand_full && is_nested) { char *logical_path = join_prefix_path(NULL, rel); - int include_nested = should_extract_path(&includes, &excludes, logical_path); + int include_nested = should_extract_path(matching, logical_path); if (!include_nested && includes.len > 0 && has_include_descendant(&includes, logical_path)) { include_nested = 1; @@ -828,14 +793,14 @@ int main(int argc, char **argv) { .eof = 0, }; - extract_nested_archive_from_stream(&in, nested_outdir, flags, &includes, - &excludes, nested_strip, rel); + extract_nested_archive_from_stream(&in, nested_outdir, flags, matching, + nested_strip, rel); } free(nested_outdir); free(rel); } else { char *logical_path = join_prefix_path(NULL, rel); - if (!should_extract_path(&includes, &excludes, logical_path)) { + if (!should_extract_path(matching, logical_path)) { archive_read_data_skip(xar); free(logical_path); free(rel); @@ -858,7 +823,7 @@ int main(int argc, char **argv) { archive_write_free(disk); archive_read_free(xar); + archive_match_free(matching); pattern_list_free(&includes); - pattern_list_free(&excludes); return (0); } diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index daecae2..d3cc16a 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -73,10 +73,10 @@ run_binary( args = [ "--include", "Payload/Library/Developer/CommandLineTools/SDKs/MacOSX15.5.sdk/usr/*", + "--include", + "Payload/Library/Developer/CommandLineTools/SDKs/MacOSX15.5.sdk/System/*", "--exclude", - "Payload/Library/Developer/CommandLineTools/SDKs/MacOSX15.5.sdk/usr/include/tkPlatDecls.h", - "--exclude", - "Payload/Library/Developer/CommandLineTools/SDKs/MacOSX15.5.sdk/usr/lib/libMTLCapture.tbd", + "Payload/Library/Developer/CommandLineTools/SDKs/MacOSX15.5.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.6/Headers/ruby/ruby", "--exclude", "Payload/Library/Developer/CommandLineTools/SDKs/MacOSX15.5.sdk/usr/share/*", "--expand-full", @@ -202,6 +202,7 @@ exec_test( args = [ "-e", "$(location :pkgutil_component_expand_full_strip_6_usr_filter_action)/usr", + "$(location :pkgutil_component_expand_full_strip_6_usr_filter_action)/System", "$(location :pkgutil_component_expand_full_strip_6_usr_filter_action)/usr/lib/libNFC_HAL.tbd", ], data = [ @@ -215,7 +216,6 @@ exec_test( src = ":test", args = [ "-ne", - "$(location :pkgutil_component_expand_full_strip_6_usr_filter_action)/System", "$(location :pkgutil_component_expand_full_strip_6_usr_filter_action)/usr/share", ], data = [