summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Mason <chris.mason@oracle.com>2012-02-05 10:14:01 -0500
committerChris Mason <chris.mason@oracle.com>2012-02-05 10:14:01 -0500
commit8358341fdcc0504f16ac9fbc7ac63c095c5eb187 (patch)
tree8df2ce690eba566f334ca1e979da0b0e13c43581
parenta46e7ff22ed8e0e8567a36cd226a910a1fab47d7 (diff)
parentd675085a67fba325c7c35dbf3b2ec544354f7742 (diff)
downloadbtrfs-progs-8358341fdcc0504f16ac9fbc7ac63c095c5eb187.tar.gz
Merge branch 'parser' of git://github.com/idryomov/btrfs-progs
-rw-r--r--Makefile6
-rw-r--r--btrfs.c579
-rw-r--r--btrfs_cmds.c1307
-rw-r--r--btrfs_cmds.h44
-rw-r--r--cmds-device.c259
-rw-r--r--cmds-filesystem.c572
-rw-r--r--cmds-inspect.c241
-rw-r--r--cmds-scrub.c (renamed from scrub.c)109
-rw-r--r--cmds-subvolume.c531
-rw-r--r--commands.h95
-rw-r--r--common.c46
-rw-r--r--help.c207
12 files changed, 2231 insertions, 1765 deletions
diff --git a/Makefile b/Makefile
index 834be47..deafda6 100644
--- a/Makefile
+++ b/Makefile
@@ -5,6 +5,8 @@ objects = ctree.o disk-io.o radix-tree.o extent-tree.o print-tree.o \
root-tree.o dir-item.o file-item.o inode-item.o \
inode-map.o crc32c.o rbtree.o extent-cache.o extent_io.o \
volumes.o utils.o btrfs-list.o btrfslabel.o
+cmds_objects = cmds-subvolume.o cmds-filesystem.o cmds-device.o cmds-scrub.o \
+ cmds-inspect.o
CHECKFLAGS= -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ -Wbitwise \
-Wuninitialized -Wshadow -Wundef
@@ -36,8 +38,8 @@ all: version $(progs) manpages
version:
bash version.sh
-btrfs: $(objects) btrfs.o btrfs_cmds.o scrub.o
- $(CC) $(CFLAGS) -o btrfs btrfs.o btrfs_cmds.o scrub.o \
+btrfs: $(objects) btrfs.o help.o common.o $(cmds_objects)
+ $(CC) $(CFLAGS) -o btrfs btrfs.o help.o common.o $(cmds_objects) \
$(objects) $(LDFLAGS) $(LIBS) -lpthread
calc-size: $(objects) calc-size.o
diff --git a/btrfs.c b/btrfs.c
index 1def354..599a3e7 100644
--- a/btrfs.c
+++ b/btrfs.c
@@ -19,444 +19,255 @@
#include <stdlib.h>
#include <string.h>
-#include "kerncompat.h"
-#include "btrfs_cmds.h"
+#include "commands.h"
#include "version.h"
-#define BASIC_HELP 0
-#define ADVANCED_HELP 1
-
-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; from the 2nd line onward they
- are automatically indented */
- char *adv_help; /* advanced help message; from the 2nd line
- onward they are automatically indented */
-
- /* the following fields are run-time filled by the program */
- char **cmds; /* array of subcommands */
- int ncmds; /* number of subcommand */
-};
+static const char btrfs_cmd_group_usage[] =
+ "btrfs [--help] [--version] <group> [<group>...] <command> [<args>]";
-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", "[-r] <source> [<dest>/]<name>\n"
- "Create a writable/readonly snapshot of the subvolume <source> with\n"
- "the name <name> in the <dest> directory.",
- NULL
- },
- { do_delete_subvolume, 1,
- "subvolume delete", "<subvolume>\n"
- "Delete the subvolume <subvolume>.",
- NULL
- },
- { do_create_subvol, 1,
- "subvolume create", "[<dest>/]<name>\n"
- "Create a subvolume in <dest> (or the current directory if\n"
- "not passed).",
- NULL
- },
- { do_subvol_list, -1, "subvolume list", "[-p] <path>\n"
- "List the snapshot/subvolume of a filesystem.",
- "[-p] <path>\n"
- "List the snapshot/subvolume of a filesystem.\n"
- "-p print parent ID"
- },
- { 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.",
- NULL
- },
- { do_find_newer, 2, "subvolume find-new", "<path> <last_gen>\n"
- "List the recently modified files in a filesystem.",
- NULL
- },
- { do_defrag, -1,
- "filesystem defragment", "[-vf] [-c[zlib,lzo]] [-s start] [-l len] [-t size] <file>|<dir> [<file>|<dir>...]\n"
- "Defragment a file or a directory.",
- "[-vcf] [-s start] [-l len] [-t size] <file>|<dir> [<file>|<dir>...]\n"
- "Defragment file data or directory metadata.\n"
- "-v be verbose\n"
- "-c compress the file while defragmenting\n"
- "-f flush data to disk immediately after defragmenting\n"
- "-s start defragment only from byte onward\n"
- "-l len defragment only up to len bytes\n"
- "-t size minimal size of file to be considered for defragmenting\n"
- },
- { do_get_default_subvol, 1, "subvolume get-default", "<path>\n"
- "Get the default subvolume of a filesystem."
- },
- { do_fssync, 1,
- "filesystem sync", "<path>\n"
- "Force a sync on the filesystem <path>.",
- NULL
- },
- { 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.",
- NULL
- },
- { do_show_filesystem, 999,
- "filesystem show", "[--all-devices][<uuid>|<label>]\n"
- "Show the info of a btrfs filesystem. If no argument\n"
- "is passed, info of all the btrfs filesystem are shown.",
- NULL
- },
- { do_df_filesystem, 1,
- "filesystem df", "<path>\n"
- "Show space usage information for a mount point.",
- NULL
- },
- { do_balance, 1,
- "filesystem balance", "<path>\n"
- "Balance the chunks across the device.",
- NULL
- },
- { do_change_label, -1,
- "filesystem label", "<device> [<newlabel>]\n"
- "With one argument, get the label of filesystem on <device>.\n"
- "If <newlabel> is passed, set the filesystem label to <newlabel>.\n"
- "The filesystem must be unmounted.\n"
- },
- { do_scrub_start, -1,
- "scrub start", "[-Bdqr] <path>|<device>\n"
- "Start a new scrub.",
- "\n-B do not background\n"
- "-d stats per device (-B only)\n"
- "-q quiet\n"
- "-r read only mode\n"
- },
- { do_scrub_cancel, 1,
- "scrub cancel", "<path>|<device>\n"
- "Cancel a running scrub.",
- NULL
- },
- { do_scrub_resume, -1,
- "scrub resume", "[-Bdqr] <path>|<device>\n"
- "Resume previously canceled or interrupted scrub.",
- NULL
- },
- { do_scrub_status, -1,
- "scrub status", "[-d] <path>|<device>\n"
- "Show status of running or finished scrub.",
- NULL
- },
- { do_scan, 999,
- "device scan", "[<device>...]\n"
- "Scan all device for or the passed device for a btrfs\n"
- "filesystem.",
- NULL
- },
- { do_add_volume, -2,
- "device add", "<device> [<device>...] <path>\n"
- "Add a device to a filesystem.",
- NULL
- },
- { do_remove_volume, -2,
- "device delete", "<device> [<device>...] <path>\n"
- "Remove a device from a filesystem.",
- NULL
- },
- { do_ino_to_path, -2,
- "inspect-internal inode-resolve", "[-v] <inode> <path>\n"
- "get file system paths for the given inode.",
- NULL
- },
- { do_logical_to_ino, -2,
- "inspect-internal logical-resolve", "[-v] [-P] <logical> <path>\n"
- "get file system paths for the given logical address.",
- NULL
- },
- { 0, 0, 0, 0 }
-};
+char argv0_buf[ARGV0_BUF_SIZE] = "btrfs";
-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, int helptype)
+int prefixcmp(const char *str, const char *prefix)
{
- char *pc;
-
- printf("\t%s %s ", programname, cmd->verb );
+ for (; ; str++, prefix++)
+ if (!*prefix)
+ return 0;
+ else if (*str != *prefix)
+ return (unsigned char)*prefix - (unsigned char)*str;
+}
- if (helptype == ADVANCED_HELP && cmd->adv_help)
- for(pc = cmd->adv_help; *pc; pc++){
- putchar(*pc);
- if(*pc == '\n')
- printf("\t\t");
- }
- else
- for(pc = cmd->help; *pc; pc++){
- putchar(*pc);
- if(*pc == '\n')
- printf("\t\t");
+static int parse_one_token(const char *arg, const struct cmd_group *grp,
+ const struct cmd_struct **cmd_ret)
+{
+ 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;
- putchar('\n');
-}
+ *cmd_ret = cmd;
+ return 0;
+ }
-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, BASIC_HELP);
+ 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\t%s <cmd> --help\n\t\tShow detailed help for a command or\n\t\t"
- "subset of commands.\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;
+ const struct cmd_struct *cmd;
- for( *commands = 0, l = c = 0, p = s = cmd ; ; p++, l++ ){
- if ( *p && *p != ' ' )
- continue;
-
- /* 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;
+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;
- }
+static void fixup_argv0(char **argv, const char *token)
+{
+ int len = strlen(argv0_buf);
- ret[0] = newname;
- for(i=0; i < *ac ; i++ )
- ret[i+1] = (*av)[i];
+ snprintf(argv0_buf + len, sizeof(argv0_buf) - len, " %s", token);
+ argv[0] = argv0_buf;
+}
- strcpy(newname, prgname);
- strcat(newname, " ");
- strcat(newname, cmd->verb);
+int handle_command_group(const struct cmd_group *grp, int argc,
+ char **argv)
- (*ac)++;
- *av = ret;
+{
+ const struct cmd_struct *cmd;
- return 0;
+ argc--;
+ argv++;
+ if (argc < 1) {
+ usage_command_group(grp, 0, 0);
+ exit(1);
+ }
+ cmd = parse_command_token(argv[0], grp);
+
+ handle_help_options_next_level(cmd, argc, argv);
+
+ fixup_argv0(argv, cmd->token);
+ return cmd->fn(argc, argv);
}
+int check_argc_exact(int nargs, int expected)
+{
+ if (nargs < expected)
+ fprintf(stderr, "%s: too few arguments\n", argv0_buf);
+ if (nargs > expected)
+ fprintf(stderr, "%s: too many arguments\n", argv0_buf);
+ return nargs != expected;
+}
-/*
- This function performs 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 detailed help
- for all the matching commands
- - if the command doesn't match show an error
- - finally, if a command matches, they return which command 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_ )
+int check_argc_min(int nargs, int expected)
{
- 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;
+ if (nargs < expected) {
+ fprintf(stderr, "%s: too few arguments\n", argv0_buf);
+ return 1;
}
- for( cp = commands; cp->verb; cp++ )
- if( !cp->ncmds)
- cp->ncmds = split_command(cp->verb, &(cp->cmds));
+ return 0;
+}
- for( cp = commands; cp->verb; cp++ ){
- int match;
+int check_argc_max(int nargs, int expected)
+{
+ if (nargs > expected) {
+ fprintf(stderr, "%s: too many arguments\n", argv0_buf);
+ return 1;
+ }
- 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;
- }
- }
+ 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, ADVANCED_HELP);
- helprequested=1;
- continue;
- }
+const struct cmd_group btrfs_cmd_group;
- if(!match)
- continue;
-
- matchcmd = cp;
- *nargs_ = argc-matchcmd->ncmds-1;
- *cmd_ = matchcmd->verb;
- *args_ = argv+matchcmd->ncmds+1;
- *func_ = cp->func;
+static const char * const cmd_help_usage[] = {
+ "btrfs help [--full]",
+ "Dislay help information",
+ "",
+ "--full display detailed help on every command",
+ NULL
+};
- break;
- }
+static int cmd_help(int argc, char **argv)
+{
+ help_command_group(&btrfs_cmd_group, argc, argv);
+ return 0;
+}
- if(helprequested){
- printf("\n%s\n", BTRFS_BUILD_VERSION);
- return 0;
- }
+static const char * const cmd_version_usage[] = {
+ "btrfs version",
+ "Display btrfs-progs version",
+ NULL
+};
- if(!matchcmd){
- fprintf( stderr, "ERROR: unknown command '%s'\n",argv[1]);
- help(prgname);
- return -1;
- }
+static int cmd_version(int argc, char **argv)
+{
+ printf("%s\n", BTRFS_BUILD_VERSION);
+ return 0;
+}
- if(check_ambiguity(matchcmd, argv))
- return -2;
+static int handle_options(int *argc, char ***argv)
+{
+ char **orig_argv = *argv;
+
+ while (*argc > 0) {
+ const char *arg = (*argv)[0];
+ if (arg[0] != '-')
+ break;
+
+ if (!strcmp(arg, "--help")) {
+ break;
+ } else if (!strcmp(arg, "--version")) {
+ break;
+ } else {
+ fprintf(stderr, "Unknown option: %s\n", arg);
+ fprintf(stderr, "usage: %s\n",
+ btrfs_cmd_group.usagestr);
+ exit(129);
+ }
- /* 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;
+ (*argv)++;
+ (*argc)--;
}
-
- if (prepare_args( nargs_, args_, prgname, matchcmd )){
- fprintf(stderr, "ERROR: not enough memory\\n");
- return -20;
- }
-
- return 1;
+ return (*argv) - orig_argv;
}
-int main(int ac, char **av )
-{
- char *cmd=0, **args=0;
- int nargs=0, r;
- CommandFunction func=0;
+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 },
+ { "device", cmd_device, NULL, &device_cmd_group, 0 },
+ { "scrub", cmd_scrub, NULL, &scrub_cmd_group, 0 },
+ { "inspect-internal", cmd_inspect, NULL, &inspect_cmd_group, 0 },
+ { "help", cmd_help, cmd_help_usage, NULL, 0 },
+ { "version", cmd_version, cmd_version_usage, NULL, 0 },
+ { 0, 0, 0, 0, 0 }
+ },
+};
- r = parse_args(ac, av, &func, &nargs, &cmd, &args);
- if( r <= 0 ){
- /* error or no command to parse*/
- exit(-r);
+int main(int argc, char **argv)
+{
+ const struct cmd_struct *cmd;
+
+ argc--;
+ argv++;
+ handle_options(&argc, &argv);
+ if (argc > 0) {
+ if (!prefixcmp(argv[0], "--"))
+ argv[0] += 2;
+ } else {
+ usage_command_group(&btrfs_cmd_group, 0, 0);
+ exit(1);
}
- exit(func(nargs, args));
+ cmd = parse_command_token(argv[0], &btrfs_cmd_group);
-}
+ handle_help_options_next_level(cmd, argc, argv);
+ fixup_argv0(argv, cmd->token);
+ exit(cmd->fn(argc, argv));
+}
diff --git a/btrfs_cmds.c b/btrfs_cmds.c
deleted file mode 100644
index b59e9cb..0000000
--- a/btrfs_cmds.c
+++ /dev/null
@@ -1,1307 +0,0 @@
-/*
- * 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 <sys/ioctl.h>
-#include <sys/types.h>
-#include <dirent.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <libgen.h>
-#include <limits.h>
-#include <uuid/uuid.h>
-#include <ctype.h>
-
-#undef ULONG_MAX
-
-#include "kerncompat.h"
-#include "ctree.h"
-#include "transaction.h"
-#include "utils.h"
-#include "version.h"
-#include "ioctl.h"
-#include "volumes.h"
-
-#include "btrfs_cmds.h"
-#include "btrfslabel.h"
-
-#ifdef __CHECKER__
-#define BLKGETSIZE64 0
-#define BTRFS_IOC_SNAP_CREATE_V2 0
-#define BTRFS_VOL_NAME_MAX 255
-struct btrfs_ioctl_vol_args { char name[BTRFS_VOL_NAME_MAX]; };
-static inline int ioctl(int fd, int define, void *arg) { return 0; }
-#endif
-
-/*
- * test if path is a subvolume:
- * this function return
- * 0-> path exists but it is not a subvolume
- * 1-> path exists and it is a subvolume
- * -1 -> path is unaccessible
- */
-static int test_issubvolume(char *path)
-{
-
- struct stat st;
- int res;
-
- res = stat(path, &st);
- if(res < 0 )
- return -1;
-
- return (st.st_ino == 256) && S_ISDIR(st.st_mode);
-
-}
-
-/*
- * test if path is a directory
- * this function return
- * 0-> path exists but it is not a directory
- * 1-> path exists and it is a directory
- * -1 -> path is unaccessible
- */
-static int test_isdir(char *path)
-{
- struct stat st;
- int res;
-
- res = stat(path, &st);
- if(res < 0 )
- return -1;
-
- return S_ISDIR(st.st_mode);
-
-}
-
-int open_file_or_dir(const char *fname)
-{
- int ret;
- struct stat st;
- DIR *dirstream;
- int fd;
-
- ret = stat(fname, &st);
- if (ret < 0) {
- return -1;
- }
- if (S_ISDIR(st.st_mode)) {
- dirstream = opendir(fname);
- if (!dirstream) {
- return -2;
- }
- fd = dirfd(dirstream);
- } else {
- fd = open(fname, O_RDWR);
- }
- if (fd < 0) {
- return -3;
- }
- return fd;
-}
-
-static u64 parse_size(char *s)
-{
- int len = strlen(s);
- char c;
- u64 mult = 1;
-
- if (!isdigit(s[len - 1])) {
- c = tolower(s[len - 1]);
- switch (c) {
- case 'g':
- mult *= 1024;
- case 'm':
- mult *= 1024;
- case 'k':
- mult *= 1024;
- case 'b':
- break;
- default:
- fprintf(stderr, "Unknown size descriptor %c\n", c);
- exit(1);
- }
- s[len - 1] = '\0';
- }
- return atoll(s) * mult;
-}
-
-static int parse_compress_type(char *s)
-{
- if (strcmp(optarg, "zlib") == 0)
- return BTRFS_COMPRESS_ZLIB;
- else if (strcmp(optarg, "lzo") == 0)
- return BTRFS_COMPRESS_LZO;
- else {
- fprintf(stderr, "Unknown compress type %s\n", s);
- exit(1);
- };
-}
-
-int do_defrag(int ac, char **av)
-{
- int fd;
- int flush = 0;
- u64 start = 0;
- u64 len = (u64)-1;
- u32 thresh = 0;
- int i;
- int errors = 0;
- int ret = 0;
- int verbose = 0;
- int fancy_ioctl = 0;
- struct btrfs_ioctl_defrag_range_args range;
- int e=0;
- int compress_type = BTRFS_COMPRESS_NONE;
-
- optind = 1;
- while(1) {
- int c = getopt(ac, av, "vc::fs:l:t:");
- if (c < 0)
- break;
- switch(c) {
- case 'c':
- compress_type = BTRFS_COMPRESS_ZLIB;
- if (optarg)
- compress_type = parse_compress_type(optarg);
- fancy_ioctl = 1;
- break;
- case 'f':
- flush = 1;
- fancy_ioctl = 1;
- break;
- case 'v':
- verbose = 1;
- break;
- case 's':
- start = parse_size(optarg);
- fancy_ioctl = 1;
- break;
- case 'l':
- len = parse_size(optarg);
- fancy_ioctl = 1;
- break;
- case 't':
- thresh = parse_size(optarg);
- fancy_ioctl = 1;
- break;
- default:
- fprintf(stderr, "Invalid arguments for defragment\n");
- free(av);
- return 1;
- }
- }
- if (ac - optind == 0) {
- fprintf(stderr, "Invalid arguments for defragment\n");
- free(av);
- return 1;
- }
-
- memset(&range, 0, sizeof(range));
- range.start = start;
- range.len = len;
- range.extent_thresh = thresh;
- if (compress_type) {
- range.flags |= BTRFS_DEFRAG_RANGE_COMPRESS;
- range.compress_type = compress_type;
- }
- if (flush)
- range.flags |= BTRFS_DEFRAG_RANGE_START_IO;
-
- for (i = optind; i < ac; i++) {
- if (verbose)
- printf("%s\n", av[i]);
- fd = open_file_or_dir(av[i]);
- if (fd < 0) {
- fprintf(stderr, "failed to open %s\n", av[i]);
- perror("open:");
- errors++;
- continue;
- }
- if (!fancy_ioctl) {
- ret = ioctl(fd, BTRFS_IOC_DEFRAG, NULL);
- e=errno;
- } else {
- ret = ioctl(fd, BTRFS_IOC_DEFRAG_RANGE, &range);
- if (ret && errno == ENOTTY) {
- fprintf(stderr, "ERROR: defrag range ioctl not "
- "supported in this kernel, please try "
- "without any options.\n");
- errors++;
- close(fd);
- break;
- }
- }
- if (ret) {
- fprintf(stderr, "ERROR: defrag failed on %s - %s\n",
- av[i], strerror(e));
- errors++;
- }
- close(fd);
- }
- if (verbose)
- printf("%s\n", BTRFS_BUILD_VERSION);
- if (errors) {
- fprintf(stderr, "total %d failures\n", errors);
- exit(1);
- }
-
- free(av);
- return errors + 20;
-}
-
-int do_find_newer(int argc, char **argv)
-{
- int fd;
- int ret;
- char *subvol;
- u64 last_gen;
-
- subvol = argv[1];
- last_gen = atoll(argv[2]);
-
- ret = test_issubvolume(subvol);
- if (ret < 0) {
- fprintf(stderr, "ERROR: error accessing '%s'\n", subvol);
- return 12;
- }
- if (!ret) {
- fprintf(stderr, "ERROR: '%s' is not a subvolume\n", subvol);
- return 13;
- }
-
- fd = open_file_or_dir(subvol);
- if (fd < 0) {
- fprintf(stderr, "ERROR: can't access '%s'\n", subvol);
- return 12;
- }
- ret = find_updated_files(fd, 0, last_gen);
- if (ret)
- return 19;
- return 0;
-}
-
-int do_subvol_list(int argc, char **argv)
-{
- int fd;
- int ret;
- int print_parent = 0;
- char *subvol;
- int optind = 1;
-
- while(1) {
- int c = getopt(argc, argv, "p");
- if (c < 0) break;
- switch(c) {
- case 'p':
- print_parent = 1;
- optind++;
- break;
- }
- }
-
- if (argc - optind != 1) {
- fprintf(stderr, "ERROR: invalid arguments for subvolume list\n");
- return 1;
- }
-
- subvol = argv[optind];
-
- ret = test_issubvolume(subvol);
- if (ret < 0) {
- fprintf(stderr, "ERROR: error accessing '%s'\n", subvol);
- return 12;
- }
- if (!ret) {
- fprintf(stderr, "ERROR: '%s' is not a subvolume\n", subvol);
- return 13;
- }
-
- fd = open_file_or_dir(subvol);
- if (fd < 0) {
- fprintf(stderr, "ERROR: can't access '%s'\n", subvol);
- return 12;
- }
- ret = list_subvols(fd, print_parent, 0);
- if (ret)
- return 19;
- return 0;
-}
-
-int do_clone(int argc, char **argv)
-{
- char *subvol, *dst;
- int res, fd, fddst, len, e, optind = 0, readonly = 0;
- char *newname;
- char *dstdir;
- struct btrfs_ioctl_vol_args_v2 args;
-
- memset(&args, 0, sizeof(args));
-
- while (1) {
- int c = getopt(argc, argv, "r");
-
- if (c < 0)
- break;
- switch (c) {
- case 'r':
- optind++;
- readonly = 1;
- break;
- default:
- fprintf(stderr,
- "Invalid arguments for subvolume snapshot\n");
- free(argv);
- return 1;
- }
- }
- if (argc - optind != 3) {
- fprintf(stderr, "Invalid arguments for subvolume snapshot\n");
- free(argv);
- return 1;
- }
-
- subvol = argv[optind+1];
- dst = argv[optind+2];
-
- res = test_issubvolume(subvol);
- if(res<0){
- fprintf(stderr, "ERROR: error accessing '%s'\n", subvol);
- return 12;
- }
- if(!res){
- fprintf(stderr, "ERROR: '%s' is not a subvolume\n", subvol);
- return 13;
- }
-
- res = test_isdir(dst);
- if(res == 0 ){
- fprintf(stderr, "ERROR: '%s' exists and it is not a directory\n", dst);
- return 12;
- }
-
- if(res>0){
- newname = strdup(subvol);
- newname = basename(newname);
- dstdir = dst;
- }else{
- newname = strdup(dst);
- newname = basename(newname);
- dstdir = strdup(dst);
- dstdir = dirname(dstdir);
- }
-
- if( !strcmp(newname,".") || !strcmp(newname,"..") ||
- strchr(newname, '/') ){
- fprintf(stderr, "ERROR: incorrect snapshot name ('%s')\n",
- newname);
- return 14;
- }
-
- len = strlen(newname);
- if (len == 0 || len >= BTRFS_VOL_NAME_MAX) {
- fprintf(stderr, "ERROR: snapshot name too long ('%s)\n",
- newname);
- return 14;
- }
-
- fddst = open_file_or_dir(dstdir);
- if (fddst < 0) {
- fprintf(stderr, "ERROR: can't access to '%s'\n", dstdir);
- return 12;
- }
-
- fd = open_file_or_dir(subvol);
- if (fd < 0) {
- close(fddst);
- fprintf(stderr, "ERROR: can't access to '%s'\n", dstdir);
- return 12;
- }
-
- if (readonly) {
- args.flags |= BTRFS_SUBVOL_RDONLY;
- printf("Create a readonly snapshot of '%s' in '%s/%s'\n",
- subvol, dstdir, newname);
- } else {
- printf("Create a snapshot of '%s' in '%s/%s'\n",
- subvol, dstdir, newname);
- }
-
- args.fd = fd;
- strncpy(args.name, newname, BTRFS_SUBVOL_NAME_MAX);
- res = ioctl(fddst, BTRFS_IOC_SNAP_CREATE_V2, &args);
- e = errno;
-
- close(fd);
- close(fddst);
-
- if(res < 0 ){
- fprintf( stderr, "ERROR: cannot snapshot '%s' - %s\n",
- subvol, strerror(e));
- return 11;
- }
-
- return 0;
-
-}
-
-int do_delete_subvolume(int argc, char **argv)
-{
- int res, fd, len, e;
- struct btrfs_ioctl_vol_args args;
- char *dname, *vname, *cpath;
- char *path = argv[1];
-
- res = test_issubvolume(path);
- if(res<0){
- fprintf(stderr, "ERROR: error accessing '%s'\n", path);
- return 12;
- }
- if(!res){
- fprintf(stderr, "ERROR: '%s' is not a subvolume\n", path);
- return 13;
- }
-
- cpath = realpath(path, 0);
- dname = strdup(cpath);
- dname = dirname(dname);
- vname = strdup(cpath);
- vname = basename(vname);
- free(cpath);
-
- if( !strcmp(vname,".") || !strcmp(vname,"..") ||
- strchr(vname, '/') ){
- fprintf(stderr, "ERROR: incorrect subvolume name ('%s')\n",
- vname);
- return 14;
- }
-
- len = strlen(vname);
- if (len == 0 || len >= BTRFS_VOL_NAME_MAX) {
- fprintf(stderr, "ERROR: snapshot name too long ('%s)\n",
- vname);
- return 14;
- }
-
- fd = open_file_or_dir(dname);
- if (fd < 0) {
- close(fd);
- fprintf(stderr, "ERROR: can't access to '%s'\n", dname);
- return 12;
- }
-
- printf("Delete subvolume '%s/%s'\n", dname, vname);
- strncpy(args.name, vname, BTRFS_PATH_NAME_MAX);
- res = ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &args);
- e = errno;
-
- close(fd);
-
- if(res < 0 ){
- fprintf( stderr, "ERROR: cannot delete '%s/%s' - %s\n",
- dname, vname, strerror(e));
- return 11;
- }
-
- return 0;
-
-}
-
-int do_create_subvol(int argc, char **argv)
-{
- int res, fddst, len, e;
- char *newname;
- char *dstdir;
- struct btrfs_ioctl_vol_args args;
- char *dst = argv[1];
-
- res = test_isdir(dst);
- if(res >= 0 ){
- fprintf(stderr, "ERROR: '%s' exists\n", dst);
- return 12;
- }
-
- newname = strdup(dst);
- newname = basename(newname);
- dstdir = strdup(dst);
- dstdir = dirname(dstdir);
-
- if( !strcmp(newname,".") || !strcmp(newname,"..") ||
- strchr(newname, '/') ){
- fprintf(stderr, "ERROR: uncorrect subvolume name ('%s')\n",
- newname);
- return 14;
- }
-
- len = strlen(newname);
- if (len == 0 || len >= BTRFS_VOL_NAME_MAX) {
- fprintf(stderr, "ERROR: subvolume name too long ('%s)\n",
- newname);
- return 14;
- }
-
- fddst = open_file_or_dir(dstdir);
- if (fddst < 0) {
- fprintf(stderr, "ERROR: can't access to '%s'\n", dstdir);
- return 12;
- }
-
- printf("Create subvolume '%s/%s'\n", dstdir, newname);
- strncpy(args.name, newname, BTRFS_PATH_NAME_MAX);
- res = ioctl(fddst, BTRFS_IOC_SUBVOL_CREATE, &args);
- e = errno;
-
- close(fddst);
-
- if(res < 0 ){
- fprintf( stderr, "ERROR: cannot create subvolume - %s\n",
- strerror(e));
- return 11;
- }
-
- return 0;
-
-}
-
-int do_fssync(int argc, char **argv)
-{
- int fd, res, e;
- char *path = argv[1];
-
- fd = open_file_or_dir(path);
- if (fd < 0) {
- fprintf(stderr, "ERROR: can't access to '%s'\n", path);
- return 12;
- }
-
- printf("FSSync '%s'\n", path);
- res = ioctl(fd, BTRFS_IOC_SYNC);
- e = errno;
- close(fd);
- if( res < 0 ){
- fprintf(stderr, "ERROR: unable to fs-syncing '%s' - %s\n",
- path, strerror(e));
- return 16;
- }
-
- return 0;
-}
-
-int do_scan(int argc, char **argv)
-{
- int i, fd, e;
- int checklist = 1;
- int devstart = 1;
-
- if( argc >= 2 && !strcmp(argv[1],"--all-devices")){
-
- if( argc >2 ){
- fprintf(stderr, "ERROR: too may arguments\n");
- return 22;
- }
-
- checklist = 0;
- devstart += 1;
- }
-
- if(argc<=devstart){
-
- int ret;
-
- printf("Scanning for Btrfs filesystems\n");
- if(checklist)
- ret = btrfs_scan_block_devices(1);
- else
- ret = btrfs_scan_one_dir("/dev", 1);
- if (ret){
- fprintf(stderr, "ERROR: error %d while scanning\n", ret);
- return 18;
- }
- return 0;
- }
-
- fd = open("/dev/btrfs-control", O_RDWR);
- if (fd < 0) {
- perror("failed to open /dev/btrfs-control");
- return 10;
- }
-
- for( i = devstart ; i < argc ; i++ ){
- struct btrfs_ioctl_vol_args args;
- int ret;
-
- printf("Scanning for Btrfs filesystems in '%s'\n", argv[i]);
-
- strncpy(args.name, argv[i], BTRFS_PATH_NAME_MAX);
- /*
- * FIXME: which are the error code returned by this ioctl ?
- * it seems that is impossible to understand if there no is
- * a btrfs filesystem from an I/O error !!!
- */
- ret = ioctl(fd, BTRFS_IOC_SCAN_DEV, &args);
- e = errno;
-
- if( ret < 0 ){
- close(fd);
- fprintf(stderr, "ERROR: unable to scan the device '%s' - %s\n",
- argv[i], strerror(e));
- return 11;
- }
- }
-
- close(fd);
- return 0;
-
-}
-
-int do_resize(int argc, char **argv)
-{
-
- struct btrfs_ioctl_vol_args args;
- int fd, res, len, e;
- char *amount=argv[1], *path=argv[2];
-
- fd = open_file_or_dir(path);
- if (fd < 0) {
- fprintf(stderr, "ERROR: can't access to '%s'\n", path);
- return 12;
- }
- len = strlen(amount);
- if (len == 0 || len >= BTRFS_VOL_NAME_MAX) {
- fprintf(stderr, "ERROR: size value too long ('%s)\n",
- amount);
- return 14;
- }
-
- printf("Resize '%s' of '%s'\n", path, amount);
- strncpy(args.name, amount, BTRFS_PATH_NAME_MAX);
- res = ioctl(fd, BTRFS_IOC_RESIZE, &args);
- e = errno;
- close(fd);
- if( res < 0 ){
- fprintf(stderr, "ERROR: unable to resize '%s' - %s\n",
- path, strerror(e));
- return 30;
- }
- return 0;
-}
-
-static int uuid_search(struct btrfs_fs_devices *fs_devices, char *search)
-{
- struct list_head *cur;
- struct btrfs_device *device;
-
- list_for_each(cur, &fs_devices->devices) {
- device = list_entry(cur, struct btrfs_device, dev_list);
- if ((device->label && strcmp(device->label, search) == 0) ||
- strcmp(device->name, search) == 0)
- return 1;
- }
- return 0;
-}
-
-static void print_one_uuid(struct btrfs_fs_devices *fs_devices)
-{
- char uuidbuf[37];
- struct list_head *cur;
- struct btrfs_device *device;
- char *super_bytes_used;
- u64 devs_found = 0;
- u64 total;
-
- uuid_unparse(fs_devices->fsid, uuidbuf);
- device = list_entry(fs_devices->devices.next, struct btrfs_device,
- dev_list);
- if (device->label && device->label[0])
- printf("Label: '%s' ", device->label);
- else
- printf("Label: none ");
-
- super_bytes_used = pretty_sizes(device->super_bytes_used);
-
- total = device->total_devs;
- printf(" uuid: %s\n\tTotal devices %llu FS bytes used %s\n", uuidbuf,
- (unsigned long long)total, super_bytes_used);
-
- free(super_bytes_used);
-
- list_for_each(cur, &fs_devices->devices) {
- char *total_bytes;
- char *bytes_used;
- device = list_entry(cur, struct btrfs_device, dev_list);
- total_bytes = pretty_sizes(device->total_bytes);
- bytes_used = pretty_sizes(device->bytes_used);
- printf("\tdevid %4llu size %s used %s path %s\n",
- (unsigned long long)device->devid,
- total_bytes, bytes_used, device->name);
- free(total_bytes);
- free(bytes_used);
- devs_found++;
- }
- if (devs_found < total) {
- printf("\t*** Some devices missing\n");
- }
- printf("\n");
-}
-
-int do_show_filesystem(int argc, char **argv)
-{
- struct list_head *all_uuids;
- struct btrfs_fs_devices *fs_devices;
- struct list_head *cur_uuid;
- char *search = 0;
- int ret;
- int checklist = 1;
- int searchstart = 1;
-
- if( argc >= 2 && !strcmp(argv[1],"--all-devices")){
- checklist = 0;
- searchstart += 1;
- }
-
- if( argc > searchstart+1 ){
- fprintf(stderr, "ERROR: too many arguments\n");
- return 22;
- }
-
- if(checklist)
- ret = btrfs_scan_block_devices(0);
- else
- ret = btrfs_scan_one_dir("/dev", 0);
-
- if (ret){
- fprintf(stderr, "ERROR: error %d while scanning\n", ret);
- return 18;
- }
-
- if(searchstart < argc)
- search = argv[searchstart];
-
- all_uuids = btrfs_scanned_uuids();
- list_for_each(cur_uuid, all_uuids) {
- fs_devices = list_entry(cur_uuid, struct btrfs_fs_devices,
- list);
- if (search && uuid_search(fs_devices, search) == 0)
- continue;
- print_one_uuid(fs_devices);
- }
- printf("%s\n", BTRFS_BUILD_VERSION);
- return 0;
-}
-
-int do_add_volume(int nargs, char **args)
-{
-
- char *mntpnt = args[nargs-1];
- int i, fdmnt, ret=0, e;
-
-
- fdmnt = open_file_or_dir(mntpnt);
- if (fdmnt < 0) {
- fprintf(stderr, "ERROR: can't access to '%s'\n", mntpnt);
- return 12;
- }
-
- for (i = 1; i < (nargs-1); i++ ){
- struct btrfs_ioctl_vol_args ioctl_args;
- int devfd, res;
- u64 dev_block_count = 0;
- struct stat st;
- int mixed = 0;
-
- res = check_mounted(args[i]);
- if (res < 0) {
- fprintf(stderr, "error checking %s mount status\n",
- args[i]);
- ret++;
- continue;
- }
- if (res == 1) {
- fprintf(stderr, "%s is mounted\n", args[i]);
- ret++;
- continue;
- }
-
- devfd = open(args[i], O_RDWR);
- if (!devfd) {
- fprintf(stderr, "ERROR: Unable to open device '%s'\n", args[i]);
- close(devfd);
- ret++;
- continue;
- }
- res = fstat(devfd, &st);
- if (res) {
- fprintf(stderr, "ERROR: Unable to stat '%s'\n", args[i]);
- close(devfd);
- ret++;
- continue;
- }
- if (!S_ISBLK(st.st_mode)) {
- fprintf(stderr, "ERROR: '%s' is not a block device\n", args[i]);
- close(devfd);
- ret++;
- continue;
- }
-
- res = btrfs_prepare_device(devfd, args[i], 1, &dev_block_count, &mixed);
- if (res) {
- fprintf(stderr, "ERROR: Unable to init '%s'\n", args[i]);
- close(devfd);
- ret++;
- continue;
- }
- close(devfd);
-
- strncpy(ioctl_args.name, args[i], BTRFS_PATH_NAME_MAX);
- res = ioctl(fdmnt, BTRFS_IOC_ADD_DEV, &ioctl_args);
- e = errno;
- if(res<0){
- fprintf(stderr, "ERROR: error adding the device '%s' - %s\n",
- args[i], strerror(e));
- ret++;
- }
-
- }
-
- close(fdmnt);
- if (ret)
- return ret+20;
- else
- return 0;
-
-}
-
-int do_balance(int argc, char **argv)
-{
-
- int fdmnt, ret=0, e;
- struct btrfs_ioctl_vol_args args;
- char *path = argv[1];
-
- fdmnt = open_file_or_dir(path);
- if (fdmnt < 0) {
- fprintf(stderr, "ERROR: can't access to '%s'\n", path);
- return 12;
- }
-
- memset(&args, 0, sizeof(args));
- ret = ioctl(fdmnt, BTRFS_IOC_BALANCE, &args);
- e = errno;
- close(fdmnt);
- if(ret<0){
- fprintf(stderr, "ERROR: error during balancing '%s' - %s\n",
- path, strerror(e));
-
- return 19;
- }
- return 0;
-}
-int do_remove_volume(int nargs, char **args)
-{
-
- char *mntpnt = args[nargs-1];
- int i, fdmnt, ret=0, e;
-
- fdmnt = open_file_or_dir(mntpnt);
- if (fdmnt < 0) {
- fprintf(stderr, "ERROR: can't access to '%s'\n", mntpnt);
- return 12;
- }
-
- for(i=1 ; i < (nargs-1) ; i++ ){
- struct btrfs_ioctl_vol_args arg;
- int res;
-
- strncpy(arg.name, args[i], BTRFS_PATH_NAME_MAX);
- res = ioctl(fdmnt, BTRFS_IOC_RM_DEV, &arg);
- e = errno;
- if(res<0){
- fprintf(stderr, "ERROR: error removing the device '%s' - %s\n",
- args[i], strerror(e));
- ret++;
- }
- }
-
- close(fdmnt);
- if( ret)
- return ret+20;
- else
- return 0;
-}
-
-int do_set_default_subvol(int nargs, char **argv)
-{
- int ret=0, fd, e;
- u64 objectid;
- char *path = argv[2];
- char *subvolid = argv[1];
-
- fd = open_file_or_dir(path);
- if (fd < 0) {
- fprintf(stderr, "ERROR: can't access to '%s'\n", path);
- return 12;
- }
-
- objectid = (unsigned long long)strtoll(subvolid, NULL, 0);
- if (errno == ERANGE) {
- fprintf(stderr, "ERROR: invalid tree id (%s)\n",subvolid);
- return 30;
- }
- ret = ioctl(fd, BTRFS_IOC_DEFAULT_SUBVOL, &objectid);
- e = errno;
- close(fd);
- if( ret < 0 ){
- fprintf(stderr, "ERROR: unable to set a new default subvolume - %s\n",
- strerror(e));
- return 30;
- }
- return 0;
-}
-
-int do_change_label(int nargs, char **argv)
-{
- /* check the number of argument */
- if ( nargs > 3 ){
- fprintf(stderr, "ERROR: '%s' requires maximum 2 args\n",
- argv[0]);
- return -2;
- }else if (nargs == 2){
- return get_label(argv[1]);
- } else { /* nargs == 0 */
- return set_label(argv[1], argv[2]);
- }
-}
-
-
-int do_get_default_subvol(int nargs, char **argv)
-{
- int fd;
- int ret;
- char *subvol;
-
- subvol = argv[1];
-
- ret = test_issubvolume(subvol);
- if (ret < 0) {
- fprintf(stderr, "ERROR: error accessing '%s'\n", subvol);
- return 12;
- }
- if (!ret) {
- fprintf(stderr, "ERROR: '%s' is not a subvolume\n", subvol);
- return 13;
- }
-
- fd = open_file_or_dir(subvol);
- if (fd < 0) {
- fprintf(stderr, "ERROR: can't access '%s'\n", subvol);
- return 12;
- }
- ret = list_subvols(fd, 0, 1);
- if (ret)
- return 19;
- return 0;
-}
-
-int do_df_filesystem(int nargs, char **argv)
-{
- struct btrfs_ioctl_space_args *sargs;
- u64 count = 0, i;
- int ret;
- int fd;
- int e;
- char *path = argv[1];
-
- fd = open_file_or_dir(path);
- if (fd < 0) {
- fprintf(stderr, "ERROR: can't access to '%s'\n", path);
- return 12;
- }
-
- sargs = malloc(sizeof(struct btrfs_ioctl_space_args));
- if (!sargs)
- return -ENOMEM;
-
- sargs->space_slots = 0;
- sargs->total_spaces = 0;
-
- ret = ioctl(fd, BTRFS_IOC_SPACE_INFO, sargs);
- e = errno;
- if (ret) {
- fprintf(stderr, "ERROR: couldn't get space info on '%s' - %s\n",
- path, strerror(e));
- free(sargs);
- return ret;
- }
- if (!sargs->total_spaces)
- return 0;
-
- count = sargs->total_spaces;
-
- sargs = realloc(sargs, sizeof(struct btrfs_ioctl_space_args) +
- (count * sizeof(struct btrfs_ioctl_space_info)));
- if (!sargs)
- return -ENOMEM;
-
- sargs->space_slots = count;
- sargs->total_spaces = 0;
-
- ret = ioctl(fd, BTRFS_IOC_SPACE_INFO, sargs);
- e = errno;
- if (ret) {
- fprintf(stderr, "ERROR: couldn't get space info on '%s' - %s\n",
- path, strerror(e));
- close(fd);
- free(sargs);
- return ret;
- }
-
- for (i = 0; i < sargs->total_spaces; i++) {
- char description[80];
- char *total_bytes;
- char *used_bytes;
- int written = 0;
- u64 flags = sargs->spaces[i].flags;
-
- memset(description, 0, 80);
-
- if (flags & BTRFS_BLOCK_GROUP_DATA) {
- if (flags & BTRFS_BLOCK_GROUP_METADATA) {
- snprintf(description, 14, "%s",
- "Data+Metadata");
- written += 13;
- } else {
- snprintf(description, 5, "%s", "Data");
- written += 4;
- }
- } else if (flags & BTRFS_BLOCK_GROUP_SYSTEM) {
- snprintf(description, 7, "%s", "System");
- written += 6;
- } else if (flags & BTRFS_BLOCK_GROUP_METADATA) {
- snprintf(description, 9, "%s", "Metadata");
- written += 8;
- }
-
- if (flags & BTRFS_BLOCK_GROUP_RAID0) {
- snprintf(description+written, 8, "%s", ", RAID0");
- written += 7;
- } else if (flags & BTRFS_BLOCK_GROUP_RAID1) {
- snprintf(description+written, 8, "%s", ", RAID1");
- written += 7;
- } else if (flags & BTRFS_BLOCK_GROUP_DUP) {
- snprintf(description+written, 6, "%s", ", DUP");
- written += 5;
- } else if (flags & BTRFS_BLOCK_GROUP_RAID10) {
- snprintf(description+written, 9, "%s", ", RAID10");
- written += 8;
- }
-
- total_bytes = pretty_sizes(sargs->spaces[i].total_bytes);
- used_bytes = pretty_sizes(sargs->spaces[i].used_bytes);
- printf("%s: total=%s, used=%s\n", description, total_bytes,
- used_bytes);
- }
- free(sargs);
-
- return 0;
-}
-
-static int __ino_to_path_fd(u64 inum, int fd, int verbose, const char *prepend)
-{
- int ret;
- int i;
- struct btrfs_ioctl_ino_path_args ipa;
- struct btrfs_data_container *fspath;
-
- fspath = malloc(4096);
- if (!fspath)
- return 1;
-
- ipa.inum = inum;
- ipa.size = 4096;
- ipa.fspath = (u64)fspath;
-
- ret = ioctl(fd, BTRFS_IOC_INO_PATHS, &ipa);
- if (ret) {
- printf("ioctl ret=%d, error: %s\n", ret, strerror(errno));
- goto out;
- }
-
- if (verbose)
- printf("ioctl ret=%d, bytes_left=%lu, bytes_missing=%lu, "
- "cnt=%d, missed=%d\n", ret,
- (unsigned long)fspath->bytes_left,
- (unsigned long)fspath->bytes_missing,
- fspath->elem_cnt, fspath->elem_missed);
-
- for (i = 0; i < fspath->elem_cnt; ++i) {
- char **str = (char **)fspath->val;
- str[i] += (unsigned long)fspath->val;
- if (prepend)
- printf("%s/%s\n", prepend, str[i]);
- else
- printf("%s\n", str[i]);
- }
-
-out:
- free(fspath);
- return ret;
-}
-
-int do_ino_to_path(int nargs, char **argv)
-{
- int fd;
- int verbose = 0;
-
- optind = 1;
- while (1) {
- int c = getopt(nargs, argv, "v");
- if (c < 0)
- break;
- switch (c) {
- case 'v':
- verbose = 1;
- break;
- default:
- fprintf(stderr, "invalid arguments for ipath\n");
- return 1;
- }
- }
- if (nargs - optind != 2) {
- fprintf(stderr, "invalid arguments for ipath\n");
- return 1;
- }
-
- fd = open_file_or_dir(argv[optind+1]);
- if (fd < 0) {
- fprintf(stderr, "ERROR: can't access '%s'\n", argv[optind+1]);
- return 12;
- }
-
- return __ino_to_path_fd(atoll(argv[optind]), fd, verbose,
- argv[optind+1]);
-}
-
-int do_logical_to_ino(int nargs, char **argv)
-{
- int ret;
- int fd;
- int i;
- int verbose = 0;
- int getpath = 1;
- int bytes_left;
- struct btrfs_ioctl_logical_ino_args loi;
- struct btrfs_data_container *inodes;
- char full_path[4096];
- char *path_ptr;
-
- optind = 1;
- while (1) {
- int c = getopt(nargs, argv, "Pv");
- if (c < 0)
- break;
- switch (c) {
- case 'P':
- getpath = 0;
- break;
- case 'v':
- verbose = 1;
- break;
- default:
- fprintf(stderr, "invalid arguments for ipath\n");
- return 1;
- }
- }
- if (nargs - optind != 2) {
- fprintf(stderr, "invalid arguments for ipath\n");
- return 1;
- }
-
- inodes = malloc(4096);
- if (!inodes)
- return 1;
-
- loi.logical = atoll(argv[optind]);
- loi.size = 4096;
- loi.inodes = (u64)inodes;
-
- fd = open_file_or_dir(argv[optind+1]);
- if (fd < 0) {
- fprintf(stderr, "ERROR: can't access '%s'\n", argv[optind+1]);
- ret = 12;
- goto out;
- }
-
- ret = ioctl(fd, BTRFS_IOC_LOGICAL_INO, &loi);
- if (ret) {
- printf("ioctl ret=%d, error: %s\n", ret, strerror(errno));
- goto out;
- }
-
- if (verbose)
- printf("ioctl ret=%d, bytes_left=%lu, bytes_missing=%lu, "
- "cnt=%d, missed=%d\n", ret,
- (unsigned long)inodes->bytes_left,
- (unsigned long)inodes->bytes_missing,
- inodes->elem_cnt, inodes->elem_missed);
-
- bytes_left = sizeof(full_path);
- ret = snprintf(full_path, bytes_left, "%s/", argv[optind+1]);
- path_ptr = full_path + ret;
- bytes_left -= ret + 1;
- BUG_ON(bytes_left < 0);
-
- for (i = 0; i < inodes->elem_cnt; i += 3) {
- u64 inum = inodes->val[i];
- u64 offset = inodes->val[i+1];
- u64 root = inodes->val[i+2];
- int path_fd;
- char *name;
-
- if (getpath) {
- name = path_for_root(fd, root);
- if (IS_ERR(name))
- return PTR_ERR(name);
- if (!name) {
- path_ptr[-1] = '\0';
- path_fd = fd;
- } else {
- path_ptr[-1] = '/';
- ret = snprintf(path_ptr, bytes_left, "%s",
- name);
- BUG_ON(ret >= bytes_left);
- free(name);
- path_fd = open_file_or_dir(full_path);
- if (path_fd < 0) {
- fprintf(stderr, "ERROR: can't access "
- "'%s'\n", full_path);
- goto out;
- }
- }
- __ino_to_path_fd(inum, path_fd, verbose, full_path);
- } else {
- printf("inode %llu offset %llu root %llu\n", inum,
- offset, root);
- }
- }
-
-out:
- free(inodes);
- return ret;
-}
diff --git a/btrfs_cmds.h b/btrfs_cmds.h
deleted file mode 100644
index 81182b1..0000000
--- a/btrfs_cmds.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.
- */
-
-/* btrfs_cmds.c*/
-int do_clone(int nargs, char **argv);
-int do_delete_subvolume(int nargs, char **argv);
-int do_create_subvol(int nargs, char **argv);
-int do_fssync(int nargs, char **argv);
-int do_defrag(int argc, char **argv);
-int do_show_filesystem(int nargs, char **argv);
-int do_add_volume(int nargs, char **args);
-int do_balance(int nargs, char **argv);
-int do_scrub_start(int nargs, char **argv);
-int do_scrub_status(int argc, char **argv);
-int do_scrub_resume(int argc, char **argv);
-int do_scrub_cancel(int nargs, char **argv);
-int do_remove_volume(int nargs, char **args);
-int do_scan(int nargs, char **argv);
-int do_resize(int nargs, char **argv);
-int do_subvol_list(int nargs, char **argv);
-int do_set_default_subvol(int nargs, char **argv);
-int do_get_default_subvol(int nargs, char **argv);
-int list_subvols(int fd, int print_parent, int get_default);
-int do_df_filesystem(int nargs, char **argv);
-int find_updated_files(int fd, u64 root_id, u64 oldest_gen);
-int do_find_newer(int argc, char **argv);
-int do_change_label(int argc, char **argv);
-int open_file_or_dir(const char *fname);
-int do_ino_to_path(int nargs, char **argv);
-int do_logical_to_ino(int nargs, char **argv);
-char *path_for_root(int fd, u64 root);
diff --git a/cmds-device.c b/cmds-device.c
new file mode 100644
index 0000000..51089ba
--- /dev/null
+++ b/cmds-device.c
@@ -0,0 +1,259 @@
+/*
+ * 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 <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+#include "kerncompat.h"
+#include "ctree.h"
+#include "ioctl.h"
+#include "utils.h"
+
+#include "commands.h"
+
+/* FIXME - imported cruft, fix sparse errors and warnings */
+#ifdef __CHECKER__
+#define BLKGETSIZE64 0
+#define BTRFS_IOC_SNAP_CREATE_V2 0
+#define BTRFS_VOL_NAME_MAX 255
+struct btrfs_ioctl_vol_args { char name[BTRFS_VOL_NAME_MAX]; };
+static inline int ioctl(int fd, int define, void *arg) { return 0; }
+#endif
+
+static const char device_cmd_group_usage[] =
+ "btrfs device <command> [<args>]";
+
+static const char * const cmd_add_dev_usage[] = {
+ "btrfs device add <device> [<device>...] <path>",
+ "Add a device to a filesystem",
+ NULL
+};
+
+static int cmd_add_dev(int argc, char **argv)
+{
+ char *mntpnt;
+ int i, fdmnt, ret=0, e;
+
+ if (check_argc_min(argc, 3))
+ usage(cmd_add_dev_usage);
+
+ mntpnt = argv[argc - 1];
+
+ fdmnt = open_file_or_dir(mntpnt);
+ if (fdmnt < 0) {
+ fprintf(stderr, "ERROR: can't access to '%s'\n", mntpnt);
+ return 12;
+ }
+
+ for (i = 1; i < argc - 1; i++ ){
+ struct btrfs_ioctl_vol_args ioctl_args;
+ int devfd, res;
+ u64 dev_block_count = 0;
+ struct stat st;
+ int mixed = 0;
+
+ res = check_mounted(argv[i]);
+ if (res < 0) {
+ fprintf(stderr, "error checking %s mount status\n",
+ argv[i]);
+ ret++;
+ continue;
+ }
+ if (res == 1) {
+ fprintf(stderr, "%s is mounted\n", argv[i]);
+ ret++;
+ continue;
+ }
+
+ devfd = open(argv[i], O_RDWR);
+ if (!devfd) {
+ fprintf(stderr, "ERROR: Unable to open device '%s'\n", argv[i]);
+ close(devfd);
+ ret++;
+ continue;
+ }
+ res = fstat(devfd, &st);
+ if (res) {
+ fprintf(stderr, "ERROR: Unable to stat '%s'\n", argv[i]);
+ close(devfd);
+ ret++;
+ continue;
+ }
+ if (!S_ISBLK(st.st_mode)) {
+ fprintf(stderr, "ERROR: '%s' is not a block device\n", argv[i]);
+ close(devfd);
+ ret++;
+ continue;
+ }
+
+ res = btrfs_prepare_device(devfd, argv[i], 1, &dev_block_count, &mixed);
+ if (res) {
+ fprintf(stderr, "ERROR: Unable to init '%s'\n", argv[i]);
+ close(devfd);
+ ret++;
+ continue;
+ }
+ close(devfd);
+
+ strncpy(ioctl_args.name, argv[i], BTRFS_PATH_NAME_MAX);
+ res = ioctl(fdmnt, BTRFS_IOC_ADD_DEV, &ioctl_args);
+ e = errno;
+ if(res<0){
+ fprintf(stderr, "ERROR: error adding the device '%s' - %s\n",
+ argv[i], strerror(e));
+ ret++;
+ }
+
+ }
+
+ close(fdmnt);
+ if (ret)
+ return ret+20;
+ else
+ return 0;
+}
+
+static const char * const cmd_rm_dev_usage[] = {
+ "btrfs device delete <device> [<device>...] <path>",
+ "Remove a device from a filesystem",
+ NULL
+};
+
+static int cmd_rm_dev(int argc, char **argv)
+{
+ char *mntpnt;
+ int i, fdmnt, ret=0, e;
+
+ if (check_argc_min(argc, 3))
+ usage(cmd_rm_dev_usage);
+
+ mntpnt = argv[argc - 1];
+
+ fdmnt = open_file_or_dir(mntpnt);
+ if (fdmnt < 0) {
+ fprintf(stderr, "ERROR: can't access to '%s'\n", mntpnt);
+ return 12;
+ }
+
+ for(i=1 ; i < argc - 1; i++ ){
+ struct btrfs_ioctl_vol_args arg;
+ int res;
+
+ strncpy(arg.name, argv[i], BTRFS_PATH_NAME_MAX);
+ res = ioctl(fdmnt, BTRFS_IOC_RM_DEV, &arg);
+ e = errno;
+ if(res<0){
+ fprintf(stderr, "ERROR: error removing the device '%s' - %s\n",
+ argv[i], strerror(e));
+ ret++;
+ }
+ }
+
+ close(fdmnt);
+ if( ret)
+ return ret+20;
+ else
+ return 0;
+}
+
+static const char * const cmd_scan_dev_usage[] = {
+ "btrfs device scan [<device>...]",
+ "Scan devices for a btrfs filesystem",
+ NULL
+};
+
+static int cmd_scan_dev(int argc, char **argv)
+{
+ int i, fd, e;
+ int checklist = 1;
+ int devstart = 1;
+
+ if( argc > 1 && !strcmp(argv[1],"--all-devices")){
+ if (check_argc_max(argc, 2))
+ usage(cmd_scan_dev_usage);
+
+ checklist = 0;
+ devstart += 1;
+ }
+
+ if(argc<=devstart){
+
+ int ret;
+
+ printf("Scanning for Btrfs filesystems\n");
+ if(checklist)
+ ret = btrfs_scan_block_devices(1);
+ else
+ ret = btrfs_scan_one_dir("/dev", 1);
+ if (ret){
+ fprintf(stderr, "ERROR: error %d while scanning\n", ret);
+ return 18;
+ }
+ return 0;
+ }
+
+ fd = open("/dev/btrfs-control", O_RDWR);
+ if (fd < 0) {
+ perror("failed to open /dev/btrfs-control");
+ return 10;
+ }
+
+ for( i = devstart ; i < argc ; i++ ){
+ struct btrfs_ioctl_vol_args args;
+ int ret;
+
+ printf("Scanning for Btrfs filesystems in '%s'\n", argv[i]);
+
+ strncpy(args.name, argv[i], BTRFS_PATH_NAME_MAX);
+ /*
+ * FIXME: which are the error code returned by this ioctl ?
+ * it seems that is impossible to understand if there no is
+ * a btrfs filesystem from an I/O error !!!
+ */
+ ret = ioctl(fd, BTRFS_IOC_SCAN_DEV, &args);
+ e = errno;
+
+ if( ret < 0 ){
+ close(fd);
+ fprintf(stderr, "ERROR: unable to scan the device '%s' - %s\n",
+ argv[i], strerror(e));
+ return 11;
+ }
+ }
+
+ close(fd);
+ return 0;
+}
+
+const struct cmd_group device_cmd_group = {
+ device_cmd_group_usage, NULL, {
+ { "add", cmd_add_dev, cmd_add_dev_usage, NULL, 0 },
+ { "delete", cmd_rm_dev, cmd_rm_dev_usage, NULL, 0 },
+ { "scan", cmd_scan_dev, cmd_scan_dev_usage, NULL, 0 },
+ { 0, 0, 0, 0, 0 }
+ }
+};
+
+int cmd_device(int argc, char **argv)
+{
+ return handle_command_group(&device_cmd_group, argc, argv);
+}
diff --git a/cmds-filesystem.c b/cmds-filesystem.c
new file mode 100644
index 0000000..828ca0c
--- /dev/null
+++ b/cmds-filesystem.c
@@ -0,0 +1,572 @@
+/*
+ * 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 <unistd.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+#include <uuid/uuid.h>
+#include <ctype.h>
+
+#include "kerncompat.h"
+#include "ctree.h"
+#include "ioctl.h"
+#include "utils.h"
+#include "volumes.h"
+
+#include "version.h"
+
+#include "commands.h"
+#include "btrfslabel.h"
+
+static const char filesystem_cmd_group_usage[] =
+ "btrfs filesystem [<group>] <command> [<args>]";
+
+static const char * const cmd_df_usage[] = {
+ "btrfs filesystem df <path>",
+ "Show space usage information for a mount point",
+ NULL
+};
+
+static int cmd_df(int argc, char **argv)
+{
+ struct btrfs_ioctl_space_args *sargs;
+ u64 count = 0, i;
+ int ret;
+ int fd;
+ int e;
+ char *path;
+
+ if (check_argc_exact(argc, 2))
+ usage(cmd_df_usage);
+
+ path = argv[1];
+
+ fd = open_file_or_dir(path);
+ if (fd < 0) {
+ fprintf(stderr, "ERROR: can't access to '%s'\n", path);
+ return 12;
+ }
+
+ sargs = malloc(sizeof(struct btrfs_ioctl_space_args));
+ if (!sargs)
+ return -ENOMEM;
+
+ sargs->space_slots = 0;
+ sargs->total_spaces = 0;
+
+ ret = ioctl(fd, BTRFS_IOC_SPACE_INFO, sargs);
+ e = errno;
+ if (ret) {
+ fprintf(stderr, "ERROR: couldn't get space info on '%s' - %s\n",
+ path, strerror(e));
+ free(sargs);
+ return ret;
+ }
+ if (!sargs->total_spaces)
+ return 0;
+
+ count = sargs->total_spaces;
+
+ sargs = realloc(sargs, sizeof(struct btrfs_ioctl_space_args) +
+ (count * sizeof(struct btrfs_ioctl_space_info)));
+ if (!sargs)
+ return -ENOMEM;
+
+ sargs->space_slots = count;
+ sargs->total_spaces = 0;
+
+ ret = ioctl(fd, BTRFS_IOC_SPACE_INFO, sargs);
+ e = errno;
+ if (ret) {
+ fprintf(stderr, "ERROR: couldn't get space info on '%s' - %s\n",
+ path, strerror(e));
+ close(fd);
+ free(sargs);
+ return ret;
+ }
+
+ for (i = 0; i < sargs->total_spaces; i++) {
+ char description[80];
+ char *total_bytes;
+ char *used_bytes;
+ int written = 0;
+ u64 flags = sargs->spaces[i].flags;
+
+ memset(description, 0, 80);
+
+ if (flags & BTRFS_BLOCK_GROUP_DATA) {
+ if (flags & BTRFS_BLOCK_GROUP_METADATA) {
+ snprintf(description, 14, "%s",
+ "Data+Metadata");
+ written += 13;
+ } else {
+ snprintf(description, 5, "%s", "Data");
+ written += 4;
+ }
+ } else if (flags & BTRFS_BLOCK_GROUP_SYSTEM) {
+ snprintf(description, 7, "%s", "System");
+ written += 6;
+ } else if (flags & BTRFS_BLOCK_GROUP_METADATA) {
+ snprintf(description, 9, "%s", "Metadata");
+ written += 8;
+ }
+
+ if (flags & BTRFS_BLOCK_GROUP_RAID0) {
+ snprintf(description+written, 8, "%s", ", RAID0");
+ written += 7;
+ } else if (flags & BTRFS_BLOCK_GROUP_RAID1) {
+ snprintf(description+written, 8, "%s", ", RAID1");
+ written += 7;
+ } else if (flags & BTRFS_BLOCK_GROUP_DUP) {
+ snprintf(description+written, 6, "%s", ", DUP");
+ written += 5;
+ } else if (flags & BTRFS_BLOCK_GROUP_RAID10) {
+ snprintf(description+written, 9, "%s", ", RAID10");
+ written += 8;
+ }
+
+ total_bytes = pretty_sizes(sargs->spaces[i].total_bytes);
+ used_bytes = pretty_sizes(sargs->spaces[i].used_bytes);
+ printf("%s: total=%s, used=%s\n", description, total_bytes,
+ used_bytes);
+ }
+ free(sargs);
+
+ return 0;
+}
+
+static int uuid_search(struct btrfs_fs_devices *fs_devices, char *search)
+{
+ struct list_head *cur;
+ struct btrfs_device *device;
+
+ list_for_each(cur, &fs_devices->devices) {
+ device = list_entry(cur, struct btrfs_device, dev_list);
+ if ((device->label && strcmp(device->label, search) == 0) ||
+ strcmp(device->name, search) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+static void print_one_uuid(struct btrfs_fs_devices *fs_devices)
+{
+ char uuidbuf[37];
+ struct list_head *cur;
+ struct btrfs_device *device;
+ char *super_bytes_used;
+ u64 devs_found = 0;
+ u64 total;
+
+ uuid_unparse(fs_devices->fsid, uuidbuf);
+ device = list_entry(fs_devices->devices.next, struct btrfs_device,
+ dev_list);
+ if (device->label && device->label[0])
+ printf("Label: '%s' ", device->label);
+ else
+ printf("Label: none ");
+
+ super_bytes_used = pretty_sizes(device->super_bytes_used);
+
+ total = device->total_devs;
+ printf(" uuid: %s\n\tTotal devices %llu FS bytes used %s\n", uuidbuf,
+ (unsigned long long)total, super_bytes_used);
+
+ free(super_bytes_used);
+
+ list_for_each(cur, &fs_devices->devices) {
+ char *total_bytes;
+ char *bytes_used;
+ device = list_entry(cur, struct btrfs_device, dev_list);
+ total_bytes = pretty_sizes(device->total_bytes);
+ bytes_used = pretty_sizes(device->bytes_used);
+ printf("\tdevid %4llu size %s used %s path %s\n",
+ (unsigned long long)device->devid,
+ total_bytes, bytes_used, device->name);
+ free(total_bytes);
+ free(bytes_used);
+ devs_found++;
+ }
+ if (devs_found < total) {
+ printf("\t*** Some devices missing\n");
+ }
+ printf("\n");
+}
+
+static const char * const cmd_show_usage[] = {
+ "btrfs filesystem show [--all-devices] [<uuid>|<label>]",
+ "Show the structure of a filesystem",
+ "If no argument is given, structure of all present filesystems is shown.",
+ NULL
+};
+
+static int cmd_show(int argc, char **argv)
+{
+ struct list_head *all_uuids;
+ struct btrfs_fs_devices *fs_devices;
+ struct list_head *cur_uuid;
+ char *search = 0;
+ int ret;
+ int checklist = 1;
+ int searchstart = 1;
+
+ if( argc > 1 && !strcmp(argv[1],"--all-devices")){
+ checklist = 0;
+ searchstart += 1;
+ }
+
+ if (check_argc_max(argc, searchstart + 1))
+ usage(cmd_show_usage);
+
+ if(checklist)
+ ret = btrfs_scan_block_devices(0);
+ else
+ ret = btrfs_scan_one_dir("/dev", 0);
+
+ if (ret){
+ fprintf(stderr, "ERROR: error %d while scanning\n", ret);
+ return 18;
+ }
+
+ if(searchstart < argc)
+ search = argv[searchstart];
+
+ all_uuids = btrfs_scanned_uuids();
+ list_for_each(cur_uuid, all_uuids) {
+ fs_devices = list_entry(cur_uuid, struct btrfs_fs_devices,
+ list);
+ if (search && uuid_search(fs_devices, search) == 0)
+ continue;
+ print_one_uuid(fs_devices);
+ }
+ printf("%s\n", BTRFS_BUILD_VERSION);
+ return 0;
+}
+
+static const char * const cmd_sync_usage[] = {
+ "btrfs filesystem sync <path>",
+ "Force a sync on a filesystem",
+ NULL
+};
+
+static int cmd_sync(int argc, char **argv)
+{
+ int fd, res, e;
+ char *path;
+
+ if (check_argc_exact(argc, 2))
+ usage(cmd_sync_usage);
+
+ path = argv[1];
+
+ fd = open_file_or_dir(path);
+ if (fd < 0) {
+ fprintf(stderr, "ERROR: can't access to '%s'\n", path);
+ return 12;
+ }
+
+ printf("FSSync '%s'\n", path);
+ res = ioctl(fd, BTRFS_IOC_SYNC);
+ e = errno;
+ close(fd);
+ if( res < 0 ){
+ fprintf(stderr, "ERROR: unable to fs-syncing '%s' - %s\n",
+ path, strerror(e));
+ return 16;
+ }
+
+ return 0;
+}
+
+static u64 parse_size(char *s)
+{
+ int len = strlen(s);
+ char c;
+ u64 mult = 1;
+
+ if (!isdigit(s[len - 1])) {
+ c = tolower(s[len - 1]);
+ switch (c) {
+ case 'g':
+ mult *= 1024;
+ case 'm':
+ mult *= 1024;
+ case 'k':
+ mult *= 1024;
+ case 'b':
+ break;
+ default:
+ fprintf(stderr, "Unknown size descriptor %c\n", c);
+ exit(1);
+ }
+ s[len - 1] = '\0';
+ }
+ return atoll(s) * mult;
+}
+
+static int parse_compress_type(char *s)
+{
+ if (strcmp(optarg, "zlib") == 0)
+ return BTRFS_COMPRESS_ZLIB;
+ else if (strcmp(optarg, "lzo") == 0)
+ return BTRFS_COMPRESS_LZO;
+ else {
+ fprintf(stderr, "Unknown compress type %s\n", s);
+ exit(1);
+ };
+}
+
+static const char * const cmd_defrag_usage[] = {
+ "btrfs filesystem defragment [options] <file>|<dir> [<file>|<dir>...]",
+ "Defragment a file or a directory",
+ "",
+ "-v be verbose",
+ "-c[zlib,lzo] compress the file while defragmenting",
+ "-f flush data to disk immediately after defragmenting",
+ "-s start defragment only from byte onward",
+ "-l len defragment only up to len bytes",
+ "-t size minimal size of file to be considered for defragmenting",
+ NULL
+};
+
+static int cmd_defrag(int argc, char **argv)
+{
+ int fd;
+ int flush = 0;
+ u64 start = 0;
+ u64 len = (u64)-1;
+ u32 thresh = 0;
+ int i;
+ int errors = 0;
+ int ret = 0;
+ int verbose = 0;
+ int fancy_ioctl = 0;
+ struct btrfs_ioctl_defrag_range_args range;
+ int e=0;
+ int compress_type = BTRFS_COMPRESS_NONE;
+
+ optind = 1;
+ while(1) {
+ int c = getopt(argc, argv, "vc::fs:l:t:");
+ if (c < 0)
+ break;
+
+ switch(c) {
+ case 'c':
+ compress_type = BTRFS_COMPRESS_ZLIB;
+ if (optarg)
+ compress_type = parse_compress_type(optarg);
+ fancy_ioctl = 1;
+ break;
+ case 'f':
+ flush = 1;
+ fancy_ioctl = 1;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ case 's':
+ start = parse_size(optarg);
+ fancy_ioctl = 1;
+ break;
+ case 'l':
+ len = parse_size(optarg);
+ fancy_ioctl = 1;
+ break;
+ case 't':
+ thresh = parse_size(optarg);
+ fancy_ioctl = 1;
+ break;
+ default:
+ usage(cmd_defrag_usage);
+ }
+ }
+
+ if (check_argc_min(argc - optind, 1))
+ usage(cmd_defrag_usage);
+
+ memset(&range, 0, sizeof(range));
+ range.start = start;
+ range.len = len;
+ range.extent_thresh = thresh;
+ if (compress_type) {
+ range.flags |= BTRFS_DEFRAG_RANGE_COMPRESS;
+ range.compress_type = compress_type;
+ }
+ if (flush)
+ range.flags |= BTRFS_DEFRAG_RANGE_START_IO;
+
+ for (i = optind; i < argc; i++) {
+ if (verbose)
+ printf("%s\n", argv[i]);
+ fd = open_file_or_dir(argv[i]);
+ if (fd < 0) {
+ fprintf(stderr, "failed to open %s\n", argv[i]);
+ perror("open:");
+ errors++;
+ continue;
+ }
+ if (!fancy_ioctl) {
+ ret = ioctl(fd, BTRFS_IOC_DEFRAG, NULL);
+ e=errno;
+ } else {
+ ret = ioctl(fd, BTRFS_IOC_DEFRAG_RANGE, &range);
+ if (ret && errno == ENOTTY) {
+ fprintf(stderr, "ERROR: defrag range ioctl not "
+ "supported in this kernel, please try "
+ "without any options.\n");
+ errors++;
+ close(fd);
+ break;
+ }
+ }
+ if (ret) {
+ fprintf(stderr, "ERROR: defrag failed on %s - %s\n",
+ argv[i], strerror(e));
+ errors++;
+ }
+ close(fd);
+ }
+ if (verbose)
+ printf("%s\n", BTRFS_BUILD_VERSION);
+ if (errors) {
+ fprintf(stderr, "total %d failures\n", errors);
+ exit(1);
+ }
+
+ return errors + 20;
+}
+
+static const char * const cmd_balance_usage[] = {
+ "btrfs filesystem balance <path>",
+ "Balance the chunks across the device",
+ NULL
+};
+
+static int cmd_balance(int argc, char **argv)
+{
+ int fdmnt, ret=0, e;
+ struct btrfs_ioctl_vol_args args;
+ char *path;
+
+ if (check_argc_exact(argc, 2))
+ usage(cmd_balance_usage);
+
+ path = argv[1];
+
+ fdmnt = open_file_or_dir(path);
+ if (fdmnt < 0) {
+ fprintf(stderr, "ERROR: can't access to '%s'\n", path);
+ return 12;
+ }
+
+ memset(&args, 0, sizeof(args));
+ ret = ioctl(fdmnt, BTRFS_IOC_BALANCE, &args);
+ e = errno;
+ close(fdmnt);
+ if(ret<0){
+ fprintf(stderr, "ERROR: error during balancing '%s' - %s\n",
+ path, strerror(e));
+
+ return 19;
+ }
+ return 0;
+}
+
+static const char * const cmd_resize_usage[] = {
+ "btrfs filesystem resize [+/-]<newsize>[gkm]|max <path>",
+ "Resize a filesystem",
+ "If 'max' is passed, the filesystem will occupy all available space",
+ "on the device.",
+ NULL
+};
+
+static int cmd_resize(int argc, char **argv)
+{
+ struct btrfs_ioctl_vol_args args;
+ int fd, res, len, e;
+ char *amount, *path;
+
+ if (check_argc_exact(argc, 3))
+ usage(cmd_resize_usage);
+
+ amount = argv[1];
+ path = argv[2];
+
+ fd = open_file_or_dir(path);
+ if (fd < 0) {
+ fprintf(stderr, "ERROR: can't access to '%s'\n", path);
+ return 12;
+ }
+ len = strlen(amount);
+ if (len == 0 || len >= BTRFS_VOL_NAME_MAX) {
+ fprintf(stderr, "ERROR: size value too long ('%s)\n",
+ amount);
+ return 14;
+ }
+
+ printf("Resize '%s' of '%s'\n", path, amount);
+ strncpy(args.name, amount, BTRFS_PATH_NAME_MAX);
+ res = ioctl(fd, BTRFS_IOC_RESIZE, &args);
+ e = errno;
+ close(fd);
+ if( res < 0 ){
+ fprintf(stderr, "ERROR: unable to resize '%s' - %s\n",
+ path, strerror(e));
+ return 30;
+ }
+ return 0;
+}
+
+static const char * const cmd_label_usage[] = {
+ "btrfs filesystem label <device> [<newlabel>]",
+ "Get or change the label of an unmounted filesystem",
+ "With one argument, get the label of filesystem on <device>.",
+ "If <newlabel> is passed, set the filesystem label to <newlabel>.",
+ NULL
+};
+
+static int cmd_label(int argc, char **argv)
+{
+ if (check_argc_min(argc, 2) || check_argc_max(argc, 3))
+ usage(cmd_label_usage);
+
+ if (argc > 2)
+ return set_label(argv[1], argv[2]);
+ else
+ return get_label(argv[1]);
+}
+
+const struct cmd_group filesystem_cmd_group = {
+ filesystem_cmd_group_usage, NULL, {
+ { "df", cmd_df, cmd_df_usage, NULL, 0 },
+ { "show", cmd_show, cmd_show_usage, NULL, 0 },
+ { "sync", cmd_sync, cmd_sync_usage, NULL, 0 },
+ { "defragment", cmd_defrag, cmd_defrag_usage, NULL, 0 },
+ { "balance", cmd_balance, cmd_balance_usage, NULL, 0 },
+ { "resize", cmd_resize, cmd_resize_usage, NULL, 0 },
+ { "label", cmd_label, cmd_label_usage, NULL, 0 },
+ { 0, 0, 0, 0, 0 },
+ }
+};
+
+int cmd_filesystem(int argc, char **argv)
+{
+ return handle_command_group(&filesystem_cmd_group, argc, argv);
+}
diff --git a/cmds-inspect.c b/cmds-inspect.c
new file mode 100644
index 0000000..6cf565d
--- /dev/null
+++ b/cmds-inspect.c
@@ -0,0 +1,241 @@
+/*
+ * 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 <unistd.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+
+#include "kerncompat.h"
+#include "ioctl.h"
+
+#include "commands.h"
+
+/* btrfs-list.c */
+char *path_for_root(int fd, u64 root);
+
+static const char inspect_cmd_group_usage[] =
+ "btrfs inspect-internal <command> <args>";
+
+static int __ino_to_path_fd(u64 inum, int fd, int verbose, const char *prepend)
+{
+ int ret;
+ int i;
+ struct btrfs_ioctl_ino_path_args ipa;
+ struct btrfs_data_container *fspath;
+
+ fspath = malloc(4096);
+ if (!fspath)
+ return 1;
+
+ ipa.inum = inum;
+ ipa.size = 4096;
+ ipa.fspath = (u64)fspath;
+
+ ret = ioctl(fd, BTRFS_IOC_INO_PATHS, &ipa);
+ if (ret) {
+ printf("ioctl ret=%d, error: %s\n", ret, strerror(errno));
+ goto out;
+ }
+
+ if (verbose)
+ printf("ioctl ret=%d, bytes_left=%lu, bytes_missing=%lu, "
+ "cnt=%d, missed=%d\n", ret,
+ (unsigned long)fspath->bytes_left,
+ (unsigned long)fspath->bytes_missing,
+ fspath->elem_cnt, fspath->elem_missed);
+
+ for (i = 0; i < fspath->elem_cnt; ++i) {
+ char **str = (char **)fspath->val;
+ str[i] += (unsigned long)fspath->val;
+ if (prepend)
+ printf("%s/%s\n", prepend, str[i]);
+ else
+ printf("%s\n", str[i]);
+ }
+
+out:
+ free(fspath);
+ return ret;
+}
+
+static const char * const cmd_inode_resolve_usage[] = {
+ "btrfs inspect-internal inode-resolve [-v] <inode> <path>",
+ "Get file system paths for the given inode",
+ NULL
+};
+
+static int cmd_inode_resolve(int argc, char **argv)
+{
+ int fd;
+ int verbose = 0;
+
+ optind = 1;
+ while (1) {
+ int c = getopt(argc, argv, "v");
+ if (c < 0)
+ break;
+
+ switch (c) {
+ case 'v':
+ verbose = 1;
+ break;
+ default:
+ usage(cmd_inode_resolve_usage);
+ }
+ }
+
+ if (check_argc_exact(argc - optind, 2))
+ usage(cmd_inode_resolve_usage);
+
+ fd = open_file_or_dir(argv[optind+1]);
+ if (fd < 0) {
+ fprintf(stderr, "ERROR: can't access '%s'\n", argv[optind+1]);
+ return 12;
+ }
+
+ return __ino_to_path_fd(atoll(argv[optind]), fd, verbose,
+ argv[optind+1]);
+}
+
+static const char * const cmd_logical_resolve_usage[] = {
+ "btrfs inspect-internal logical-resolve [-Pv] <logical> <path>",
+ "Get file system paths for the given logical address",
+ NULL
+};
+
+static int cmd_logical_resolve(int argc, char **argv)
+{
+ int ret;
+ int fd;
+ int i;
+ int verbose = 0;
+ int getpath = 1;
+ int bytes_left;
+ struct btrfs_ioctl_logical_ino_args loi;
+ struct btrfs_data_container *inodes;
+ char full_path[4096];
+ char *path_ptr;
+
+ optind = 1;
+ while (1) {
+ int c = getopt(argc, argv, "Pv");
+ if (c < 0)
+ break;
+
+ switch (c) {
+ case 'P':
+ getpath = 0;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ default:
+ usage(cmd_logical_resolve_usage);
+ }
+ }
+
+ if (check_argc_exact(argc - optind, 2))
+ usage(cmd_logical_resolve_usage);
+
+ inodes = malloc(4096);
+ if (!inodes)
+ return 1;
+
+ loi.logical = atoll(argv[optind]);
+ loi.size = 4096;
+ loi.inodes = (u64)inodes;
+
+ fd = open_file_or_dir(argv[optind+1]);
+ if (fd < 0) {
+ fprintf(stderr, "ERROR: can't access '%s'\n", argv[optind+1]);
+ ret = 12;
+ goto out;
+ }
+
+ ret = ioctl(fd, BTRFS_IOC_LOGICAL_INO, &loi);
+ if (ret) {
+ printf("ioctl ret=%d, error: %s\n", ret, strerror(errno));
+ goto out;
+ }
+
+ if (verbose)
+ printf("ioctl ret=%d, bytes_left=%lu, bytes_missing=%lu, "
+ "cnt=%d, missed=%d\n", ret,
+ (unsigned long)inodes->bytes_left,
+ (unsigned long)inodes->bytes_missing,
+ inodes->elem_cnt, inodes->elem_missed);
+
+ bytes_left = sizeof(full_path);
+ ret = snprintf(full_path, bytes_left, "%s/", argv[optind+1]);
+ path_ptr = full_path + ret;
+ bytes_left -= ret + 1;
+ BUG_ON(bytes_left < 0);
+
+ for (i = 0; i < inodes->elem_cnt; i += 3) {
+ u64 inum = inodes->val[i];
+ u64 offset = inodes->val[i+1];
+ u64 root = inodes->val[i+2];
+ int path_fd;
+ char *name;
+
+ if (getpath) {
+ name = path_for_root(fd, root);
+ if (IS_ERR(name))
+ return PTR_ERR(name);
+ if (!name) {
+ path_ptr[-1] = '\0';
+ path_fd = fd;
+ } else {
+ path_ptr[-1] = '/';
+ ret = snprintf(path_ptr, bytes_left, "%s",
+ name);
+ BUG_ON(ret >= bytes_left);
+ free(name);
+ path_fd = open_file_or_dir(full_path);
+ if (path_fd < 0) {
+ fprintf(stderr, "ERROR: can't access "
+ "'%s'\n", full_path);
+ goto out;
+ }
+ }
+ __ino_to_path_fd(inum, path_fd, verbose, full_path);
+ } else {
+ printf("inode %llu offset %llu root %llu\n", inum,
+ offset, root);
+ }
+ }
+
+out:
+ free(inodes);
+ return ret;
+}
+
+const struct cmd_group inspect_cmd_group = {
+ inspect_cmd_group_usage, NULL, {
+ { "inode-resolve", cmd_inode_resolve, cmd_inode_resolve_usage,
+ NULL, 0 },
+ { "logical-resolve", cmd_logical_resolve,
+ cmd_logical_resolve_usage, NULL, 0 },
+ { 0, 0, 0, 0, 0 }
+ }
+};
+
+int cmd_inspect(int argc, char **argv)
+{
+ return handle_command_group(&inspect_cmd_group, argc, argv);
+}
diff --git a/scrub.c b/cmds-scrub.c
index 9dca5f6..af855ba 100644
--- a/scrub.c
+++ b/cmds-scrub.c
@@ -34,11 +34,15 @@
#include "ctree.h"
#include "ioctl.h"
-#include "btrfs_cmds.h"
#include "utils.h"
#include "volumes.h"
#include "disk-io.h"
+#include "commands.h"
+
+static const char scrub_cmd_group_usage[] =
+ "btrfs scrub <command> [options] <path>|<device>";
+
#define SCRUB_DATA_FILE "/var/lib/btrfs/scrub.status"
#define SCRUB_PROGRESS_SOCKET_PATH "/var/lib/btrfs/scrub.progress"
#define SCRUB_FILE_VERSION_PREFIX "scrub status"
@@ -1047,6 +1051,9 @@ int mkdir_p(char *path)
return 0;
}
+static const char * const cmd_scrub_start_usage[];
+static const char * const cmd_scrub_resume_usage[];
+
static int scrub_start(int argc, char **argv, int resume)
{
int fdmnt;
@@ -1114,21 +1121,16 @@ static int scrub_start(int argc, char **argv, int resume)
break;
case '?':
default:
- fprintf(stderr, "ERROR: scrub args invalid.\n"
- " -B do not background\n"
- " -d stats per device (-B only)\n"
- " -q quiet\n"
- " -r read only mode\n");
- return 1;
+ usage(resume ? cmd_scrub_resume_usage :
+ cmd_scrub_start_usage);
}
}
/* try to catch most error cases before forking */
- if (optind + 1 != argc) {
- fprintf(stderr, "ERROR: scrub start needs path as last "
- "argument\n");
- return 1;
+ if (check_argc_exact(argc - optind, 1)) {
+ usage(resume ? cmd_scrub_resume_usage :
+ cmd_scrub_start_usage);
}
spc.progress = NULL;
@@ -1473,25 +1475,42 @@ out:
return 0;
}
-int do_scrub_start(int argc, char **argv)
+static const char * const cmd_scrub_start_usage[] = {
+ "btrfs scrub start [-Bdqr] <path>|<device>",
+ "Start a new scrub",
+ "",
+ "-B do not background",
+ "-d stats per device (-B only)",
+ "-q be quiet",
+ "-r read only mode",
+ NULL
+};
+
+static int cmd_scrub_start(int argc, char **argv)
{
return scrub_start(argc, argv, 0);
}
-int do_scrub_resume(int argc, char **argv)
-{
- return scrub_start(argc, argv, 1);
-}
+static const char * const cmd_scrub_cancel_usage[] = {
+ "btrfs scrub cancel <path>|<device>",
+ "Cancel a running scrub",
+ NULL
+};
-int do_scrub_cancel(int argc, char **argv)
+static int cmd_scrub_cancel(int argc, char **argv)
{
- char *path = argv[1];
+ char *path;
int ret;
int fdmnt;
int err;
char mp[BTRFS_PATH_NAME_MAX + 1];
struct btrfs_fs_devices *fs_devices_mnt = NULL;
+ if (check_argc_exact(argc, 2))
+ usage(cmd_scrub_cancel_usage);
+
+ path = argv[1];
+
fdmnt = open_file_or_dir(path);
if (fdmnt < 0) {
fprintf(stderr, "ERROR: scrub cancel failed\n");
@@ -1528,9 +1547,33 @@ again:
return 0;
}
-int do_scrub_status(int argc, char **argv)
+static const char * const cmd_scrub_resume_usage[] = {
+ "btrfs scrub resume [-Bdqr] <path>|<device>",
+ "Resume previously canceled or interrupted scrub",
+ "",
+ "-B do not background",
+ "-d stats per device (-B only)",
+ "-q be quiet",
+ "-r read only mode",
+ NULL
+};
+
+static int cmd_scrub_resume(int argc, char **argv)
{
+ return scrub_start(argc, argv, 1);
+}
+
+static const char * const cmd_scrub_status_usage[] = {
+ "btrfs scrub status [-dR] <path>|<device>",
+ "Show status of running or finished scrub",
+ "",
+ "-d stats per device",
+ "-R print raw stats",
+ NULL
+};
+static int cmd_scrub_status(int argc, char **argv)
+{
char *path;
struct btrfs_ioctl_fs_info_args fi_args;
struct btrfs_ioctl_dev_info_args *di_args = NULL;
@@ -1543,7 +1586,6 @@ int do_scrub_status(int argc, char **argv)
int ret;
int fdmnt;
int i;
- optind = 1;
int print_raw = 0;
int do_stats_per_dev = 0;
char c;
@@ -1551,6 +1593,7 @@ int do_scrub_status(int argc, char **argv)
int fdres = -1;
int err = 0;
+ optind = 1;
while ((c = getopt(argc, argv, "dR")) != -1) {
switch (c) {
case 'd':
@@ -1561,17 +1604,12 @@ int do_scrub_status(int argc, char **argv)
break;
case '?':
default:
- fprintf(stderr, "ERROR: scrub status args invalid.\n"
- " -d stats per device\n");
- return 1;
+ usage(cmd_scrub_status_usage);
}
}
- if (optind + 1 != argc) {
- fprintf(stderr, "ERROR: scrub status needs path as last "
- "argument\n");
- return 1;
- }
+ if (check_argc_exact(argc - optind, 1))
+ usage(cmd_scrub_status_usage);
path = argv[optind];
@@ -1664,3 +1702,18 @@ out:
return err;
}
+
+const struct cmd_group scrub_cmd_group = {
+ scrub_cmd_group_usage, NULL, {
+ { "start", cmd_scrub_start, cmd_scrub_start_usage, NULL, 0 },
+ { "cancel", cmd_scrub_cancel, cmd_scrub_cancel_usage, NULL, 0 },
+ { "resume", cmd_scrub_resume, cmd_scrub_resume_usage, NULL, 0 },
+ { "status", cmd_scrub_status, cmd_scrub_status_usage, NULL, 0 },
+ { 0, 0, 0, 0, 0 }
+ }
+};
+
+int cmd_scrub(int argc, char **argv)
+{
+ return handle_command_group(&scrub_cmd_group, argc, argv);
+}
diff --git a/cmds-subvolume.c b/cmds-subvolume.c
new file mode 100644
index 0000000..68ebd40
--- /dev/null
+++ b/cmds-subvolume.c
@@ -0,0 +1,531 @@
+/*
+ * 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 <unistd.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <libgen.h>
+#include <limits.h>
+
+#include "kerncompat.h"
+#include "ioctl.h"
+
+#include "commands.h"
+
+/* btrfs-list.c */
+int list_subvols(int fd, int print_parent, int get_default);
+int find_updated_files(int fd, u64 root_id, u64 oldest_gen);
+
+static const char subvolume_cmd_group_usage[] =
+ "btrfs subvolume <command> <args>";
+
+/*
+ * test if path is a directory
+ * this function return
+ * 0-> path exists but it is not a directory
+ * 1-> path exists and it is a directory
+ * -1 -> path is unaccessible
+ */
+static int test_isdir(char *path)
+{
+ struct stat st;
+ int res;
+
+ res = stat(path, &st);
+ if(res < 0 )
+ return -1;
+
+ return S_ISDIR(st.st_mode);
+}
+
+static const char * const cmd_subvol_create_usage[] = {
+ "btrfs subvolume create [<dest>/]<name>",
+ "Create a subvolume",
+ "Create a subvolume <name> in <dest>. If <dest> is not given",
+ "subvolume <name> will be created in the current directory.",
+ NULL
+};
+
+static int cmd_subvol_create(int argc, char **argv)
+{
+ int res, fddst, len, e;
+ char *newname;
+ char *dstdir;
+ struct btrfs_ioctl_vol_args args;
+ char *dst;
+
+ if (check_argc_exact(argc, 2))
+ usage(cmd_subvol_create_usage);
+
+ dst = argv[1];
+
+ res = test_isdir(dst);
+ if(res >= 0 ){
+ fprintf(stderr, "ERROR: '%s' exists\n", dst);
+ return 12;
+ }
+
+ newname = strdup(dst);
+ newname = basename(newname);
+ dstdir = strdup(dst);
+ dstdir = dirname(dstdir);
+
+ if( !strcmp(newname,".") || !strcmp(newname,"..") ||
+ strchr(newname, '/') ){
+ fprintf(stderr, "ERROR: uncorrect subvolume name ('%s')\n",
+ newname);
+ return 14;
+ }
+
+ len = strlen(newname);
+ if (len == 0 || len >= BTRFS_VOL_NAME_MAX) {
+ fprintf(stderr, "ERROR: subvolume name too long ('%s)\n",
+ newname);
+ return 14;
+ }
+
+ fddst = open_file_or_dir(dstdir);
+ if (fddst < 0) {
+ fprintf(stderr, "ERROR: can't access to '%s'\n", dstdir);
+ return 12;
+ }
+
+ printf("Create subvolume '%s/%s'\n", dstdir, newname);
+ strncpy(args.name, newname, BTRFS_PATH_NAME_MAX);
+ res = ioctl(fddst, BTRFS_IOC_SUBVOL_CREATE, &args);
+ e = errno;
+
+ close(fddst);
+
+ if(res < 0 ){
+ fprintf( stderr, "ERROR: cannot create subvolume - %s\n",
+ strerror(e));
+ return 11;
+ }
+
+ return 0;
+}
+
+/*
+ * test if path is a subvolume:
+ * this function return
+ * 0-> path exists but it is not a subvolume
+ * 1-> path exists and it is a subvolume
+ * -1 -> path is unaccessible
+ */
+static int test_issubvolume(char *path)
+{
+ struct stat st;
+ int res;
+
+ res = stat(path, &st);
+ if(res < 0 )
+ return -1;
+
+ return (st.st_ino == 256) && S_ISDIR(st.st_mode);
+}
+
+static const char * const cmd_subvol_delete_usage[] = {
+ "btrfs subvolume delete <name>",
+ "Delete a subvolume",
+ NULL
+};
+
+static int cmd_subvol_delete(int argc, char **argv)
+{
+ int res, fd, len, e;
+ struct btrfs_ioctl_vol_args args;
+ char *dname, *vname, *cpath;
+ char *path;
+
+ if (check_argc_exact(argc, 2))
+ usage(cmd_subvol_delete_usage);
+
+ path = argv[1];
+
+ res = test_issubvolume(path);
+ if(res<0){
+ fprintf(stderr, "ERROR: error accessing '%s'\n", path);
+ return 12;
+ }
+ if(!res){
+ fprintf(stderr, "ERROR: '%s' is not a subvolume\n", path);
+ return 13;
+ }
+
+ cpath = realpath(path, 0);
+ dname = strdup(cpath);
+ dname = dirname(dname);
+ vname = strdup(cpath);
+ vname = basename(vname);
+ free(cpath);
+
+ if( !strcmp(vname,".") || !strcmp(vname,"..") ||
+ strchr(vname, '/') ){
+ fprintf(stderr, "ERROR: incorrect subvolume name ('%s')\n",
+ vname);
+ return 14;
+ }
+
+ len = strlen(vname);
+ if (len == 0 || len >= BTRFS_VOL_NAME_MAX) {
+ fprintf(stderr, "ERROR: snapshot name too long ('%s)\n",
+ vname);
+ return 14;
+ }
+
+ fd = open_file_or_dir(dname);
+ if (fd < 0) {
+ close(fd);
+ fprintf(stderr, "ERROR: can't access to '%s'\n", dname);
+ return 12;
+ }
+
+ printf("Delete subvolume '%s/%s'\n", dname, vname);
+ strncpy(args.name, vname, BTRFS_PATH_NAME_MAX);
+ res = ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &args);
+ e = errno;
+
+ close(fd);
+
+ if(res < 0 ){
+ fprintf( stderr, "ERROR: cannot delete '%s/%s' - %s\n",
+ dname, vname, strerror(e));
+ return 11;
+ }
+
+ return 0;
+}
+
+static const char * const cmd_subvol_list_usage[] = {
+ "btrfs subvolume list [-p] <path>",
+ "List subvolumes (and snapshots)",
+ "",
+ "-p print parent ID",
+ NULL
+};
+
+static int cmd_subvol_list(int argc, char **argv)
+{
+ int fd;
+ int ret;
+ int print_parent = 0;
+ char *subvol;
+
+ optind = 1;
+ while(1) {
+ int c = getopt(argc, argv, "p");
+ if (c < 0)
+ break;
+
+ switch(c) {
+ case 'p':
+ print_parent = 1;
+ break;
+ default:
+ usage(cmd_subvol_list_usage);
+ }
+ }
+
+ if (check_argc_exact(argc - optind, 1))
+ usage(cmd_subvol_list_usage);
+
+ subvol = argv[optind];
+
+ ret = test_issubvolume(subvol);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: error accessing '%s'\n", subvol);
+ return 12;
+ }
+ if (!ret) {
+ fprintf(stderr, "ERROR: '%s' is not a subvolume\n", subvol);
+ return 13;
+ }
+
+ fd = open_file_or_dir(subvol);
+ if (fd < 0) {
+ fprintf(stderr, "ERROR: can't access '%s'\n", subvol);
+ return 12;
+ }
+ ret = list_subvols(fd, print_parent, 0);
+ if (ret)
+ return 19;
+ return 0;
+}
+
+static const char * const cmd_snapshot_usage[] = {
+ "btrfs subvolume snapshot [-r] <source> [<dest>/]<name>",
+ "Create a snapshot of the subvolume",
+ "Create a writable/readonly snapshot of the subvolume <source> with",
+ "the name <name> in the <dest> directory",
+ "",
+ "-r create a readonly snapshot",
+ NULL
+};
+
+static int cmd_snapshot(int argc, char **argv)
+{
+ char *subvol, *dst;
+ int res, fd, fddst, len, e, readonly = 0;
+ char *newname;
+ char *dstdir;
+ struct btrfs_ioctl_vol_args_v2 args;
+
+ memset(&args, 0, sizeof(args));
+
+ optind = 1;
+ while (1) {
+ int c = getopt(argc, argv, "r");
+ if (c < 0)
+ break;
+
+ switch (c) {
+ case 'r':
+ readonly = 1;
+ break;
+ default:
+ usage(cmd_snapshot_usage);
+ }
+ }
+
+ if (check_argc_exact(argc - optind, 2))
+ usage(cmd_snapshot_usage);
+
+ subvol = argv[optind];
+ dst = argv[optind + 1];
+
+ res = test_issubvolume(subvol);
+ if(res<0){
+ fprintf(stderr, "ERROR: error accessing '%s'\n", subvol);
+ return 12;
+ }
+ if(!res){
+ fprintf(stderr, "ERROR: '%s' is not a subvolume\n", subvol);
+ return 13;
+ }
+
+ res = test_isdir(dst);
+ if(res == 0 ){
+ fprintf(stderr, "ERROR: '%s' exists and it is not a directory\n", dst);
+ return 12;
+ }
+
+ if(res>0){
+ newname = strdup(subvol);
+ newname = basename(newname);
+ dstdir = dst;
+ }else{
+ newname = strdup(dst);
+ newname = basename(newname);
+ dstdir = strdup(dst);
+ dstdir = dirname(dstdir);
+ }
+
+ if( !strcmp(newname,".") || !strcmp(newname,"..") ||
+ strchr(newname, '/') ){
+ fprintf(stderr, "ERROR: incorrect snapshot name ('%s')\n",
+ newname);
+ return 14;
+ }
+
+ len = strlen(newname);
+ if (len == 0 || len >= BTRFS_VOL_NAME_MAX) {
+ fprintf(stderr, "ERROR: snapshot name too long ('%s)\n",
+ newname);
+ return 14;
+ }
+
+ fddst = open_file_or_dir(dstdir);
+ if (fddst < 0) {
+ fprintf(stderr, "ERROR: can't access to '%s'\n", dstdir);
+ return 12;
+ }
+
+ fd = open_file_or_dir(subvol);
+ if (fd < 0) {
+ close(fddst);
+ fprintf(stderr, "ERROR: can't access to '%s'\n", dstdir);
+ return 12;
+ }
+
+ if (readonly) {
+ args.flags |= BTRFS_SUBVOL_RDONLY;
+ printf("Create a readonly snapshot of '%s' in '%s/%s'\n",
+ subvol, dstdir, newname);
+ } else {
+ printf("Create a snapshot of '%s' in '%s/%s'\n",
+ subvol, dstdir, newname);
+ }
+
+ args.fd = fd;
+ strncpy(args.name, newname, BTRFS_SUBVOL_NAME_MAX);
+ res = ioctl(fddst, BTRFS_IOC_SNAP_CREATE_V2, &args);
+ e = errno;
+
+ close(fd);
+ close(fddst);
+
+ if(res < 0 ){
+ fprintf( stderr, "ERROR: cannot snapshot '%s' - %s\n",
+ subvol, strerror(e));
+ return 11;
+ }
+
+ return 0;
+}
+
+static const char * const cmd_subvol_get_default_usage[] = {
+ "btrfs subvolume get-dafault <path>",
+ "Get the default subvolume of a filesystem",
+ NULL
+};
+
+static int cmd_subvol_get_default(int argc, char **argv)
+{
+ int fd;
+ int ret;
+ char *subvol;
+
+ if (check_argc_exact(argc, 2))
+ usage(cmd_subvol_get_default_usage);
+
+ subvol = argv[1];
+
+ ret = test_issubvolume(subvol);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: error accessing '%s'\n", subvol);
+ return 12;
+ }
+ if (!ret) {
+ fprintf(stderr, "ERROR: '%s' is not a subvolume\n", subvol);
+ return 13;
+ }
+
+ fd = open_file_or_dir(subvol);
+ if (fd < 0) {
+ fprintf(stderr, "ERROR: can't access '%s'\n", subvol);
+ return 12;
+ }
+ ret = list_subvols(fd, 0, 1);
+ if (ret)
+ return 19;
+ return 0;
+}
+
+static const char * const cmd_subvol_set_default_usage[] = {
+ "btrfs subvolume set-dafault <subvolid> <path>",
+ "Set the default subvolume of a filesystem",
+ NULL
+};
+
+static int cmd_subvol_set_default(int argc, char **argv)
+{
+ int ret=0, fd, e;
+ u64 objectid;
+ char *path;
+ char *subvolid;
+
+ if (check_argc_exact(argc, 3))
+ usage(cmd_subvol_set_default_usage);
+
+ subvolid = argv[1];
+ path = argv[2];
+
+ fd = open_file_or_dir(path);
+ if (fd < 0) {
+ fprintf(stderr, "ERROR: can't access to '%s'\n", path);
+ return 12;
+ }
+
+ objectid = (unsigned long long)strtoll(subvolid, NULL, 0);
+ if (errno == ERANGE) {
+ fprintf(stderr, "ERROR: invalid tree id (%s)\n",subvolid);
+ return 30;
+ }
+ ret = ioctl(fd, BTRFS_IOC_DEFAULT_SUBVOL, &objectid);
+ e = errno;
+ close(fd);
+ if( ret < 0 ){
+ fprintf(stderr, "ERROR: unable to set a new default subvolume - %s\n",
+ strerror(e));
+ return 30;
+ }
+ return 0;
+}
+
+static const char * const cmd_find_new_usage[] = {
+ "btrfs subvolume find-new <path> <lastgen>",
+ "List the recently modified files in a filesystem",
+ NULL
+};
+
+static int cmd_find_new(int argc, char **argv)
+{
+ int fd;
+ int ret;
+ char *subvol;
+ u64 last_gen;
+
+ if (check_argc_exact(argc, 3))
+ usage(cmd_find_new_usage);
+
+ subvol = argv[1];
+ last_gen = atoll(argv[2]);
+
+ ret = test_issubvolume(subvol);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: error accessing '%s'\n", subvol);
+ return 12;
+ }
+ if (!ret) {
+ fprintf(stderr, "ERROR: '%s' is not a subvolume\n", subvol);
+ return 13;
+ }
+
+ fd = open_file_or_dir(subvol);
+ if (fd < 0) {
+ fprintf(stderr, "ERROR: can't access '%s'\n", subvol);
+ return 12;
+ }
+ ret = find_updated_files(fd, 0, last_gen);
+ if (ret)
+ return 19;
+ return 0;
+}
+
+const struct cmd_group subvolume_cmd_group = {
+ subvolume_cmd_group_usage, NULL, {
+ { "create", cmd_subvol_create, cmd_subvol_create_usage, NULL, 0 },
+ { "delete", cmd_subvol_delete, cmd_subvol_delete_usage, NULL, 0 },
+ { "list", cmd_subvol_list, cmd_subvol_list_usage, NULL, 0 },
+ { "snapshot", cmd_snapshot, cmd_snapshot_usage, NULL, 0 },
+ { "get-default", cmd_subvol_get_default,
+ cmd_subvol_get_default_usage, NULL, 0 },
+ { "set-default", cmd_subvol_set_default,
+ cmd_subvol_set_default_usage, NULL, 0 },
+ { "find-new", cmd_find_new, cmd_find_new_usage, NULL, 0 },
+ { 0, 0, 0, 0, 0 }
+ }
+};
+
+int cmd_subvolume(int argc, char **argv)
+{
+ return handle_command_group(&subvolume_cmd_group, argc, argv);
+}
diff --git a/commands.h b/commands.h
new file mode 100644
index 0000000..5d024c2
--- /dev/null
+++ b/commands.h
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+
+#define ARGV0_BUF_SIZE 64
+
+struct cmd_struct {
+ const char *token;
+ int (*fn)(int, char **);
+
+ /*
+ * Usage strings
+ *
+ * A NULL-terminated array of the following format:
+ *
+ * usagestr[0] - one-line synopsis (required)
+ * usagestr[1] - one-line short description (required)
+ * usagestr[2..m] - a long (possibly multi-line) description
+ * (optional)
+ * usagestr[m + 1] - an empty line separator (required if at least one
+ * option string is given, not needed otherwise)
+ * usagestr[m + 2..n] - option strings, one option per line
+ * (optional)
+ * usagestr[n + 1] - NULL terminator
+ *
+ * Options (if present) should always (even if there is no long
+ * description) be prepended with an empty line. Supplied strings are
+ * indented but otherwise printed as-is, no automatic wrapping is done.
+ *
+ * Grep for cmd_*_usage[] for examples.
+ */
+ const char * const *usagestr;
+
+ /* should be NULL if token is not a subgroup */
+ const struct cmd_group *next;
+
+ /* if true don't list this token in help listings */
+ int hidden;
+};
+
+struct cmd_group {
+ const char *usagestr;
+ const char *infostr;
+
+ const struct cmd_struct commands[];
+};
+
+/* btrfs.c */
+int prefixcmp(const char *str, const char *prefix);
+
+int check_argc_exact(int nargs, int expected);
+int check_argc_min(int nargs, int expected);
+int check_argc_max(int nargs, int expected);
+
+int handle_command_group(const struct cmd_group *grp, int argc,
+ char **argv);
+
+/* help.c */
+extern const char * const generic_cmd_help_usage[];
+
+void usage(const char * const *usagestr);
+void usage_command(const struct cmd_struct *cmd, int full, int err);
+void usage_command_group(const struct cmd_group *grp, int all, int err);
+
+void help_unknown_token(const char *arg, const struct cmd_group *grp);
+void help_ambiguous_token(const char *arg, const struct cmd_group *grp);
+
+void help_command_group(const struct cmd_group *grp, int argc, char **argv);
+
+/* common.c */
+int open_file_or_dir(const char *fname);
+
+extern const struct cmd_group subvolume_cmd_group;
+extern const struct cmd_group filesystem_cmd_group;
+extern const struct cmd_group device_cmd_group;
+extern const struct cmd_group scrub_cmd_group;
+extern const struct cmd_group inspect_cmd_group;
+
+int cmd_subvolume(int argc, char **argv);
+int cmd_filesystem(int argc, char **argv);
+int cmd_device(int argc, char **argv);
+int cmd_scrub(int argc, char **argv);
+int cmd_inspect(int argc, char **argv);
diff --git a/common.c b/common.c
new file mode 100644
index 0000000..03f6570
--- /dev/null
+++ b/common.c
@@ -0,0 +1,46 @@
+/*
+ * 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 <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <fcntl.h>
+
+int open_file_or_dir(const char *fname)
+{
+ int ret;
+ struct stat st;
+ DIR *dirstream;
+ int fd;
+
+ ret = stat(fname, &st);
+ if (ret < 0) {
+ return -1;
+ }
+ if (S_ISDIR(st.st_mode)) {
+ dirstream = opendir(fname);
+ if (!dirstream) {
+ return -2;
+ }
+ fd = dirfd(dirstream);
+ } else {
+ fd = open(fname, O_RDWR);
+ }
+ if (fd < 0) {
+ return -3;
+ }
+ return fd;
+}
diff --git a/help.c b/help.c
new file mode 100644
index 0000000..932bdf2
--- /dev/null
+++ b/help.c
@@ -0,0 +1,207 @@
+/*
+ * 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 "commands.h"
+
+extern char argv0_buf[ARGV0_BUF_SIZE];
+
+#define USAGE_SHORT 1U
+#define USAGE_LONG 2U
+#define USAGE_OPTIONS 4U
+#define USAGE_LISTING 8U
+
+static int do_usage_one_command(const char * const *usagestr,
+ unsigned int flags, FILE *outf)
+{
+ int pad = 4;
+
+ if (!usagestr || !*usagestr)
+ return -1;
+
+ fprintf(outf, "%s%s\n", (flags & USAGE_LISTING) ? " " : "usage: ",
+ *usagestr++);
+
+ /* a short one-line description (mandatory) */
+ if ((flags & USAGE_SHORT) == 0)
+ return 0;
+ else if (!*usagestr)
+ return -2;
+
+ if (flags & USAGE_LISTING)
+ pad = 8;
+ else
+ fputc('\n', outf);
+
+ fprintf(outf, "%*s%s\n", pad, "", *usagestr++);
+
+ /* a long (possibly multi-line) description (optional) */
+ if (!*usagestr || ((flags & USAGE_LONG) == 0))
+ return 0;
+
+ if (**usagestr)
+ fputc('\n', outf);
+ while (*usagestr && **usagestr)
+ fprintf(outf, "%*s%s\n", pad, "", *usagestr++);
+
+ /* options (optional) */
+ if (!*usagestr || ((flags & USAGE_OPTIONS) == 0))
+ return 0;
+
+ /*
+ * options (if present) should always (even if there is no long
+ * description) be prepended with an empty line, skip it
+ */
+ usagestr++;
+
+ fputc('\n', outf);
+ while (*usagestr)
+ fprintf(outf, "%*s%s\n", pad, "", *usagestr++);
+
+ return 0;
+}
+
+static int usage_command_internal(const char * const *usagestr,
+ const char *token, int full, int lst,
+ FILE *outf)
+{
+ unsigned int flags = USAGE_SHORT;
+ int ret;
+
+ if (full)
+ flags |= USAGE_LONG | USAGE_OPTIONS;
+ if (lst)
+ flags |= USAGE_LISTING;
+
+ ret = do_usage_one_command(usagestr, flags, outf);
+ switch (ret) {
+ case -1:
+ fprintf(outf, "No usage for '%s'\n", token);
+ break;
+ case -2:
+ fprintf(outf, "No short description for '%s'\n", token);
+ break;
+ }
+
+ return ret;
+}
+
+static void usage_command_usagestr(const char * const *usagestr,
+ const char *token, int full, int err)
+{
+ FILE *outf = err ? stderr : stdout;
+ int ret;
+
+ ret = usage_command_internal(usagestr, token, full, 0, outf);
+ if (!ret)
+ fputc('\n', outf);
+}
+
+void usage_command(const struct cmd_struct *cmd, int full, int err)
+{
+ usage_command_usagestr(cmd->usagestr, cmd->token, full, err);
+}
+
+void usage(const char * const *usagestr)
+{
+ usage_command_usagestr(usagestr, NULL, 1, 1);
+ exit(129);
+}
+
+static void usage_command_group_internal(const struct cmd_group *grp, int full,
+ FILE *outf)
+{
+ const struct cmd_struct *cmd = grp->commands;
+ int do_sep = 0;
+
+ for (; cmd->token; cmd++) {
+ if (cmd->hidden)
+ continue;
+
+ if (full && cmd != grp->commands)
+ fputc('\n', outf);
+
+ if (!cmd->next) {
+ if (do_sep) {
+ fputc('\n', outf);
+ do_sep = 0;
+ }
+
+ usage_command_internal(cmd->usagestr, cmd->token, full,
+ 1, outf);
+ continue;
+ }
+
+ /* this is an entry point to a nested command group */
+
+ if (!full && cmd != grp->commands)
+ fputc('\n', outf);
+
+ usage_command_group_internal(cmd->next, full, outf);
+
+ if (!full)
+ do_sep = 1;
+ }
+}
+
+void usage_command_group(const struct cmd_group *grp, int full, int err)
+{
+ FILE *outf = err ? stderr : stdout;
+
+ fprintf(outf, "usage: %s\n\n", grp->usagestr);
+ usage_command_group_internal(grp, full, outf);
+ fputc('\n', outf);
+
+ if (grp->infostr)
+ fprintf(outf, "%s\n", grp->infostr);
+}
+
+void help_unknown_token(const char *arg, const struct cmd_group *grp)
+{
+ fprintf(stderr, "%s: unknown token '%s'\n", argv0_buf, arg);
+ usage_command_group(grp, 0, 1);
+ exit(1);
+}
+
+void help_ambiguous_token(const char *arg, const struct cmd_group *grp)
+{
+ const struct cmd_struct *cmd = grp->commands;
+
+ fprintf(stderr, "%s: ambiguous token '%s'\n", argv0_buf, arg);
+ fprintf(stderr, "\nDid you mean one of these ?\n");
+
+ for (; cmd->token; cmd++) {
+ if (!prefixcmp(cmd->token, arg))
+ fprintf(stderr, "\t%s\n", cmd->token);
+ }
+
+ exit(1);
+}
+
+void help_command_group(const struct cmd_group *grp, int argc, char **argv)
+{
+ int full = 0;
+
+ if (argc > 1) {
+ if (!strcmp(argv[1], "--full"))
+ full = 1;
+ }
+
+ usage_command_group(grp, full, 0);
+}