2 * lafs - Examine and manipulate and LaFS image
4 * This program is essentially a front-end to liblafs. It allows
5 * a LaFS filesystem to be examined and modified. All interesting
6 * manipulations are function calls in to liblafs.
7 * The program simply parses textual commands converting them into
8 * library calls, and provides a readline interface with autocompletion
9 * and context-sensitive help.
11 * Copyright (C) 2011 NeilBrown <neil@brown.name>
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28 * Email: <neil@brown.name>
33 #include <sys/types.h>
39 #include <readline/readline.h>
40 #include <readline/history.h>
41 #include <lafs/lafs.h>
43 #include <uuid/uuid.h>
46 /* This is the global state which is passed around among
47 * the various commands.
55 /* Every command can have arguments, both positional and
57 * Named arguments are specified with a leading hyphen, though
58 * multiple hyphens may be given in input (completion chooses 2).
59 * Tags for positional arguments must not start with '-', and is used
60 * only for help messages.
61 * Named arguments are normally followed by an '=' and a value. If an argument is
62 * defined as a 'flag' then no '=' is expected.
63 * A tag argument can provide a value to a positional argument, and the
64 * command can easily determine if the tag version was used.
65 * Each tag may only be given once.
66 * A positional argument can be a subcommand which links to a new set
67 * of argument descriptions.
69 * flag: expect --tagname This is either present or not.
70 * opaque: An uninterpreted string, often a number.
71 * choice: On of a defined list of strings.
72 * external: An external filename - completion is possible.
73 * internal: An internal filename - completion might be possible.
74 * subcommand: one of a list of subcommands.
75 * Any unique prefix of a tag is allowed to match.
77 enum argtype { flag, opaque, choice, external, internal, subcommand};
82 union { struct cmd *subcmd; char **options; };
85 #define TERMINAL_ARG {NULL, opaque, 0, {NULL}, NULL}
87 /* When a positional parameter is identified as 'subcommand' it is associated
88 * with a list of 'struct cmd' identifying the possible subcommands.
89 * Any unique prefix of a command is allowed to match.
90 * The initial argument list description allows a single argument which is
91 * a 'subcommand' identifying all the commands that lafs understands.
95 void (*cmd)(struct state *st, void **args);
100 /* Find a command in a list. The word must be an exact match, or
103 static struct cmd *find_cmd(struct cmd *cmds, char *word)
106 int l = strlen(word);
109 for (c = 0; cmds[c].name; c++) {
110 if (strcmp(word, cmds[c].name) == 0)
112 if (strncmp(word, cmds[c].name, l) == 0) {
124 /* Find a tag in an argument list. The given tag (up to the given length)
125 * must be an exact match or a unique prefix.
127 static int find_tag(struct args *args, const char *tag, int len)
132 for (i = 0; args[i].tag; i++)
133 if (args[i].tag[0] == '-') {
134 if (strncmp(tag, args[i].tag+1, len) != 0)
136 if (strlen(args[i].tag+1) == len)
146 /* Find an option in the list, return position.
147 * No prefixing is allowed
149 static int find_option(char **options, char *w)
152 for (i = 0; options[i]; i++)
153 if (strcmp(options[i], w) == 0)
158 /* Return the first word on the line, modifying the line in-place and
159 * updating *line to be ready to get the next word.
161 * Space/tab separates words. ' or " quotes words.
162 * \ protects quotes and spaces.
164 static char *take_word(char **line)
166 char *rv = *line; /* the buffer we will return - if non-empty */
167 char *lp = rv; /* the line we are examining */
168 char *wp = rv; /* the end of the word we are creating. */
169 static char *delim = " \t\n";
172 while (*lp && strchr(delim, *lp) != NULL)
175 while (*lp && (quote || strchr(delim, *lp) == NULL)) {
176 if (quote && *lp == quote) {
193 if (lp[1] == '\'' || lp[1] == '"' || lp[1] == ' ')
208 static int get_int(char *str, int *result)
213 num = strtol(str, &ep, 10);
221 /* Return an array of void* corresponding to the entries in
222 * 'args'. If an arg is present, the array entry is not NULL.
223 * For flags, the array entry will be the full flag.
224 * for --tag=, the array entry will be from after the '='
225 * for positional, the array entry will be the whole arg.
226 * where a tag can fill a positional, the value is placed in both
228 * If an arg is a subcommand, then the 'struct cmd' point is placed
229 * in the return array rather than a string.
230 * The 'args' pointer is updated to the most precise subcommand.
231 * '*offsetp' is set to the offset in the returned array of the
232 * first argument in the new 'args' list.
233 * '*lastp' is set to the last tag in 'args' that was matched.
234 * '*error' is an error message if something when astray.
236 * Named arguments can appear before, after, or among positional
239 static void **parse_line(struct args **argsp, char *line, int *offsetp,
240 int *lastp, char **error)
247 struct args *args = *argsp;
252 for (i = 0; args[i].tag; i++)
254 rv = calloc(i+offset, sizeof(char*));
257 while (!*error && (w = take_word(&line)) != NULL) {
259 /* Find the matching tag. */
265 while (*e && *e != '=')
267 n = n2 = find_tag(args, t, e-t);
269 asprintf(error, "Unrecognised option: %s", w);
274 if (args[n].pos >= 0)
276 if (rv[n+offset] != NULL || rv[n2+offset] != NULL) {
277 asprintf(error, "Duplicate option: %s", w);
282 else if (args[n].type != flag) {
283 w = take_word(&line);
286 "Missing value for --%s", t);
294 /* must be next positional */
296 args[i].tag && args[i].tag[0] != '-';
298 if (rv[i+offset] == NULL)
300 if (args[i].tag == NULL || args[i].tag[0] == '-') {
301 /* No positions left */
302 asprintf(error, "Extra positional parameter: %s", w);
306 /* If this is a subcommand arg then we need to
307 * parse the remaining args in the context of the
308 * given subcommand - if it exists.
310 switch(args[i].type) {
315 o = find_option(args[i].options, w);
317 asprintf(error, "Value %s for %s is not acceptable", w, args[i].tag);
320 c = find_cmd(args[i].subcmd, w);
328 for (i = 0; args[i].tag; i++)
330 rv = realloc(rv, (i+offset) * sizeof(void*));
331 while (size < i + offset) {
336 asprintf(error, "Unrecognised command: %s",w);
346 /* parse and execute the given command line against the given state. */
347 static int execute_line(struct state *st, char *line)
350 struct args *args = lafs_args;
354 arglist = parse_line(&args, line, NULL, NULL, &error);
357 fprintf(stderr, "lafs: %s\n", error);
362 c = (struct cmd*)arglist[0];
369 static char **complete_in_context(const char *word, int start, int end);
371 /* 'interact' is the main interface when used interactively.
372 * It reads lines using 'readline' and executes them.
373 * 'readline' is configured to provide context sensitive completion
376 static void interact(void)
378 struct state st = {0};
379 st.lafs = lafs_alloc();
381 rl_attempted_completion_function = complete_in_context;
382 rl_basic_word_break_characters = " \t\n=";
383 rl_completer_quote_characters = "\"'";
387 char *line = readline("LaFS: ");
396 execute_line(&st, line);
402 /* 'runfile' is the alternate interface when a regular file is
403 * given with commands. It reads and executes commands until
406 static void runfile(FILE *f)
408 struct state st = {0};
409 st.lafs = lafs_alloc();
417 len = getline(&line, &size, f);
421 else if (execute_line(&st, line) < 0)
429 int main(int argc, char *argv[])
432 if (strcmp(argv[1], "-") == 0)
435 FILE *f = fopen(argv[1], "r");
439 fprintf(stderr, "lafs: cannot open %s\n", argv[1]);
447 * Here be routines to provide context sensitive completion and
449 * Completion understands:
450 * - lists of subcommands
451 * - lists of tags for options
452 * - options that expect filenames, whether internal or external.
453 * Before listing possible matches, the description of the expected
457 /* cmd_gen is used to generate a list of matching commands.
458 * 'gen_cmds' must be initialised to point to the list.
460 static struct cmd *gen_cmds;
461 static char *cmd_gen(const char *prefix, int state)
464 int len = strlen(prefix);
467 for ( ; gen_cmds[next].name ; next++)
468 if (strncmp(prefix, gen_cmds[next].name, len) == 0) {
470 return strdup(gen_cmds[next-1].name);
475 /* tag_gen is used to generate a list of expected tags.
476 * 'gen_args' is the relevant argument list.
477 * 'gen_found' is used to determine which tags have already been given,
478 * so they are not offered again.
479 * generated tags always start "--" even if user types "-ta".
481 static struct args *gen_args;
482 static void **gen_found;
483 static char *tag_gen(const char *prefix, int state)
491 while (*prefix == '-')
493 len = strlen(prefix);
495 for ( ; gen_args[next].tag; next++) {
497 if (gen_args[next].tag[0] != '-')
501 if (gen_args[next].pos >= 0 &&
502 gen_found[gen_args[next].pos])
504 if (strncmp(prefix, gen_args[next].tag+1, len) != 0)
507 c = malloc(2 + strlen(gen_args[next].tag+1) + 2);
509 strcpy(c+2, gen_args[next].tag+1);
510 if (gen_args[next].type != flag) {
512 rl_completion_suppress_append = 1;
520 /* choice_gen is used to generate a list of possible values
521 * for a 'choice' field.
522 * 'gen_options' is the options that can go here.
524 static char **gen_options;
525 static char *choice_gen(const char *prefix, int state)
533 len = strlen(prefix);
534 for (; gen_options[next] ; next++) {
535 if (strncmp(prefix, gen_options[next], len) != 0)
538 return strdup(gen_options[next-1]);
544 * This is the brains of the completion handler.
545 * We parse the line-so-far to determine way options have already
546 * been provided, and so what is left to provide.
547 * We then look at the given prefix to determine if a positional or
548 * tag argument is most likely, and provide relevant completions.
550 static char **complete_in_context(const char *prefix, int start, int end)
552 static char *buf = NULL;
553 static int bufsize = 0;
561 char **matches = NULL;
563 while (bufsize < start+1) {
565 buf = realloc(buf, bufsize);
567 memcpy(buf, rl_line_buffer, start);
572 arglist = parse_line(&args, line, &offset, &last, &error);
575 error && strncmp(error, "Missing value for", 17) == 0) {
578 } else if (!(start && rl_line_buffer[start-1] == '='))
582 printf("\n *** %s ***\n", error);
587 if (last >= 0 && (arglist[last+offset] == NULL ||
588 ((char*)arglist[last+offset])[0] == '\0'))
592 for (p = 0; args[p].tag && args[p].tag[0] != '-' ; p++)
593 if (arglist[p+offset] == NULL)
595 if (args[p].tag == NULL || args[p].tag[0] == '-')
598 /* 'p' is the arg we expect here, either first positional arg that
599 * we haven't seen, or tag that we have "--tag=" for. */
601 if (last >= 0 || (p >= 0 && (!*prefix || *prefix != '-'))) {
602 switch(args[p].type) {
604 gen_cmds = args[p].subcmd;
605 matches = rl_completion_matches(prefix, cmd_gen);
608 matches = rl_completion_matches(
609 prefix, rl_filename_completion_function);
612 gen_options = args[p].options;
613 matches = rl_completion_matches(
619 if (rl_completion_type == '?') {
620 printf("\n *** Please give: %s ***", args[p].desc);
626 rl_attempted_completion_over = 1;
629 if (!*prefix || *prefix == '-') {
631 gen_found = arglist + offset;
632 rl_attempted_completion_over = 1;
633 return rl_completion_matches(prefix, tag_gen);
636 printf("\n *** No further positional arguments expected:"
637 " try '-' instead ***\n");
640 rl_attempted_completion_over = 1;
644 /***********************************************************************8
645 * Here are the commands.
646 * Each command X must define
647 * static char help_X = "One line of description for the command";
649 * static struct args args_X[] = { list of arg definitions; TERMINAL_ARG};
650 * static void c_X(struct state *st, void *args) { implement command ;}
651 * and must be listed in lafs_cmds below.
654 /* common helper functions... */
655 static long long parse_size_print(char *arg, char **error, char *func, char *name)
657 long long rv = parse_size(arg, error);
659 printf("%s: %s: %s for %s\n", func, *error, arg, name);
662 static long parse_num_print(char *arg, char **error, char *func, char *name)
665 long rv = strtol(arg, &endp, 0);
666 if (!*arg || *endp) {
667 *error = "Not a valid number";
668 printf("%s: %s: %s for %s\n", func, *error, arg, name);
673 static char help_exit[] = "Exit lafs";
674 static struct args args_exit[] = { TERMINAL_ARG };
675 static void c_exit(struct state *st, void **args)
680 #define help_quit help_exit
681 #define c_quit c_exit
682 #define args_quit args_exit
685 static char help_help[] = "Print help for a command or all commands";
686 static struct args args_help[] = {
687 { "COMMAND", subcommand, -1, {lafs_cmds}, "Command to display help for"},
688 { "-all", flag, -1, {NULL}, "List brief help for all commands"},
692 static void c_help(struct state *st, void **args)
695 struct cmd *cmd = args[1];
697 if (cmd == NULL && args[2] == NULL) {
698 for (c = 0; lafs_cmds[c].name; c++) {
699 printf("%-9s ", lafs_cmds[c].name);
708 printf("%s: %s\n", cmd->name, cmd->help);
709 if (cmd->args[0].tag) {
711 printf(" Usage: %s", cmd->name);
712 for (i=0; cmd->args[i].tag; i++) {
713 struct args *a = cmd->args+i;
714 if (a->tag[0] == '-') {
715 printf(" [--options...]");
718 printf(" %s", a->tag);
721 printf(" Arguments:\n");
722 for (i=0; cmd->args[i].tag; i++) {
723 struct args *a = cmd->args+i;
724 if (a->tag[0] != '-') {
725 printf(" %-15s: %s\n", a->tag, a->desc);
727 printf(" -%-14s: %s\n", a->tag, a->desc);
731 printf("-------------------------------------------\n");
732 for (c=0; lafs_cmds[c].name; c++)
733 printf("%-9s: %s\n", lafs_cmds[c].name, lafs_cmds[c].help);
737 /****** RESET ******/
738 static char help_reset[] = "Forget all fs information, and prepare to start afresh";
739 static struct args args_reset[] = {
740 { "-force", flag, -1, {NULL}, "Reset even if there are unflushed changes"},
743 static void c_reset(struct state *st, void **args)
746 if (st->lafs->blocksize == 0) {
747 printf("reset: Filesystem state is already clear\n");
751 if (args[1] == NULL &&
752 !list_empty(&st->lafs->wc[0].blocks)) {
753 printf("reset: filesystem has unflushed changes. Consider using\n"
754 " \"flush\" command of \"--force\" argument\n");
757 talloc_free(st->lafs);
758 st->lafs = lafs_alloc();
760 printf("Filesystem state has been reset\n");
764 /****** NEWFS ******/
765 static char help_newfs[] = "Create a new LaFS filesystem, which can then be stored on one or more devices.";
766 static char *block_sizes[] = { "512", "1024", "2048", "4096", NULL };
767 static char *state_sizes[] = { "512", "1024", "2048", "4096", "8192",
768 "16384", "32768", NULL };
769 static struct args args_newfs[] = {
770 { "BLOCK-SIZE", choice, -1, {.options=block_sizes},
771 "Block size, 512..4096, defaults to 1024"},
772 { "-block-size", choice, 0, {.options=block_sizes},
773 "Block size, 512..4096, defaults to 1024"},
774 { "-state-size", choice, -1, {.options=state_sizes},
775 "Size of state block, 512..32768, defaults to 1024"},
776 { "-uuid", opaque, -1, {NULL}, "UUID - normally randomly generated"},
777 { "-black", opaque, -1, {NULL}, "nothing (just testing)"},
780 static void c_newfs(struct state *st, void **args)
782 int blockbytes = 1024;
786 struct lafs_ino *ifile, *imfile, *rootdir;
787 int create_atime = 1;
789 if (st->lafs->blocksize) {
790 printf("newfs: Filesytem already has state"
791 " - consider using \"reset\"\n");
796 /* As this is a 'choice' it must be a valid number. */
797 get_int(args[1], &blockbytes);
800 /* state-size was given */
801 get_int(args[3], &state_size);
805 if (uuid_parse((char*)args[4], uu) < 0) {
806 printf("newfs: UUID in wrong format: %s\n", (char*)args[4]);
810 lafs_new(st->lafs, blockbytes);
812 st->lafs->statesize = state_size;
814 memcpy(st->lafs->uuid, uu, 16);
816 ifile = lafs_get_itable(st->lafs);
817 imfile = lafs_add_inode(ifile, 1, TypeInodeMap);
818 rootdir = lafs_add_inode(ifile, 2, TypeDir);
820 lafs_add_inode(ifile, 3, TypeAccessTime);
821 rootdir->md.file.linkcount = 2;
822 rootdir->md.file.mode = 0755;
823 rootdir->md.file.parent = 2;
824 lafs_dirty_inode(rootdir);
825 lafs_add_inode(ifile, 8, TypeOrphanList);
827 lafs_imap_set(imfile, 1);
828 lafs_imap_set(imfile, 2);
829 lafs_imap_set(imfile, 8);
831 lafs_cluster_init(st->lafs, 0, 0, 0, 1);
834 uuid_unparse(st->lafs->uuid, uuidstr);
835 printf("Filesystem has been initilised: block size %d, "
836 "state size %d\n UUID=%s\n",
837 st->lafs->blocksize, st->lafs->statesize,
843 /****** ADD_DEVICE ******/
844 static char help_add_device[] = "Add a device to the current LaFS";
845 static struct args args_add_device[] = {
846 /*1*/ { "DEVNAME", external, -1, {NULL}, "Device to store filesystem on"},
847 /*2*/ { "-file", external, 0, {NULL}, "Regular file to use like a device"},
848 /*3*/ { "-size", opaque, -1, {NULL}, "Size of regular file (K,M,G prefix allowed)"},
849 /*4*/ { "-segsize",opaque, -1, {NULL}, "Segment size for this device"},
850 /*5*/ { "-stride", opaque, -1, {NULL}, "Stride (from one member device to next)"},
851 /*6*/ { "-width", opaque, -1, {NULL}, "Width of array in data-devices"},
852 /*7*/ { "-usage_inum", opaque, -1, {NULL}, "Inode number for segment-usage file"},
855 static void c_add_device(struct state *st, void **args)
857 long block_bytes, segment_bytes = 0, stride_bytes = 0;
859 long long device_bytes = 0;
862 char *devname = args[1];
864 struct lafs_device *dev;
865 struct lafs_ino *ifile, *imfile, *segmap;
868 printf("add_device: No device or file name given to add\n");
872 block_bytes = st->lafs->blocksize;
873 if (block_bytes == 0) {
874 printf("add_device: filesystem is not ready for devices"
875 " - consider \"newfs\".\n");
879 ifile = st->lafs->ss.root;
881 printf("add_device: filesystem has no root inode - strange"
882 " - consider \"newfs\" again.\n");
886 imfile = lafs_get_inode(ifile, 1);
888 printf("add_device: Cannot find inode-map\n");
894 device_bytes = parse_size_print(args[3], &err, "add_device",
900 segment_bytes = parse_size_print(args[4], &err, "add_device",
906 stride_bytes = parse_size_print(args[5], &err, "add_device",
912 width = parse_num_print(args[6], &err, "add_device", "width");
917 usage_inum = parse_num_print(args[7], &err,
918 "add_device", "inode number");
923 fd = open_device(devname, &device_bytes, args[2] != NULL, &err);
926 printf("add_device: %s\n", err);
931 err = lafs_validate_geometry(&block_bytes, &segment_bytes,
932 &stride_bytes, &width, device_bytes);
935 printf("add_device: %s\n", err);
941 printf("FIXME defaulting usage inum to 16 "
942 "- should check if in-use\n");
945 dev = lafs_add_device(st->lafs, devname, fd,
946 segment_bytes / block_bytes,
947 stride_bytes / block_bytes,
951 segmap = lafs_add_inode(ifile, usage_inum, TypeSegmentMap);
952 if (segmap == NULL) {
953 printf("ERROR: could not allocate segusage file.\n");
954 st->lafs->flags |= LAFS_NEED_CHECK;
957 dev->segsum = segmap;
958 segmap->md.segmentusage.table_size = dev->tablesize * usage_inum;
959 dev->tablesize = segmap->md.segmentusage.table_size;
960 lafs_dirty_inode(segmap);
961 lafs_imap_set(imfile, usage_inum);
963 printf("Added device %s at %llu with %llu segments of %llu %dk blocks\n",
964 devname, (unsigned long long)dev->start,
965 (unsigned long long)dev->segment_count,
966 (unsigned long long)dev->segment_size,
967 st->lafs->blocksize/1024);
971 /****** WRITE_DEV ******/
972 static char help_write_dev[] = "Write devices blocks to one or all devices";
973 static struct args args_write_dev[] = {
974 { "DEVNAME", external, -1, {NULL}, "Device to write devblocks to"},
975 { "-all", flag, 0, {NULL}, "Write to all devices"},
978 static void c_write_dev(struct state *st, void **args)
980 struct lafs_device *dev;
982 if (!args[1] && !args[2]) {
983 printf("write dev: no device given for writing\n");
987 for (dev = st->lafs->devs; dev; dev = dev->next) {
988 if (args[2] || strcmp(dev->name, (char*)args[1]) == 0) {
989 int err = lafs_write_dev(dev);
992 printf("write dev: error when writing to %s\n",
994 else if (st->verbose)
995 printf("Device block written to %s\n", dev->name);
1001 printf("write dev: no devices exist to write to.\n");
1003 printf("write dev: %s is not a registered device in this LaFS.\n",
1007 /****** WRITE_STATE ******/
1008 static char help_write_state[] = "Write state blocks to all devices";
1009 static struct args args_write_state[] = {
1012 static void c_write_state(struct state *st, void **args)
1014 if (st->lafs->blocksize == 0)
1015 printf("write state: filesystem is not initialised\n");
1016 else if (st->lafs->devs == NULL)
1017 printf("write state: No devices exist to write to\n");
1018 else if (lafs_write_state(st->lafs))
1019 printf("write state: Error writing a state block\n");
1020 else if (st->verbose)
1021 printf("%s state blocks written: seq now %llu\n",
1022 (st->lafs->seq & 1) ? "Odd" : "Even",
1023 (unsigned long long) st->lafs->seq);
1026 /****** WRITE_CHECKPOINT ******/
1027 static char help_write_checkpoint[] = "Write a checkpoint with all committed blocks";
1028 static struct args args_write_checkpoint[] = {
1031 static void c_write_checkpoint(struct state *st, void **args)
1033 if (st->lafs->blocksize == 0)
1034 printf("write checkpoint: filesystem is not initialised\n");
1035 else if (st->lafs->devs == NULL)
1036 printf("write checkpoint: No devices exist to write to\n");
1037 else if (st->lafs->free_head == st->lafs->free_tail)
1038 printf("write checkpoint: No free segments - try find_free\n");
1039 else if (lafs_checkpoint(st->lafs))
1040 printf("write state: Error writing checkpoint\n");
1041 else if (st->verbose)
1042 printf("Checkpoint written: seq now %llu\n",
1043 (unsigned long long) st->lafs->seq);
1046 /****** LOAD_DEV ******/
1047 static char help_load_dev[] = "Allow access to LaFS filesystem stored on given device";
1048 static struct args args_load_dev[] = {
1049 { "DEVNAME", external, -1, {NULL}, "Device to load filesystem info from"},
1050 { "-file", external, 0, {NULL}, "Regular file to load filesystem info from"},
1053 static void c_load_dev(struct state *st, void **args)
1055 char *devname = args[1];
1057 long long device_bytes = 0;
1059 struct lafs_device *dev;
1062 printf("load_dev: No device of file name given to load\n");
1066 fd = open_device(devname, &device_bytes, args[2] != NULL, &err);
1069 printf("load_dev: %s\n", err);
1074 dev = lafs_load(fd, device_bytes, &err);
1077 printf("load_dev: Cannot load %s: %s\n", devname, err);
1084 if (lafs_include_dev(st->lafs, dev, &err) != 0) {
1085 printf("load_dev: Cannot include %s: %s\n", devname, err);
1090 printf("loaded device %s - have %d of %d\n", devname,
1091 st->lafs->loaded_devs, st->lafs->devices);
1095 /****** STORE ******/
1096 static char help_store[] = "Create a file in the LaFS from an external file";
1097 static struct args args_store[] = {
1098 { "FROM", external, -1, {NULL}, "File to copy into LaFS"},
1099 { "TO", internal, -1, {NULL}, "Where to store file in LaFS"},
1100 { "-from", external, 0, {NULL}, "File to copy into LaFS"},
1103 static void c_store(struct state *st, void **args)
1105 char *from = args[1];
1108 printf("ERROR: Source file is missing\n");
1110 printf("ERROR: destination file name is missing\n");
1112 printf("Oh how I wish I could copy %s to %s\n", from, to);
1115 /* list of all commands - preferably in alphabetical order */
1116 #define CMD(x) {#x, c_##x, args_##x, help_##x}
1117 static struct cmd lafs_cmds[] = {
1118 {"?", c_help, args_help, help_help},
1127 CMD(write_checkpoint),
1130 { NULL, NULL, NULL, NULL}
1133 static struct args lafs_args[] = {
1134 { "COMMAND", subcommand, -1, {lafs_cmds}, "Command for lafs to execute"},