diff options
Diffstat (limited to 'common/tool.c')
-rw-r--r-- | common/tool.c | 331 |
1 files changed, 331 insertions, 0 deletions
diff --git a/common/tool.c b/common/tool.c new file mode 100644 index 0000000..70bb4d2 --- /dev/null +++ b/common/tool.c @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2011, Collabora Ltd. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Author: Stef Walter <stefw@collabora.co.uk> + */ + +#include "config.h" + +#include "buffer.h" +#include "compat.h" +#include "debug.h" +#include "message.h" +#include "path.h" + +#include <assert.h> +#include <ctype.h> +#include <getopt.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "tool.h" + +static char +short_option (int opt) +{ + if (isalpha (opt) || isdigit (opt)) + return (char)opt; + return 0; +} + +static const struct option * +find_option (const struct option *longopts, + int opt) +{ + int i; + + for (i = 0; longopts[i].name != NULL; i++) { + if (longopts[i].val == opt) + return longopts + i; + } + + return NULL; +} + +void +p11_tool_usage (const p11_tool_desc *usages, + const struct option *longopts) +{ + const struct option *longopt; + const int indent = 22; + const char *long_name; + const char *description; + const char *next; + char short_name; + int spaces; + int len; + int i; + + for (i = 0; usages[i].text != NULL; i++) { + + /* If no option, then this is a heading */ + if (!usages[i].option) { + printf ("%s\n\n", usages[i].text); + continue; + } + + longopt = find_option (longopts, usages[i].option); + long_name = longopt ? longopt->name : NULL; + short_name = short_option (usages[i].option); + description = usages[i].text; + + if (short_name && long_name) + len = printf (" -%c, --%s", (int)short_name, long_name); + else if (long_name) + len = printf (" --%s", long_name); + else + len = printf (" -%c", (int)short_name); + if (longopt && longopt->has_arg) + len += printf ("%s<%s>", + long_name ? "=" : " ", + usages[i].arg ? usages[i].arg : "..."); + if (len < indent) { + spaces = indent - len; + } else { + printf ("\n"); + spaces = indent; + } + while (description) { + while (spaces-- > 0) + fputc (' ', stdout); + next = strchr (description, '\n'); + if (next) { + next += 1; + printf ("%.*s", (int)(next - description), description); + description = next; + spaces = indent; + } else { + printf ("%s\n", description); + break; + } + } + + } +} + +int +p11_tool_getopt (int argc, + char *argv[], + const struct option *longopts) +{ + p11_buffer buf; + int ret; + char opt; + int i; + + if (!p11_buffer_init_null (&buf, 64)) + return_val_if_reached (-1); + + for (i = 0; longopts[i].name != NULL; i++) { + opt = short_option (longopts[i].val); + if (opt != 0) { + p11_buffer_add (&buf, &opt, 1); + assert (longopts[i].has_arg != optional_argument); + if (longopts[i].has_arg == required_argument) + p11_buffer_add (&buf, ":", 1); + } + } + + ret = getopt_long (argc, argv, buf.data, longopts, NULL); + + p11_buffer_uninit (&buf); + + return ret; +} + +static void +command_usage (const p11_tool_command *commands) +{ + const char *progname; + int i; + + progname = getprogname (); + printf ("usage: %s command <args>...\n", progname); + printf ("\nCommon %s commands are:\n", progname); + for (i = 0; commands[i].name != NULL; i++) { + if (strcmp (commands[i].name, P11_TOOL_FALLBACK) != 0) + printf (" %-15s %s\n", commands[i].name, commands[i].text); + } + printf ("\nSee '%s <command> --help' for more information\n", progname); +} + +static void +verbose_arg (void) +{ + putenv ("P11_KIT_DEBUG=all"); + p11_message_loud (); +} + +static void +quiet_arg (void) +{ + putenv ("P11_KIT_DEBUG="); + p11_message_quiet (); +} + +int +p11_tool_main (int argc, + char *argv[], + const p11_tool_command *commands) +{ + const p11_tool_command *fallback = NULL; + char *command = NULL; + bool want_help = false; + bool skip; + int in, out; + int i; + + /* + * Parse the global options. We rearrange the options as + * necessary, in order to pass relevant options through + * to the commands, but also have them take effect globally. + */ + + for (in = 1, out = 1; in < argc; in++, out++) { + + /* The non-option is the command, take it out of the arguments */ + if (argv[in][0] != '-') { + if (!command) { + skip = true; + command = argv[in]; + } else { + skip = false; + } + + /* The global long options */ + } else if (argv[in][1] == '-') { + skip = false; + + if (strcmp (argv[in], "--") == 0) { + if (!command) { + p11_message ("no command specified"); + return 2; + } else { + break; + } + + } else if (strcmp (argv[in], "--verbose") == 0) { + verbose_arg (); + + } else if (strcmp (argv[in], "--quiet") == 0) { + quiet_arg (); + + } else if (strcmp (argv[in], "--help") == 0) { + want_help = true; + + } else if (!command) { + p11_message ("unknown global option: %s", argv[in]); + return 2; + } + + /* The global short options */ + } else { + skip = false; + + for (i = 1; argv[in][i] != '\0'; i++) { + switch (argv[in][i]) { + case 'h': + want_help = true; + break; + + /* Compatibility option */ + case 'l': + command = "list-modules"; + break; + + case 'v': + verbose_arg (); + break; + + case 'q': + quiet_arg (); + break; + + default: + if (!command) { + p11_message ("unknown global option: -%c", (int)argv[in][i]); + return 2; + } + break; + } + } + } + + /* Skipping this argument? */ + if (skip) + out--; + else + argv[out] = argv[in]; + } + + /* Initialize tool's debugging after setting env vars above */ + p11_debug_init (); + + if (command == NULL) { + /* As a special favor if someone just typed the command, help them out */ + if (argc == 1) { + command_usage (commands); + return 2; + } else if (want_help) { + command_usage (commands); + return 0; + } else { + p11_message ("no command specified"); + return 2; + } + } + + argc = out; + + /* Look for the command */ + for (i = 0; commands[i].name != NULL; i++) { + if (strcmp (commands[i].name, P11_TOOL_FALLBACK) == 0) { + fallback = commands + i; + + } else if (strcmp (commands[i].name, command) == 0) { + argv[0] = command; + return (commands[i].function) (argc, argv); + } + } + + /* Got here because no command matched */ + if (fallback != NULL) { + argv[0] = command; + return (fallback->function) (argc, argv); + } + + /* At this point we have no command */ + p11_message ("'%s' is not a valid command. See '%s --help'", + command, getprogname ()); + return 2; +} |