diff options
author | Arne Jansen <sensille@gmx.net> | 2012-08-07 12:37:54 +0200 |
---|---|---|
committer | Jan Schmidt <list.btrfs@jan-o-sch.net> | 2012-09-04 11:15:49 +0200 |
commit | 89fe5b5f666c247aa3173745fb87c710f3a71a4a (patch) | |
tree | 7bf585132371b0982319624b2eec095005831df4 /cmds-qgroup.c | |
parent | f99b1f1a631f3dfb8dc9ad03b9a00bdc2a231d31 (diff) |
Btrfs progs: quota groups support
Signed-off-by: Jan Schmidt <list.btrfs@jan-o-sch.net>
Signed-off-by: Arne Jansen <sensille@gmx.net>
Diffstat (limited to 'cmds-qgroup.c')
-rw-r--r-- | cmds-qgroup.c | 454 |
1 files changed, 454 insertions, 0 deletions
diff --git a/cmds-qgroup.c b/cmds-qgroup.c new file mode 100644 index 00000000..15534eae --- /dev/null +++ b/cmds-qgroup.c @@ -0,0 +1,454 @@ +/* + * Copyright (C) 2012 STRATO. All rights reserved. + * + * 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/ioctl.h> +#include <unistd.h> +#include <getopt.h> + +#include "ctree.h" +#include "ioctl.h" + +#include "commands.h" + +static const char * const qgroup_cmd_group_usage[] = { + "btrfs qgroup <command> [options] <path>", + NULL +}; + +static u64 parse_qgroupid(char *p) +{ + char *s = strchr(p, '/'); + u64 level; + u64 id; + + if (!s) + return atoll(p); + level = atoll(p); + id = atoll(s + 1); + + return (level << 48) | id; +} + +static int qgroup_assign(int assign, int argc, char **argv) +{ + int ret = 0; + int fd; + int e; + char *path = argv[3]; + struct btrfs_ioctl_qgroup_assign_args args; + + if (check_argc_exact(argc, 4)) + return -1; + + memset(&args, 0, sizeof(args)); + args.assign = assign; + args.src = parse_qgroupid(argv[1]); + args.dst = parse_qgroupid(argv[2]); + + /* + * FIXME src should accept subvol path + */ + if (args.src >= args.dst) { + fprintf(stderr, "ERROR: bad relation requested '%s'\n", path); + return 12; + } + fd = open_file_or_dir(path); + if (fd < 0) { + fprintf(stderr, "ERROR: can't access '%s'\n", path); + return 12; + } + + ret = ioctl(fd, BTRFS_IOC_QGROUP_ASSIGN, &args); + e = errno; + close(fd); + if (ret < 0) { + fprintf(stderr, "ERROR: unable to assign quota group: %s\n", + strerror(e)); + return 30; + } + return 0; +} + +static int qgroup_create(int create, int argc, char **argv) +{ + int ret = 0; + int fd; + int e; + char *path = argv[2]; + struct btrfs_ioctl_qgroup_create_args args; + + if (check_argc_exact(argc, 3)) + return -1; + + memset(&args, 0, sizeof(args)); + args.create = create; + args.qgroupid = parse_qgroupid(argv[1]); + + fd = open_file_or_dir(path); + if (fd < 0) { + fprintf(stderr, "ERROR: can't access '%s'\n", path); + return 12; + } + + ret = ioctl(fd, BTRFS_IOC_QGROUP_CREATE, &args); + e = errno; + close(fd); + if (ret < 0) { + fprintf(stderr, "ERROR: unable to create quota group: %s\n", + strerror(e)); + return 30; + } + return 0; +} + +void print_qgroup_info(u64 objectid, struct btrfs_qgroup_info_item *info) +{ + printf("%llu/%llu %lld %lld\n", objectid >> 48, + objectid & ((1ll << 48) - 1), + btrfs_stack_qgroup_info_referenced(info), + btrfs_stack_qgroup_info_exclusive(info)); +} + +int list_qgroups(int fd) +{ + int ret; + struct btrfs_ioctl_search_args args; + struct btrfs_ioctl_search_key *sk = &args.key; + struct btrfs_ioctl_search_header *sh; + unsigned long off = 0; + unsigned int i; + int e; + struct btrfs_qgroup_info_item *info; + + memset(&args, 0, sizeof(args)); + + /* search in the quota tree */ + sk->tree_id = BTRFS_QUOTA_TREE_OBJECTID; + + /* + * set the min and max to backref keys. The search will + * only send back this type of key now. + */ + sk->max_type = BTRFS_QGROUP_INFO_KEY; + sk->min_type = BTRFS_QGROUP_INFO_KEY; + sk->max_objectid = 0; + sk->max_offset = (u64)-1; + sk->max_transid = (u64)-1; + + /* just a big number, doesn't matter much */ + sk->nr_items = 4096; + + while (1) { + ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args); + e = errno; + if (ret < 0) { + fprintf(stderr, + "ERROR: can't perform the search - %s\n", + strerror(e)); + return ret; + } + /* the ioctl returns the number of item it found in nr_items */ + if (sk->nr_items == 0) + break; + + off = 0; + + /* + * for each item, pull the key out of the header and then + * read the root_ref item it contains + */ + for (i = 0; i < sk->nr_items; i++) { + sh = (struct btrfs_ioctl_search_header *)(args.buf + + off); + off += sizeof(*sh); + + if (sh->objectid != 0) + goto done; + + if (sh->type != BTRFS_QGROUP_INFO_KEY) + goto done; + + info = (struct btrfs_qgroup_info_item *) + (args.buf + off); + print_qgroup_info(sh->offset, info); + + off += sh->len; + + /* + * record the mins in sk so we can make sure the + * next search doesn't repeat this root + */ + sk->min_offset = sh->offset; + } + sk->nr_items = 4096; + /* + * this iteration is done, step forward one qgroup for the next + * ioctl + */ + if (sk->min_offset < (u64)-1) + sk->min_offset++; + else + break; + } + +done: + return ret; +} + +static int parse_limit(const char *p, unsigned long long *s) +{ + char *endptr; + unsigned long long size; + + if (strcasecmp(p, "none") == 0) { + *s = 0; + return 1; + } + size = strtoull(p, &endptr, 10); + switch (*endptr) { + case 'T': + case 't': + size *= 1024; + case 'G': + case 'g': + size *= 1024; + case 'M': + case 'm': + size *= 1024; + case 'K': + case 'k': + size *= 1024; + ++endptr; + break; + case 0: + break; + default: + return 0; + } + + if (*endptr) + return 0; + + *s = size; + + return 1; +} + +static const char * const cmd_qgroup_assign_usage[] = { + "btrfs qgroup assign <src> <dst> <path>", + "Enable subvolume qgroup support for a filesystem.", + NULL +}; + +static int cmd_qgroup_assign(int argc, char **argv) +{ + int ret = qgroup_assign(1, argc, argv); + if (ret < 0) + usage(cmd_qgroup_assign_usage); + return ret; +} + +static const char * const cmd_qgroup_remove_usage[] = { + "btrfs qgroup remove <src> <dst> <path>", + "Remove a subvol from a quota group.", + NULL +}; + +static int cmd_qgroup_remove(int argc, char **argv) +{ + int ret = qgroup_assign(0, argc, argv); + if (ret < 0) + usage(cmd_qgroup_remove_usage); + return ret; +} + +static const char * const cmd_qgroup_create_usage[] = { + "btrfs qgroup create <qgroupid> <path>", + "Create a subvolume quota group.", + NULL +}; + +static int cmd_qgroup_create(int argc, char **argv) +{ + int ret = qgroup_create(1, argc, argv); + if (ret < 0) + usage(cmd_qgroup_create_usage); + return ret; +} + +static const char * const cmd_qgroup_destroy_usage[] = { + "btrfs qgroup destroy <qgroupid> <path>", + "Destroy a subvolume quota group.", + NULL +}; + +static int cmd_qgroup_destroy(int argc, char **argv) +{ + int ret = qgroup_create(0, argc, argv); + if (ret < 0) + usage(cmd_qgroup_destroy_usage); + return ret; +} + +static const char * const cmd_qgroup_show_usage[] = { + "btrfs qgroup show <path>", + "Show all subvolume quota groups.", + NULL +}; + +static int cmd_qgroup_show(int argc, char **argv) +{ + int ret = 0; + int fd; + char *path = argv[1]; + + if (check_argc_exact(argc, 2)) + usage(cmd_qgroup_show_usage); + + fd = open_file_or_dir(path); + if (fd < 0) { + fprintf(stderr, "ERROR: can't access '%s'\n", path); + return 12; + } + + ret = list_qgroups(fd); + if (ret < 0) { + fprintf(stderr, "ERROR: can't list qgroups\n"); + return 30; + } + + close(fd); + + return ret; +} + +static const char * const cmd_qgroup_limit_usage[] = { + "btrfs qgroup limit [options] <size>|none [<qgroupid>] <path>", + "Limit the size of a subvolume quota group.", + "", + "-c limit amount of data after compression", + "-e limit space exclusively assigned to this qgroup", + NULL +}; + +static int cmd_qgroup_limit(int argc, char **argv) +{ + int ret = 0; + int fd; + int e; + char *path; + struct btrfs_ioctl_qgroup_limit_args args; + unsigned long long size; + int compressed = 0; + int exclusive = 0; + + optind = 1; + while (1) { + int c = getopt(argc, argv, "ce"); + if (c < 0) + break; + switch (c) { + case 'c': + compressed = 1; + break; + case 'e': + exclusive = 1; + break; + default: + usage(cmd_qgroup_limit_usage); + } + } + + if (!parse_limit(argv[optind], &size)) { + fprintf(stderr, "Invalid size argument given\n"); + return 1; + } + + memset(&args, 0, sizeof(args)); + args.qgroupid = parse_qgroupid(argv[optind + 1]); + if (size) { + if (compressed) + args.lim.flags |= BTRFS_QGROUP_LIMIT_RFER_CMPR | + BTRFS_QGROUP_LIMIT_EXCL_CMPR; + if (exclusive) { + args.lim.flags |= BTRFS_QGROUP_LIMIT_MAX_EXCL; + args.lim.max_exclusive = size; + } else { + args.lim.flags |= BTRFS_QGROUP_LIMIT_MAX_RFER; + args.lim.max_referenced = size; + } + } + + if (args.qgroupid == 0) { + if (check_argc_exact(argc - optind, 2)) + usage(cmd_qgroup_limit_usage); + path = argv[optind + 1]; + ret = test_issubvolume(path); + if (ret < 0) { + fprintf(stderr, "ERROR: error accessing '%s'\n", path); + return 12; + } + if (!ret) { + fprintf(stderr, "ERROR: '%s' is not a subvolume\n", + path); + return 13; + } + /* + * keep qgroupid at 0, this indicates that the subvolume the + * fd refers to is to be limited + */ + } else { + if (check_argc_exact(argc - optind, 3)) + usage(cmd_qgroup_limit_usage); + path = argv[optind + 2]; + } + + fd = open_file_or_dir(path); + if (fd < 0) { + fprintf(stderr, "ERROR: can't access '%s'\n", path); + return 12; + } + + ret = ioctl(fd, BTRFS_IOC_QGROUP_LIMIT, &args); + e = errno; + close(fd); + if (ret < 0) { + fprintf(stderr, "ERROR: unable to limit requested quota group: " + "%s\n", strerror(e)); + return 30; + } + return 0; +} + +const struct cmd_group qgroup_cmd_group = { + qgroup_cmd_group_usage, NULL, { + { "assign", cmd_qgroup_assign, cmd_qgroup_assign_usage, 0, 0 }, + { "remove", cmd_qgroup_remove, cmd_qgroup_remove_usage, 0, 0 }, + { "create", cmd_qgroup_create, cmd_qgroup_create_usage, 0, 0 }, + { "destroy", cmd_qgroup_destroy, + cmd_qgroup_destroy_usage, 0, 0 }, + { "show", cmd_qgroup_show, cmd_qgroup_show_usage, 0, 0 }, + { "limit", cmd_qgroup_limit, cmd_qgroup_limit_usage, 0, 0 }, + { 0, 0, 0, 0, 0 } + } +}; + +int cmd_qgroup(int argc, char **argv) +{ + return handle_command_group(&qgroup_cmd_group, argc, argv); +} |