]> git.ipfire.org Git - thirdparty/util-linux.git/blob - misc-utils/getopt.c
Merge branch 'fix/opal_luks_blkid_scan' of https://github.com/oldium/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 "strutils.h"
73 #include "xalloc.h"
74
75 /* NON_OPT is the code that is returned getopt(3) when a non-option is
76 * found in 'char optstring[]="-abc...";', e.g., it begins by '-' */
77 #define NON_OPT 1
78 /* LONG_OPT is the code that is returned when a long option is found. */
79 #define LONG_OPT 0
80
81 /* The shells recognized. */
82 typedef enum { BASH, TCSH } shell_t;
83
84 struct getopt_control {
85 shell_t shell; /* the shell we generate output for */
86 char *optstr; /* getopt(3) optstring */
87 char *name;
88 struct option *long_options; /* long options */
89 int long_options_length; /* length of options array */
90 int long_options_nr; /* number of used elements in array */
91 unsigned int
92 compatible:1, /* compatibility mode for 'difficult' programs */
93 quiet_errors:1, /* print errors */
94 quiet_output:1, /* print output */
95 quote:1; /* quote output */
96 };
97
98 enum { REALLOC_INCREMENT = 8 };
99
100 /* Allow changing which getopt is in use with function pointer. */
101 static int (*getopt_long_fp) (int argc, char *const *argv, const char *optstr,
102 const struct option * longopts, int *longindex);
103
104 /*
105 * This function 'normalizes' a single argument: it puts single quotes
106 * around it and escapes other special characters. If quote is false, it
107 * just returns its argument.
108 *
109 * Bash only needs special treatment for single quotes; tcsh also recognizes
110 * exclamation marks within single quotes, and nukes whitespace. This
111 * function returns a pointer to a buffer that is overwritten by each call.
112 */
113 static void print_normalized(const struct getopt_control *ctl, const char *arg)
114 {
115 char *buf;
116 const char *argptr = arg;
117 char *bufptr;
118
119 if (!ctl->quote) {
120 printf(" %s", arg);
121 return;
122 }
123
124 /*
125 * Each character in arg may take up to four characters in the
126 * result: For a quote we need a closing quote, a backslash, a quote
127 * and an opening quote! We need also the global opening and closing
128 * quote, and one extra character for '\0'.
129 */
130 buf = xmalloc(strlen(arg) * 4 + 3);
131 bufptr = buf;
132
133 for (*bufptr++ = '\''; *argptr; argptr++) {
134 if (ctl->shell == TCSH) {
135 switch (*argptr) {
136 case '\\':
137 /* Backslash: replace it with: '\\' */
138 *bufptr++ = '\\';
139 *bufptr++ = '\\';
140 continue;
141 case '!':
142 /* Exclamation mark: replace it with: \! */
143 *bufptr++ = '\'';
144 *bufptr++ = '\\';
145 *bufptr++ = '!';
146 *bufptr++ = '\'';
147 continue;
148 case '\n':
149 /* Newline: replace it with: \n */
150 *bufptr++ = '\\';
151 *bufptr++ = 'n';
152 continue;
153 }
154 if (isspace(*argptr)) {
155 /* Non-newline whitespace: replace it with \<ws> */
156 *bufptr++ = '\'';
157 *bufptr++ = '\\';
158 *bufptr++ = *argptr;
159 *bufptr++ = '\'';
160 continue;
161 }
162 }
163 if (*argptr == '\'') {
164 /* Quote: replace it with: '\'' */
165 *bufptr++ = '\'';
166 *bufptr++ = '\\';
167 *bufptr++ = '\'';
168 *bufptr++ = '\'';
169 } else
170 /* Just copy */
171 *bufptr++ = *argptr;
172 }
173
174 *bufptr++ = '\'';
175 *bufptr++ = '\0';
176 printf(" %s", buf);
177 free(buf);
178 }
179
180 /*
181 * Generate the output. argv[0] is the program name (used for reporting errors).
182 * argv[1..] contains the options to be parsed. argc must be the number of
183 * elements in argv (ie. 1 if there are no options, only the program name),
184 * optstr must contain the short options, and longopts the long options.
185 * Other settings are found in global variables.
186 */
187 static int generate_output(struct getopt_control *ctl, char *argv[], int argc)
188 {
189 int exit_code = EXIT_SUCCESS; /* Assume everything will be OK */
190 int opt;
191 int longindex;
192 const char *charptr;
193
194 if (ctl->quiet_errors)
195 /* No error reporting from getopt(3) */
196 opterr = 0;
197 /* Reset getopt(3) */
198 optind = 0;
199
200 while ((opt =
201 (getopt_long_fp
202 (argc, argv, ctl->optstr,
203 (const struct option *)ctl->long_options, &longindex)))
204 != EOF) {
205 if (opt == '?' || opt == ':')
206 exit_code = GETOPT_EXIT_CODE;
207 else if (!ctl->quiet_output) {
208 switch (opt) {
209 case LONG_OPT:
210 printf(" --%s", ctl->long_options[longindex].name);
211 if (ctl->long_options[longindex].has_arg)
212 print_normalized(ctl, optarg ? optarg : "");
213 break;
214 case NON_OPT:
215 print_normalized(ctl, optarg ? optarg : "");
216 break;
217 default:
218 printf(" -%c", opt);
219 charptr = strchr(ctl->optstr, opt);
220 if (charptr != NULL && *++charptr == ':')
221 print_normalized(ctl, optarg ? optarg : "");
222 }
223 }
224 }
225 if (!ctl->quiet_output) {
226 printf(" --");
227 while (optind < argc)
228 print_normalized(ctl, argv[optind++]);
229 printf("\n");
230 }
231 for (longindex = 0; longindex < ctl->long_options_nr; longindex++)
232 free((char *)ctl->long_options[longindex].name);
233 free(ctl->long_options);
234 free(ctl->optstr);
235 free(ctl->name);
236 return exit_code;
237 }
238
239 /*
240 * Report an error when parsing getopt's own arguments. If message is NULL,
241 * we already sent a message, we just exit with a helpful hint.
242 */
243 static void __attribute__ ((__noreturn__)) parse_error(const char *message)
244 {
245 if (message)
246 warnx("%s", message);
247 errtryhelp(PARAMETER_EXIT_CODE);
248 }
249
250
251 /* Register a long option. The contents of name is copied. */
252 static void add_longopt(struct getopt_control *ctl, const char *name, int has_arg)
253 {
254 static int flag;
255 int nr = ctl->long_options_nr;
256
257 if (ctl->long_options_nr == ctl->long_options_length) {
258 ctl->long_options_length += REALLOC_INCREMENT;
259 ctl->long_options = xreallocarray(ctl->long_options,
260 ctl->long_options_length,
261 sizeof(struct option));
262 }
263 if (name) {
264 /* Not for init! */
265 ctl->long_options[nr].has_arg = has_arg;
266 ctl->long_options[nr].flag = &flag;
267 ctl->long_options[nr].val = ctl->long_options_nr;
268 ctl->long_options[nr].name = xstrdup(name);
269 } else {
270 /* lets use add_longopt(ct, NULL, 0) to terminate the array */
271 ctl->long_options[nr].name = NULL;
272 ctl->long_options[nr].has_arg = 0;
273 ctl->long_options[nr].flag = NULL;
274 ctl->long_options[nr].val = 0;
275 }
276 }
277
278
279 static void add_short_options(struct getopt_control *ctl, char *options)
280 {
281 free(ctl->optstr);
282 if (*options != '+' && getenv("POSIXLY_CORRECT"))
283 ctl->optstr = strconcat("+", options);
284 else
285 ctl->optstr = xstrdup(options);
286 if (!ctl->optstr)
287 err_oom();
288 }
289
290
291 /*
292 * Register several long options. options is a string of long options,
293 * separated by commas or whitespace. This nukes options!
294 */
295 static void add_long_options(struct getopt_control *ctl, char *options)
296 {
297 int arg_opt;
298 char *tokptr = strtok(options, ", \t\n");
299
300 while (tokptr) {
301 size_t len = strlen(tokptr);
302
303 arg_opt = no_argument;
304 if (len > 0) {
305 if (tokptr[len - 1] == ':') {
306 if (tokptr[len - 2] == ':') {
307 tokptr[len - 2] = '\0';
308 arg_opt = optional_argument;
309 } else {
310 tokptr[len - 1] = '\0';
311 arg_opt = required_argument;
312 }
313 if (!*tokptr)
314 parse_error(_
315 ("empty long option after "
316 "-l or --long argument"));
317 }
318 add_longopt(ctl, tokptr, arg_opt);
319 ctl->long_options_nr++;
320 }
321 tokptr = strtok(NULL, ", \t\n");
322 }
323 add_longopt(ctl, NULL, 0); /* ensure long_options[] is not full */
324 }
325
326 static shell_t shell_type(const char *new_shell)
327 {
328 if (!strcmp(new_shell, "bash"))
329 return BASH;
330 if (!strcmp(new_shell, "sh"))
331 return BASH;
332 if (!strcmp(new_shell, "tcsh"))
333 return TCSH;
334 if (!strcmp(new_shell, "csh"))
335 return TCSH;
336 parse_error(_("unknown shell after -s or --shell argument"));
337 }
338
339 static void __attribute__((__noreturn__)) usage(void)
340 {
341 fputs(USAGE_HEADER, stdout);
342 printf(_(
343 " %1$s <optstring> <parameters>\n"
344 " %1$s [options] [--] <optstring> <parameters>\n"
345 " %1$s [options] -o|--options <optstring> [options] [--] <parameters>\n"),
346 program_invocation_short_name);
347
348 fputs(USAGE_SEPARATOR, stdout);
349 fputs(_("Parse command options.\n"), stdout);
350
351 fputs(USAGE_OPTIONS, stdout);
352 fputs(_(" -a, --alternative allow long options starting with single -\n"), stdout);
353 fputs(_(" -l, --longoptions <longopts> the long options to be recognized\n"), stdout);
354 fputs(_(" -n, --name <progname> the name under which errors are reported\n"), stdout);
355 fputs(_(" -o, --options <optstring> the short options to be recognized\n"), stdout);
356 fputs(_(" -q, --quiet disable error reporting by getopt(3)\n"), stdout);
357 fputs(_(" -Q, --quiet-output no normal output\n"), stdout);
358 fputs(_(" -s, --shell <shell> set quoting conventions to those of <shell>\n"), stdout);
359 fputs(_(" -T, --test test for getopt(1) version\n"), stdout);
360 fputs(_(" -u, --unquoted do not quote the output\n"), stdout);
361 fputs(USAGE_SEPARATOR, stdout);
362 fprintf(stdout, USAGE_HELP_OPTIONS(31));
363 fprintf(stdout, USAGE_MAN_TAIL("getopt(1)"));
364 exit(EXIT_SUCCESS);
365 }
366
367 int main(int argc, char *argv[])
368 {
369 struct getopt_control ctl = {
370 .shell = BASH,
371 .quote = 1
372 };
373 int opt;
374
375 /* Stop scanning as soon as a non-option argument is found! */
376 static const char *shortopts = "+ao:l:n:qQs:TuhV";
377 static const struct option longopts[] = {
378 {"options", required_argument, NULL, 'o'},
379 {"longoptions", required_argument, NULL, 'l'},
380 {"quiet", no_argument, NULL, 'q'},
381 {"quiet-output", no_argument, NULL, 'Q'},
382 {"shell", required_argument, NULL, 's'},
383 {"test", no_argument, NULL, 'T'},
384 {"unquoted", no_argument, NULL, 'u'},
385 {"help", no_argument, NULL, 'h'},
386 {"alternative", no_argument, NULL, 'a'},
387 {"name", required_argument, NULL, 'n'},
388 {"version", no_argument, NULL, 'V'},
389 {NULL, 0, NULL, 0}
390 };
391
392 setlocale(LC_ALL, "");
393 bindtextdomain(PACKAGE, LOCALEDIR);
394 textdomain(PACKAGE);
395 close_stdout_atexit();
396
397 if (getenv("GETOPT_COMPATIBLE"))
398 ctl.compatible = 1;
399
400 if (argc == 1) {
401 if (ctl.compatible) {
402 /*
403 * For some reason, the original getopt gave no
404 * error when there were no arguments.
405 */
406 printf(" --\n");
407 return EXIT_SUCCESS;
408 }
409 parse_error(_("missing optstring argument"));
410 }
411
412 add_longopt(&ctl, NULL, 0); /* init */
413 getopt_long_fp = getopt_long;
414
415 if (argv[1][0] != '-' || ctl.compatible) {
416 ctl.quote = 0;
417 ctl.optstr = xmalloc(strlen(argv[1]) + 1);
418 strcpy(ctl.optstr, argv[1] + strspn(argv[1], "-+"));
419 argv[1] = argv[0];
420 return generate_output(&ctl, argv + 1, argc - 1);
421 }
422
423 while ((opt =
424 getopt_long(argc, argv, shortopts, longopts, NULL)) != EOF)
425 switch (opt) {
426 case 'a':
427 getopt_long_fp = getopt_long_only;
428 break;
429 case 'o':
430 add_short_options(&ctl, optarg);
431 break;
432 case 'l':
433 add_long_options(&ctl, optarg);
434 break;
435 case 'n':
436 free(ctl.name);
437 ctl.name = xstrdup(optarg);
438 break;
439 case 'q':
440 ctl.quiet_errors = 1;
441 break;
442 case 'Q':
443 ctl.quiet_output = 1;
444 break;
445 case 's':
446 ctl.shell = shell_type(optarg);
447 break;
448 case 'T':
449 free(ctl.long_options);
450 return TEST_EXIT_CODE;
451 case 'u':
452 ctl.quote = 0;
453 break;
454
455 case 'V':
456 print_version(EXIT_SUCCESS);
457 case '?':
458 case ':':
459 parse_error(NULL);
460 case 'h':
461 usage();
462 default:
463 parse_error(_("internal error, contact the author."));
464 }
465
466 if (!ctl.optstr) {
467 if (optind >= argc)
468 parse_error(_("missing optstring argument"));
469 else {
470 add_short_options(&ctl, argv[optind]);
471 optind++;
472 }
473 }
474
475 if (ctl.name) {
476 argv[optind - 1] = ctl.name;
477 #if defined (HAVE_SETPROGNAME) && !defined (__linux__)
478 setprogname(ctl.name);
479 #endif
480 } else
481 argv[optind - 1] = argv[0];
482
483 return generate_output(&ctl, argv + optind - 1, argc - optind + 1);
484 }