| /* |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public |
| * License v2 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 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 021110-1307, USA. |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <getopt.h> |
| |
| #include "volumes.h" |
| #include "kernel-lib/crc32c.h" |
| #include "cmds/commands.h" |
| #include "common/utils.h" |
| #include "common/help.h" |
| #include "common/box.h" |
| |
| static const char * const btrfs_cmd_group_usage[] = { |
| "btrfs [--help] [--version] [--format <format>] <group> [<group>...] <command> [<args>]", |
| NULL |
| }; |
| |
| static const char btrfs_cmd_group_info[] = |
| "Use --help as an argument for information on a specific group or command."; |
| |
| static inline const char *skip_prefix(const char *str, const char *prefix) |
| { |
| size_t len = strlen(prefix); |
| return strncmp(str, prefix, len) ? NULL : str + len; |
| } |
| |
| static int parse_one_token(const char *arg, const struct cmd_group *grp, |
| const struct cmd_struct **cmd_ret) |
| { |
| const struct cmd_struct *abbrev_cmd = NULL, *ambiguous_cmd = NULL; |
| int i = 0; |
| |
| for (i = 0; grp->commands[i]; i++) { |
| const struct cmd_struct *cmd = grp->commands[i]; |
| 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; |
| |
| *cmd_ret = cmd; |
| return 0; |
| } |
| |
| if (ambiguous_cmd) |
| return -2; |
| |
| if (abbrev_cmd) { |
| *cmd_ret = abbrev_cmd; |
| return 0; |
| } |
| |
| return -1; |
| } |
| |
| static const struct cmd_struct * |
| parse_command_token(const char *arg, const struct cmd_group *grp) |
| { |
| const struct cmd_struct *cmd = NULL; |
| |
| switch(parse_one_token(arg, grp, &cmd)) { |
| case -1: |
| help_unknown_token(arg, grp); |
| case -2: |
| help_ambiguous_token(arg, grp); |
| } |
| |
| return cmd; |
| } |
| |
| static void check_output_format(const struct cmd_struct *cmd) |
| { |
| if (cmd->next) |
| return; |
| |
| if (!(cmd->flags & bconf.output_format & CMD_FORMAT_MASK)) { |
| fprintf(stderr, |
| "ERROR: output format %s is unsupported for this command\n", |
| output_format_name(bconf.output_format)); |
| exit(1); |
| } |
| } |
| |
| 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, true, false); |
| } |
| |
| exit(0); |
| } |
| } |
| |
| int handle_command_group(const struct cmd_struct *cmd, int argc, |
| char **argv) |
| { |
| const struct cmd_struct *subcmd; |
| |
| argc--; |
| argv++; |
| if (argc < 1) { |
| usage_command_group(cmd->next, false, false); |
| exit(1); |
| } |
| |
| subcmd = parse_command_token(argv[0], cmd->next); |
| |
| handle_help_options_next_level(subcmd, argc, argv); |
| check_output_format(subcmd); |
| |
| fixup_argv0(argv, subcmd->token); |
| return cmd_execute(subcmd, argc, argv); |
| } |
| |
| static const struct cmd_group btrfs_cmd_group; |
| |
| static const char * const cmd_help_usage[] = { |
| "btrfs help [--full] [--box]", |
| "Display help information", |
| "", |
| "--full display detailed help on every command", |
| "--box show list of built-in tools (busybox style)", |
| NULL |
| }; |
| |
| static int cmd_help(const struct cmd_struct *unused, int argc, char **argv) |
| { |
| int i; |
| |
| for (i = 0; i < argc; i++) { |
| if (strcmp(argv[i], "--box") == 0) { |
| #if ENABLE_BOX |
| printf("Standalone tools built-in in the busybox style:\n"); |
| printf("- mkfs.btrfs\n"); |
| printf("- btrfs-image\n"); |
| printf("- btrfs-convert\n"); |
| printf("- btrfstune\n"); |
| printf("- btrfs-find-root\n"); |
| #else |
| printf("No standalone tools built-in in the busybox style\n"); |
| #endif |
| exit(0); |
| } |
| } |
| help_command_group(&btrfs_cmd_group, argc, argv); |
| return 0; |
| } |
| |
| static DEFINE_SIMPLE_COMMAND(help, "help"); |
| |
| static const char * const cmd_version_usage[] = { |
| "btrfs version", |
| "Display btrfs-progs version", |
| NULL |
| }; |
| |
| static int cmd_version(const struct cmd_struct *unused, int argc, char **argv) |
| { |
| printf("%s\n", PACKAGE_STRING); |
| return 0; |
| } |
| static DEFINE_SIMPLE_COMMAND(version, "version"); |
| |
| static void print_output_formats(FILE *outf) |
| { |
| int i; |
| |
| fputs("Options for --format are:", outf); |
| for (i = 0; i < ARRAY_SIZE(output_formats); i++) |
| fprintf(outf, "%s%s", i ? ", " : " ", output_formats[i].name); |
| fputs("\n", outf); |
| } |
| |
| static void handle_output_format(const char *format) |
| { |
| int i; |
| bool found = false; |
| |
| for (i = 0; i < ARRAY_SIZE(output_formats); i++) { |
| if (!strcasecmp(format, output_formats[i].name)) { |
| bconf.output_format = output_formats[i].value; |
| found = true; |
| break; |
| } |
| } |
| |
| /* Print error for invalid format */ |
| if (!found) { |
| bconf.output_format = CMD_FORMAT_TEXT; |
| fprintf(stderr, "error: invalid output format \"%s\"\n\n", |
| format); |
| print_output_formats(stderr); |
| exit(1); |
| } |
| } |
| |
| /* |
| * 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 parsing stopped, optind is reset to 1 |
| */ |
| static int handle_global_options(int argc, char **argv) |
| { |
| enum { OPT_HELP = 256, OPT_VERSION, OPT_FULL, OPT_FORMAT }; |
| static const struct option long_options[] = { |
| { "help", no_argument, NULL, OPT_HELP }, |
| { "version", no_argument, NULL, OPT_VERSION }, |
| { "format", required_argument, NULL, OPT_FORMAT }, |
| { "full", no_argument, NULL, OPT_FULL }, |
| { NULL, 0, NULL, 0} |
| }; |
| int shift; |
| |
| if (argc == 0) |
| return 0; |
| |
| 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; |
| case OPT_FORMAT: |
| handle_output_format(optarg); |
| break; |
| default: |
| fprintf(stderr, "Unknown global option: %s\n", |
| argv[optind - 1]); |
| exit(129); |
| } |
| } |
| |
| shift = optind; |
| optind = 1; |
| |
| return shift; |
| } |
| |
| static void handle_special_globals(int shift, int argc, char **argv) |
| { |
| bool has_help = false; |
| bool has_full = false; |
| int i; |
| |
| for (i = 0; i < shift; i++) { |
| if (strcmp(argv[i], "--help") == 0) |
| has_help = true; |
| else if (strcmp(argv[i], "--full") == 0) |
| has_full = true; |
| } |
| |
| if (has_help) { |
| if (has_full) |
| usage_command_group(&btrfs_cmd_group, true, false); |
| else |
| cmd_execute(&cmd_struct_help, argc, argv); |
| print_output_formats(stdout); |
| exit(0); |
| } |
| |
| for (i = 0; i < shift; i++) |
| if (strcmp(argv[i], "--version") == 0) { |
| cmd_execute(&cmd_struct_version, argc, argv); |
| exit(0); |
| } |
| } |
| |
| static const struct cmd_group btrfs_cmd_group = { |
| btrfs_cmd_group_usage, btrfs_cmd_group_info, { |
| &cmd_struct_subvolume, |
| &cmd_struct_filesystem, |
| &cmd_struct_balance, |
| &cmd_struct_device, |
| &cmd_struct_scrub, |
| &cmd_struct_check, |
| &cmd_struct_rescue, |
| &cmd_struct_restore, |
| &cmd_struct_inspect, |
| &cmd_struct_property, |
| &cmd_struct_send, |
| &cmd_struct_receive, |
| &cmd_struct_quota, |
| &cmd_struct_qgroup, |
| &cmd_struct_replace, |
| &cmd_struct_help, |
| &cmd_struct_version, |
| NULL |
| }, |
| }; |
| |
| 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"; |
| #ifdef ENABLE_BOX |
| } else if (!strcmp(bname, "mkfs.btrfs")) { |
| return mkfs_main(argc, argv); |
| } else if (!strcmp(bname, "btrfs-image")) { |
| return image_main(argc, argv); |
| } else if (!strcmp(bname, "btrfs-convert")) { |
| return convert_main(argc, argv); |
| } else if (!strcmp(bname, "btrfstune")) { |
| return btrfstune_main(argc, argv); |
| #endif |
| } 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); |
| } |
| } |
| |
| cmd = parse_command_token(argv[0], &btrfs_cmd_group); |
| |
| handle_help_options_next_level(cmd, argc, argv); |
| |
| crc32c_optimization_init(); |
| |
| fixup_argv0(argv, cmd->token); |
| |
| ret = cmd_execute(cmd, argc, argv); |
| |
| btrfs_close_all_devices(); |
| |
| exit(ret); |
| } |