From 27ca5119f754d2d359a3cf4ac66c6672260a74d3 Mon Sep 17 00:00:00 2001 From: Martin Matuska Date: Mon, 31 Jul 2023 11:51:35 +0200 Subject: [PATCH] unzip: use libarchive-style getopt() implementation --- CMakeLists.txt | 5 - Makefile.am | 4 +- build/cmake/config.h.in | 6 - configure.ac | 7 +- unzip/CMakeLists.txt | 2 + unzip/bsdunzip.c | 56 ++++----- unzip/bsdunzip.h | 63 ++++++++++ unzip/cmdline.c | 250 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 348 insertions(+), 45 deletions(-) create mode 100644 unzip/bsdunzip.h create mode 100644 unzip/cmdline.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e2fec82e..dc1fcc72a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 \n#include \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, diff --git a/Makefile.am b/Makefile.am index 410659b61..83bbeee0b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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= \ diff --git a/build/cmake/config.h.in b/build/cmake/config.h.in index 570afb693..08a3196f2 100644 --- a/build/cmake/config.h.in +++ b/build/cmake/config.h.in @@ -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 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 diff --git a/configure.ac b/configure.ac index 8a3aebcef..4a4bdd628 100644 --- a/configure.ac +++ b/configure.ac @@ -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 ]) - # Check for Extended Attributes support AC_ARG_ENABLE([xattr], AS_HELP_STRING([--disable-xattr], diff --git a/unzip/CMakeLists.txt b/unzip/CMakeLists.txt index 0e3de3523..150eb2c6b 100644 --- a/unzip/CMakeLists.txt +++ b/unzip/CMakeLists.txt @@ -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 diff --git a/unzip/bsdunzip.c b/unzip/bsdunzip.c index 83c71a2a9..b8f6ff4bb 100644 --- a/unzip/bsdunzip.c +++ b/unzip/bsdunzip.c @@ -59,9 +59,6 @@ #ifdef HAVE_FNMATCH_H #include #endif -#ifdef HAVE_GETOPT_H -#include -#endif #ifdef HAVE_STDARG_H #include #endif @@ -82,8 +79,7 @@ #endif #endif -#include -#include +#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 index 000000000..12b65cefb --- /dev/null +++ b/unzip/bsdunzip.h @@ -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 +#include + +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 index 000000000..95d4f99b8 --- /dev/null +++ b/unzip/cmdline.c @@ -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 +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#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); +} -- 2.47.2