* Boston, MA 021110-1307, USA.
*/
-
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <getopt.h>
-#include "btrfs_cmds.h"
-#include "version.h"
-
-typedef int (*CommandFunction)(int argc, char **argv);
-
-struct Command {
- CommandFunction func; /* function which implements the command */
- int nargs; /* if == 999, any number of arguments
- if >= 0, number of arguments,
- if < 0, _minimum_ number of arguments */
- char *verb; /* verb */
- char *help; /* help lines; form the 2nd onward they are
- indented */
+#include "volumes.h"
+#include "crc32c.h"
+#include "commands.h"
+#include "utils.h"
+#include "help.h"
- /* the following fields are run-time filled by the program */
- char **cmds; /* array of subcommands */
- int ncmds; /* number of subcommand */
+static const char * const btrfs_cmd_group_usage[] = {
+ "btrfs [--help] [--version] <group> [<group>...] <command> [<args>]",
+ NULL
};
-static struct Command commands[] = {
+static const char btrfs_cmd_group_info[] =
+ "Use --help as an argument for information on a specific group or command.";
- /*
- avoid short commands different for the case only
- */
- { do_clone, 2,
- "subvolume snapshot", "<source> [<dest>/]<name>\n"
- "Create a writable snapshot of the subvolume <source> with\n"
- "the name <name> in the <dest> directory."
- },
- { do_delete_subvolume, 1,
- "subvolume delete", "<subvolume>\n"
- "Delete the subvolume <subvolume>."
- },
- { do_create_subvol, 1,
- "subvolume create", "[<dest>/]<name>\n"
- "Create a subvolume in <dest> (or the current directory if\n"
- "not passed)."
- },
- { do_subvol_list, 1, "subvolume list", "<path>\n"
- "List the snapshot/subvolume of a filesystem."
- },
- { do_defrag, -1,
- "filesystem defragment", "[-vcf] [-s start] [-l len] [-t size] <file>|<dir> [<file>|<dir>...]\n"
- "Defragment a file or a directory."
- },
- { do_set_default_subvol, 2,
- "subvolume set-default", "<id> <path>\n"
- "Set the subvolume of the filesystem <path> which will be mounted\n"
- "as default."
- },
- { do_fssync, 1,
- "filesystem sync", "<path>\n"
- "Force a sync on the filesystem <path>."
- },
- { do_resize, 2,
- "filesystem resize", "[+/-]<newsize>[gkm]|max <filesystem>\n"
- "Resize the file system. If 'max' is passed, the filesystem\n"
- "will occupe all available space on the device."
- },
- { do_show_filesystem, 999,
- "filesystem show", "[<uuid>|<label>]\n"
- "Show the info of a btrfs filesystem. If no <uuid> or <label>\n"
- "is passed, info of all the btrfs filesystem are shown."
- },
- { do_balance, 1,
- "filesystem balance", "<path>\n"
- "Balance the chunks across the device."
- },
- { do_scan,
- 999, "device scan", "[<device> [<device>..]\n"
- "Scan all device for or the passed device for a btrfs\n"
- "filesystem."
- },
- { do_add_volume, -2,
- "device add", "<dev> [<dev>..] <path>\n"
- "Add a device to a filesystem."
- },
- { do_remove_volume, -2,
- "device delete", "<dev> [<dev>..] <path>\n"
- "Remove a device from a filesystem."
- },
- /* coming soon
- { 2, "filesystem label", "<label> <path>\n"
- "Set the label of a filesystem"
- }
- */
- { 0, 0 , 0 }
-};
-
-static char *get_prgname(char *programname)
+static inline const char *skip_prefix(const char *str, const char *prefix)
{
- char *np;
- np = strrchr(programname,'/');
- if(!np)
- np = programname;
- else
- np++;
-
- return np;
+ size_t len = strlen(prefix);
+ return strncmp(str, prefix, len) ? NULL : str + len;
}
-static void print_help(char *programname, struct Command *cmd)
+static int parse_one_token(const char *arg, const struct cmd_group *grp,
+ const struct cmd_struct **cmd_ret)
{
- char *pc;
-
- printf("\t%s %s ", programname, cmd->verb );
+ const struct cmd_struct *cmd = grp->commands;
+ const struct cmd_struct *abbrev_cmd = NULL, *ambiguous_cmd = NULL;
+
+ for (; cmd->token; cmd++) {
+ const char *rest;
+
+ rest = skip_prefix(arg, cmd->token);
+ if (!rest) {
+ if (!prefixcmp(cmd->token, arg)) {
+ if (abbrev_cmd) {
+ /*
+ * If this is abbreviated, it is
+ * ambiguous. So when there is no
+ * exact match later, we need to
+ * error out.
+ */
+ ambiguous_cmd = abbrev_cmd;
+ }
+ abbrev_cmd = cmd;
+ }
+ continue;
+ }
+ if (*rest)
+ continue;
- for(pc = cmd->help; *pc; pc++){
- putchar(*pc);
- if(*pc == '\n')
- printf("\t\t");
+ *cmd_ret = cmd;
+ return 0;
}
- putchar('\n');
-}
-static void help(char *np)
-{
- struct Command *cp;
+ if (ambiguous_cmd)
+ return -2;
- printf("Usage:\n");
- for( cp = commands; cp->verb; cp++ )
- print_help(np, cp);
+ if (abbrev_cmd) {
+ *cmd_ret = abbrev_cmd;
+ return 0;
+ }
- printf("\n\t%s help|--help|-h\n\t\tShow the help.\n",np);
- printf("\n%s\n", BTRFS_BUILD_VERSION);
+ return -1;
}
-static int split_command(char *cmd, char ***commands)
+static const struct cmd_struct *
+parse_command_token(const char *arg, const struct cmd_group *grp)
{
- int c, l;
- char *p, *s;
-
- for( *commands = 0, l = c = 0, p = s = cmd ; ; p++, l++ ){
- if ( *p && *p != ' ' )
- continue;
+ const struct cmd_struct *cmd = NULL;
- /* c + 2 so that we have room for the null */
- (*commands) = realloc( (*commands), sizeof(char *)*(c + 2));
- (*commands)[c] = strndup(s, l);
- c++;
- l = 0;
- s = p+1;
- if( !*p ) break;
+ switch(parse_one_token(arg, grp, &cmd)) {
+ case -1:
+ help_unknown_token(arg, grp);
+ case -2:
+ help_ambiguous_token(arg, grp);
}
- (*commands)[c] = 0;
- return c;
+ return cmd;
}
-/*
- This function checks if the passed command is ambiguous
-*/
-static int check_ambiguity(struct Command *cmd, char **argv){
- int i;
- struct Command *cp;
- /* check for ambiguity */
- for( i = 0 ; i < cmd->ncmds ; i++ ){
- int match;
- for( match = 0, cp = commands; cp->verb; cp++ ){
- int j, skip;
- char *s1, *s2;
-
- if( cp->ncmds < i )
- continue;
-
- for( skip = 0, j = 0 ; j < i ; j++ )
- if( strcmp(cmd->cmds[j], cp->cmds[j])){
- skip=1;
- break;
- }
- if(skip)
- continue;
-
- if( !strcmp(cmd->cmds[i], cp->cmds[i]))
- continue;
- for(s2 = cp->cmds[i], s1 = argv[i+1];
- *s1 == *s2 && *s1; s1++, s2++ ) ;
- if( !*s1 )
- match++;
- }
- if(match){
- int j;
- fprintf(stderr, "ERROR: in command '");
- for( j = 0 ; j <= i ; j++ )
- fprintf(stderr, "%s%s",j?" ":"", argv[j+1]);
- fprintf(stderr, "', '%s' is ambiguous\n",argv[j]);
- return -2;
+static void handle_help_options_next_level(const struct cmd_struct *cmd,
+ int argc, char **argv)
+{
+ if (argc < 2)
+ return;
+
+ if (!strcmp(argv[1], "--help")) {
+ if (cmd->next) {
+ argc--;
+ argv++;
+ help_command_group(cmd->next, argc, argv);
+ } else {
+ usage_command(cmd, 1, 0);
}
+
+ exit(0);
}
- return 0;
}
-/*
- * This function, compacts the program name and the command in the first
- * element of the '*av' array
- */
-static int prepare_args(int *ac, char ***av, char *prgname, struct Command *cmd ){
-
- char **ret;
- int i;
- char *newname;
-
- ret = (char **)malloc(sizeof(char*)*(*ac+1));
- newname = (char*)malloc(strlen(prgname)+strlen(cmd->verb)+2);
- if( !ret || !newname ){
- free(ret);
- free(newname);
- return -1;
- }
+int handle_command_group(const struct cmd_group *grp, int argc,
+ char **argv)
- ret[0] = newname;
- for(i=0; i < *ac ; i++ )
- ret[i+1] = (*av)[i];
+{
+ const struct cmd_struct *cmd;
- strcpy(newname, prgname);
- strcat(newname, " ");
- strcat(newname, cmd->verb);
+ argc--;
+ argv++;
+ if (argc < 1) {
+ usage_command_group(grp, 0, 0);
+ exit(1);
+ }
- (*ac)++;
- *av = ret;
+ cmd = parse_command_token(argv[0], grp);
- return 0;
+ handle_help_options_next_level(cmd, argc, argv);
+ fixup_argv0(argv, cmd->token);
+ return cmd->fn(argc, argv);
}
+static const struct cmd_group btrfs_cmd_group;
+static const char * const cmd_help_usage[] = {
+ "btrfs help [--full]",
+ "Display help information",
+ "",
+ "--full display detailed help on every command",
+ NULL
+};
-/*
-
- This function perform the following jobs:
- - show the help if '--help' or 'help' or '-h' are passed
- - verify that a command is not ambiguous, otherwise show which
- part of the command is ambiguous
- - if after a (even partial) command there is '--help' show the help
- for all the matching commands
- - if the command doesn't' match show an error
- - finally, if a command match, they return which command is matched and
- the arguments
-
- The function return 0 in case of help is requested; <0 in case
- of uncorrect command; >0 in case of matching commands
- argc, argv are the arg-counter and arg-vector (input)
- *nargs_ is the number of the arguments after the command (output)
- **cmd_ is the invoked command (output)
- ***args_ are the arguments after the command
-
-*/
-static int parse_args(int argc, char **argv,
- CommandFunction *func_,
- int *nargs_, char **cmd_, char ***args_ )
+static int cmd_help(int argc, char **argv)
{
- struct Command *cp;
- struct Command *matchcmd=0;
- char *prgname = get_prgname(argv[0]);
- int i=0, helprequested=0;
-
- if( argc < 2 || !strcmp(argv[1], "help") ||
- !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")){
- help(prgname);
- return 0;
- }
+ help_command_group(&btrfs_cmd_group, argc, argv);
+ return 0;
+}
- for( cp = commands; cp->verb; cp++ )
- if( !cp->ncmds)
- cp->ncmds = split_command(cp->verb, &(cp->cmds));
+static const char * const cmd_version_usage[] = {
+ "btrfs version",
+ "Display btrfs-progs version",
+ NULL
+};
- for( cp = commands; cp->verb; cp++ ){
- int match;
+static int cmd_version(int argc, char **argv)
+{
+ printf("%s\n", PACKAGE_STRING);
+ return 0;
+}
- if( argc-1 < cp->ncmds )
- continue;
- for( match = 1, i = 0 ; i < cp->ncmds ; i++ ){
- char *s1, *s2;
- s1 = cp->cmds[i];
- s2 = argv[i+1];
-
- for(s2 = cp->cmds[i], s1 = argv[i+1];
- *s1 == *s2 && *s1;
- s1++, s2++ ) ;
- if( *s1 ){
- match=0;
- break;
- }
- }
+/*
+ * Parse global options, between binary name and first non-option argument
+ * after processing all valid options (including those with arguments).
+ *
+ * Returns index to argv where parsting stopped, optind is reset to 1
+ */
+static int handle_global_options(int argc, char **argv)
+{
+ enum { OPT_HELP = 256, OPT_VERSION, OPT_FULL };
+ static const struct option long_options[] = {
+ { "help", no_argument, NULL, OPT_HELP },
+ { "version", no_argument, NULL, OPT_VERSION },
+ { "full", no_argument, NULL, OPT_FULL },
+ { NULL, 0, NULL, 0}
+ };
+ int shift;
+
+ if (argc == 0)
+ return 0;
- /* If you understand why this code works ...
- you are a genious !! */
- if(argc>i+1 && !strcmp(argv[i+1],"--help")){
- if(!helprequested)
- printf("Usage:\n");
- print_help(prgname, cp);
- helprequested=1;
- continue;
+ opterr = 0;
+ while (1) {
+ int c;
+
+ c = getopt_long(argc, argv, "+", long_options, NULL);
+ if (c < 0)
+ break;
+
+ switch (c) {
+ case OPT_HELP: break;
+ case OPT_VERSION: break;
+ case OPT_FULL: break;
+ default:
+ fprintf(stderr, "Unknown global option: %s\n",
+ argv[optind - 1]);
+ exit(129);
}
+ }
- if(!match)
- continue;
+ shift = optind;
+ optind = 1;
- matchcmd = cp;
- *nargs_ = argc-matchcmd->ncmds-1;
- *cmd_ = matchcmd->verb;
- *args_ = argv+matchcmd->ncmds+1;
- *func_ = cp->func;
+ return shift;
+}
- break;
+void handle_special_globals(int shift, int argc, char **argv)
+{
+ int has_help = 0;
+ int has_full = 0;
+ int i;
+
+ for (i = 0; i < shift; i++) {
+ if (strcmp(argv[i], "--help") == 0)
+ has_help = 1;
+ else if (strcmp(argv[i], "--full") == 0)
+ has_full = 1;
}
- if(helprequested){
- printf("\n%s\n", BTRFS_BUILD_VERSION);
- return 0;
+ if (has_help) {
+ if (has_full)
+ usage_command_group(&btrfs_cmd_group, 1, 0);
+ else
+ cmd_help(argc, argv);
+ exit(0);
}
- if(!matchcmd){
- fprintf( stderr, "ERROR: unknown command '%s'\n",argv[1]);
- help(prgname);
- return -1;
- }
+ for (i = 0; i < shift; i++)
+ if (strcmp(argv[i], "--version") == 0) {
+ cmd_version(argc, argv);
+ exit(0);
+ }
+}
- if(check_ambiguity(matchcmd, argv))
- return -2;
+static const struct cmd_group btrfs_cmd_group = {
+ btrfs_cmd_group_usage, btrfs_cmd_group_info, {
+ { "subvolume", cmd_subvolume, NULL, &subvolume_cmd_group, 0 },
+ { "filesystem", cmd_filesystem, NULL, &filesystem_cmd_group, 0 },
+ { "balance", cmd_balance, NULL, &balance_cmd_group, 0 },
+ { "device", cmd_device, NULL, &device_cmd_group, 0 },
+ { "scrub", cmd_scrub, NULL, &scrub_cmd_group, 0 },
+ { "check", cmd_check, cmd_check_usage, NULL, 0 },
+ { "rescue", cmd_rescue, NULL, &rescue_cmd_group, 0 },
+ { "restore", cmd_restore, cmd_restore_usage, NULL, 0 },
+ { "inspect-internal", cmd_inspect, NULL, &inspect_cmd_group, 0 },
+ { "property", cmd_property, NULL, &property_cmd_group, 0 },
+ { "send", cmd_send, cmd_send_usage, NULL, 0 },
+ { "receive", cmd_receive, cmd_receive_usage, NULL, 0 },
+ { "quota", cmd_quota, NULL, "a_cmd_group, 0 },
+ { "qgroup", cmd_qgroup, NULL, &qgroup_cmd_group, 0 },
+ { "replace", cmd_replace, NULL, &replace_cmd_group, 0 },
+ { "help", cmd_help, cmd_help_usage, NULL, 0 },
+ { "version", cmd_version, cmd_version_usage, NULL, 0 },
+ NULL_CMD_STRUCT
+ },
+};
- /* check the number of argument */
- if (matchcmd->nargs < 0 && matchcmd->nargs < -*nargs_ ){
- fprintf(stderr, "ERROR: '%s' requires minimum %d arg(s)\n",
- matchcmd->verb, -matchcmd->nargs);
- return -2;
- }
- if(matchcmd->nargs >= 0 && matchcmd->nargs != *nargs_ && matchcmd->nargs != 999){
- fprintf(stderr, "ERROR: '%s' requires %d arg(s)\n",
- matchcmd->verb, matchcmd->nargs);
- return -2;
+int main(int argc, char **argv)
+{
+ const struct cmd_struct *cmd;
+ const char *bname;
+ int ret;
+
+ btrfs_config_init();
+
+ if ((bname = strrchr(argv[0], '/')) != NULL)
+ bname++;
+ else
+ bname = argv[0];
+
+ if (!strcmp(bname, "btrfsck")) {
+ argv[0] = "check";
+ } else {
+ int shift;
+
+ shift = handle_global_options(argc, argv);
+ handle_special_globals(shift, argc, argv);
+ while (shift-- > 0) {
+ argc--;
+ argv++;
+ }
+ if (argc == 0) {
+ usage_command_group_short(&btrfs_cmd_group);
+ exit(1);
+ }
}
-
- if (prepare_args( nargs_, args_, prgname, matchcmd )){
- fprintf(stderr, "ERROR: not enough memory\\n");
- return -20;
- }
+ cmd = parse_command_token(argv[0], &btrfs_cmd_group);
- return 1;
-}
-int main(int ac, char **av )
-{
+ handle_help_options_next_level(cmd, argc, argv);
- char *cmd=0, **args=0;
- int nargs=0, r;
- CommandFunction func=0;
+ crc32c_optimization_init();
- r = parse_args(ac, av, &func, &nargs, &cmd, &args);
- if( r <= 0 ){
- /* error or no command to parse*/
- exit(-r);
- }
+ fixup_argv0(argv, cmd->token);
- exit(func(nargs, args));
+ ret = cmd->fn(argc, argv);
-}
+ btrfs_close_all_devices();
+ exit(ret);
+}