aboutsummaryrefslogtreecommitdiffstats
path: root/src/argparse.c
diff options
context:
space:
mode:
authorWerner Koch <[email protected]>2020-02-18 15:47:05 +0000
committerWerner Koch <[email protected]>2020-02-18 15:50:46 +0000
commit933eb9346a84c87f83f77d990be2f66e2f7b62e7 (patch)
tree4560fa9440d0e98f8662eb737c56d40d4499dab9 /src/argparse.c
parentcore: Add gpgrt_fnameconcat and gpgrt_absfnameconcat. (diff)
downloadlibgpg-error-933eb9346a84c87f83f77d990be2f66e2f7b62e7.tar.gz
libgpg-error-933eb9346a84c87f83f77d990be2f66e2f7b62e7.zip
core: Add a high level option/argument parser.
* gpg-error.h.in (GPGRT_CONFDIR_USER, GPGRT_CONFDIR_SYS): New consts. (ARGPARSE_FLAG_SYS, ARGPARSE_FLAG_USER, ARGPARSE_FLAG_VERBOSE) (ARGPARSE_NO_CONFFILE, ARGPARSE_CONFFILE, ARGPARSE_OPT_CONFFILE): New consts. (ARGPARSE_conffile, ARGPARSE_noconffile): New macros. (gpgrt_set_confdir): New func. (gpgrt_argparser): New func. * src/argparse.c (confdir): New var. (enum argparser_states): New. (struct _gpgrt_argparse_internal_s): Add a couple of new fields. (initialize): Init them. (any_opt_conffile): New. (_gpgrt_argparser): New. (_gpgrt_set_confdir): New. * src/visibility.c (gpgrt_argparser): New. (gpgrt_set_confdir): New. * src/gpg-error.def.in, src/gpg-error.vers: Add those functions. * tests/t-argparse.c (main): Reworked. * tests/etc/t-argparse.conf: New file. * tests/t-argparse.conf: New file. -- gpgrt_argparser is a high level version of gpgrt_argparse. It handles reading of configuration files internally and allows allows for a global configuration file. The design is so that it minimizes the work to replace the existing option parsing in gpg and friends by this one and to allow global configuration files for them. This is the just the basic code which should allow replacement of the parsers. A forthcoming patch will implement flags for options given in the global config file. GnuPG-bug-id: 4788 Signed-off-by: Werner Koch <[email protected]>
Diffstat (limited to 'src/argparse.c')
-rw-r--r--src/argparse.c384
1 files changed, 378 insertions, 6 deletions
diff --git a/src/argparse.c b/src/argparse.c
index fecd3b5..10e18c0 100644
--- a/src/argparse.c
+++ b/src/argparse.c
@@ -1,7 +1,7 @@
/* argparse.c - Argument Parser for option handling
* Copyright (C) 1997-2001, 2006-2008, 2013-2017 Werner Koch
* Copyright (C) 1998-2001, 2006-2008, 2012 Free Software Foundation, Inc.
- * Copyright (C) 2015-2018 g10 Code GmbH
+ * Copyright (C) 2015-2020 g10 Code GmbH
*
* This file is part of Libgpg-error.
*
@@ -36,6 +36,15 @@
#include "gpgrt-int.h"
+
+/* The malloced configuration directories or NULL. */
+static struct
+{
+ char *user;
+ char *sys;
+} confdir;
+
+
/* Special short options which are auto-inserterd. */
#define ARGPARSE_SHORTOPT_HELP 32768
#define ARGPARSE_SHORTOPT_VERSION 32769
@@ -45,16 +54,36 @@
/* A mask for the types. */
#define ARGPARSE_TYPE_MASK 7 /* Mask for the type values. */
+/* The states for the gpgrt_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
+ };
+
+
/* Internal object of the public gpgrt_argparse_t object. */
struct _gpgrt_argparse_internal_s
{
- int idx;
+ int idx; /* Note that this is saved and restored in _gpgrt_argparser. */
int inarg;
int stopped;
+ int explicit_confopt; /* A conffile option has been given. */
+ char *explicit_conffile; /* Malloced name of an explicit conffile. */
+ unsigned int opt_flags; /* Current option flags. */
+ enum argparser_states state; /* of gpgrt_argparser. */
const char *last;
void *aliases;
const void *cur_alias;
void *iio_list;
+ estream_t conffp;
+ char *confname;
gpgrt_opt_t **opts; /* Malloced array of pointer to user provided opts. */
};
@@ -189,6 +218,7 @@ deinitialize (gpgrt_argparse_t *arg)
{
if (arg->internal)
{
+ xfree (arg->internal->explicit_conffile);
xfree (arg->internal->opts);
xfree (arg->internal);
arg->internal = NULL;
@@ -228,9 +258,15 @@ initialize (gpgrt_argparse_t *arg, gpgrt_opt_t *opts, estream_t fp)
arg->internal->last = NULL;
arg->internal->inarg = 0;
arg->internal->stopped = 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. */
@@ -240,7 +276,7 @@ initialize (gpgrt_argparse_t *arg, gpgrt_opt_t *opts, estream_t fp)
* 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->flags & ARGPARSE_FLAG_NOLINENO))
+ if (fp || arg->internal->conffp || !(arg->flags & ARGPARSE_FLAG_NOLINENO))
arg->lineno = 0;
/* Need to clear the reset request. */
@@ -312,6 +348,9 @@ initialize (gpgrt_argparse_t *arg, gpgrt_opt_t *opts, estream_t fp)
/* Last option was erroneous. */
const char *s;
+ if (!fp && arg->internal->conffp)
+ fp = arg->internal->conffp;
+
if (fp)
{
if ( arg->r_opt == ARGPARSE_UNEXPECTED_ARG )
@@ -330,10 +369,13 @@ initialize (gpgrt_argparse_t *arg, gpgrt_opt_t *opts, estream_t fp)
s = _("invalid alias definition");
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
s = _("invalid option");
- _gpgrt_log_error ("%s:%u: %s\n",
- _gpgrt_fname_get (fp), arg->lineno, s);
+ if (s)
+ _gpgrt_log_error ("%s:%u: %s\n",
+ _gpgrt_fname_get (fp), arg->lineno, s);
}
else
{
@@ -353,7 +395,9 @@ initialize (gpgrt_argparse_t *arg, gpgrt_opt_t *opts, estream_t fp)
else if ( arg->r_opt == ARGPARSE_AMBIGUOUS_COMMAND )
_gpgrt_log_error (_("command \"%.50s\" is ambiguous\n"),s );
else if ( arg->r_opt == ARGPARSE_OUT_OF_CORE )
- _gpgrt_log_error ("%s\n", _("out of core\n"));
+ _gpgrt_log_error ("%s\n", _("out of core"));
+ else if ( arg->r_opt == ARGPARSE_NO_CONFFILE)
+ ; /* Error has already been printed. */
else
_gpgrt_log_error (_("invalid option \"%.50s\"\n"), s);
}
@@ -824,6 +868,281 @@ _gpgrt_argparse (estream_t fp, gpgrt_argparse_t *arg, gpgrt_opt_t *opts_orig)
}
+/* Return true if the list of options OPTS has any option marked with
+ * ARGPARSE_OPT_CONFFILE. */
+static int
+any_opt_conffile (gpgrt_opt_t *opts)
+{
+ int i;
+
+ for (i=0; opts[i].short_opt; i++ )
+ if ((opts[i].flags & ARGPARSE_OPT_CONFFILE))
+ return 1;
+ return 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 gpgrt_set_confdir.
+ */
+int
+_gpgrt_argparser (gpgrt_argparse_t *arg, gpgrt_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 (any_opt_conffile (opts))
+ {
+ /* 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. */
+ 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);
+ while (arg_parse (arg, opts))
+ {
+ if ((arg->internal->opt_flags & ARGPARSE_OPT_CONFFILE))
+ {
+ arg->internal->explicit_confopt = 1;
+ if (arg->r_type == 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_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:
+ xfree (arg->internal->confname);
+ arg->internal->confname = _gpgrt_fnameconcat
+ (confdir.sys? confdir.sys : "/etc", confname, NULL);
+ arg->lineno = 0;
+ _gpgrt_fclose (arg->internal->conffp);
+ arg->internal->conffp = _gpgrt_fopen (arg->internal->confname, "r");
+ /* FIXME: Add a callback. */
+ /* if (arg->internal->conffp && is_secured_file (fileno (configfp)))*/
+ /* { */
+ /* es_fclose (arg->internal->conffp); */
+ /* arg->internal->conffp = NULL; */
+ /* gpg_err_set_errno (EPERM); */
+ /* } */
+ if (!arg->internal->conffp)
+ {
+ if ((arg->flags & ARGPARSE_FLAG_VERBOSE))
+ _gpgrt_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))
+ _gpgrt_log_info (_("reading options from '%s'\n"),
+ arg->internal->confname);
+ arg->internal->state = STATE_read_sys;
+ 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. */
+ xfree (arg->internal->confname);
+ arg->internal->confname = _gpgrt_fnameconcat
+ (confdir.user? confdir.user : "/FIXME", confname, NULL);
+ }
+ arg->lineno = 0;
+ _gpgrt_fclose (arg->internal->conffp);
+ arg->internal->conffp = _gpgrt_fopen (arg->internal->confname, "r");
+ /* FIXME: Add a callback. */
+ /* if (arg->internal->conffp && is_secured_file (fileno (configfp)))*/
+ /* { */
+ /* es_fclose (arg->internal->conffp); */
+ /* arg->internal->conffp = NULL; */
+ /* gpg_err_set_errno (EPERM); */
+ /* } */
+ if (!arg->internal->conffp)
+ {
+ arg->internal->state = STATE_open_cmdline;
+ if (arg->internal->explicit_confopt)
+ {
+ _gpgrt_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))
+ _gpgrt_log_info (_("Note: no default option file '%s'\n"),
+ arg->internal->confname);
+ goto next_state;
+ }
+ }
+
+ if ((arg->flags & ARGPARSE_FLAG_VERBOSE))
+ _gpgrt_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:
+ xfree (arg->internal->confname);
+ arg->internal->confname = NULL;
+ 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 = _gpgrt_argparse (arg->internal->conffp, arg, opts);
+ if (!arg->r_opt)
+ {
+ 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 = _gpgrt_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 = _gpgrt_argparse (arg->internal->conffp, arg, opts);
+ 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 OPTS and a keyword, return the index of
* the long option macthing KEYWORD. On error -1 is retruned for not
* found or -2 for ambigious keyword. */
@@ -1149,6 +1468,7 @@ set_opt_arg (gpgrt_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:
@@ -1591,3 +1911,55 @@ _gpgrt_set_fixed_string_mapper (const char *(*f)(const char*))
{
fixed_string_mapper = f;
}
+
+
+/* Register a configuration directory for use by the argparse
+ * functions. The defined values for WHAT are:
+ *
+ * GPGRT_CONFDIR_SYS The systems's configuration dir.
+ * The default is /etc
+ *
+ * GPGRT_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
+_gpgrt_set_confdir (int what, const char *name)
+{
+ char *buf, *p;
+
+ if (what == GPGRT_CONFDIR_SYS)
+ {
+ _gpgrt_free (confdir.sys);
+ buf = confdir.sys = _gpgrt_strdup (name);
+ }
+ else if (what == GPGRT_CONFDIR_USER)
+ {
+ _gpgrt_free (confdir.user);
+ buf = confdir.user = _gpgrt_strdup (name);
+ }
+ else
+ return;
+
+ if (!buf)
+ _gpgrt_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;
+ }
+}