]> www.infradead.org Git - users/dhowells/kafs-utils.git/commitdiff
Implement the core of the command suite
authorDavid Howells <dhowells@redhat.com>
Fri, 11 Oct 2019 14:35:46 +0000 (15:35 +0100)
committerDavid Howells <dhowells@redhat.com>
Fri, 5 May 2023 09:05:31 +0000 (10:05 +0100)
Implement the core of the command suite.  This includes:

 (*) Standard AFS argument parsing.

 (*) Autogeneration of parsers for commands, based on description in banner
     comments and handler function arguments.

 (*) Bash completion.

 (*) Command suite multiplexing based on argv[0] and argv[1].

 (*) "help" and "apropos" for bos, fs, pts and vos.

Each command must be preceded by a comment that conforms to the following
template:

/***
 * COMMAND: suite command - one-line help
 * ARG: "-switchname"
 * ARG: "-switchname <param-help>"
 * ARG: "-switchname <param-help>+"
 * ARG: [...]"
 * ARG: ... - Auth"
 * ARG: ... - New"
 * NOCOMBINE: switchname, switchname
 * NOCOMBINE: switchname, switchname
 * NOCOMBINE: switchname, switchname
 *
 * multiline help
 * multiline help
 * multiline help
 * multiline help
 */

Aliases can be coded by:

/***
 * ALIAS: suite command - command_alias_for
 */

Signed-off-by: David Howells <dhowells@redhat.com>
16 files changed:
Makefile
kafs/.gitignore [new file with mode: 0644]
kafs/Makefile [new file with mode: 0644]
kafs/arg_completion.c [new file with mode: 0644]
kafs/arg_parse.c [new file with mode: 0644]
kafs/arg_parse.h [new file with mode: 0644]
kafs/bash_complete [new file with mode: 0644]
kafs/bos_help.c [new file with mode: 0644]
kafs/display.h [new file with mode: 0644]
kafs/display_error.c [new file with mode: 0644]
kafs/fs_help.c [new file with mode: 0644]
kafs/gen_command.py [new file with mode: 0755]
kafs/kafs.c [new file with mode: 0644]
kafs/kafs.h [new file with mode: 0644]
kafs/pts_help.c [new file with mode: 0644]
kafs/vos_help.c [new file with mode: 0644]

index 38fda8f6e04de74fe07bd29fe62e7ffb261e9704..52be5472ca6dc8c3fdcd9935327853e86433351d 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,13 @@
 all:
        $(MAKE) -C lib
+       $(MAKE) -C kafs
 
 clean:
        $(RM) *~
        $(MAKE) -C lib clean
+       $(MAKE) -C kafs clean
        $(MAKE) -C rxgen clean
