--- /dev/null
+/*
+ * 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 <neil@brown.name>
+ *
+ * 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: <neil@brown.name>
+ *
+ */
+
+#define _GNU_SOURCE
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <readline/readline.h>
+#include <readline/history.h>
+#include <lafs/lafs.h>
+#include <talloc.h>
+
+/* 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
+};