Enable GNU tar's --occurrence option when FEATURE_TAR_LONG_OPTIONS is enabled.

Signed-off-by: Gavin Li <gavi...@thegavinli.com>
---
 archival/ar.c                                 |  6 +-
 archival/cpio.c                               |  2 +-
 archival/dpkg.c                               | 38 ++++++------
 archival/dpkg_deb.c                           | 24 ++++----
 archival/libarchive/Kbuild.src                |  2 +
 archival/libarchive/accept_add_to.c           | 15 +++++
 archival/libarchive/accept_add_to_end.c       | 15 +++++
 archival/libarchive/filter_accept_list.c      |  2 +-
 .../libarchive/filter_accept_list_reassign.c  |  2 +-
 .../libarchive/filter_accept_reject_list.c    | 18 +++++-
 archival/libarchive/get_header_tar.c          |  7 +++
 archival/tar.c                                | 59 ++++++++++++++-----
 include/bb_archive.h                          | 14 ++++-
 testsuite/tar/tar-handles-occurrence          | 18 ++++++
 14 files changed, 168 insertions(+), 54 deletions(-)
 create mode 100644 archival/libarchive/accept_add_to.c
 create mode 100644 archival/libarchive/accept_add_to_end.c
 create mode 100644 testsuite/tar/tar-handles-occurrence

diff --git a/archival/ar.c b/archival/ar.c
index 320cbae72..f1df21e95 100644
--- a/archival/ar.c
+++ b/archival/ar.c
@@ -56,7 +56,7 @@
 /* filter out entries with same names as specified on the command line */
 static char FAST_FUNC filter_replaceable(archive_handle_t *handle)
 {
-       if (find_list_entry(handle->accept, handle->file_header->name))
+       if (find_list_entry((llist_t *)handle->accept, 
handle->file_header->name))
                return EXIT_FAILURE;
 
        return EXIT_SUCCESS;
@@ -124,7 +124,7 @@ static int write_ar_header(archive_handle_t *handle)
        struct stat st;
        int fd;
 
-       fn = llist_pop(&handle->accept);
+       fn = llist_pop((llist_t **)&handle->accept);
        if (!fn)
                return -1;
 
@@ -287,7 +287,7 @@ int ar_main(int argc UNUSED_PARAM, char **argv)
        if (*argv)
                archive_handle->filter = filter_accept_list;
        while (*argv) {
-               llist_add_to_end(&archive_handle->accept, *argv++);
+               accept_add_to_end(&archive_handle->accept, *argv++);
        }
 
 #if ENABLE_FEATURE_AR_CREATE
diff --git a/archival/cpio.c b/archival/cpio.c
index f0d990048..4950d6ead 100644
--- a/archival/cpio.c
+++ b/archival/cpio.c
@@ -559,7 +559,7 @@ int cpio_main(int argc UNUSED_PARAM, char **argv)
 
        while (*argv) {
                archive_handle->filter = filter_accept_list;
-               llist_add_to(&archive_handle->accept, *argv);
+               accept_add_to(&archive_handle->accept, *argv);
                argv++;
        }
 
diff --git a/archival/dpkg.c b/archival/dpkg.c
index 8031956e9..23e46e062 100644
--- a/archival/dpkg.c
+++ b/archival/dpkg.c
@@ -1495,15 +1495,15 @@ static void init_archive_deb_control(archive_handle_t 
*ar_handle)
        tar_handle->src_fd = ar_handle->src_fd;
 
        /* We don't care about data.tar.* or debian-binary, just control.tar.* 
*/
-       llist_add_to(&(ar_handle->accept), (char*)"control.tar");
+       accept_add_to(&(ar_handle->accept), (char*)"control.tar");
 #if ENABLE_FEATURE_SEAMLESS_GZ
-       llist_add_to(&(ar_handle->accept), (char*)"control.tar.gz");
+       accept_add_to(&(ar_handle->accept), (char*)"control.tar.gz");
 #endif
 #if ENABLE_FEATURE_SEAMLESS_BZ2
-       llist_add_to(&(ar_handle->accept), (char*)"control.tar.bz2");
+       accept_add_to(&(ar_handle->accept), (char*)"control.tar.bz2");
 #endif
 #if ENABLE_FEATURE_SEAMLESS_XZ
-       llist_add_to(&(ar_handle->accept), (char*)"control.tar.xz");
+       accept_add_to(&(ar_handle->accept), (char*)"control.tar.xz");
 #endif
 
        /* Assign the tar handle as a subarchive of the ar handle */
@@ -1519,18 +1519,18 @@ static void init_archive_deb_data(archive_handle_t 
*ar_handle)
        tar_handle->src_fd = ar_handle->src_fd;
 
        /* We don't care about control.tar.* or debian-binary, just data.tar.* 
*/
-       llist_add_to(&(ar_handle->accept), (char*)"data.tar");
+       accept_add_to(&(ar_handle->accept), (char*)"data.tar");
 #if ENABLE_FEATURE_SEAMLESS_GZ
-       llist_add_to(&(ar_handle->accept), (char*)"data.tar.gz");
+       accept_add_to(&(ar_handle->accept), (char*)"data.tar.gz");
 #endif
 #if ENABLE_FEATURE_SEAMLESS_BZ2
-       llist_add_to(&(ar_handle->accept), (char*)"data.tar.bz2");
+       accept_add_to(&(ar_handle->accept), (char*)"data.tar.bz2");
 #endif
 #if ENABLE_FEATURE_SEAMLESS_LZMA
-       llist_add_to(&(ar_handle->accept), (char*)"data.tar.lzma");
+       accept_add_to(&(ar_handle->accept), (char*)"data.tar.lzma");
 #endif
 #if ENABLE_FEATURE_SEAMLESS_XZ
-       llist_add_to(&(ar_handle->accept), (char*)"data.tar.xz");
+       accept_add_to(&(ar_handle->accept), (char*)"data.tar.xz");
 #endif
 
        /* Assign the tar handle as a subarchive of the ar handle */
@@ -1545,7 +1545,7 @@ static void FAST_FUNC 
data_extract_to_buffer(archive_handle_t *archive_handle)
        xread(archive_handle->src_fd, archive_handle->dpkg__buffer, size);
 }
 
