Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/libarchive/cat/cmdline.c
39482 views
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 bsdcat.
10
*/
11
12
#include "bsdcat_platform.h"
13
14
#ifdef HAVE_ERRNO_H
15
#include <errno.h>
16
#endif
17
#ifdef HAVE_STDLIB_H
18
#include <stdlib.h>
19
#endif
20
#ifdef HAVE_STRING_H
21
#include <string.h>
22
#endif
23
24
#include "bsdcat.h"
25
#include "err.h"
26
27
/*
28
* Short options for bsdcat. Please keep this sorted.
29
*/
30
static const char *short_options = "h";
31
32
/*
33
* Long options for bsdcat. Please keep this list sorted.
34
*
35
* The symbolic names for options that lack a short equivalent are
36
* defined in bsdcat.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 bsdcat_option {
42
const char *name;
43
int required; /* 1 if this option requires an argument. */
44
int equivalent; /* Equivalent short option. */
45
} bsdcat_longopts[] = {
46
{ "help", 0, 'h' },
47
{ "version", 0, OPTION_VERSION },
48
{ NULL, 0, 0 }
49
};
50
51
/*
52
* This getopt implementation has two key features that common
53
* getopt_long() implementations lack. Apart from those, it's a
54
* straightforward option parser, considerably simplified by not
55
* needing to support the wealth of exotic getopt_long() features. It
56
* has, of course, been shamelessly tailored for bsdcat. (If you're
57
* looking for a generic getopt_long() implementation for your
58
* project, I recommend Gregory Pietsch's public domain getopt_long()
59
* implementation.) The two additional features are:
60
*
61
* Old-style tar arguments: The original tar implementation treated
62
* the first argument word as a list of single-character option
63
* letters. All arguments follow as separate words. For example,
64
* tar xbf 32 /dev/tape
65
* Here, the "xbf" is three option letters, "32" is the argument for
66
* "b" and "/dev/tape" is the argument for "f". We support this usage
67
* if the first command-line argument does not begin with '-'. We
68
* also allow regular short and long options to follow, e.g.,
69
* tar xbf 32 /dev/tape -P --format=pax
70
*
71
* -W long options: There's an obscure GNU convention (only rarely
72
* supported even there) that allows "-W option=argument" as an
73
* alternative way to support long options. This was supported in
74
* early bsdtar as a way to access long options on platforms that did
75
* not support getopt_long() and is preserved here for backwards
76
* compatibility. (Of course, if I'd started with a custom
77
* command-line parser from the beginning, I would have had normal
78
* long option support on every platform so that hack wouldn't have
79
* been necessary. Oh, well. Some mistakes you just have to live
80
* with.)
81
*
82
* TODO: We should be able to use this to pull files and intermingled
83
* options (such as -C) from the command line in write mode. That
84
* will require a little rethinking of the argument handling in
85
* bsdcat.c.
86
*
87
* TODO: If we want to support arbitrary command-line options from -T
88
* input (as GNU tar does), we may need to extend this to handle option
89
* words from sources other than argv/argc. I'm not really sure if I
90
* like that feature of GNU tar, so it's certainly not a priority.
91
*/
92
93
int
94
bsdcat_getopt(struct bsdcat *bsdcat)
95
{
96
enum { state_start = 0, state_old_tar, state_next_word,
97
state_short, state_long };
98
99
const struct bsdcat_option *popt, *match, *match2;
100
const char *p, *long_prefix;
101
size_t optlength;
102
int opt;
103
int required;
104
105
again:
106
match = NULL;
107
match2 = NULL;
108
long_prefix = "--";
109
opt = '?';
110
required = 0;
111
bsdcat->argument = NULL;
112
113
/* First time through, initialize everything. */
114
if (bsdcat->getopt_state == state_start) {
115
/* Skip program name. */
116
++bsdcat->argv;
117
--bsdcat->argc;
118
if (*bsdcat->argv == NULL)
119
return (-1);
120
/* Decide between "new style" and "old style" arguments. */
121
bsdcat->getopt_state = state_next_word;
122
}
123
124
/*
125
* We're ready to look at the next word in argv.
126
*/
127
if (bsdcat->getopt_state == state_next_word) {
128
/* No more arguments, so no more options. */
129
if (bsdcat->argv[0] == NULL)
130
return (-1);
131
/* Doesn't start with '-', so no more options. */
132
if (bsdcat->argv[0][0] != '-')
133
return (-1);
134
/* "--" marks end of options; consume it and return. */
135
if (strcmp(bsdcat->argv[0], "--") == 0) {
136
++bsdcat->argv;
137
--bsdcat->argc;
138
return (-1);
139
}
140
/* Get next word for parsing. */
141
bsdcat->getopt_word = *bsdcat->argv++;
142
--bsdcat->argc;
143
if (bsdcat->getopt_word[1] == '-') {
144
/* Set up long option parser. */
145
bsdcat->getopt_state = state_long;
146
bsdcat->getopt_word += 2; /* Skip leading '--' */
147
} else {
148
/* Set up short option parser. */
149
bsdcat->getopt_state = state_short;
150
++bsdcat->getopt_word; /* Skip leading '-' */
151
}
152
}
153
154
/*
155
* We're parsing a group of POSIX-style single-character options.
156
*/
157
if (bsdcat->getopt_state == state_short) {
158
/* Peel next option off of a group of short options. */
159
opt = *bsdcat->getopt_word++;
160
if (opt == '\0') {
161
/* End of this group; recurse to get next option. */
162
bsdcat->getopt_state = state_next_word;
163
goto again;
164
}
165
166
/* Does this option take an argument? */
167
p = strchr(short_options, opt);
168
if (p == NULL)
169
return ('?');
170
if (p[1] == ':')
171
required = 1;
172
173
/* If it takes an argument, parse that. */
174
if (required) {
175
/* If arg is run-in, bsdcat->getopt_word already points to it. */
176
if (bsdcat->getopt_word[0] == '\0') {
177
/* Otherwise, pick up the next word. */
178
bsdcat->getopt_word = *bsdcat->argv;
179
if (bsdcat->getopt_word == NULL) {
180
lafe_warnc(0,
181
"Option -%c requires an argument",
182
opt);
183
return ('?');
184
}
185
++bsdcat->argv;
186
--bsdcat->argc;
187
}
188
if (opt == 'W') {
189
bsdcat->getopt_state = state_long;
190
long_prefix = "-W "; /* For clearer errors. */
191
} else {
192
bsdcat->getopt_state = state_next_word;
193
bsdcat->argument = bsdcat->getopt_word;
194
}
195
}
196
}
197
198
/* We're reading a long option, including -W long=arg convention. */
199
if (bsdcat->getopt_state == state_long) {
200
/* After this long option, we'll be starting a new word. */
201
bsdcat->getopt_state = state_next_word;
202
203
/* Option name ends at '=' if there is one. */
204
p = strchr(bsdcat->getopt_word, '=');
205
if (p != NULL) {
206
optlength = (size_t)(p - bsdcat->getopt_word);
207
bsdcat->argument = (char *)(uintptr_t)(p + 1);
208
} else {
209
optlength = strlen(bsdcat->getopt_word);
210
}
211
212
/* Search the table for an unambiguous match. */
213
for (popt = bsdcat_longopts; popt->name != NULL; popt++) {
214
/* Short-circuit if first chars don't match. */
215
if (popt->name[0] != bsdcat->getopt_word[0])
216
continue;
217
/* If option is a prefix of name in table, record it.*/
218
if (strncmp(bsdcat->getopt_word, popt->name, optlength) == 0) {
219
match2 = match; /* Record up to two matches. */
220
match = popt;
221
/* If it's an exact match, we're done. */
222
if (strlen(popt->name) == optlength) {
223
match2 = NULL; /* Forget the others. */
224
break;
225
}
226
}
227
}
228
229
/* Fail if there wasn't a unique match. */
230
if (match == NULL) {
231
lafe_warnc(0,
232
"Option %s%s is not supported",
233
long_prefix, bsdcat->getopt_word);
234
return ('?');
235
}
236
if (match2 != NULL) {
237
lafe_warnc(0,
238
"Ambiguous option %s%s (matches --%s and --%s)",
239
long_prefix, bsdcat->getopt_word, match->name, match2->name);
240
return ('?');
241
}
242
243
/* We've found a unique match; does it need an argument? */
244
if (match->required) {
245
/* Argument required: get next word if necessary. */
246
if (bsdcat->argument == NULL) {
247
bsdcat->argument = *bsdcat->argv;
248
if (bsdcat->argument == NULL) {
249
lafe_warnc(0,
250
"Option %s%s requires an argument",
251
long_prefix, match->name);
252
return ('?');
253
}
254
++bsdcat->argv;
255
--bsdcat->argc;
256
}
257
} else {
258
/* Argument forbidden: fail if there is one. */
259
if (bsdcat->argument != NULL) {
260
lafe_warnc(0,
261
"Option %s%s does not allow an argument",
262
long_prefix, match->name);
263
return ('?');
264
}
265
}
266
return (match->equivalent);
267
}
268
269
return (opt);
270
}
271
272