From 900d56f1b996ab2c217542cf0a5b3d415c1698dc Mon Sep 17 00:00:00 2001 From: David Sterba Date: Thu, 29 Sep 2022 16:07:02 +0200 Subject: [PATCH] btrfs-progs: add new group reflink and command Add initial reflink group with example command 'clone' to test the interface. Work in progress, experimental build needed. Issue: #396 Signed-off-by: David Sterba --- Makefile | 1 + btrfs.c | 3 + cmds/commands.h | 1 + cmds/reflink.c | 204 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 209 insertions(+) create mode 100644 cmds/reflink.c diff --git a/Makefile b/Makefile index 76a97725..23b1fae1 100644 --- a/Makefile +++ b/Makefile @@ -209,6 +209,7 @@ cmds_objects = cmds/subvolume.o cmds/subvolume-list.o \ cmds/rescue-super-recover.o \ cmds/property.o cmds/filesystem-usage.o cmds/inspect-dump-tree.o \ cmds/inspect-dump-super.o cmds/inspect-tree-stats.o cmds/filesystem-du.o \ + cmds/reflink.o \ mkfs/common.o check/mode-common.o check/mode-lowmem.o \ check/clear-cache.o diff --git a/btrfs.c b/btrfs.c index a1db7109..0f817872 100644 --- a/btrfs.c +++ b/btrfs.c @@ -343,6 +343,9 @@ static const struct cmd_group btrfs_cmd_group = { &cmd_struct_qgroup, &cmd_struct_quota, &cmd_struct_receive, +#if EXPERIMENTAL + &cmd_struct_reflink, +#endif &cmd_struct_replace, &cmd_struct_rescue, &cmd_struct_restore, diff --git a/cmds/commands.h b/cmds/commands.h index 42291a35..5ab7c881 100644 --- a/cmds/commands.h +++ b/cmds/commands.h @@ -145,6 +145,7 @@ DECLARE_COMMAND(inspect_tree_stats); DECLARE_COMMAND(property); DECLARE_COMMAND(send); DECLARE_COMMAND(receive); +DECLARE_COMMAND(reflink); DECLARE_COMMAND(quota); DECLARE_COMMAND(qgroup); DECLARE_COMMAND(replace); diff --git a/cmds/reflink.c b/cmds/reflink.c new file mode 100644 index 00000000..5ed9a127 --- /dev/null +++ b/cmds/reflink.c @@ -0,0 +1,204 @@ +/* + * 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 "kerncompat.h" +#include +#include +#include +#include +#include +#include "kernel-lib/list.h" +#include "common/messages.h" +#include "common/open-utils.h" +#include "common/parse-utils.h" +#include "common/help.h" +#include "cmds/commands.h" + +static const char * const reflink_cmd_group_usage[] = { + "btrfs reflink ", + NULL +}; + +static const char * const cmd_reflink_clone_usage[] = { + "btrfs reflink clone [options] source target", + "Lightweight file copy", + "Lightweight file copy, extents are cloned and COW if changed. Multiple", + "ranges can be specified, source and target file can be the same,", + "ranges can be combined from both and processed in the order.", + "Options:", + " -s RANGESPEC take range spec from the source file", + " -t RANGESPEC take range from the target file", + "", + "RANGESPEC has three parts and is of format SRCOFF:LENGTH:DESTOFF,", + "where SRCOFF is offset in the respective file, LENGTH is range length,", + "DESTOFF is offset in the destination file (always target).", + "All three values accept the size suffix (k/m/g/t/p/e, case insensitive).", + NULL +}; + +struct reflink_range { + struct list_head list; + u64 from; + u64 length; + u64 to; + bool same_file; +}; + +void parse_reflink_range(const char *str, u64 *from, u64 *length, u64 *to) +{ + char tmp[512]; + int i; + + /* Parse from */ + i = 0; + while (*str && i < sizeof(tmp) && *str != ':') + tmp[i++] = *str++; + if (i >= sizeof(tmp)) { + error("range spec too long"); + exit(1); + } + if (*str != ':') { + error("wrong range spec near %s", str); + exit(1); + } + *from = parse_size_from_string(tmp); + str++; + + /* Parse length */ + i = 0; + while (*str && i < sizeof(tmp) && *str != ':') + tmp[i++] = *str++; + if (i >= sizeof(tmp)) { + error("range spec too long"); + exit(1); + } + if (*str != ':') { + error("wrong range spec near %s", str); + exit(1); + } + *length = parse_size_from_string(tmp); + str++; + + /* Parse to, until end of string */ + *to = parse_size_from_string(str); +} + +static int reflink_apply_range(int fd_in, int fd_out, const struct reflink_range *range) +{ + return -EOPNOTSUPP; +} + +static int cmd_reflink_clone(const struct cmd_struct *cmd, int argc, char **argv) +{ + int ret = 0; + LIST_HEAD(ranges); + struct reflink_range *range = NULL, *tmp, whole; + const char *source, *target; + int fd_source = -1, fd_target = -1; + + optind = 0; + while (1) { + int c = getopt(argc, argv, "r:s:"); + bool same_file = false; + + if (c < 0) + break; + + switch (c) { + case 's': + same_file = true; + /* fallthrough */ + case 'r': + range = malloc(sizeof(struct reflink_range)); + if (!range) { + error("not enough memory"); + return 1; + } + INIT_LIST_HEAD(&range->list); + range->same_file = same_file; + parse_reflink_range(optarg, &range->from, &range->length, &range->to); + list_add_tail(&range->list, &ranges); + pr_verbose(LOG_DEBUG, "ADD: %llu:%llu:%llu\n", range->from, range->length, range->to); + break; + default: + usage_unknown_option(cmd, argv); + } + } + + if (check_argc_exact(argc - optind, 2)) + return 1; + + source = argv[optind]; + target = argv[optind + 1]; + pr_verbose(LOG_DEFAULT, "Source: %s\n", source); + pr_verbose(LOG_DEFAULT, "Target: %s\n", target); + + fd_source = open(source, O_RDONLY); + if (fd_source == -1) { + error("cannot open source file: %m"); + ret = 1; + goto out; + } + if (fd_target == -1) { + error("cannot open target file: %m"); + ret = 1; + goto out; + } + + if (list_empty(&ranges)) { + struct stat st; + + ret = fstat(fd_source, &st); + if (ret == -1) { + error("cannot fstat target file to determine size: %m"); + goto out; + } + + pr_verbose(LOG_DEFAULT, "No ranges, use entire flile\n"); + whole.from = 0; + whole.length = st.st_size; + whole.to = 0; + ret = reflink_apply_range(fd_source, fd_target, &whole); + } else { + list_for_each_entry(range, &ranges, list) { + pr_verbose(LOG_DEFAULT, "Range: %llu:%llu:%llu\n", range->from, range->length, range->to); + ret = reflink_apply_range(fd_source, fd_target, range); + } + + } +out: + list_for_each_entry_safe(range, tmp, &ranges, list) { + free(range); + } + if (fd_source != -1) + close(fd_source); + if (fd_target != -1) + close(fd_target); + return !!ret; +} +static DEFINE_SIMPLE_COMMAND(reflink_clone, "clone"); + +static const char reflink_cmd_group_info[] = +"reflink, shallow file copies: clone"; + +static const struct cmd_group reflink_cmd_group = { + reflink_cmd_group_usage, reflink_cmd_group_info, { + &cmd_struct_reflink_clone, + NULL + } +}; + +DEFINE_GROUP_COMMAND_TOKEN(reflink);