+
+clean-deps:
+       $(RM) lib/.*.o.d
+       $(RM) kafs/.*.o.d
diff --git a/kafs/.gitignore b/kafs/.gitignore
new file mode 100644 (file)
index 0000000..ac691f9
--- /dev/null
@@ -0,0 +1,5 @@
+bos.[ch]
+fs.[ch]
+pts.[ch]
+vos.[ch]
+kafs
diff --git a/kafs/Makefile b/kafs/Makefile
new file mode 100644 (file)
index 0000000..189f656
--- /dev/null
@@ -0,0 +1,75 @@
+RXGEN  := ../rxgen/rxgen.py
+
+CPPFLAGS       := -I ../lib -I .
+CFLAGS         := -g -Wall -Wformat -fpie
+
+CORE_SRCS := \
+       kafs.c \
+       arg_completion.c \
+       arg_parse.c \
+       display_error.c
+
+BOS_SRCS := \
+       bos.c \
+       bos_help.c
+
+FS_SRCS := \
+       fs.c \
+       fs_help.c
+
+PTS_SRCS := \
+       pts.c \
+       pts_help.c
+
+VOS_SRCS := \
+       vos.c \
+       vos_help.c
+
+CORE_OBJS := $(patsubst %.c,%.o,$(CORE_SRCS))
+BOS_OBJS  :=  $(patsubst %.c,%.o,$(BOS_SRCS))
+FS_OBJS   :=  $(patsubst %.c,%.o,$(FS_SRCS))
+PTS_OBJS  :=  $(patsubst %.c,%.o,$(PTS_SRCS))
+VOS_OBJS  :=  $(patsubst %.c,%.o,$(VOS_SRCS))
+KAFS_OBJS := $(CORE_OBJS) $(BOS_OBJS) $(FS_OBJS) $(PTS_OBJS) $(VOS_OBJS)
+
+all: kafs
+
+bos.c bos.h: gen_command.py $(filter-out bos.c, $(BOS_SRCS))
+       ./gen_command.py bos $(filter-out bos.c, $(BOS_SRCS))
+
+fs.c fs.h: gen_command.py $(filter-out fs.c, $(FS_SRCS))
+       ./gen_command.py fs $(filter-out fs.c, $(FS_SRCS))
+
+pts.c pts.h: gen_command.py $(filter-out pts.c, $(PTS_SRCS))
+       ./gen_command.py pts $(filter-out pts.c, $(PTS_SRCS))
+
+vos.c vos.h: gen_command.py $(filter-out vos.c, $(VOS_SRCS))
+       ./gen_command.py vos $(filter-out vos.c, $(VOS_SRCS))
+
+GENFILES := \
+       bos.c bos.h \
+       fs.c fs.h \
+       pts.c pts.h \
+       vos.c vos.h
+
+$(KAFS_OBJS) $(GENFILES): Makefile
+
+DEPS           := $(wildcard .*.o.d)
+ifneq ($(DEPS),)
+include $(DEPS)
+else
+$(KAFS_OBJS): bos.h fs.h pts.h vos.h
+endif
+
+%.o: %.c
+       $(CC) $(CPPFLAGS) -MMD -MF .$@.d $(CFLAGS) -o $@ -c $<
+
+LIBDIR := ../lib
+
+kafs: $(KAFS_OBJS) $(LIBDIR)/libkafs_utils.a
+       $(CC) -o $@ $(KAFS_OBJS) -L$(LIBDIR) -lkafs_utils -lkafs_client -luuid
+
+clean:
+       $(RM) *~ *.o $(DEPS)
+       $(RM) bos.[ch] fs.[ch] pts.[ch] vos.[ch]
+       $(RM) kafs
diff --git a/kafs/arg_completion.c b/kafs/arg_completion.c
new file mode 100644 (file)
index 0000000..6d7d180
--- /dev/null
@@ -0,0 +1,478 @@
+/* Bash completion helper
+ *
+ * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <alloca.h>
+#include <kafs/cellserv.h>
+#include "arg_parse.h"
+
+bool kafs_debug_completion;
+
+static FILE *completion_log;
+
+static bool str_is_flag(const char *p)
+{
+       if (!*p)
+               return false;
+       if (p[0] != '-')
+               return false;
+       if (!*p)
+               return false;
+       for (; *p; p++)
+               if (!isdigit(*p))
+                       return true;
+       return false;
+}
+
+void comp_log(const char *fmt, ...)
+{
+       va_list va;
+
+       if (kafs_debug_completion) {
+               if (!completion_log) {
+                       completion_log = fopen("/tmp/kafs_completer.log", "a");
+                       if (!completion_log) {
+                               perror("/tmp/kafs_completer.log");
+                               exit(2);
+                       }
+                       setlinebuf(completion_log);
+               }
+
+               va_start(va, fmt);
+               vfprintf(completion_log, fmt, va);
+               va_end(va);
+       }
+}
+
+/*
+ * Complete the name of the command suite.
+ */
+void bash_complete_command_suite(char *s)
+{
+       const struct kafs_suite *suite;
+       bool need_space = false;
+       int i, l = strlen(s);
+
+       comp_log("Word '%s'\n", s);
+
+       for (i = 0; kafs_suites[i]; i++) {
+               suite = kafs_suites[i];
+               if (strncmp(s, suite->name, l) == 0) {
+                       if (need_space)
+                               putchar(' ');
+                       fputs(suite->name, stdout);
+                       need_space = true;
+               }
+       }
+
+       exit(0);
+}
+
+/*
+ * Generate a list of matching cell names.
+ */
+static char **complete_cell_name(const char *prefix)
+{
+       char **cells;
+       int i, c, l = strlen(prefix);
+
+       cells = calloc(kafs_cellserv_db->nr_cells + 1, sizeof(char *));
+       if (!cells) {
+               perror(NULL);
+               exit(1);
+       }
+
+       c = 0;
+       for (i = 0; i < kafs_cellserv_db->nr_cells; i++) {
+               struct kafs_cell *cell = kafs_cellserv_db->cells[i];
+
+               if (strncmp(prefix, cell->name, l) == 0)
+                       cells[c++] = cell->name;
+       }
+
+       return cells;
+}
+
+/*
+ * Expand a command name.
+ */
+void bash_complete_command_name(const struct kafs_suite *suite, char *word)
+{
+       bool need_space = false;
+       int i, l = strlen(word);
+
+       comp_log("Word '%s'\n", word);
+
+       for (i = 0; suite->commands[i]; i++) {
+               const struct kafs_command *cmd = suite->commands[i];
+
+               if (strncmp(word, cmd->name, l) == 0) {
+                       if (need_space)
+                               putchar(' ');
+                       fputs(cmd->name, stdout);
+                       need_space = true;
+               }
+       }
+
+       exit(0);
+}
+
+struct arg_notes {
+       const struct kafs_argument      *canon_flag;
+       const struct kafs_argument      *word_type;
+       bool                            additional_arg;
+};
+
+/*
+ * Perform command line completion.
+ */
+void bash_complete(int argc, char **argv, int target,
+                  const struct kafs_suite *suite)
+{
+       const struct kafs_argument *last_sw, *arg, *word_type, *canon;
+       const struct kafs_argument *next_required; //, *next_opts;
+       const struct kafs_command *cmd;
+       struct arg_notes *notes;
+       const char *word, *special, *space = "";
+       char **p, *eliminated_args;
+       bool skip_flag, additional_arg;
+       int pos, i, j, wl;
+
+       //completion_log = stderr;
+       comp_log("Compl %d\n", target);
+       comp_log("Suite: %s\n", suite->name);
+
+       if (target == 1)
+               bash_complete_command_name(suite, argv[1]);
+       if (target >= argc)
+               exit(0);
+
+       for (p = argv; *p; p++) {
+               if (p - argv == target)
+                       comp_log(" >%s<", *p);
+               else
+                       comp_log(" %s", *p);
+       }
+       comp_log("\n");
+
+       cmd = look_up_command(suite, argv[1]);
+       if (!cmd) {
+               comp_log("Unknown command %s\n", argv[1]);
+               exit(1);
+       }
+
+       /* Discard the suite name and the command name */
+       argc -= 2;
+       argv += 2;
+       target -= 2;
+
+       /* Ignore any arguments after the one being expanded */
+       argv[argc] = NULL;
+
+       comp_log("--- TARGET %s\n", argv[target]);
+
+        // Determine a list of canonicalised flag names and initialise an array
+        // to keep track of the non-flag argument types.
+        //
+        // An argument beginning with a dash that directly follows on from a
+        // flag that takes an argument is assumed to be an argument, not a
+        // flag.
+       notes = alloca(sizeof(struct arg_notes) * argc);
+       memset(notes, 0, sizeof(struct arg_notes) * argc);
+
+       // Keep track of any arguments that have been eliminated
+        eliminated_args = alloca(cmd->nr_args + 1);
+       memset(eliminated_args, '-', cmd->nr_args);
+       eliminated_args[cmd->nr_args] = 0;
+        comp_log("ELIMINATED_ARGS 0: %s\n", eliminated_args);
+
+        skip_flag = false;
+        for (i = 0; i < argc; i++) {
+                word = argv[i];
+               notes[i].canon_flag = NULL;
+               notes[i].word_type = NULL;
+                notes[i].additional_arg = false;
+
+                if (str_is_flag(word) && !skip_flag) {
+                       const struct kafs_argument *match = NULL;
+                       const char *sw = word + 1;
+                       int swl = strlen(word) - 1;
+
+                       for (j = 0; j < cmd->nr_args; j++) {
+                               arg = &cmd->args[j];
+
+                               if (strcmp(sw, arg->name) == 0) {
+                                       match = arg;
+                                       break;
+                               }
+                               if (strncmp(sw, arg->name, swl) == 0) {
+                                       if (match) {
+                                               comp_log("AMBIGUOUS %s\n", word);
+                                               match = NULL;
+                                               break;
+                                       }
+                                       match = arg;
+                               }
+                       }
+
+                       if (match) {
+                               comp_log("CANON %s\n", sw);
+                               notes[i].canon_flag = match;
+                               eliminated_args[match - cmd->args] = 'y';
+                               if (!match->flag)
+                                       skip_flag = true;
+                       }
+               } else {
+                       skip_flag = false;
+               }
+       }
+
+        comp_log("ELIMINATED_ARGS 1: %s\n", eliminated_args);
+
+        // Try to eliminate required arguments by position where the flag is
+        // implicit.
+        pos = 0;
+       for (j = 0; j < cmd->nr_args; j++) {
+               const struct kafs_argument *arg = &cmd->args[j];
+
+               if (pos >= argc)
+                       break;
+               word = argv[pos];
+               if (str_is_flag(word))
+                       break;
+               if (!arg->req)
+                       break;
+
+               // We have a required argument.  If the first word of this
+               // argument is currently being expanded and is blank, then we
+               // stick the flag in first.
+               if (!word[0] && pos == target) {
+                       printf("-%s\n", arg->name);
+                       exit(0);
+               }
+
+               notes[pos].word_type = arg;
+               eliminated_args[j] = true;
+               pos++;
+               if (arg->single)
+                       continue;
+               if (j + 1 < cmd->nr_args && cmd->args[j + 1].req)
+                       continue;
+               while (pos < argc) {
+                       word = argv[pos];
+                       if (str_is_flag(word))
+                               break;
+                       notes[pos].word_type = arg;
+                       notes[pos].additional_arg = true;
+                       pos++;
+               }
+               break;
+       }
+
+        comp_log("ELIMINATED_ARGS 2: %s\n", eliminated_args);
+
+        // Work out the types of any optional arguments
+        last_sw = NULL;
+        additional_arg = false;
+       for (i = 0; i < argc; i++) {
+               arg = notes[i].canon_flag;
+               if (arg) {
+                       if (!arg->flag)
+                               last_sw = arg;
+                       else
+                               last_sw = NULL;
+                       additional_arg = false;
+               } else if (last_sw) {
+                       notes[i].word_type = last_sw;
+                       notes[i].additional_arg = additional_arg;
+                       if (last_sw->single)
+                               last_sw = NULL;
+                       else
+                               additional_arg = true;
+               }
+       }
+
+        comp_log("TYPES [");
+       for (i = 0; i < argc; i++) {
+               if (notes[i].canon_flag)
+                       comp_log(" -%s", notes[i].canon_flag->name);
+               //else if (notes[i].word_type == "-")
+               //      comp_log(" -?");
+               else if (notes[i].word_type)
+                       comp_log(" <%s>", notes[i].word_type->val);
+               else
+                       comp_log(" ??");
+       }
+        comp_log(" ]\n");
+
+        // Try to determine how to deal with the word being expanded
+        word = argv[target];
+       wl = strlen(word);
+        word_type      = notes[target].word_type;
+        canon          = notes[target].canon_flag;
+        additional_arg = notes[target].additional_arg;
+        comp_log("WORD \"%s\"\n", word);
+        comp_log("WORD_TYPE %s\n", word_type ? word_type->name : "*NULL*");
+        comp_log("CANON %s\n", canon ? canon->name : "*NULL*");
+
+        // Expand unambiguous flags fully
+        if (canon) {
+               comp_log("*** GEN CANON %s\n", canon->name);
+               printf("-%s\n", canon->name);
+               exit(0);
+       }
+
+        next_required = NULL;
+       for (j = 0; j < cmd->nr_args; j++) {
+               const struct kafs_argument *arg = &cmd->args[j];
+               if (arg->req) {
+                       if (eliminated_args[j] == '-') {
+                               next_required = arg;
+                               break;
+                       }
+               }
+       }
+
+        // Insert a required flag now if there is one and if there is no
+        // mandatory argument to the previous flag
+        if (next_required) {
+               if (!word_type ||
+                   (strcmp(word, "-") == 0 && additional_arg)) {
+                       comp_log("*** GEN REQFLAG %s\n", next_required->name);
+                       printf("-%s\n", next_required->name);
+                       exit(0);
+               }
+       }
+
+        // Work out what type this argument will be if it's not a flag and
+        // instruct the parent to run compgen
+        special = "";
+        if (word_type) {
+               if (strcmp(word_type->name, "cell") == 0) {
+                       special = "@CELL@";
+#if 0
+               } else if (word_type[1] == get_bosserver ||
+                          word_type[1] == get_fileserver ||
+                          word_type[1] == get_volserver ||
+                          word_type[1] == get_vlservers ||
+                          word_type[1] == get_machine_name ||
+                          word_type[1] == get_machine_names) {
+                       special = "@HOSTNAME@";
+               } else if (word_type[1] == get_volume_name ||
+                          word_type[1] == get_volume_names) {
+                       special = "@VOLNAME@";
+               } else if (word_type[1] == get_partition_id) {
+                       special = "@PARTID@";
+               } else if (word_type[1] == get_path_name ||
+                          word_type[1] == get_path_names ||
+                          word_type[1] == get_file_name ||
+                          word_type[1] == get_file_names) {
+                       special = "@FILE@";
+#endif
+               } else {
+                       special = "@OTHER@";
+               }
+       }
+       comp_log("SPECIAL %s\n", special);
+
+        // Expand an argument that can't be a flag
+        if (word_type && !additional_arg) {
+               comp_log("*** GEN NONFLAG %s\n", word_type->name);
+               goto expand_special;
+       }
+
+        // Work out a list of what options could go next;
+       if (!word[0] || (word[0] == '-' && !word[1])) {
+               for (j = 0; j < cmd->nr_args; j++) {
+                       if (eliminated_args[j] == '-') {
+                               arg = &cmd->args[j];
+                               printf("%s-%s", space, arg->name);
+                               space = " ";
+                       }
+               }
+       } else {
+               for (j = 0; j < cmd->nr_args; j++) {
+                       if (eliminated_args[j] == '-') {
+                               arg = &cmd->args[j];
+                               if (strncmp(word + 1, arg->name, wl - 1) == 0) {
+                                       printf("%s-%s", space, arg->name);
+                                       space = " ";
+                               }
+                       }
+               }
+       }
+
+
+#if 0
+        next_opts = NULL;
+        if (!word[0] || str_is_flag(word)) {
+               next_opts = next_required;
+               if (!next_opts) {
+                       next_opts = "";
+                       for (j = 0; j < cmd->nr_args; j++) {
+                               if (eliminated_args[j] == 'y')
+                                       continue;
+                               arg = &cmd->args[j];
+                               if (strncmp(word + 1, arg->name, wl - 1) == 0) {
+                                       if (next_opts == "")
+                                               next_opts = "-";
+                                       else
+                                               next_opts += " -";
+                                       next_opts += i;
+                               }
+                       }
+               }
+       }
+        comp_log("NEXT OPTS %s\n", next_opts);
+
+        if (next_opts != "") {
+               // If the next word has to be a flag of some sort, display a
+               // list thereof
+               if (!word_type || (additional_arg && word[0] == '-')) {
+                       comp_log("*** GEN OPTIONS %s\n", next_opts);
+                       printf("%s\n", next_opts);
+                       exit(0);
+               }
+       }
+#endif
+
+        // The next word can be a flag or an additional argument
+        if (additional_arg) {
+               comp_log("*** GEN ADDARG %s\n", special);
+               goto expand_special;
+       }
+
+        // Nothing left;
+        comp_log("*** GEN NOTHING\n");
+        printf("\n");
+       exit(0);
+
+expand_special:
+       if (strcmp(special, "@CELL@") == 0) {
+               char **cells = complete_cell_name(word), **c;
+
+               for (c = cells; *c; c++) {
+                       printf("%s%s", space, *c);
+                       space = " ";
+               }
+               printf("\n");
+               free(cells);
+       } else {
+               printf("%s\n", special);
+       }
+       exit(0);
+}
diff --git a/kafs/arg_parse.c b/kafs/arg_parse.c
new file mode 100644 (file)
index 0000000..ead026b
--- /dev/null
@@ -0,0 +1,949 @@
+/* Argument parser.
+ *
+ * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#define _GNU_SOURCE
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <kafs/cellserv.h>
+#include "rxrpc.h"
+#include "afs_xg.h"
+#include "arg_parse.h"
+
+static bool str_is_flag(const char *p)
+{
+       if (!*p)
+               return false;
+       if (p[0] != '-')
+               return false;
+       if (!*p)
+               return false;
+       for (; *p; p++)
+               if (!isdigit(*p))
+                       return true;
+       return false;
+}
+
+static bool append_param(struct extracted_arg *result, char *a)
+{
+       unsigned int n = result->nr_values;
+       char **p = result->values;
+
+       p = realloc(p, (n + 2) * sizeof(const char *));
+       if (!p)
+               return false;
+       p[n] = a;
+       p[n + 1] = NULL;
+       result->values = p;
+       result->nr_values = n + 1;
+       return true;
+}
+
+void free_extracted_args(struct extracted_arg *arg)
+{
+       struct extracted_arg *p = arg;
+
+       for (;;) {
+               if (p->values) {
+                       //for (i = 0; i < p->nr_values; i++)
+                       //      free(p->values[i]);
+                       free(p->values);
+               }
+
+               if (p->last)
+                       break;
+               p++;
+       }
+}
+
+/*
+ * Parse an argument list according to a defined set of switches.
+ *
+ * The following is an excerpt from the AFS Reference Manual, Introduction to
+ * AFS commands:
+ *
+ * CONDITIONS FOR OMITTING SWITCHES
+ *
+ *    It is always acceptable to type the switch part of an argument, but in
+ *    many cases it is not necessary. Specifically, switches can be omitted if
+ *    the following conditions are met.
+ *
+ *    (*) All of the command's required arguments appear in the order prescribed
+ *        by the syntax statement.
+ *
+ *    (*) No switch is provided for any argument.
+ *
+ *    (*) There is only one value for each argument (but note the important
+ *        exception discussed in the following paragraph).
+ *
+ *    Omitting switches is possible only because there is a prescribed order for
+ *    each command's arguments. When the issuer does not include switches, the
+ *    command interpreter relies instead on the order of arguments; it assumes
+ *    that the first element after the operation code is the command's first
+ *    argument, the next element is the command's second argument, and so
+ *    on. The important exception is when a command's final required argument
+ *    accepts multiple values. In this case, the command interpreter assumes
+ *    that the issuer has correctly provided one value for each argument up
+ *    through the final one, so any additional values at the end belong to the
+ *    final argument.
+ *
+ *    The following list describes the rules for omitting switches from the
+ *    opposite perspective: an argument's switch must be provided when any of
+ *    the following conditions apply.
+ *
+ *    (*) The command's arguments do not appear in the prescribed order.
+ *
+ *    (*) An optional argument is omitted but a subsequent optional argument is
+ *        provided.
+ *
+ *    (*) A switch is provided for a preceding argument.
+ *
+ *    (*) More than one value is supplied for a preceding argument (which must
+ *        take multiple values, of course); without a switch on the current
+ *        argument, the command interpreter assumes that the current argument is
+ *        another value for the preceding argument.
+ *
+ */
+struct extracted_arg *extract_arguments(struct kafs_context *ctx, char **args,
+                                       const struct kafs_command *cmd)
+{
+       const struct kafs_nocombine *combo;
+       const struct kafs_argument *arg;
+       struct extracted_arg *results, *result;
+       bool need_switch = false;
+       unsigned int av = 0;    /* Available arguments index; */
+       int i, j;
+
+       results = calloc(cmd->nr_args, sizeof(struct extracted_arg));
+       if (!results) {
+               kafs_nomem(ctx);
+               return NULL;
+       }
+
+       for (i = 0; i < cmd->nr_args; i++)
+               results[i].arg = &cmd->args[i];
+       results[cmd->nr_args - 1].last = true;
+
+       if (!args[0]) {
+               if (!cmd->nr_args)
+                       return results;
+
+               if (cmd->args[0].req) {
+                       kafs_inval(ctx, "Missing required parameters");
+                       goto error;
+               }
+
+               return results;
+       }
+
+       /* Process all the optional arguments or switch-based required arguments */
+       while (*args) {
+               char *a = *args;
+
+               if (!str_is_flag(a)) {
+                       /* Deal with positional arguments */
+                       if (need_switch) {
+                               kafs_inval(ctx, "Need switch before argument %d", i);
+                               goto error;
+                       }
+                       if (av >= cmd->nr_args) {
+                               kafs_inval(ctx, "Unexpected positional argument");
+                               goto error;
+                       }
+
+                       arg = &cmd->args[av];
+                       result = &results[av];
+                       if (arg->flag) {
+                               kafs_inval(ctx, "Unexpected positional argument");
+                               goto error;
+                       }
+                       av = av + 1;
+
+                       if (!append_param(result, a))
+                               goto error;
+                       args++;
+
+                       if (arg->multi) {
+                               /* Multiple-value arguments that are not
+                                * preceded by their switch are forced to be
+                                * single-value if there's yet another required
+                                * argument following.
+                                */
+                               if (av < cmd->nr_args && cmd->args[av].req) {
+                                       ;
+                               } else {
+                                       /* All remaining arguments up to the
+                                        * next switch belong to this.
+                                        */
+                                       for (; *args; args++) {
+                                               a = *args;
+                                               if (str_is_flag(a))
+                                                       break;
+                                               if (!append_param(result, a))
+                                                       goto error;
+                                       }
+                                       need_switch = true;
+                               }
+                       }
+                       result->seen = true;
+
+               } else {
+                       /* Deal with tagged arguments */
+                       char *sw = a + 1;
+                       size_t l = strlen(sw);
+
+                       args++;
+
+                       if (!sw[0]) {
+                               kafs_inval(ctx, "Missing switch name");
+                               goto error;
+                       }
+
+                       if (strcmp(sw, "help") == 0)
+                               goto help;
+
+                       /* Look up the switch in the table of possible
+                        * arguments and flags.
+                        */
+                       for (j = 0; j < cmd->nr_args; j++) {
+                               arg = &cmd->args[j];
+                               result = &results[j];
+                               if (strcmp(sw, arg->name) == 0)
+                                       goto found;
+                       }
+
+                       /* The switch name may be abbreviated to the shortest
+                        * possible non-conflicting stem.
+                        */
+                       for (j = 0; j < cmd->nr_args; j++) {
+                               arg = &cmd->args[j];
+                               result = &results[j];
+                               if (l > strlen(arg->name))
+                                       continue;
+                               if (memcmp(sw, arg->name, l) != 0)
+                                       continue;
+                               if (l >= arg->mink)
+                                       goto found;
+
+                               kafs_inval(ctx, "Ambiguous switch name abbreviation '-%s'", sw);
+                               goto error;
+                       }
+
+                       kafs_inval(ctx, "Unsupported switch '-%s'", sw);
+                       goto error;
+
+               found:
+                       /* Reject repeat flags */
+                       if (result->seen) {
+                               kafs_inval(ctx, "Duplicate switch '-%s' not permitted", sw);
+                               goto error;
+                       }
+                       result->seen = true;
+
+                       /* Arrange the parameters associated with the switch
+                        * into a list.
+                        */
+                       for (; *args; args++) {
+                               a = *args;
+                               if (str_is_flag(a))
+                                       break;
+                               append_param(result, a);
+                       }
+
+                       /* Check that we have the number of arguments we're expecting */
+                       if (arg->flag && result->nr_values > 0) {
+                               kafs_inval(ctx, "Switch '-%s' expects no arguments", sw);
+                               goto error;
+                       }
+                       if (arg->single && result->nr_values != 1) {
+                               kafs_inval(ctx, "Switch '-%s' expects one argument", sw);
+                               goto error;
+                       }
+                       if (arg->multi && result->nr_values < 1) {
+                               kafs_inval(ctx, "Switch '-%s' expects one or more arguments", sw);
+                               goto error;
+                       }
+               }
+
+               /* Check that none of the arguments are too big. */
+               if (arg->size_limit && result->nr_values > 0) {
+                       for (i = 0; i < result->nr_values; i++) {
+                               const char *v = result->values[i];
+                               if (strlen(v) > arg->size_limit) {
+                                       kafs_inval(ctx, "Switch '-%s' has an overlong argument",
+                                                  result->arg->name);
+                                       goto error;
+                               }
+                       }
+               }
+
+               /* Call the syntax checker */
+               if (arg->syntax) {
+                       if (!arg->syntax(result))
+                               goto error;
+               }
+       }
+
+       result = NULL;
+
+       /* Check for missing required arguments */
+       for (i = 0; i < cmd->nr_args; i++) {
+               arg = &cmd->args[i];
+               if (!arg->req)
+                       break;
+
+               if (!results[i].seen) {
+                       kafs_inval(ctx, "Missing '-%s' argument", arg->name);
+                       goto error;
+               }
+       }
+
+       /* Check for invalid argument combinations */
+       if (cmd->no_combine) {
+               for (combo = cmd->no_combine; combo->a; combo++) {
+                       if (results[combo->a].seen && results[combo->b].seen) {
+                               kafs_inval(ctx, "Can't combine -%s with -%s",
+                                          results[combo->a].arg->name,
+                                          results[combo->b].arg->name);
+                               goto error;
+                       }
+               }
+       }
+
+       return results;
+
+error:
+       free_extracted_args(results);
+       return NULL;
+
+help:
+       kafs_command_usage(cmd);
+       free_extracted_args(results);
+       return NULL;
+}
+
+
+void free_kafs_context(struct kafs_context *ctx)
+{
+       /* Do nothing */
+
+#if 0
+       if (ctx->cell_db)
+               kafs_free_cell(cell->db);
+       free(ctx->cell_name);
+       free(ctx);
+#endif
+}
+
+void free_kafs_partition(struct kafs_partition *part)
+{
+       free(part);
+}
+
+
+bool extract_kafs_context(struct kafs_context *ctx, struct extracted_arg *arg)
+{
+       const char *name = arg->arg->name;
+
+       if (strcmp(name, "noauth") == 0) {
+               if (arg->seen)
+                       ctx->sec_level = rxrpc_security_no_auth;
+               return true;
+       }
+
+       if (strcmp(name, "localauth") == 0) {
+               if (arg->seen)
+                       ctx->sec_level = rxrpc_security_local_auth;
+               return true;
+       }
+
+       if (strcmp(name, "encrypt") == 0) {
+               if (arg->seen)
+                       ctx->sec_level = rxrpc_security_encrypt;
+               return true;
+       }
+
+       printf("Unimplemented: extract_kafs_auth %s\n", arg->arg->name);
+       abort();
+}
+
+bool kafs_get_local_cell(struct kafs_context *ctx)
+{
+       size_t size;
+       char buf[256], *cell_name;
+       FILE *f;
+
+       f = fopen("/proc/net/afs/rootcell", "r");
+       if (!f)
+               goto error;
+
+       size = fread(buf, sizeof(char), sizeof(buf) - 1, f);
+       if (ferror(f))
+               goto error_f;
+       fclose(f);
+
+       if (size > 0 && buf[size - 1] == '\n')
+               size--;
+
+       if (size == sizeof(buf) - 1)
+               return kafs_error(ctx, "Local cell name unexpectedly long");
+       if (!size)
+               return kafs_error(ctx, "Local cell name not set");
+
+       cell_name = malloc(size + 1);
+       if (!cell_name)
+               return kafs_nomem(ctx);
+       cell_name[size] = 0;
+       ctx->cell_name = memcpy(cell_name, buf, size);
+       return true;
+
+error_f:
+       fclose(f);
+error:
+       return kafs_syserror(ctx, "/proc/net/afs/rootcell");
+}
+
+/*
+ * Determine the cell we're working in.
+ */
+bool extract_kafs_cell(struct kafs_context *ctx, struct extracted_arg *arg)
+{
+       if (arg->nr_values > 1)
+               return kafs_inval(ctx, "-cell with %d values\n", arg->nr_values);
+
+       if (!arg->seen) {
+               if (!kafs_get_local_cell(ctx))
+                       return false;
+       } else {
+               ctx->cell_name = strdup(arg->values[0]);
+               if (!ctx->cell_name)
+                       return kafs_nomem(ctx);
+       }
+
+       ctx->cell_db = kafs_lookup_cell(ctx->cell_name, &kafs_lookup_context);
+       if (!ctx->cell_db)
+               return kafs_syserror(ctx, "%s: Unknown cell", ctx->cell_name);
+
+       return true;
+}
+
+/*
+ * Extract the volume server name or address.
+ */
+bool extract_kafs_vlserver(struct kafs_context *ctx, struct extracted_arg *arg,
+                          struct kafs_vlserver **_vls)
+{
+       struct kafs_vlserver *vls;
+       struct kafs_server *s;
+
+       if (!arg->seen) {
+               *_vls = NULL;
+               return true;
+       }
+
+       vls = calloc(1, sizeof(struct kafs_vlserver));
+       if (!vls) {
+               perror(NULL);
+               return false;
+       }
+
+       vls->name = strdup(arg->values[0]);
+       if (!vls->name)
+               goto nomem;
+
+       vls->slist = calloc(1, sizeof(struct kafs_server_list));
+       if (!vls->slist)
+               goto nomem;
+       vls->slist->source = kafs_record_from_config;
+       vls->slist->status = kafs_lookup_good;
+       vls->slist->nr_servers = 1;
+       vls->slist->max_servers = 1;
+
+       vls->slist->servers = calloc(1, sizeof(struct kafs_server_list));
+       if (!vls->slist->servers)
+               goto nomem;
+
+       s = &vls->slist->servers[0];
+       s->name = vls->name;
+       s->borrowed_name = true;
+
+       if (kafs_dns_lookup_addresses(vls->slist, &kafs_lookup_context) < 0)
+               goto error;
+
+       *_vls = vls;
+       return true;
+
+nomem:
+       perror(NULL);
+error:
+       free_kafs_vlserver(vls);
+       return false;
+}
+
+void free_kafs_vlserver(struct kafs_vlserver *vls)
+{
+       if (vls) {
+               kafs_free_server_list(vls->slist);
+               free(vls->name);
+               free(vls);
+       }
+}
+
+/*
+ * Extract the volume server name or address.
+ */
+bool extract_kafs_volserver_spec(struct kafs_context *ctx, struct extracted_arg *arg,
+                                struct kafs_volserver_spec **_vs)
+{
+       struct kafs_volserver_spec *vs;
+
+       if (!arg->seen) {
+               *_vs = NULL;
+               return true;
+       }
+
+       vs = calloc(1, sizeof(struct kafs_volserver_spec));
+       if (!vs) {
+               perror(NULL);
+               return false;
+       }
+
+       vs->name = strdup(arg->values[0]);
+       if (!vs->name) {
+               perror(NULL);
+               goto error;
+       }
+
+       *_vs = vs;
+       return true;
+
+error:
+       free_kafs_volserver_spec(vs);
+       return false;
+}
+
+void free_kafs_volserver_spec(struct kafs_volserver_spec *vs)
+{
+       if (vs) {
+               free(vs->addrs);
+               free(vs->name);
+               free(vs);
+       }
+}
+
+/*
+ * Extract the volume server specifier name or address.
+ */
+bool extract_kafs_fileserver_spec(struct kafs_context *ctx, struct extracted_arg *arg,
+                                 struct kafs_fileserver_spec **_fs)
+{
+       struct kafs_fileserver_spec *fs;
+
+       if (!arg->seen) {
+               *_fs = NULL;
+               return true;
+       }
+
+       fs = calloc(1, sizeof(struct kafs_fileserver_spec));
+       if (!fs) {
+               perror(NULL);
+               return false;
+       }
+
+       fs->name = strdup(arg->values[0]);
+       if (!fs->name) {
+               perror(NULL);
+               goto error;
+       }
+
+       *_fs = fs;
+       return true;
+
+error:
+       free_kafs_fileserver_spec(fs);
+       return false;
+}
+
+void free_kafs_fileserver_spec(struct kafs_fileserver_spec *fs)
+{
+       if (fs) {
+               free(fs->addrs);
+               free(fs->name);
+               free(fs);
+       }
+}
+
+/*
+ * Extract the name of a partition.
+ */
+bool extract_kafs_partition(struct kafs_context *ctx, struct extracted_arg *arg,
+                           struct kafs_partition **_part)
+{
+       struct kafs_partition *part;
+       unsigned long n;
+       char *name, *end, *a;
+
+       if (!arg->seen) {
+               *_part = NULL;
+               return true;
+       }
+
+       name = arg->values[0];
+
+       if (!*name) {
+               fprintf(stderr, "Empty partition name string\n");
+               return false;
+       }
+
+       n = strtoul(name, &end, 0);
+       if (!*end) {
+               ;
+       } else {
+               if (strncmp(name, "/vicep", 6) == 0)
+                       a = name + 6;
+               else if (strncmp(name, "vicep", 5) == 0)
+                       a = name + 5;
+               else
+                       a = name;
+
+               if (!a[0]) {
+                       goto unparseable;
+               } else if (!a[1] && isalpha(a[0])) {
+                       n = tolower(a[0]) - 'a';
+               } else if (!a[2] && isalpha(a[0]) && isalpha(a[1])) {
+                       n = (tolower(a[0]) - 'a') * 26;
+                       n += tolower(a[1]) - 'a';
+                       n += 26;
+               } else {
+                       goto unparseable;
+               }
+       }
+
+       if (n < 0 || n > 255) {
+               fprintf(stderr, "Partition ID '%s' out of range\n", name);
+               return false;
+       }
+
+       part = calloc(1, sizeof(struct kafs_partition));
+       if (!part) {
+               perror(NULL);
+               return false;
+       }
+
+       part->id = n;
+       *_part = part;
+       return true;
+
+unparseable:
+       fprintf(stderr, "Unparseable partition ID '%s'\n", name);
+       return false;
+}
+
+/*
+ * Extract a volume name.
+ */
+bool extract_kafs_volume_spec(struct kafs_context *ctx, struct extracted_arg *arg,
+                             struct kafs_volume_spec **_vs)
+{
+       struct kafs_volume_spec *vs;
+       char *end;
+
+       if (!arg->seen) {
+               *_vs = NULL;
+               return true;
+       }
+
+       if (strlen(arg->values[0]) >= VLDB_MAXNAMELEN) {
+               fprintf(stderr, "Volume name too long\n");
+               return false;
+       }
+
+       vs = calloc(1, sizeof(struct kafs_volume_spec));
+       if (!vs) {
+               perror(NULL);
+               return false;
+       }
+
+       if (isdigit(arg->values[0][0])) {
+               /* If it appears to be a number, attempt to parse it and turn
+                * it into decimal.
+                */
+               vs->number = strtoull(arg->values[0], &end, 0);
+               if (!*end) {
+                       fprintf(stderr, "Volume ID is not a number\n");
+                       goto error;
+               }
+
+               if (asprintf(&vs->name, "%llu", vs->number) == -1){
+                       kafs_nomem(ctx);
+                       goto error;
+               }
+               vs->is_numeric = true;
+       } else {
+               vs->name = strdup(arg->values[0]);
+               if (!vs->name) {
+                       perror(NULL);
+                       goto error;
+               }
+       }
+
+       *_vs = vs;
+       return true;
+
+error:
+       free_kafs_volume_spec(vs);
+       return false;
+}
+
+void free_kafs_volume_spec(struct kafs_volume_spec *vs)
+{
+       if (vs) {
+               free(vs->name);
+               free(vs);
+       }
+}
+
+/*
+ * Extract a user name.
+ */
+bool extract_kafs_user(struct kafs_context *ctx, struct extracted_arg *arg,
+                      struct kafs_user **_user)
+{
+       struct kafs_user *user;
+
+       if (!arg->seen) {
+               *_user = NULL;
+               return true;
+       }
+
+       user = calloc(1, sizeof(struct kafs_user));
+       if (!user) {
+               perror(NULL);
+               return false;
+       }
+
+       user->name = strdup(arg->values[0]);
+       if (!user->name) {
+               perror(NULL);
+               goto error;
+       }
+
+       *_user = user;
+       return true;
+
+error:
+       free_kafs_user(user);
+       return false;
+}
+
+void free_kafs_user(struct kafs_user *user)
+{
+       if (user) {
+               free(user->name);
+               free(user);
+       }
+}
+
+/*
+ * Extract a list of user names.
+ */
+bool extract_list_kafs_user(struct kafs_context *ctx, struct extracted_arg *arg,
+                           struct kafs_user ***_users)
+{
+       struct kafs_user **users;
+       int i;
+
+       if (!arg->seen) {
+               *_users = NULL;
+               return true;
+       }
+
+       users = calloc(arg->nr_values + 1, sizeof(struct kafs_user *));
+       if (!users) {
+               perror(NULL);
+               return false;
+       }
+
+       for (i = 0; i < arg->nr_values; i++) {
+               users[i] = calloc(1, sizeof(struct kafs_user));
+               if (!users[i]) {
+                       perror(NULL);
+                       goto error;
+               }
+
+               users[i]->name = strdup(arg->values[0]);
+               if (!users[i]->name) {
+                       perror(NULL);
+                       goto error;
+               }
+       }
+
+       *_users = users;
+       return true;
+
+error:
+       free_list_kafs_user(users);
+       return false;
+}
+
+void free_list_kafs_user(struct kafs_user **users)
+{
+       struct kafs_user **p;
+
+       if (users) {
+               for (p = users; *p; p++)
+                       free_kafs_user(*p);
+               free(users);
+       }
+}
+
+/*
+ * Extract a group name.
+ */
+bool extract_kafs_group(struct kafs_context *ctx, struct extracted_arg *arg,
+                      struct kafs_group **_group)
+{
+       struct kafs_group *group;
+
+       if (!arg->seen) {
+               *_group = NULL;
+               return true;
+       }
+
+       group = calloc(1, sizeof(struct kafs_group));
+       if (!group) {
+               perror(NULL);
+               return false;
+       }
+
+       group->name = strdup(arg->values[0]);
+       if (!group->name) {
+               perror(NULL);
+               goto error;
+       }
+
+       *_group = group;
+       return true;
+
+error:
+       free_kafs_group(group);
+       return false;
+}
+
+void free_kafs_group(struct kafs_group *group)
+{
+       if (group) {
+               free(group->name);
+               free(group);
+       }
+}
+
+/*
+ * Extract a list of group names.
+ */
+bool extract_list_kafs_group(struct kafs_context *ctx, struct extracted_arg *arg,
+                           struct kafs_group ***_groups)
+{
+       struct kafs_group **groups;
+       int i;
+
+       if (!arg->seen) {
+               *_groups = NULL;
+               return true;
+       }
+
+       groups = calloc(arg->nr_values + 1, sizeof(struct kafs_group *));
+       if (!groups) {
+               perror(NULL);
+               return false;
+       }
+
+       for (i = 0; i < arg->nr_values; i++) {
+               groups[i] = calloc(1, sizeof(struct kafs_group));
+               if (!groups[i]) {
+                       perror(NULL);
+                       goto error;
+               }
+
+               groups[i]->name = strdup(arg->values[0]);
+               if (!groups[i]->name) {
+                       perror(NULL);
+                       goto error;
+               }
+       }
+
+       *_groups = groups;
+       return true;
+
+error:
+       free_list_kafs_group(groups);
+       return false;
+}
+
+void free_list_kafs_group(struct kafs_group **groups)
+{
+       struct kafs_group **p;
+
+       if (groups) {
+               for (p = groups; *p; p++)
+                       free_kafs_group(*p);
+               free(groups);
+       }
+}
+
+bool extract_string(struct kafs_context *ctx, struct extracted_arg *arg,
+                   char **_s)
+{
+       if (!arg->seen) {
+               *_s = NULL;
+               return true;
+       }
+
+       *_s = arg->values[0];
+       return true;
+}
+
+bool extract_list_string(struct kafs_context *ctx, struct extracted_arg *arg,
+                        char ***_sl)
+{
+       *_sl = arg->values;
+       return true;
+}
+
+bool extract_bool(struct kafs_context *ctx, struct extracted_arg *arg,
+                 bool *_b)
+{
+       if (arg->seen)
+               *_b = true;
+       return true;
+}
+
+bool extract_uuid_t(struct kafs_context *ctx, struct extracted_arg *arg,
+                   uuid_t **_u)
+{
+       uuid_t *u;
+
+       if (arg->seen) {
+               u = malloc(sizeof(*u));
+               if (!u)
+                       return kafs_nomem(ctx);
+               if (uuid_parse(arg->values[0], *u) < 0) {
+                       free(u);
+                       return kafs_error(ctx, "Failed to parse UUID");
+               }
+               *_u = u;
+       }
+       return true;
+}
diff --git a/kafs/arg_parse.h b/kafs/arg_parse.h
new file mode 100644 (file)
index 0000000..7df6f36
--- /dev/null
@@ -0,0 +1,116 @@
+/* Argument parser definitions.
+ *
+ * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#ifndef ARG_PARSER_H
+#define ARG_PARSER_H
+
+#include <stdbool.h>
+#include "kafs.h"
+
+struct extracted_arg {
+       const struct kafs_argument *arg;
+       char            **values;
+       unsigned int    nr_values;
+       bool            seen;
+       bool            last;
+};
+
+extern bool extract_kafs_context(struct kafs_context *, struct extracted_arg *);
+extern bool extract_kafs_cell(struct kafs_context *, struct extracted_arg *);
+extern bool extract_string(struct kafs_context *, struct extracted_arg *, char **);
+extern bool extract_bool(struct kafs_context *, struct extracted_arg *, bool *);
+extern bool extract_uuid_t(struct kafs_context *, struct extracted_arg *, uuid_t **);
+extern bool extract_kafs_volserver_spec(struct kafs_context *, struct extracted_arg *,
+                                       struct kafs_volserver_spec **);
+extern bool extract_kafs_vlserver(struct kafs_context *, struct extracted_arg *,
+                                 struct kafs_vlserver **);
+extern bool extract_kafs_fileserver_spec(struct kafs_context *, struct extracted_arg *,
+                                        struct kafs_fileserver_spec **);
+extern bool extract_kafs_partition(struct kafs_context *, struct extracted_arg *,
+                                  struct kafs_partition **);
+extern bool extract_kafs_volume_spec(struct kafs_context *, struct extracted_arg *,
+                                    struct kafs_volume_spec **);
+extern bool extract_kafs_user(struct kafs_context *, struct extracted_arg *,
+                             struct kafs_user **);
+extern bool extract_kafs_group(struct kafs_context *, struct extracted_arg *,
+                              struct kafs_group **);
+extern bool extract_list_kafs_user(struct kafs_context *, struct extracted_arg *,
+                                  struct kafs_user ***);
+extern bool extract_list_kafs_group(struct kafs_context *, struct extracted_arg *,
+                                   struct kafs_group ***);
+
+extern bool extract_list_string(struct kafs_context *, struct extracted_arg *, char ***);
+
+struct kafs_command {
+       const struct kafs_command *alias;       /* Command this is an alias of */
+       const char              *suite;         /* Name of the command suite of which part */
+       const char              *name;          /* Name of the command */
+       const char              *abbrev;        /* Abbreviation of the command name (or NULL) */
+       const char              *help;          /* Single-line help text */
+       const char              *desc;          /* Multi-line description */
+       const struct kafs_argument *args;       /* Argument list terminated with {}. */
+       const struct kafs_nocombine *no_combine; /* Arguments that cannot be combined */
+       int                     nr_args;        /* Number of arguments */
+       unsigned char           mink;           /* Minimum size of abbreviation of name */
+       bool (*handler)(struct kafs_context *ctx, char **argv);
+};
+
+struct kafs_suite {
+       const char                      *name;
+       const struct kafs_command       **commands;
+};
+
+struct kafs_argument {
+       const char      *name;          /* Argument name */
+       unsigned char   mink;           /* Minimum size of abbreviation of name */
+       const char      *val;           /* Value description */
+       unsigned int    size_limit;     /* Value size limit */
+       bool            req;            /* T if argument mandatory */
+       bool            flag;           /* T if takes no argument */
+       bool            single;         /* T if takes a single argument */
+       bool            multi;          /* T if takes multiple arguments */
+       bool            newn;           /* T if new name (ie. no tab expansion) */
+       bool            auth;           /* T if auth data */
+       bool (*syntax)(struct extracted_arg *result);
+};
+
+struct kafs_nocombine {
+       unsigned int    a;
+       unsigned int    b;
+};
+
+/*
+ * arg_parse.c
+ */
+extern bool kafs_debug_completion;
+
+extern void free_extracted_args(struct extracted_arg *arg);
+extern struct extracted_arg *extract_arguments(struct kafs_context *ctx, char **args,
+                                              const struct kafs_command *cmd);
+
+/*
+ * kafs.c
+ */
+extern const struct kafs_suite *kafs_suites[];
+
+extern bool kafs_command_usage(const struct kafs_command *cmd);
+extern bool kafs_help(const struct kafs_suite *suite, char *topic);
+extern const struct kafs_command *look_up_command(const struct kafs_suite *suite, char *command);
+
+/*
+ * completion.c
+ */
+extern void comp_log(const char *fmt, ...);
+extern void bash_complete_command_suite(char *s);
+extern void bash_complete(int argc, char **argv, int completion_argc,
+                         const struct kafs_suite *suite);
+
+#endif /* ARG_PARSER_H */
diff --git a/kafs/bash_complete b/kafs/bash_complete
new file mode 100644 (file)
index 0000000..1da7163
--- /dev/null
@@ -0,0 +1,24 @@
+# -*- sh -*-
+# bash completion script for kafs-utils
+#
+# To use these routines:
+#
+#    1. Copy this file to somewhere (e.g. ~/.kafs-completion.bash).
+#
+#    2. Add the following line to your .bashrc:
+#         . ~/.kafs-completion.bash
+
+_kafs () {
+    COMPREPLY=($(/data/afs/kafs-utils/kafs/kafs --complete $COMP_CWORD "${COMP_WORDS[@]}"))
+
+    case "${COMPREPLY[0]}" in
+       @HOSTNAME@)
+       COMPREPLY=($(compgen -A hostname -- "${COMP_WORDS[$COMP_CWORD]}"))
+       ;;
+       @VOLNAME@|@PARTID@|@FILE@|@OTHER@)
+       COMPREPLY=($(compgen -A file -- "${COMP_WORDS[$COMP_CWORD]}"))
+       ;;
+    esac
+}
+
+complete -o bashdefault -o default -F _kafs kafs bos pts vos
diff --git a/kafs/bos_help.c b/kafs/bos_help.c
new file mode 100644 (file)
index 0000000..a04ded1
--- /dev/null
@@ -0,0 +1,54 @@
+/* The bos help command
+ *
+ * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "bos.h"
+
+/***
+ * COMMAND: bos help - Get help on commands
+ * ARG: "[-topic <help string>+]"
+ * ARG: "[-admin]"
+ */
+bool COMMAND_bos_help(
+       struct kafs_context     *ctx,
+       char                    **a_topic,
+       bool                    a_admin)
+{
+       if (!a_topic)
+               return kafs_help(&gen_bos_suite, NULL);
+       for (; *a_topic; a_topic++)
+               if (!kafs_help(&gen_bos_suite, *a_topic))
+                       return false;
+       return true;
+}
+
+/***
+ * COMMAND: bos apropos - Search for a command by its help text
+ * ARG: "-topic <help string>"
+ */
+bool COMMAND_bos_apropos(
+       struct kafs_context     *ctx,
+       char                    *a_topic)
+{
+       const struct kafs_command *cmd;
+       int i;
+
+       for (i = 0; gen_bos_suite.commands[i]; i++) {
+               cmd = gen_bos_suite.commands[i];
+
+               if (strstr(cmd->name, a_topic) ||
+                   strstr(cmd->help, a_topic))
+                       printf("%s: %s\n", cmd->name, cmd->help);
+       }
+
+       return true;
+}
diff --git a/kafs/display.h b/kafs/display.h
new file mode 100644 (file)
index 0000000..d9525b1
--- /dev/null
@@ -0,0 +1,22 @@
+/* Display routine definitions
+ *
+ * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#ifndef DISPLAY_H
+#define DISPLAY_H
+
+#include "kafs.h"
+
+/*
+ * display_error.c
+ */
+extern bool kafs_display_error(struct kafs_context *);
+
+#endif /* DISPLAY_H */
diff --git a/kafs/display_error.c b/kafs/display_error.c
new file mode 100644 (file)
index 0000000..eba55e5
--- /dev/null
@@ -0,0 +1,85 @@
+/* Error handling and displaying
+ *
+ * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include "kafs.h"
+#include "afs_xg.h"
+
+bool kafs_inval(struct kafs_context *ctx, const char *fmt, ...)
+{
+       va_list va;
+
+       va_start(va, fmt);
+       vsnprintf(ctx->err_buf, sizeof(ctx->err_buf), fmt, va);
+       va_end(va);
+
+       ctx->result.source = rxrpc_error_from_parameters;
+       ctx->result.error = 0;
+       ctx->result.abort_code = 0;
+       return false;
+}
+
+bool kafs_error(struct kafs_context *ctx, const char *fmt, ...)
+{
+       va_list va;
+
+       va_start(va, fmt);
+       vsnprintf(ctx->err_buf, sizeof(ctx->err_buf), fmt, va);
+       va_end(va);
+
+       ctx->result.source = rxrpc_error_from_parameters;
+       ctx->result.error = 0;
+       ctx->result.abort_code = 0;
+       return false;
+}
+
+/*
+ * Default error display.
+ */
+bool kafs_display_error(struct kafs_context *ctx)
+{
+       char buf[128], *p;
+
+       switch (ctx->result.source) {
+       case rxrpc_error_none:
+               fprintf(stderr, "No error!\n");
+               break;
+       case rxrpc_error_remote_abort:
+               fprintf(stderr, "Remote abort: %d\n", ctx->result.abort_code);
+               break;
+       case rxrpc_error_peer_busy:
+               fprintf(stderr, "Peer says busy\n");
+               break;
+       case rxrpc_error_from_system:
+       case rxrpc_error_from_network:
+               buf[0] = 0;
+#if (_POSIX_C_SOURCE >= 200112L) && !_GNU_SOURCE
+               strerror_r(ctx->result.error, buf, sizeof(buf));
+               p = buf;
+#else
+               p = strerror_r(ctx->result.error, buf, sizeof(buf));
+#endif
+               fprintf(stderr, "%s error: %s\n",
+                       (ctx->result.source == rxrpc_error_from_system) ? "System" : "Network",
+                       p);
+               break;
+       case rxrpc_error_from_parameters:
+               fprintf(stderr, "%s\n", ctx->err_buf);
+               break;
+       default:
+               fprintf(stderr, "Undefined error source\n");
+               break;
+       }
+
+       return false;
+}
diff --git a/kafs/fs_help.c b/kafs/fs_help.c
new file mode 100644 (file)
index 0000000..e6b60a4
--- /dev/null
@@ -0,0 +1,54 @@
+/* The fs help command
+ *
+ * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "fs.h"
+
+/***
+ * COMMAND: fs help - Get help on commands
+ * ARG: "[-topic <help string>+]"
+ * ARG: "[-admin]"
+ */
+bool COMMAND_fs_help(
+       struct kafs_context     *ctx,
+       char                    **a_topic,
+       bool                    a_admin)
+{
+       if (!a_topic)
+               return kafs_help(&gen_fs_suite, NULL);
+       for (; *a_topic; a_topic++)
+               if (!kafs_help(&gen_fs_suite, *a_topic))
+                       return false;
+       return true;
+}
+
+/***
+ * COMMAND: fs apropos - Search for a command by its help text
+ * ARG: "-topic <help string>"
+ */
+bool COMMAND_fs_apropos(
+       struct kafs_context     *ctx,
+       char                    *a_topic)
+{
+       const struct kafs_command *cmd;
+       int i;
+
+       for (i = 0; gen_fs_suite.commands[i]; i++) {
+               cmd = gen_fs_suite.commands[i];
+
+               if (strstr(cmd->name, a_topic) ||
+                   strstr(cmd->help, a_topic))
+                       printf("%s: %s\n", cmd->name, cmd->help);
+       }
+
+       return true;
+}
diff --git a/kafs/gen_command.py b/kafs/gen_command.py
new file mode 100755 (executable)
index 0000000..81183c2
--- /dev/null
@@ -0,0 +1,706 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+#
+# Tool for processing vos, pts, etc. command definitions into parser stubs,
+# argument tables and tab expansion drivers.
+#
+
+__copyright__ = """
+Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
+Written by David Howells (dhowells@redhat.com)
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public Licence version 2 as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public Licence for more details.
+
+You should have received a copy of the GNU General Public Licence
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+"""
+
+import sys
+import re
+
+suite_name = None
+output_file = None
+header_file = None
+
+def output(*va):
+    for i in va:
+        output_file.write(str(i))
+
+def header(*va):
+    for i in va:
+        header_file.write(str(i))
+
+def verbose(*va):
+    print(*va, file=sys.stderr)
+
+def error(*va):
+    print(*va, file=sys.stderr)
+
+#
+# Command argument definition
+# Looks like:
+#       ARG: "-name"                    -- Required flag with no value
+#       ARG: "-name <value>"            -- Required flag with value
+#       ARG: "-name <value>+"           -- Required flag with multiple values
+#       ARG: "[-name <value>]"          -- Optional flag with value
+#       ARG: "-name <value>" - New      -- Required flag with new value (can't tab-expand)
+#       ARG: "-name <value>" - Auth     -- Required flag with auth contribution
+#
+class kafs_argument:
+    def __init__(self, source, line):
+        self.source = source
+        self.name = None
+        self.multi = False
+        self.optional = False
+        self.value = None
+        self.auth_info = False
+        self.name_new = False
+        self.index = None
+
+        arg_re = re.compile(r' (["][^"]*["])( [-] (.*))?')
+        m = arg_re.fullmatch(line)
+        if not m:
+            raise RuntimeError("ARG didn't match")
+        flag = m.group(1).strip('"')
+        quals = m.group(3)
+
+        if flag[0] == "[":
+            if flag[-1] != "]":
+                raise RuntimeError("Missing ']' in ARG")
+            flag = flag[1:-1]
+            if flag[-1] == "]":
+                raise RuntimeError("Extra ']' in ARG")
+            self.optional = True
+        if flag[-1] == "+":
+            flag = flag[0:-1]
+            if flag[-1] == "+":
+                raise RuntimeError("Multiple '+' in ARG")
+            self.multi = True
+        if flag[0] != "-":
+            raise RuntimeError("Missing '-' at beginning of flag name")
+        flag = flag[1:]
+
+        (flag, sep, value) = flag.partition(" ")
+        if not flag:
+            raise RuntimeError("Missing flag name")
+        self.name = flag
+        if value:
+            if value[0] != "<":
+                raise RuntimeError("Missing '<' in ARG value")
+            if value[-1] != ">":
+                raise RuntimeError("Missing '>' in ARG value")
+            value = value[1:-1]
+            if not value:
+                raise RuntimeError("Missing description in ARG value")
+            self.value = value
+        else:
+            if self.multi:
+                raise RuntimeError("Multiple values, but no value type")
+
+        if quals:
+            for q in quals.split():
+                if q == "Auth":
+                    self.auth_info = True
+                elif q == "New":
+                    self.name_new = True
+                else:
+                    raise RuntimeError("Unknown qualifier '" + q + "'")
+
+#
+# Function argument definition
+#
+class kafs_func_argument:
+    def __init__(self, arg):
+        self.syntax = None              # Extraction function name
+        self.c_type = None              # Argument C type, including pointer markers
+        self.c_name = None              # Argument name
+        self.c_var_type = None          # Temp variable type
+
+        arg_re = re.compile(r'([_a-zA-Z0-9 ]+) ([*]*)([_a-zA-Z0-9]*)')
+        m = arg_re.fullmatch(arg)
+        if not m:
+            raise RuntimeError("Func arg could not be parsed")
+        if m.group(2):
+            self.c_type = m.group(1) + " " + m.group(2)
+        else:
+            self.c_type = m.group(1)
+
+        name = m.group(3)
+        if name != "ctx":
+            if name.startswith("a_"):
+                name = name[2:]
+            else:
+                raise RuntimeError("Function argument should begin with 'a_'")
+
+        self.c_name = name
+        self.c_var_type = self.c_type
+
+        if self.c_name == "cell":
+            raise RuntimeError("'cell' function argument should be in ctx")
+
+        if (m.group(1) == "char" and m.group(2).startswith("*") or
+            m.group(1) == "const char" and m.group(2).startswith("*")):
+            self.syntax = "string"
+        else:
+            (a, space, b) = m.group(1).partition(" ")
+            if space:
+                self.syntax = b
+            else:
+                self.syntax = a
+
+        if self.c_type.startswith("const "):
+            self.c_var_type = self.c_type[6:]
+
+        #verbose("ARG", self.syntax, self.c_name)
+
+#
+# Command definition
+#
+class kafs_command:
+    def __init__(self, line):
+        self.suite = None
+        self.name = None
+        self.help_str = None
+        self.desc = list()
+        self.params = list()
+        self.param_by_name = dict()
+        self.func_name = None
+        self.func_args = list()
+        self.func_args_by_name = dict()
+        self.no_combine = list()
+        self.seen_ctx_arg = False
+
+        (line, sep, help_str) = line.partition("-")
+        line = line.strip()
+        self.help_str = help_str.lstrip()
+        if not self.help_str:
+            raise RuntimeError("Missing help text on COMMAND")
+
+        (self.suite, sep, self.name) = line.partition(" ")
+        if not self.suite:
+            raise RuntimeError("Missing suite name")
+        if not self.name:
+            raise RuntimeError("Missing command name")
+
+    def add_arg(self, arg):
+        arg.index = len(self.params)
+        self.params.append(arg)
+        self.param_by_name[arg.name] = arg
+
+    def add_nocombine(self, arg):
+        (a, sep, b) = arg.strip().partition(",")
+        a = a.rstrip()
+        b = b.lstrip()
+        if not a or not b:
+            raise RuntimeError("Improperly formatted NOCOMBINE statement")
+        self.no_combine.append((a, b))
+
+    def add_to_description(self, line):
+        if line[0] == "*":
+            line = line[1:]
+        line = line.strip()
+        if line or self.desc:
+            self.desc.append(line)
+
+    def set_func(self, func):
+        self.func_name = func.strip()
+
+    def add_func_arg(self, arg):
+        if len(self.func_args) == 0 and not self.seen_ctx_arg:
+            if arg.c_name != "ctx":
+                raise RuntimeError("First command func argument must be 'ctx'")
+            self.seen_ctx_arg = True
+            return
+        self.func_args.append(arg)
+        self.func_args_by_name[arg.c_name] = arg
+
+    def finalise(self):
+        if "localauth" in self.param_by_name:
+            if "cell" in self.param_by_name:
+                self.no_combine.append(("cell", "localauth"))
+            if "noauth" in self.param_by_name:
+                self.no_combine.append(("noauth", "localauth"))
+
+#
+# Alias for a command
+#
+class kafs_command_alias:
+    def __init__(self, line):
+        self.suite = None
+        self.name = None
+        self.alias = None
+
+        (line, sep, alias) = line.partition("-")
+        self.alias = alias.lstrip()
+        if not self.alias:
+            raise RuntimeError("Missing alias-to on ALIAS")
+
+        line = line.strip()
+        (self.suite, sep, self.name) = line.partition(" ")
+        if not self.suite:
+            raise RuntimeError("Missing suite name")
+        if not self.name:
+            raise RuntimeError("Missing command name")
+
+    def finalise(self):
+        pass
+
+#
+# Command suite definition
+#
+class kafs_suite:
+    def __init__(self, name):
+        self.name = name
+        self.commands = dict()          # Commands in the suite
+        self.command_names = None
+        self.aliases = dict()           # Command aliases in the suite
+        self.alias_names = None
+
+    def add_command(self, cmd):
+        self.commands[cmd.name] = cmd
+
+    def add_alias(self, cmd):
+        self.aliases[cmd.name] = cmd
+
+    def finalise(self):
+        self.command_names = list(self.commands.keys())
+        self.command_names.sort()
+        for cmd in self.commands.values():
+            cmd.mink = find_longest_abbrev(cmd.name, self.command_names)
+            cmd.finalise()
+
+        self.alias_names = list(self.aliases.keys())
+        self.alias_names.sort()
+        for cmd in self.aliases.values():
+            cmd.mink = find_longest_abbrev(cmd.name, self.alias_names)
+            cmd.finalise()
+
+###############################################################################
+#
+# Parse a file, letting the caller handle error reporting
+#
+###############################################################################
+class line_parser:
+    def __init__(self):
+        self.commands = list()
+        self.source = None
+        self.lineno = 0
+        self.lines = list()
+        self.phase = 0
+        self.cmd = None
+        self.suites = dict()
+        self.suite_names = None
+
+    def new_file(self, infile):
+        self.source = infile
+        self.lineno = 0
+
+    def parse_line(self, line):
+        self.lineno += 1
+
+        if line == "/***":
+            self.phase = 1
+            return
+
+        if self.phase == 0:
+            return
+
+        line = " ".join(line.split())
+        #verbose("[" + self.phase + "] " + line)
+
+        if self.phase == 1:
+            if line.startswith("* COMMAND:"):
+                cmd = kafs_command(line[10:])
+                self.commands.append(cmd)
+                self.cmd = cmd
+                if cmd.suite not in self.suites:
+                    self.suites[cmd.suite] = kafs_suite(cmd.suite)
+                self.suites[cmd.suite].add_command(cmd)
+                self.phase = 2
+                return
+
+            if line.startswith("* ALIAS:"):
+                cmd = kafs_command_alias(line[8:])
+                self.commands.append(cmd)
+                if cmd.suite not in self.suites:
+                    self.suites[cmd.suite] = kafs_suite(cmd.suite)
+                self.suites[cmd.suite].add_alias(cmd)
+                self.phase = 0
+                return
+
+            raise RuntimeError("Missing command and help text")
+
+        if self.phase == 2:
+            if line == "/***":
+                raise RuntimeError("Re-opening command")
+            if not line.startswith("*"):
+                raise RuntimeError("Expected comment continuation")
+            if line == "*/":
+                self.phase = 3
+                return
+
+            if line.startswith("* ARG:"):
+                self.cmd.add_arg(kafs_argument(self.source + ":" + str(self.lineno),
+                                               line[6:]))
+                return
+
+            if line.startswith("* NOCOMBINE:"):
+                self.cmd.add_nocombine(line[12:])
+                return
+
+            self.cmd.add_to_description(line)
+            return
+
+        if self.phase == 3:
+            if line.startswith("bool COMMAND_"):
+                ob = line.find("(")
+                if ob == -1:
+                    raise RuntimeError("Missing opening bracket on function")
+                self.cmd.set_func(line[4:ob])
+                arg = line[ob + 1:]
+                if arg == "":
+                    self.phase = 4
+                    return
+
+                cb = arg.find(")")
+                if cb != -1:
+                    arg = arg[:cb - 1]
+
+                if arg == "void" and cb != -1:
+                    self.phase = 0
+                    return
+                self.cmd.add_func_arg(kafs_func_argument(arg))
+                if cb != -1:
+                    self.phase = 0
+                else:
+                    self.phase = 4
+                return
+
+            raise RuntimeError("Missing COMMAND_ function def")
+
+        if self.phase == 4:
+            line = line.lstrip()
+            if line == ")":
+                self.cmd = None
+                self.phase = 0
+                return
+            if line == "":
+                    raise RuntimeError("Blank line in argument list")
+
+            cb = line.find(")")
+            if cb != -1:
+                line = line[:cb]
+
+            for arg in line.split(","):
+                arg = arg.strip()
+                if arg != "":
+                    self.cmd.add_func_arg(kafs_func_argument(arg))
+
+            if cb != -1:
+                self.phase = 0
+            return
+
+###############################################################################
+#
+# Bits
+#
+###############################################################################
+def find_common_prefix_size(a, b):
+    "Find the size of the common prefix between two strings"
+    al = len(a)
+    l = min(al, len(b))
+    uniq = al - 1
+    for i in range(0, l):
+        if a[i] != b[i]:
+            uniq = len(a) - i - 1
+            #verbose("MIN", a, b, i, uniq)
+            break
+    return uniq
+
+def find_longest_abbrev(a, args):
+    "Find the longest abbreviation for an argument"
+    m = len(a) - 1
+    for i in args:
+        p = find_common_prefix_size(a, i)
+        if (p < m):
+            m = p
+    return len(a) - m
+
+###############################################################################
+#
+# Emit an argument parser stub to call the command function.
+#
+###############################################################################
+def emit_argument_table(cmd):
+    output("static const struct kafs_argument gen_", cmd.suite, "_", cmd.name, "_arguments[] = {\n")
+    for p in cmd.params:
+        name_q = "\"" + p.name + "\","
+        output("\t[", p.index, "] = { ", name_q.ljust(14), "");
+        output("", find_longest_abbrev(p.name, cmd.param_by_name.keys()))
+        if p.value:
+            output(", \"", p.value, "\"");
+        if not p.optional:
+            output(", .req = 1")
+        if p.multi:
+            output(", .multi = 1")
+        elif p.value:
+            output(", .single = 1")
+        else:
+            output(", .flag = 1")
+        if p.name_new:
+            output(", .newn = 1")
+        if p.auth_info:
+            output(", .auth = 1")
+        output(" },\n")
+    output("\t{}\n")
+    output("};\n")
+
+def emit_no_combining_table(cmd):
+    output("\n")
+    output("static const struct kafs_nocombine gen_", cmd.suite, "_", cmd.name, "_no_combine[] = {\n")
+    for i in cmd.no_combine:
+        a = cmd.param_by_name[i[0]]
+        b = cmd.param_by_name[i[1]]
+        output("\t{ ", a.index, ", ", b.index, " }, // ", i[0], ", ", i[1], "\n")
+    output("\t{}\n")
+    output("};\n")
+
+def emit_command_def(cmd):
+    emit_argument_table(cmd)
+    if cmd.no_combine:
+        emit_no_combining_table(cmd)
+    output("\n")
+    output("static bool gen_handler_", cmd.suite, "_", cmd.name, "(struct kafs_context *ctx, char **argv);\n")
+    output("static const struct kafs_command gen_cmd_", cmd.suite, "_", cmd.name, " = {\n")
+    output("\t.handler = gen_handler_", cmd.suite, "_", cmd.name, ",\n")
+    output("\t.suite = \"", cmd.suite, "\",\n")
+    output("\t.name = \"", cmd.name, "\",\n")
+    output("\t.mink = ", cmd.mink, ",\n")
+    output("\t.args = gen_", cmd.suite, "_", cmd.name, "_arguments,\n")
+    output("\t.nr_args = ", len(cmd.params), ",\n")
+    if cmd.no_combine:
+        output("\t.no_combine = gen_", cmd.suite, "_", cmd.name, "_no_combine,\n")
+    output("\t.help = \"", cmd.help_str, "\",\n")
+    if cmd.desc:
+        output("\t.desc =")
+        for d in cmd.desc:
+            output("\n")
+            output("\t\t\"", d, "\"")
+        output(",\n")
+
+    output("};\n")
+
+def emit_alias_def(cmd):
+    output("static const struct kafs_command gen_cmd_", cmd.suite, "_", cmd.name, " = {\n")
+    output("\t.suite = \"", cmd.suite, "\",\n")
+    output("\t.name = \"", cmd.name, "\",\n")
+    output("\t.alias = &gen_cmd_", cmd.suite, "_", cmd.alias, ",\n")
+    output("\t.help = \"-- Alias of ", cmd.alias, "\",\n")
+    output("\t.args = gen_", cmd.suite, "_", cmd.alias, "_arguments,\n")
+    output("};\n")
+
+def emit_command_handler(cmd):
+    header("extern bool ", cmd.func_name, "(\n")
+    header("\tstruct kafs_context *ctx")
+    for i in cmd.func_args:
+        header(",\n")
+        header("\t", i.c_type, " a_", i.c_name)
+    header(");\n")
+
+    output("static bool gen_handler_", cmd.suite, "_", cmd.name, "(struct kafs_context *ctx, char **args)\n")
+    output("{\n")
+
+    # Declare variables
+    output("\tstruct extracted_arg *results = NULL;\n")
+    for i in cmd.func_args:
+        if i.c_var_type == "struct kafs_context *":
+            pass
+        elif i.c_var_type.endswith("*"):
+            output("\t", i.c_var_type, "a_", i.c_name, " = NULL;\n")
+        elif i.c_var_type == "bool":
+            output("\t", i.c_var_type, " a_", i.c_name, " = false;\n")
+        else:
+            output("\t", i.c_var_type, " a_", i.c_name, " = 0;\n")
+    output("\tbool ret;\n")
+    output("\n")
+    output("\tresults = extract_arguments(ctx, args, &gen_cmd_", cmd.suite, "_", cmd.name, ");\n")
+    output("\tif (!results)\n")
+    output("\t\treturn false;\n")
+
+    # Write extraction list
+    output("\n")
+    output("\tif (")
+    need_cont = False
+    for p in cmd.params:
+        if p.auth_info:
+            if need_cont:
+                output(" ||\n\t    ")
+            output("!extract_kafs_context             (ctx, &results[", p.index, "])")
+            need_cont = True
+    if "cell" in cmd.param_by_name:
+        p = cmd.param_by_name["cell"]
+        if need_cont:
+            output(" ||\n\t    ")
+        fu = "extract_kafs_cell"
+        output("!", fu.ljust(30), "(ctx, &results[", p.index, "])")
+        need_cont = True
+    for p in cmd.params:
+        if not p.auth_info and p.name != "cell":
+            fa = cmd.func_args_by_name[p.name]
+            if need_cont:
+                output(" ||\n\t    ")
+            fu = "extract_"
+            if p.multi:
+                fu += "list_"
+            fu += fa.syntax
+            output("!", fu.ljust(30), "(ctx, &results[", p.index, "], &a_", p.name, ")")
+            need_cont = True
+    output(")\n")
+    output("\t\tgoto error;\n")
+
+    # Call processor function
+    output("\n")
+    output("\tret = ", cmd.func_name, "(\n")
+    output("\t\tctx")
+    for i in cmd.func_args:
+        output(",\n")
+        if i.c_var_type == "struct kafs_context *":
+            output("\t\t&a_", i.c_name)
+        else:
+            output("\t\ta_", i.c_name)
+    output(");\n")
+
+    # Do cleanup
+    output("\n")
+    output("error:\n")
+    for i in cmd.func_args:
+        if i.c_var_type == "struct kafs_context *":
+            pass
+        elif i.syntax == "string":
+            pass # output("\tfree(a_", i.c_name, ");\n")
+        elif i.syntax == "bool":
+            pass
+        elif i.c_var_type.endswith("**"):
+            output("\tfree_list_", i.syntax, "(a_", i.c_name, ");\n")
+        else:
+            output("\tfree_", i.syntax, "(a_", i.c_name, ");\n")
+    output("\tfree_kafs_context(ctx);\n")
+    output("\tfree_extracted_args(results);\n")
+    output("\treturn ret;\n")
+    output("}\n")
+
+def emit_command_table(suite):
+    names = suite.command_names + suite.alias_names
+    output("\n")
+    output("static const struct kafs_command *gen_", suite.name, "_commands[] = {\n")
+    for command in names:
+        try:
+            cmd = suite.commands[command]
+            output("\t&gen_cmd_", cmd.suite, "_", cmd.name, ",\n")
+        except KeyError:
+            cmd = suite.aliases[command]
+            output("\t&gen_cmd_", cmd.suite, "_", cmd.name, ",\n")
+    output("\tNULL\n")
+    output("};\n")
+
+def emit_suite_def(suite):
+    header("\n")
+    header("extern const struct kafs_suite gen_", suite.name, "_suite;\n")
+
+    output("\n")
+    output("const struct kafs_suite gen_", suite.name, "_suite = {\n")
+    output("\t.name = \"", suite.name, "\",\n")
+    output("\t.commands = gen_", suite.name, "_commands,\n")
+    output("};\n")
+
+
+
+###############################################################################
+#
+# Do the work
+#
+###############################################################################
+def run(sname, files):
+    suite_name = sname
+
+    parser = line_parser()
+    for infile in files:
+        parser.new_file(infile)
+        verbose("Parsing", infile)
+        f = open(infile, encoding="utf-8")
+        for line in f.readlines():
+            try:
+                parser.parse_line(line.rstrip())
+            except RuntimeError as e:
+                error(infile + ":" + str(parser.lineno) + ":", e)
+        f.close()
+
+    suites = parser.suites
+    if not suites:
+        error("No suites defined")
+        exit(1)
+
+    for suite in suites.values():
+        suite.finalise()
+    suite_names = list(suites.keys())
+    suite_names.sort()
+    parser.suite_names = suite_names
+
+    global output_file
+    output_file = open(suite_name + ".c", "w", encoding="utf-8")
+    if not output_file:
+        error(suite_name, ".c: Unable to open file")
+
+    global header_file
+    header_file = open(suite_name + ".h", "w", encoding="utf-8")
+    if not header_file:
+        error(suite_name, ".h: Unable to open file")
+
+    header("// AUTOGENERATED - DO NOT EDIT\n")
+    header("#include <stdlib.h>\n");
+    header("#include \"arg_parse.h\"\n");
+    header("\n")
+
+    output("// AUTOGENERATED - DO NOT EDIT\n")
+    output("#include \"", suite_name, ".h\"\n");
+
+    # Emit command table
+    for s in suite_names:
+        suite = suites[s]
+
+        output("\n")
+        output("/* ---- SUITE ", suite.name, " ---- */\n")
+
+        for command in suite.command_names:
+            output("\n")
+            cmd = suite.commands[command]
+            emit_command_def(cmd)
+            output("\n")
+            emit_command_handler(cmd)
+
+        for command in suite.alias_names:
+            output("\n")
+            cmd = suite.aliases[command]
+            emit_alias_def(cmd)
+
+        emit_command_table(suite)
+        emit_suite_def(suite)
+
+    # Emit an argument parser stub
+
+
+
+
+if len(sys.argv) < 3:
+    verbose("Usage: {:s} <suite_name> <filename>+".format(sys.argv[0]))
+    sys.exit(1)
+
+run(sys.argv[1], sys.argv[2:])
diff --git a/kafs/kafs.c b/kafs/kafs.c
new file mode 100644 (file)
index 0000000..a417c60
--- /dev/null
@@ -0,0 +1,390 @@
+/* AFS utility suite main entry point.
+ *
+ * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <kafs/cellserv.h>
+#include <rxrpc.h>
+#include "kafs.h"
+#include "arg_parse.h"
+#include "bos.h"
+#include "fs.h"
+#include "pts.h"
+#include "vos.h"
+#include "display.h"
+
+bool kafs_debug_caps;
+bool kafs_debug_trace;
+
+static struct kafs_context context;
+
+const struct kafs_suite *kafs_suites[] = {
+       &gen_bos_suite,
+       &gen_fs_suite,
+       &gen_pts_suite,
+       &gen_vos_suite,
+       NULL
+};
+
+static void error_report(const char *fmt, ...)
+{
+       va_list va;
+
+       va_start(va, fmt);
+       vfprintf(stderr, fmt, va);
+       fputc('\n', stderr);
+       va_end(va);
+}
+
+static void verbose(const char *fmt, ...)
+{
+       va_list va;
+
+       va_start(va, fmt);
+       //printf("[V] ");
+       //vprintf(fmt, va);
+       //putchar('\n');
+       va_end(va);
+}
+
+bool kafs_open_endpoint(struct kafs_context *ctx)
+{
+       struct rxrpc_local_endpoint *endpoint;
+       struct rxrpc_security *security;
+       char buf[1024];
+
+       if (!ctx->endpoint) {
+               endpoint = rxrpc_new_local_endpoint(NULL, 0);
+               if (!endpoint) {
+                       perror("Local endpoint");
+                       exit(1);
+               }
+
+               ctx->endpoint = endpoint;
+       }
+
+       if (!ctx->security) {
+               snprintf(buf, sizeof(buf), "afs@%s", ctx->cell_name);
+               security = rxrpc_new_security(ctx->endpoint, buf, ctx->sec_level, &ctx->result);
+               if (!security)
+                       return false;
+
+               ctx->security = security;
+       }
+
+       return true;
+}
+
+bool kafs_command_usage(const struct kafs_command *cmd)
+{
+       const struct kafs_argument *arg;
+       size_t seg;
+       char buf[256], *p;
+       int llen;
+
+       llen = printf("Usage: %s %s", cmd->suite, cmd->name);
+       for (arg = cmd->args; arg->name; arg++) {
+               p = buf;
+               *p++ = ' ';
+               if (!arg->req)
+                       *p++ = '[';
+               *p++ = '-';
+               strcpy(p, arg->name);
+               p += strlen(p);
+               if (!arg->flag)
+                       p += sprintf(p, " <%s>", arg->val);
+               if (arg->multi)
+                       *p++ = '+';
+               if (!arg->req)
+                       *p++ = ']';
+               *p = 0;
+
+               seg = p - buf;
+               if (llen + seg > 79) {
+                       putchar('\n');
+                       llen = printf("         ");
+               }
+               llen += printf("%s", buf);
+       }
+
+       seg = sprintf(buf, " [-help]");
+       if (llen + seg > 79) {
+               putchar('\n');
+               llen = printf("         ");
+       }
+       llen += printf("%s", buf);
+       printf("\n");
+       return true;
+}
+
+bool kafs_help(const struct kafs_suite *suite, char *topic)
+{
+       const struct kafs_command *cmd;
+       size_t cl;
+       bool ambiguous = false;
+       int i;
+
+       if (!topic) {
+               printf("%s: Commands are:\n", suite->name);
+               for (i = 0; suite->commands[i]; i++) {
+                       cmd = suite->commands[i];
+                       printf("%-15s %s\n", cmd->name, cmd->help);
+               }
+               return true;
+       }
+
+       cl = strlen(topic);
+       for (i = 0; suite->commands[i]; i++) {
+               cmd = suite->commands[i];
+               if (strcmp(topic, cmd->name) == 0)
+                       goto matched_command;
+               if (cl >= strlen(cmd->name))
+                       continue;
+               if (cl >= cmd->mink && strncmp(topic, cmd->name, cl) == 0)
+                       goto matched_command;
+               if (strncmp(topic, cmd->name, cl) == 0)
+                       ambiguous = true;
+       }
+
+       if (ambiguous)
+               fprintf(stderr, "%s: Ambiguous topic '%s'; use 'apropos' to list\n",
+                       suite->name, topic);
+       else
+               fprintf(stderr, "%s: Unknown topic '%s'\n",
+                       suite->name, topic);
+       fprintf(stderr, "%s: Unrecognised or ambiguous command name\n", suite->name);
+       return false;
+
+matched_command:
+       return kafs_command_usage(cmd);
+}
+
+const struct kafs_command *look_up_command(const struct kafs_suite *suite, char *command)
+{
+       const struct kafs_command *cmd;
+       size_t cl;
+       bool ambiguous = false;
+       int i;
+
+       cl = strlen(command);
+
+       for (i = 0; suite->commands[i]; i++) {
+               cmd = suite->commands[i];
+               if (strcmp(command, cmd->name) == 0)
+                       goto match;
+               if (cl >= strlen(cmd->name))
+                       continue;
+               if (cl >= cmd->mink && strncmp(command, cmd->name, cl) == 0)
+                       goto match;
+               if (strncmp(command, cmd->name, cl) == 0)
+                       ambiguous = true;
+       }
+
+       if (ambiguous)
+               fprintf(stderr, "%s: Ambiguous command '%s'; type '%s help' for list\n",
+                       suite->name, command, suite->name);
+       else
+               fprintf(stderr, "%s: Unrecognised command '%s'; type '%s help' for list\n",
+                       suite->name, command, suite->name);
+       return NULL;
+
+match:
+       return cmd->alias ? cmd->alias : cmd;
+}
+
+bool dispatch_command(const struct kafs_suite *suite, char *command, char **args)
+{
+       const struct kafs_command *cmd;
+
+       _enter("%s", command);
+
+       cmd = look_up_command(suite, command);
+       if (cmd) {
+               _debug("found %s", cmd->name);
+               return cmd->handler(&context, args);
+       }
+       return false;
+}
+
+struct kafs_lookup_context kafs_lookup_context = {
+       .report.error           = error_report,
+       .report.verbose         = verbose,
+       .report.verbose2        = verbose,
+       .want_ipv4_addrs        = true,
+       .want_ipv6_addrs        = true,
+};
+
+static void parse_debug(char *args)
+{
+       char *p, *save = NULL;
+
+       while ((p = strtok_r(args, ",", &save))) {
+               args = NULL;
+               if (strcmp(p, "buf") == 0)              rxrpc_debug_buffers = true;
+               else if (strcmp(p, "compl") == 0)       kafs_debug_completion = true;
+               else if (strcmp(p, "core") == 0)        rxrpc_debug_core = true;
+               else if (strcmp(p, "caps") == 0)        kafs_debug_caps = true;
+               else if (strcmp(p, "data") == 0)        rxrpc_debug_data = true;
+               else if (strcmp(p, "ops") == 0)         rxrpc_debug_ops = true;
+               else if (strcmp(p, "trans") == 0)       rxrpc_debug_transport = true;
+               else if (strcmp(p, "xdr") == 0)         rxrpc_debug_xdr = true;
+               else if (strcmp(p, "trace") == 0)       kafs_debug_trace = true;
+       }
+}
+
+int main(int argc, char *argv[])
+{
+       const struct kafs_suite *suite;
+       char *prog, *s, **p;
+       int completion_argc = -1;
+       int i;
+
+       prog = strrchr(argv[0], '/');
+       if (prog)
+               prog++;
+       else
+               prog = argv[0];
+       argc -= 1;
+       argv += 1;
+
+       if (argc > 0 && strcmp(argv[0], "--complete") == 0) {
+               if (argc < 2) {
+                       fprintf(stderr, "Missing completion arg number\n");
+                       exit(2);
+               }
+
+               completion_argc = atoi(argv[1]);
+               if (completion_argc < 0) {
+                       fprintf(stderr, "Completion arg is negative\n");
+                       exit(2);
+               }
+
+               prog = strrchr(argv[2], '/');
+               if (prog)
+                       prog++;
+               else
+                       prog = argv[2];
+
+               completion_argc--;
+               argc -= 3;
+               argv += 3;
+
+               comp_log("\n");
+               comp_log("Compl %d\n", completion_argc);
+               for (p = argv; *p; p++) {
+                       if (p - argv == completion_argc)
+                               comp_log(" >%s<", *p);
+                       else
+                               comp_log(" '%s'", *p);
+               }
+               comp_log("\n");
+       }
+
+       if (argc > 0 && strncmp(argv[0], "--debug", 7) == 0) {
+               char *deb = NULL;
+               if (completion_argc < 0) {
+                       if (argv[0][7] == '=') {
+                               deb = argv[0] + 8;
+                               completion_argc--;
+                               argc -= 1;
+                               argv += 1;
+                       } else if (argv[0][7] != '\0') {
+                               fprintf(stderr, "Unknown option '%s'\n", argv[0]);
+                               exit(2);
+                       } else if (argc == 1) {
+                               fprintf(stderr, "Missing --debug parameter\n");
+                               exit(2);
+                       } else {
+                               deb = argv[1];
+                               completion_argc -= 2;
+                               argc -= 2;
+                               argv += 2;
+                       }
+               } else {
+                       if (argc < 3) {
+                               fprintf(stderr, "Missing --debug parameter\n");
+                               exit(2);
+                       }
+
+                       if (strcmp(argv[1], "=") != 0) {
+                               fprintf(stderr, "Unknown separator '%s'\n", argv[1]);
+                               exit(2);
+                       }
+
+                       deb = argv[2];
+                       completion_argc -= 3;
+                       argc -= 3;
+                       argv += 3;
+               }
+               parse_debug(deb);
+       }
+
+       if (kafs_init_lookup_context(&kafs_lookup_context) < 0)
+               exit(1);
+
+       if (kafs_read_config(NULL, &kafs_lookup_context.report) < 0)
+               exit(kafs_lookup_context.report.bad_config ? 3 : 1);
+
+       if (completion_argc == 0)
+               bash_complete_command_suite(argv[0]);
+
+       if (strcmp(prog, "kafs") != 0 &&
+           strcmp(prog, "afs") != 0) {
+               for (i = 0; kafs_suites[i]; i++) {
+                       suite = kafs_suites[i];
+                       if (strcmp(prog, suite->name) == 0)
+                               goto use_suite;
+               }
+               fprintf(stderr, "%s: Unrecognised command suite\n", prog);
+               exit(2);
+       }
+
+       if (argc == 0) {
+               fprintf(stderr, "kafs supports the following command suites:\n");
+               for (i = 0; kafs_suites[i]; i++) {
+                       suite = kafs_suites[i];
+                       printf("  %s\n", suite->name);
+               }
+
+               printf("Type 'kafs <suite> help' to get a command list\n");
+               exit(1);
+       }
+
+       s = argv[0];
+       for (i = 0; kafs_suites[i]; i++) {
+               suite = kafs_suites[i];
+               if (strcmp(s, suite->name) == 0)
+                       goto use_suite;
+       }
+
+       fprintf(stderr, "Cannot determine command suite to use\n");
+       exit(2);
+
+use_suite:
+       comp_log("suite: %s\n", suite->name);
+       if (completion_argc >= 0)
+               bash_complete(argc, argv, completion_argc, suite);
+       argc--;
+       argv++;
+
+       if (!*argv) {
+               fprintf(stderr, "%s: Type '%s help' or '%s help <topic>' for help\n",
+                       suite->name, suite->name, suite->name);
+               exit(2);
+       }
+
+       if (!dispatch_command(suite, *argv, argv + 1))
+               kafs_display_error(&context);
+       exit(0);
+}
diff --git a/kafs/kafs.h b/kafs/kafs.h
new file mode 100644 (file)
index 0000000..397a69e
--- /dev/null
@@ -0,0 +1,173 @@
+/* KAFS program definitions
+ *
+ * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#ifndef KAFS_H
+#define KAFS_H
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <uuid/uuid.h>
+#include <errno.h>
+#include <kafs/cellserv.h>
+#include <rxrpc.h>
+
+#define FS_PORT                        7000    /* AFS file server port */
+#define FS_SERVICE             1       /* AFS File Service ID */
+
+typedef unsigned long long kafs_volume_id_t;
+
+enum kafs_service_t {
+       kafs_service_afs,
+       kafs_service_yfs,
+};
+
+struct kafs_context {
+       struct rxrpc_local_endpoint *endpoint;
+       struct rxrpc_security   *security;
+       struct rxrpc_result     result;
+
+       char                    *cell_name;
+       struct kafs_cell        *cell_db;
+       struct kafs_server      *vl_server;     /* Preferred VL server */
+       unsigned int            vl_addr;        /* Preferred address on vl_server */
+       enum kafs_service_t     vl_service;     /* Service provided on that server */
+       unsigned int            vl_caps[1];     /* VL server capabilities */
+       struct rxrpc_call_params vl_params;     /* Parameters for accessing pref VL server */
+
+       enum rxrpc_security_auth_level sec_level; /* Security level */
+       char                    err_buf[1024];
+};
+
+struct kafs_user {
+       char                    *name;
+};
+
+struct kafs_group {
+       char                    *name;
+};
+
+struct kafs_partition {
+       unsigned int            id;
+};
+
+struct kafs_fileserver_spec {
+       uuid_t                  uuid;
+       char                    *name;
+       unsigned int            nr_addrs;
+       struct sockaddr_rxrpc   *addrs;
+};
+
+struct kafs_vlserver {
+       char                    *name;
+       struct kafs_server_list *slist;
+};
+
+struct kafs_volume_spec {
+       char                    *name;
+       unsigned long long      number;
+       bool                    is_numeric:1;
+};
+
+struct kafs_volserver_spec {
+       char                    *name;
+       unsigned int            nr_addrs;
+       struct sockaddr_rxrpc   *addrs;
+};
+
+/*
+ * arg_parse.c
+ */
+extern void free_kafs_context(struct kafs_context *);
+extern void free_kafs_group(struct kafs_group *);
+extern void free_kafs_partition(struct kafs_partition *);
+extern void free_kafs_user(struct kafs_user *);
+extern void free_kafs_vlserver(struct kafs_vlserver *);
+extern void free_kafs_volserver_spec(struct kafs_volserver_spec *);
+extern void free_kafs_fileserver_spec(struct kafs_fileserver_spec *);
+extern void free_kafs_volume_spec(struct kafs_volume_spec *);
+extern void free_list_kafs_group(struct kafs_group **);
+extern void free_list_kafs_user(struct kafs_user **);
+
+static inline void free_uuid_t(uuid_t *uuid) { free(uuid); }
+
+/*
+ * display_error.c
+ */
+extern __attribute__((format(printf,2,3)))
+bool kafs_inval(struct kafs_context *ctx, const char *fmt, ...);
+extern __attribute__((format(printf,2,3)))
+bool kafs_error(struct kafs_context *ctx, const char *fmt, ...);
+
+/*
+ * kafs.c
+ */
+extern struct kafs_lookup_context kafs_lookup_context;
+extern bool kafs_open_endpoint(struct kafs_context *);
+
+/*
+ * Inline functions.
+ */
+
+static inline __attribute__((format(printf,2,3)))
+bool kafs_syserror(struct kafs_context *ctx, const char *fmt, ...)
+{
+       ctx->result.source = rxrpc_error_from_system;
+       ctx->result.error = errno;
+       ctx->result.abort_code = 0;
+       return false;
+}
+
+static inline bool kafs_nomem(struct kafs_context *ctx)
+{
+       ctx->result.source = rxrpc_error_from_system;
+       ctx->result.error = ENOMEM;
+       ctx->result.abort_code = 0;
+       return false;
+}
+
+extern bool kafs_debug_trace;
+extern bool kafs_debug_caps;
+
+#define kenter(FMT,...)        printf("==> %s("FMT")\n",__func__ ,##__VA_ARGS__)
+#define kleave(FMT,...)        printf("<== %s()"FMT"\n",__func__ ,##__VA_ARGS__)
+#define kdebug(FMT,...)        printf("    "FMT"\n" ,##__VA_ARGS__)
+
+#if defined(__KDEBUG)
+#define _enter(FMT,...)        kenter(FMT,##__VA_ARGS__)
+#define _leave(FMT,...)        kleave(FMT,##__VA_ARGS__)
+#define _debug(FMT,...)        kdebug(FMT,##__VA_ARGS__)
+
+#elif 1
+#define _enter(FMT,...)                                        \
+do {                                                   \
+       if (kafs_debug_trace)                           \
+               kenter(FMT,##__VA_ARGS__);              \
+} while (0)
+
+#define _leave(FMT,...)                                        \
+do {                                                   \
+       if (kafs_debug_trace)                           \
+               kleave(FMT,##__VA_ARGS__);              \
+} while (0)
+
+#define _debug(FMT,...)                                        \
+do {                                                   \
+       if (kafs_debug_trace)                           \
+               kdebug(FMT,##__VA_ARGS__);              \
+} while (0)
+
+#else
+#define _enter(FMT,...)        if (0) printf("==> %s("FMT")",__func__ ,##__VA_ARGS__)
+#define _leave(FMT,...)        if (0) printf("<== %s()"FMT"",__func__ ,##__VA_ARGS__)
+#define _debug(FMT,...)        if (0) printf("    "FMT ,##__VA_ARGS__)
+#endif
+
+#endif /* KAFS_H */
diff --git a/kafs/pts_help.c b/kafs/pts_help.c
new file mode 100644 (file)
index 0000000..22b47d8
--- /dev/null
@@ -0,0 +1,54 @@
+/* The pts help command
+ *
+ * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "pts.h"
+
+/***
+ * COMMAND: pts help - Get help on commands
+ * ARG: "[-topic <help string>+]"
+ * ARG: "[-admin]"
+ */
+bool COMMAND_pts_help(
+       struct kafs_context     *ctx,
+       char                    **a_topic,
+       bool                    a_admin)
+{
+       if (!a_topic)
+               return kafs_help(&gen_pts_suite, NULL);
+       for (; *a_topic; a_topic++)
+               if (!kafs_help(&gen_pts_suite, *a_topic))
+                       return false;
+       return true;
+}
+
+/***
+ * COMMAND: pts apropos - Search for a command by its help text
+ * ARG: "-topic <help string>"
+ */
+bool COMMAND_pts_apropos(
+       struct kafs_context     *ctx,
+       char                    *a_topic)
+{
+       const struct kafs_command *cmd;
+       int i;
+
+       for (i = 0; gen_pts_suite.commands[i]; i++) {
+               cmd = gen_pts_suite.commands[i];
+
+               if (strstr(cmd->name, a_topic) ||
+                   strstr(cmd->help, a_topic))
+                       printf("%s: %s\n", cmd->name, cmd->help);
+       }
+
+       return true;
+}
diff --git a/kafs/vos_help.c b/kafs/vos_help.c
new file mode 100644 (file)
index 0000000..9de221f
--- /dev/null
@@ -0,0 +1,55 @@
+/* The vos help command
+ *
+ * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "kafs.h"
+#include "vos.h"
+
+/***
+ * COMMAND: vos help - Get help on commands
+ * ARG: "[-topic <help string>+]"
+ * ARG: "[-admin]"
+ */
+bool COMMAND_vos_help(
+       struct kafs_context     *ctx,
+       char                    **a_topic,
+       bool                    a_admin)
+{
+       if (!a_topic)
+               return kafs_help(&gen_vos_suite, NULL);
+       for (; *a_topic; a_topic++)
+               if (!kafs_help(&gen_vos_suite, *a_topic))
+                       return false;
+       return true;
+}
+
+/***
+ * COMMAND: vos apropos - Search for a command by its help text
+ * ARG: "-topic <help string>"
+ */
+bool COMMAND_vos_apropos(
+       struct kafs_context     *ctx,
+       const char              *a_topic)
+{
+       const struct kafs_command *cmd;
+       int i;
+
+       for (i = 0; gen_vos_suite.commands[i]; i++) {
+               cmd = gen_vos_suite.commands[i];
+
+               if (strstr(cmd->name, a_topic) ||
+                   strstr(cmd->help, a_topic))
+                       printf("%s: %s\n", cmd->name, cmd->help);
+       }
+
+       return true;
+}