diff options
Diffstat (limited to 'common/argparse.c')
-rw-r--r-- | common/argparse.c | 2415 |
1 files changed, 1747 insertions, 668 deletions
diff --git a/common/argparse.c b/common/argparse.c index c04cf830d..fff720c19 100644 --- a/common/argparse.c +++ b/common/argparse.c @@ -29,9 +29,8 @@ * if not, see <https://gnu.org/licenses/>. */ -/* This file may be used as part of GnuPG or standalone. A GnuPG - build is detected by the presence of the macro GNUPG_MAJOR_VERSION. - Some feature are only availalbe in the GnuPG build mode. +/* This is a modified version of gpgrt/libgpg-error src/argparse.c. + * We use this to require a dependency on a newer gpgrt version. */ #ifdef HAVE_CONFIG_H @@ -45,232 +44,115 @@ #include <stdarg.h> #include <limits.h> #include <errno.h> - -#ifdef GNUPG_MAJOR_VERSION -# include "util.h" -# include "common-defs.h" -# include "i18n.h" -# include "mischelp.h" -# include "stringhelp.h" -# include "logging.h" -# include "utf8conv.h" -#endif /*GNUPG_MAJOR_VERSION*/ - +#include <unistd.h> +#include <time.h> + +#include "util.h" +#include "common-defs.h" +#include "i18n.h" +#include "mischelp.h" +#include "stringhelp.h" +#include "logging.h" +#include "utf8conv.h" +#include "sysutils.h" #include "argparse.h" -/* GnuPG uses GPLv3+ but a standalone version of this defaults to - GPLv2+ because that is the license of this file. Change this if - you include it in a program which uses GPLv3. If you don't want to - set a copyright string for your usage() you may also hardcode it - here. */ -#ifndef GNUPG_MAJOR_VERSION - -# define ARGPARSE_GPL_VERSION 2 -# define ARGPARSE_CRIGHT_STR "Copyright (C) YEAR NAME" - -#else /* Used by GnuPG */ - -/* GnuPG has always been a part of the GNU project and thus we have - * shown the FSF as holder of the copyright. We continue to do so for - * the reason that without the FSF the free software used all over the - * world would not have come into existence. However, under Windows - * we print a different copyright string with --version because the - * copyright assignments of g10 Code and Werner Koch were terminated - * many years ago, g10 Code is still the major contributor to the - * code, and Windows is not an FSF endorsed platform. Note that the - * list of copyright holders can be found in the AUTHORS file. */ - -# define ARGPARSE_GPL_VERSION 3 -# ifdef HAVE_W32_SYSTEM -# define ARGPARSE_CRIGHT_STR "Copyright (C) 2020 g10 Code GmbH" -# else -# define ARGPARSE_CRIGHT_STR "Copyright (C) 2020 Free Software Foundation, Inc." -# endif - -#endif /*GNUPG_MAJOR_VERSION*/ - -/* Replacements for standalone builds. */ -#ifndef GNUPG_MAJOR_VERSION -# ifndef _ -# define _(a) (a) -# endif -# ifndef DIM -# define DIM(v) (sizeof(v)/sizeof((v)[0])) -# endif -# define xtrymalloc(a) malloc ((a)) -# define xtryrealloc(a,b) realloc ((a), (b)) -# define xtrystrdup(a) strdup ((a)) -# define xfree(a) free ((a)) -# define log_error my_log_error -# define log_bug my_log_bug -# define trim_spaces(a) my_trim_spaces ((a)) -# define map_static_macro_string(a) (a) -#endif /*!GNUPG_MAJOR_VERSION*/ - - -#define ARGPARSE_STR(v) #v -#define ARGPARSE_STR2(v) ARGPARSE_STR(v) - - -/* Replacements for standalone builds. */ -#ifndef GNUPG_MAJOR_VERSION -static void -my_log_error (const char *fmt, ...) -{ - va_list arg_ptr ; - va_start (arg_ptr, fmt); - fprintf (stderr, "%s: ", strusage (11)); - vfprintf (stderr, fmt, arg_ptr); - va_end (arg_ptr); -} +/* Optional handler to write strings. See gnupg_set_usage_outfnc. */ +static int (*custom_outfnc) (int, const char *); -static void -my_log_bug (const char *fmt, ...) -{ - va_list arg_ptr ; - va_start (arg_ptr, fmt); - fprintf (stderr, "%s: Ohhhh jeeee: ", strusage (11)); - vfprintf (stderr, fmt, arg_ptr); - va_end (arg_ptr); - abort (); -} +#if USE_INTERNAL_ARGPARSE -/* Return true if the native charset is utf-8. */ -static int -is_native_utf8 (void) -{ - return 1; -} +/* The almost always needed user handler for strusage. */ +static const char *(*strusage_handler)( int ) = NULL; +/* Optional handler to map strings. See gnupg_set_fixed_string_mapper. */ +static const char *(*fixed_string_mapper)(const char*); -static char * -my_trim_spaces (char *str) -{ - char *string, *p, *mark; - string = str; - /* Find first non space character. */ - for (p=string; *p && isspace (*(unsigned char*)p) ; p++) - ; - /* Move characters. */ - for ((mark = NULL); (*string = *p); string++, p++) - if (isspace (*(unsigned char*)p)) - { - if (!mark) - mark = string; - } - else - mark = NULL; - if (mark) - *mark = '\0' ; /* Remove trailing spaces. */ +/* Hidden argparse flag used to mark the object as initialized. */ +#define ARGPARSE_FLAG__INITIALIZED (1u << ((8*4)-1)) - return str ; -} +/* Special short options which are auto-inserterd. Must fit into an + * unsigned short. */ +#define ARGPARSE_SHORTOPT_HELP 32768 +#define ARGPARSE_SHORTOPT_VERSION 32769 +#define ARGPARSE_SHORTOPT_WARRANTY 32770 +#define ARGPARSE_SHORTOPT_DUMP_OPTIONS 32771 +#define ARGPARSE_SHORTOPT_DUMP_OPTTBL 32772 -#endif /*!GNUPG_MAJOR_VERSION*/ +/* The malloced configuration directories or NULL. */ +static struct +{ + char *user; + char *sys; +} confdir; + + +/* The states for the gnupg_argparser machinery. */ +enum argparser_states + { + STATE_init = 0, + STATE_open_sys, + STATE_open_user, + STATE_open_cmdline, + STATE_read_sys, + STATE_read_user, + STATE_read_cmdline, + STATE_finished + }; -/********************************* - * @Summary arg_parse - * #include "argparse.h" - * - * typedef struct { - * char *argc; pointer to argc (value subject to change) - * char ***argv; pointer to argv (value subject to change) - * unsigned flags; Global flags (DO NOT CHANGE) - * int err; print error about last option - * 1 = warning, 2 = abort - * int r_opt; return option - * int r_type; type of return value (0 = no argument found) - * union { - * int ret_int; - * long ret_long - * ulong ret_ulong; - * char *ret_str; - * } r; Return values - * struct { - * int idx; - * const char *last; - * void *aliases; - * } internal; DO NOT CHANGE - * } ARGPARSE_ARGS; - * - * typedef struct { - * int short_opt; - * const char *long_opt; - * unsigned flags; - * } ARGPARSE_OPTS; - * - * int arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts ); - * - * @Description - * This is my replacement for getopt(). See the example for a typical usage. - * Global flags are: - * Bit 0 : Do not remove options form argv - * Bit 1 : Do not stop at last option but return other args - * with r_opt set to -1. - * Bit 2 : Assume options and real args are mixed. - * Bit 3 : Do not use -- to stop option processing. - * Bit 4 : Do not skip the first arg. - * Bit 5 : allow usage of long option with only one dash - * Bit 6 : ignore --version - * all other bits must be set to zero, this value is modified by the - * function, so assume this is write only. - * Local flags (for each option): - * Bit 2-0 : 0 = does not take an argument - * 1 = takes int argument - * 2 = takes string argument - * 3 = takes long argument - * 4 = takes ulong argument - * Bit 3 : argument is optional (r_type will the be set to 0) - * Bit 4 : allow 0x etc. prefixed values. - * Bit 6 : Ignore this option - * Bit 7 : This is a command and not an option - * You stop the option processing by setting opts to NULL, the function will - * then return 0. - * @Return Value - * Returns the args.r_opt or 0 if ready - * r_opt may be -2/-7 to indicate an unknown option/command. - * @See Also - * ArgExpand - * @Notes - * You do not need to process the options 'h', '--help' or '--version' - * because this function includes standard help processing; but if you - * specify '-h', '--help' or '--version' you have to do it yourself. - * The option '--' stops argument processing; if bit 1 is set the function - * continues to return normal arguments. - * To process float args or unsigned args you must use a string args and do - * the conversion yourself. - * @Example - * - * ARGPARSE_OPTS opts[] = { - * { 'v', "verbose", 0 }, - * { 'd', "debug", 0 }, - * { 'o', "output", 2 }, - * { 'c', "cross-ref", 2|8 }, - * { 'm', "my-option", 1|8 }, - * { 300, "ignored-long-option, ARGPARSE_OP_IGNORE}, - * { 500, "have-no-short-option-for-this-long-option", 0 }, - * {0} }; - * ARGPARSE_ARGS pargs = { &argc, &argv, 0 } - * - * while( ArgParse( &pargs, &opts) ) { - * switch( pargs.r_opt ) { - * case 'v': opt.verbose++; break; - * case 'd': opt.debug++; break; - * case 'o': opt.outfile = pargs.r.ret_str; break; - * case 'c': opt.crf = pargs.r_type? pargs.r.ret_str:"a.crf"; break; - * case 'm': opt.myopt = pargs.r_type? pargs.r.ret_int : 1; break; - * case 500: opt.a_long_one++; break - * default : pargs.err = 1; break; -- force warning output -- - * } - * } - * if( argc > 1 ) - * log_fatal( "Too many args"); - * - */ +/* An internal object used to store the user provided option table and + * some meta information. */ +typedef struct +{ + unsigned short short_opt; + unsigned short ordinal; /* (for --help) */ + unsigned int flags; + const char *long_opt; /* Points into the user provided table. */ + const char *description; /* Points into the user provided table. */ + unsigned int forced:1; /* Forced to use the sysconf value. */ + unsigned int ignore:1; /* Ignore this option everywhere but in + * the sysconf file. */ + unsigned int explicit_ignore:1; /* Ignore was explicitly set. */ +} opttable_t; + + +/* Internal object of the public gnupg_argparse_t object. */ +struct _argparse_internal_s +{ + int idx; /* Note that this is saved and restored in gnupg_argparser. */ + int inarg; /* (index into args) */ + unsigned int verbose:1; /* Print diagnostics. */ + unsigned int stopped:1; /* Option processing has stopped. */ + unsigned int in_sysconf:1; /* Processing global config file. */ + unsigned int mark_forced:1; /* Mark options as forced. */ + unsigned int mark_ignore:1; /* Mark options as to be ignored. */ + unsigned int explicit_ignore:1; /* Option has explicitly been set + * to ignore or unignore. */ + unsigned int ignore_all_seen:1; /* [ignore-all] has been seen. */ + unsigned int user_seen:1; /* A [user] has been seen. */ + unsigned int user_wildcard:1; /* A [user *] has been seen. */ + unsigned int user_any_active:1; /* Any user section was active. */ + unsigned int user_active:1; /* User section active. */ + unsigned int explicit_confopt:1; /* A conffile option has been given. */ + char *explicit_conffile; /* Malloced name of an explicit + * conffile. */ + char *username; /* Malloced current user name. */ + unsigned int opt_flags; /* Current option flags. */ + enum argparser_states state; /* State of the gnupg_argparser. */ + const char *last; + void *aliases; + const void *cur_alias; + void *iio_list; + estream_t conffp; + char *confname; + opttable_t *opts; /* Malloced option table. */ + unsigned int nopts; /* Number of items in OPTS. */ +}; + typedef struct alias_def_s *ALIAS_DEF; struct alias_def_s { @@ -289,26 +171,103 @@ struct iio_item_def_s char name[1]; /* String with the long option name. */ }; -static const char *(*strusage_handler)( int ) = NULL; -static int (*custom_outfnc) (int, const char *); -static int set_opt_arg(ARGPARSE_ARGS *arg, unsigned flags, char *s); -static void show_help(ARGPARSE_OPTS *opts, unsigned flags); -static void show_version(void); -static int writestrings (int is_error, const char *string, ...) -#if __GNUC__ >= 4 - __attribute__ ((sentinel(0))) -#endif - ; +static int set_opt_arg (gnupg_argparse_t *arg, unsigned int flags, char *s); +static void show_help (opttable_t *opts, unsigned int nopts,unsigned int flags); +static void show_version (void); +static void dump_option_table (gnupg_argparse_t *arg); +static int writestrings (int is_error, const char *string, + ...) GPGRT_ATTR_SENTINEL(0); + +static int arg_parse (gnupg_argparse_t *arg, gnupg_opt_t *opts, int no_init); + + + +/* Set a function to write strings which is then used instead of + * estream. The first arg of that function is MODE and the second the + * STRING to write. A mode of 1 is used for writing to stdout and a + * mode of 2 to write to stderr. Other modes are reserved and should + * not output anything. A NULL for STRING requests a flush. */ +void +gnupg_set_usage_outfnc (int (*f)(int, const char *)) +{ + custom_outfnc = f; +} +/* Register function F as a string mapper which takes a string as + * argument, replaces known "@FOO@" style macros and returns a new + * fixed string. Warning: The input STRING must have been allocated + * statically. */ void -argparse_register_outfnc (int (*fnc)(int, const char *)) +gnupg_set_fixed_string_mapper (const char *(*f)(const char*)) { - custom_outfnc = fnc; + fixed_string_mapper = f; } +/* Register a configuration directory for use by the argparse + * functions. The defined values for WHAT are: + * + * GNUPG_CONFDIR_SYS The systems's configuration dir. + * The default is /etc + * + * GNUPG_CONFDIR_USER The user's configuration directory. + * The default is $HOME. + * + * A trailing slash is ignored; to have the function lookup + * configuration files in the current directory, use ".". There is no + * error return; more configuraion values may be added in future + * revisions of this library. + */ +void +gnupg_set_confdir (int what, const char *name) +{ + char *buf, *p; + + if (what == GNUPG_CONFDIR_SYS) + { + xfree (confdir.sys); + buf = confdir.sys = xtrystrdup (name); + } + else if (what == GNUPG_CONFDIR_USER) + { + xfree (confdir.user); + buf = confdir.user = xtrystrdup (name); + } + else + return; + + if (!buf) + log_fatal ("out of core in %s\n", __func__); +#ifdef HAVE_W32_SYSTEM + for (p=buf; *p; p++) + if (*p == '\\') + *p = '/'; +#endif + /* Strip trailing slashes unless buf is "/" or any other single char + * string. */ + if (*buf) + { + for (p=buf + strlen (buf)-1; p > buf; p--) + if (*p == '/') + *p = 0; + else + break; + } +} + + + +static const char * +map_fixed_string (const char *string) +{ + return fixed_string_mapper? fixed_string_mapper (string) : string; +} + +#endif /* USE_INTERNAL_ARGPARSE */ + + /* Write STRING and all following const char * arguments either to stdout or, if IS_ERROR is set, to stderr. The list of strings must be terminated by a NULL. */ @@ -324,7 +283,7 @@ writestrings (int is_error, const char *string, ...) s = string; va_start (arg_ptr, string); do - { + { /* Fixme: Swicth to estream? */ if (custom_outfnc) custom_outfnc (is_error? 2:1, s); else @@ -348,32 +307,214 @@ flushstrings (int is_error) } +#if USE_INTERNAL_ARGPARSE + static void -initialize( ARGPARSE_ARGS *arg, const char *filename, unsigned *lineno ) +deinitialize (gnupg_argparse_t *arg) { - if( !(arg->flags & (1<<15)) ) + if (arg->internal) { + xfree (arg->internal->username); + xfree (arg->internal->explicit_conffile); + xfree (arg->internal->opts); + xfree (arg->internal); + arg->internal = NULL; + } + + arg->flags &= ARGPARSE_FLAG__INITIALIZED; + arg->lineno = 0; + arg->err = 0; +} + +/* Our own exit handler to clean up used memory. */ +static void +my_exit (gnupg_argparse_t *arg, int code) +{ + deinitialize (arg); + exit (code); +} + + +static gpg_err_code_t +initialize (gnupg_argparse_t *arg, gnupg_opt_t *opts, estream_t fp) +{ + /* We use a dedicated flag to detect whether *ARG has been + * initialized. This is because the old version of that struct, as + * used in GnuPG, had no requirement to zero out all fields of the + * object and existing code still sets only argc,argv and flags. */ + if (!(arg->flags & ARGPARSE_FLAG__INITIALIZED) + || (arg->flags & ARGPARSE_FLAG_RESET) + || !arg->internal) + { + /* Allocate internal data. */ + if (!(arg->flags & ARGPARSE_FLAG__INITIALIZED) || !arg->internal) + { + arg->internal = xtrymalloc (sizeof *arg->internal); + if (!arg->internal) + return gpg_err_code_from_syserror (); + arg->flags |= ARGPARSE_FLAG__INITIALIZED; /* Mark as initialized. */ + } + else if (arg->internal->opts) + xfree (arg->internal->opts); + arg->internal->opts = NULL; + arg->internal->nopts = 0; + /* Initialize this instance. */ - arg->internal.idx = 0; - arg->internal.last = NULL; - arg->internal.inarg = 0; - arg->internal.stopped = 0; - arg->internal.aliases = NULL; - arg->internal.cur_alias = NULL; - arg->internal.iio_list = NULL; + arg->internal->idx = 0; + arg->internal->last = NULL; + arg->internal->inarg = 0; + arg->internal->stopped = 0; + arg->internal->in_sysconf = 0; + arg->internal->user_seen = 0; + arg->internal->user_wildcard = 0; + arg->internal->user_any_active = 0; + arg->internal->user_active = 0; + arg->internal->username = NULL; + arg->internal->mark_forced = 0; + arg->internal->mark_ignore = 0; + arg->internal->explicit_ignore = 0; + arg->internal->ignore_all_seen = 0; + arg->internal->explicit_confopt = 0; + arg->internal->explicit_conffile = NULL; + arg->internal->opt_flags = 0; + arg->internal->state = STATE_init; + arg->internal->aliases = NULL; + arg->internal->cur_alias = NULL; + arg->internal->iio_list = NULL; + arg->internal->conffp = NULL; + arg->internal->confname = NULL; + + /* Clear the copy of the option list. */ + /* Clear the error indicator. */ arg->err = 0; - arg->flags |= 1<<15; /* Mark as initialized. */ + + /* Usually an option file will be parsed from the start. + * However, we do not open the stream and thus we have no way to + * know the current lineno. Using this flag we can allow the + * user to provide a lineno which we don't reset. */ + if (fp || arg->internal->conffp || !(arg->flags & ARGPARSE_FLAG_NOLINENO)) + arg->lineno = 0; + + /* Need to clear the reset request. */ + arg->flags &= ~ARGPARSE_FLAG_RESET; + + /* Check initial args. */ if ( *arg->argc < 0 ) - log_bug ("invalid argument for arg_parse\n"); + log_bug ("invalid argument passed to gnupg_argparse\n"); + } + /* Create an array with pointers to the provided list of options. + * Keeping a copy is useful to sort that array and thus do a binary + * search and to allow for extra space at the end to insert the + * hidden options. An ARGPARSE_FLAG_RESET can be used to reinit + * this array. */ + if (!arg->internal->opts) + { + int seen_help = 0; + int seen_version = 0; + int seen_warranty = 0; + int seen_dump_options = 0; + int seen_dump_option_table = 0; + int i; + + for (i=0; opts[i].short_opt; i++) + { + if (opts[i].long_opt) + { + if (!strcmp(opts[i].long_opt, "help")) + seen_help = 1; + else if (!strcmp(opts[i].long_opt, "version")) + seen_version = 1; + else if (!strcmp(opts[i].long_opt, "warranty")) + seen_warranty = 1; + else if (!strcmp(opts[i].long_opt, "dump-options")) + seen_dump_options = 1; + else if (!strcmp(opts[i].long_opt, "dump-option-table")) + seen_dump_option_table = 1; + } + } + i += 5; /* The number of the above internal options. */ + i++; /* End of list marker. */ + arg->internal->opts = xtrycalloc (i, sizeof *arg->internal->opts); + if (!arg->internal->opts) + return gpg_err_code_from_syserror (); + for(i=0; opts[i].short_opt; i++) + { + arg->internal->opts[i].short_opt = opts[i].short_opt; + arg->internal->opts[i].flags = opts[i].flags; + arg->internal->opts[i].long_opt = opts[i].long_opt; + arg->internal->opts[i].description = opts[i].description; + arg->internal->opts[i].ordinal = i; + } + + if (!seen_help) + { + arg->internal->opts[i].short_opt = ARGPARSE_SHORTOPT_HELP; + arg->internal->opts[i].flags = ARGPARSE_TYPE_NONE; + arg->internal->opts[i].long_opt = "help"; + arg->internal->opts[i].description = "@"; + arg->internal->opts[i].ordinal = i; + i++; + } + if (!seen_version) + { + arg->internal->opts[i].short_opt = ARGPARSE_SHORTOPT_VERSION; + arg->internal->opts[i].flags = ARGPARSE_TYPE_NONE; + arg->internal->opts[i].long_opt = "version"; + arg->internal->opts[i].description = "@"; + arg->internal->opts[i].ordinal = i; + i++; + } + + if (!seen_warranty) + { + arg->internal->opts[i].short_opt = ARGPARSE_SHORTOPT_WARRANTY; + arg->internal->opts[i].flags = ARGPARSE_TYPE_NONE; + arg->internal->opts[i].long_opt = "warranty"; + arg->internal->opts[i].description = "@"; + arg->internal->opts[i].ordinal = i; + i++; + } + + if (!seen_dump_option_table) + { + arg->internal->opts[i].short_opt = ARGPARSE_SHORTOPT_DUMP_OPTTBL; + arg->internal->opts[i].flags = ARGPARSE_TYPE_NONE; + arg->internal->opts[i].long_opt = "dump-option-table"; + arg->internal->opts[i].description = "@"; + arg->internal->opts[i].ordinal = i; + i++; + } + + if (!seen_dump_options) + { + arg->internal->opts[i].short_opt = ARGPARSE_SHORTOPT_DUMP_OPTIONS; + arg->internal->opts[i].flags = ARGPARSE_TYPE_NONE; + arg->internal->opts[i].long_opt = "dump-options"; + arg->internal->opts[i].description = "@"; + arg->internal->opts[i].ordinal = i; + i++; + } + /* Take care: When adding new options remember to increase the + * size of the array. */ + + arg->internal->opts[i].short_opt = 0; + + /* Note that we do not count the end marker but keep it in the + * table anyway as an extra item. */ + arg->internal->nopts = i; + } if (arg->err) { /* Last option was erroneous. */ const char *s; - if (filename) + if (!fp && arg->internal->conffp) + fp = arg->internal->conffp; + + if (fp) { if ( arg->r_opt == ARGPARSE_UNEXPECTED_ARG ) s = _("argument not expected"); @@ -389,22 +530,35 @@ initialize( ARGPARSE_ARGS *arg, const char *filename, unsigned *lineno ) s = _("invalid command"); else if ( arg->r_opt == ARGPARSE_INVALID_ALIAS ) s = _("invalid alias definition"); + else if ( arg->r_opt == ARGPARSE_PERMISSION_ERROR ) + s = _("permission error"); else if ( arg->r_opt == ARGPARSE_OUT_OF_CORE ) s = _("out of core"); + else if ( arg->r_opt == ARGPARSE_NO_CONFFILE ) + s = NULL; /* Error has already been printed. */ + else if ( arg->r_opt == ARGPARSE_INVALID_META ) + s = _("invalid meta command"); + else if ( arg->r_opt == ARGPARSE_UNKNOWN_META ) + s = _("unknown meta command"); + else if ( arg->r_opt == ARGPARSE_UNEXPECTED_META ) + s = _("unexpected meta command"); else s = _("invalid option"); - log_error ("%s:%u: %s\n", filename, *lineno, s); + if (s) + log_error ("%s:%u: %s\n", + gpgrt_fname_get (fp), arg->lineno, s); } else { - s = arg->internal.last? arg->internal.last:"[??]"; + s = arg->internal->last? arg->internal->last:"[??]"; if ( arg->r_opt == ARGPARSE_MISSING_ARG ) log_error (_("missing argument for option \"%.50s\"\n"), s); else if ( arg->r_opt == ARGPARSE_INVALID_ARG ) log_error (_("invalid argument for option \"%.50s\"\n"), s); else if ( arg->r_opt == ARGPARSE_UNEXPECTED_ARG ) - log_error (_("option \"%.50s\" does not expect an argument\n"), s); + log_error (_("option \"%.50s\" does not expect " + "an argument\n"), s); else if ( arg->r_opt == ARGPARSE_INVALID_COMMAND ) log_error (_("invalid command \"%.50s\"\n"), s); else if ( arg->r_opt == ARGPARSE_AMBIGUOUS_OPTION ) @@ -412,18 +566,30 @@ initialize( ARGPARSE_ARGS *arg, const char *filename, unsigned *lineno ) else if ( arg->r_opt == ARGPARSE_AMBIGUOUS_COMMAND ) log_error (_("command \"%.50s\" is ambiguous\n"),s ); else if ( arg->r_opt == ARGPARSE_OUT_OF_CORE ) - log_error ("%s\n", _("out of core\n")); + log_error ("%s\n", _("out of core")); + else if ( arg->r_opt == ARGPARSE_PERMISSION_ERROR ) + log_error ("%s\n", _("permission error")); + else if ( arg->r_opt == ARGPARSE_NO_CONFFILE) + ; /* Error has already been printed. */ + else if ( arg->r_opt == ARGPARSE_INVALID_META ) + log_error ("%s\n", _("invalid meta command")); + else if ( arg->r_opt == ARGPARSE_UNKNOWN_META ) + log_error ("%s\n", _("unknown meta command")); + else if ( arg->r_opt == ARGPARSE_UNEXPECTED_META ) + log_error ("%s\n",_("unexpected meta command")); else log_error (_("invalid option \"%.50s\"\n"), s); } if (arg->err != ARGPARSE_PRINT_WARNING) - exit (2); + my_exit (arg, 2); arg->err = 0; } /* Zero out the return value union. */ arg->r.ret_str = NULL; arg->r.ret_long = 0; + + return 0; } @@ -451,7 +617,7 @@ store_alias( ARGPARSE_ARGS *arg, char *name, char *value ) static int ignore_invalid_option_p (ARGPARSE_ARGS *arg, const char *keyword) { - IIO_ITEM_DEF item = arg->internal.iio_list; + IIO_ITEM_DEF item = arg->internal->iio_list; for (; item; item = item->next) if (!strcmp (item->name, keyword)) @@ -465,7 +631,7 @@ ignore_invalid_option_p (ARGPARSE_ARGS *arg, const char *keyword) character read wll be the first of a new line. The function returns 0 on success or true on malloc failure. */ static int -ignore_invalid_option_add (ARGPARSE_ARGS *arg, FILE *fp) +ignore_invalid_option_add (ARGPARSE_ARGS *arg, estream_t fp) { IIO_ITEM_DEF item; int c; @@ -476,7 +642,7 @@ ignore_invalid_option_add (ARGPARSE_ARGS *arg, FILE *fp) while (!ready) { - c = getc (fp); + c = gpgrt_getc (fp); if (c == '\n') ready = 1; else if (c == EOF) @@ -524,8 +690,8 @@ ignore_invalid_option_add (ARGPARSE_ARGS *arg, FILE *fp) if (!item) return 1; strcpy (item->name, name); - item->next = (IIO_ITEM_DEF)arg->internal.iio_list; - arg->internal.iio_list = item; + item->next = (IIO_ITEM_DEF)arg->internal->iio_list; + arg->internal->iio_list = item; } state = skipWS; goto again; @@ -541,15 +707,261 @@ ignore_invalid_option_clear (ARGPARSE_ARGS *arg) { IIO_ITEM_DEF item, tmpitem; - for (item = arg->internal.iio_list; item; item = tmpitem) + for (item = arg->internal->iio_list; item; item = tmpitem) { tmpitem = item->next; xfree (item); } - arg->internal.iio_list = NULL; + arg->internal->iio_list = NULL; +} + + +/* Make sure the username field is filled. Return 0 on success. */ +static int +assure_username (gnupg_argparse_t *arg) +{ + if (!arg->internal->username) + { + arg->internal->username = "dummyuser"; /*FIXMEgpgrt_getusername ();*/ + if (!arg->internal->username) + { + log_error ("%s:%u: error getting current user's name: %s\n", + arg->internal->confname, arg->lineno, + gpg_strerror (gpg_error_from_syserror ())); + /* Not necessary the correct error code but given that we + * either have a malloc error or some internal system error, + * it is the best we can do. */ + return ARGPARSE_PERMISSION_ERROR; + } + } + return 0; +} + + +/* Implementation of the "user" command. ARG is the context. ARGS is + * a non-empty string which this function is allowed to modify. */ +static int +handle_meta_user (gnupg_argparse_t *arg, unsigned int alternate, char *args) +{ + int rc; + + (void)alternate; + + rc = assure_username (arg); + if (rc) + return rc; + + arg->internal->user_seen = 1; + if (*args == '*' && !args[1]) + { + arg->internal->user_wildcard = 1; + arg->internal->user_active = !arg->internal->user_any_active; + } + else if (arg->internal->user_wildcard) + { + /* All other user statements are ignored after a wildcard. */ + arg->internal->user_active = 0; + } + else if (!strcasecmp (args, arg->internal->username)) + { + arg->internal->user_any_active = 1; + arg->internal->user_active = 1; + } + else + { + arg->internal->user_active = 0; + } + + return 0; +} + + +/* Implementation of the "force" command. ARG is the context. A + * value of 0 for ALTERNATE is "force", a value of 1 requests an + * unforce". ARGS is the empty string and not used. */ +static int +handle_meta_force (gnupg_argparse_t *arg, unsigned int alternate, char *args) +{ + (void)args; + + arg->internal->mark_forced = alternate? 0 : 1; + + return 0; +} + + +/* Implementation of the "ignore" command. ARG is the context. A + * value of 0 for ALTERNATE is a plain "ignore", a value of 1 request + * an "unignore, a value of 2 requests an "ignore-all". ARGS is the + * empty string and not used. */ +static int +handle_meta_ignore (gnupg_argparse_t *arg, unsigned int alternate, char *args) +{ + (void)args; + + if (!alternate) + { + arg->internal->mark_ignore = 1; + arg->internal->explicit_ignore = 1; + } + else if (alternate == 1) + { + arg->internal->mark_ignore = 0; + arg->internal->explicit_ignore = 1; + } + else + arg->internal->ignore_all_seen = 1; + + return 0; +} + + +/* Implementation of the "echo" command. ARG is the context. If + * ALTERNATE is true the filename is not printed. ARGS is the string + * to log. */ +static int +handle_meta_echo (gnupg_argparse_t *arg, unsigned int alternate, char *args) +{ + int rc = 0; + char *p, *pend; + + if (alternate) + log_info ("%s", ""); + else + log_info ("%s:%u: ", arg->internal->confname, arg->lineno); + + while (*args) + { + p = strchr (args, '$'); + if (!p) + { + log_printf ("%s", args); + break; + } + *p = 0; + log_printf ("%s", args); + if (p[1] == '$') + { + log_printf ("$"); + args = p+2; + continue; + } + if (p[1] != '{') + { + log_printf ("$"); + args = p+1; + continue; + } + pend = strchr (p+2, '}'); + if (!pend) /* No closing brace. */ + { + log_printf ("$"); + args = p+1; + continue; + } + p += 2; + *pend = 0; + args = pend+1; + if (!strcmp (p, "user")) + { + rc = assure_username (arg); + if (rc) + goto leave; + log_printf ("%s", arg->internal->username); + } + else if (!strcmp (p, "file")) + log_printf ("%s", arg->internal->confname); + else if (!strcmp (p, "line")) + log_printf ("%u", arg->lineno); + else if (!strcmp (p, "epoch")) + log_printf ("%lu", (unsigned long)time (NULL)); + } + + leave: + log_printf ("\n"); + return rc; } +/* Implementation of the "verbose" command. ARG is the context. If + * ALTERNATE is true the verbosity is disabled. ARGS is not used. */ +static int +handle_meta_verbose (gnupg_argparse_t *arg, unsigned int alternate, char *args) +{ + (void)args; + + if (alternate) + arg->internal->verbose = 0; + else + arg->internal->verbose = 1; + return 0; +} + +/* Handle a meta command. KEYWORD has the content inside the brackets + * with leading and trailing spaces removed. The function may modify + * KEYWORD. On success 0 is returned, on error an ARGPARSE_ error + * code is returned. */ +static int +handle_metacmd (gnupg_argparse_t *arg, char *keyword) +{ + static struct { + const char *name; /* Name of the command. */ + unsigned short alternate; /* Use alternate version of the command. */ + unsigned short needarg:1; /* Command requires an argument. */ + unsigned short always:1; /* Command allowed in all conf files. */ + unsigned short noskip:1; /* Even done in non-active [user] mode. */ + int (*func)(gnupg_argparse_t *arg, + unsigned int alternate, char *args); /*handler*/ + } cmds[] = + {{ "user", 0, 1, 0, 1, handle_meta_user }, + { "force", 0, 0, 0, 0, handle_meta_force }, + { "+force", 0, 0, 0, 0, handle_meta_force }, + { "-force", 1, 0, 0, 0, handle_meta_force }, + { "ignore", 0, 0, 0, 0, handle_meta_ignore }, + { "+ignore", 0, 0, 0, 0, handle_meta_ignore }, + { "-ignore", 1, 0, 0, 0, handle_meta_ignore }, + { "ignore-all", 2, 0, 0, 0, handle_meta_ignore }, + { "+ignore-all", 2, 0, 0, 0, handle_meta_ignore }, + { "verbose", 0, 0, 1, 1, handle_meta_verbose }, + { "+verbose", 0, 0, 1, 1, handle_meta_verbose }, + { "-verbose", 1, 0, 1, 1, handle_meta_verbose }, + { "echo", 0, 1, 1, 1, handle_meta_echo }, + { "-echo", 1, 1, 1, 1, handle_meta_echo }, + { "info", 0, 1, 1, 0, handle_meta_echo }, + { "-info", 1, 1, 1, 0, handle_meta_echo } + }; + char *rest; + int i; + + for (rest = keyword; *rest && !(isascii (*rest) && isspace (*rest)); rest++) + ; + if (*rest) + { + *rest++ = 0; + trim_spaces (rest); + } + + for (i=0; i < DIM (cmds); i++) + if (!strcmp (cmds[i].name, keyword)) + break; + if (!(i < DIM (cmds))) + return ARGPARSE_UNKNOWN_META; + if (cmds[i].needarg && !*rest) + return ARGPARSE_MISSING_ARG; + if (!cmds[i].needarg && *rest) + return ARGPARSE_UNEXPECTED_ARG; + if (!arg->internal->in_sysconf && !cmds[i].always) + return ARGPARSE_UNEXPECTED_META; + + if (!cmds[i].noskip + && arg->internal->in_sysconf + && arg->internal->user_seen + && !arg->internal->user_active) + return 0; /* Skip this meta command. */ + + return cmds[i].func (arg, cmds[i].alternate, rest); +} + /**************** * Get options from a file. @@ -573,11 +985,25 @@ ignore_invalid_option_clear (ARGPARSE_ARGS *arg) * Note: Abbreviation of options is here not allowed. */ int -optfile_parse (FILE *fp, const char *filename, unsigned *lineno, - ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts) +gnupg_argparse (estream_t fp, gnupg_argparse_t *arg, gnupg_opt_t *opts_orig) { - int state, i, c; - int idx=0; + enum { Ainit, + Acomment, /* In a comment line. */ + Acopykeyword, /* Collecting a keyword. */ + Awaitarg, /* Wait for an argument. */ + Acopyarg, /* Copy the argument. */ + Akeyword_eol, /* Got keyword at end of line. */ + Akeyword_spc, /* Got keyword at space. */ + Acopymetacmd, /* Copy a meta command. */ + Askipmetacmd, /* Skip spaces after metacmd. */ + Askipmetacmd2,/* Skip comment after metacmd. */ + Ametacmd, /* Process the metacmd. */ + Askipandleave /* Skip the rest of the line and then leave. */ + } state; + opttable_t *opts; + unsigned int nopts; + int i, c; + int idx = 0; char keyword[100]; char *buffer = NULL; size_t buflen = 0; @@ -585,97 +1011,246 @@ optfile_parse (FILE *fp, const char *filename, unsigned *lineno, int unread_buf[3]; /* We use an int so that we can store EOF. */ int unread_buf_count = 0; + if (arg && !opts_orig) + { + deinitialize (arg); + return 0; + } + if (!fp) /* Divert to arg_parse() in this case. */ - return arg_parse (arg, opts); + return arg_parse (arg, opts_orig, 0); - initialize (arg, filename, lineno); + if (initialize (arg, opts_orig, fp)) + return (arg->r_opt = ARGPARSE_OUT_OF_CORE); + + opts = arg->internal->opts; + nopts = arg->internal->nopts; /* If the LINENO is zero we assume that we are at the start of a * file and we skip over a possible Byte Order Mark. */ - if (!*lineno) + if (!arg->lineno) { - unread_buf[0] = getc (fp); - unread_buf[1] = getc (fp); - unread_buf[2] = getc (fp); + unread_buf[0] = gpgrt_fgetc (fp); + unread_buf[1] = gpgrt_fgetc (fp); + unread_buf[2] = gpgrt_fgetc (fp); if (unread_buf[0] != 0xef || unread_buf[1] != 0xbb || unread_buf[2] != 0xbf) unread_buf_count = 3; } + arg->internal->opt_flags = 0; + /* Find the next keyword. */ - state = i = 0; + state = Ainit; + i = 0; for (;;) { - if (unread_buf_count) - c = unread_buf[3 - unread_buf_count--]; - else - c = getc (fp); - if (c == '\n' || c== EOF ) + nextstate: + /* Before scanning the next char handle the keyword seen states. */ + if (state == Akeyword_eol || state == Akeyword_spc) { - if ( c != EOF ) - ++*lineno; - if (state == -1) - break; - else if (state == 2) + /* We are either at the end of a line or right after a + * keyword. In the latter case we need to find the keyword + * so that we can decide whether an argument is required. */ + + /* Check the keyword. */ + for (idx=0; idx < nopts; idx++ ) { - keyword[i] = 0; - for (i=0; opts[i].short_opt; i++ ) - { - if (opts[i].long_opt && !strcmp (opts[i].long_opt, keyword)) - break; - } - idx = i; - arg->r_opt = opts[idx].short_opt; - if ((opts[idx].flags & ARGPARSE_OPT_IGNORE)) + if (opts[idx].long_opt && !strcmp (opts[idx].long_opt, keyword)) + break; + } + arg->r_opt = opts[idx].short_opt; + if (!(idx < nopts)) + { + /* The option (keyword) is not known - check for + * internal keywords before returning an error. */ + if (state == Akeyword_spc && !strcmp (keyword, "alias")) { - state = i = 0; - continue; + in_alias = 1; + state = Awaitarg; } - else if (!opts[idx].short_opt ) + else if (!strcmp (keyword, "ignore-invalid-option")) { - if (!strcmp (keyword, "ignore-invalid-option")) + /* We might have keywords as argument - add them to + * the list of ignored keywords. Note that we + * ignore empty argument lists and thus do not to + * call the function in the Akeyword_eol state. */ + if (state == Akeyword_spc) { - /* No argument - ignore this meta option. */ - state = i = 0; - continue; - } - else if (ignore_invalid_option_p (arg, keyword)) - { - /* This invalid option is in the iio list. */ - state = i = 0; - continue; + if (ignore_invalid_option_add (arg, fp)) + { + arg->r_opt = ARGPARSE_OUT_OF_CORE; + goto leave; + } + arg->lineno++; } + state = Ainit; + i = 0; + } + else if (ignore_invalid_option_p (arg, keyword)) + { + /* This invalid option is already in the iio list. */ + state = state == Akeyword_eol? Ainit : Acomment; + i = 0; + } + else + { arg->r_opt = ((opts[idx].flags & ARGPARSE_OPT_COMMAND) ? ARGPARSE_INVALID_COMMAND : ARGPARSE_INVALID_OPTION); + if (state == Akeyword_spc) + state = Askipandleave; + else + goto leave; } - else if (!(opts[idx].flags & ARGPARSE_TYPE_MASK)) - arg->r_type = 0; /* Does not take an arg. */ + } + else if (state == Akeyword_spc) + { + /* Known option but need to scan for args. */ + state = Awaitarg; + } + else if (arg->internal->in_sysconf + && arg->internal->user_seen + && !arg->internal->user_active) + { + /* We are in a [user] meta command and it is not active. + * Skip the command. */ + state = state == Akeyword_eol? Ainit : Acomment; + i = 0; + } + else if ((opts[idx].flags & ARGPARSE_OPT_IGNORE)) + { + /* Known option is configured to be ignored. Start from + * scratch (new line) or process like a comment. */ + state = state == Akeyword_eol? Ainit : Acomment; + i = 0; + } + else /* Known option */ + { + int set_ignore = 0; + + if (arg->internal->in_sysconf) + { + /* Set the current forced and ignored attributes. */ + if (arg->internal->mark_forced) + opts[idx].forced = 1; + if (arg->internal->mark_ignore) + opts[idx].ignore = 1; + if (arg->internal->explicit_ignore) + opts[idx].explicit_ignore = 1; + } + else /* Non-sysconf file */ + { /* Act upon the forced and ignored attributes. */ + if (opts[idx].ignore || opts[idx].forced) + { + if (arg->internal->verbose) + log_info ("%s:%u: ignoring option \"--%s\"" + " due to attributes:%s%s\n", + arg->internal->confname, + arg->lineno, + opts[idx].long_opt, + opts[idx].forced? " forced":"", + opts[idx].ignore? " ignore":""); + if ((arg->flags & ARGPARSE_FLAG_WITHATTR)) + set_ignore = 1; + else + { + state = Ainit; + i = 0; + goto nextstate; /* Ignore this one. */ + } + } + } + + arg->r_opt = opts[idx].short_opt; + if (!(opts[idx].flags & ARGPARSE_TYPE_MASK)) + arg->r_type = ARGPARSE_TYPE_NONE; /* Does not take an arg. */ else if ((opts[idx].flags & ARGPARSE_OPT_OPTIONAL) ) - arg->r_type = 0; /* Arg is optional. */ + arg->r_type = ARGPARSE_TYPE_NONE; /* Arg is optional. */ else arg->r_opt = ARGPARSE_MISSING_ARG; - break; + /* If the caller wants us to return the attributes or + * ignored options, or the flags in. */ + if ((arg->flags & ARGPARSE_FLAG_WITHATTR)) + { + if (opts[idx].ignore) + arg->r_type |= ARGPARSE_ATTR_IGNORE; + if (opts[idx].forced) + arg->r_type |= ARGPARSE_ATTR_FORCE; + if (set_ignore) + arg->r_type |= ARGPARSE_OPT_IGNORE; + } + + goto leave; + } + } /* (end state Akeyword_eol/Akeyword_spc) */ + else if (state == Ametacmd) + { + /* We are at the end of a line. */ + log_assert (*keyword == '['); + trim_spaces (keyword+1); + if (!keyword[1]) + { + arg->r_opt = ARGPARSE_INVALID_META; /* Empty. */ + goto leave; + } + c = handle_metacmd (arg, keyword+1); + if (c) + { + arg->r_opt = c; /* Return error. */ + goto leave; + } + state = Ainit; + i = 0; + } + + /* Get the next character from the line. */ + if (unread_buf_count) + c = unread_buf[3 - unread_buf_count--]; + else + c = gpgrt_fgetc (fp); + + if (c == '\n' || c== EOF ) + { /* Handle end of line. */ + if ( c != EOF ) + arg->lineno++; + if (state == Askipandleave) + goto leave; + else if (state == Acopykeyword) + { + keyword[i] = 0; + state = Akeyword_eol; + goto nextstate; + } + else if (state == Acopymetacmd) + { + arg->r_opt = ARGPARSE_INVALID_META; /* "]" missing */ + goto leave; } - else if (state == 3) + else if (state == Askipmetacmd || state == Askipmetacmd2) { - /* No argument found. */ + state = Ametacmd; + goto nextstate; + } + else if (state == Awaitarg) + { + /* No argument found at the end of the line. */ if (in_alias) arg->r_opt = ARGPARSE_MISSING_ARG; else if (!(opts[idx].flags & ARGPARSE_TYPE_MASK)) - arg->r_type = 0; /* Does not take an arg. */ + arg->r_type = ARGPARSE_TYPE_NONE; /* Does not take an arg. */ else if ((opts[idx].flags & ARGPARSE_OPT_OPTIONAL)) - arg->r_type = 0; /* No optional argument. */ + arg->r_type = ARGPARSE_TYPE_NONE; /* No optional argument. */ else arg->r_opt = ARGPARSE_MISSING_ARG; - break; + goto leave; } - else if (state == 4) + else if (state == Acopyarg) { - /* Has an argument. */ + /* Has an argument at the end of a line. */ if (in_alias) { if (!buffer) @@ -735,82 +1310,61 @@ optfile_parse (FILE *fp, const char *filename, unsigned *lineno, gpgrt_annotate_leaked_object (buffer); } } - break; + goto leave; } else if (c == EOF) { ignore_invalid_option_clear (arg); - if (ferror (fp)) + if (gpgrt_ferror (fp)) arg->r_opt = ARGPARSE_READ_ERROR; else arg->r_opt = 0; /* EOF. */ - break; + goto leave; } - state = 0; + state = Ainit; i = 0; - } - else if (state == -1) + } /* (end handle end of line) */ + else if (state == Askipandleave) ; /* Skip. */ - else if (state == 0 && isascii (c) && isspace(c)) + else if (state == Ainit && isascii (c) && isspace(c)) ; /* Skip leading white space. */ - else if (state == 0 && c == '#' ) - state = 1; /* Start of a comment. */ - else if (state == 1) + else if (state == Ainit && c == '#' ) + state = Acomment; /* Start of a comment. */ + else if (state == Acomment || state == Askipmetacmd2) ; /* Skip comments. */ - else if (state == 2 && isascii (c) && isspace(c)) + else if (state == Askipmetacmd) { - /* Check keyword. */ - keyword[i] = 0; - for (i=0; opts[i].short_opt; i++ ) - if (opts[i].long_opt && !strcmp (opts[i].long_opt, keyword)) - break; - idx = i; - arg->r_opt = opts[idx].short_opt; - if ((opts[idx].flags & ARGPARSE_OPT_IGNORE)) - { - state = 1; /* Process like a comment. */ - } - else if (!opts[idx].short_opt) + if (c == '#') + state = Askipmetacmd2; + else if (!(isascii (c) && isspace(c))) { - if (!strcmp (keyword, "alias")) - { - in_alias = 1; - state = 3; - } - else if (!strcmp (keyword, "ignore-invalid-option")) - { - if (ignore_invalid_option_add (arg, fp)) - { - arg->r_opt = ARGPARSE_OUT_OF_CORE; - break; - } - state = i = 0; - ++*lineno; - } - else if (ignore_invalid_option_p (arg, keyword)) - state = 1; /* Process like a comment. */ - else - { - arg->r_opt = ((opts[idx].flags & ARGPARSE_OPT_COMMAND) - ? ARGPARSE_INVALID_COMMAND - : ARGPARSE_INVALID_OPTION); - state = -1; /* Skip rest of line and leave. */ - } + arg->r_opt = ARGPARSE_INVALID_META; + state = Askipandleave; } - else - state = 3; } - else if (state == 3) + else if (state == Acopykeyword && isascii (c) && isspace(c)) + { + keyword[i] = 0; + state = Akeyword_spc; + goto nextstate; + } + else if (state == Acopymetacmd && c == ']') + { + keyword[i] = 0; + state = Askipmetacmd; + goto nextstate; + } + else if (state == Awaitarg) { /* Skip leading spaces of the argument. */ if (!isascii (c) || !isspace(c)) { i = 0; keyword[i++] = c; - state = 4; + state = Acopyarg; } } - else if (state == 4) + else if (state == Acopyarg) { /* Collect the argument. */ if (buffer) @@ -833,7 +1387,7 @@ optfile_parse (FILE *fp, const char *filename, unsigned *lineno, { xfree (buffer); arg->r_opt = ARGPARSE_OUT_OF_CORE; - break; + goto leave; } } } @@ -852,130 +1406,554 @@ optfile_parse (FILE *fp, const char *filename, unsigned *lineno, else { arg->r_opt = ARGPARSE_OUT_OF_CORE; - break; + goto leave; } } } else if (i >= DIM(keyword)-1) { arg->r_opt = ARGPARSE_KEYWORD_TOO_LONG; - state = -1; /* Skip rest of line and leave. */ + state = Askipandleave; /* Skip rest of line and leave. */ + } + else if (!i) + { + state = c == '[' ? Acopymetacmd : Acopykeyword; + keyword[i++] = c; } else { keyword[i++] = c; - state = 2; } } + leave: return arg->r_opt; } +/* Return true if the list of options OPTS has any option marked with + * ARGPARSE_OPT_CONFFILE. */ +static int +any_opt_conffile (opttable_t *opts, unsigned int nopts) +{ + int i; + for (i=0; i < nopts; i++ ) + if ((opts[i].flags & ARGPARSE_OPT_CONFFILE)) + return 1; + return 0; +} + + +/* Return true if FNAME is an absolute filename. */ static int -find_long_option( ARGPARSE_ARGS *arg, - ARGPARSE_OPTS *opts, const char *keyword ) +is_absfname (const char *fname) { - int i; - size_t n; - - (void)arg; - - /* Would be better if we can do a binary search, but it is not - possible to reorder our option table because we would mess - up our help strings - What we can do is: Build a nice option - lookup table when this function is first invoked */ - if( !*keyword ) - return -1; - for(i=0; opts[i].short_opt; i++ ) - if( opts[i].long_opt && !strcmp( opts[i].long_opt, keyword) ) - return i; -#if 0 + const char *s; + +#ifdef HAVE_W32_SYSTEM + s = strchr (fname, ':'); + if (s) + s++; + else + s = fname; +#else + s = fname; +#endif + + return (*s == '/' +#ifdef HAVE_W32_SYSTEM + || *s == DIRSEP_C +#endif + ); +} + + +/* If FNAME specifies two files of the form + * NAME1:/NAME2 (Unix) + * or + * NAME1;[x:]/NAME2 (Windows) + * return a pointer to the delimiter or NULL if there is none. + */ +static const char * +is_twopartfname (const char *fname) +{ + const char *s; + + if ((s = strchr (fname, PATHSEP_C)) && is_absfname (s+1) && s != fname) + return s; + return NULL; +} + + +/* Try to use a version-ed config file name. A version-ed config file + * name is one which has the packages version number appended. For + * example if the standard config file name is "foo.conf" and the + * version of the foo program is 1.2.3-beta1 the following config + * files are tried in order until one is readable: + * + * foo.conf-1.2.3-beta1 + * foo.conf-1.2.3 + * foo.conf-1.2 + * foo.conf-1 + * foo.conf + * + * The argument CONFIGNAME should already be expanded. On success a + * newly allocated file name is returned. On error NULL is returned. + */ +static char * +try_versioned_conffile (const char *configname) +{ + const char *version = strusage (13); + char *name; + char *dash, *endp; + + if (!version || !*version) + return NULL; /* No program version known. */ + + name = strconcat (configname, "-", version, NULL); + if (!name) + return NULL; /* Oops: Out of core - ignore. */ + dash = name + strlen (configname); + + endp = dash + strlen (dash) - 1; + while (endp > dash) { - ALIAS_DEF a; - /* see whether it is an alias */ - for( a = args->internal.aliases; a; a = a->next ) { - if( !strcmp( a->name, keyword) ) { - /* todo: must parse the alias here */ - args->internal.cur_alias = a; - return -3; /* alias available */ - } - } + if (!gnupg_access (name, R_OK)) + { + return name; + } + for (; endp > dash; endp--) + { + if (*endp == '-' || *endp == '.') + { + *endp = 0; + break; + } + } } -#endif - /* not found, see whether it is an abbreviation */ - /* aliases may not be abbreviated */ - n = strlen( keyword ); - for(i=0; opts[i].short_opt; i++ ) { - if( opts[i].long_opt && !strncmp( opts[i].long_opt, keyword, n ) ) { - int j; - for(j=i+1; opts[j].short_opt; j++ ) { - if( opts[j].long_opt - && !strncmp( opts[j].long_opt, keyword, n ) - && !(opts[j].short_opt == opts[i].short_opt - && opts[j].flags == opts[i].flags ) ) - return -2; /* abbreviation is ambiguous */ + + xfree (name); + return NULL; +} + + +/* This function is called after a sysconf file has been read. */ +static void +finish_read_sys (gnupg_argparse_t *arg) +{ + opttable_t *opts = arg->internal->opts; + unsigned int nopts = arg->internal->nopts; + int i; + + if (arg->internal->ignore_all_seen) + { + /* [ignore-all] was used: Set all options which have not + * explictly been set as ignore or not ignore to ignore. */ + for (i = 0; i < nopts; i++) + { + if (!opts[i].explicit_ignore) + opts[i].ignore = 1; + } + } + + /* Reset all flags which pertain only to sysconf files. */ + arg->internal->in_sysconf = 0; + arg->internal->user_active = 0; + arg->internal->mark_forced = 0; + arg->internal->mark_ignore = 0; + arg->internal->explicit_ignore = 0; + arg->internal->ignore_all_seen = 0; +} + +/* The full arg parser which handles option files and command line + * arguments. The behaviour depends on the combinations of CONFNAME + * and the ARGPARSE_FLAG_xxx values: + * + * | CONFNAME | SYS | USER | Action | + * |----------+-----+------+--------------------| + * | NULL | - | - | cmdline | + * | string | 0 | 1 | user, cmdline | + * | string | 1 | 0 | sys, cmdline | + * | string | 1 | 1 | sys, user, cmdline | + * + * Note that if an option has been flagged with ARGPARSE_OPT_CONFFILE + * and a type of ARGPARSE_TYPE_STRING that option is not returned but + * the specified configuration file is processed directly; if + * ARGPARSE_TYPE_NONE is used no user configuration files are + * processed and from the system configuration files only those which + * are immutable are processed. The string values for CONFNAME shall + * not include a directory part because that is taken from the values + * set by gnupg_set_confdir. However, if CONFNAME is a twopart + * filename delimited by a colon (semicolon on Windows) with the + * second part being an absolute filename, the first part is used for + * the SYS file and the the entire second part for the USER file. + */ +int +gnupg_argparser (gnupg_argparse_t *arg, gnupg_opt_t *opts, + const char *confname) +{ + /* First check whether releasing the resources has been requested. */ + if (arg && !opts) + { + deinitialize (arg); + return 0; + } + + /* Make sure that the internal data object is ready and also print + * warnings or errors from the last iteration. */ + if (initialize (arg, opts, NULL)) + return (arg->r_opt = ARGPARSE_OUT_OF_CORE); + + next_state: + switch (arg->internal->state) + { + case STATE_init: + if (arg->argc && arg->argv && *arg->argc + && any_opt_conffile (arg->internal->opts, arg->internal->nopts)) + { + /* The list of option allow for conf files + * (e.g. gpg's "--option FILE" and "--no-options") + * Now check whether one was really given on the command + * line. Note that we don't need to run this code if no + * argument array was provided. */ + int save_argc = *arg->argc; + char **save_argv = *arg->argv; + unsigned int save_flags = arg->flags; + int save_idx = arg->internal->idx; + int any_no_conffile = 0; + + arg->flags = (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION + | ARGPARSE_FLAG__INITIALIZED); + while (arg_parse (arg, opts, 1)) + { + if ((arg->internal->opt_flags & ARGPARSE_OPT_CONFFILE)) + { + arg->internal->explicit_confopt = 1; + if ((arg->r_type & ARGPARSE_TYPE_MASK) == ARGPARSE_TYPE_STRING + && !arg->internal->explicit_conffile) + { + /* Store the first conffile name. All further + * conf file options are not handled. */ + arg->internal->explicit_conffile + = xtrystrdup (arg->r.ret_str); + if (!arg->internal->explicit_conffile) + return (arg->r_opt = ARGPARSE_OUT_OF_CORE); + + } + else if ((arg->r_type & ARGPARSE_TYPE_MASK) + == ARGPARSE_TYPE_NONE) + any_no_conffile = 1; + } + } + if (any_no_conffile) + { + /* A NoConffile option overrides any other conf file option. */ + xfree (arg->internal->explicit_conffile); + arg->internal->explicit_conffile = NULL; + } + /* Restore parser. */ + *arg->argc = save_argc; + *arg->argv = save_argv; + arg->flags = save_flags; + arg->internal->idx = save_idx; + } + + if (confname && *confname) + { + if ((arg->flags & ARGPARSE_FLAG_SYS)) + arg->internal->state = STATE_open_sys; + else if ((arg->flags & ARGPARSE_FLAG_USER)) + arg->internal->state = STATE_open_user; + else + return (arg->r_opt = ARGPARSE_INVALID_ARG); + } + else + arg->internal->state = STATE_open_cmdline; + goto next_state; + + case STATE_open_sys: + { + /* If it is a two part name take the first part. */ + const char *s; + char *tmpname = NULL; + + if ((s = is_twopartfname (confname))) + { + tmpname = xtrymalloc (s - confname + 1); + if (!tmpname) + return (arg->r_opt = ARGPARSE_OUT_OF_CORE); + memcpy (tmpname, confname, s-confname); + tmpname[s-confname] = 0; + s = tmpname; + } + else + s = confname; + xfree (arg->internal->confname); + arg->internal->confname = make_filename_try + (confdir.sys? confdir.sys : "/etc", s, NULL); + xfree (tmpname); + if (!arg->internal->confname) + return (arg->r_opt = ARGPARSE_OUT_OF_CORE); + } + arg->lineno = 0; + arg->internal->idx = 0; + arg->internal->verbose = 0; + arg->internal->stopped = 0; + arg->internal->inarg = 0; + gpgrt_fclose (arg->internal->conffp); + arg->internal->conffp = gpgrt_fopen (arg->internal->confname, "r"); + if (!arg->internal->conffp) + { + if ((arg->flags & ARGPARSE_FLAG_VERBOSE) || arg->internal->verbose) + log_info (_("Note: no default option file '%s'\n"), + arg->internal->confname); + if ((arg->flags & ARGPARSE_FLAG_USER)) + arg->internal->state = STATE_open_user; + else + arg->internal->state = STATE_open_cmdline; + goto next_state; + } + + if ((arg->flags & ARGPARSE_FLAG_VERBOSE) || arg->internal->verbose) + log_info (_("reading options from '%s'\n"), + arg->internal->confname); + arg->internal->state = STATE_read_sys; + arg->internal->in_sysconf = 1; + arg->r.ret_str = xtrystrdup (arg->internal->confname); + if (!arg->r.ret_str) + arg->r_opt = ARGPARSE_OUT_OF_CORE; + else + { + gpgrt_annotate_leaked_object (arg->r.ret_str); + arg->r_opt = ARGPARSE_CONFFILE; + arg->r_type = ARGPARSE_TYPE_STRING; + } + break; + + case STATE_open_user: + if (arg->internal->explicit_confopt + && arg->internal->explicit_conffile) + { + /* An explict option to use a specific configuration file + * has been given - use that one. */ + xfree (arg->internal->confname); + arg->internal->confname + = xtrystrdup (arg->internal->explicit_conffile); + if (!arg->internal->confname) + return (arg->r_opt = ARGPARSE_OUT_OF_CORE); + } + else if (arg->internal->explicit_confopt) + { + /* An explict option not to use a configuration file has + * been given - leap direct to command line reading. */ + arg->internal->state = STATE_open_cmdline; + goto next_state; + } + else + { + /* Use the standard configure file. If it is a two part + * name take the second part. If it is the standard name + * and ARGPARSE_FLAG_USERVERS is set try versioned config + * files. */ + const char *s; + char *nconf; + + xfree (arg->internal->confname); + if ((s = is_twopartfname (confname))) + { + arg->internal->confname = make_filename_try (s + 1, NULL); + if (!arg->internal->confname) + return (arg->r_opt = ARGPARSE_OUT_OF_CORE); + } + else + { + arg->internal->confname = make_filename_try + (confdir.user? confdir.user : "~/.config", confname, NULL); + if (!arg->internal->confname) + return (arg->r_opt = ARGPARSE_OUT_OF_CORE); + if ((arg->flags & ARGPARSE_FLAG_USERVERS) + && (nconf = try_versioned_conffile (arg->internal->confname))) + { + xfree (arg->internal->confname); + arg->internal->confname = nconf; + } + } + } + arg->lineno = 0; + arg->internal->idx = 0; + arg->internal->verbose = 0; + arg->internal->stopped = 0; + arg->internal->inarg = 0; + arg->internal->in_sysconf = 0; + gpgrt_fclose (arg->internal->conffp); + arg->internal->conffp = gpgrt_fopen (arg->internal->confname, "r"); + if (!arg->internal->conffp) + { + arg->internal->state = STATE_open_cmdline; + if (arg->internal->explicit_confopt) + { + log_error (_("option file '%s': %s\n"), + arg->internal->confname, strerror (errno)); + return (arg->r_opt = ARGPARSE_NO_CONFFILE); + } + else + { + if ((arg->flags & ARGPARSE_FLAG_VERBOSE) + || arg->internal->verbose) + log_info (_("Note: no default option file '%s'\n"), + arg->internal->confname); + goto next_state; + } + } + + if ((arg->flags & ARGPARSE_FLAG_VERBOSE) || arg->internal->verbose) + log_info (_("reading options from '%s'\n"), + arg->internal->confname); + arg->internal->state = STATE_read_user; + arg->r.ret_str = xtrystrdup (arg->internal->confname); + if (!arg->r.ret_str) + arg->r_opt = ARGPARSE_OUT_OF_CORE; + else + { + gpgrt_annotate_leaked_object (arg->r.ret_str); + arg->r_opt = ARGPARSE_CONFFILE; + arg->r_type = ARGPARSE_TYPE_STRING; + } + break; + + case STATE_open_cmdline: + gpgrt_fclose (arg->internal->conffp); + arg->internal->conffp = NULL; + xfree (arg->internal->confname); + arg->internal->confname = NULL; + arg->internal->idx = 0; + arg->internal->verbose = 0; + arg->internal->stopped = 0; + arg->internal->inarg = 0; + arg->internal->in_sysconf = 0; + if (!arg->argc || !arg->argv || !*arg->argv) + { + /* No or empty argument vector - don't bother to parse things. */ + arg->internal->state = STATE_finished; + goto next_state; + } + arg->r_opt = ARGPARSE_CONFFILE; + arg->r_type = ARGPARSE_TYPE_NONE; + arg->r.ret_str = NULL; + arg->internal->state = STATE_read_cmdline; + break; + + case STATE_read_sys: + arg->r_opt = gnupg_argparse (arg->internal->conffp, arg, opts); + if (!arg->r_opt) + { + finish_read_sys (arg); + arg->internal->state = STATE_open_user; + goto next_state; + } + if ((arg->internal->opt_flags & ARGPARSE_OPT_CONFFILE)) + goto next_state; /* Already handled - again. */ + break; + + case STATE_read_user: + arg->r_opt = gnupg_argparse (arg->internal->conffp, arg, opts); + if (!arg->r_opt) + { + arg->internal->state = STATE_open_cmdline; + goto next_state; + } + if ((arg->internal->opt_flags & ARGPARSE_OPT_CONFFILE)) + goto next_state; /* Already handled - again. */ + break; + + case STATE_read_cmdline: + arg->r_opt = arg_parse (arg, opts, 1); + if (!arg->r_opt) + { + arg->internal->state = STATE_finished; + goto next_state; + } + if ((arg->internal->opt_flags & ARGPARSE_OPT_CONFFILE)) + goto next_state; /* Already handled - again. */ + break; + + case STATE_finished: + arg->r_opt = 0; + break; + } + + return arg->r_opt; +} + + + +/* Given the list of options in ARG and a keyword, return the index of + * the long option matching KEYWORD. On error -1 is returned for not + * found or -2 for ambigious keyword. */ +static int +find_long_option (gnupg_argparse_t *arg, const char *keyword) +{ + int i; + size_t n; + opttable_t *opts = arg->internal->opts; + unsigned int nopts = arg->internal->nopts; + + /* Would be better if we can do a binary search, but it is not + * possible to reorder our option table because we would mess up our + * help strings. What we can do is: Build an option lookup table + * when this function is first invoked. The latter has already been + * done. */ + if (!*keyword) + return -1; + for (i=0; i < nopts; i++ ) + if (opts[i].long_opt && !strcmp (opts[i].long_opt, keyword)) + return i; + /* Not found. See whether it is an abbreviation. Aliases may not + * be abbreviated, though. */ + n = strlen (keyword); + for (i=0; i < nopts; i++) + { + if (opts[i].long_opt && !strncmp (opts[i].long_opt, keyword, n)) + { + int j; + for (j=i+1; j < nopts; j++) + { + if (opts[j].long_opt + && !strncmp (opts[j].long_opt, keyword, n) + && !(opts[j].short_opt == opts[i].short_opt + && opts[j].flags == opts[i].flags ) ) + return -2; /* Abbreviation is ambiguous. */ } - return i; + return i; } } - return -1; /* Not found. */ + return -1; /* Not found. */ } -int -arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts) + +/* The option parser for command line options. */ +static int +arg_parse (gnupg_argparse_t *arg, gnupg_opt_t *opts_orig, int no_init) { int idx; + opttable_t *opts; + unsigned int nopts; int argc; char **argv; char *s, *s2; int i; - /* Fill in missing standard options: help, version, warranty and - * dump-options. */ - ARGPARSE_OPTS help_opt - = ARGPARSE_s_n (ARGPARSE_SHORTOPT_HELP, "help", "@"); - ARGPARSE_OPTS version_opt - = ARGPARSE_s_n (ARGPARSE_SHORTOPT_VERSION, "version", "@"); - ARGPARSE_OPTS warranty_opt - = ARGPARSE_s_n (ARGPARSE_SHORTOPT_WARRANTY, "warranty", "@"); - ARGPARSE_OPTS dump_options_opt - = ARGPARSE_s_n(ARGPARSE_SHORTOPT_DUMP_OPTIONS, "dump-options", "@"); - int seen_help = 0; - int seen_version = 0; - int seen_warranty = 0; - int seen_dump_options = 0; + if (no_init) + ; + else if (initialize (arg, opts_orig, NULL)) + return (arg->r_opt = ARGPARSE_OUT_OF_CORE); - i = 0; - while (opts[i].short_opt) - { - if (opts[i].long_opt) - { - if (!strcmp(opts[i].long_opt, help_opt.long_opt)) - seen_help = 1; - else if (!strcmp(opts[i].long_opt, version_opt.long_opt)) - seen_version = 1; - else if (!strcmp(opts[i].long_opt, warranty_opt.long_opt)) - seen_warranty = 1; - else if (!strcmp(opts[i].long_opt, dump_options_opt.long_opt)) - seen_dump_options = 1; - } - i++; - } - if (! seen_help) - opts[i++] = help_opt; - if (! seen_version) - opts[i++] = version_opt; - if (! seen_warranty) - opts[i++] = warranty_opt; - if (! seen_dump_options) - opts[i++] = dump_options_opt; - - initialize( arg, NULL, NULL ); + opts = arg->internal->opts; + nopts = arg->internal->nopts; argc = *arg->argc; argv = *arg->argv; - idx = arg->internal.idx; + idx = arg->internal->idx; if (!idx && argc && !(arg->flags & ARGPARSE_FLAG_ARG0)) { @@ -984,24 +1962,24 @@ arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts) } next_one: - if (!argc) + if (!argc || (s = *argv) == NULL) { /* No more args. */ arg->r_opt = 0; goto leave; /* Ready. */ } - s = *argv; - arg->internal.last = s; + arg->internal->last = s; + arg->internal->opt_flags = 0; - if (arg->internal.stopped && (arg->flags & ARGPARSE_FLAG_ALL)) + if (arg->internal->stopped && (arg->flags & ARGPARSE_FLAG_ALL)) { arg->r_opt = ARGPARSE_IS_ARG; /* Not an option but an argument. */ - arg->r_type = 2; + arg->r_type = ARGPARSE_TYPE_STRING; arg->r.ret_str = s; argc--; argv++; idx++; /* set to next one */ } - else if( arg->internal.stopped ) + else if (arg->internal->stopped) { arg->r_opt = 0; goto leave; /* Ready. */ @@ -1011,11 +1989,11 @@ arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts) /* Long option. */ char *argpos; - arg->internal.inarg = 0; + arg->internal->inarg = 0; if (!s[2] && !(arg->flags & ARGPARSE_FLAG_NOSTOP)) { /* Stop option processing. */ - arg->internal.stopped = 1; + arg->internal->stopped = 1; arg->flags |= ARGPARSE_FLAG_STOP_SEEN; argc--; argv++; idx++; goto next_one; @@ -1024,33 +2002,38 @@ arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts) argpos = strchr( s+2, '=' ); if ( argpos ) *argpos = 0; - i = find_long_option ( arg, opts, s+2 ); + i = find_long_option (arg, s+2); if ( argpos ) *argpos = '='; if (i > 0 && opts[i].short_opt == ARGPARSE_SHORTOPT_HELP) - show_help (opts, arg->flags); + { + show_help (opts, nopts, arg->flags); + my_exit (arg, 0); + } else if (i > 0 && opts[i].short_opt == ARGPARSE_SHORTOPT_VERSION) { if (!(arg->flags & ARGPARSE_FLAG_NOVERSION)) { show_version (); - exit(0); + my_exit (arg, 0); } } else if (i > 0 && opts[i].short_opt == ARGPARSE_SHORTOPT_WARRANTY) { writestrings (0, strusage (16), "\n", NULL); - exit (0); + my_exit (arg, 0); } + else if (i > 0 && opts[i].short_opt == ARGPARSE_SHORTOPT_DUMP_OPTTBL) + dump_option_table (arg); else if (i > 0 && opts[i].short_opt == ARGPARSE_SHORTOPT_DUMP_OPTIONS) { - for (i=0; opts[i].short_opt; i++ ) + for (i=0; i < nopts; i++ ) { if (opts[i].long_opt && !(opts[i].flags & ARGPARSE_OPT_IGNORE)) writestrings (0, "--", opts[i].long_opt, "\n", NULL); } - exit (0); + my_exit (arg, 0); } if ( i == -2 ) @@ -1062,6 +2045,7 @@ arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts) } else arg->r_opt = opts[i].short_opt; + if ( i < 0 ) ; else if ( (opts[i].flags & ARGPARSE_TYPE_MASK) ) @@ -1074,6 +2058,7 @@ arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts) } else s2 = argv[1]; + if ( !s2 && (opts[i].flags & ARGPARSE_OPT_OPTIONAL) ) { arg->r_type = ARGPARSE_TYPE_NONE; /* Argument is optional. */ @@ -1105,125 +2090,157 @@ arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts) if ( argpos ) arg->r_type = ARGPARSE_UNEXPECTED_ARG; else - arg->r_type = 0; + { + arg->internal->opt_flags = opts[i].flags; + arg->r_type = ARGPARSE_TYPE_NONE; + } } argc--; argv++; idx++; /* Set to next one. */ } - else if ( (*s == '-' && s[1]) || arg->internal.inarg ) - { - /* Short option. */ - int dash_kludge = 0; + else if ( (*s == '-' && s[1]) || arg->internal->inarg ) + { + /* Short option. */ + int dash_kludge = 0; - i = 0; - if ( !arg->internal.inarg ) - { - arg->internal.inarg++; - if ( (arg->flags & ARGPARSE_FLAG_ONEDASH) ) - { - for (i=0; opts[i].short_opt; i++ ) - if ( opts[i].long_opt && !strcmp (opts[i].long_opt, s+1)) - { - dash_kludge = 1; - break; - } - } - } - s += arg->internal.inarg; + i = 0; + if ( !arg->internal->inarg ) + { + arg->internal->inarg++; + if ( (arg->flags & ARGPARSE_FLAG_ONEDASH) ) + { + for (i=0; i < nopts; i++ ) + if ( opts[i].long_opt && !strcmp (opts[i].long_opt, s+1)) + { + dash_kludge = 1; + break; + } + } + } + s += arg->internal->inarg; - if (!dash_kludge ) - { - for (i=0; opts[i].short_opt; i++ ) - if ( opts[i].short_opt == *s ) - break; - } + if (!dash_kludge ) + { + for (i=0; i < nopts; i++ ) + if ( opts[i].short_opt == *s ) + break; + } - if ( !opts[i].short_opt && ( *s == 'h' || *s == '?' ) ) - show_help (opts, arg->flags); + if ( !opts[i].short_opt && ( *s == 'h' || *s == '?' ) ) + { + show_help (opts, nopts, arg->flags); + my_exit (arg, 0); + } - arg->r_opt = opts[i].short_opt; - if (!opts[i].short_opt ) - { - arg->r_opt = (opts[i].flags & ARGPARSE_OPT_COMMAND)? - ARGPARSE_INVALID_COMMAND:ARGPARSE_INVALID_OPTION; - arg->internal.inarg++; /* Point to the next arg. */ - arg->r.ret_str = s; - } - else if ( (opts[i].flags & ARGPARSE_TYPE_MASK) ) - { - if ( s[1] && !dash_kludge ) - { - s2 = s+1; - set_opt_arg (arg, opts[i].flags, s2); - } - else - { - s2 = argv[1]; - if ( !s2 && (opts[i].flags & ARGPARSE_OPT_OPTIONAL) ) - { - arg->r_type = ARGPARSE_TYPE_NONE; - } - else if ( !s2 ) - { - arg->r_opt = ARGPARSE_MISSING_ARG; - } - else if ( *s2 == '-' && s2[1] - && (opts[i].flags & ARGPARSE_OPT_OPTIONAL) ) - { - /* The argument is optional and the next seems to - be an option. We do not check this possible - option but assume no argument. */ - arg->r_type = ARGPARSE_TYPE_NONE; - } - else - { - set_opt_arg (arg, opts[i].flags, s2); - argc--; argv++; idx++; /* Skip one. */ - } - } - s = "x"; /* This is so that !s[1] yields false. */ - } - else - { - /* Does not take an argument. */ - arg->r_type = ARGPARSE_TYPE_NONE; - arg->internal.inarg++; /* Point to the next arg. */ - } - if ( !s[1] || dash_kludge ) - { - /* No more concatenated short options. */ - arg->internal.inarg = 0; - argc--; argv++; idx++; - } - } + arg->r_opt = opts[i].short_opt; + if (!opts[i].short_opt ) + { + arg->r_opt = (opts[i].flags & ARGPARSE_OPT_COMMAND)? + ARGPARSE_INVALID_COMMAND:ARGPARSE_INVALID_OPTION; + arg->internal->inarg++; /* Point to the next arg. */ + arg->r.ret_str = s; + } + else if ( (opts[i].flags & ARGPARSE_TYPE_MASK) ) + { + if ( s[1] && !dash_kludge ) + { + s2 = s+1; + set_opt_arg (arg, opts[i].flags, s2); + } + else + { + s2 = argv[1]; + if ( !s2 && (opts[i].flags & ARGPARSE_OPT_OPTIONAL) ) + { + arg->r_type = ARGPARSE_TYPE_NONE; + arg->internal->opt_flags = opts[i].flags; + } + else if ( !s2 ) + { + arg->r_opt = ARGPARSE_MISSING_ARG; + } + else if ( *s2 == '-' && s2[1] + && (opts[i].flags & ARGPARSE_OPT_OPTIONAL) ) + { + /* The argument is optional and the next seems to + be an option. We do not check this possible + option but assume no argument. */ + arg->r_type = ARGPARSE_TYPE_NONE; + arg->internal->opt_flags = opts[i].flags; + } + else + { + set_opt_arg (arg, opts[i].flags, s2); + argc--; argv++; idx++; /* Skip one. */ + } + } + s = "x"; /* This is so that !s[1] yields false. */ + } + else + { + /* Does not take an argument. */ + arg->r_type = ARGPARSE_TYPE_NONE; + arg->internal->opt_flags = opts[i].flags; + arg->internal->inarg++; /* Point to the next arg. */ + } + if ( !s[1] || dash_kludge ) + { + /* No more concatenated short options. */ + arg->internal->inarg = 0; + argc--; argv++; idx++; + } + } else if ( arg->flags & ARGPARSE_FLAG_MIXED ) { arg->r_opt = ARGPARSE_IS_ARG; - arg->r_type = 2; + arg->r_type = ARGPARSE_TYPE_STRING; arg->r.ret_str = s; argc--; argv++; idx++; /* Set to next one. */ } else { - arg->internal.stopped = 1; /* Stop option processing. */ + arg->internal->stopped = 1; /* Stop option processing. */ goto next_one; } + if (arg->r_opt > 0 && i >= 0 && i < nopts + && ((opts[i].ignore && opts[i].explicit_ignore) || opts[i].forced)) + { + + if ((arg->flags & ARGPARSE_FLAG_WITHATTR)) + { + if (opts[i].ignore) + arg->r_type |= ARGPARSE_ATTR_IGNORE; + if (opts[i].forced) + arg->r_type |= ARGPARSE_ATTR_FORCE; + arg->r_type |= ARGPARSE_OPT_IGNORE; + } + else + { + log_info (_("Note: ignoring option \"--%s\"" + " due to global config\n"), + opts[i].long_opt); + goto next_one; /* Skip ignored/forced option. */ + } + } + leave: *arg->argc = argc; *arg->argv = argv; - arg->internal.idx = idx; + arg->internal->idx = idx; return arg->r_opt; } + /* Returns: -1 on error, 0 for an integer type and 1 for a non integer type argument. */ static int -set_opt_arg (ARGPARSE_ARGS *arg, unsigned flags, char *s) +set_opt_arg (gnupg_argparse_t *arg, unsigned flags, char *s) { int base = (flags & ARGPARSE_OPT_PREFIX)? 0 : 10; long l; + arg->internal->opt_flags = flags; switch ( (arg->r_type = (flags & ARGPARSE_TYPE_MASK)) ) { case ARGPARSE_TYPE_LONG: @@ -1272,8 +2289,10 @@ set_opt_arg (ARGPARSE_ARGS *arg, unsigned flags, char *s) } +/* Return the length of the option O. This needs to consider the + * description as well as the option name. */ static size_t -long_opt_strlen( ARGPARSE_OPTS *o ) +long_opt_strlen (opttable_t *o) { size_t n = strlen (o->long_opt); @@ -1286,8 +2305,8 @@ long_opt_strlen( ARGPARSE_OPTS *o ) if ( *s != '=' ) n++; /* For a (mostly) correct length calculation we exclude - continuation bytes (10xxxxxx) if we are on a native utf8 - terminal. */ + * continuation bytes (10xxxxxx) if we are on a native utf8 + * terminal. */ for (; *s && *s != '|'; s++ ) if ( is_utf8 && (*s&0xc0) != 0x80 ) n++; @@ -1296,22 +2315,35 @@ long_opt_strlen( ARGPARSE_OPTS *o ) } +/* Qsort compare for show_help. */ +static int +cmp_ordtbl (const void *a_v, const void *b_v) +{ + const unsigned short *a = a_v; + const unsigned short *b = b_v; + + return *a - *b; +} + + /**************** * Print formatted help. The description string has some special * meanings: * - A description string which is "@" suppresses help output for * this option - * - a description,ine which starts with a '@' and is followed by + * - a description which starts with a '@' and is followed by * any other characters is printed as is; this may be used for examples - * ans such. + * and such. This is a legacy methiod, moder codes uses the flags + * ARGPARSE_OPT_VERBATIM or ARGPARSE_OPT_HEADER. * - A description which starts with a '|' outputs the string between this * bar and the next one as arguments of the long option. */ static void -show_help (ARGPARSE_OPTS *opts, unsigned int flags) +show_help (opttable_t *opts, unsigned int nopts, unsigned int flags) { const char *s; char tmp[2]; + unsigned int *ordtbl = NULL; show_version (); writestrings (0, "\n", NULL); @@ -1325,30 +2357,68 @@ show_help (ARGPARSE_OPTS *opts, unsigned int flags) } s = strusage(41); writestrings (0, s, "\n", NULL); - if ( opts[0].description ) + if ( nopts ) { /* Auto format the option description. */ - int i,j, indent; + int i,j,indent; + const char *last_header = NULL; + + ordtbl = xtrycalloc (nopts, sizeof *ordtbl); + if (!ordtbl) + { + writestrings (1, "\nOoops: Out of memory whilst printing the help.\n", + NULL); + goto leave; + } /* Get max. length of long options. */ - for (i=indent=0; opts[i].short_opt; i++ ) + for (i=indent=0; i < nopts; i++ ) { if ( opts[i].long_opt ) if ( !opts[i].description || *opts[i].description != '@' ) if ( (j=long_opt_strlen(opts+i)) > indent && j < 35 ) indent = j; + ordtbl[i] = opts[i].ordinal; } + qsort (ordtbl, nopts, sizeof *ordtbl, cmp_ordtbl); + + /* The first option needs to have a description; if not do not + * print the help at all. */ + if (!opts[ordtbl[0]].description) + goto leave; + /* Example: " -v, --verbose Viele Sachen ausgeben" */ indent += 10; - if ( *opts[0].description != '@' ) + if ( *opts[ordtbl[0]].description != '@' + && !(opts[ordtbl[0]].flags + & (ARGPARSE_OPT_VERBATIM|ARGPARSE_OPT_HEADER))) writestrings (0, "Options:", "\n", NULL); - for (i=0; opts[i].short_opt; i++ ) + for (i=0; i < nopts; i++ ) { - s = map_static_macro_string (_( opts[i].description )); + s = map_fixed_string (_( opts[ordtbl[i]].description )); if ( s && *s== '@' && !s[1] ) /* Hide this line. */ continue; - if ( s && *s == '@' ) /* Unindented comment only line. */ + if ( s && (opts[ordtbl[i]].flags & ARGPARSE_OPT_HEADER)) + { + /* We delay printing until we have found one real output + * line. This avoids having a header above an empty + * section. */ + last_header = s; + continue; + } + if (last_header) + { + if (*last_header) + writestrings (0, "\n", last_header, ":\n", NULL); + last_header = NULL; + } + if ( s && (opts[ordtbl[i]].flags & ARGPARSE_OPT_VERBATIM)) + { + writestrings (0, s, NULL); + continue; + } + if ( s && *s == '@' ) /* Unindented legacy comment only line. */ { for (s++; *s; s++ ) { @@ -1369,12 +2439,12 @@ show_help (ARGPARSE_OPTS *opts, unsigned int flags) } j = 3; - if ( opts[i].short_opt < 256 ) + if ( opts[ordtbl[i]].short_opt < 256 ) { - tmp[0] = opts[i].short_opt; + tmp[0] = opts[ordtbl[i]].short_opt; tmp[1] = 0; writestrings (0, " -", tmp, NULL ); - if ( !opts[i].long_opt ) + if ( !opts[ordtbl[i]].long_opt ) { if (s && *s == '|' ) { @@ -1392,11 +2462,11 @@ show_help (ARGPARSE_OPTS *opts, unsigned int flags) } else writestrings (0, " ", NULL); - if ( opts[i].long_opt ) + if ( opts[ordtbl[i]].long_opt ) { - tmp[0] = opts[i].short_opt < 256?',':' '; + tmp[0] = opts[ordtbl[i]].short_opt < 256?',':' '; tmp[1] = 0; - j += writestrings (0, tmp, " --", opts[i].long_opt, NULL); + j += writestrings (0, tmp, " --", opts[ordtbl[i]].long_opt, NULL); if (s && *s == '|' ) { if ( *++s != '=' ) @@ -1456,10 +2526,13 @@ show_help (ARGPARSE_OPTS *opts, unsigned int flags) writestrings (0, "\n", NULL); writestrings (0, s, NULL); } + + leave: flushstrings (0); - exit(0); + xfree (ordtbl); } + static void show_version () { @@ -1495,44 +2568,73 @@ show_version () } -void -usage (int level) +/* Print the table of options with flags etc. */ +static void +dump_option_table (gnupg_argparse_t *arg) { - const char *p; + opttable_t *opts; + unsigned int nopts; + const char *s; + char tmp[50]; + unsigned int *ordtbl = NULL; + int i; - if (!level) + opts = arg->internal->opts; + nopts = arg->internal->nopts; + if (!nopts) + return; + + ordtbl = xtrycalloc (nopts, sizeof *ordtbl); + if (!ordtbl) { - writestrings (1, strusage(11), " ", strusage(13), "; ", - strusage (14), "\n", NULL); + writestrings (1, "\nOoops: Out of memory whilst dumping the table.\n", + NULL); flushstrings (1); + my_exit (arg, 2); } - else if (level == 1) - { - p = strusage (40); - writestrings (1, p, NULL); - if (*p && p[strlen(p)] != '\n') - writestrings (1, "\n", NULL); - exit (2); - } - else if (level == 2) + for (i=0; i < nopts; i++ ) + ordtbl[i] = opts[i].ordinal; + qsort (ordtbl, nopts, sizeof *ordtbl, cmp_ordtbl); + for (i=0; i < nopts; i++ ) { - p = strusage (42); - if (p && *p == '1') + if (!opts[ordtbl[i]].long_opt) + continue; + writestrings (0, opts[ordtbl[i]].long_opt, ":", NULL); + snprintf (tmp, sizeof tmp, "%u:%u:", + opts[ordtbl[i]].short_opt, + opts[ordtbl[i]].flags); + writestrings (0, tmp, NULL); + s = opts[ordtbl[i]].description; + if (s) { - p = strusage (40); - writestrings (1, p, NULL); - if (*p && p[strlen(p)] != '\n') - writestrings (1, "\n", NULL); + for (; *s; s++) + { + if (*s == '%' || *s == ':' || *s == '\n') + snprintf (tmp, sizeof tmp, "%%%02X", *s); + else + { + tmp[0] = *s; + tmp[1] = 0; + } + writestrings (0, tmp, NULL); + } } - writestrings (0, strusage(41), "\n", NULL); - exit (0); + writestrings (0, ":\n", NULL); } + + flushstrings (0); + xfree (ordtbl); + my_exit (arg, 0); } + + /* Level * 0: Print copyright string to stderr * 1: Print a short usage hint to stderr and terminate * 2: Print a long usage hint to stdout and terminate + * 8: Return NULL for UTF-8 or string with the native charset. + * 9: Return the SPDX License tag. * 10: Return license info string * 11: Return the name of the program * 12: Return optional name of package which includes this program. @@ -1556,6 +2658,7 @@ const char * strusage( int level ) { const char *p = strusage_handler? strusage_handler(level) : NULL; + const char *tmp; if ( p ) return map_static_macro_string (p); @@ -1563,28 +2666,41 @@ strusage( int level ) switch ( level ) { + case 8: break; /* Default to utf-8. */ + case 9: p = "GPL-3.0-or-later"; break; case 10: -#if ARGPARSE_GPL_VERSION == 3 - p = ("License GPLv3+: GNU GPL version 3 or later " - "<https://gnu.org/licenses/gpl.html>"); -#else - p = ("License GPLv2+: GNU GPL version 2 or later " - "<https://gnu.org/licenses/>"); -#endif + tmp = strusage (9); + if (tmp && !strcmp (tmp, "LGPL-2.1-or-later")) + p = ("License GNU LGPL-2.1-or-later <https://gnu.org/licenses/>"); + else /* Default to GPLv3+. */ + p =("License GNU GPL-3.0-or-later <https://gnu.org/licenses/gpl.html>"); break; case 11: p = "foo"; break; case 13: p = "0.0"; break; - case 14: p = ARGPARSE_CRIGHT_STR; break; + case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 15: p = "This is free software: you are free to change and redistribute it.\n" "There is NO WARRANTY, to the extent permitted by law.\n"; break; - case 16: p = + case 16: + tmp = strusage (9); + if (tmp && !strcmp (tmp, "LGPL-2.1-or-later")) + p = +"This is free software; you can redistribute it and/or modify\n" +"it under the terms of the GNU Lesser General Public License as\n" +"published by the Free Software Foundation; either version 2.1 of\n" +"the License, or (at your option) any later version.\n\n" +"It is distributed in the hope that it will be useful,\n" +"but WITHOUT ANY WARRANTY; without even the implied warranty of\n" +"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" +"GNU Lesser General Public License for more details.\n\n" +"You should have received a copy of the GNU Lesser General Public License\n" +"along with this software. If not, see <https://gnu.org/licenses/>.\n"; + else /* Default */ + p = "This is free software; you can redistribute it and/or modify\n" "it under the terms of the GNU General Public License as published by\n" -"the Free Software Foundation; either version " -ARGPARSE_STR2(ARGPARSE_GPL_VERSION) -" of the License, or\n" +"the Free Software Foundation; either version 3 of the License, or\n" "(at your option) any later version.\n\n" "It is distributed in the hope that it will be useful,\n" "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" @@ -1608,76 +2724,39 @@ set_strusage ( const char *(*f)( int ) ) strusage_handler = f; } +#endif /* USE_INTERNAL_ARGPARSE */ -#ifdef TEST -static struct { - int verbose; - int debug; - char *outfile; - char *crf; - int myopt; - int echo; - int a_long_one; -} opt; -int -main(int argc, char **argv) +void +usage (int level) { - ARGPARSE_OPTS opts[] = { - ARGPARSE_x('v', "verbose", NONE, 0, "Laut sein"), - ARGPARSE_s_n('e', "echo" , ("Zeile ausgeben, damit wir sehen, " - "was wir eingegeben haben")), - ARGPARSE_s_n('d', "debug", "Debug\nfalls mal etwas\nschief geht"), - ARGPARSE_s_s('o', "output", 0 ), - ARGPARSE_o_s('c', "cross-ref", "cross-reference erzeugen\n" ), - /* Note that on a non-utf8 terminal the ß might garble the output. */ - ARGPARSE_s_n('s', "street","|Straße|set the name of the street to Straße"), - ARGPARSE_o_i('m', "my-option", 0), - ARGPARSE_s_n(500, "a-long-option", 0 ), - ARGPARSE_end() - }; - ARGPARSE_ARGS pargs = { &argc, &argv, (ARGPARSE_FLAG_ALL - | ARGPARSE_FLAG_MIXED - | ARGPARSE_FLAG_ONEDASH) }; - int i; + const char *p; - while (arg_parse (&pargs, opts)) + if (!level) + { + writestrings (1, strusage(11), " ", strusage(13), "; ", + strusage (14), "\n", NULL); + flushstrings (1); + } + else if (level == 1) + { + p = strusage (40); + writestrings (1, p, NULL); + if (*p && p[strlen(p)] != '\n') + writestrings (1, "\n", NULL); + exit (2); + } + else if (level == 2) { - switch (pargs.r_opt) + p = strusage (42); + if (p && *p == '1') { - case ARGPARSE_IS_ARG : - printf ("arg='%s'\n", pargs.r.ret_str); - break; - case 'v': opt.verbose++; break; - case 'e': opt.echo++; break; - case 'd': opt.debug++; break; - case 'o': opt.outfile = pargs.r.ret_str; break; - case 'c': opt.crf = pargs.r_type? pargs.r.ret_str:"a.crf"; break; - case 'm': opt.myopt = pargs.r_type? pargs.r.ret_int : 1; break; - case 500: opt.a_long_one++; break; - default : pargs.err = ARGPARSE_PRINT_WARNING; break; - } + p = strusage (40); + writestrings (1, p, NULL); + if (*p && p[strlen(p)] != '\n') + writestrings (1, "\n", NULL); + } + writestrings (0, strusage(41), "\n", NULL); + exit (0); } - for (i=0; i < argc; i++ ) - printf ("%3d -> (%s)\n", i, argv[i] ); - puts ("Options:"); - if (opt.verbose) - printf (" verbose=%d\n", opt.verbose ); - if (opt.debug) - printf (" debug=%d\n", opt.debug ); - if (opt.outfile) - printf (" outfile='%s'\n", opt.outfile ); - if (opt.crf) - printf (" crffile='%s'\n", opt.crf ); - if (opt.myopt) - printf (" myopt=%d\n", opt.myopt ); - if (opt.a_long_one) - printf (" a-long-one=%d\n", opt.a_long_one ); - if (opt.echo) - printf (" echo=%d\n", opt.echo ); - - return 0; } -#endif /*TEST*/ - -/**** bottom of file ****/ |