diff --git a/Makefile b/Makefile index 278ef4c1..da051976 100644 --- a/Makefile +++ b/Makefile @@ -9,11 +9,12 @@ CFLAGS = -g -O1 -fno-strict-aliasing 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 \ extent-cache.o extent_io.o volumes.o utils.o repair.o \ - qgroup.o raid6.o free-space-cache.o list_sort.o + qgroup.o raid6.o free-space-cache.o list_sort.o props.o cmds_objects = cmds-subvolume.o cmds-filesystem.o cmds-device.o cmds-scrub.o \ cmds-inspect.o cmds-balance.o cmds-send.o cmds-receive.o \ cmds-quota.o cmds-qgroup.o cmds-replace.o cmds-check.o \ - cmds-restore.o cmds-rescue.o chunk-recover.o super-recover.o + cmds-restore.o cmds-rescue.o chunk-recover.o super-recover.o \ + cmds-property.o libbtrfs_objects = send-stream.o send-utils.o rbtree.o btrfs-list.o crc32c.o \ uuid-tree.o libbtrfs_headers = send-stream.h send-utils.h send.h rbtree.h btrfs-list.h \ diff --git a/btrfs.c b/btrfs.c index d5fc738a..98ff6f5f 100644 --- a/btrfs.c +++ b/btrfs.c @@ -250,6 +250,7 @@ static const struct cmd_group btrfs_cmd_group = { { "rescue", cmd_rescue, NULL, &rescue_cmd_group, 0 }, { "restore", cmd_restore, cmd_restore_usage, NULL, 0 }, { "inspect-internal", cmd_inspect, NULL, &inspect_cmd_group, 0 }, + { "property", cmd_property, NULL, &property_cmd_group, 0 }, { "send", cmd_send, cmd_send_usage, NULL, 0 }, { "receive", cmd_receive, cmd_receive_usage, NULL, 0 }, { "quota", cmd_quota, NULL, "a_cmd_group, 0 }, diff --git a/cmds-property.c b/cmds-property.c new file mode 100644 index 00000000..6699e615 --- /dev/null +++ b/cmds-property.c @@ -0,0 +1,459 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include + +#include "commands.h" +#include "props.h" +#include "ctree.h" + +static const char * const property_cmd_group_usage[] = { + "btrfs property get/set/list [-t ] [] [value]", + NULL +}; + +static const char * const cmd_get_usage[] = { + "btrfs property get [-t ] []", + "Gets a property from a btrfs object.", + "If no name is specified, all properties for the given object are", + "printed.", + "A filesystem object can be a the filesystem itself, a subvolume,", + "an inode or a device. The '-t ' option can be used to explicitly", + "specify what type of object you meant. This is only needed when a", + "property could be set for more then one object type. Possible types", + "are s[ubvol], f[ilesystem], i[node] and d[evice].", + NULL +}; + +static const char * const cmd_set_usage[] = { + "btrfs property set [-t ] ", + "Sets a property on a btrfs object.", + "Please see the help of 'btrfs property get' for a description of", + "objects and object types.", + NULL +}; + +static const char * const cmd_list_usage[] = { + "btrfs property list [-t ] ", + "Lists available properties with their descriptions for the given object.", + "Please see the help of 'btrfs property get' for a description of", + "objects and object types.", + NULL +}; + +static int parse_prop(const char *arg, const struct prop_handler *props, + const struct prop_handler **prop_ret) +{ + const struct prop_handler *prop = props; + + for (; prop->name; prop++) { + if (!strcmp(prop->name, arg)) { + *prop_ret = prop; + return 0; + } + } + + return -1; +} + +static int get_fsid(const char *path, u8 *fsid) +{ + int ret; + int fd; + struct btrfs_ioctl_fs_info_args args; + + fd = open(path, O_RDONLY); + if (fd < 0) { + ret = -errno; + fprintf(stderr, "ERROR: open %s failed. %s\n", path, + strerror(-ret)); + goto out; + } + + ret = ioctl(fd, BTRFS_IOC_FS_INFO, &args); + if (ret < 0) { + ret = -errno; + goto out; + } + + memcpy(fsid, args.fsid, BTRFS_FSID_SIZE); + ret = 0; + +out: + if (fd != -1) + close(fd); + return ret; +} + +static int check_btrfs_object(const char *object) +{ + int ret; + u8 fsid[BTRFS_FSID_SIZE]; + + ret = get_fsid(object, fsid); + if (ret < 0) + ret = 0; + else + ret = 1; + return ret; +} + +static int check_is_root(const char *object) +{ + int ret; + u8 fsid[BTRFS_FSID_SIZE]; + u8 fsid2[BTRFS_FSID_SIZE]; + char *tmp; + + tmp = malloc(strlen(object) + 5); + if (!tmp) { + ret = -ENOMEM; + goto out; + } + strcpy(tmp, object); + if (tmp[strlen(tmp) - 1] != '/') + strcat(tmp, "/"); + strcat(tmp, ".."); + + ret = get_fsid(object, fsid); + if (ret < 0) { + fprintf(stderr, "ERROR: get_fsid for %s failed. %s\n", object, + strerror(-ret)); + goto out; + } + + ret = get_fsid(tmp, fsid2); + if (ret < 0) { + ret = 0; + goto out; + } + + if (!memcmp(fsid, fsid2, BTRFS_FSID_SIZE)) { + ret = 0; + goto out; + } + + ret = 1; + +out: + free(tmp); + return ret; +} + +static int count_bits(int v) +{ + unsigned int tmp = (unsigned int)v; + int cnt = 0; + + while (tmp) { + if (tmp & 1) + cnt++; + tmp >>= 1; + } + return cnt; +} + +static int autodetect_object_types(const char *object, int *types_out) +{ + int ret; + int is_btrfs_object; + int types = 0; + struct stat st; + + is_btrfs_object = check_btrfs_object(object); + + ret = lstat(object, &st); + if (ret < 0) { + ret = -errno; + goto out; + } + + if (is_btrfs_object) { + types |= prop_object_inode; + if (st.st_ino == BTRFS_FIRST_FREE_OBJECTID) + types |= prop_object_subvol; + + ret = check_is_root(object); + if (ret < 0) + goto out; + if (ret) + types |= prop_object_root; + } + + if (S_ISBLK(st.st_mode)) + types |= prop_object_dev; + + ret = 0; + *types_out = types; + +out: + return ret; +} + +static int print_prop_help(const struct prop_handler *prop) +{ + fprintf(stdout, "%-20s%s\n", prop->name, prop->desc); + return 0; +} + +static int dump_prop(const struct prop_handler *prop, + const char *object, + int types, + int type, + int name_and_help) +{ + int ret = 0; + + if ((types & type) && (prop->types & type)) { + if (!name_and_help) + ret = prop->handler(type, object, prop->name, NULL); + else + ret = print_prop_help(prop); + } + return ret; +} + +static int dump_props(int types, const char *object, int name_and_help) +{ + int ret; + int i; + int j; + const struct prop_handler *prop; + + for (i = 0; prop_handlers[i].name; i++) { + prop = &prop_handlers[i]; + for (j = 1; j < __prop_object_max; j <<= 1) { + ret = dump_prop(prop, object, types, j, name_and_help); + if (ret < 0) { + ret = 50; + goto out; + } + } + } + + ret = 0; + +out: + return ret; +} + +static int setget_prop(int types, const char *object, + const char *name, const char *value) +{ + int ret; + const struct prop_handler *prop = NULL; + + ret = parse_prop(name, prop_handlers, &prop); + if (ret == -1) { + fprintf(stderr, "ERROR: property is unknown\n"); + ret = 40; + goto out; + } else if (ret) { + fprintf(stderr, "ERROR: parse_prop reported unknown error\n"); + ret = 42; + goto out; + } + + types &= prop->types; + if (!types) { + fprintf(stderr, + "ERROR: object is not compatible with property\n"); + ret = 47; + goto out; + } + + if (count_bits(types) > 1) { + fprintf(stderr, + "ERROR: type of object is ambiguous. Please specify a type by hand.\n"); + ret = 48; + goto out; + } + + if (value && prop->read_only) { + fprintf(stderr, "ERROR: %s is a read-only property.\n", + prop->name); + ret = 51; + goto out; + } + + ret = prop->handler(types, object, name, value); + + if (ret < 0) + ret = 50; + else + ret = 0; + +out: + return ret; + +} + +static void parse_args(int argc, char **argv, + const char * const *usage_str, + int *types, char **object, + char **name, char **value) +{ + int ret; + char *type_str = NULL; + + optind = 1; + while (1) { + int c = getopt(argc, argv, "t:"); + if (c < 0) + break; + + switch (c) { + case 't': + type_str = optarg; + break; + default: + usage(usage_str); + break; + } + } + + *types = 0; + if (type_str) { + if (!strcmp(type_str, "s") || !strcmp(type_str, "subvol")) { + *types = prop_object_subvol; + } else if (!strcmp(type_str, "f") || + !strcmp(type_str, "filesystem")) { + *types = prop_object_root; + } else if (!strcmp(type_str, "i") || + !strcmp(type_str, "inode")) { + *types = prop_object_inode; + } else if (!strcmp(type_str, "d") || + !strcmp(type_str, "device")) { + *types = prop_object_dev; + } else { + fprintf(stderr, "ERROR: invalid object type.\n"); + usage(usage_str); + } + } + + if (object && optind < argc) + *object = argv[optind++]; + if (name && optind < argc) + *name = argv[optind++]; + if (value && optind < argc) + *value = argv[optind++]; + + if (optind != argc) { + fprintf(stderr, "ERROR: invalid arguments.\n"); + usage(usage_str); + } + + if (!*types && object && *object) { + ret = autodetect_object_types(*object, types); + if (ret < 0) { + fprintf(stderr, + "ERROR: failed to detect object type. %s\n", + strerror(-ret)); + usage(usage_str); + } + if (!*types) { + fprintf(stderr, + "ERROR: object is not a btrfs object.\n"); + usage(usage_str); + } + } +} + +static int cmd_get(int argc, char **argv) +{ + int ret; + char *object; + char *name = NULL; + int types = 0; + + if (check_argc_min(argc, 2) || check_argc_max(argc, 4)) + usage(cmd_get_usage); + + parse_args(argc, argv, cmd_get_usage, &types, &object, &name, NULL); + if (!object) { + fprintf(stderr, "ERROR: invalid arguments.\n"); + usage(cmd_set_usage); + } + + if (name) + ret = setget_prop(types, object, name, NULL); + else + ret = dump_props(types, object, 0); + + return ret; +} + +static int cmd_set(int argc, char **argv) +{ + int ret; + char *object; + char *name; + char *value; + int types = 0; + + if (check_argc_min(argc, 4) || check_argc_max(argc, 5)) + usage(cmd_set_usage); + + parse_args(argc, argv, cmd_set_usage, &types, &object, &name, &value); + if (!object || !name || !value) { + fprintf(stderr, "ERROR: invalid arguments.\n"); + usage(cmd_set_usage); + } + + ret = setget_prop(types, object, name, value); + + return ret; +} + +static int cmd_list(int argc, char **argv) +{ + int ret; + char *object = NULL; + int types = 0; + + if (check_argc_min(argc, 2) || check_argc_max(argc, 3)) + usage(cmd_list_usage); + + parse_args(argc, argv, cmd_list_usage, &types, &object, NULL, NULL); + if (!object) { + fprintf(stderr, "ERROR: invalid arguments.\n"); + usage(cmd_set_usage); + } + + ret = dump_props(types, object, 1); + + return ret; +} + +const struct cmd_group property_cmd_group = { + property_cmd_group_usage, NULL, { + { "get", cmd_get, cmd_get_usage, NULL, 0 }, + { "set", cmd_set, cmd_set_usage, NULL, 0 }, + { "list", cmd_list, cmd_list_usage, NULL, 0 }, + { 0, 0, 0, 0, 0 }, + } +}; + +int cmd_property(int argc, char **argv) +{ + return handle_command_group(&property_cmd_group, argc, argv); +} diff --git a/commands.h b/commands.h index b791d687..23c1201f 100644 --- a/commands.h +++ b/commands.h @@ -87,6 +87,7 @@ extern const struct cmd_group balance_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; +extern const struct cmd_group property_cmd_group; extern const struct cmd_group quota_cmd_group; extern const struct cmd_group qgroup_cmd_group; extern const struct cmd_group replace_cmd_group; @@ -109,6 +110,7 @@ int cmd_check(int argc, char **argv); int cmd_chunk_recover(int argc, char **argv); int cmd_super_recover(int argc, char **argv); int cmd_inspect(int argc, char **argv); +int cmd_property(int argc, char **argv); int cmd_send(int argc, char **argv); int cmd_receive(int argc, char **argv); int cmd_quota(int argc, char **argv); diff --git a/props.c b/props.c new file mode 100644 index 00000000..763fd57e --- /dev/null +++ b/props.c @@ -0,0 +1,110 @@ +/* + * 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 +#include +#include +#include + +#include "ctree.h" +#include "commands.h" +#include "utils.h" +#include "props.h" + +static int prop_read_only(enum prop_object_type type, + const char *object, + const char *name, + const char *value) +{ + int ret = 0; + int fd = -1; + u64 flags = 0; + + fd = open(object, O_RDONLY); + if (fd < 0) { + ret = -errno; + fprintf(stderr, "ERROR: open %s failed. %s\n", + object, strerror(-ret)); + goto out; + } + + ret = ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags); + if (ret < 0) { + ret = -errno; + fprintf(stderr, "ERROR: failed to get flags for %s. %s\n", + object, strerror(-ret)); + goto out; + } + + if (!value) { + if (flags & BTRFS_SUBVOL_RDONLY) + fprintf(stdout, "ro=true\n"); + else + fprintf(stdout, "ro=false\n"); + ret = 0; + goto out; + } + + if (!strcmp(value, "true")) { + flags |= BTRFS_SUBVOL_RDONLY; + } else if (!strcmp(value, "false")) { + flags = flags & ~BTRFS_SUBVOL_RDONLY; + } else { + ret = -EINVAL; + fprintf(stderr, "ERROR: invalid value for property.\n"); + goto out; + } + + ret = ioctl(fd, BTRFS_IOC_SUBVOL_SETFLAGS, &flags); + if (ret < 0) { + ret = -errno; + fprintf(stderr, "ERROR: failed to set flags for %s. %s\n", + object, strerror(-ret)); + goto out; + } + +out: + if (fd != -1) + close(fd); + return ret; +} + +static int prop_label(enum prop_object_type type, + const char *object, + const char *name, + const char *value) +{ + int ret; + + if (value) { + ret = set_label((char *) object, (char *) value); + } else { + char label[BTRFS_LABEL_SIZE]; + + ret = get_label((char *) object, label); + if (!ret) + fprintf(stdout, "label=%s\n", label); + } + + return ret; +} + +const struct prop_handler prop_handlers[] = { + {"ro", "Set/get read-only flag of subvolume.", 0, prop_object_subvol, + prop_read_only}, + {"label", "Set/get label of device.", 0, prop_object_dev, prop_label}, + {0, 0, 0, 0, 0} +}; diff --git a/props.h b/props.h new file mode 100644 index 00000000..faa4410e --- /dev/null +++ b/props.h @@ -0,0 +1,43 @@ +/* + * 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. + */ + +#ifndef PROPS_H_ +#define PROPS_H_ + +enum prop_object_type { + prop_object_dev = (1 << 0), + prop_object_root = (1 << 1), + prop_object_subvol = (1 << 2), + prop_object_inode = (1 << 3), + __prop_object_max, +}; + +typedef int (*prop_handler_t)(enum prop_object_type type, + const char *object, + const char *name, + const char *value); + +struct prop_handler { + const char *name; + const char *desc; + int read_only; + int types; + prop_handler_t handler; +}; + +extern const struct prop_handler prop_handlers[]; + +#endif /* PROPS_H_ */