]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
unzip: use libarchive-style getopt() implementation
authorMartin Matuska <martin@matuska.de>
Mon, 31 Jul 2023 09:51:35 +0000 (11:51 +0200)
committerMartin Matuska <martin@matuska.de>
Mon, 31 Jul 2023 09:59:39 +0000 (11:59 +0200)
CMakeLists.txt
Makefile.am
build/cmake/config.h.in
configure.ac
unzip/CMakeLists.txt
unzip/bsdunzip.c
unzip/bsdunzip.h [new file with mode: 0644]
unzip/cmdline.c [new file with mode: 0644]

index 4e2fec82e0d123dbb28f1d7d262064ee0edb16b3..dc1fcc72a7fc21ec38ddd1a0085f0d04403ac44c 100644 (file)
@@ -696,7 +696,6 @@ int main(void) { return EXT2_IOC_GETFLAGS; }" HAVE_WORKING_EXT2_IOC_GETFLAGS)
 
 LA_CHECK_INCLUDE_FILE("fcntl.h" HAVE_FCNTL_H)
 LA_CHECK_INCLUDE_FILE("fnmatch.h" HAVE_FNMATCH_H)
-LA_CHECK_INCLUDE_FILE("getopt.h" HAVE_GETOPT_H)
 LA_CHECK_INCLUDE_FILE("grp.h" HAVE_GRP_H)
 LA_CHECK_INCLUDE_FILE("inttypes.h" HAVE_INTTYPES_H)
 LA_CHECK_INCLUDE_FILE("io.h" HAVE_IO_H)
@@ -1489,10 +1488,6 @@ CHECK_C_SOURCE_COMPILES(
   "#include <fcntl.h>\n#include <unistd.h>\nint main() {char buf[10]; return readlinkat(AT_FDCWD, \"\", buf, 0);}"
   HAVE_READLINKAT)
 
-# Check for getopt uses optreset to reset
-CHECK_SYMBOL_EXISTS(optreset "getopt.h" HAVE_GETOPT_OPTRESET)
-
-
 # To verify major(), we need to both include the header
 # of interest and verify that the result can be linked.
 # CHECK_FUNCTION_EXISTS doesn't accept a header argument,
index 410659b61bbe2ece7484a54042d8f2615cb2e3e8..83bbeee0b7e9cdd9ebb8c37c047d245936427502 100644 (file)
@@ -1451,7 +1451,9 @@ bsdcat_test_EXTRA_DIST= \
 
 bsdunzip_SOURCES= \
                unzip/bsdunzip.c \
+               unzip/bsdunzip.h \
                unzip/bsdunzip_platform.h \
+               unzip/cmdline.c \
                unzip/la_getline.c \
                unzip/la_queue.h
 
@@ -1489,7 +1491,7 @@ bsdunzip_programs=
 endif
 
 #
-# bsdcat_test
+# bsdunzip_test
 #
 
 bsdunzip_test_SOURCES= \
index 570afb693bd6cef4d8de0b83143001614b9d3637..08a3196f2a6ded9ba45febe8c02f1d56f299470d 100644 (file)
@@ -648,12 +648,6 @@ typedef uint64_t uintmax_t;
 /* Define to 1 if you have the `getline' function. */
 #cmakedefine HAVE_GETLINE 1
 
-/* Define to 1 if you have the <getopt.h> header file. */
-#cmakedefine HAVE_GETOPT_H 1
-
-/* Define to 1 if platform uses `optreset` to reset `getopt` */
-#cmakedefine HAVE_GETOPT_OPTRESET 1
-
 /* Define to 1 if you have the `getpid' function. */
 #cmakedefine HAVE_GETPID 1
 
