btrfs-progs/cmds-subvolume.c
Miao Xie 162df1e30c Btrfs-progs: restructure list_subvolumes
The current code of list_subvols() has very bad scalability, if we want to
add new filter conditions or new sort methods, we have to modify lots of code.

Beside that, the most code of list_snapshots() is similar to list_subvols(),

So I restructure list_subvols(), and split the subvolume filter function,
the subvolume sort function and the output function from list_subvols().
In order to implement it, we defined some importtant structures:
struct btrfs_list_filter {
	btrfs_list_filter_func filter_func;
	void *data;
};

struct btrfs_list_comparer {
	btrfs_list_comp_func comp_func;
	int is_descending;
};

struct {
	char	*name;
	char	*column_name;
	int	need_print;
} btrfs_list_columns[];

If we want to add a new filter condition, we can choose a suitable filter
function, or implement a new filter function[1], and add it into a set of
the filters, and then pass the filter set into list_subvols(). We also can
mix several filters (just add those filters into the set, and pass the set
into list_subvols()) if the users specify two or more filter conditions.

The subvolume sort function is similar to the subvolume filter function. The
differentiation is the order of comparers in the array which is passed into
list_subvols() show us the priority of the sort methods.

The output function is different with the above two functions, we define a
array to manage all the columns that can be outputed, and use a member variant
(->need_print) to control the output of the relative column. Some columns are
outputed by default. But we can change it according to the requirement of the
users.

After appling this patch, we needn't implement a independent list_snapshots()
function, just pass a filter function which is used to identify the snapshot
into list_subvols().

[1]: If we implement new filter functions or compare functions, we must add
them into the array all_filter_funcs or the array all_comp_funcs, and modify
the relative enum variants(btrfs_list_filter_enum, btrfs_list_comp_enum).

Signed-off-by: Miao Xie <miaox@cn.fujitsu.com>
2012-10-04 16:26:32 -04:00

646 lines
14 KiB
C

/*
* 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 "qgroup.h"
#include "ctree.h"
#include "commands.h"
#include "btrfs-list.h"
static const char * const subvolume_cmd_group_usage[] = {
"btrfs subvolume <command> <args>",
NULL
};
/*
* 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;
char *dst;
struct btrfs_qgroup_inherit *inherit = NULL;
optind = 1;
while (1) {
int c = getopt(argc, argv, "c:i:r");
if (c < 0)
break;
switch (c) {
case 'c':
res = qgroup_inherit_add_copy(&inherit, optarg, 0);
if (res)
return res;
break;
case 'i':
res = qgroup_inherit_add_group(&inherit, optarg);
if (res)
return res;
break;
default:
usage(cmd_subvol_create_usage);
}
}
if (check_argc_exact(argc - optind, 1))
usage(cmd_subvol_create_usage);
dst = argv[optind];
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);
if (inherit) {
struct btrfs_ioctl_vol_args_v2 args;
memset(&args, 0, sizeof(args));
strncpy(args.name, newname, BTRFS_SUBVOL_NAME_MAX);
args.name[BTRFS_SUBVOL_NAME_MAX-1] = 0;
args.flags |= BTRFS_SUBVOL_QGROUP_INHERIT;
args.size = qgroup_inherit_size(inherit);
args.qgroup_inherit = inherit;
res = ioctl(fddst, BTRFS_IOC_SUBVOL_CREATE_V2, &args);
} else {
struct btrfs_ioctl_vol_args args;
memset(&args, 0, sizeof(args));
strncpy(args.name, newname, BTRFS_SUBVOL_NAME_MAX);
args.name[BTRFS_SUBVOL_NAME_MAX-1] = 0;
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;
}
free(inherit);
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
*/
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);
args.name[BTRFS_PATH_NAME_MAX-1] = 0;
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 [-pu] [-s 0|1] <path>",
"List subvolumes (and snapshots)",
"",
"-p print parent ID",
"-u print the uuid of subvolumes (and snapshots)",
"-s value list snapshots with generation in ascending/descending order",
" (1: ascending, 0: descending)",
NULL
};
static int cmd_subvol_list(int argc, char **argv)
{
struct btrfs_list_filter_set *filter_set;
struct btrfs_list_comparer_set *comparer_set;
int fd;
int ret;
int order;
char *subvol;
filter_set = btrfs_list_alloc_filter_set();
comparer_set = btrfs_list_alloc_comparer_set();
optind = 1;
while(1) {
int c = getopt(argc, argv, "ps:u");
if (c < 0)
break;
switch(c) {
case 'p':
btrfs_list_setup_print_column(BTRFS_LIST_PARENT);
break;
case 's':
order = atoi(optarg);
btrfs_list_setup_filter(&filter_set,
BTRFS_LIST_FILTER_SNAPSHOT_ONLY,
0);
btrfs_list_setup_comparer(&comparer_set,
BTRFS_LIST_COMP_OGEN,
!order);
btrfs_list_setup_print_column(BTRFS_LIST_OGENERATION);
btrfs_list_setup_print_column(BTRFS_LIST_OTIME);
break;
case 'u':
btrfs_list_setup_print_column(BTRFS_LIST_UUID);
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 = btrfs_list_subvols(fd, filter_set, comparer_set);
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;
struct btrfs_qgroup_inherit *inherit = NULL;
optind = 1;
memset(&args, 0, sizeof(args));
while (1) {
int c = getopt(argc, argv, "c:i:r");
if (c < 0)
break;
switch (c) {
case 'c':
res = qgroup_inherit_add_copy(&inherit, optarg, 0);
if (res)
return res;
break;
case 'i':
res = qgroup_inherit_add_group(&inherit, optarg);
if (res)
return res;
break;
case 'r':
readonly = 1;
break;
case 'x':
res = qgroup_inherit_add_copy(&inherit, optarg, 1);
if (res)
return res;
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;
if (inherit) {
args.flags |= BTRFS_SUBVOL_QGROUP_INHERIT;
args.size = qgroup_inherit_size(inherit);
args.qgroup_inherit = inherit;
}
strncpy(args.name, newname, BTRFS_SUBVOL_NAME_MAX);
args.name[BTRFS_SUBVOL_NAME_MAX-1] = 0;
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;
}
free(inherit);
return 0;
}
static const char * const cmd_subvol_get_default_usage[] = {
"btrfs subvolume get-default <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;
struct btrfs_list_filter_set *filter_set;
u64 default_id;
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 = btrfs_list_get_default_subvolume(fd, &default_id);
if (ret) {
fprintf(stderr, "ERROR: can't perform the search - %s\n",
strerror(errno));
return ret;
}
if (default_id == 0) {
fprintf(stderr, "ERROR: 'default' dir item not found\n");
return ret;
}
/* no need to resolve roots if FS_TREE is default */
if (default_id == BTRFS_FS_TREE_OBJECTID) {
printf("ID 5 (FS_TREE)\n");
return ret;
}
filter_set = btrfs_list_alloc_filter_set();
btrfs_list_setup_filter(&filter_set, BTRFS_LIST_FILTER_ROOTID,
default_id);
ret = btrfs_list_subvols(fd, filter_set, NULL);
if (ret)
return 19;
return 0;
}
static const char * const cmd_subvol_set_default_usage[] = {
"btrfs subvolume set-default <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 = btrfs_list_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);
}