From: David Howells Date: Fri, 11 Oct 2019 14:35:46 +0000 (+0100) Subject: Implement the core of the command suite X-Git-Url: https://www.infradead.org/git/?a=commitdiff_plain;h=6a523bb265a500cac6a8f8eaec8f03793b89fd67;p=users%2Fdhowells%2Fkafs-utils.git Implement the core of the command suite 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 " * ARG: "-switchname +" * 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 --- diff --git a/Makefile b/Makefile index 38fda8f..52be547 100644 --- 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 index 0000000..94f131d --- /dev/null +++ b/kafs/.gitignore @@ -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 index 0000000..aa1ff17 --- /dev/null +++ b/kafs/Makefile @@ -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 index 0000000..1bd2e70 --- /dev/null +++ b/kafs/arg_completion.C @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +extern "C" { +#include +} +#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 complete_cell_name(const char *prefix) +{ + std::vector 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 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 index 0000000..5111584 --- /dev/null +++ b/kafs/arg_parse.C @@ -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 +#include +#include +#include +#include +#include +#include +extern "C" { +#include +} +#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 &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 &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 &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 &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 &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 &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 index 0000000..3c7bb03 --- /dev/null +++ b/kafs/arg_parse.H @@ -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 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 &); +extern void extract(Context *, Extracted_arg &, std::vector &); +extern void extract(Context *, Extracted_arg &, std::vector &); + +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 &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 index 0000000..1da7163 --- /dev/null +++ b/kafs/bash_complete @@ -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 index 0000000..88e6413 --- /dev/null +++ b/kafs/bos_help.C @@ -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 +#include "bos.H" + +/*** + * COMMAND: bos help - Get help on commands + * ARG: "[-topic +]" + * ARG: "[-admin]" + */ +void COMMAND_bos_help( + kafs::Context *ctx, + std::vector &a_topic, + bool a_admin) +{ + if (a_topic.empty()) + return kafs::help(&gen_bos_suite, kafs::empty_string); + for (std::vector::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 " + */ +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 index 0000000..26c0fdf --- /dev/null +++ b/kafs/display.H @@ -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 index 0000000..14e756a --- /dev/null +++ b/kafs/display_error.C @@ -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 +#include +#include +#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 index 0000000..c230830 --- /dev/null +++ b/kafs/fs_help.C @@ -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 +]" + * ARG: "[-admin]" + */ +void COMMAND_fs_help( + kafs::Context *ctx, + std::vector &a_topic, + bool a_admin) +{ + if (a_topic.empty()) + return kafs::help(&gen_fs_suite); + for (std::vector::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 " + */ +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 index 0000000..93d529f --- /dev/null +++ b/kafs/gen_command.py @@ -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 " -- Required flag with value +# ARG: "-name +" -- Required flag with multiple values +# ARG: "[-name ]" -- Optional flag with value +# ARG: "-name " - New -- Required flag with new value (can't tab-expand) +# ARG: "-name " - 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 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 \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} +".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 index 0000000..c028b55 --- /dev/null +++ b/kafs/kafs.C @@ -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 +#include +#include +#include +#include +#include +#include +extern "C" { +#include +} +#include +#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 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 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 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 ' 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 index 0000000..0ae8b76 --- /dev/null +++ b/kafs/kafs.H @@ -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 +#include +#include +#include +extern "C" { +#include +} + +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 endpoint; + rxrpc::ref 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 index 0000000..a3e8ef2 --- /dev/null +++ b/kafs/pts_help.C @@ -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 +]" + * ARG: "[-admin]" + */ +void COMMAND_pts_help( + kafs::Context *ctx, + std::vector &a_topic, + bool a_admin) +{ + if (a_topic.empty()) + return kafs::help(&gen_pts_suite); + for (std::vector::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 " + */ +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 index 0000000..d12cdbc --- /dev/null +++ b/kafs/vos_help.C @@ -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 +]" + * ARG: "[-admin]" + */ +void COMMAND_vos_help( + kafs::Context *ctx, + std::vector &a_topic, + bool a_admin) +{ + if (a_topic.empty()) + return kafs::help(&gen_vos_suite); + for (std::vector::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 " + */ +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); + } +}