]> git.ipfire.org Git - thirdparty/util-linux.git/blob - misc-utils/getopt.c
Merge branch 'meson-more-build-options' of https://github.com/jwillikers/util-linux
[thirdparty/util-linux.git] / misc-utils / getopt.c
1 /*
2 * getopt.c - Enhanced implementation of BSD getopt(1)
3 * Copyright (c) 1997-2014 Frodo Looijaard <frodo@frodo.looijaard.name>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 /*
21 * Version 1.0-b4: Tue Sep 23 1997. First public release.
22 * Version 1.0: Wed Nov 19 1997.
23 * Bumped up the version number to 1.0
24 * Fixed minor typo (CSH instead of TCSH)
25 * Version 1.0.1: Tue Jun 3 1998
26 * Fixed sizeof instead of strlen bug
27 * Bumped up the version number to 1.0.1
28 * Version 1.0.2: Thu Jun 11 1998 (not present)
29 * Fixed gcc-2.8.1 warnings
30 * Fixed --version/-V option (not present)
31 * Version 1.0.5: Tue Jun 22 1999
32 * Make -u option work (not present)
33 * Version 1.0.6: Tue Jun 27 2000
34 * No important changes
35 * Version 1.1.0: Tue Jun 30 2000
36 * Added NLS support (partly written by Arkadiusz Miƛkiewicz
37 * <misiek@pld.org.pl>)
38 * Version 1.1.4: Mon Nov 7 2005
39 * Fixed a few type's in the manpage
40 * Version 1.1.5: Sun Aug 12 2012
41 * Sync with util-linux-2.21, fixed build problems, many new translations
42 * Version 1.1.6: Mon Nov 24 2014
43 * Sync with util-linux git 20141120, detect ambiguous long options, fix
44 * backslash problem in tcsh
45 */
46
47 /* Exit codes:
48 * 0) No errors, successful operation.
49 * 1) getopt(3) returned an error.
50 * 2) A problem with parameter parsing for getopt(1).
51 * 3) Internal error, out of memory
52 * 4) Returned for -T
53 */
54 #define GETOPT_EXIT_CODE 1
55 #define PARAMETER_EXIT_CODE 2
56 #define XALLOC_EXIT_CODE 3
57 #define CLOSE_EXIT_CODE XALLOC_EXIT_CODE
58 #define TEST_EXIT_CODE 4
59
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <string.h>
63 #include <unistd.h>
64 #include <ctype.h>
65 #include <getopt.h>
66 #ifdef HAVE_SYS_PARAM_H
67 # include <sys/param.h> /* BSD */
68 #endif
69
70 #include "closestream.h"
71 #include "nls.h"
72 #include "xalloc.h"
73
74 /* NON_OPT is the code that is returned getopt(3) when a non-option is
75 * found in 'char optstring[]="-abc...";', e.g., it begins by '-' */
76 #define NON_OPT 1
77 /* LONG_OPT is the code that is returned when a long option is found. */
78 #define LONG_OPT 0
79
80 /* The shells recognized. */
81 typedef enum { BASH, TCSH } shell_t;
82
83 struct getopt_control {
84 shell_t shell; /* the shell we generate output for */
85 char *optstr; /* getopt(3) optstring */
86 char *name;
87 struct option *long_options; /* long options */
88 int long_options_length; /* length of options array */
89 int long_options_nr; /* number of used elements in array */
90 unsigned int
91 compatible:1, /* compatibility mode for 'difficult' programs */
92 quiet_errors:1, /* print errors */
93 quiet_output:1, /* print output */
94 quote:1; /* quote output */
95 };
96
97 enum { REALLOC_INCREMENT = 8 };
98
99 /* Allow changing which getopt is in use with function pointer. */
100 static int (*getopt_long_fp) (int argc, char *const *argv, const char *optstr,
101 const struct option * longopts, int *longindex);
102
103 /*
104 * This function 'normalizes' a single argument: it puts single quotes
105 * around it and escapes other special characters. If quote is false, it
106 * just returns its argument.
107 *
108 * Bash only needs special treatment for single quotes; tcsh also recognizes
109 * exclamation marks within single quotes, and nukes whitespace. This
110 * function returns a pointer to a buffer that is overwritten by each call.
111 */
112 static void print_normalized(const struct getopt_control *ctl, const char *arg)
113 {
114 char *buf;
115 const char *argptr = arg;
116 char *bufptr;
117
118 if (!ctl->quote) {
119 printf(" %s", arg);
120 return;
121 }
122
123 /*
124 * Each character in arg may take up to four characters in the
125 * result: For a quote we need a closing quote, a backslash, a quote
126 * and an opening quote! We need also the global opening and closing
127 * quote, and one extra character for '\0'.
128 */
129 buf = xmalloc(strlen(arg) * 4 + 3);
130 bufptr = buf;
131
132 for (*bufptr++ = '\''; *argptr; argptr++) {
133 if (ctl->shell == TCSH) {
134 switch (*argptr) {
135 case '\\':
136 /* Backslash: replace it with: '\\' */
137 *bufptr++ = '\\';
138 *bufptr++ = '\\';
139 continue;
140 case '!':
141 /* Exclamation mark: replace it with: \! */
142 *bufptr++ = '\'';
143 *bufptr++ = '\\';
144 *bufptr++ = '!';
145 *bufptr++ = '\'';
146 continue;
147 case '\n':
148 /* Newline: replace it with: \n */
149 *bufptr++ = '\\';
150 *bufptr++ = 'n';
151 continue;
152 }
153 if (isspace(*argptr)) {
154 /* Non-newline whitespace: replace it with \<ws> */
155 *bufptr++ = '\'';
156 *bufptr++ = '\\';
157 *bufptr++ = *argptr;
158 *bufptr++ = '\'';
159 continue;
160 }
161 }
162 if (*argptr == '\'') {
163 /* Quote: replace it with: '\'' */
164 *bufptr++ = '\'';
165 *bufptr++ = '\\';
166 *bufptr++ = '\'';
167 *bufptr++ = '\'';
168 } else
169 /* Just copy */
170 *bufptr++ = *argptr;
171 }
172
173 *bufptr++ = '\'';
174 *bufptr++ = '\0';
175 printf(" %s", buf);
176 free(buf);
177 }
178
179 /*
180 * Generate the output. argv[0] is the program name (used for reporting errors).
181 * argv[1..] contains the options to be parsed. argc must be the number of
182 * elements in argv (ie. 1 if there are no options, only the program name),
183 * optstr must contain the short options, and longopts the long options.
184 * Other settings are found in global variables.
185 */
186 static int generate_output(struct getopt_control *ctl, char *argv[], int argc)
187 {
188 int exit_code = EXIT_SUCCESS; /* Assume everything will be OK */
189 int opt;
190 int longindex;
191 const char *charptr;
192
193 if (ctl->quiet_errors)
194 /* No error reporting from getopt(3) */
195 opterr = 0;
196 /* Reset getopt(3) */
197 optind = 0;
198
199 while ((opt =
200 (getopt_long_fp
201 (argc, argv, ctl->optstr,
202 (const struct option *)ctl->long_options, &longindex)))
203 != EOF) {
204 if (opt == '?' || opt == ':')
205 exit_code = GETOPT_EXIT_CODE;
206 else if (!ctl->quiet_output) {
207 switch (opt) {
208 case LONG_OPT:
209 printf(" --%s", ctl->long_options[longindex].name);
210 if (ctl->long_options[longindex].has_arg)
211 print_normalized(ctl, optarg ? optarg : "");
212 break;
213 case NON_OPT:
214 print_normalized(ctl, optarg ? optarg : "");
215 break;
216 default:
217 printf(" -%c", opt);
218 charptr = strchr(ctl->optstr, opt);
219 if (charptr != NULL && *++charptr == ':')
220 print_normalized(ctl, optarg ? optarg : "");
221 }
222 }
223 }
224 if (!ctl->quiet_output) {
225 printf(" --");
226 while (optind < argc)
227 print_normalized(ctl, argv[optind++]);
228 printf("\n");
229 }
230 for (longindex = 0; longindex < ctl->long_options_nr; longindex++)
231 free((char *)ctl->long_options[longindex].name);
232 free(ctl->long_options);
233 free(ctl->optstr);
234 free(ctl->name);
235 return exit_code;
236 }
237
238 /*
239 * Report an error when parsing getopt's own arguments. If message is NULL,
240 * we already sent a message, we just exit with a helpful hint.
241 */
242 static void __attribute__ ((__noreturn__)) parse_error(const char *message)
243 {
244 if (message)
245 warnx("%s", message);
246 errtryhelp(PARAMETER_EXIT_CODE);
247 }
248
249
250 /* Register a long option. The contents of name is copied. */
251 static void add_longopt(struct getopt_control *ctl, const char *name, int has_arg)
252 {
253 static int flag;
254 int nr = ctl->long_options_nr;
255
256 if (ctl->long_options_nr == ctl->long_options_length) {
257 ctl->long_options_length += REALLOC_INCREMENT;
258 ctl->long_options = xrealloc(ctl->long_options,
259 sizeof(struct option) *
260 ctl->long_options_length);
261 }
262 if (name) {
263 /* Not for init! */
264 ctl->long_options[nr].has_arg = has_arg;
265 ctl->long_options[nr].flag = &flag;
266 ctl->long_options[nr].val = ctl->long_options_nr;
267 ctl->long_options[nr].name = xstrdup(name);
268 } else {
269 /* lets use add_longopt(ct, NULL, 0) to terminate the array */
270 ctl->long_options[nr].name = NULL;
271 ctl->long_options[nr].has_arg = 0;
272 ctl->long_options[nr].flag = NULL;
273 ctl->long_options[nr].val = 0;
274 }
275 }
276
277
278 /*
279 * Register several long options. options is a string of long options,
280 * separated by commas or whitespace. This nukes options!
281 */
282 static void add_long_options(struct getopt_control *ctl, char *options)
283 {
284 int arg_opt;
285 char *tokptr = strtok(options, ", \t\n");
286
287 while (tokptr) {
288 size_t len = strlen(tokptr);
289
290 arg_opt = no_argument;
291 if (len > 0) {
292 if (tokptr[len - 1] == ':') {
293 if (tokptr[len - 2] == ':') {
294 tokptr[len - 2] = '\0';
295 arg_opt = optional_argument;
296 } else {
297 tokptr[len - 1] = '\0';
298 arg_opt = required_argument;
299 }
300 if (!*tokptr)
301 parse_error(_
302 ("empty long option after "
303 "-l or --long argument"));
304 }
305 add_longopt(ctl, tokptr, arg_opt);
306 ctl->long_options_nr++;
307 }
308 tokptr = strtok(NULL, ", \t\n");
309 }
310 add_longopt(ctl, NULL, 0); /* ensure long_options[] is not full */
311 }
312
313 static shell_t shell_type(const char *new_shell)
314 {
315 if (!strcmp(new_shell, "bash"))
316 return BASH;
317 if (!strcmp(new_shell, "sh"))
318 return BASH;
319 if (!strcmp(new_shell, "tcsh"))
320 return TCSH;
321 if (!strcmp(new_shell, "csh"))
322 return TCSH;
323 parse_error(_("unknown shell after -s or --shell argument"));
324 }
325
326 static void __attribute__((__noreturn__)) usage(void)
327 {
328 fputs(USAGE_HEADER, stdout);
329 printf(_(
330 " %1$s <optstring> <parameters>\n"
331 " %1$s [options] [--] <optstring> <parameters>\n"
332 " %1$s [options] -o|--options <optstring> [options] [--] <parameters>\n"),
333 program_invocation_short_name);
334
335 fputs(USAGE_SEPARATOR, stdout);
336 fputs(_("Parse command options.\n"), stdout);
337
338 fputs(USAGE_OPTIONS, stdout);
339 fputs(_(" -a, --alternative allow long options starting with single -\n"), stdout);
340 fputs(_(" -l, --longoptions <longopts> the long options to be recognized\n"), stdout);
341 fputs(_(" -n, --name <progname> the name under which errors are reported\n"), stdout);
342 fputs(_(" -o, --options <optstring> the short options to be recognized\n"), stdout);
343 fputs(_(" -q, --quiet disable error reporting by getopt(3)\n"), stdout);
344 fputs(_(" -Q, --quiet-output no normal output\n"), stdout);
345 fputs(_(" -s, --shell <shell> set quoting conventions to those of <shell>\n"), stdout);
346 fputs(_(" -T, --test test for getopt(1) version\n"), stdout);
347 fputs(_(" -u, --unquoted do not quote the output\n"), stdout);
348 fputs(USAGE_SEPARATOR, stdout);
349 printf(USAGE_HELP_OPTIONS(31));
350 printf(USAGE_MAN_TAIL("getopt(1)"));
351 exit(EXIT_SUCCESS);
352 }
353
354 int main(int argc, char *argv[])
355 {
356 struct getopt_control ctl = {
357 .shell = BASH,
358 .quote = 1
359 };
360 int opt;
361
362 /* Stop scanning as soon as a non-option argument is found! */
363 static const char *shortopts = "+ao:l:n:qQs:TuhV";
364 static const struct option longopts[] = {
365 {"options", required_argument, NULL, 'o'},
366 {"longoptions", required_argument, NULL, 'l'},
367 {"quiet", no_argument, NULL, 'q'},
368 {"quiet-output", no_argument, NULL, 'Q'},
369 {"shell", required_argument, NULL, 's'},
370 {"test", no_argument, NULL, 'T'},
371 {"unquoted", no_argument, NULL, 'u'},
372 {"help", no_argument, NULL, 'h'},
373 {"alternative", no_argument, NULL, 'a'},
374 {"name", required_argument, NULL, 'n'},
375 {"version", no_argument, NULL, 'V'},
376 {NULL, 0, NULL, 0}
377 };
378
379 setlocale(LC_ALL, "");
380 bindtextdomain(PACKAGE, LOCALEDIR);
381 textdomain(PACKAGE);
382 close_stdout_atexit();
383
384 if (getenv("GETOPT_COMPATIBLE"))
385 ctl.compatible = 1;
386
387 if (argc == 1) {
388 if (ctl.compatible) {
389 /*
390 * For some reason, the original getopt gave no
391 * error when there were no arguments.
392 */
393 printf(" --\n");
394 return EXIT_SUCCESS;
395 } else
396 parse_error(_("missing optstring argument"));
397 }
398
399 add_longopt(&ctl, NULL, 0); /* init */
400 getopt_long_fp = getopt_long;
401
402 if (argv[1][0] != '-' || ctl.compatible) {
403 ctl.quote = 0;
404 ctl.optstr = xmalloc(strlen(argv[1]) + 1);
405 strcpy(ctl.optstr, argv[1] + strspn(argv[1], "-+"));
406 argv[1] = argv[0];
407 return generate_output(&ctl, argv + 1, argc - 1);
408 }
409
410 while ((opt =
411 getopt_long(argc, argv, shortopts, longopts, NULL)) != EOF)
412 switch (opt) {
413 case 'a':
414 getopt_long_fp = getopt_long_only;
415 break;
416 case 'o':
417 free(ctl.optstr);
418 ctl.optstr = xstrdup(optarg);
419 break;
420 case 'l':
421 add_long_options(&ctl, optarg);
422 break;
423 case 'n':
424 free(ctl.name);
425 ctl.name = xstrdup(optarg);
426 break;
427 case 'q':
428 ctl.quiet_errors = 1;
429 break;
430 case 'Q':
431 ctl.quiet_output = 1;
432 break;
433 case 's':
434 ctl.shell = shell_type(optarg);
435 break;
436 case 'T':
437 free(ctl.long_options);
438 return TEST_EXIT_CODE;
439 case 'u':
440 ctl.quote = 0;
441 break;
442
443 case 'V':
444 print_version(EXIT_SUCCESS);
445 case '?':
446 case ':':
447 parse_error(NULL);
448 case 'h':
449 usage();
450 default:
451 parse_error(_("internal error, contact the author."));
452 }
453
454 if (!ctl.optstr) {
455 if (optind >= argc)
456 parse_error(_("missing optstring argument"));
457 else {
458 ctl.optstr = xstrdup(argv[optind]);
459 optind++;
460 }
461 }
462
463 if (ctl.name) {
464 argv[optind - 1] = ctl.name;
465 #if defined (HAVE_SETPROGNAME) && !defined (__linux__)
466 setprogname(ctl.name);
467 #endif
468 } else
469 argv[optind - 1] = argv[0];
470
471 return generate_output(&ctl, argv + optind - 1, argc - optind + 1);
472 }