diff --git a/Makefile b/Makefile
index 6ce44b3..6762eec 100644
--- a/Makefile
+++ b/Makefile
@@ -39,7 +39,11 @@ LIBIFUPDOWN_SRC = \
 LIBIFUPDOWN_OBJ = ${LIBIFUPDOWN_SRC:.c=.o}
 LIBIFUPDOWN_LIB = libifupdown.a
 
-MULTICALL_SRC = cmd/multicall.c
+MULTICALL_SRC = \
+	cmd/multicall.c \
+	cmd/multicall-options.c \
+	cmd/multicall-exec-options.c \
+	cmd/multicall-match-options.c
 MULTICALL_OBJ = ${MULTICALL_SRC:.c=.o}
 MULTICALL = ifupdown
 
diff --git a/cmd/ifquery.c b/cmd/ifquery.c
index 2778dea..0612823 100644
--- a/cmd/ifquery.c
+++ b/cmd/ifquery.c
@@ -21,11 +21,6 @@
 #include "libifupdown/libifupdown.h"
 #include "cmd/multicall.h"
 
-static struct lif_execute_opts exec_opts = {
-	.interfaces_file = INTERFACES_FILE,
-	.executor_path = EXECUTOR_PATH
-};
-
 void
 print_interface(struct lif_interface *iface)
 {
@@ -163,38 +158,6 @@ print_interface_property(struct lif_interface *iface, const char *property)
 	}
 }
 
-void
-ifquery_usage(int status)
-{
-	fprintf(stderr, "usage: ifquery [options] <interfaces>\n");
-	fprintf(stderr, "       ifquery [options] --list\n");
-
-	fprintf(stderr, "\nOptions:\n");
-	fprintf(stderr, "  -h, --help                   this help\n");
-	fprintf(stderr, "  -V, --version                show this program's version\n");
-	fprintf(stderr, "  -i, --interfaces FILE        use FILE for interface definitions\n");
-	fprintf(stderr, "  -L, --list                   list matching interfaces\n");
-	fprintf(stderr, "  -a, --auto                   only match against interfaces hinted as 'auto'\n");
-	fprintf(stderr, "  -I, --include PATTERN        only match against interfaces matching PATTERN\n");
-	fprintf(stderr, "  -X, --exclude PATTERN        never match against interfaces matching PATTERN\n");
-	fprintf(stderr, "  -P, --pretty-print           pretty print the interfaces instead of just listing\n");
-	fprintf(stderr, "  -S, --state-file FILE        use FILE for state\n");
-	fprintf(stderr, "  -s, --state                  show configured state\n");
-	fprintf(stderr, "  -D, --dot                    generate a dependency graph\n");
-	fprintf(stderr, "  -p, --property PROPERTY      print values of properties matching PROPERTY\n");
-
-	exit(status);
-}
-
-struct match_options {
-	bool is_auto;
-	char *exclude_pattern;
-	char *include_pattern;
-	bool pretty_print;
-	bool dot;
-	char *property;
-};
-
 void
 list_interfaces(struct lif_dict *collection, struct match_options *opts)
 {
@@ -256,83 +219,56 @@ list_state(struct lif_dict *state, struct match_options *opts)
 	}
 }
 
