Commit be68bd50b7 for strongswan.org

commit be68bd50b7d43f50f9e9f5a4c3ed9b2689face8d
Author: Tobias Brunner <tobias@strongswan.org>
Date:   Thu Jan 29 17:32:03 2026 +0100

    pki: Add global --debug and --options arguments

    This allows setting the log level before initializing the library and
    loading the plugins, as well as having the top-level command itself in
    an options file.

diff --git a/src/pki/command.c b/src/pki/command.c
index abf0ba61c1..18a3b7dedb 100644
--- a/src/pki/command.c
+++ b/src/pki/command.c
@@ -1,4 +1,5 @@
 /*
+ * Copyright (C) 2012-2026 Tobias Brunner
  * Copyright (C) 2009 Martin Willi
  *
  * Copyright (C) secunet Security Networks AG
@@ -63,12 +64,48 @@ static struct option command_opts[MAX_COMMANDS > MAX_OPTIONS ? MAX_COMMANDS : MA
  */
 static char command_optstring[(MAX_COMMANDS > MAX_OPTIONS ? MAX_COMMANDS : MAX_OPTIONS) * 3];

+/**
+ * Common options
+ */
+static command_option_t shared_options[] = {
+	{
+		"debug", 'v', 1, "set debug level, default: 1"
+	},
+	{
+		"options", '+', 1, "read command line options from file"
+	},
+};
+
+/**
+ * Add a single option to command_opts/command_optstr at the current locations.
+ */
+static void build_option(command_option_t *option, int i, int *pos)
+{
+	command_opts[i].name = option->name;
+	command_opts[i].has_arg = option->arg;
+	command_opts[i].val = option->op;
+
+	command_optstring[(*pos)++] = option->op;
+	switch (option->arg)
+	{
+		case optional_argument:
+			command_optstring[(*pos)++] = ':';
+			/* FALL */
+		case required_argument:
+			command_optstring[(*pos)++] = ':';
+			/* FALL */
+		case no_argument:
+		default:
+			break;
+	}
+}
+
 /**
  * Build command_opts/command_optstr for the active command
  */
 static void build_opts()
 {
-	int i, pos = 0;
+	int i, j, pos = 0;

 	memset(command_opts, 0, sizeof(command_opts));
 	memset(command_optstring, 0, sizeof(command_optstring));
@@ -80,33 +117,32 @@ static void build_opts()
 			command_opts[i].val = cmds[i].op;
 			command_optstring[i] = cmds[i].op;
 		}
+		/* add shared options not as actual option but as commands with
+		 * argument (which commands usually don't take) */
+		if (i > MAX_COMMANDS - countof(shared_options))
+		{
+			fprintf(stderr, "unable to add global shared options, please "
+					"increase MAX_COMMANDS\n");
+		}
+		else
+		{
+			for (j = 0, pos = i; j < countof(shared_options); j++, i++)
+			{
+				build_option(&shared_options[j], i, &pos);
+			}
+		}
 	}
 	else
 	{
-		for (i = 0; cmds[active].options[i].name; i++)
+		for (i = 0; i < MAX_OPTIONS && cmds[active].options[i].name; i++)
 		{
-			command_opts[i].name = cmds[active].options[i].name;
-			command_opts[i].has_arg = cmds[active].options[i].arg;
-			command_opts[i].val = cmds[active].options[i].op;
-			command_optstring[pos++] = cmds[active].options[i].op;
-			switch (cmds[active].options[i].arg)
-			{
-				case optional_argument:
-					command_optstring[pos++] = ':';
-					/* FALL */
-				case required_argument:
-					command_optstring[pos++] = ':';
-					/* FALL */
-				case no_argument:
-				default:
-					break;
-			}
+			build_option(&cmds[active].options[i], i, &pos);
 		}
 	}
 }

 /**
- * getopt_long wrapper
+ * Wrapper around getopt_long() that skippes common options
  */
 int command_getopt(char **arg)
 {
@@ -121,17 +157,25 @@ int command_getopt(char **arg)
 			case 'v':
 				continue;
 			default:
-				*arg = optarg;
+				if (arg)
+				{
+					*arg = optarg;
+				}
 				return op;
 		}
 	}
 }

 /**
- * Process options common for all commands
+ * Pre-process options common for all commands
  */
