From 3731ea9dced83da58c9fc35ac264476365618de9 Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Sat, 12 Mar 2011 18:58:32 +1100 Subject: [PATCH] Add new "lafs" program "lafs" is similar to "debugfs" for ext[234]. It allows a LaFS to be examined and modified. Various commands can be enterred, or read from a file. This preliminary check-in only provides the infrastructure for reading and parsing commands together with support for context sensitive completion and help using readline. Commands implemented are: ? help exit quit Naturally more will follow. Signed-off-by: NeilBrown --- .gitignore | 1 + tools/Makefile | 6 +- tools/lafs.c | 684 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 689 insertions(+), 2 deletions(-) create mode 100644 tools/lafs.c diff --git a/.gitignore b/.gitignore index bffc514..0d064a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.o *.a mkfs.lafs +lafs core diff --git a/tools/Makefile b/tools/Makefile index c54b1e8..592289a 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -24,8 +24,10 @@ CPPFLAGS = -I../include CFLAGS = -Wall -Werror -g LDFLAGS = -L../lib -LDLIBS = -llafs -ltalloc +LDLIBS = -llafs -ltalloc -lreadline -all : mkfs.lafs +all : mkfs.lafs lafs mkfs.lafs : mkfs.lafs.o ../lib/liblafs.a + +lafs : lafs.o ../lib/liblafs.a diff --git a/tools/lafs.c b/tools/lafs.c new file mode 100644 index 0000000..af8e00b --- /dev/null +++ b/tools/lafs.c @@ -0,0 +1,684 @@ +/* + * lafs - Examine and manipulate and LaFS image + * + * This program is essentially a front-end to liblafs. It allows + * a LaFS filesystem to be examined and modified. All interesting + * manipulations are function calls in to liblafs. + * The program simply parses textual commands converting them into + * library calls, and provides a readline interface with autocompletion + * and context-sensitive help. + * + * Copyright (C) 2011 NeilBrown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Neil Brown + * Email: + * + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* This is the global state which is passed around among + * the various commands. + */ +struct state { + struct lafs *lafs; + int done; +}; + +/* Every command can have arguments, both positional and + * named. + * Named arguments are specified with a leading hyphen, though + * multiple hyphens may be given in input (completion chooses 2). + * Tags for positional arguments must not start with '-', and is used + * only for help messages. + * Named arguments are normally followed by an '=' and a value. If an argument is + * defined as a 'flag' then no '=' is expected. + * A tag argument can provide a value to a positional argument, and the + * command can easily determine if the tag version was used. + * Each tag may only be given once. + * A positional argument can be a subcommand which links to a new set + * of argument descriptions. + * types are: + * flag: expect --tagname This is either present or not. + * opaque: An uninterpreted string, often a number. + * external: An external filename - completion is possible. + * internal: An internal filename - completion might be possible. + * subcommand: one of a list of subcommands. + * Any unique prefix of a tag is allowed to match. + */ +enum argtype { flag, opaque, external, internal, subcommand, terminal }; +static struct args { + char *tag; + enum argtype type; + int pos; + struct cmd *subcmd; + char *desc; +} lafs_args[]; +#define TERMINAL_ARG {NULL, terminal, 0, NULL, NULL} + +/* When a positional parameter is identified as 'subcommand' it is associated + * with a list of 'struct cmd' identifying the possible subcommands. + * Any unique prefix of a command is allowed to match. + * The initial argument list description allows a single argument which is + * a 'subcommand' identifying all the commands that lafs understands. + */ +static struct cmd { + char *name; + void (*cmd)(struct state *st, void **args); + struct args *args; + char *help; +} lafs_cmds[]; + +/* Find a command in a list. The word must be an exact match, or + * a unique prefix. + */ +static struct cmd *find_cmd(struct cmd *cmds, char *word) +{ + int c; + int l = strlen(word); + int best = -1; + + for (c = 0; cmds[c].name; c++) { + if (strcmp(word, cmds[c].name) == 0) + return cmds+c; + if (strncmp(word, cmds[c].name, l) == 0) { + if (best == -1) + best = c; + else + best = -2; + } + } + if (best < 0) + return NULL; + return cmds + best; +} + +/* Find a tag in an argument list. The given tag (up to the given length) + * must be an exact match or a unique prefix. + */ +static int find_tag(struct args *args, const char *tag, int len) +{ + int i; + int best = -1; + + for (i = 0; args[i].type != terminal; i++) + if (args[i].tag[0] == '-') { + if (strncmp(tag, args[i].tag+1, len) != 0) + continue; + if (strlen(args[i].tag+1) == len) + return i; + if (best == -1) + best = i; + else + best = -2; + } + return best; +} + + +/* Return the first word on the line, modifying the line in-place and + * updating *line to be ready to get the next word. + * + * Space/tab separates words. ' or " quotes words. + * \ protects quotes and spaces. + */ +static char *take_word(char **line) +{ + char *rv = *line; /* the buffer we will return - if non-empty */ + char *lp = rv; /* the line we are examining */ + char *wp = rv; /* the end of the word we are creating. */ + static char *delim = " \t\n"; + char quote = '\0'; + + while (*lp && strchr(delim, *lp) != NULL) + lp++; + + while (*lp && (quote || strchr(delim, *lp) == NULL)) { + if (quote && *lp == quote) { + lp++; + continue; + } + switch(*lp) { + case '\'': + case '"': + if (quote == *lp) { + quote = '\0'; + continue; + } + if (!quote) { + quote = *lp++; + continue; + } + break; + case '\\': + if (lp[1] == '\'' || lp[1] == '"' || lp[1] == ' ') + lp++; + break; + } + *wp++ = *lp++; + } + if (*lp) + lp++; + *line = lp; + *wp = '\0'; + if (wp > rv) + return rv; + return NULL; +} + +/* Return an array of void* corresponding to the entries in + * 'args'. If an arg is present, the array entry is not NULL. + * For flags, the array entry will be the full flag. + * for --tag=, the array entry will be from after the '=' + * for positional, the array entry will be the whole arg. + * where a tag can fill a positional, the value is placed in both + * slots. + * If an arg is a subcommand, then the 'struct cmd' point is placed + * in the return array rather than a string. + * The 'args' pointer is updated to the most precise subcommand. + * '*offsetp' is set to the offset in the returned array of the + * first argument in the new 'args' list. + * '*lastp' is set to the last tag in 'args' that was matched. + * '*error' is an error message if something when astray. + * + * Named arguments can appear before, after, or among positional + * arguments. + */ +static void **parse_line(struct args **argsp, char *line, int *offsetp, + int *lastp, char **error) +{ + void **rv; + int i; + char *w; + int offset = 0; + int size; + struct args *args = *argsp; + + if (lastp) + *lastp = -1; + + for (i = 0; args[i].type != terminal; i++) + ; + rv = calloc(i+offset, sizeof(char*)); + size = i+offset; + + while ((w = take_word(&line)) != NULL) { + if (*w == '-') { + /* Find the matching tag. */ + char *t = w, *e; + int n, n2; + while (*t == '-') + t++; + e = t; + while (*e && *e != '=') + e++; + n = n2 = find_tag(args, t, e-t); + if (n < 0) { + asprintf(error, "Unrecognised option: %s", w); + break; + } + if (lastp) + *lastp = n; + if (args[n].pos >= 0) + n2 = args[n].pos; + if (rv[n+offset] != NULL || rv[n2+offset] != NULL) { + asprintf(error, "Duplicate option: %s", w); + break; + } else { + if (*e == '=') + w = e+1; + rv[n+offset] = w; + rv[n2+offset] = w; + } + } else { + /* must be next positional */ + for (i=0; + args[i].tag[0] != '-' && args[i].type != terminal; + i++) + if (rv[i+offset] == NULL) + break; + if (args[i].tag[0] == '-' || args[i].type == terminal) { + /* No positions left */ + asprintf(error, "Extra positional parameter: %s", w); + break; + } + rv[i+offset] = w; + /* If this is a subcommand arg then we need to + * parse the remaining args in the context of the + * given subcommand - if it exists. + */ + if (args[i].type == subcommand) { + struct cmd *c = find_cmd(args[i].subcmd, w); + rv[i+offset] = c; + if (c) { + args = c->args; + *argsp = args; + offset += i+1; + if (lastp) + *lastp = -1; + for (i = 0; args[i].type != terminal; i++) + ; + rv = realloc(rv, (i+offset) * sizeof(void*)); + while (size < i + offset) { + rv[size] = NULL; + size++; + } + } else + asprintf(error, "Unrecognised command: %s",w); + } + } + } + if (offsetp) + *offsetp = offset; + return rv; +} + +/* parse and execute the given command line against the given state. */ +static int execute_line(struct state *st, char *line) +{ + struct cmd *c; + struct args *args = lafs_args; + void **arglist; + char *error = NULL; + + arglist = parse_line(&args, line, NULL, NULL, &error); + + if (error) { + fprintf(stderr, "lafs: %s\n", error); + free(error); + free(arglist); + return -1; + } + c = (struct cmd*)arglist[0]; + + c->cmd(st, arglist); + free(arglist); + return 1; +} + +static char **complete_in_context(const char *word, int start, int end); + +/* 'interact' is the main interface when used interactively. + * It reads lines using 'readline' and executes them. + * 'readline' is configured to provide context sensitive completion + * and help. + */ +static void interact(void) +{ + struct state st = {0}; + st.lafs = lafs_alloc(); + rl_attempted_completion_function = complete_in_context; + rl_basic_word_break_characters = " \t\n="; + rl_completer_quote_characters = "\"'"; + rl_initialize(); + + while (!st.done) { + char *line = readline("LaFS: "); + + if (!line) { + printf("\n"); + break; + } + + if (*line) + add_history(line); + execute_line(&st, line); + + free(line); + } +} + +/* 'runfile' is the alternate interface when a regular file is + * given with commands. It reads and executes commands until + * it finds an error. + */ +static void runfile(FILE *f) +{ + struct state st = {0}; + st.lafs = lafs_alloc(); + + while (!st.done) { + char *line = NULL; + size_t len, size; + + len = getline(&line, &size, f); + + if (execute_line(&st, line) < 0) + st.done = 1; + + free(line); + } +} + + +int main(int argc, char *argv[]) +{ + if (argc > 1) { + if (strcmp(argv[1], "-") == 0) + runfile(stdin); + else { + FILE *f = fopen(argv[1], "r"); + if (f) + runfile(f); + else + fprintf(stderr, "lafs: cannot open %s\n", argv[1]); + } + } else + interact(); + exit(0); +} + +/* + * Here be routines to provide context sensitive completion and + * help. + * Completion understands: + * - lists of subcommands + * - lists of tags for options + * - options that expect filenames, whether internal or external. + * Before listing possible matches, the description of the expected + * option is given. + */ + +/* cmd_gen is used to generate a list of matching commands. + * 'gen_cmds' must be initialised to point to the list. + */ +static struct cmd *gen_cmds; +static char *cmd_gen(const char *prefix, int state) +{ + static int next; + int len = strlen(prefix); + if (state == 0) + next = 0; + for ( ; gen_cmds[next].name ; next++) + if (strncmp(prefix, gen_cmds[next].name, len) == 0) { + next++; + return strdup(gen_cmds[next-1].name); + } + return NULL; +} + +/* tag_gen is used to generate a list of expected tags. + * 'gen_args' is the relevant argument list. + * 'gen_found' is used to determine which tags have already been given, + * so they are not offered again. + * generated tags always start "--" even if user types "-ta". + */ +static struct args *gen_args; +static void **gen_found; +static char *tag_gen(const char *prefix, int state) +{ + static int next; + int len; + + if (state == 0) + next = 0; + + while (*prefix == '-') + prefix++; + len = strlen(prefix); + + for ( ; gen_args[next].type != terminal; next++) { + char *c; + if (gen_args[next].tag[0] != '-') + continue; + if (gen_found[next]) + continue; + if (gen_args[next].pos >= 0 && + gen_found[gen_args[next].pos]) + continue; + if (strncmp(prefix, gen_args[next].tag+1, len) != 0) + continue; + + c = malloc(2 + strlen(gen_args[next].tag+1) + 2); + strcpy(c, "--"); + strcpy(c+2, gen_args[next].tag+1); + if (gen_args[next].type != flag) { + strcat(c, "="); + rl_completion_suppress_append = 1; + } + next++; + return c; + } + return NULL; +} + +/* + * This is the brains of the completion handler. + * We parse the line-so-far to determine way options have already + * been provided, and so what is left to provide. + * We then look at the given prefix to determine if a positional or + * tag argument is most likely, and provide relevant completions. + */ +static char **complete_in_context(const char *prefix, int start, int end) +{ + static char *buf = NULL; + static int bufsize = 0; + + char *line; + void **arglist; + struct args *args; + int offset, last; + int p; + char *error = NULL; + char **matches = NULL; + + while (bufsize < start+1) { + bufsize += 80; + buf = realloc(buf, bufsize); + } + memcpy(buf, rl_line_buffer, start); + buf[start] = 0; + line = buf; + + args = lafs_args; + arglist = parse_line(&args, line, &offset, &last, &error); + + if (error) { + printf("\n *** %s ***\n", error); + free(error); + goto after_message; + } + + if (start && rl_line_buffer[start-1] == '=' && + last >= 0 && arglist[last+offset] && + ((char*)arglist[last+offset])[0] == '\0') + p = last; + else { + last = -1; + for (p = 0; args[p].tag[0] != '-' && args[p].pos != terminal; p++) + if (arglist[p+offset] == NULL) + break; + if (args[p].tag[0] == '-' || args[p].pos == terminal) + p = -1; + } + /* 'p' is the arg we expect here, either first positional arg that + * we haven't seen, or tag that we have "--tag=" for. */ + + if (last >= 0 || (p >= 0 && (!*prefix || *prefix != '-'))) { + switch(args[p].type) { + case subcommand: + gen_cmds = args[p].subcmd; + matches = rl_completion_matches(prefix, cmd_gen); + break; + case external: + matches = rl_completion_matches( + prefix, rl_filename_completion_function); + break; + default: + break; + } + if (rl_completion_type == '?') { + printf("\n *** Please give: %s ***", args[p].desc); + if (!matches) { + printf("\n"); + rl_on_new_line(); + } + } + rl_attempted_completion_over = 1; + return matches; + } + if (!*prefix || *prefix == '-') { + gen_args = args; + gen_found = arglist + offset; + rl_attempted_completion_over = 1; + return rl_completion_matches(prefix, tag_gen); + } + + printf("\n *** No further positional arguments expected:" + " try '-' instead ***\n"); +after_message: + rl_on_new_line(); + rl_attempted_completion_over = 1; + return NULL; +} + +/***********************************************************************8 + * Here are the commands. + * Each command X must define + * static char help_X = "One line of description for the command"; + * static struct args args_X[] = { list of arg definitions; TERMINAL_ARG}; + * static void c_X(struct state *st, void *args) { implement command ;} + * and must be listed in lafs_cmds below. + */ + +/****** EXIT ******/ +static char help_exit[] = "Exit lafs"; +static struct args args_exit[] = { TERMINAL_ARG }; +static void c_exit(struct state *st, void **args) +{ + st->done = 1; +} +/****** QUIT ******/ +#define help_quit help_exit +#define c_quit c_exit +#define args_quit args_exit + +/****** HELP ******/ +static char help_help[] = "Print help for a command or all commands"; +static struct args args_help[] = { + { "COMMAND", subcommand, -1, lafs_cmds, "Command to display help for"}, + { "-all", flag, -1, NULL, "List brief help for all commands"}, + TERMINAL_ARG +}; + +static void c_help(struct state *st, void **args) +{ + int c; + struct cmd *cmd = args[1]; + + if (cmd == NULL && args[2] == NULL) { + for (c = 0; lafs_cmds[c].name; c++) { + printf("%-9s ", lafs_cmds[c].name); + if ((c%8)==7) + printf("\n"); + } + printf("\n"); + return; + } + + if (cmd) { + printf("%s: %s\n", cmd->name, cmd->help); + if (cmd->args[0].type != terminal) { + int i; + printf(" Usage: %s", cmd->name); + for (i=0; cmd->args[i].type != terminal; i++) { + struct args *a = cmd->args+i; + if (a->tag[0] == '-') { + printf(" [--options...]"); + break; + } + printf(" %s", a->tag); + } + printf("\n"); + printf(" Arguments:\n"); + for (i=0; cmd->args[i].type != terminal; i++) { + struct args *a = cmd->args+i; + if (a->tag[0] != '-') { + printf(" %-15s: %s\n", a->tag, a->desc); + } else + printf(" -%-14s: %s\n", a->tag, a->desc); + } + } + } else { + printf("-------------------------------------------\n"); + for (c=0; lafs_cmds[c].name; c++) + printf("%-9s: %s\n", lafs_cmds[c].name, lafs_cmds[c].help); + } +} + +/****** NEWFS ******/ +static char help_newfs[] = "Create a new LaFS filesystem, which can then be stored on one or more devices."; +static struct args args_newfs[] = { + { "BLOCK-SIZE", opaque, -1, NULL, "Block size, 512..4096, defaults to 1024"}, + { "-block-size", opaque, 0, NULL, "Block size, 512..4096, defaults to 1024"}, + { "-state-size", opaque, -1, NULL, "Size of state block, defaults to 1024"}, + { "-uuid", opaque, -1, NULL, "UUID - normally randomly generated"}, + { "-black", opaque, -1, NULL, "nothing (just testing)"}, + TERMINAL_ARG +}; +static void c_newfs(struct state *st, void **args) +{ + /* FIXME */ + return; +} + +/****** STORE ******/ +static char help_store[] = "Create a file in the LaFS from an external file"; +static struct args args_store[] = { + { "FROM", external, -1, NULL, "File to copy into LaFS"}, + { "TO", internal, -1, NULL, "Where to store file in LaFS"}, + { "-from", external, 0, NULL, "File to copy into LaFS"}, + TERMINAL_ARG +}; +static void c_store(struct state *st, void **args) +{ + char *from = args[1]; + char *to = args[2]; + if (!from) + printf("ERROR: Source file is missing\n"); + else if (!to) + printf("ERROR: destination file name is missing\n"); + else + printf("Oh how I wish I could copy %s to %s\n", from, to); +} + +/* list of all commands - preferably in alphabetical order */ +#define CMD(x) {#x, c_##x, args_##x, help_##x} +static struct cmd lafs_cmds[] = { + {"?", c_help, args_help, help_help}, + CMD(exit), + CMD(help), + CMD(newfs), + CMD(quit), + CMD(store), + { NULL, NULL, NULL, NULL} +}; + +static struct args lafs_args[] = { + { "COMMAND", subcommand, -1, lafs_cmds, "Command for lafs to execute"}, + TERMINAL_ARG +}; -- 2.39.5