+static bool listing = false, listing_stat = false;
+
+static void
+handle_local(int short_opt, const struct if_option *opt, const char *opt_arg, const struct if_applet *applet)
+{
+	(void) opt;
+	(void) applet;
+
+	switch (short_opt) {
+	case 'L':
+		listing = true;
+		break;
+	case 'P':
+		match_opts.pretty_print = true;
+		break;
+	case 's':
+		listing_stat = true;
+		break;
+	case 'D':
+		match_opts.dot = true;
+		break;
+	case 'p':
+		match_opts.property = opt_arg;
+		break;
+	}
+}
+
+static struct if_option local_options[] = {
+	{'s', "state", NULL, "show configured state", false, handle_local},
+	{'p', "property", "property PROPERTY", "print values of properties matching PROPERTY", true, handle_local},
+	{'D', "dot", NULL, "generate a dependency graph", false, handle_local},
+	{'L', "list", NULL, "list matching interfaces", false, handle_local},
+	{'P', "pretty-print", NULL, "pretty print the interfaces instead of just listing", false, handle_local},
+};
+
+static struct if_option_group local_option_group = {
+	.desc = "Program-specific options",
+	.group_size = ARRAY_SIZE(local_options),
+	.group = local_options
+};
+
 int
 ifquery_main(int argc, char *argv[])
 {
 	struct lif_dict state = {};
 	struct lif_dict collection = {};
-	struct option long_options[] = {
-		{"help", no_argument, 0, 'h'},
-		{"version", no_argument, 0, 'V'},
-		{"interfaces", required_argument, 0, 'i'},
-		{"list", no_argument, 0, 'L'},
-		{"auto", no_argument, 0, 'a'},
-		{"include", required_argument, 0, 'I'},
-		{"exclude", required_argument, 0, 'X'},
-		{"pretty-print", no_argument, 0, 'P'},
-		{"state-file", required_argument, 0, 'S'},
-		{"state", no_argument, 0, 's'},
-		{"dot", no_argument, 0, 'D'},
-		{"property", required_argument, 0, 'p'},
-		{"executor-path", required_argument, 0, 'E'},
-		{NULL, 0, 0, 0}
-	};
-	struct match_options match_opts = {};
-	bool listing = false, listing_stat = false;
-	char *state_file = STATE_FILE;
 
-	for (;;)
+	if (!lif_state_read_path(&state, exec_opts.state_file))
 	{
-		int c = getopt_long(argc, argv, "hVi:LaI:X:PS:sDp:E:", long_options, NULL);
-		if (c == -1)
-			break;
-
-		switch (c) {
-		case 'h':
-			ifquery_usage(EXIT_SUCCESS);
-			break;
-		case 'V':
-			lif_common_version();
-			break;
-		case 'i':
-			exec_opts.interfaces_file = optarg;
-			break;
-		case 'L':
-			listing = true;
-			break;
-		case 'a':
-			match_opts.is_auto = true;
-			break;
-		case 'I':
-			match_opts.include_pattern = optarg;
-			break;
-		case 'X':
-			match_opts.exclude_pattern = optarg;
-			break;
-		case 'P':
-			match_opts.pretty_print = true;
-			break;
-		case 'S':
-			state_file = optarg;
-			break;
-		case 's':
-			listing_stat = true;
-			break;
-		case 'D':
-			match_opts.dot = true;
-			break;
-		case 'p':
-			match_opts.property = optarg;
-			break;
-		case 'E':
-			exec_opts.executor_path = optarg;
-			break;
-		}
-	}
-
-	if (!lif_state_read_path(&state, state_file))
-	{
-		fprintf(stderr, "%s: could not parse %s\n", argv0, state_file);
+		fprintf(stderr, "%s: could not parse %s\n", argv0, exec_opts.state_file);
 		return EXIT_FAILURE;
 	}
 
@@ -344,7 +280,7 @@ ifquery_main(int argc, char *argv[])
 
 	/* --list --state is not allowed */
 	if (listing && listing_stat)
-		ifquery_usage(EXIT_FAILURE);
+		generic_usage(self_applet, EXIT_FAILURE);
 
 	if (listing)
 	{
@@ -358,7 +294,7 @@ ifquery_main(int argc, char *argv[])
 	}
 
 	if (optind >= argc)
-		ifquery_usage(EXIT_FAILURE);
+		generic_usage(self_applet, EXIT_FAILURE);
 
 	int idx = optind;
 	for (; idx < argc; idx++)
@@ -390,6 +326,8 @@ ifquery_main(int argc, char *argv[])
 
 struct if_applet ifquery_applet = {
 	.name = "ifquery",
+	.desc = "query interface configuration",
 	.main = ifquery_main,
-	.usage = ifquery_usage
+	.usage = "ifquery [options] <interfaces>\n  ifquery [options] --list",
+	.groups = { &global_option_group, &match_option_group, &exec_option_group, &local_option_group },
 };
diff --git a/cmd/ifupdown.c b/cmd/ifupdown.c
index 9a9aada..c1bce3a 100644
--- a/cmd/ifupdown.c
+++ b/cmd/ifupdown.c
@@ -24,40 +24,7 @@
 #include "libifupdown/libifupdown.h"
 #include "cmd/multicall.h"
 
-struct match_options {
-	bool is_auto;
-	char *exclude_pattern;
-	char *include_pattern;
-};
-
 static bool up;
-static struct lif_execute_opts exec_opts = {
-	.executor_path = EXECUTOR_PATH,
-	.interfaces_file = INTERFACES_FILE,
-	.state_file = STATE_FILE,
-};
-
-void
-ifupdown_usage(int status)
-{
-	fprintf(stderr, "usage: %s [options] <interfaces>\n", argv0);
-
-	fprintf(stderr, "\nOptions:\n");
-	fprintf(stderr, "  -h, --help                   this help\n");
-	fprintf(stderr, "  -V, --version                show this program's version\n");
-	fprintf(stderr, "  -i, --interfaces FILE        use FILE for interface definitions\n");
-	fprintf(stderr, "  -S, --state-file FILE        use FILE for state\n");
-	fprintf(stderr, "  -a, --auto                   only match against interfaces hinted as 'auto'\n");
-	fprintf(stderr, "  -I, --include PATTERN        only match against interfaces matching PATTERN\n");
-	fprintf(stderr, "  -X, --exclude PATTERN        never match against interfaces matching PATTERN\n");
-	fprintf(stderr, "  -n, --no-act                 do not actually run any commands\n");
-	fprintf(stderr, "  -v, --verbose                show what commands are being run\n");
-	fprintf(stderr, "  -E, --executor-path PATH     use PATH for executor directory\n");
-	fprintf(stderr, "  -f, --force                  force (de)configuration\n");
-	fprintf(stderr, "  -L, --no-lock                do not use a lockfile to serialize state changes\n");
-
-	exit(status);
-}
 
 bool
 is_ifdown()
@@ -195,68 +162,6 @@ ifupdown_main(int argc, char *argv[])
 
 	struct lif_dict state = {};
 	struct lif_dict collection = {};
-	struct option long_options[] = {
-		{"help", no_argument, 0, 'h'},
-		{"version", no_argument, 0, 'V'},
-		{"interfaces", required_argument, 0, 'i'},
-		{"auto", no_argument, 0, 'a'},
-		{"include", required_argument, 0, 'I'},
-		{"exclude", required_argument, 0, 'X'},
-		{"state-file", required_argument, 0, 'S'},
-		{"no-act", no_argument, 0, 'n'},
-		{"verbose", no_argument, 0, 'v'},
-		{"executor-path", required_argument, 0, 'E'},
-		{"force", no_argument, 0, 'f'},
-		{"no-lock", no_argument, 0, 'L'},
-		{NULL, 0, 0, 0}
-	};
-	struct match_options match_opts = {};
-
-	for (;;)
-	{
-		int c = getopt_long(argc, argv, "hVi:aI:X:S:nvE:fL", long_options, NULL);
-		if (c == -1)
-			break;
-
-		switch (c) {
-		case 'h':
-			ifupdown_usage(EXIT_SUCCESS);
-			break;
-		case 'V':
-			lif_common_version();
-			break;
-		case 'i':
-			exec_opts.interfaces_file = optarg;
-			break;
-		case 'a':
-			match_opts.is_auto = true;
-			break;
-		case 'I':
-			match_opts.include_pattern = optarg;
-			break;
-		case 'X':
-			match_opts.exclude_pattern = optarg;
-			break;
-		case 'S':
-			exec_opts.state_file = optarg;
-			break;
-		case 'n':
-			exec_opts.mock = true;
-			exec_opts.verbose = true;
-			break;
-		case 'v':
-			exec_opts.verbose = true;
-			break;
-		case 'E':
-			exec_opts.executor_path = optarg;
-			break;
-		case 'f':
-			break;
-		case 'L':
-			exec_opts.no_lock = true;
-			break;
-		}
-	}
 
 	if (!lif_state_read_path(&state, exec_opts.state_file))
 	{
@@ -284,7 +189,7 @@ ifupdown_main(int argc, char *argv[])
 		return EXIT_SUCCESS;
 	}
 	else if (optind >= argc)
-		ifupdown_usage(EXIT_FAILURE);
+		generic_usage(self_applet, EXIT_FAILURE);
 
 	int idx = optind;
 	for (; idx < argc; idx++)
@@ -331,12 +236,16 @@ ifupdown_main(int argc, char *argv[])
 
 struct if_applet ifup_applet = {
 	.name = "ifup",
+	.desc = "bring interfaces up",
 	.main = ifupdown_main,
-	.usage = ifupdown_usage
+	.usage = "ifup [options] <interfaces>",
+	.groups = { &global_option_group, &match_option_group, &exec_option_group, },
 };
 
 struct if_applet ifdown_applet = {
 	.name = "ifdown",
+	.desc = "take interfaces down",
 	.main = ifupdown_main,
-	.usage = ifupdown_usage
+	.usage = "ifdown [options] <interfaces>",
+	.groups = { &global_option_group, &match_option_group, &exec_option_group, },
 };
diff --git a/cmd/multicall-exec-options.c b/cmd/multicall-exec-options.c
new file mode 100644
index 0000000..0ebe85d
--- /dev/null
+++ b/cmd/multicall-exec-options.c
@@ -0,0 +1,77 @@
+/*
+ * cmd/multicall-exec-options.c
+ * Purpose: multi-call binary frontend -- option handling
+ *
+ * Copyright (c) 2020 Ariadne Conill <ariadne@dereferenced.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * This software is provided 'as is' and without any warranty, express or
+ * implied.  In no event shall the authors be liable for any damages arising
+ * from the use of this software.
+ */
+
+#define _GNU_SOURCE
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include "cmd/multicall.h"
+
+struct lif_execute_opts exec_opts = {
+	.interfaces_file = INTERFACES_FILE,
+	.executor_path = EXECUTOR_PATH,
+	.state_file = STATE_FILE
+};
+
+static void handle_exec(int short_opt, const struct if_option *opt, const char *opt_arg, const struct if_applet *applet)
+{
+	(void) opt;
+	(void) applet;
+
+	switch (short_opt)
+	{
+		case 'i':
+			exec_opts.interfaces_file = opt_arg;
+			break;
+		case 'S':
+			exec_opts.state_file = opt_arg;
+			break;
+		case 'n':
+			exec_opts.mock = true;
+			exec_opts.verbose = true;
+			break;
+		case 'v':
+			exec_opts.verbose = true;
+			break;
+		case 'E':
+			exec_opts.executor_path = opt_arg;
+			break;
+		case 'f':
+			break;
+		case 'l':
+			exec_opts.no_lock = true;
+			break;
+		default:
+			break;
+	}
+}
+
+static struct if_option exec_options[] = {
+	{'f', "force", NULL, "force (de)configuration", true, handle_exec},
+	{'i', "interfaces", "interfaces FILE", "use FILE for interface definitions", true, handle_exec},
+	{'l', "no-lock", NULL, "do not use a lockfile to serialize state changes", false, handle_exec},
+	{'n', "no-act", NULL, "do not actually run any commands", false, handle_exec},
+	{'v', "verbose", NULL, "show what commands are being run", false, handle_exec},
+	{'E', "executor-path", "executor-path PATH", "use PATH for executor directory", true, handle_exec},
+	{'S', "state-file", "state-file FILE", "use FILE for state", true, handle_exec},
+};
+
+struct if_option_group exec_option_group = {
+	.desc = "Execution",
+	.group_size = ARRAY_SIZE(exec_options),
+	.group = exec_options
+};
diff --git a/cmd/multicall-match-options.c b/cmd/multicall-match-options.c
new file mode 100644
index 0000000..d89056a
--- /dev/null
+++ b/cmd/multicall-match-options.c
@@ -0,0 +1,57 @@
+/*
+ * cmd/multicall-match-options.c
+ * Purpose: multi-call binary frontend -- option handling
+ *
+ * Copyright (c) 2020 Ariadne Conill <ariadne@dereferenced.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * This software is provided 'as is' and without any warranty, express or
+ * implied.  In no event shall the authors be liable for any damages arising
+ * from the use of this software.
+ */
+
+#define _GNU_SOURCE
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include "cmd/multicall.h"
+
+struct match_options match_opts = {};
+
+static void handle_match(int short_opt, const struct if_option *opt, const char *opt_arg, const struct if_applet *applet)
+{
+	(void) opt;
+	(void) applet;
+
+	switch (short_opt)
+	{
+	case 'a':
+		match_opts.is_auto = true;
+		break;
+	case 'I':
+		match_opts.include_pattern = opt_arg;
+		break;
+	case 'X':
+		match_opts.exclude_pattern = opt_arg;
+		break;
+	default:
+		break;
+	}
+}
+
+static struct if_option match_options[] = {
+	{'a', "auto", NULL, "only match against interfaces hinted as 'auto'", false, handle_match},
+	{'I', "include", "include PATTERN", "only match against interfaces matching PATTERN", true, handle_match},
+	{'X', "exclude", "exclude PATTERN", "never match against interfaces matching PATTERN", true, handle_match},
+};
+
+struct if_option_group match_option_group = {
+	.desc = "Matching interfaces",
+	.group_size = ARRAY_SIZE(match_options),
+	.group = match_options
+};
diff --git a/cmd/multicall-options.c b/cmd/multicall-options.c
new file mode 100644
index 0000000..9037264
--- /dev/null
+++ b/cmd/multicall-options.c
@@ -0,0 +1,159 @@
+/*
+ * cmd/multicall-options.c
+ * Purpose: multi-call binary frontend -- option handling
+ *
+ * Copyright (c) 2020 Ariadne Conill <ariadne@dereferenced.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * This software is provided 'as is' and without any warranty, express or
+ * implied.  In no event shall the authors be liable for any damages arising
+ * from the use of this software.
+ */
+
+#define _GNU_SOURCE
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include "cmd/multicall.h"
+
+void
+generic_usage(const struct if_applet *applet, int result)
+{
+	fprintf(stderr, "%s", applet->name);
+	if (applet->desc != NULL)
+		fprintf(stderr, " - %s", applet->desc);
+
+	fprintf(stderr, "\n");
+
+	if (applet->usage != NULL)
+		fprintf(stderr, "\nUsage:\n  %s\n", applet->usage);
+
+	size_t iter;
+	for (iter = 0; applet->groups[iter] != NULL; iter++)
+	{
+		const struct if_option_group *group = applet->groups[iter];
+
+		fprintf(stderr, "\n%s:\n", group->desc);
+
+		size_t group_iter;
+		for (group_iter = 0; group_iter < group->group_size; group_iter++)
+		{
+			const struct if_option *opt = &group->group[group_iter];
+
+			fprintf(stderr, "  ");
+
+			if (opt->short_opt)
+				fprintf(stderr, "-%c", opt->short_opt);
+			else
+				fprintf(stderr, "  ");
+
+			if (opt->long_opt)
+				fprintf(stderr, "%c --%-30s", opt->short_opt ? ',' : ' ',
+					opt->long_opt_desc ? opt->long_opt_desc : opt->long_opt);
+			else
+				fprintf(stderr, "%34s", "");
+
+			fprintf(stderr, "%s\n", opt->desc);
+		}
+	}
+
+	exit(result);
+}
+
+static void
+generic_usage_request(int short_opt, const struct if_option *option, const char *opt_arg, const struct if_applet *applet)
+{
+	(void) short_opt;
+	(void) option;
+	(void) opt_arg;
+
+	generic_usage(applet, EXIT_SUCCESS);
+}
+
+static struct if_option global_options[] = {
+	{'h', "help", NULL, "this help", false, generic_usage_request},
+	{'V', "version", NULL, "show this program's version", false, (void *) lif_common_version},
+};
+
+struct if_option_group global_option_group = {
+	.desc = "Global options",
+	.group_size = ARRAY_SIZE(global_options),
+	.group = global_options
+};
+
+const struct if_option *
+lookup_option(const struct if_applet *applet, int opt)
+{
+	size_t iter;
+	for (iter = 0; applet->groups[iter] != NULL; iter++)
+	{
+		const struct if_option_group *group = applet->groups[iter];
+		size_t group_iter;
+
+		for (group_iter = 0; group_iter < group->group_size; group_iter++)
+		{
+			const struct if_option *option = &group->group[group_iter];
+
+			if (option->short_opt == opt)
+				return option;
+		}
+	}
+
+	return NULL;
+}
+
+void
+process_options(const struct if_applet *applet, int argc, char *argv[])
+{
+	char short_opts[256] = {}, *p = short_opts;
+	struct option long_opts[256] = {};
+
+	size_t iter, long_opt_iter = 0;
+	for (iter = 0; applet->groups[iter] != NULL; iter++)
+	{
+		const struct if_option_group *group = applet->groups[iter];
+		size_t group_iter;
+
+		for (group_iter = 0; group_iter < group->group_size; group_iter++)
+		{
+			const struct if_option *opt = &group->group[group_iter];
+
+			if (opt->short_opt)
+			{
+				*p++ = opt->short_opt;
+				if (opt->require_argument)
+					*p++ = ':';
+			}
+
+			if (opt->long_opt)
+			{
+				/* XXX: handle long-opts without short-opts */
+				long_opts[long_opt_iter] = (struct option) {
+					.name = opt->long_opt,
+					.has_arg = opt->require_argument ? required_argument : no_argument,
+					.val = opt->short_opt
+				};
+
+				long_opt_iter++;
+			}
+		}
+	}
+
+	for (;;)
+	{
+		int c = getopt_long(argc, argv, short_opts, long_opts, NULL);
+		if (c == -1)
+			break;
+
+		const struct if_option *opt = lookup_option(applet, c);
+		if (opt == NULL)
+			break;
+
+		opt->handle(c, opt, optarg, applet);
+	}
+}
diff --git a/cmd/multicall.c b/cmd/multicall.c
index c6ae3d4..ccb7688 100644
--- a/cmd/multicall.c
+++ b/cmd/multicall.c
@@ -13,10 +13,12 @@
  * from the use of this software.
  */
 
+#define _GNU_SOURCE
 #include <libgen.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <getopt.h>
 #include "cmd/multicall.h"
 
 char *argv0;
@@ -25,6 +27,7 @@ extern struct if_applet ifquery_applet;
 extern struct if_applet ifup_applet;
 extern struct if_applet ifdown_applet;
 struct if_applet ifupdown_applet;
+const struct if_applet *self_applet = NULL;
 
 struct if_applet *applet_table[] = {
 	&ifdown_applet,
@@ -33,8 +36,6 @@ struct if_applet *applet_table[] = {
 	&ifupdown_applet,
 };
 
-#define ARRAY_SIZE(x)	(sizeof(x) / sizeof(*x))
-
 int
 applet_cmp(const void *a, const void *b)
 {
@@ -50,7 +51,7 @@ int
 main(int argc, char *argv[])
 {
 	argv0 = basename(argv[0]);
-	struct if_applet **app;
+	const struct if_applet **app;
 
 	app = bsearch(argv0, applet_table,
 		      ARRAY_SIZE(applet_table), sizeof(*applet_table),
@@ -62,6 +63,9 @@ main(int argc, char *argv[])
 		multicall_usage(EXIT_FAILURE);
 	}
 
+	self_applet = *app;
+	process_options(*app, argc, argv);
+
 	return (*app)->main(argc, argv);
 }
 
@@ -74,6 +78,8 @@ multicall_main(int argc, char *argv[])
 	return main(argc - 1, argv + 1);
 }
 
+struct if_applet ifupdown_applet;
+
 void
 multicall_usage(int status)
 {
@@ -96,5 +102,5 @@ multicall_usage(int status)
 struct if_applet ifupdown_applet = {
 	.name = "ifupdown",
 	.main = multicall_main,
-	.usage = multicall_usage,
+	.groups = { &global_option_group, NULL }
 };
diff --git a/cmd/multicall.h b/cmd/multicall.h
index 6fb26a4..23cb836 100644
--- a/cmd/multicall.h
+++ b/cmd/multicall.h
@@ -13,17 +13,63 @@
  * from the use of this software.
  */
 
+#include <stdbool.h>
+
 #ifndef IFUPDOWN_CMD_MULTICALL_H__GUARD
 #define IFUPDOWN_CMD_MULTICALL_H__GUARD
 
 #include "libifupdown/libifupdown.h"
 
+#define ARRAY_SIZE(x)   (sizeof(x) / sizeof(*x))
+
+struct if_applet;
+
+struct if_option {
+	char short_opt;
+	const char *long_opt;
+	const char *long_opt_desc;
+	const char *desc;
+	bool require_argument;
+	void (*const handle)(int short_opt, const struct if_option *opt, const char *opt_arg, const struct if_applet *applet);
+};
+
+struct if_option_group {
+	const char *desc;
+	size_t group_size;
+	const struct if_option *group;
+};
+
 struct if_applet {
 	const char *name;
+	const char *desc;
+	const char *usage;
 	int (*const main)(int argc, char *argv[]);
-	void (*const usage)(int status);
+	const struct if_option_group *groups[16];
 };
 
 extern char *argv0;
+extern const struct if_applet *self_applet;
+extern struct if_option_group global_option_group;
+
+struct match_options {
+	bool is_auto;
+	const char *exclude_pattern;
+	const char *include_pattern;
+	bool pretty_print;
+	bool dot;
+	const char *property;
+};
+
+extern struct match_options match_opts;
+
+extern void process_options(const struct if_applet *applet, int argc, char *argv[]);
+extern const struct if_option *lookup_option(const struct if_applet *applet, int opt);
+
+extern struct if_option_group match_option_group;
+
+extern struct lif_execute_opts exec_opts;
+extern struct if_option_group exec_option_group;
+
+void generic_usage(const struct if_applet *applet, int result);
 
 #endif