-static bool process_common_opts()
+static bool process_common_opts(bool init)
 {
+	int prevoptind = optind;
+	bool success = TRUE;
+
+	/* don't report any errors during this pre-processing */
+	opterr = 0;
 	while (TRUE)
 	{
 		switch (getopt_long(argc, argv, command_optstring, command_opts, NULL))
@@ -139,20 +183,30 @@ static bool process_common_opts()
 			case '+':
 				if (!options->from(options, optarg, &argc, &argv, optind))
 				{
-					return FALSE;
+					success = FALSE;
+					break;
 				}
 				continue;
 			case 'v':
 				dbg_default_set_level(atoi(optarg));
 				continue;
 			default:
+				if (init)
+				{	/* stop if we found a known command during initialization,
+					 * otherwise we'd process e.g. --options twice */
+					break;
+				}
 				continue;
 			case '?':
-				return FALSE;
 			case EOF:
-				return TRUE;
+				break;
 		}
+		break;
 	}
+	/* restore option parser state after pre-processing */
+	optind = prevoptind;
+	opterr = 1;
+	return success;
 }

 /**
@@ -160,7 +214,7 @@ static bool process_common_opts()
  */
 void command_register(command_t command)
 {
-	int i;
+	int i, j;

 	if (registered == MAX_COMMANDS)
 	{
@@ -173,26 +227,24 @@ void command_register(command_t command)
 	/* append default options, but not to --help */
 	if (!active)
 	{
-		for (i = 0; i < countof(cmds[registered].options) - 1; i++)
+		for (i = 0; i < MAX_OPTIONS; i++)
 		{
 			if (!cmds[registered].options[i].name)
 			{
 				break;
 			}
 		}
-		if (i > countof(cmds[registered].options) - 3)
+		if (i > MAX_OPTIONS - countof(shared_options))
 		{
 			fprintf(stderr, "command '%s' registered too many options, please "
 					"increase MAX_OPTIONS\n", command.cmd);
 		}
 		else
 		{
-			cmds[registered].options[i++] = (command_option_t) {
-				"debug",	'v', 1, "set debug level, default: 1"
-			};
-			cmds[registered].options[i++] = (command_option_t) {
-				"options",	'+', 1, "read command line options from file"
-			};
+			for (j = 0; j < countof(shared_options); j++)
+			{
+				cmds[registered].options[i++] = shared_options[j];
+			}
 		}
 		for (i = 0; cmds[registered].line[i]; i++)
 		{
@@ -223,14 +275,25 @@ int command_usage(char *error)

 	if (active == help_idx)
 	{
-		fprintf(out, "\nloaded plugins: %s\nusage:\n"
-				"  pki command [options]\ncommands:\n",
-				lib->plugins->loaded_plugins(lib->plugins));
+		fprintf(out, "\n");
+		if (lib)
+		{
+			fprintf(out, "loaded plugins: %s\nusage:\n"
+					"  pki command [options]\ncommands:\n",
+					lib->plugins->loaded_plugins(lib->plugins));
+		}
 		for (i = 0; i < MAX_COMMANDS && cmds[i].cmd; i++)
 		{
 			fprintf(out, "  --%-7s (-%c)  %s\n",
 					cmds[i].cmd, cmds[i].op, cmds[i].description);
 		}
+		fprintf(out, "options:\n");
+		for (i = 0; i < countof(shared_options); i++)
+		{
+			fprintf(out, "  --%-7s (-%c)  %s\n",
+					shared_options[i].name, shared_options[i].op,
+					shared_options[i].desc);
+		}
 	}
 	else
 	{
@@ -249,7 +312,7 @@ int command_usage(char *error)
 			}
 		}
 		fprintf(out, "options:\n");
-		for (i = 0; cmds[active].options[i].name; i++)
+		for (i = 0; i < MAX_OPTIONS && cmds[active].options[i].name; i++)
 		{
 			fprintf(out, "  --%-15s (-%c)  %s\n",
 					cmds[active].options[i].name, cmds[active].options[i].op,
@@ -280,35 +343,51 @@ static void cleanup()
 	options->destroy(options);
 }

-/**
- * Dispatch commands.
+/*
+ * Described in header
  */
-int command_dispatch(int c, char *v[])
+int command_init(int c, char *v[])
 {
 	int op, i;

+	argc = c;
+	argv = v;
+
 	options = options_create();
 	atexit(cleanup);
+
 	active = help_idx = registered;
-	argc = c;
-	argv = v;
-	command_register((command_t){help, 'h', "help", "show usage information"});
+	command_register((command_t){help, 'h', "help", "show usage, version and "
+					 "plugin information"});

 	build_opts();
-	op = getopt_long(c, v, command_optstring, command_opts, NULL);
+	/* handle common options until we find a command */
+	if (!process_common_opts(TRUE))
+	{
+		return command_usage("invalid --options");
+	}
+	op = command_getopt(NULL);
 	for (i = 0; i < MAX_COMMANDS && cmds[i].cmd; i++)
 	{
 		if (cmds[i].op == op)
 		{
 			active = i;
 			build_opts();
-			if (!process_common_opts())
+			/* handle common options again, now with specific options loaded */
+			if (!process_common_opts(FALSE))
 			{
-				return command_usage("invalid options");
+				return command_usage("invalid --options");
 			}
-			optind = 2;
-			return cmds[i].call();
+			return 0;
 		}
 	}
-	return command_usage(c > 1 ? "invalid operation" : NULL);
+	return command_usage(op != EOF ? "invalid command" : NULL);
+}
+
+/*
+ * Described in header
+ */
+int command_dispatch()
+{
+	return cmds[active].call();
 }
diff --git a/src/pki/command.h b/src/pki/command.h
index f46b49de03..d1d41492ab 100644
--- a/src/pki/command.h
+++ b/src/pki/command.h
@@ -1,4 +1,5 @@
 /*
+ * Copyright (C) 2014-2026 Tobias Brunner
  * Copyright (C) 2009 Martin Willi
  *
  * Copyright (C) secunet Security Networks AG
@@ -23,14 +24,14 @@
 #define COMMAND_H_

 /**
- * Maximum number of commands (+1).
+ * Maximum number of commands (+2).
  */
-#define MAX_COMMANDS 19
+#define MAX_COMMANDS 21

 /**
- * Maximum number of options in a command (+3)
+ * Maximum number of options in a command (+2)
  */
-#define MAX_OPTIONS 36
+#define MAX_OPTIONS 35

 /**
  * Maximum number of usage summary lines (+1)
@@ -83,10 +84,15 @@ int command_getopt(char **arg);
  */
 void command_register(command_t command);

+/**
+ * Initialize command parsing/dispatching.
+ */
+int command_init(int argc, char *argv[]);
+
 /**
  * Dispatch commands.
  */
-int command_dispatch(int argc, char *argv[]);
+int command_dispatch();

 /**
  * Show usage information of active command.
diff --git a/src/pki/pki.c b/src/pki/pki.c
index 0b03bf7aa6..c50d44a6af 100644
--- a/src/pki/pki.c
+++ b/src/pki/pki.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012-2023 Tobias Brunner
+ * Copyright (C) 2012-2026 Tobias Brunner
  * Copyright (C) 2009 Martin Willi
  *
  * Copyright (C) secunet Security Networks AG
@@ -485,6 +485,13 @@ static void remove_callback()
 int main(int argc, char *argv[])
 {
 	char *plugins;
+	int status;
+
+	status = command_init(argc, argv);
+	if (status)
+	{
+		return status;
+	}

 	atexit(library_deinit);
 	if (!library_init(NULL, "pki"))
@@ -509,5 +516,5 @@ int main(int argc, char *argv[])

 	add_callback();
 	atexit(remove_callback);
-	return command_dispatch(argc, argv);
+	return command_dispatch();
 }