-static char *deb_extract_control_file_to_buffer(archive_handle_t *ar_handle, 
llist_t *myaccept)
+static char *deb_extract_control_file_to_buffer(archive_handle_t *ar_handle, 
accept_llist_t *myaccept)
 {
        ar_handle->dpkg__sub_archive->action_data = data_extract_to_buffer;
        ar_handle->dpkg__sub_archive->accept = myaccept;
@@ -1557,7 +1557,7 @@ static char 
*deb_extract_control_file_to_buffer(archive_handle_t *ar_handle, lli
        return ar_handle->dpkg__sub_archive->dpkg__buffer;
 }
 
-static void append_control_file_to_llist(const char *package_name, const char 
*control_name, llist_t **ll)
+static void append_control_file_to_llist(const char *package_name, const char 
*control_name, accept_llist_t **ll)
 {
        FILE *fp;
        char *filename, *line;
@@ -1567,7 +1567,7 @@ static void append_control_file_to_llist(const char 
*package_name, const char *c
        free(filename);
        if (fp != NULL) {
                while ((line = xmalloc_fgetline(fp)) != NULL)
-                       llist_add_to(ll, line);
+                       accept_add_to(ll, line);
                fclose(fp);
        }
 }
@@ -1578,7 +1578,7 @@ static char FAST_FUNC 
filter_rename_config(archive_handle_t *archive_handle)
        char *name_ptr = archive_handle->file_header->name + 1;
 
        /* Is this file marked as config file? */
-       if (!find_list_entry(archive_handle->accept, name_ptr))
+       if (!find_list_entry((llist_t *)archive_handle->accept, name_ptr))
                return EXIT_SUCCESS; /* no */
 
        fd = open(name_ptr, O_RDONLY);
@@ -1600,7 +1600,7 @@ static char FAST_FUNC 
filter_rename_config(archive_handle_t *archive_handle)
                free(buf);
 
                /* Is it changed after install? */
-               if (find_list_entry(archive_handle->accept, md5line) == NULL) {
+               if (find_list_entry((llist_t *)archive_handle->accept, md5line) 
== NULL) {
                        printf("Warning: Creating %s as %s.dpkg-new\n", 
name_ptr, name_ptr);
                        archive_handle->file_header->name = 
xasprintf("%s.dpkg-new", archive_handle->file_header->name);
                }
@@ -1660,8 +1660,8 @@ static void unpack_package(deb_file_t *deb_file)
        char *list_filename;
        archive_handle_t *archive_handle;
        FILE *out_stream;
-       llist_t *accept_list;
-       llist_t *conffile_list;
+       accept_llist_t *accept_list;
+       accept_llist_t *conffile_list;
        int i;
 
        /* If existing version, remove it first */
@@ -1690,7 +1690,7 @@ static void unpack_package(deb_file_t *deb_file)
        i = 0;
        while (i < ARRAY_SIZE(all_control_files)) {
                char *c = xasprintf("./%s", all_control_files[i]);
-               llist_add_to(&accept_list, c);
+               accept_add_to(&accept_list, c);
                i++;
        }
        archive_handle->dpkg__sub_archive->accept = accept_list;
@@ -1831,10 +1831,10 @@ int dpkg_main(int argc UNUSED_PARAM, char **argv)
                if (opt & (OPT_install | OPT_unpack)) {
                        /* -i/-u: require filename */
                        archive_handle_t *archive_handle;
-                       llist_t *control_list = NULL;
+                       accept_llist_t *control_list = NULL;
 
                        /* Extract the control file */
-                       llist_add_to(&control_list, (char*)"./control");
+                       accept_add_to(&control_list, (char*)"./control");
                        archive_handle = init_archive_deb_ar(argv[0]);
                        init_archive_deb_control(archive_handle);
                        deb_file[deb_count]->control_file = 
deb_extract_control_file_to_buffer(archive_handle, control_list);
diff --git a/archival/dpkg_deb.c b/archival/dpkg_deb.c
index dda931169..988c6bcad 100644
--- a/archival/dpkg_deb.c
+++ b/archival/dpkg_deb.c
@@ -47,7 +47,7 @@ int dpkg_deb_main(int argc UNUSED_PARAM, char **argv)
 {
        archive_handle_t *ar_archive;
        archive_handle_t *tar_archive;
-       llist_t *control_tar_llist = NULL;
+       accept_llist_t *control_tar_llist = NULL;
        unsigned opt;
        const char *extract_dir;
 
@@ -59,23 +59,23 @@ int dpkg_deb_main(int argc UNUSED_PARAM, char **argv)
        ar_archive->dpkg__sub_archive = tar_archive;
        ar_archive->filter = filter_accept_list_reassign;
 
-       llist_add_to(&ar_archive->accept, (char*)"data.tar");
-       llist_add_to(&control_tar_llist, (char*)"control.tar");
+       accept_add_to(&ar_archive->accept, (char*)"data.tar");
+       accept_add_to(&control_tar_llist, (char*)"control.tar");
 #if ENABLE_FEATURE_SEAMLESS_GZ
-       llist_add_to(&ar_archive->accept, (char*)"data.tar.gz");
-       llist_add_to(&control_tar_llist, (char*)"control.tar.gz");
+       accept_add_to(&ar_archive->accept, (char*)"data.tar.gz");
+       accept_add_to(&control_tar_llist, (char*)"control.tar.gz");
 #endif
 #if ENABLE_FEATURE_SEAMLESS_BZ2
-       llist_add_to(&ar_archive->accept, (char*)"data.tar.bz2");
-       llist_add_to(&control_tar_llist, (char*)"control.tar.bz2");
+       accept_add_to(&ar_archive->accept, (char*)"data.tar.bz2");
+       accept_add_to(&control_tar_llist, (char*)"control.tar.bz2");
 #endif
 #if ENABLE_FEATURE_SEAMLESS_LZMA
-       llist_add_to(&ar_archive->accept, (char*)"data.tar.lzma");
-       llist_add_to(&control_tar_llist, (char*)"control.tar.lzma");
+       accept_add_to(&ar_archive->accept, (char*)"data.tar.lzma");
+       accept_add_to(&control_tar_llist, (char*)"control.tar.lzma");
 #endif
 #if ENABLE_FEATURE_SEAMLESS_XZ
-       llist_add_to(&ar_archive->accept, (char*)"data.tar.xz");
-       llist_add_to(&control_tar_llist, (char*)"control.tar.xz");
+       accept_add_to(&ar_archive->accept, (char*)"data.tar.xz");
+       accept_add_to(&control_tar_llist, (char*)"control.tar.xz");
 #endif
 
        /* Must have 1 or 2 args */
@@ -95,7 +95,7 @@ int dpkg_deb_main(int argc UNUSED_PARAM, char **argv)
                /* Print the entire control file */
 //TODO: standard tool accepts an optional list of fields to print
                ar_archive->accept = control_tar_llist;
-               llist_add_to(&(tar_archive->accept), (char*)"./control");
+               accept_add_to(&(tar_archive->accept), (char*)"./control");
                tar_archive->filter = filter_accept_list;
                tar_archive->action_data = data_extract_to_stdout;
                if (extract_dir)
diff --git a/archival/libarchive/Kbuild.src b/archival/libarchive/Kbuild.src
index d2f284b08..ccc83a9f0 100644
--- a/archival/libarchive/Kbuild.src
+++ b/archival/libarchive/Kbuild.src
@@ -7,6 +7,8 @@
 lib-y:= common.o
 
 COMMON_FILES:= \
+       accept_add_to.o \
+       accept_add_to_end.o \
 \
        data_skip.o \
        data_extract_all.o \
diff --git a/archival/libarchive/accept_add_to.c 
b/archival/libarchive/accept_add_to.c
new file mode 100644
index 000000000..36310ba9d
--- /dev/null
+++ b/archival/libarchive/accept_add_to.c
@@ -0,0 +1,15 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ */
+#include "libbb.h"
+#include "bb_archive.h"
+
+void FAST_FUNC accept_add_to(accept_llist_t **old_head, char *data)
+{
+       accept_llist_t *new_head = xzalloc(sizeof(accept_llist_t));
+
+       new_head->data = data;
+       new_head->link = *old_head;
+       *old_head = new_head;
+}
diff --git a/archival/libarchive/accept_add_to_end.c 
b/archival/libarchive/accept_add_to_end.c
new file mode 100644
index 000000000..8260ea23e
--- /dev/null
+++ b/archival/libarchive/accept_add_to_end.c
@@ -0,0 +1,15 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ */
+#include "libbb.h"
+#include "bb_archive.h"
+
+void FAST_FUNC accept_add_to_end(accept_llist_t **list_head, char *data)
+{
+       while (*list_head)
+               list_head = &(*list_head)->link;
+       *list_head = xzalloc(sizeof(accept_llist_t));
+       (*list_head)->data = data;
+       /*(*list_head)->link = NULL;*/
+}
diff --git a/archival/libarchive/filter_accept_list.c 
b/archival/libarchive/filter_accept_list.c
index 32f806574..0319ff927 100644
--- a/archival/libarchive/filter_accept_list.c
+++ b/archival/libarchive/filter_accept_list.c
@@ -12,7 +12,7 @@
  */
 char FAST_FUNC filter_accept_list(archive_handle_t *archive_handle)
 {
-       if (find_list_entry(archive_handle->accept, 
archive_handle->file_header->name))
+       if (find_list_entry((llist_t *)archive_handle->accept, 
archive_handle->file_header->name))
                return EXIT_SUCCESS;
        return EXIT_FAILURE;
 }
diff --git a/archival/libarchive/filter_accept_list_reassign.c 
b/archival/libarchive/filter_accept_list_reassign.c
index 826c5c29d..c6428f9c3 100644
--- a/archival/libarchive/filter_accept_list_reassign.c
+++ b/archival/libarchive/filter_accept_list_reassign.c
@@ -17,7 +17,7 @@
 char FAST_FUNC filter_accept_list_reassign(archive_handle_t *archive_handle)
 {
        /* Check the file entry is in the accept list */
-       if (find_list_entry(archive_handle->accept, 
archive_handle->file_header->name)) {
+       if (find_list_entry((llist_t *)archive_handle->accept, 
archive_handle->file_header->name)) {
                const char *name_ptr;
 
                /* Find extension */
diff --git a/archival/libarchive/filter_accept_reject_list.c 
b/archival/libarchive/filter_accept_reject_list.c
index 939e626fa..935e60c29 100644
--- a/archival/libarchive/filter_accept_reject_list.c
+++ b/archival/libarchive/filter_accept_reject_list.c
@@ -14,7 +14,7 @@ char FAST_FUNC filter_accept_reject_list(archive_handle_t 
*archive_handle)
 {
        const char *key;
        const llist_t *reject_entry;
-       const llist_t *accept_entry;
+       accept_llist_t *accept_entry;
 
        key = archive_handle->file_header->name;
 
@@ -26,10 +26,24 @@ char FAST_FUNC filter_accept_reject_list(archive_handle_t 
*archive_handle)
 
        /* Fail if an accept list was specified and the key wasnt in there */
        if (archive_handle->accept) {
-               accept_entry = find_list_entry2(archive_handle->accept, key);
+               accept_entry = (accept_llist_t *)find_list_entry2((llist_t 
*)archive_handle->accept, key);
                if (!accept_entry) {
                        return EXIT_FAILURE;
                }
+
+               /* Mark the file as seen */
+               accept_entry->tar__seen_count++;
+
+#if ENABLE_FEATURE_TAR_LONG_OPTIONS
+               /* Support tar --occurrence */
+               if (archive_handle->tar__occurrence) {
+                       if (accept_entry->tar__seen_count == 
archive_handle->tar__occurrence) {
+                               archive_handle->tar__occurrence_remaining--;
+                       } else {
+                               return EXIT_FAILURE;
+                       }
+               }
+#endif
        }
 
        /* Accepted */
diff --git a/archival/libarchive/get_header_tar.c 
b/archival/libarchive/get_header_tar.c
index cc6f3f0ad..9ae190609 100644
--- a/archival/libarchive/get_header_tar.c
+++ b/archival/libarchive/get_header_tar.c
@@ -176,6 +176,13 @@ char FAST_FUNC get_header_tar(archive_handle_t 
*archive_handle)
 # define p_linkname 0
 #endif
 
+#if ENABLE_FEATURE_TAR_LONG_OPTIONS
+       if (archive_handle->tar__occurrence && 
archive_handle->tar__occurrence_remaining == 0) {
+               /* We've found all of the occurrences we were looking for, 
signal end of archive */
+               return EXIT_FAILURE;
+       }
+#endif
+
 #if ENABLE_FEATURE_TAR_GNU_EXTENSIONS || ENABLE_FEATURE_TAR_SELINUX
  again:
 #endif
diff --git a/archival/tar.c b/archival/tar.c
index d6ca6c1e0..7aa393dc2 100644
--- a/archival/tar.c
+++ b/archival/tar.c
@@ -655,7 +655,7 @@ static void NOINLINE vfork_compressor(int tar_fd, const 
char *gzip)
 static NOINLINE int writeTarFile(
        struct TarBallInfo *tbInfo,
        int recurseFlags,
-       const llist_t *filelist,
+       const accept_llist_t *filelist,
        const char *gzip)
 {
        int errorFlag = FALSE;
@@ -715,9 +715,9 @@ static NOINLINE int writeTarFile(
 #endif /* FEATURE_TAR_CREATE */
 
 #if ENABLE_FEATURE_TAR_FROM
-static llist_t *append_file_list_to_list(llist_t *list)
+static accept_llist_t *append_file_list_to_list(llist_t *list)
 {
-       llist_t *newlist = NULL;
+       accept_llist_t *newlist = NULL;
 
        while (list) {
                FILE *src_stream;
@@ -729,7 +729,7 @@ static llist_t *append_file_list_to_list(llist_t *list)
                        char *cp = last_char_is(line, '/');
                        if (cp > line)
                                *cp = '\0';
-                       llist_add_to_end(&newlist, line);
+                       accept_add_to_end(&newlist, line);
                }
                fclose(src_stream);
        }
@@ -799,6 +799,7 @@ static llist_t *append_file_list_to_list(llist_t *list)
 //usage:       )
 //usage:       )
 //usage:       IF_FEATURE_TAR_LONG_OPTIONS(
+//usage:     "\n       --occurrence [NUM]      Exit after NUM (default 1) 
occurrences"
 //usage:     "\n       --overwrite             Replace existing files"
 //usage:     "\n       --strip-components NUM  NUM of leading components to 
strip"
 //usage:     "\n       --no-recursion          Don't descend in directories"
@@ -826,6 +827,7 @@ enum {
        OPTBIT_AUTOCOMPRESS_BY_EXT,
        IF_FEATURE_TAR_NOPRESERVE_TIME(OPTBIT_NOPRESERVE_TIME,)
 #if ENABLE_FEATURE_TAR_LONG_OPTIONS
+       OPTBIT_OCCURRENCE,
        OPTBIT_STRIP_COMPONENTS,
        IF_FEATURE_SEAMLESS_LZMA(OPTBIT_LZMA        ,)
        OPTBIT_NORECURSION,
@@ -853,6 +855,7 @@ enum {
        OPT_COMPRESS     = IF_FEATURE_SEAMLESS_Z(   (1 << OPTBIT_COMPRESS    )) 
+ 0, // Z
        OPT_AUTOCOMPRESS_BY_EXT = 1 << OPTBIT_AUTOCOMPRESS_BY_EXT,              
     // a
        OPT_NOPRESERVE_TIME  = IF_FEATURE_TAR_NOPRESERVE_TIME((1 << 
OPTBIT_NOPRESERVE_TIME)) + 0, // m
+       OPT_OCCURRENCE       = IF_FEATURE_TAR_LONG_OPTIONS((1 << 
OPTBIT_OCCURRENCE      )) + 0, // occurrence
        OPT_STRIP_COMPONENTS = IF_FEATURE_TAR_LONG_OPTIONS((1 << 
OPTBIT_STRIP_COMPONENTS)) + 0, // strip-components
        OPT_LZMA             = 
IF_FEATURE_TAR_LONG_OPTIONS(IF_FEATURE_SEAMLESS_LZMA((1 << OPTBIT_LZMA))) + 0, 
// lzma
        OPT_NORECURSION      = IF_FEATURE_TAR_LONG_OPTIONS((1 << 
OPTBIT_NORECURSION    )) + 0, // no-recursion
@@ -901,6 +904,7 @@ static const char tar_longopts[] ALIGN1 =
 # if ENABLE_FEATURE_TAR_NOPRESERVE_TIME
        "touch\0"               No_argument       "m"
 # endif
+       "occurrence\0"          Optional_argument "\xf7"
        "strip-components\0"    Required_argument "\xf8"
 # if ENABLE_FEATURE_SEAMLESS_LZMA
        "lzma\0"                No_argument       "\xf9"
@@ -936,6 +940,9 @@ int tar_main(int argc UNUSED_PARAM, char **argv)
        const char *tar_filename = "-";
        unsigned opt;
        int verboseFlag = 0;
+#if ENABLE_FEATURE_TAR_FROM
+       llist_t *accept = NULL, *reject = NULL;
+#endif
 #if ENABLE_FEATURE_TAR_LONG_OPTIONS && ENABLE_FEATURE_TAR_FROM
        llist_t *excludes = NULL;
 #endif
@@ -999,6 +1006,7 @@ int tar_main(int argc UNUSED_PARAM, char **argv)
                IF_FEATURE_SEAMLESS_Z(   "Z"     )
                "a"
                IF_FEATURE_TAR_NOPRESERVE_TIME("m")
+               IF_FEATURE_TAR_LONG_OPTIONS("\xf7:") // --occurrence
                IF_FEATURE_TAR_LONG_OPTIONS("\xf8:") // --strip-components
                "\0"
                "tt:vv:" // count -t,-v
@@ -1009,14 +1017,16 @@ int tar_main(int argc UNUSED_PARAM, char **argv)
                IF_FEATURE_TAR_CREATE("c--tx:t--cx:x--ct") // mutually exclusive
                IF_NOT_FEATURE_TAR_CREATE("t--x:x--t") // mutually exclusive
 #if ENABLE_FEATURE_TAR_LONG_OPTIONS
+               ":\xf7+" // --occurrence[=NUM]
                ":\xf8+" // --strip-components=NUM
 #endif
                LONGOPTS
                , &base_dir // -C dir
                , &tar_filename // -f filename
-               IF_FEATURE_TAR_FROM(, &(tar_handle->accept)) // T
-               IF_FEATURE_TAR_FROM(, &(tar_handle->reject)) // X
+               IF_FEATURE_TAR_FROM(, &accept) // T
+               IF_FEATURE_TAR_FROM(, &reject) // X
 #if ENABLE_FEATURE_TAR_LONG_OPTIONS
+               , &tar_handle->tar__occurrence       // --occurrence
                , &tar_handle->tar__strip_components // --strip-components
 #endif
                IF_FEATURE_TAR_TO_COMMAND(, &(tar_handle->tar__to_command)) // 
--to-command
@@ -1061,6 +1071,7 @@ int tar_main(int argc UNUSED_PARAM, char **argv)
        bb_error_msg("verboseFlag:%d", verboseFlag);
        bb_error_msg("tar_handle->tar__to_command:'%s'", 
tar_handle->tar__to_command);
        bb_error_msg("tar_handle->tar__strip_components:%u", 
tar_handle->tar__strip_components);
+       bb_error_msg("tar_handle->tar__occurrence:%u", 
tar_handle->tar__occurrence);
        return 0;
 # undef showopt
 #endif
@@ -1106,7 +1117,7 @@ int tar_main(int argc UNUSED_PARAM, char **argv)
 
 #if ENABLE_FEATURE_TAR_FROM
        /* Convert each -X EXCLFILE to list of to-be-rejected glob patterns */
-       tar_handle->reject = append_file_list_to_list(tar_handle->reject);
+       tar_handle->reject = (llist_t *)append_file_list_to_list(reject);
 # if ENABLE_FEATURE_TAR_LONG_OPTIONS
        /* Append --exclude=GLOBPATTERNs to reject */
        if (excludes) {
@@ -1116,7 +1127,7 @@ int tar_main(int argc UNUSED_PARAM, char **argv)
                *p2next = excludes;
        }
 # endif
-       tar_handle->accept = append_file_list_to_list(tar_handle->accept);
+       tar_handle->accept = append_file_list_to_list(accept);
 #endif
 
        /* Setup an array of filenames to work with */
@@ -1126,13 +1137,26 @@ int tar_main(int argc UNUSED_PARAM, char **argv)
                char *cp = last_char_is(*argv, '/');
                if (cp > *argv)
                        *cp = '\0';
-               llist_add_to_end(&tar_handle->accept, *argv);
+               accept_add_to_end(&tar_handle->accept, *argv);
                argv++;
        }
 
        if (tar_handle->accept || tar_handle->reject)
                tar_handle->filter = filter_accept_reject_list;
 
+#if ENABLE_FEATURE_TAR_LONG_OPTIONS
+       if (tar_handle->tar__occurrence) {
+               accept_llist_t *a = tar_handle->accept;
+
+               if (opt & OPT_CREATE)
+                       bb_simple_error_msg_and_die("--occurrence cannot be 
used with -c");
+               if (!a)
+                       bb_simple_error_msg_and_die("--occurrence requires a 
file list");
+               for (; a; a = a->link)
+                       tar_handle->tar__occurrence_remaining++;
+       }
+#endif
+
        /* Open the tar file */
        {
                int tar_fd = STDIN_FILENO;
@@ -1265,14 +1289,21 @@ int tar_main(int argc UNUSED_PARAM, char **argv)
        create_links_from_list(tar_handle->link_placeholders);
 
        /* Check that every file that should have been extracted was */
-       while (tar_handle->accept) {
-               if (!find_list_entry(tar_handle->reject, 
tar_handle->accept->data)
-                && !find_list_entry(tar_handle->passed, 
tar_handle->accept->data)
-               ) {
+       for (; tar_handle->accept; tar_handle->accept = 
tar_handle->accept->link) {
+               if (find_list_entry(tar_handle->reject, 
tar_handle->accept->data)) {
+                       continue;
+               }
+               if (tar_handle->accept->tar__seen_count == 0) {
                        bb_error_msg_and_die("%s: not found in archive",
                                tar_handle->accept->data);
                }
-               tar_handle->accept = tar_handle->accept->link;
+#if ENABLE_FEATURE_TAR_LONG_OPTIONS
+               if (tar_handle->tar__occurrence
+                && tar_handle->accept->tar__seen_count < 
tar_handle->tar__occurrence) {
+                       bb_error_msg_and_die("%s: required occurrence not found 
in archive",
+                               tar_handle->accept->data);
+               }
+#endif
        }
        if (ENABLE_FEATURE_CLEAN_UP /* && tar_handle->src_fd != STDIN_FILENO */)
                close(tar_handle->src_fd);
diff --git a/include/bb_archive.h b/include/bb_archive.h
index e0ef8fc4e..74b55236f 100644
--- a/include/bb_archive.h
+++ b/include/bb_archive.h
@@ -43,6 +43,13 @@ typedef struct file_header_t {
        dev_t device;
 } file_header_t;
 
+typedef struct accept_llist_t {
+       /* link/data must be first: this struct needs to be llist-compatible */
+       struct accept_llist_t *link;
+       char *data;
+       unsigned tar__seen_count;
+} accept_llist_t;
+
 struct hardlinks_t;
 
 typedef struct archive_handle_t {
@@ -55,7 +62,7 @@ typedef struct archive_handle_t {
        /* Define if the header and data component should be processed */
        char FAST_FUNC (*filter)(struct archive_handle_t *);
        /* List of files that have been accepted */
-       llist_t *accept;
+       accept_llist_t *accept;
        /* List of files that have been rejected */
        llist_t *reject;
        /* List of files that have successfully been worked on */
@@ -82,6 +89,8 @@ typedef struct archive_handle_t {
        /* Archiver specific. Can make it a union if it ever gets big */
 #if ENABLE_FEATURE_TAR_LONG_OPTIONS
        unsigned tar__strip_components;
+       unsigned tar__occurrence;
+       unsigned tar__occurrence_remaining;
 #endif
 #define PAX_NEXT_FILE 0
 #define PAX_GLOBAL    1
@@ -175,6 +184,9 @@ extern const char cpio_TRAILER[];
 
 archive_handle_t *init_handle(void) FAST_FUNC;
 
+void accept_add_to(accept_llist_t **old_head, char *data) FAST_FUNC;
+void accept_add_to_end(accept_llist_t **list_head, char *data) FAST_FUNC;
+
 char filter_accept_all(archive_handle_t *archive_handle) FAST_FUNC;
 char filter_accept_list(archive_handle_t *archive_handle) FAST_FUNC;
 char filter_accept_list_reassign(archive_handle_t *archive_handle) FAST_FUNC;
diff --git a/testsuite/tar/tar-handles-occurrence 
b/testsuite/tar/tar-handles-occurrence
new file mode 100644
index 000000000..4d7a8c5db
--- /dev/null
+++ b/testsuite/tar/tar-handles-occurrence
@@ -0,0 +1,18 @@
+# FEATURE: CONFIG_FEATURE_TAR_LONG_OPTIONS
+
+echo one > test.txt
+busybox tar -cf one.tar test.txt
+
+echo two > test.txt
+busybox tar -cf two.tar test.txt
+
+(head -c 1024 one.tar; head -c 1024 two.tar) > combined.tar
+
+data=$(busybox tar -xO --occurrence=1 test.txt < combined.tar)
+test "$data" = "one"
+
+data=$(busybox tar -xO --occurrence=2 test.txt < combined.tar)
+test "$data" = "two"
+
+data=$(busybox tar -xO --occurrence=3 test.txt < combined.tar) && exit 1
+test "$data" = ""
-- 
2.39.2

_______________________________________________
busybox mailing list
busybox@busybox.net
http://lists.busybox.net/mailman/listinfo/busybox

Reply via email to