]> 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 10:53:22 +0000 (11:53 +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..94f131d
--- /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..aa1ff17
--- /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
+       $(CXX) $(CPPFLAGS) -MMD -MF .$@.d $(CFLAGS) -o $@ -c $<
+
+LIBDIR := ../lib
+
+kafs: $(KAFS_OBJS) $(LIBDIR)/libkafs_utils.a
+       $(CXX) -o $@ $(KAFS_OBJS) -L$(LIBDIR) -lkafs_utils -lkafs_client -luuid -lfmt
+
+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..1bd2e70
--- /dev/null
@@ -0,0 +1,482 @@
+/* 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 <cstdarg>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <cctype>
+#include <vector>
+#include <string>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <alloca.h>
+extern "C" {
+#include <kafs/cellserv.h>
+}
+#include "arg_parse.H"
+
+namespace kafs {
+void bash_complete_command_name(const Suite *suite, char *word);
+}
+
+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 kafs::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 kafs::bash_complete_command_suite(const char *s)
+{
+       const Suite *suite;
+       bool need_space = false;
+       int i, l = strlen(s);
+
+       comp_log("Word '%s'\n", s);
+
+       for (i = 0; command_suites[i]; i++) {
+               suite = command_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 std::vector<std::string> complete_cell_name(const char *prefix)
+{
+       std::vector<std::string> cells;
+       int c, l = strlen(prefix);
+
+       cells.reserve(kafs_cellserv_db->nr_cells);
+
+       c = 0;
+       for (size_t 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 kafs::bash_complete_command_name(const 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 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 kafs::Argument    *canon_flag;
+       const kafs::Argument    *word_type;
+       bool                            additional_arg;
+};
+
+/*
+ * Perform command line completion.
+ */
+void kafs::bash_complete(unsigned int argc, char **argv, unsigned int target,
+                        const Suite *suite)
+{
+       const Argument *last_sw, *arg, *word_type, *canon;
+       const Argument *next_required; //, *next_opts;
+       const Command *cmd;
+       struct arg_notes *notes;
+       const char *word, *special, *space = "";
+       char **p, *eliminated_args;
+       bool skip_flag, additional_arg;
+       unsigned int pos, i, j, wl;
+
+       //completion_log = stderr;
+       comp_log("Compl %d\n", target);
+       comp_log("Suite: %s\n", suite->name);
+
+       if (target == 0)
+               bash_complete_command_name(suite, argv[0]);
+       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[0]);
+       if (!cmd) {
+               comp_log("Unknown command %s\n", argv[0]);
+               exit(1);
+       }
+
+       /* Discard the suite name and the command name */
+       argc -= 1;
+       argv += 1;
+       target -= 1;
+
+       /* 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 = (struct arg_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 = (char *)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 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 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 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) {
+               std::vector<std::string> cells = complete_cell_name(word);
+
+               for (size_t i = 0; i < cells.size(); i++) {
+                       const std::string &c = cells[i];
+                       printf("%s%s", space, c.c_str());
+                       space = " ";
+               }
+               printf("\n");
+       } 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..5111584
--- /dev/null
@@ -0,0 +1,597 @@
+/* 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.
+ */
+
+#include <fmt/core.h>
+#include <cstdarg>
+#include <cstdlib>
+#include <cstring>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <netdb.h>
+extern "C" {
+#include <kafs/cellserv.h>
+}
+#include "rxrpc.H"
+#include "afs_xg.H"
+#include "arg_parse.H"
+
+using rxrpc::ref;
+
+namespace kafs {
+static Extracted_arg &find_switch(const Command *cmd, char *sw,
+                                 std::vector<Extracted_arg> &results);
+static void append_argument(Extracted_arg &result, char *a);
+static void get_local_cell(Context *ctx);
+}
+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 kafs::Extracted_arg &kafs::find_switch(const kafs::Command *cmd, char *sw,
+                                             std::vector<Extracted_arg> &results)
+{
+       const kafs::Argument *arg;
+       unsigned int j;
+       size_t l = strlen(sw);
+
+       /* Look up the switch in the table of possible arguments and flags. */
+       for (j = 0; j < cmd->nr_args; j++) {
+               arg = &cmd->args[j];
+               Extracted_arg &result = results[j];
+               if (strcmp(sw, arg->name) == 0)
+                       return result;
+       }
+
+       /* 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];
+               Extracted_arg &result = results[j];
+               if (l > strlen(arg->name))
+                       continue;
+               if (memcmp(sw, arg->name, l) != 0)
+                       continue;
+               if (l >= arg->mink)
+                       return result;
+
+               throw std::invalid_argument(
+                       fmt::format("Ambiguous switch name abbreviation '-{}'", sw));
+       }
+
+       throw std::invalid_argument(fmt::format("Unsupported switch '-{}'", sw));
+}
+
+/*
+ * Append an argument to a list.
+ */
+static void kafs::append_argument(Extracted_arg &result, char *a)
+{
+       const kafs::Argument *arg = result.arg;
+       size_t size = strlen(a);
+
+       /* Check that none of the arguments are too big. */
+       if (arg->size_limit && size > arg->size_limit)
+               throw std::invalid_argument(
+                       fmt::format("Switch '-{}' has an overlong argument",
+                                   result.arg->name));
+
+       result.values.push_back(a);
+}
+
+/*
+ * 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.
+ *
+ */
+void kafs::extract_arguments(Context *ctx, char **args,
+                            const Command *cmd,
+                            std::vector<Extracted_arg> &results)
+{
+       const Nocombine *combo;
+       const Argument *arg;
+       bool need_switch = false;
+       unsigned int av = 0;    /* Available arguments index; */
+       unsigned int i;
+
+       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;
+
+               if (cmd->args[0].req)
+                       throw std::invalid_argument("Missing required parameters");
+               return;
+       }
+
+       /* 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)
+                               throw std::invalid_argument(
+                                       fmt::format("Need switch before argument {:d}", i));
+                       if (av >= cmd->nr_args)
+                               throw std::invalid_argument("Unexpected positional argument");
+
+                       arg = &cmd->args[av];
+                       if (arg->flag)
+                               throw std::invalid_argument("Unexpected positional argument");
+
+                       Extracted_arg &result = results[av];
+                       av = av + 1;
+
+                       append_argument(result, a);
+                       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;
+                                               append_argument(result, a);
+                                       }
+                                       need_switch = true;
+                               }
+                       }
+                       result.seen = true;
+
+               } else {
+                       /* Deal with tagged arguments */
+                       char *sw = a + 1;
+
+                       args++;
+
+                       if (!sw[0])
+                               throw std::invalid_argument("Missing switch name");
+
+                       if (strcmp(sw, "help") == 0)
+                               goto help;
+
+                       Extracted_arg &result = find_switch(cmd, sw, results);
+                       arg = result.arg;
+
+                       /* Reject repeat flags */
+                       if (result.seen)
+                               throw std::invalid_argument(
+                                       fmt::format("Duplicate switch '-{}' not permitted", sw));
+                       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_argument(result, a);
+                       }
+               }
+       }
+
+       /* Check for missing required arguments */
+       for (i = 0; i < cmd->nr_args; i++) {
+               const Argument *arg = &cmd->args[i];
+
+               if (!arg->req)
+                       break;
+
+               if (!results[i].seen)
+                       throw std::invalid_argument(
+                               fmt::format("Missing '-{}' argument", arg->name));
+       }
+
+       /* 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)
+                               throw std::invalid_argument(
+                                       fmt::format("Can't combine -{} with -{}",
+                                                   results[combo->a].arg->name,
+                                                   results[combo->b].arg->name));
+               }
+       }
+
+       /* Check argument syntax and value counts */
+       for (i = 0; i < cmd->nr_args; i++) {
+               const Argument *arg = &cmd->args[i];
+               Extracted_arg &result = results[i];
+
+               if (!results[i].seen)
+                       continue;
+
+               /* Check that we have the number of arguments we're expecting */
+               if (arg->flag && result.values.size() > 0)
+                       throw std::invalid_argument(
+                               fmt::format("Switch '-{}' expects no arguments", arg->name));
+               if (arg->single && result.values.size() != 1)
+                       throw std::invalid_argument(
+                               fmt::format("Switch '-{}' expects one argument", arg->name));
+               if (arg->multi && result.values.size() < 1)
+                       throw std::invalid_argument(
+                               fmt::format("Switch '-{}' expects one or more arguments", arg->name));
+
+               /* Call the syntax checker */
+               if (arg->syntax)
+                       arg->syntax(result);
+       }
+
+       return;
+
+help:
+       command_usage(cmd);
+       throw Gave_help();
+}
+
+/*
+ * Destroy a context.
+ */
+kafs::Context::~Context()
+{
+       if (cell_db)
+               kafs_free_cell(cell_db);
+}
+
+void kafs::extract_kafs_context(Context *ctx, 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;
+       }
+
+       if (strcmp(name, "localauth") == 0) {
+               if (arg.seen)
+                       ctx->sec_level = rxrpc::security_local_auth;
+               return;
+       }
+
+       if (strcmp(name, "encrypt") == 0) {
+               if (arg.seen)
+                       ctx->sec_level = rxrpc::security_encrypt;
+               return;
+       }
+
+       printf("Unimplemented: extract_kafs_auth %s\n", arg.arg->name);
+       abort();
+}
+
+static void kafs::get_local_cell(Context *ctx)
+{
+       size_t size;
+       char buf[256];
+       FILE *f;
+       int err = 0;
+
+       f = fopen("/proc/net/afs/rootcell", "r");
+       if (!f)
+               throw rxrpc::syserror("/proc/net/afs/rootcell");
+
+       size = fread(buf, sizeof(char), sizeof(buf) - 1, f);
+       if (ferror(f))
+               err = errno;
+       fclose(f);
+       if (err)
+               throw rxrpc::syserror(err, "/proc/net/afs/rootcell");
+
+       if (size > 0 && buf[size - 1] == '\n')
+               size--;
+
+       if (size == sizeof(buf) - 1)
+               throw std::invalid_argument("Local cell name unexpectedly long");
+       if (!size)
+               throw std::invalid_argument("Local cell name not set");
+
+       ctx->cell_name = std::string(buf, size);
+}
+
+/*
+ * Determine the cell we're working in.
+ */
+void kafs::extract_kafs_cell(Context *ctx, Extracted_arg &arg)
+{
+       if (!arg.seen)
+               kafs::get_local_cell(ctx);
+       else
+               ctx->cell_name = arg.values[0];
+
+       ctx->cell_db = kafs_lookup_cell(ctx->cell_name.c_str(), &lookup_context);
+       if (!ctx->cell_db)
+               throw rxrpc::syserror(fmt::format("{}: Unknown cell", ctx->cell_name.c_str()));
+}
+
+/*
+ * Extract the volume server name or address.
+ */
+void kafs::extract(Context *ctx, Extracted_arg &arg, Vlserver_spec &vls)
+{
+       struct kafs_server *s;
+
+       if (!arg.seen)
+               return;
+
+       vls.specified = true;
+
+       vls.name = arg.values[0];
+
+       vls.slist = (struct kafs_server_list *)calloc(1, sizeof(struct kafs_server_list));
+       if (!vls.slist)
+               throw rxrpc::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 = (struct kafs_server *)calloc(1, sizeof(struct kafs_server));
+       if (!vls.slist->servers)
+               throw rxrpc::nomem();
+
+       s = &vls.slist->servers[0];
+       s->name = (char *)vls.name.c_str();
+       s->borrowed_name = true;
+
+       if (kafs_dns_lookup_addresses(vls.slist, &lookup_context) < 0)
+               throw rxrpc::network_error("Unable to look up DNS");
+}
+
+/*
+ * Extract the volume server name or address.
+ */
+void kafs::extract(Context *ctx, Extracted_arg &arg, Volserver_spec &vs)
+{
+       if (arg.seen) {
+               vs.specified = true;
+               vs.name = arg.values[0];
+       }
+}
+
+/*
+ * Extract the volume server specifier name or address.
+ */
+void kafs::extract(Context *ctx, Extracted_arg &arg, Fileserver_spec &fs)
+{
+       if (arg.seen) {
+               fs.specified = true;
+               fs.name = arg.values[0];
+       }
+}
+
+/*
+ * Extract the name of a partition.
+ */
+void kafs::extract(Context *ctx, Extracted_arg &arg, Partition_spec &part)
+{
+       unsigned long n;
+       const char *name, *a;
+       char *end;
+
+       if (!arg.seen)
+               return;
+       part.specified = true;
+
+       name = arg.values[0].c_str();
+
+       if (!*name)
+               throw std::invalid_argument("Empty partition name string");
+
+       n = strtoul(name, &end, 0);
+       if (*end) {
+               /* The ID is not strictly numeric in form. */
+               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])
+                       throw std::invalid_argument(
+                               fmt::format("Unparseable partition ID '{}'", name));
+               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 {
+                       throw std::invalid_argument(
+                               fmt::format("Unparseable partition ID '{}'", name));
+               }
+       }
+
+       if (n < 0 || n > 255)
+               throw std::invalid_argument(
+                       fmt::format("Partition ID '{}' out of range", name));
+
+       part.id = n;
+}
+
+/*
+ * Extract a volume name.
+ */
+void kafs::extract(Context *ctx, Extracted_arg &arg, Volume_spec &vs)
+{
+       char *end;
+
+       if (!arg.seen)
+               return;
+       vs.specified = true;
+
+       if (arg.values[0].size() == 0)
+               throw std::invalid_argument("Empty volume name");
+       if (arg.values[0].size() >= afs::VLDB_MAXNAMELEN)
+               throw std::invalid_argument("Volume name too long");
+
+       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].c_str(), &end, 0);
+               if (*end)
+                       throw std::invalid_argument("Volume ID is not a number");
+
+               vs.name = fmt::format("{:d}", vs.number);
+               vs.is_numeric = true;
+       } else {
+               vs.name = arg.values[0];
+       }
+}
+
+/*
+ * Extract a user name.
+ */
+void kafs::extract(Context *ctx, Extracted_arg &arg, User_spec &user)
+{
+       if (arg.seen) {
+               user.specified = true;
+               user.name = arg.values[0];
+       }
+}
+
+/*
+ * Extract a list of user names.
+ */
+void kafs::extract(Context *ctx, Extracted_arg &arg, std::vector<User_spec> &users)
+{
+       if (arg.seen) {
+               users.resize(arg.values.size());
+               for (size_t i = 0; i < arg.values.size(); i++) {
+                       users[i].specified = true;
+                       users[i].name = arg.values[i];
+               }
+       }
+}
+
+/*
+ * Extract a group name.
+ */
+void kafs::extract(Context *ctx, Extracted_arg &arg, Group_spec &group)
+{
+       if (arg.seen) {
+               group.specified = true;
+               group.name = arg.values[0];
+       }
+}
+
+/*
+ * Extract a list of group names.
+ */
+void kafs::extract(Context *ctx, Extracted_arg &arg, std::vector<Group_spec> &groups)
+{
+       if (arg.seen) {
+               groups.resize(arg.values.size());
+               for (size_t i = 0; i < arg.values.size(); i++) {
+                       groups[i].specified = true;
+                       groups[i].name = arg.values[i];
+               }
+       }
+}
+
+void kafs::extract(Context *ctx, Extracted_arg &arg, std::string &s)
+{
+       if (arg.seen)
+               s = arg.values[0];
+}
+
+void kafs::extract(Context *ctx, Extracted_arg &arg, std::vector<std::string> &sl)
+{
+       if (arg.seen)
+               sl.swap(arg.values);
+}
+
+void kafs::extract(Context *ctx, Extracted_arg &arg, bool &b)
+{
+       if (arg.seen)
+               b = true;
+}
+
+void kafs::extract(Context *ctx, Extracted_arg &arg, Uuid_spec &u)
+{
+       if (arg.seen) {
+               u.specified = true;
+               if (uuid_parse(arg.values[0].c_str(), u.uuid.uuid) < 0)
+                       throw std::invalid_argument("Failed to parse UUID");
+       }
+}
diff --git a/kafs/arg_parse.H b/kafs/arg_parse.H
new file mode 100644 (file)
index 0000000..3c7bb03
--- /dev/null
@@ -0,0 +1,117 @@
+/* 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 "kafs.H"
+
+namespace kafs {
+
+struct Argument;
+struct Command;
+struct Nocombine;
+
+struct Extracted_arg {
+       const Argument                  *arg;
+       std::vector<std::string>        values;
+       bool                            seen;
+       bool                            last;
+
+       Extracted_arg() { arg = NULL; seen = false; last = false; }
+};
+
+extern void extract_kafs_context(Context *, Extracted_arg &);
+extern void extract_kafs_cell(Context *, Extracted_arg &);
+extern void extract(Context *, Extracted_arg &, std::string &);
+extern void extract(Context *, Extracted_arg &, bool &);
+extern void extract(Context *, Extracted_arg &, Volserver_spec &);
+extern void extract(Context *, Extracted_arg &, Vlserver_spec &);
+extern void extract(Context *, Extracted_arg &, Fileserver_spec &);
+extern void extract(Context *, Extracted_arg &, Partition_spec &);
+extern void extract(Context *, Extracted_arg &, Volume_spec &);
+extern void extract(Context *, Extracted_arg &, User_spec &);
+extern void extract(Context *, Extracted_arg &, Group_spec &);
+extern void extract(Context *, Extracted_arg &, Uuid_spec &);
+extern void extract(Context *, Extracted_arg &, std::vector<User_spec> &);
+extern void extract(Context *, Extracted_arg &, std::vector<Group_spec> &);
+extern void extract(Context *, Extracted_arg &, std::vector<std::string> &);
+
+struct Command {
+       const 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 Argument          *args;  /* Argument list terminated with {}. */
+       unsigned int            nr_args;        /* Number of arguments */
+       unsigned char           mink;           /* Minimum size of abbreviation of name */
+       void (*handler)(Context *ctx, char **argv);
+       const Nocombine         *no_combine;    /* Arguments that cannot be combined */
+       const char              *help;          /* Single-line help text */
+       const char              *desc;          /* Multi-line description */
+};
+
+struct Suite {
+       const char              *name;
+       const Command           **commands;
+};
+
+struct 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 */
+       void (*syntax)(Extracted_arg &result);
+};
+
+struct Nocombine {
+       unsigned int    a;
+       unsigned int    b;
+};
+
+class Gave_help : std::exception {
+};
+
+/*
+ * arg_parse.C
+ */
+extern bool debug_completion;
+
+extern void extract_arguments(Context *ctx, char **args,
+                             const Command *command_table,
+                             std::vector<Extracted_arg> &results);
+
+/*
+ * kafs.C
+ */
+extern const Suite *command_suites[];
+
+extern void command_usage(const Command *cmd);
+extern void help(const Suite *suite, std::string &topic=empty_string);
+extern const Command *look_up_command(const Suite *suite, char *command);
+
+/*
+ * arg_completion.C
+ */
+extern void comp_log(const char *fmt, ...);
+extern void bash_complete_command_suite(const char *s);
+extern void bash_complete(unsigned int argc, char **argv, unsigned int completion_argc,
+                         const Suite *suite);
+
+} /* end namespace kafs */
+
+#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..88e6413
--- /dev/null
@@ -0,0 +1,49 @@
+/* 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 <iostream>
+#include "bos.H"
+
+/***
+ * COMMAND: bos help - Get help on commands
+ * ARG: "[-topic <help string>+]"
+ * ARG: "[-admin]"
+ */
+void COMMAND_bos_help(
+       kafs::Context                   *ctx,
+       std::vector<std::string>        &a_topic,
+       bool                            a_admin)
+{
+       if (a_topic.empty())
+               return kafs::help(&gen_bos_suite, kafs::empty_string);
+       for (std::vector<std::string>::iterator i = a_topic.begin(); i != a_topic.end(); i++)
+               kafs::help(&gen_bos_suite, *i);
+}
+
+/***
+ * COMMAND: bos apropos - Search for a command by its help text
+ * ARG: "-topic <help string>"
+ */
+void COMMAND_bos_apropos(
+       kafs::Context           *ctx,
+       std::string             &a_topic)
+{
+       const 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.c_str()) ||
+                   strstr(cmd->help, a_topic.c_str()))
+                       std::cout << cmd->name << ": " << cmd->help << "\n";
+       }
+}
diff --git a/kafs/display.H b/kafs/display.H
new file mode 100644 (file)
index 0000000..26c0fdf
--- /dev/null
@@ -0,0 +1,26 @@
+/* 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"
+
+namespace kafs {
+
+/*
+ * display_error.C
+ */
+extern bool kafs_display_error(Context *);
+
+} /* end namespace kafs */
+
+#endif /* DISPLAY_H */
diff --git a/kafs/display_error.C b/kafs/display_error.C
new file mode 100644 (file)
index 0000000..14e756a
--- /dev/null
@@ -0,0 +1,59 @@
+/* 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 <cstdarg>
+#include <cstdio>
+#include <string>
+#include "kafs.H"
+#include "afs_xg.H"
+
+#if 0
+/*
+ * 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;
+}
+#endif
diff --git a/kafs/fs_help.C b/kafs/fs_help.C
new file mode 100644 (file)
index 0000000..c230830
--- /dev/null
@@ -0,0 +1,48 @@
+/* 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 "fs.H"
+
+/***
+ * COMMAND: fs help - Get help on commands
+ * ARG: "[-topic <help string>+]"
+ * ARG: "[-admin]"
+ */
+void COMMAND_fs_help(
+       kafs::Context                   *ctx,
+       std::vector<std::string>        &a_topic,
+       bool                            a_admin)
+{
+       if (a_topic.empty())
+               return kafs::help(&gen_fs_suite);
+       for (std::vector<std::string>::iterator i = a_topic.begin(); i != a_topic.end(); i++)
+               kafs::help(&gen_fs_suite, *i);
+}
+
+/***
+ * COMMAND: fs apropos - Search for a command by its help text
+ * ARG: "-topic <help string>"
+ */
+void COMMAND_fs_apropos(
+       kafs::Context           *ctx,
+       std::string             &a_topic)
+{
+       const 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.c_str()) ||
+                   strstr(cmd->help, a_topic.c_str()))
+                       printf("%s: %s\n", cmd->name, cmd->help);
+       }
+}
diff --git a/kafs/gen_command.py b/kafs/gen_command.py
new file mode 100755 (executable)
index 0000000..93d529f
--- /dev/null
@@ -0,0 +1,691 @@
+#!/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_decl = None              # Argument C type, including pointer markers
+        self.c_name = None              # Argument name
+        self.c_var_type = None          # Temp variable type
+        self.is_list = False            # If list of objects
+
+        arg_re = re.compile(r'(const)?\s*([_a-zA-Z:0-9]+)(<[_a-zA-Z:0-9]+>)? ([*&]*)([_a-zA-Z0-9]*)')
+        m = arg_re.fullmatch(arg)
+        if not m:
+            raise RuntimeError("Func arg could not be parsed")
+        m_const    = m.group(1)
+        m_type     = m.group(2)
+        m_template = m.group(3)
+        m_ptr      = m.group(4)
+        m_name     = m.group(5)
+
+        if m_const == None:
+            m_const = ""
+        else:
+            m_const += " "
+
+        is_list = False
+        if m_template:
+            c_type = m_template.strip("<>")
+            c_decl = m_const + m_type + m_template
+            c_var  = m_type + m_template
+            if m_type == "std::vector":
+                is_list = True
+        else:
+            c_type = m_type
+            c_decl = m_const + m_type
+            c_var  = m_type
+
+        if m_ptr:
+            c_decl += m_ptr
+        self.c_decl = c_type
+        self.is_list = is_list
+
+        name = m_name
+        if name != "ctx":
+            if not name.startswith("a_"):
+                raise RuntimeError("Function argument should begin with 'a_'")
+            name = name[2:]
+
+        self.c_name = name
+        self.c_var_type = c_var
+
+        if self.c_name == "cell":
+            raise RuntimeError("'cell' function argument should be in ctx")
+
+        if (m_type == "std::string" or
+            m_type == "char" and m_ptr.startswith("*") or
+            m_type == "const char" and m_ptr.startswith("*")):
+            self.syntax = "string"
+        else:
+            (a, space, b) = m_type.partition(" ")
+            if space:
+                self.syntax = b
+            else:
+                self.syntax = a
+
+        #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("void 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 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 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 void gen_handler_", cmd.suite, "_", cmd.name,
+           "(kafs::Context *ctx, char **argv);\n")
+    output("static const kafs::Command gen_cmd_", cmd.suite, "_", cmd.name, " = {\n")
+    output("\t.suite = \"", cmd.suite, "\",\n")
+    output("\t.name = \"", cmd.name, "\",\n")
+    output("\t.args = gen_", cmd.suite, "_", cmd.name, "_arguments,\n")
+    output("\t.nr_args = ", len(cmd.params), ",\n")
+    output("\t.mink = ", cmd.mink, ",\n")
+    output("\t.handler = gen_handler_", cmd.suite, "_", cmd.name, ",\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 kafs::Command gen_cmd_", cmd.suite, "_", cmd.name, " = {\n")
+    output("\t.alias = &gen_cmd_", cmd.suite, "_", cmd.alias, ",\n")
+    output("\t.suite = \"", cmd.suite, "\",\n")
+    output("\t.name = \"", cmd.name, "\",\n")
+    output("\t.args = gen_", cmd.suite, "_", cmd.alias, "_arguments,\n")
+    output("\t.help = \"-- Alias of ", cmd.alias, "\",\n")
+    output("};\n")
+
+def emit_command_handler(cmd):
+    header("extern void ", cmd.func_name, "(\n")
+    header("\tkafs::Context *ctx")
+    for i in cmd.func_args:
+        header(",\n")
+        if (i.c_decl == "bool"):
+            header("\t", i.c_var_type, " a_", i.c_name)
+        else:
+            header("\t", i.c_var_type, " &a_", i.c_name)
+    header(");\n")
+
+    output("static void gen_handler_", cmd.suite, "_", cmd.name,
+           "(kafs::Context *ctx, char **args)\n")
+    output("{\n")
+
+    # Declare variables
+    output("\tstd::vector<kafs::Extracted_arg> results(", len(cmd.params), ");\n")
+    for i in cmd.func_args:
+        if i.c_var_type == "kafs::Context *":
+            pass
+        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, ";\n")
+    output("\n")
+    output("\textract_arguments(ctx, args, &gen_cmd_", cmd.suite, "_", cmd.name, ", results);\n")
+
+    # Write extraction list
+    for p in cmd.params:
+        if p.auth_info:
+            output("\textract_kafs_context             (ctx, results[", p.index, "]);\n")
+    if "cell" in cmd.param_by_name:
+        p = cmd.param_by_name["cell"]
+        fu = "extract_kafs_cell"
+        output("\t", fu.ljust(30), "(ctx, results[", p.index, "]);\n")
+    for p in cmd.params:
+        if not p.auth_info and p.name != "cell":
+            fa = cmd.func_args_by_name[p.name]
+            output("\textract(ctx, results[", p.index, "], a_", p.name, ");\n")
+
+    # Call processor function
+    output("\n")
+    output("\t", cmd.func_name, "(\n")
+    output("\t\tctx")
+    for i in cmd.func_args:
+        output(",\n")
+        if i.c_var_type == "kafs::Context *":
+            output("\t\t&a_", i.c_name)
+        else:
+            output("\t\ta_", i.c_name)
+    output(");\n")
+    output("}\n")
+
+def emit_command_table(suite):
+    names = suite.command_names + suite.alias_names
+    output("\n")
+    output("static const 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 kafs::Suite gen_", suite.name, "_suite;\n")
+
+    output("\n")
+    output("const 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 <cstdlib>\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..c028b55
--- /dev/null
@@ -0,0 +1,421 @@
+/* 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 <iostream>
+#include <cstdarg>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <cctype>
+#include <fmt/core.h>
+extern "C" {
+#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"
+
+using rxrpc::ref;
+
+namespace kafs {
+void dispatch_command(const Suite *suite, char *command, char **args);
+}
+
+bool kafs::debug_caps;
+bool kafs::debug_trace;
+std::string kafs::empty_string("");
+
+static ref<kafs::Context> context;
+
+const kafs::Suite *kafs::command_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);
+}
+
+kafs::Context::Context()
+{
+       cell_db = NULL;
+       no_resolve = false;
+       sec_level = rxrpc::security_unset;
+}
+
+void kafs::open_endpoint(kafs::Context *ctx)
+{
+       char buf[1024];
+
+       rxrpc::find_transport();
+
+       if (!ctx->security) {
+               snprintf(buf, sizeof(buf), "afs@%s", ctx->cell_name.c_str());
+               ctx->security = rxrpc::new_security(buf, ctx->sec_level);
+       }
+
+       if (!ctx->endpoint)
+               ctx->endpoint = rxrpc::new_local_endpoint(NULL, ctx->security);
+}
+
+void kafs::command_usage(const Command *cmd)
+{
+       const 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");
+}
+
+void kafs::help(const Suite *suite, std::string &topic)
+{
+       const Command *cmd;
+       size_t cl;
+       bool ambiguous = false;
+       int i;
+
+       if (topic.empty()) {
+               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;
+       }
+
+       cl = topic.size();
+       for (i = 0; suite->commands[i]; i++) {
+               cmd = suite->commands[i];
+               if (topic == cmd->name)
+                       goto matched_command;
+               if (cl >= strlen(cmd->name))
+                       continue;
+               if (topic.compare(0, cl, cmd->name) == 0) {
+                       if (cl >= cmd->mink)
+                               goto matched_command;
+                       ambiguous = true;
+               }
+       }
+
+       if (ambiguous)
+               fprintf(stderr, "%s: Ambiguous topic '%s'; use 'apropos' to list\n",
+                       suite->name, topic.c_str());
+       else
+               fprintf(stderr, "%s: Unknown topic '%s'\n",
+                       suite->name, topic.c_str());
+       throw std::invalid_argument(fmt::format("{}: Unrecognised or ambiguous command name",
+                                               suite->name));
+
+matched_command:
+       return kafs::command_usage(cmd);
+}
+
+const kafs::Command *kafs::look_up_command(const Suite *suite, char *command)
+{
+       const 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;
+}
+
+void kafs::dispatch_command(const Suite *suite, char *command, char **args)
+{
+       const kafs::Command *cmd;
+
+       _enter("%s", command);
+
+       cmd = kafs::look_up_command(suite, command);
+       if (cmd) {
+               _debug("found %s", cmd->name);
+               cmd->handler(context, args);
+       }
+}
+
+struct kafs_lookup_context kafs::lookup_context = {
+       .report = {
+               .error          = error_report,
+               .verbose        = verbose,
+               .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;
+               else
+                       fprintf(stderr, "Unrecognised debug type '%s'\n", p);
+       }
+}
+
+int main(int argc, char *argv[])
+{
+       using kafs::comp_log;
+       const kafs::Suite *suite;
+       char *prog, *suite_name, **p;
+       int completion_argc = -1;
+       int i;
+
+       for (p = argv; *p; p++)
+               comp_log(" '%s'", *p);
+       comp_log("\n");
+
+       prog = strrchr(argv[0], '/');
+       if (prog)
+               prog++;
+       else
+               prog = argv[0];
+       argc -= 1;
+       argv += 1;
+
+       // We see: kafs --complete <cargc> <realargv[0]> <realargv[1]>...
+       // cargc is 0 at realargv[0]
+       // But --x=y get split into 3 around the '='
+       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;
+                               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];
+                               argc -= 2;
+                               argv += 2;
+                       }
+               } else {
+                       // Bash split the argument into three
+                       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);
+       }
+
+       context = new kafs::Context;
+
+       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 (strcmp(prog, "kafs") == 0 ||
+           strcmp(prog, "afs") == 0) {
+               if (completion_argc == 0)
+                       kafs::bash_complete_command_suite(argv[0] ?: "");
+               if (argc == 0)
+                       goto no_suite;
+
+               suite_name = argv[0];
+               argc--;
+               argv++;
+               completion_argc--;
+       } else {
+               suite_name = prog;
+       }
+
+       for (i = 0; kafs::command_suites[i]; i++) {
+               suite = kafs::command_suites[i];
+               if (strcmp(suite_name, suite->name) == 0)
+                       goto use_suite;
+       }
+       fprintf(stderr, "%s: Unrecognised command suite\n", prog);
+       exit(2);
+
+no_suite:
+       fprintf(stderr, "kafs supports the following command suites:\n");
+       for (i = 0; kafs::command_suites[i]; i++) {
+               suite = kafs::command_suites[i];
+               printf("  %s\n", suite->name);
+       }
+
+       printf("Type 'kafs <suite> help' to get a command list\n");
+       exit(1);
+
+use_suite:
+       comp_log("suite: %s\n", suite->name);
+       if (completion_argc >= 0)
+               kafs::bash_complete(argc, argv, completion_argc, suite);
+
+       if (!*argv) {
+               fprintf(stderr, "%s: Type '%s help' or '%s help <topic>' for help\n",
+                       suite->name, suite->name, suite->name);
+               exit(2);
+       }
+
+       try {
+               dispatch_command(suite, *argv, argv + 1);
+               exit(0);
+       } catch (const kafs::Gave_help &g) {
+               exit(0);
+       } catch (const rxrpc::rx_abort &ab) {
+               std::cerr << ab.what() << " (" << ab.get_abort_code() << ")\n";
+               exit(1);
+       } catch (const rxrpc::network_error &e) {
+               std::cerr << "Network failure: " << e.what() << "\n";
+               exit(1);
+       } catch (const rxrpc::syserror &e) {
+               std::cerr << "Local failure: " << e.what() << "\n";
+               exit(1);
+       } catch (const std::runtime_error &a) {
+               std::cerr << a.what() << "\n";
+               exit(1);
+       } catch (const std::invalid_argument &a) {
+               std::cerr << a.what() << "\n";
+               exit(2);
+       }
+}
diff --git a/kafs/kafs.H b/kafs/kafs.H
new file mode 100644 (file)
index 0000000..0ae8b76
--- /dev/null
@@ -0,0 +1,155 @@
+/* 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 <cstdlib>
+#include <uuid/uuid.h>
+#include <cerrno>
+#include <rxrpc.H>
+extern "C" {
+#include <kafs/cellserv.h>
+}
+
+namespace kafs {
+
+namespace afs {
+constexpr unsigned int FS_PORT         = 7000; /* AFS file server port */
+constexpr unsigned int FS_SERVICE      = 1;    /* AFS File Service ID */
+}
+
+typedef unsigned long long Volume_id;
+
+enum Service {
+       service_afs,
+       service_yfs,
+};
+
+class Context : public rxrpc::refcount {
+public:
+       rxrpc::ref<rxrpc::Endpoint> endpoint;
+       rxrpc::ref<rxrpc::Security> security;
+
+       std::string             cell_name;
+       struct kafs_cell        *cell_db;
+       bool                    no_resolve;     /* Don't resolve addresses for display */
+
+       rxrpc::security_auth_level sec_level;   /* Security level */
+
+       Context();
+       ~Context();
+};
+
+struct Spec {
+       bool                    specified;
+       Spec()                  { specified = false; }
+};
+
+struct User_spec : public Spec {
+       std::string             name;
+};
+
+struct Group_spec : public Spec {
+       std::string             name;
+};
+
+struct Partition_spec : public Spec {
+       unsigned int            id;
+       Partition_spec()        { id = 0; }
+};
+
+struct FVserver_spec : public Spec {
+       std::string             name;
+       rxrpc::Uuid             uuid;
+       unsigned int            nr_addrs;
+       struct sockaddr_rxrpc   *addrs;
+       FVserver_spec()         { nr_addrs = 0; addrs = NULL;  }
+       virtual bool is_volser_spec() const = 0;
+};
+
+struct Fileserver_spec : public FVserver_spec {
+       bool is_volser_spec() const;
+};
+
+struct Volserver_spec : public FVserver_spec {
+       bool is_volser_spec() const;
+};
+
+struct Vlserver_spec : public Spec {
+       std::string             name;
+       struct kafs_server_list *slist;
+
+       Vlserver_spec()         { slist = NULL; }
+       ~Vlserver_spec()        { kafs_free_server_list(slist); }
+};
+
+struct Volume_spec : public Spec {
+       bool                    is_numeric;
+       std::string             name;
+       Volume_id               number;
+       Volume_spec()           { is_numeric = false; number = 0; }
+};
+
+struct Uuid_spec : public Spec {
+       rxrpc::Uuid             uuid;
+};
+
+/*
+ * kafs.C
+ */
+extern std::string empty_string;
+extern struct kafs_lookup_context lookup_context;
+extern void open_endpoint(Context *);
+
+/*
+ * Debugging.
+ */
+extern bool debug_trace;
+extern bool 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
+
+} /* end namespace kafs */
+
+#endif /* KAFS_H */
diff --git a/kafs/pts_help.C b/kafs/pts_help.C
new file mode 100644 (file)
index 0000000..a3e8ef2
--- /dev/null
@@ -0,0 +1,48 @@
+/* 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 "pts.H"
+
+/***
+ * COMMAND: pts help - Get help on commands
+ * ARG: "[-topic <help string>+]"
+ * ARG: "[-admin]"
+ */
+void COMMAND_pts_help(
+       kafs::Context                   *ctx,
+       std::vector<std::string>        &a_topic,
+       bool                            a_admin)
+{
+       if (a_topic.empty())
+               return kafs::help(&gen_pts_suite);
+       for (std::vector<std::string>::iterator i = a_topic.begin(); i != a_topic.end(); i++)
+               kafs::help(&gen_pts_suite, *i);
+}
+
+/***
+ * COMMAND: pts apropos - Search for a command by its help text
+ * ARG: "-topic <help string>"
+ */
+void COMMAND_pts_apropos(
+       kafs::Context           *ctx,
+       std::string             &a_topic)
+{
+       const 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.c_str()) ||
+                   strstr(cmd->help, a_topic.c_str()))
+                       printf("%s: %s\n", cmd->name, cmd->help);
+       }
+}
diff --git a/kafs/vos_help.C b/kafs/vos_help.C
new file mode 100644 (file)
index 0000000..d12cdbc
--- /dev/null
@@ -0,0 +1,48 @@
+/* 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 "vos.H"
+
+/***
+ * COMMAND: vos help - Get help on commands
+ * ARG: "[-topic <help string>+]"
+ * ARG: "[-admin]"
+ */
+void COMMAND_vos_help(
+       kafs::Context                   *ctx,
+       std::vector<std::string>        &a_topic,
+       bool                            a_admin)
+{
+       if (a_topic.empty())
+               return kafs::help(&gen_vos_suite);
+       for (std::vector<std::string>::iterator i = a_topic.begin(); i != a_topic.end(); i++)
+               kafs::help(&gen_vos_suite, *i);
+}
+
+/***
+ * COMMAND: vos apropos - Search for a command by its help text
+ * ARG: "-topic <help string>"
+ */
+void COMMAND_vos_apropos(
+       kafs::Context           *ctx,
+       std::string             &a_topic)
+{
+       const 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.c_str()) ||
+                   strstr(cmd->help, a_topic.c_str()))
+                       printf("%s: %s\n", cmd->name, cmd->help);
+       }
+}