lobase

Linux port of OpenBSDs userland.
Log | Files | Refs | README

commit a8a25006dbfab8d8d0dad56e458222b33562ee48
parent 8a36ce0ce894e41b6bf0d8305a297d617b89be67
Author: Duncaen <mail@duncano.de>
Date:   Wed, 24 May 2017 02:38:29 +0200

lib/libopenbsd: add getopt{,_long}(3)

Diffstat:
lib/libopenbsd/stdlib/Makefile.inc | 3+++
lib/libopenbsd/stdlib/getopt.3 | 363+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
lib/libopenbsd/stdlib/getopt_long.3 | 444+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
lib/libopenbsd/stdlib/getopt_long.c | 523+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 1333 insertions(+), 0 deletions(-)

diff --git a/lib/libopenbsd/stdlib/Makefile.inc b/lib/libopenbsd/stdlib/Makefile.inc @@ -3,6 +3,9 @@ # stdlib sources VPATH+= ${LIBCSRCDIR}/stdlib +SRCS+= getopt_long.c +MAN+= getopt.3 getopt_long.3 + ifneq ($(HAVE_REALLOCARRAY),1) SRCS+= reallocarray.c recallocarray.c MAN+= malloc.3 diff --git a/lib/libopenbsd/stdlib/getopt.3 b/lib/libopenbsd/stdlib/getopt.3 @@ -0,0 +1,363 @@ +.\" Copyright (c) 1988, 1991, 1993 +.\" The Regents of the University of California. 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. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. +.\" +.\" $OpenBSD: getopt.3,v 1.46 2016/01/04 19:43:13 tb Exp $ +.\" +.Dd $Mdocdate: January 4 2016 $ +.Dt GETOPT 3 +.Os +.Sh NAME +.Nm getopt +.Nd get option character from command line argument list +.Sh SYNOPSIS +.In unistd.h +.Vt extern char *optarg; +.Vt extern int opterr; +.Vt extern int optind; +.Vt extern int optopt; +.Vt extern int optreset; +.Ft int +.Fn getopt "int argc" "char * const *argv" "const char *optstring" +.Sh DESCRIPTION +The +.Fn getopt +function incrementally parses a command line argument list +.Fa argv +and returns the next +.Em known +option character. +An option character is +.Em known +if it has been specified in the string of accepted option characters, +.Fa optstring . +.Pp +The option string +.Fa optstring +may contain the following elements: individual characters, +characters followed by a colon, and characters followed by two colons. +A character followed by a single colon indicates that an argument +is to follow the option on the command line. +Two colons indicates that the argument is optional \- this is an +extension not covered by POSIX. +For example, an option string +.Qq x +recognizes an option +.Fl x , +and an option string +.Qq Li x: +recognizes an option and argument +.Fl x Ar argument . +It does not matter to +.Fn getopt +if a following argument has leading whitespace; except in the case where +the argument is optional, denoted with two colons, no leading whitespace +is permitted. +.Pp +On return from +.Fn getopt , +.Va optarg +points to an option argument, if it is anticipated, +and the variable +.Va optind +contains the index to the next +.Fa argv +argument for a subsequent call +to +.Fn getopt . +.Pp +The variables +.Va opterr +and +.Va optind +are both initialized to 1. +The +.Va optind +variable may be set to another value larger than 0 before a set of calls to +.Fn getopt +in order to skip over more or less +.Fa argv +entries. +An +.Va optind +value of 0 is reserved for compatibility with GNU +.Fn getopt . +.Pp +In order to use +.Fn getopt +to evaluate multiple sets of arguments, or to evaluate a single set of +arguments multiple times, +the variable +.Va optreset +must be set to 1 before the second and each additional set of calls to +.Fn getopt , +and the variable +.Va optind +must be reinitialized. +.Pp +The +.Fn getopt +function returns \-1 when the argument list is exhausted. +The interpretation of options in the argument list may be cancelled +by the option +.Ql -- +(double dash) which causes +.Fn getopt +to signal the end of argument processing and return \-1. +When all options have been processed (i.e., up to the first non-option +argument), +.Fn getopt +returns \-1. +.Sh RETURN VALUES +The +.Fn getopt +function returns the next known option character in +.Fa optstring . +If +.Fn getopt +encounters a character not found in +.Fa optstring +or if it detects a missing option argument, +it returns +.Sq \&? +(question mark). +If +.Fa optstring +has a leading +.Sq \&: +then a missing option argument causes +.Sq \&: +to be returned instead of +.Sq \&? . +In either case, the variable +.Va optopt +is set to the character that caused the error. +The +.Fn getopt +function returns \-1 when the argument list is exhausted. +.Sh EXAMPLES +The following code accepts the options +.Fl b +and +.Fl f Ar argument +and adjusts +.Va argc +and +.Va argv +after option argument processing has completed. +.Bd -literal -offset indent +int bflag, ch, fd; + +bflag = 0; +while ((ch = getopt(argc, argv, "bf:")) != -1) { + switch (ch) { + case 'b': + bflag = 1; + break; + case 'f': + if ((fd = open(optarg, O_RDONLY, 0)) == -1) + err(1, "%s", optarg); + break; + default: + usage(); + } +} +argc -= optind; +argv += optind; +.Ed +.Sh DIAGNOSTICS +If the +.Fn getopt +function encounters a character not found in the string +.Fa optstring +or detects +a missing option argument, it writes an error message to +.Em stderr +and returns +.Ql \&? . +Setting +.Va opterr +to a zero will disable these error messages. +If +.Fa optstring +has a leading +.Ql \&: +then a missing option argument causes a +.Ql \&: +to be returned in addition to suppressing any error messages. +.Pp +Option arguments are allowed to begin with +.Ql - ; +this is reasonable but reduces the amount of error checking possible. +.Sh SEE ALSO +.Xr getopt 1 , +.Xr getopt_long 3 , +.Xr getsubopt 3 +.Sh STANDARDS +The +.Fn getopt +function implements a superset of the functionality specified by +.St -p1003.1 . +.Pp +The following extensions are supported: +.Bl -bullet +.It +The +.Va optreset +variable was added to make it possible to call the +.Fn getopt +function multiple times. +.It +If the +.Va optind +variable is set to 0, +.Fn getopt +will behave as if the +.Va optreset +variable has been set. +This is for compatibility with +.Tn GNU +.Fn getopt . +New code should use +.Va optreset +instead. +.It +If the first character of +.Fa optstring +is a plus sign +.Pq Ql + , +it will be ignored. +This is for compatibility with +.Tn GNU +.Fn getopt . +.It +If the first character of +.Fa optstring +is a dash +.Pq Ql - , +non-options will be returned as arguments to the option character +.Ql \e1 . +This is for compatibility with +.Tn GNU +.Fn getopt . +.It +A single dash +.Pq Ql - +may be specified as a character in +.Fa optstring , +however it should +.Em never +have an argument associated with it. +This allows +.Fn getopt +to be used with programs that expect +.Ql - +as an option flag. +This practice is wrong, and should not be used in any current development. +It is provided for backward compatibility +.Em only . +Care should be taken not to use +.Ql - +as the first character in +.Fa optstring +to avoid a semantic conflict with +.Tn GNU +.Fn getopt +semantics (see above). +By default, a single dash causes +.Fn getopt +to return \-1. +.El +.Pp +Historic +.Bx +versions of +.Fn getopt +set +.Fa optopt +to the last option character processed. +However, this conflicts with +.St -p1003.1 +which stipulates that +.Fa optopt +be set to the last character that caused an error. +.Sh HISTORY +The +.Fn getopt +function appeared in +.Bx 4.3 . +.Sh BUGS +The +.Fn getopt +function was once specified to return +.Dv EOF +instead of \-1. +This was changed by +.St -p1003.2-92 +to decouple +.Fn getopt +from +.In stdio.h . +.Pp +It is possible to handle digits as option letters. +This allows +.Fn getopt +to be used with programs that expect a number +.Pq Dq Li \-3 +as an option. +This practice is wrong, and should not be used in any current development. +It is provided for backward compatibility +.Em only . +The following code fragment works in most cases and can handle mixed +number and letter arguments. +.Bd -literal -offset indent +int aflag = 0, bflag = 0, ch, lastch = '\e0'; +int length = -1, newarg = 1, prevoptind = 1; + +while ((ch = getopt(argc, argv, "0123456789ab")) != -1) { + switch (ch) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + if (newarg || !isdigit(lastch)) + length = 0; + else if (length > INT_MAX / 10) + usage(); + length = (length * 10) + (ch - '0'); + break; + case 'a': + aflag = 1; + break; + case 'b': + bflag = 1; + break; + default: + usage(); + } + lastch = ch; + newarg = optind != prevoptind; + prevoptind = optind; +} +.Ed diff --git a/lib/libopenbsd/stdlib/getopt_long.3 b/lib/libopenbsd/stdlib/getopt_long.3 @@ -0,0 +1,444 @@ +.\" $OpenBSD: getopt_long.3,v 1.21 2016/01/04 19:43:13 tb Exp $ +.\" $NetBSD: getopt_long.3,v 1.11 2002/10/02 10:54:19 wiz Exp $ +.\" +.\" Copyright (c) 1988, 1991, 1993 +.\" The Regents of the University of California. 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. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. +.\" +.\" @(#)getopt.3 8.5 (Berkeley) 4/27/95 +.\" +.Dd $Mdocdate: January 4 2016 $ +.Dt GETOPT_LONG 3 +.Os +.Sh NAME +.Nm getopt_long , +.Nm getopt_long_only +.Nd get long options from command line argument list +.Sh SYNOPSIS +.In getopt.h +.Vt extern char *optarg; +.Vt extern int optind; +.Vt extern int optopt; +.Vt extern int opterr; +.Vt extern int optreset; +.Ft int +.Fn getopt_long "int argc" "char * const *argv" "const char *optstring" "const struct option *longopts" "int *longindex" +.Ft int +.Fn getopt_long_only "int argc" "char * const *argv" "const char *optstring" "const struct option *longopts" "int *longindex" +.Sh DESCRIPTION +The +.Fn getopt_long +function is similar to +.Xr getopt 3 +but it accepts options in two forms: words and characters. +The +.Fn getopt_long +function provides a superset of the functionality of +.Xr getopt 3 . +.Fn getopt_long +can be used in two ways. +In the first way, every long option understood by the program has a +corresponding short option, and the option structure is only used to +translate from long options to short options. +When used in this fashion, +.Fn getopt_long +behaves identically to +.Xr getopt 3 . +This is a good way to add long option processing to an existing program +with the minimum of rewriting. +.Pp +In the second mechanism, a long option sets a flag in the +.Fa option +structure passed, or will store a pointer to the command line argument +in the +.Fa option +structure passed to it for options that take arguments. +Additionally, the long option's argument may be specified as a single +argument with an equal sign, e.g. +.Bd -literal -offset indent +$ myprogram --myoption=somevalue +.Ed +.Pp +When a long option is processed, the call to +.Fn getopt_long +will return 0. +For this reason, long option processing without +shortcuts is not backwards compatible with +.Xr getopt 3 . +.Pp +It is possible to combine these methods, providing for long options +processing with short option equivalents for some options. +Less frequently used options would be processed as long options only. +.Pp +Abbreviated long option names are accepted when +.Fn getopt_long +processes long options if the abbreviation is unique. +An exact match is always preferred for a defined long option. +.Pp +The +.Fn getopt_long +call requires an array to be initialized describing the long +options. +Each element of the array is a structure: +.Bd -literal -offset indent +struct option { + char *name; + int has_arg; + int *flag; + int val; +}; +.Ed +.Pp +The +.Fa name +field should contain the option name without the leading double dash. +.Pp +The +.Fa has_arg +field should be one of: +.Pp +.Bl -tag -width "optional_argument" -compact -offset indent +.It Dv no_argument +no argument to the option is expected. +.It Dv required_argument +an argument to the option is required. +.It Dv optional_argument +an argument to the option may be presented. +.El +.Pp +If +.Fa flag +is not +.Dv NULL , +then the integer pointed to by it will be set to the value in the +.Fa val +field. +If the +.Fa flag +field is +.Dv NULL , +then the +.Fa val +field will be returned. +Setting +.Fa flag +to +.Dv NULL +and setting +.Fa val +to the corresponding short option will make this function act just +like +.Xr getopt 3 . +.Pp +If the +.Fa longindex +field is not +.Dv NULL , +then the integer pointed to by it will be set to the index of the long +option relative to +.Fa longopts . +.Pp +The last element of the +.Fa longopts +array has to be filled with zeroes. +.Pp +The +.Fn getopt_long_only +function behaves identically to +.Fn getopt_long +with the exception that long options may start with +.Sq - +in addition to +.Sq -- . +If an option starting with +.Sq - +does not match a long option but does match a single-character option, +the single-character option is returned. +.Sh RETURN VALUES +If the +.Fa flag +field in +.Li struct option +is +.Dv NULL , +.Fn getopt_long +and +.Fn getopt_long_only +return the value specified in the +.Fa val +field, which is usually just the corresponding short option. +If +.Fa flag +is not +.Dv NULL , +these functions return 0 and store +.Fa val +in the location pointed to by +.Fa flag . +These functions return +.Sq \&: +if there was a missing option argument, +.Sq \&? +if the user specified an unknown or ambiguous option, and +\-1 when the argument list has been exhausted. +.Sh IMPLEMENTATION DIFFERENCES +This section describes differences to the GNU implementation +found in glibc-2.1.3: +.Bl -bullet +.It +handling of +.Ql - +within the option string (not the first character): +.Bl -tag -width "OpenBSD" +.It GNU +treats a +.Ql - +on the command line as a non-argument. +.It OpenBSD +a +.Ql - +within the option string matches a +.Ql - +(single dash) on the command line. +This functionality is provided for backward compatibility with +programs, such as +.Xr su 1 , +that use +.Ql - +as an option flag. +This practice is wrong, and should not be used in any current development. +.El +.It +handling of +.Ql :: +in the option string in the presence of +.Ev POSIXLY_CORRECT : +.Bl -tag -width "OpenBSD" +.It Both +GNU and +.Ox +ignore +.Ev POSIXLY_CORRECT +here and take +.Ql :: +to mean the preceding option takes an optional argument. +.El +.It +return value in case of missing argument if first character +(after +.Ql + +or +.Ql - ) +in the option string is not +.Ql \&: : +.Bl -tag -width "OpenBSD" +.It GNU +returns +.Ql \&? +.It OpenBSD +returns +.Ql \&: +(since +.Ox Ns 's +.Xr getopt 3 +does). +.El +.It +handling of +.Ql --a +in +.Xr getopt 3 : +.Bl -tag -width "OpenBSD" +.It GNU +parses this as option +.Ql - , +option +.Ql a . +.It OpenBSD +parses this as +.Ql -- , +and returns \-1 (ignoring the +.Ql a ) +(because the original +.Fn getopt +did.) +.El +.It +setting of +.Va optopt +for long options with +.Va flag +.No non- Ns Dv NULL : +.Bl -tag -width "OpenBSD" +.It GNU +sets +.Va optopt +to +.Va val . +.It OpenBSD +sets +.Va optopt +to 0 (since +.Va val +would never be returned). +.El +.It +handling of +.Ql -W +with +.Ql W; +in the option string in +.Xr getopt 3 +(not +.Fn getopt_long ) : +.Bl -tag -width "OpenBSD" +.It GNU +causes a segmentation fault. +.It OpenBSD +no special handling is done; +.Ql W; +is interpreted as two separate options, neither of which take an argument. +.El +.It +setting of +.Va optarg +for long options without an argument that are invoked via +.Ql -W +(with +.Ql W; +in the option string): +.Bl -tag -width "OpenBSD" +.It GNU +sets +.Va optarg +to the option name (the argument of +.Ql -W ) . +.It OpenBSD +sets +.Va optarg +to +.Dv NULL +(the argument of the long option). +.El +.It +handling of +.Ql -W +with an argument that is not (a prefix to) a known long option +(with +.Ql W; +in the option string): +.Bl -tag -width "OpenBSD" +.It GNU +returns +.Ql -W +with +.Va optarg +set to the unknown option. +.It OpenBSD +treats this as an error (unknown option) and returns +.Ql \&? +with +.Va optopt +set to 0 and +.Va optarg +set to +.Dv NULL +(as GNU's man page documents). +.El +.It +The error messages are different. +.It +.Ox +does not permute the argument vector at the same points in +the calling sequence as GNU does. +The aspects normally used by the caller +(ordering after \-1 is returned, value of +.Va optind +relative to current positions) are the same, though. +(We do fewer variable swaps.) +.El +.Sh ENVIRONMENT +.Bl -tag -width Ev +.It Ev POSIXLY_CORRECT +If set, option processing stops when the first non-option is found and +a leading +.Sq + +in the +.Ar optstring +is ignored. +.El +.Sh EXAMPLES +.Bd -literal +int bflag, ch, fd; +int daggerset; + +/* options descriptor */ +static struct option longopts[] = { + { "buffy", no_argument, NULL, 'b' }, + { "fluoride", required_argument, NULL, 'f' }, + { "daggerset", no_argument, &daggerset, 1 }, + { NULL, 0, NULL, 0 } +}; + +bflag = 0; +while ((ch = getopt_long(argc, argv, "bf:", longopts, NULL)) != -1) + switch (ch) { + case 'b': + bflag = 1; + break; + case 'f': + if ((fd = open(optarg, O_RDONLY, 0)) == -1) + err(1, "unable to open %s", optarg); + break; + case 0: + if (daggerset) + fprintf(stderr, "Buffy will use her dagger to " + "apply fluoride to dracula's teeth\en"); + break; + default: + usage(); + } +argc -= optind; +argv += optind; +.Ed +.Sh SEE ALSO +.Xr getopt 3 +.Sh HISTORY +The +.Fn getopt_long +and +.Fn getopt_long_only +functions first appeared in GNU libiberty. +This implementation first appeared in +.Ox 3.3 . +.Sh BUGS +The +.Ar argv +argument is not really +.Dv const +as its elements may be permuted (unless +.Ev POSIXLY_CORRECT +is set). diff --git a/lib/libopenbsd/stdlib/getopt_long.c b/lib/libopenbsd/stdlib/getopt_long.c @@ -0,0 +1,523 @@ +/* $OpenBSD: getopt_long.c,v 1.29 2015/10/01 02:32:07 guenther Exp $ */ +/* $NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $ */ + +/* + * Copyright (c) 2002 Todd C. Miller <Todd.Miller@courtesan.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Dieter Baron and Thomas Klausner. + * + * 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``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 FOUNDATION OR CONTRIBUTORS + * 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. + */ + +#include <err.h> +#include <errno.h> +#include <getopt.h> +#include <stdlib.h> +#include <string.h> + +int opterr = 1; /* if error message should be printed */ +int optind = 1; /* index into parent argv vector */ +int optopt = '?'; /* character checked for validity */ +int optreset; /* reset getopt */ +char *optarg; /* argument associated with option */ + +#if 0 +/* DEF_* only work on initialized (non-COMMON) variables */ +DEF_WEAK(opterr); +DEF_WEAK(optind); +DEF_WEAK(optopt); +#endif + +#define PRINT_ERROR ((opterr) && (*options != ':')) + +#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */ +#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */ +#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */ + +/* return values */ +#define BADCH (int)'?' +#define BADARG ((*options == ':') ? (int)':' : (int)'?') +#define INORDER (int)1 + +#define EMSG "" + +static int getopt_internal(int, char * const *, const char *, + const struct option *, int *, int); +static int parse_long_options(char * const *, const char *, + const struct option *, int *, int, int); +static int gcd(int, int); +static void permute_args(int, int, int, char * const *); + +static char *place = EMSG; /* option letter processing */ + +/* XXX: set optreset to 1 rather than these two */ +static int nonopt_start = -1; /* first non option argument (for permute) */ +static int nonopt_end = -1; /* first option after non options (for permute) */ + +/* Error messages */ +static const char recargchar[] = "option requires an argument -- %c"; +static const char recargstring[] = "option requires an argument -- %s"; +static const char ambig[] = "ambiguous option -- %.*s"; +static const char noarg[] = "option doesn't take an argument -- %.*s"; +static const char illoptchar[] = "unknown option -- %c"; +static const char illoptstring[] = "unknown option -- %s"; + +/* + * Compute the greatest common divisor of a and b. + */ +static int +gcd(int a, int b) +{ + int c; + + c = a % b; + while (c != 0) { + a = b; + b = c; + c = a % b; + } + + return (b); +} + +/* + * Exchange the block from nonopt_start to nonopt_end with the block + * from nonopt_end to opt_end (keeping the same order of arguments + * in each block). + */ +static void +permute_args(int panonopt_start, int panonopt_end, int opt_end, + char * const *nargv) +{ + int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos; + char *swap; + + /* + * compute lengths of blocks and number and size of cycles + */ + nnonopts = panonopt_end - panonopt_start; + nopts = opt_end - panonopt_end; + ncycle = gcd(nnonopts, nopts); + cyclelen = (opt_end - panonopt_start) / ncycle; + + for (i = 0; i < ncycle; i++) { + cstart = panonopt_end+i; + pos = cstart; + for (j = 0; j < cyclelen; j++) { + if (pos >= panonopt_end) + pos -= nnonopts; + else + pos += nopts; + swap = nargv[pos]; + ((char **)nargv)[pos] = nargv[cstart]; + ((char **)nargv)[cstart] = swap; + } + } +} + +/* + * parse_long_options -- + * Parse long options in argc/argv argument vector. + * Returns -1 if short_too is set and the option does not match long_options. + */ +static int +parse_long_options(char * const *nargv, const char *options, + const struct option *long_options, int *idx, int short_too, int flags) +{ + char *current_argv, *has_equal; + size_t current_argv_len; + int i, match, exact_match, second_partial_match; + + current_argv = place; + match = -1; + exact_match = 0; + second_partial_match = 0; + + optind++; + + if ((has_equal = strchr(current_argv, '=')) != NULL) { + /* argument found (--option=arg) */ + current_argv_len = has_equal - current_argv; + has_equal++; + } else + current_argv_len = strlen(current_argv); + + for (i = 0; long_options[i].name; i++) { + /* find matching long option */ + if (strncmp(current_argv, long_options[i].name, + current_argv_len)) + continue; + + if (strlen(long_options[i].name) == current_argv_len) { + /* exact match */ + match = i; + exact_match = 1; + break; + } + /* + * If this is a known short option, don't allow + * a partial match of a single character. + */ + if (short_too && current_argv_len == 1) + continue; + + if (match == -1) /* first partial match */ + match = i; + else if ((flags & FLAG_LONGONLY) || + long_options[i].has_arg != long_options[match].has_arg || + long_options[i].flag != long_options[match].flag || + long_options[i].val != long_options[match].val) + second_partial_match = 1; + } + if (!exact_match && second_partial_match) { + /* ambiguous abbreviation */ + if (PRINT_ERROR) + warnx(ambig, (int)current_argv_len, current_argv); + optopt = 0; + return (BADCH); + } + if (match != -1) { /* option found */ + if (long_options[match].has_arg == no_argument + && has_equal) { + if (PRINT_ERROR) + warnx(noarg, (int)current_argv_len, + current_argv); + /* + * XXX: GNU sets optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; + return (BADARG); + } + if (long_options[match].has_arg == required_argument || + long_options[match].has_arg == optional_argument) { + if (has_equal) + optarg = has_equal; + else if (long_options[match].has_arg == + required_argument) { + /* + * optional argument doesn't use next nargv + */ + optarg = nargv[optind++]; + } + } + if ((long_options[match].has_arg == required_argument) + && (optarg == NULL)) { + /* + * Missing argument; leading ':' indicates no error + * should be generated. + */ + if (PRINT_ERROR) + warnx(recargstring, + current_argv); + /* + * XXX: GNU sets optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; + --optind; + return (BADARG); + } + } else { /* unknown option */ + if (short_too) { + --optind; + return (-1); + } + if (PRINT_ERROR) + warnx(illoptstring, current_argv); + optopt = 0; + return (BADCH); + } + if (idx) + *idx = match; + if (long_options[match].flag) { + *long_options[match].flag = long_options[match].val; + return (0); + } else + return (long_options[match].val); +} + +/* + * getopt_internal -- + * Parse argc/argv argument vector. Called by user level routines. + */ +static int +getopt_internal(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx, int flags) +{ + char *oli; /* option letter list index */ + int optchar, short_too; + static int posixly_correct = -1; + + if (options == NULL) + return (-1); + + /* + * XXX Some GNU programs (like cvs) set optind to 0 instead of + * XXX using optreset. Work around this braindamage. + */ + if (optind == 0) + optind = optreset = 1; + + /* + * Disable GNU extensions if POSIXLY_CORRECT is set or options + * string begins with a '+'. + */ + if (posixly_correct == -1 || optreset) + posixly_correct = (getenv("POSIXLY_CORRECT") != NULL); + if (*options == '-') + flags |= FLAG_ALLARGS; + else if (posixly_correct || *options == '+') + flags &= ~FLAG_PERMUTE; + if (*options == '+' || *options == '-') + options++; + + optarg = NULL; + if (optreset) + nonopt_start = nonopt_end = -1; +start: + if (optreset || !*place) { /* update scanning pointer */ + optreset = 0; + if (optind >= nargc) { /* end of argument vector */ + place = EMSG; + if (nonopt_end != -1) { + /* do permutation, if we have to */ + permute_args(nonopt_start, nonopt_end, + optind, nargv); + optind -= nonopt_end - nonopt_start; + } + else if (nonopt_start != -1) { + /* + * If we skipped non-options, set optind + * to the first of them. + */ + optind = nonopt_start; + } + nonopt_start = nonopt_end = -1; + return (-1); + } + if (*(place = nargv[optind]) != '-' || + (place[1] == '\0' && strchr(options, '-') == NULL)) { + place = EMSG; /* found non-option */ + if (flags & FLAG_ALLARGS) { + /* + * GNU extension: + * return non-option as argument to option 1 + */ + optarg = nargv[optind++]; + return (INORDER); + } + if (!(flags & FLAG_PERMUTE)) { + /* + * If no permutation wanted, stop parsing + * at first non-option. + */ + return (-1); + } + /* do permutation */ + if (nonopt_start == -1) + nonopt_start = optind; + else if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + optind, nargv); + nonopt_start = optind - + (nonopt_end - nonopt_start); + nonopt_end = -1; + } + optind++; + /* process next argument */ + goto start; + } + if (nonopt_start != -1 && nonopt_end == -1) + nonopt_end = optind; + + /* + * If we have "-" do nothing, if "--" we are done. + */ + if (place[1] != '\0' && *++place == '-' && place[1] == '\0') { + optind++; + place = EMSG; + /* + * We found an option (--), so if we skipped + * non-options, we have to permute. + */ + if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + optind, nargv); + optind -= nonopt_end - nonopt_start; + } + nonopt_start = nonopt_end = -1; + return (-1); + } + } + + /* + * Check long options if: + * 1) we were passed some + * 2) the arg is not just "-" + * 3) either the arg starts with -- we are getopt_long_only() + */ + if (long_options != NULL && place != nargv[optind] && + (*place == '-' || (flags & FLAG_LONGONLY))) { + short_too = 0; + if (*place == '-') + place++; /* --foo long option */ + else if (*place != ':' && strchr(options, *place) != NULL) + short_too = 1; /* could be short option too */ + + optchar = parse_long_options(nargv, options, long_options, + idx, short_too, flags); + if (optchar != -1) { + place = EMSG; + return (optchar); + } + } + + if ((optchar = (int)*place++) == (int)':' || + (optchar == (int)'-' && *place != '\0') || + (oli = strchr(options, optchar)) == NULL) { + /* + * If the user specified "-" and '-' isn't listed in + * options, return -1 (non-option) as per POSIX. + * Otherwise, it is an unknown option character (or ':'). + */ + if (optchar == (int)'-' && *place == '\0') + return (-1); + if (!*place) + ++optind; + if (PRINT_ERROR) + warnx(illoptchar, optchar); + optopt = optchar; + return (BADCH); + } + if (long_options != NULL && optchar == 'W' && oli[1] == ';') { + /* -W long-option */ + if (*place) /* no space */ + /* NOTHING */; + else if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + warnx(recargchar, optchar); + optopt = optchar; + return (BADARG); + } else /* white space */ + place = nargv[optind]; + optchar = parse_long_options(nargv, options, long_options, + idx, 0, flags); + place = EMSG; + return (optchar); + } + if (*++oli != ':') { /* doesn't take argument */ + if (!*place) + ++optind; + } else { /* takes (optional) argument */ + optarg = NULL; + if (*place) /* no white space */ + optarg = place; + else if (oli[1] != ':') { /* arg not optional */ + if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + warnx(recargchar, optchar); + optopt = optchar; + return (BADARG); + } else + optarg = nargv[optind]; + } + place = EMSG; + ++optind; + } + /* dump back option letter */ + return (optchar); +} + +/* + * getopt -- + * Parse argc/argv argument vector. + * + * [eventually this will replace the BSD getopt] + */ +int +getopt(int nargc, char * const *nargv, const char *options) +{ + + /* + * We don't pass FLAG_PERMUTE to getopt_internal() since + * the BSD getopt(3) (unlike GNU) has never done this. + * + * Furthermore, since many privileged programs call getopt() + * before dropping privileges it makes sense to keep things + * as simple (and bug-free) as possible. + */ + return (getopt_internal(nargc, nargv, options, NULL, NULL, 0)); +} + +/* + * getopt_long -- + * Parse argc/argv argument vector. + */ +int +getopt_long(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx) +{ + + return (getopt_internal(nargc, nargv, options, long_options, idx, + FLAG_PERMUTE)); +} + +/* + * getopt_long_only -- + * Parse argc/argv argument vector. + */ +int +getopt_long_only(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx) +{ + + return (getopt_internal(nargc, nargv, options, long_options, idx, + FLAG_PERMUTE|FLAG_LONGONLY)); +}