index 8a3aebcef8ec4a90799b27e3ec2f3f871a23a920..4a4bdd628639a033086bea1d6dc764a7add450b4 100644 (file)
@@ -329,7 +329,7 @@ AC_HEADER_DIRENT
 AC_HEADER_SYS_WAIT
 AC_CHECK_HEADERS([acl/libacl.h attr/xattr.h])
 AC_CHECK_HEADERS([copyfile.h ctype.h])
-AC_CHECK_HEADERS([errno.h ext2fs/ext2_fs.h fcntl.h fnmatch.h getopt.h grp.h])
+AC_CHECK_HEADERS([errno.h ext2fs/ext2_fs.h fcntl.h fnmatch.h grp.h])
 
 AC_CACHE_CHECK([whether EXT2_IOC_GETFLAGS is usable],
     [ac_cv_have_decl_EXT2_IOC_GETFLAGS],
@@ -837,11 +837,6 @@ AC_CHECK_MEMBER(struct dirent.d_namlen,,,
 #endif
 ])
 
-AC_CHECK_DECL([optreset],
-               [AC_DEFINE(HAVE_GETOPT_OPTRESET, 1, [Platform uses optreset to reset getopt])],
-               [],
-               [#include <getopt.h>])
-
 # Check for Extended Attributes support
 AC_ARG_ENABLE([xattr],
                AS_HELP_STRING([--disable-xattr],
index 0e3de35231df0a5d77d08a8b948af22992c0e162..150eb2c6b05d48a5f417484719d888d88dc6650f 100644 (file)
@@ -7,7 +7,9 @@ IF(ENABLE_UNZIP)
 
   SET(bsdunzip_SOURCES
     bsdunzip.c
+    bsdunzip.h
     bsdunzip_platform.h
+    cmdline.c
     la_getline.c
     la_queue.h
     ../libarchive_fe/err.c
index 83c71a2a9bc6701d328bb45393b1d0a015e559d9..b8f6ff4bbf95ffd7562903a46db611678dc51f21 100644 (file)
@@ -59,9 +59,6 @@
 #ifdef HAVE_FNMATCH_H
 #include <fnmatch.h>
 #endif
-#ifdef HAVE_GETOPT_H
-#include <getopt.h>
-#endif
 #ifdef HAVE_STDARG_H
 #include <stdarg.h>
 #endif
@@ -82,8 +79,7 @@
 #endif
 #endif
 
-#include <archive.h>
-#include <archive_entry.h>
+#include "bsdunzip.h"
 #include "passphrase.h"
 #include "err.h"
 
@@ -93,13 +89,13 @@ static int           C_opt;         /* match case-insensitively */
 static int              c_opt;         /* extract to stdout */
 static const char      *d_arg;         /* directory */
 static int              f_opt;         /* update existing files only */
-static char            *O_arg;         /* encoding */
+static const char      *O_arg;         /* encoding */
 static int              j_opt;         /* junk directories */
 static int              L_opt;         /* lowercase names */
 static int              n_opt;         /* never overwrite */
 static int              o_opt;         /* always overwrite */
 static int              p_opt;         /* extract to stdout, quiet */
-static char            *P_arg;         /* passphrase */
+static const char      *P_arg;         /* passphrase */
 static int              q_opt;         /* quiet */
 static int              t_opt;         /* test */
 static int              u_opt;         /* update */
@@ -117,6 +113,8 @@ static int           zipinfo_mode;
 /* running on tty? */
 static int              tty;
 
+int bsdunzip_optind;
+
 /* convenience macro */
 /* XXX should differentiate between ARCHIVE_{WARN,FAIL,RETRY} */
 #define ac(call)                                               \
@@ -1105,25 +1103,18 @@ version(void)
 static int
 getopts(int argc, char *argv[])
 {
+       struct bsdunzip *bsdunzip, bsdunzip_storage;
        int opt;
+       bsdunzip_optind = 1;
 
-       static struct option longopts[] = {
-           { "version", no_argument, &version_opt, 1 },
-           { 0, 0, 0, 0}
-       };
+       bsdunzip = &bsdunzip_storage;
+       memset(bsdunzip, 0, sizeof(*bsdunzip));
 
-       optind = 1;
-#ifdef HAVE_GETOPT_OPTRESET
-       optreset = 1;
-#endif
-       while ((opt = getopt_long(argc, argv,
-           "aCcd:fI:jLlnO:opP:qtuvx:yZ1", longopts, NULL)) != -1) {
+        bsdunzip->argv = argv;
+        bsdunzip->argc = argc;
+
+       while ((opt = bsdunzip_getopt(bsdunzip)) != -1) {
                switch (opt) {
-               case 0:
-                       break;
-               case '1':
-                       Z1_opt = 1;
-                       break;
                case 'a':
                        a_opt = 1;
                        break;
@@ -1134,14 +1125,14 @@ getopts(int argc, char *argv[])
                        c_opt = 1;
                        break;
                case 'd':
-                       d_arg = optarg;
+                       d_arg = bsdunzip->argument;
                        break;
                case 'f':
                        f_opt = 1;
                        break;
                case 'I':
                case 'O':
-                       O_arg = optarg;
+                       O_arg = bsdunzip->argument;
                        break;
                case 'j':
                        j_opt = 1;
@@ -1164,7 +1155,7 @@ getopts(int argc, char *argv[])
                        p_opt = 1;
                        break;
                case 'P':
-                       P_arg = optarg;
+                       P_arg = bsdunzip->argument;
                        break;
                case 'q':
                        q_opt = 1;
@@ -1179,19 +1170,30 @@ getopts(int argc, char *argv[])
                        v_opt = 2;
                        break;
                case 'x':
-                       add_pattern(&exclude, optarg);
+                       add_pattern(&exclude, bsdunzip->argument);
                        break;
                case 'y':
                        y_str = "  ";
                        break;
                case 'Z':
                        zipinfo_mode = 1;
+                       if (bsdunzip->argument != NULL &&
+                           strcmp(bsdunzip->argument, "1") == 0) {
+                               Z1_opt = 1;
+                       }
+                       break;
+               case OPTION_VERSION:
+                       version_opt = 1;
+                       break;
+               case OPTION_NONE:
                        break;
                default:
                        usage();
                }
+               if (opt == OPTION_NONE)
+                       break;
        }
-       return (optind);
+       return (bsdunzip_optind);
 }
 
 int
diff --git a/unzip/bsdunzip.h b/unzip/bsdunzip.h
new file mode 100644 (file)
index 0000000..12b65ce
--- /dev/null
@@ -0,0 +1,63 @@
+/*-
+ * Copyright (c) 2023, Martin Matuska
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BSDUNZIP_H_INCLUDED
+#define BSDUNZIP_H_INCLUDED
+
+#if defined(PLATFORM_CONFIG_H)
+/* Use hand-built config.h in environments that need it. */
+#include PLATFORM_CONFIG_H
+#else
+/* Not having a config.h of some sort is a serious problem. */
+#include "config.h"
+#endif
+
+#include <archive.h>
+#include <archive_entry.h>
+
+struct bsdunzip {
+       /* Option parser state */
+       int               getopt_state;
+       char             *getopt_word;
+
+       /* Miscellaneous state information */
+       int               argc;
+       char            **argv;
+       const char       *argument;
+};
+
+struct bsdunzip_getopt_ret {
+       int             index;
+       int             opt;
+};
+
+enum {
+       OPTION_NONE,
+       OPTION_VERSION
+};
+
+int bsdunzip_getopt(struct bsdunzip *);
+
+#endif
diff --git a/unzip/cmdline.c b/unzip/cmdline.c
new file mode 100644 (file)
index 0000000..95d4f99
--- /dev/null
@@ -0,0 +1,250 @@
+/*-
+ * Copyright (c) 2003-2008 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Command line parser for bsdunzip.
+ */
+
+#include "bsdunzip_platform.h"
+__FBSDID("$FreeBSD$");
+
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+
+#include "bsdunzip.h"
+#include "err.h"
+
+extern int bsdunzip_optind;
+
+/*
+ * Short options for bsdunzip.  Please keep this sorted.
+ */
+static const char *short_options
+       = "aCcd:fI:jLlnO:opP:qtuvx:yZ:";
+
+/*
+ * Long options for bsdunzip.  Please keep this list sorted.
+ *
+ * The symbolic names for options that lack a short equivalent are
+ * defined in bsdunzip.h.  Also note that so far I've found no need
+ * to support optional arguments to long options.  That would be
+ * a small change to the code below.
+ */
+
+static const struct bsdunzip_option {
+       const char *name;
+       int required;      /* 1 if this option requires an argument. */
+       int equivalent;    /* Equivalent short option. */
+} bsdunzip_longopts[] = {
+       { "version", 0, OPTION_VERSION },
+       { NULL, 0, 0 }
+};
+
+/*
+ * This getopt implementation has two key features that common
+ * getopt_long() implementations lack.  Apart from those, it's a
+ * straightforward option parser, considerably simplified by not
+ * needing to support the wealth of exotic getopt_long() features.  It
+ * has, of course, been shamelessly tailored for bsdunzip.  (If you're
+ * looking for a generic getopt_long() implementation for your
+ * project, I recommend Gregory Pietsch's public domain getopt_long()
+ * implementation.)  The two additional features are:
+ */
+
+int
+bsdunzip_getopt(struct bsdunzip *bsdunzip)
+{
+       enum { state_start = 0, state_next_word, state_short, state_long };
+
+       const struct bsdunzip_option *popt, *match = NULL, *match2 = NULL;
+       const char *p, *long_prefix = "--";
+       size_t optlength;
+       int opt = OPTION_NONE;
+       int required = 0;
+
+       bsdunzip->argument = NULL;
+
+       /* First time through, initialize everything. */
+       if (bsdunzip->getopt_state == state_start) {
+               /* Skip program name. */
+               ++bsdunzip->argv;
+               --bsdunzip->argc;
+               if (*bsdunzip->argv == NULL)
+                       return (-1);
+               bsdunzip->getopt_state = state_next_word;
+       }
+
+       /*
+        * We're ready to look at the next word in argv.
+        */
+       if (bsdunzip->getopt_state == state_next_word) {
+               /* No more arguments, so no more options. */
+               if (bsdunzip->argv[0] == NULL)
+                       return (-1);
+               /* Doesn't start with '-', so no more options. */
+               if (bsdunzip->argv[0][0] != '-')
+                       return (-1);
+               /* "--" marks end of options; consume it and return. */
+               if (strcmp(bsdunzip->argv[0], "--") == 0) {
+                       ++bsdunzip->argv;
+                       --bsdunzip->argc;
+                       return (-1);
+               }
+               /* Get next word for parsing. */
+               bsdunzip->getopt_word = *bsdunzip->argv++;
+               --bsdunzip->argc;
+               bsdunzip_optind++;
+               if (bsdunzip->getopt_word[1] == '-') {
+                       /* Set up long option parser. */
+                       bsdunzip->getopt_state = state_long;
+                       bsdunzip->getopt_word += 2; /* Skip leading '--' */
+               } else {
+                       /* Set up short option parser. */
+                       bsdunzip->getopt_state = state_short;
+                       ++bsdunzip->getopt_word;  /* Skip leading '-' */
+               }
+       }
+
+       /*
+        * We're parsing a group of POSIX-style single-character options.
+        */
+       if (bsdunzip->getopt_state == state_short) {
+               /* Peel next option off of a group of short options. */
+               opt = *bsdunzip->getopt_word++;
+               if (opt == '\0') {
+                       /* End of this group; recurse to get next option. */
+                       bsdunzip->getopt_state = state_next_word;
+                       return bsdunzip_getopt(bsdunzip);
+               }
+
+               /* Does this option take an argument? */
+               p = strchr(short_options, opt);
+               if (p == NULL)
+                       return ('?');
+               if (p[1] == ':')
+                       required = 1;
+
+               /* If it takes an argument, parse that. */
+               if (required) {
+                       /* If arg is run-in, bsdunzip->getopt_word already points to it. */
+                       if (bsdunzip->getopt_word[0] == '\0') {
+                               /* Otherwise, pick up the next word. */
+                               bsdunzip->getopt_word = *bsdunzip->argv;
+                               if (bsdunzip->getopt_word == NULL) {
+                                       lafe_warnc(0,
+                                           "Option -%c requires an argument",
+                                           opt);
+                                       return ('?');
+                               }
+                               ++bsdunzip->argv;
+                               --bsdunzip->argc;
+                               bsdunzip_optind++;
+                       }
+                       bsdunzip->getopt_state = state_next_word;
+                       bsdunzip->argument = bsdunzip->getopt_word;
+               }
+       }
+
+       /* We're reading a long option */
+       if (bsdunzip->getopt_state == state_long) {
+               /* After this long option, we'll be starting a new word. */
+               bsdunzip->getopt_state = state_next_word;
+
+               /* Option name ends at '=' if there is one. */
+               p = strchr(bsdunzip->getopt_word, '=');
+               if (p != NULL) {
+                       optlength = (size_t)(p - bsdunzip->getopt_word);
+                       bsdunzip->argument = (char *)(uintptr_t)(p + 1);
+               } else {
+                       optlength = strlen(bsdunzip->getopt_word);
+               }
+
+               /* Search the table for an unambiguous match. */
+               for (popt = bsdunzip_longopts; popt->name != NULL; popt++) {
+                       /* Short-circuit if first chars don't match. */
+                       if (popt->name[0] != bsdunzip->getopt_word[0])
+                               continue;
+                       /* If option is a prefix of name in table, record it.*/
+                       if (strncmp(bsdunzip->getopt_word, popt->name, optlength) == 0) {
+                               match2 = match; /* Record up to two matches. */
+                               match = popt;
+                               /* If it's an exact match, we're done. */
+                               if (strlen(popt->name) == optlength) {
+                                       match2 = NULL; /* Forget the others. */
+                                       break;
+                               }
+                       }
+               }
+
+               /* Fail if there wasn't a unique match. */
+               if (match == NULL) {
+                       lafe_warnc(0,
+                           "Option %s%s is not supported",
+                           long_prefix, bsdunzip->getopt_word);
+                       return ('?');
+               }
+               if (match2 != NULL) {
+                       lafe_warnc(0,
+                           "Ambiguous option %s%s (matches --%s and --%s)",
+                           long_prefix, bsdunzip->getopt_word, match->name, match2->name);
+                       return ('?');
+               }
+
+               /* We've found a unique match; does it need an argument? */
+               if (match->required) {
+                       /* Argument required: get next word if necessary. */
+                       if (bsdunzip->argument == NULL) {
+                               bsdunzip->argument = *bsdunzip->argv;
+                               if (bsdunzip->argument == NULL) {
+                                       lafe_warnc(0,
+                                           "Option %s%s requires an argument",
+                                           long_prefix, match->name);
+                                       return ('?');
+                               }
+                               ++bsdunzip->argv;
+                               --bsdunzip->argc;
+                               bsdunzip_optind++;
+                       }
+               } else {
+                       /* Argument forbidden: fail if there is one. */
+                       if (bsdunzip->argument != NULL) {
+                               lafe_warnc(0,
+                                   "Option %s%s does not allow an argument",
+                                   long_prefix, match->name);
+                               return ('?');
+                       }
+               }
+               return (match->equivalent);
+       }
+
+       return (opt);
+}