1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2003-2008 Tim Kientzle
5 * All rights reserved.
6 */
7
8 /*
9 * Command line parser for bsdunzip.
10 */
11
12 #include "bsdunzip_platform.h"
13 #ifdef HAVE_ERRNO_H
14 #include <errno.h>
15 #endif
16 #ifdef HAVE_STDLIB_H
17 #include <stdlib.h>
18 #endif
19 #ifdef HAVE_STRING_H
20 #include <string.h>
21 #endif
22
23 #include "bsdunzip.h"
24 #include "err.h"
25
26 /*
27 * Short options for bsdunzip. Please keep this sorted.
28 */
29 static const char *short_options
30 = "aCcd:fI:jLlnO:opP:qtuvx:yZ:";
31
32 /*
33 * Long options for bsdunzip. Please keep this list sorted.
34 *
35 * The symbolic names for options that lack a short equivalent are
36 * defined in bsdunzip.h. Also note that so far I've found no need
37 * to support optional arguments to long options. That would be
38 * a small change to the code below.
39 */
40
41 static const struct bsdunzip_option {
42 const char *name;
43 int required; /* 1 if this option requires an argument. */
44 int equivalent; /* Equivalent short option. */
45 } bsdunzip_longopts[] = {
46 { "version", 0, OPTION_VERSION },
47 { NULL, 0, 0 }
48 };
49
50 /*
51 * This getopt implementation has two key features that common
52 * getopt_long() implementations lack. Apart from those, it's a
53 * straightforward option parser, considerably simplified by not
54 * needing to support the wealth of exotic getopt_long() features. It
55 * has, of course, been shamelessly tailored for bsdunzip. (If you're
56 * looking for a generic getopt_long() implementation for your
57 * project, I recommend Gregory Pietsch's public domain getopt_long()
58 * implementation.) The two additional features are:
59 */
60
61 int
bsdunzip_getopt(struct bsdunzip * bsdunzip)62 bsdunzip_getopt(struct bsdunzip *bsdunzip)
63 {
64 enum { state_start = 0, state_next_word, state_short, state_long };
65
66 const struct bsdunzip_option *popt, *match, *match2;
67 const char *p, *long_prefix;
68 size_t optlength;
69 int opt;
70 int required;
71
72 again:
73 match = NULL;
74 match2 = NULL;
75 long_prefix = "--";
76 opt = OPTION_NONE;
77 required = 0;
78 bsdunzip->argument = NULL;
79
80 /* First time through, initialize everything. */
81 if (bsdunzip->getopt_state == state_start) {
82 /* Skip program name. */
83 ++bsdunzip->argv;
84 --bsdunzip->argc;
85 if (*bsdunzip->argv == NULL)
86 return (-1);
87 bsdunzip->getopt_state = state_next_word;
88 }
89
90 /*
91 * We're ready to look at the next word in argv.
92 */
93 if (bsdunzip->getopt_state == state_next_word) {
94 /* No more arguments, so no more options. */
95 if (bsdunzip->argv[0] == NULL)
96 return (-1);
97 /* Doesn't start with '-', so no more options. */
98 if (bsdunzip->argv[0][0] != '-')
99 return (-1);
100 /* "--" marks end of options; consume it and return. */
101 if (strcmp(bsdunzip->argv[0], "--") == 0) {
102 ++bsdunzip->argv;
103 --bsdunzip->argc;
104 bsdunzip_optind++;
105 return (-1);
106 }
107 /* Get next word for parsing. */
108 bsdunzip->getopt_word = *bsdunzip->argv++;
109 --bsdunzip->argc;
110 bsdunzip_optind++;
111 if (bsdunzip->getopt_word[1] == '-') {
112 /* Set up long option parser. */
113 bsdunzip->getopt_state = state_long;
114 bsdunzip->getopt_word += 2; /* Skip leading '--' */
115 } else {
116 /* Set up short option parser. */
117 bsdunzip->getopt_state = state_short;
118 ++bsdunzip->getopt_word; /* Skip leading '-' */
119 }
120 }
121
122 /*
123 * We're parsing a group of POSIX-style single-character options.
124 */
125 if (bsdunzip->getopt_state == state_short) {
126 /* Peel next option off of a group of short options. */
127 opt = *bsdunzip->getopt_word++;
128 if (opt == '\0') {
129 /* End of this group; recurse to get next option. */
130 bsdunzip->getopt_state = state_next_word;
131 goto again;
132 }
133
134 /* Does this option take an argument? */
135 p = strchr(short_options, opt);
136 if (p == NULL)
137 return ('?');
138 if (p[1] == ':')
139 required = 1;
140
141 /* If it takes an argument, parse that. */
142 if (required) {
143 /* If arg is run-in, bsdunzip->getopt_word already points to it. */
144 if (bsdunzip->getopt_word[0] == '\0') {
145 /* Otherwise, pick up the next word. */
146 bsdunzip->getopt_word = *bsdunzip->argv;
147 if (bsdunzip->getopt_word == NULL) {
148 lafe_warnc(0,
149 "Option -%c requires an argument",
150 opt);
151 return ('?');
152 }
153 ++bsdunzip->argv;
154 --bsdunzip->argc;
155 bsdunzip_optind++;
156 }
157 bsdunzip->getopt_state = state_next_word;
158 bsdunzip->argument = bsdunzip->getopt_word;
159 }
160 }
161
162 /* We're reading a long option */
163 if (bsdunzip->getopt_state == state_long) {
164 /* After this long option, we'll be starting a new word. */
165 bsdunzip->getopt_state = state_next_word;
166
167 /* Option name ends at '=' if there is one. */
168 p = strchr(bsdunzip->getopt_word, '=');
169 if (p != NULL) {
170 optlength = (size_t)(p - bsdunzip->getopt_word);
171 bsdunzip->argument = (char *)(uintptr_t)(p + 1);
172 } else {
173 optlength = strlen(bsdunzip->getopt_word);
174 }
175
176 /* Search the table for an unambiguous match. */
177 for (popt = bsdunzip_longopts; popt->name != NULL; popt++) {
178 /* Short-circuit if first chars don't match. */
179 if (popt->name[0] != bsdunzip->getopt_word[0])
180 continue;
181 /* If option is a prefix of name in table, record it.*/
182 if (strncmp(bsdunzip->getopt_word, popt->name, optlength) == 0) {
183 match2 = match; /* Record up to two matches. */
184 match = popt;
185 /* If it's an exact match, we're done. */
186 if (strlen(popt->name) == optlength) {
187 match2 = NULL; /* Forget the others. */
188 break;
189 }
190 }
191 }
192
193 /* Fail if there wasn't a unique match. */
194 if (match == NULL) {
195 lafe_warnc(0,
196 "Option %s%s is not supported",
197 long_prefix, bsdunzip->getopt_word);
198 return ('?');
199 }
200 if (match2 != NULL) {
201 lafe_warnc(0,
202 "Ambiguous option %s%s (matches --%s and --%s)",
203 long_prefix, bsdunzip->getopt_word, match->name, match2->name);
204 return ('?');
205 }
206
207 /* We've found a unique match; does it need an argument? */
208 if (match->required) {
209 /* Argument required: get next word if necessary. */
210 if (bsdunzip->argument == NULL) {
211 bsdunzip->argument = *bsdunzip->argv;
212 if (bsdunzip->argument == NULL) {
213 lafe_warnc(0,
214 "Option %s%s requires an argument",
215 long_prefix, match->name);
216 return ('?');
217 }
218 ++bsdunzip->argv;
219 --bsdunzip->argc;
220 bsdunzip_optind++;
221 }
222 } else {
223 /* Argument forbidden: fail if there is one. */
224 if (bsdunzip->argument != NULL) {
225 lafe_warnc(0,
226 "Option %s%s does not allow an argument",
227 long_prefix, match->name);
228 return ('?');
229 }
230 }
231 return (match->equivalent);
232 }
233
234 return (opt);
235 }
236