summaryrefslogtreecommitdiffstats
path: root/xtoptions.c
diff options
context:
space:
mode:
authorJan Engelhardt <jengelh@medozas.de>2011-02-07 04:00:50 +0100
committerJan Engelhardt <jengelh@medozas.de>2011-04-06 12:54:22 +0200
commitaa37acc1423126f555135935c687eb91995b9440 (patch)
tree39fdfa04a20875aeb2a47f0ef1bc2085d5796d0d /xtoptions.c
parent458d84de2412b43604a8efe2b82a2084a2859a46 (diff)
libxtables: guided option parser
This patchset seeks to drastically reduce the code in the individual extensions by centralizing their argument parsing (breakdown of strings), validation, and in part, assignment. As a secondary goal, this reduces the number of static storage duration variables in flight. Signed-off-by: Jan Engelhardt <jengelh@medozas.de>
Diffstat (limited to 'xtoptions.c')
-rw-r--r--xtoptions.c299
1 files changed, 299 insertions, 0 deletions
diff --git a/xtoptions.c b/xtoptions.c
new file mode 100644
index 00000000..3286aa10
--- /dev/null
+++ b/xtoptions.c
@@ -0,0 +1,299 @@
+/*
+ * Argument parser
+ * Copyright © Jan Engelhardt, 2011
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <netdb.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include "xtables.h"
+#include "xshared.h"
+
+#define XTOPT_MKPTR(cb) \
+ ((void *)((char *)(cb)->data + (cb)->entry->ptroff))
+
+/**
+ * Creates getopt options from the x6-style option map, and assigns each a
+ * getopt id.
+ */
+struct option *
+xtables_options_xfrm(struct option *orig_opts, struct option *oldopts,
+ const struct xt_option_entry *entry, unsigned int *offset)
+{
+ unsigned int num_orig, num_old = 0, num_new, i;
+ struct option *merge, *mp;
+
+ if (entry == NULL)
+ return oldopts;
+ for (num_orig = 0; orig_opts[num_orig].name != NULL; ++num_orig)
+ ;
+ if (oldopts != NULL)
+ for (num_old = 0; oldopts[num_old].name != NULL; ++num_old)
+ ;
+ for (num_new = 0; entry[num_new].name != NULL; ++num_new)
+ ;
+
+ /*
+ * Since @oldopts also has @orig_opts already (and does so at the
+ * start), skip these entries.
+ */
+ oldopts += num_orig;
+ num_old -= num_orig;
+
+ merge = malloc(sizeof(*mp) * (num_orig + num_old + num_new + 1));
+ if (merge == NULL)
+ return NULL;
+
+ /* Let the base options -[ADI...] have precedence over everything */
+ memcpy(merge, orig_opts, sizeof(*mp) * num_orig);
+ mp = merge + num_orig;
+
+ /* Second, the new options */
+ xt_params->option_offset += XT_OPTION_OFFSET_SCALE;
+ *offset = xt_params->option_offset;
+
+ for (i = 0; i < num_new; ++i, ++mp, ++entry) {
+ mp->name = entry->name;
+ mp->has_arg = entry->type != XTTYPE_NONE;
+ mp->flag = NULL;
+ mp->val = entry->id + *offset;
+ }
+
+ /* Third, the old options */
+ memcpy(mp, oldopts, sizeof(*mp) * num_old);
+ mp += num_old;
+ xtables_free_opts(0);
+
+ /* Clear trailing entry */
+ memset(mp, 0, sizeof(*mp));
+ return merge;
+}
+
+static void (*const xtopt_subparse[])(struct xt_option_call *) = {
+ [XTTYPE_NONE] = NULL,
+};
+
+static const size_t xtopt_psize[] = {
+ [XTTYPE_NONE] = 0,
+};
+
+/**
+ * The master option parsing routine. May be used for the ".x6_parse"
+ * function pointer in extensions if fully automatic parsing is desired.
+ * It may be also called manually from a custom x6_parse function.
+ */
+void xtables_option_parse(struct xt_option_call *cb)
+{
+ const struct xt_option_entry *entry = cb->entry;
+ unsigned int eflag = 1 << cb->entry->id;
+
+ /*
+ * With {.id = P_FOO, .excl = P_FOO} we can have simple double-use
+ * prevention. Though it turned out that this is too much typing (most
+ * of the options are one-time use only), so now we also have
+ * %XTOPT_MULTI.
+ */
+ if ((!(entry->flags & XTOPT_MULTI) || (entry->excl & eflag)) &&
+ cb->xflags & eflag)
+ xt_params->exit_err(PARAMETER_PROBLEM,
+ "%s: option \"--%s\" can only be used once.\n",
+ cb->ext_name, cb->entry->name);
+ if (cb->invert && !(entry->flags & XTOPT_INVERT))
+ xt_params->exit_err(PARAMETER_PROBLEM,
+ "%s: option \"--%s\" cannot be inverted.\n",
+ cb->ext_name, entry->name);
+ if (entry->type != XTTYPE_NONE && optarg == NULL)
+ xt_params->exit_err(PARAMETER_PROBLEM,
+ "%s: option \"--%s\" requires an argument.\n",
+ cb->ext_name, entry->name);
+ if (entry->type <= ARRAY_SIZE(xtopt_subparse) &&
+ xtopt_subparse[entry->type] != NULL)
+ xtopt_subparse[entry->type](cb);
+ /* Exclusion with other flags tested later in finalize. */
+ cb->xflags |= 1 << entry->id;
+}
+
+/**
+ * Verifies that an extension's option map descriptor is valid, and ought to
+ * be called right after the extension has been loaded, and before option
+ * merging/xfrm.
+ */
+void xtables_option_metavalidate(const char *name,
+ const struct xt_option_entry *entry)
+{
+ for (; entry->name != NULL; ++entry) {
+ if (entry->id >= CHAR_BIT * sizeof(unsigned int) ||
+ entry->id >= XT_OPTION_OFFSET_SCALE)
+ xt_params->exit_err(OTHER_PROBLEM,
+ "Extension %s uses invalid ID %u\n",
+ name, entry->id);
+ if (!(entry->flags & XTOPT_PUT))
+ continue;
+ if (entry->type >= ARRAY_SIZE(xtopt_psize))
+ xt_params->exit_err(OTHER_PROBLEM,
+ "%s: entry type of option \"--%s\" cannot be "
+ "combined with XTOPT_PUT\n",
+ name, entry->name);
+ if (xtopt_psize[entry->type] != entry->size)
+ xt_params->exit_err(OTHER_PROBLEM,
+ "%s: option \"--%s\" points to a memory block "
+ "of wrong size (expected %zu, got %zu)\n",
+ name, entry->name,
+ xtopt_psize[entry->type], entry->size);
+ }
+}
+
+/**
+ * Find an option entry by its id.
+ */
+static const struct xt_option_entry *
+xtables_option_lookup(const struct xt_option_entry *entry, unsigned int id)
+{
+ for (; entry->name != NULL; ++entry)
+ if (entry->id == id)
+ return entry;
+ return NULL;
+}
+
+/**
+ * @c: getopt id (i.e. with offset)
+ * @fw: struct ipt_entry or ip6t_entry
+ *
+ * Dispatch arguments to the appropriate parse function, based upon the
+ * extension's choice of API.
+ */
+void xtables_option_tpcall(unsigned int c, char **argv, bool invert,
+ struct xtables_target *t, void *fw)
+{
+ struct xt_option_call cb;
+
+ if (t->x6_parse == NULL) {
+ if (t->parse != NULL)
+ t->parse(c - t->option_offset, argv, invert,
+ &t->tflags, fw, &t->t);
+ return;
+ }
+
+ c -= t->option_offset;
+ cb.entry = xtables_option_lookup(t->x6_options, c);
+ if (cb.entry == NULL)
+ xtables_error(OTHER_PROBLEM,
+ "Extension does not know id %u\n", c);
+ cb.arg = optarg;
+ cb.invert = invert;
+ cb.ext_name = t->name;
+ cb.data = t->t->data;
+ cb.xflags = t->tflags;
+ t->x6_parse(&cb);
+ t->tflags = cb.xflags;
+}
+
+/**
+ * @c: getopt id (i.e. with offset)
+ * @fw: struct ipt_entry or ip6t_entry
+ *
+ * Dispatch arguments to the appropriate parse function, based upon the
+ * extension's choice of API.
+ */
+void xtables_option_mpcall(unsigned int c, char **argv, bool invert,
+ struct xtables_match *m, void *fw)
+{
+ struct xt_option_call cb;
+
+ if (m->x6_parse == NULL) {
+ if (m->parse != NULL)
+ m->parse(c - m->option_offset, argv, invert,
+ &m->mflags, fw, &m->m);
+ return;
+ }
+
+ c -= m->option_offset;
+ cb.entry = xtables_option_lookup(m->x6_options, c);
+ if (cb.entry == NULL)
+ xtables_error(OTHER_PROBLEM,
+ "Extension does not know id %u\n", c);
+ cb.arg = optarg;
+ cb.invert = invert;
+ cb.ext_name = m->name;
+ cb.data = m->m->data;
+ cb.xflags = m->mflags;
+ m->x6_parse(&cb);
+ m->mflags = cb.xflags;
+}
+
+/**
+ * @name: name of extension
+ * @entry: current option (from all ext's entries) being validated
+ * @xflags: flags the extension has collected
+ * @i: conflicting option (id) to test for
+ */
+static void
+xtables_option_fcheck2(const char *name, const struct xt_option_entry *entry,
+ const struct xt_option_entry *other,
+ unsigned int xflags)
+{
+ unsigned int ef = 1 << entry->id, of = 1 << other->id;
+
+ if (entry->also & of && !(xflags & of))
+ xt_params->exit_err(PARAMETER_PROBLEM,
+ "%s: option \"--%s\" also requires \"--%s\".\n",
+ name, entry->name, other->name);
+
+ if (!(entry->excl & of))
+ /* Use of entry does not collide with other option, good. */
+ return;
+ if ((xflags & (ef | of)) != (ef | of))
+ /* Conflicting options were not used. */
+ return;
+
+ xt_params->exit_err(PARAMETER_PROBLEM,
+ "%s: option \"--%s\" cannot be used together with \"--%s\".\n",
+ name, entry->name, other->name);
+}
+
+/**
+ * @name: name of extension
+ * @xflags: accumulated flags
+ * @entry: extension's option table
+ *
+ * Check that all option constraints have been met. This effectively replaces
+ * ->final_check of the older API.
+ */
+void xtables_options_fcheck(const char *name, unsigned int xflags,
+ const struct xt_option_entry *table)
+{
+ const struct xt_option_entry *entry, *other;
+ unsigned int i;
+
+ for (entry = table; entry->name != NULL; ++entry) {
+ if (entry->flags & XTOPT_MAND &&
+ !(xflags & (1 << entry->id)))
+ xt_params->exit_err(PARAMETER_PROBLEM,
+ "%s: option \"--%s\" must be specified\n",
+ name, entry->name);
+
+ for (i = 0; i < CHAR_BIT * sizeof(entry->id); ++i) {
+ if (entry->id == i)
+ /*
+ * Avoid conflict with self. Multi-use check
+ * was done earlier in xtables_option_parse.
+ */
+ continue;
+ other = xtables_option_lookup(table, i);
+ if (other == NULL)
+ continue;
+ xtables_option_fcheck2(name, entry, other, xflags);
+ }
+ }
+}