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