linux-btrfs.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC PATCH 0/6] btrfs send stream version 2
@ 2018-05-09  2:06 Howard McLauchlan
  2018-05-09  2:06 ` [RFC PATCH 1/6] btrfs: send, bump stream version Howard McLauchlan
                   ` (5 more replies)
  0 siblings, 6 replies; 10+ messages in thread
From: Howard McLauchlan @ 2018-05-09  2:06 UTC (permalink / raw)
  To: linux-btrfs
  Cc: Chris Mason, Josef Bacik, David Sterba, Filipe Manana,
	Omar Sandoval, Howard McLauchlan

In trying to implement support for inode flags in send/receive, the need for
proper versioning/compatibility came up. I found some of Filipe's old patches
[1] for send stream v2 and rebased them on 4.17-rc4. My chattr changes are
landed on top and also gated behind send stream v2. Similar was done for
btrfs-progs (v4.16.1) [2] and a relevant xfstest (master) [3].

Unfortunately, since Filipe's changes are about 4 years old, rebasing required
quite a few "I guess this is how it works" guesses. A critical eye would be
greatly appreciated.

As of 4.17-rc4, commit a6aa10c70bf7 ("Btrfs: send, fix missing truncate for
inode with prealloc extent past eof") is causing some strange behaviour with the
rebased changes (this can be best seen in the xfstest output for btrfs/160).
Specifically, the hole/data structure is consistent between sender/receiver, but
the receiver is missing some information in fiemap for prealloc extents past
eof. Filipe mentioned that his fix was considering the lack of fallocate command
in send, so that's something we can look at if this patch set gets anywhere.

A few things I was unsure about:

- I couldn't use open_inode_for_write() in process_chattr() in btrfs-progs.
  Ended up having to open() with O_RDONLY. This is probably because I was
  setting immutable on the inode for my test cases.
- Filipe's original patches had BTRFS_SEND_{A/C}_INODE_FLAGS as placeholders,
  but I'd already implemented everything as BTRFS_SEND_{A/C}_CHATTR, since
  send_chown(), send_chmod(), etc. seemed to set a precedent in naming. If this
  needs to be changed let me know:

As of v4.17-rc4, these changes pass all "send" group xfstests.

Cheers, Howard

1: https://www.spinics.net/lists/linux-btrfs/msg35169.html
2: https://patchwork.kernel.org/patch/4021491/
3: https://patchwork.kernel.org/patch/4004861/

Filipe David Borba Manana (5):
  btrfs: send, bump stream version
  btrfs: send, implement total data size command to allow for progress
    estimation
  btrfs: send, use fallocate command to punch holes
  btrfs: send, use fallocate command to allocate extents
  btrfs: add send_stream_version attribute to sysfs

Howard McLauchlan (1):
  btrfs: add chattr support for send/receive

 fs/btrfs/ctree.h           |   2 +
 fs/btrfs/ioctl.c           |   2 +-
 fs/btrfs/send.c            | 496 +++++++++++++++++++++++++++++++------
 fs/btrfs/send.h            |  26 +-
 fs/btrfs/sysfs.c           |  29 +++
 include/uapi/linux/btrfs.h |  21 +-
 6 files changed, 495 insertions(+), 81 deletions(-)

-- 
2.17.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [RFC PATCH 1/6] btrfs: send, bump stream version
  2018-05-09  2:06 [RFC PATCH 0/6] btrfs send stream version 2 Howard McLauchlan
@ 2018-05-09  2:06 ` Howard McLauchlan
  2018-05-16 18:25   ` Omar Sandoval
  2018-05-09  2:06 ` [RFC PATCH 2/6] btrfs: send, implement total data size command to allow for progress estimation Howard McLauchlan
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 10+ messages in thread
From: Howard McLauchlan @ 2018-05-09  2:06 UTC (permalink / raw)
  To: linux-btrfs
  Cc: Chris Mason, Josef Bacik, David Sterba, Filipe Manana,
	Omar Sandoval, Filipe David Borba Manana, Howard McLauchlan

From: Filipe David Borba Manana <fdmanana@gmail.com>

This increases the send stream version from version 1 to version 2, adding
new commands:

1) total data size - used to tell the receiver how much file data the stream
   will add or update;

2) fallocate - used to pre-allocate space for files and to punch holes in files;

3) inode set flags;

4) set inode otime.

This is preparation work for subsequent changes that implement the new features.

A version 2 stream is only produced if the send ioctl caller passes in one of the
new flags (BTRFS_SEND_FLAG_CALCULATE_DATA_SIZE | BTRFS_SEND_FLAG_STREAM_V2), meaning
old clients are unaffected.

[Howard: rebased on 4.17-rc4]
Signed-off-by: Howard McLauchlan <hmclauchlan@fb.com>
Signed-off-by: Filipe David Borba Manana <fdmanana@gmail.com>
---
 fs/btrfs/send.c            |  7 ++++++-
 fs/btrfs/send.h            | 21 ++++++++++++++++++++-
 include/uapi/linux/btrfs.h | 21 ++++++++++++++++++++-
 3 files changed, 46 insertions(+), 3 deletions(-)

diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c
index c0074d2d7d6d..eccd69387065 100644
--- a/fs/btrfs/send.c
+++ b/fs/btrfs/send.c
@@ -649,7 +649,10 @@ static int send_header(struct send_ctx *sctx)
 	struct btrfs_stream_header hdr;
 
 	strcpy(hdr.magic, BTRFS_SEND_STREAM_MAGIC);
-	hdr.version = cpu_to_le32(BTRFS_SEND_STREAM_VERSION);
+	if (sctx->flags & BTRFS_SEND_FLAG_STREAM_V2)
+		hdr.version = cpu_to_le32(BTRFS_SEND_STREAM_VERSION_2);
+	else
+		hdr.version = cpu_to_le32(BTRFS_SEND_STREAM_VERSION_1);
 
 	return write_buf(sctx->send_filp, &hdr, sizeof(hdr),
 					&sctx->send_off);
@@ -6535,6 +6538,8 @@ long btrfs_ioctl_send(struct file *mnt_file, struct btrfs_ioctl_send_args *arg)
 	INIT_LIST_HEAD(&sctx->name_cache_list);
 
 	sctx->flags = arg->flags;
+	if (sctx->flags & BTRFS_SEND_FLAG_CALCULATE_DATA_SIZE)
+		sctx->flags |= BTRFS_SEND_FLAG_STREAM_V2;
 
 	sctx->send_filp = fget(arg->send_fd);
 	if (!sctx->send_filp) {
diff --git a/fs/btrfs/send.h b/fs/btrfs/send.h
index ead397f7034f..a9b5489d690e 100644
--- a/fs/btrfs/send.h
+++ b/fs/btrfs/send.h
@@ -10,7 +10,8 @@
 #include "ctree.h"
 
 #define BTRFS_SEND_STREAM_MAGIC "btrfs-stream"
-#define BTRFS_SEND_STREAM_VERSION 1
+#define BTRFS_SEND_STREAM_VERSION_1 1
+#define BTRFS_SEND_STREAM_VERSION_2 2
 
 #define BTRFS_SEND_BUF_SIZE SZ_64K
 #define BTRFS_SEND_READ_SIZE (48 * SZ_1K)
@@ -77,6 +78,15 @@ enum btrfs_send_cmd {
 
 	BTRFS_SEND_C_END,
 	BTRFS_SEND_C_UPDATE_EXTENT,
+
+	/*
+	 * The following commands were added in stream version 2.
+	 */
+	BTRFS_SEND_C_TOTAL_DATA_SIZE,
+	BTRFS_SEND_C_FALLOCATE,
+	BTRFS_SEND_C_INODE_SET_FLAGS,
+	BTRFS_SEND_C_UTIMES2, /* Same as UTIMES, but it includes OTIME too. */
+
 	__BTRFS_SEND_C_MAX,
 };
 #define BTRFS_SEND_C_MAX (__BTRFS_SEND_C_MAX - 1)
@@ -115,10 +125,19 @@ enum {
 	BTRFS_SEND_A_CLONE_OFFSET,
 	BTRFS_SEND_A_CLONE_LEN,
 
+	/*
+	 * The following attributes were added in stream version 2.
+	 */
+	BTRFS_SEND_A_FALLOCATE_FLAGS,
+	BTRFS_SEND_A_INODE_FLAGS,
+
 	__BTRFS_SEND_A_MAX,
 };
 #define BTRFS_SEND_A_MAX (__BTRFS_SEND_A_MAX - 1)
 
+#define BTRFS_SEND_A_FALLOCATE_FLAG_KEEP_SIZE   (1 << 0)
+#define BTRFS_SEND_A_FALLOCATE_FLAG_PUNCH_HOLE  (1 << 1)
+
 #ifdef __KERNEL__
 long btrfs_ioctl_send(struct file *mnt_file, struct btrfs_ioctl_send_args *arg);
 #endif
diff --git a/include/uapi/linux/btrfs.h b/include/uapi/linux/btrfs.h
index c8d99b9ca550..ed63176660d2 100644
--- a/include/uapi/linux/btrfs.h
+++ b/include/uapi/linux/btrfs.h
@@ -711,10 +711,29 @@ struct btrfs_ioctl_received_subvol_args {
  */
 #define BTRFS_SEND_FLAG_OMIT_END_CMD		0x4
 
+/*
+ * Calculate the amount (in bytes) of new file data between the send and
+ * parent snapshots, or in case of a full send, the total amount of file data
+ * we will send.
+ * This corresponds to the sum of the data lengths of each write, clone and
+ * fallocate commands that are sent through the send stream. The receiving end
+ * can use this information to compute progress.
+ *
+ * Added in send stream version 2, and implies producing a version 2 stream.
+ */
+#define BTRFS_SEND_FLAG_CALCULATE_DATA_SIZE	0x8
+
+/*
+ * Used by a client to request a version 2 of the send stream.
+ */
+#define BTRFS_SEND_FLAG_STREAM_V2              0x10
+
 #define BTRFS_SEND_FLAG_MASK \
 	(BTRFS_SEND_FLAG_NO_FILE_DATA | \
 	 BTRFS_SEND_FLAG_OMIT_STREAM_HEADER | \
-	 BTRFS_SEND_FLAG_OMIT_END_CMD)
+	 BTRFS_SEND_FLAG_OMIT_END_CMD | \
+	 BTRFS_SEND_FLAG_CALCULATE_DATA_SIZE | \
+	 BTRFS_SEND_FLAG_STREAM_V2)
 
 struct btrfs_ioctl_send_args {
 	__s64 send_fd;			/* in */
-- 
2.17.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC PATCH 2/6] btrfs: send, implement total data size command to allow for progress estimation
  2018-05-09  2:06 [RFC PATCH 0/6] btrfs send stream version 2 Howard McLauchlan
  2018-05-09  2:06 ` [RFC PATCH 1/6] btrfs: send, bump stream version Howard McLauchlan
@ 2018-05-09  2:06 ` Howard McLauchlan
  2018-05-09  2:06 ` [RFC PATCH 3/6] btrfs: send, use fallocate command to punch holes Howard McLauchlan
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 10+ messages in thread
From: Howard McLauchlan @ 2018-05-09  2:06 UTC (permalink / raw)
  To: linux-btrfs
  Cc: Chris Mason, Josef Bacik, David Sterba, Filipe Manana,
	Omar Sandoval, Filipe David Borba Manana, Howard McLauchlan

From: Filipe David Borba Manana <fdmanana@gmail.com>

This new send flag makes send calculate first the amount of new file data (in bytes)
the send root has relatively to the parent root, or for the case of a non-incremental
send, the total amount of file data the stream will create (including holes and prealloc
extents). In other words, it computes the sum of the lengths of all write, clone and
fallocate operations that will be sent through the send stream.

This data size value is sent in a new command, named BTRFS_SEND_C_TOTAL_DATA_SIZE, that
immediately follows a BTRFS_SEND_C_SUBVOL or BTRFS_SEND_C_SNAPSHOT command, and precedes
any command that changes a file or the filesystem hierarchy. Upon receiving a write, clone
or fallocate command, the receiving end can increment a counter by the data length of that
command and therefore report progress by comparing the counter's value with the data size
value received in the BTRFS_SEND_C_TOTAL_DATA_SIZE command.

The approach is simple, before the normal operation of send, do a scan in the file system
tree for new inodes and new/changed file extent items, just like in send's normal operation,
and keep incrementing a counter with new inodes' size and the size of file extents (and file
holes)  that are going to be written, cloned or fallocated. This is actually a simpler and
more lightweight tree scan/processing than the one we do when sending the changes, as it
doesn't process inode references nor does any lookups in the extent tree for example.

After modifying btrfs-progs to understand this new command and report progress, here's an
example (the -o flag tells btrfs send to pass the new flag to the kernel's send ioctl):

    $ btrfs send -s --stream-version 2 /mnt/sdd/snap_base | btrfs receive /mnt/sdc
    At subvol /mnt/sdd/snap_base
    At subvol snap_base
    About to receive 9212392667 bytes
    Subvolume /mnt/sdc//snap_base, 4059722426 / 9212392667 bytes received, 44.07%, 40.32MB/s

    $ btrfs send -s --stream-version 2 -p /mnt/sdd/snap_base /mnt/sdd/snap_incr | btrfs receive /mnt/sdc
    At subvol /mnt/sdd/snap_incr
    At subvol snap_incr
    About to receive 9571342213 bytes
    Subvolume /mnt/sdc//snap_incr, 6557345221 / 9571342213 bytes received, 68.51%, 51.04MB/s

[Howard: rebased on 4.17-rc4]
Signed-off-by: Howard McLauchlan <hmclauchlan@fb.com>
Signed-off-by: Filipe David Borba Manana <fdmanana@gmail.com>
---
 fs/btrfs/send.c | 189 ++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 157 insertions(+), 32 deletions(-)

diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c
index eccd69387065..7b184831812b 100644
--- a/fs/btrfs/send.c
+++ b/fs/btrfs/send.c
@@ -66,7 +66,13 @@ struct clone_root {
 #define SEND_CTX_MAX_NAME_CACHE_SIZE 128
 #define SEND_CTX_NAME_CACHE_CLEAN_SIZE (SEND_CTX_MAX_NAME_CACHE_SIZE * 2)
 
+enum btrfs_send_phase {
+	SEND_PHASE_STREAM_CHANGES,
+	SEND_PHASE_COMPUTE_DATA_SIZE,
+};
+
 struct send_ctx {
+	enum btrfs_send_phase phase;
 	struct file *send_filp;
 	loff_t send_off;
 	char *send_buf;
@@ -102,6 +108,7 @@ struct send_ctx {
 	u64 cur_inode_next_write_offset;
 
 	u64 send_progress;
+	u64 total_data_size;
 
 	struct list_head new_refs;
 	struct list_head deleted_refs;
@@ -709,6 +716,8 @@ static int send_rename(struct send_ctx *sctx,
 	struct btrfs_fs_info *fs_info = sctx->send_root->fs_info;
 	int ret;
 
+	ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE);
+
 	btrfs_debug(fs_info, "send_rename %s -> %s", from->start, to->start);
 
 	ret = begin_cmd(sctx, BTRFS_SEND_C_RENAME);
@@ -758,6 +767,8 @@ static int send_unlink(struct send_ctx *sctx, struct fs_path *path)
 	struct btrfs_fs_info *fs_info = sctx->send_root->fs_info;
 	int ret;
 
+	ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE);
+
 	btrfs_debug(fs_info, "send_unlink %s", path->start);
 
 	ret = begin_cmd(sctx, BTRFS_SEND_C_UNLINK);
@@ -780,6 +791,7 @@ static int send_rmdir(struct send_ctx *sctx, struct fs_path *path)
 {
 	struct btrfs_fs_info *fs_info = sctx->send_root->fs_info;
 	int ret;
+	ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE);
 
 	btrfs_debug(fs_info, "send_rmdir %s", path->start);
 
@@ -2419,6 +2431,8 @@ static int send_truncate(struct send_ctx *sctx, u64 ino, u64 gen, u64 size)
 	int ret = 0;
 	struct fs_path *p;
 
+	ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE);
+
 	btrfs_debug(fs_info, "send_truncate %llu size=%llu", ino, size);
 
 	p = fs_path_alloc();
@@ -2449,6 +2463,8 @@ static int send_chmod(struct send_ctx *sctx, u64 ino, u64 gen, u64 mode)
 	int ret = 0;
 	struct fs_path *p;
 
+	ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE);
+
 	btrfs_debug(fs_info, "send_chmod %llu mode=%llu", ino, mode);
 
 	p = fs_path_alloc();
@@ -2479,6 +2495,8 @@ static int send_chown(struct send_ctx *sctx, u64 ino, u64 gen, u64 uid, u64 gid)
 	int ret = 0;
 	struct fs_path *p;
 
+	ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE);
+
 	btrfs_debug(fs_info, "send_chown %llu uid=%llu, gid=%llu",
 		    ino, uid, gid);
 
@@ -2516,6 +2534,8 @@ static int send_utimes(struct send_ctx *sctx, u64 ino, u64 gen)
 	struct btrfs_key key;
 	int slot;
 
+	ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE);
+
 	btrfs_debug(fs_info, "send_utimes %llu", ino);
 
 	p = fs_path_alloc();
@@ -2578,6 +2598,8 @@ static int send_create_inode(struct send_ctx *sctx, u64 ino)
 	u64 mode;
 	u64 rdev;
 
+	ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE);
+
 	btrfs_debug(fs_info, "send_create_inode %llu", ino);
 
 	p = fs_path_alloc();
@@ -2725,6 +2747,8 @@ static int send_create_inode_if_needed(struct send_ctx *sctx)
 {
 	int ret;
 
+	ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE);
+
 	if (S_ISDIR(sctx->cur_inode_mode)) {
 		ret = did_create_dir(sctx, sctx->cur_ino);
 		if (ret < 0)
@@ -2825,6 +2849,8 @@ static int orphanize_inode(struct send_ctx *sctx, u64 ino, u64 gen,
 	int ret;
 	struct fs_path *orphan;
 
+	ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE);
+
 	orphan = fs_path_alloc();
 	if (!orphan)
 		return -ENOMEM;
@@ -3200,6 +3226,8 @@ static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm)
 	bool is_orphan;
 	int ret;
 
+	ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE);
+
 	name = fs_path_alloc();
 	from_path = fs_path_alloc();
 	if (!name || !from_path) {
@@ -3792,6 +3820,9 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)
 	bool orphanized_dir = false;
 	bool orphanized_ancestor = false;
 
+	if (sctx->phase == SEND_PHASE_COMPUTE_DATA_SIZE)
+		return 0;
+
 	btrfs_debug(fs_info, "process_recorded_refs %llu", sctx->cur_ino);
 
 	/*
@@ -4391,6 +4422,8 @@ static int process_all_refs(struct send_ctx *sctx,
 	iterate_inode_ref_t cb;
 	int pending_move = 0;
 
+	ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE);
+
 	path = alloc_path_for_send();
 	if (!path)
 		return -ENOMEM;
@@ -4708,6 +4741,8 @@ static int process_all_new_xattrs(struct send_ctx *sctx)
 	struct extent_buffer *eb;
 	int slot;
 
+	ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE);
+
 	path = alloc_path_for_send();
 	if (!path)
 		return -ENOMEM;
@@ -4849,6 +4884,8 @@ static int send_write(struct send_ctx *sctx, u64 offset, u32 len)
 	struct fs_path *p;
 	ssize_t num_read = 0;
 
+	ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE);
+
 	p = fs_path_alloc();
 	if (!p)
 		return -ENOMEM;
@@ -4884,6 +4921,22 @@ static int send_write(struct send_ctx *sctx, u64 offset, u32 len)
 	return num_read;
 }
 
+static int send_total_data_size(struct send_ctx *sctx, u64 data_size)
+{
+	int ret;
+
+	ret = begin_cmd(sctx, BTRFS_SEND_C_TOTAL_DATA_SIZE);
+	if (ret < 0)
+		goto out;
+
+	TLV_PUT_U64(sctx, BTRFS_SEND_A_SIZE, data_size);
+	ret = send_cmd(sctx);
+
+tlv_put_failure:
+out:
+	return ret;
+}
+
 /*
  * Send a clone command to user space.
  */
@@ -4895,6 +4948,8 @@ static int send_clone(struct send_ctx *sctx,
 	struct fs_path *p;
 	u64 gen;
 
+	ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE);
+
 	btrfs_debug(sctx->send_root->fs_info,
 		    "send_clone offset=%llu, len=%d, clone_root=%llu, clone_inode=%llu, clone_offset=%llu",
 		    offset, len, clone_root->root->objectid, clone_root->ino,
@@ -4966,6 +5021,8 @@ static int send_update_extent(struct send_ctx *sctx,
 	int ret = 0;
 	struct fs_path *p;
 
+	ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE);
+
 	p = fs_path_alloc();
 	if (!p)
 		return -ENOMEM;
@@ -4997,6 +5054,10 @@ static int send_hole(struct send_ctx *sctx, u64 end)
 	u64 len;
 	int ret = 0;
 
+	if (sctx->phase == SEND_PHASE_COMPUTE_DATA_SIZE) {
+		sctx->total_data_size += end - offset;
+		return 0;
+	}
 	if (sctx->flags & BTRFS_SEND_FLAG_NO_FILE_DATA)
 		return send_update_extent(sctx, offset, end - offset);
 
@@ -5250,6 +5311,12 @@ static int send_write_or_clone(struct send_ctx *sctx,
 		goto out;
 	}
 
+	if (sctx->phase == SEND_PHASE_COMPUTE_DATA_SIZE) {
+		if (offset < sctx->cur_inode_size)
+			sctx->total_data_size += len;
+		goto out;
+	}
+
 	if (clone_root && IS_ALIGNED(offset + len, bs)) {
 		u64 disk_byte;
 		u64 data_offset;
@@ -5681,10 +5748,12 @@ static int process_extent(struct send_ctx *sctx,
 		}
 	}
 
-	ret = find_extent_clone(sctx, path, key->objectid, key->offset,
-			sctx->cur_inode_size, &found_clone);
-	if (ret != -ENOENT && ret < 0)
-		goto out;
+	if (sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE) {
+		ret = find_extent_clone(sctx, path, key->objectid, key->offset,
+					sctx->cur_inode_size, &found_clone);
+		if (ret != -ENOENT && ret < 0)
+			goto out;
+	}
 
 	ret = send_write_or_clone(sctx, path, key, found_clone);
 	if (ret)
@@ -5815,6 +5884,9 @@ static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
 	if (!at_end && sctx->cmp_key->objectid == sctx->cur_ino)
 		goto out;
 
+	if (sctx->phase == SEND_PHASE_COMPUTE_DATA_SIZE)
+		goto truncate_inode;
+
 	ret = get_inode_info(sctx->send_root, sctx->cur_ino, NULL, NULL,
 			&left_mode, &left_uid, &left_gid, NULL);
 	if (ret < 0)
@@ -5845,6 +5917,7 @@ static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
 			need_truncate = 0;
 	}
 
+truncate_inode:
 	if (S_ISREG(sctx->cur_inode_mode)) {
 		if (need_send_hole(sctx)) {
 			if (sctx->cur_inode_last_extent == (u64)-1 ||
@@ -5887,7 +5960,8 @@ static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
 	 * If other directory inodes depended on our current directory
 	 * inode's move/rename, now do their move/rename operations.
 	 */
-	if (!is_waiting_for_move(sctx, sctx->cur_ino)) {
+	if (sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE &&
+	    !is_waiting_for_move(sctx, sctx->cur_ino)) {
 		ret = apply_children_dir_moves(sctx);
 		if (ret)
 			goto out;
@@ -5972,7 +6046,8 @@ static int changed_inode(struct send_ctx *sctx,
 				sctx->left_path->nodes[0], left_ii);
 		sctx->cur_inode_rdev = btrfs_inode_rdev(
 				sctx->left_path->nodes[0], left_ii);
-		if (sctx->cur_ino != BTRFS_FIRST_FREE_OBJECTID)
+		if (sctx->cur_ino != BTRFS_FIRST_FREE_OBJECTID &&
+		    sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE)
 			ret = send_create_inode_if_needed(sctx);
 	} else if (result == BTRFS_COMPARE_TREE_DELETED) {
 		sctx->cur_inode_gen = right_gen;
@@ -5994,17 +6069,19 @@ static int changed_inode(struct send_ctx *sctx,
 			/*
 			 * First, process the inode as if it was deleted.
 			 */
-			sctx->cur_inode_gen = right_gen;
-			sctx->cur_inode_new = 0;
-			sctx->cur_inode_deleted = 1;
-			sctx->cur_inode_size = btrfs_inode_size(
+			if (sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE) {
+				sctx->cur_inode_gen = right_gen;
+				sctx->cur_inode_new = 0;
+				sctx->cur_inode_deleted = 1;
+				sctx->cur_inode_size = btrfs_inode_size(
 					sctx->right_path->nodes[0], right_ii);
-			sctx->cur_inode_mode = btrfs_inode_mode(
+				sctx->cur_inode_mode = btrfs_inode_mode(
 					sctx->right_path->nodes[0], right_ii);
-			ret = process_all_refs(sctx,
-					BTRFS_COMPARE_TREE_DELETED);
-			if (ret < 0)
-				goto out;
+				ret = process_all_refs(sctx,
+						   BTRFS_COMPARE_TREE_DELETED);
+				if (ret < 0)
+					goto out;
+			}
 
 			/*
 			 * Now process the inode as if it was new.
@@ -6018,29 +6095,38 @@ static int changed_inode(struct send_ctx *sctx,
 					sctx->left_path->nodes[0], left_ii);
 			sctx->cur_inode_rdev = btrfs_inode_rdev(
 					sctx->left_path->nodes[0], left_ii);
-			ret = send_create_inode_if_needed(sctx);
-			if (ret < 0)
-				goto out;
-
-			ret = process_all_refs(sctx, BTRFS_COMPARE_TREE_NEW);
-			if (ret < 0)
-				goto out;
+			if (sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE) {
+				ret = send_create_inode_if_needed(sctx);
+				if (ret < 0)
+					goto out;
+				ret = process_all_refs(sctx,
+						       BTRFS_COMPARE_TREE_NEW);
+				if (ret < 0)
+					goto out;
+			}
 			/*
 			 * Advance send_progress now as we did not get into
 			 * process_recorded_refs_if_needed in the new_gen case.
 			 */
 			sctx->send_progress = sctx->cur_ino + 1;
 
-			/*
-			 * Now process all extents and xattrs of the inode as if
-			 * they were all new.
-			 */
-			ret = process_all_extents(sctx);
-			if (ret < 0)
-				goto out;
-			ret = process_all_new_xattrs(sctx);
-			if (ret < 0)
-				goto out;
+			if (sctx->phase == SEND_PHASE_COMPUTE_DATA_SIZE) {
+				if (S_ISREG(sctx->cur_inode_mode))
+					sctx->total_data_size +=
+						sctx->cur_inode_size;
+				/* TODO: maybe account for xattrs one day too */
+			} else {
+				/*
+				 * Now process all extents and xattrs of the
+				 * inode as if they were all new.
+				 */
+				ret = process_all_extents(sctx);
+				if (ret < 0)
+					goto out;
+				ret = process_all_new_xattrs(sctx);
+				if (ret < 0)
+					goto out;
+			}
 		} else {
 			sctx->cur_inode_gen = left_gen;
 			sctx->cur_inode_new = 0;
@@ -6077,6 +6163,9 @@ static int changed_ref(struct send_ctx *sctx,
 		return -EIO;
 	}
 
+	if (sctx->phase == SEND_PHASE_COMPUTE_DATA_SIZE)
+		return 0;
+
 	if (!sctx->cur_inode_new_gen &&
 	    sctx->cur_ino != BTRFS_FIRST_FREE_OBJECTID) {
 		if (result == BTRFS_COMPARE_TREE_NEW)
@@ -6105,6 +6194,9 @@ static int changed_xattr(struct send_ctx *sctx,
 		return -EIO;
 	}
 
+	if (sctx->phase == SEND_PHASE_COMPUTE_DATA_SIZE)
+		return 0;
+
 	if (!sctx->cur_inode_new_gen && !sctx->cur_inode_deleted) {
 		if (result == BTRFS_COMPARE_TREE_NEW)
 			ret = process_new_xattr(sctx);
@@ -6273,6 +6365,8 @@ static int changed_cb(struct btrfs_path *left_path,
 	if (result == BTRFS_COMPARE_TREE_SAME) {
 		if (key->type == BTRFS_INODE_REF_KEY ||
 		    key->type == BTRFS_INODE_EXTREF_KEY) {
+			if (sctx->phase == SEND_PHASE_COMPUTE_DATA_SIZE)
+				return 0;
 			ret = compare_refs(sctx, left_path, key);
 			if (!ret)
 				return 0;
@@ -6369,6 +6463,24 @@ static int full_send_tree(struct send_ctx *sctx)
 	return ret;
 }
 
+static int compute_total_data_size(struct send_ctx *sctx)
+{
+	int ret;
+
+	sctx->total_data_size = 0;
+
+	if (sctx->parent_root) {
+		ret = btrfs_compare_trees(sctx->send_root, sctx->parent_root,
+					  changed_cb, sctx);
+		if (!ret)
+			ret = finish_inode_if_needed(sctx, 1);
+	} else {
+		ret = full_send_tree(sctx);
+	}
+
+	return ret;
+}
+
 static int send_subvol(struct send_ctx *sctx)
 {
 	int ret;
@@ -6383,6 +6495,19 @@ static int send_subvol(struct send_ctx *sctx)
 	if (ret < 0)
 		goto out;
 
+	if (sctx->flags & BTRFS_SEND_FLAG_CALCULATE_DATA_SIZE) {
+		sctx->phase = SEND_PHASE_COMPUTE_DATA_SIZE;
+		ret = compute_total_data_size(sctx);
+		if (ret < 0)
+			goto out;
+		ret = send_total_data_size(sctx, sctx->total_data_size);
+		if (ret < 0)
+			goto out;
+		sctx->phase = SEND_PHASE_STREAM_CHANGES;
+		sctx->cur_ino = 0;
+		sctx->send_progress = 0;
+	}
+
 	if (sctx->parent_root) {
 		ret = btrfs_compare_trees(sctx->send_root, sctx->parent_root,
 				changed_cb, sctx);
-- 
2.17.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC PATCH 3/6] btrfs: send, use fallocate command to punch holes
  2018-05-09  2:06 [RFC PATCH 0/6] btrfs send stream version 2 Howard McLauchlan
  2018-05-09  2:06 ` [RFC PATCH 1/6] btrfs: send, bump stream version Howard McLauchlan
  2018-05-09  2:06 ` [RFC PATCH 2/6] btrfs: send, implement total data size command to allow for progress estimation Howard McLauchlan
@ 2018-05-09  2:06 ` Howard McLauchlan
  2018-05-09  2:06 ` [RFC PATCH 4/6] btrfs: send, use fallocate command to allocate extents Howard McLauchlan
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 10+ messages in thread
From: Howard McLauchlan @ 2018-05-09  2:06 UTC (permalink / raw)
  To: linux-btrfs
  Cc: Chris Mason, Josef Bacik, David Sterba, Filipe Manana,
	Omar Sandoval, Filipe David Borba Manana, Howard McLauchlan

From: Filipe David Borba Manana <fdmanana@gmail.com>

Instead of sending a write command with a data buffer filled with 0 value bytes,
use the fallocate command, introduced in the send stream version 2, to tell the
receiver to punch a file hole using the fallocate system call.

[Howard: rebased on 4.17-rc4]
Signed-off-by: Howard McLauchlan <hmclauchlan@fb.com>
Signed-off-by: Filipe David Borba Manana <fdmanana@gmail.com>
---
 fs/btrfs/send.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++---
 fs/btrfs/send.h |  4 ++++
 2 files changed, 55 insertions(+), 3 deletions(-)

diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c
index 7b184831812b..328c7a2857ae 100644
--- a/fs/btrfs/send.c
+++ b/fs/btrfs/send.c
@@ -581,6 +581,7 @@ static int tlv_put(struct send_ctx *sctx, u16 attr, const void *data, int len)
 		return tlv_put(sctx, attr, &__tmp, sizeof(__tmp));	\
 	}
 
+TLV_PUT_DEFINE_INT(32)
 TLV_PUT_DEFINE_INT(64)
 
 static int tlv_put_string(struct send_ctx *sctx, u16 attr,
@@ -5047,17 +5048,57 @@ static int send_update_extent(struct send_ctx *sctx,
 	return ret;
 }
 
+static int send_fallocate(struct send_ctx *sctx, u32 flags,
+			  u64 offset, u64 len)
+{
+	struct fs_path *p = NULL;
+	int ret = 0;
+
+	ASSERT(sctx->flags & BTRFS_SEND_FLAG_STREAM_V2);
+
+	if (sctx->phase == SEND_PHASE_COMPUTE_DATA_SIZE) {
+		sctx->total_data_size += len;
+		return 0;
+	}
+
+	p = fs_path_alloc();
+	if (!p)
+		return -ENOMEM;
+	ret = get_cur_path(sctx, sctx->cur_ino, sctx->cur_inode_gen, p);
+	if (ret < 0)
+		goto out;
+
+	ret = begin_cmd(sctx, BTRFS_SEND_C_FALLOCATE);
+	if (ret < 0)
+		goto out;
+	TLV_PUT_PATH(sctx, BTRFS_SEND_A_PATH, p);
+	TLV_PUT_U32(sctx, BTRFS_SEND_A_FALLOCATE_FLAGS, flags);
+	TLV_PUT_U64(sctx, BTRFS_SEND_A_FILE_OFFSET, offset);
+	TLV_PUT_U64(sctx, BTRFS_SEND_A_SIZE, len);
+	ret = send_cmd(sctx);
+
+tlv_put_failure:
+out:
+	fs_path_free(p);
+	return ret;
+}
+
+
 static int send_hole(struct send_ctx *sctx, u64 end)
 {
 	struct fs_path *p = NULL;
 	u64 offset = sctx->cur_inode_last_extent;
-	u64 len;
+	u64 len = end - offset;
 	int ret = 0;
 
 	if (sctx->phase == SEND_PHASE_COMPUTE_DATA_SIZE) {
-		sctx->total_data_size += end - offset;
+		sctx->total_data_size += len;
 		return 0;
 	}
+	if (sctx->flags & BTRFS_SEND_FLAG_STREAM_V2)
+		return send_fallocate(sctx, BTRFS_SEND_PUNCH_HOLE_FALLOC_FLAGS,
+				      offset, len);
+
 	if (sctx->flags & BTRFS_SEND_FLAG_NO_FILE_DATA)
 		return send_update_extent(sctx, offset, end - offset);
 
@@ -5304,7 +5345,8 @@ static int send_write_or_clone(struct send_ctx *sctx,
 		ret = 0;
 		goto out;
 	}
-	if (offset + len > sctx->cur_inode_size)
+	if (offset < sctx->cur_inode_size &&
+	    offset + len > sctx->cur_inode_size)
 		len = sctx->cur_inode_size - offset;
 	if (len == 0) {
 		ret = 0;
@@ -5325,6 +5367,12 @@ static int send_write_or_clone(struct send_ctx *sctx,
 		data_offset = btrfs_file_extent_offset(path->nodes[0], ei);
 		ret = clone_range(sctx, clone_root, disk_byte, data_offset,
 				  offset, len);
+	} else if (btrfs_file_extent_disk_bytenr(path->nodes[0], ei) == 0 &&
+		 type != BTRFS_FILE_EXTENT_INLINE &&
+		 (sctx->flags & BTRFS_SEND_FLAG_STREAM_V2) &&
+		 offset < sctx->cur_inode_size) {
+		ret = send_fallocate(sctx, BTRFS_SEND_PUNCH_HOLE_FALLOC_FLAGS,
+				     offset, len);
 	} else {
 		ret = send_extent_data(sctx, offset, len);
 	}
diff --git a/fs/btrfs/send.h b/fs/btrfs/send.h
index a9b5489d690e..a5830d216ac1 100644
--- a/fs/btrfs/send.h
+++ b/fs/btrfs/send.h
@@ -138,6 +138,10 @@ enum {
 #define BTRFS_SEND_A_FALLOCATE_FLAG_KEEP_SIZE   (1 << 0)
 #define BTRFS_SEND_A_FALLOCATE_FLAG_PUNCH_HOLE  (1 << 1)
 
+#define BTRFS_SEND_PUNCH_HOLE_FALLOC_FLAGS        \
+	(BTRFS_SEND_A_FALLOCATE_FLAG_KEEP_SIZE |  \
+	 BTRFS_SEND_A_FALLOCATE_FLAG_PUNCH_HOLE)
+
 #ifdef __KERNEL__
 long btrfs_ioctl_send(struct file *mnt_file, struct btrfs_ioctl_send_args *arg);
 #endif
-- 
2.17.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC PATCH 4/6] btrfs: send, use fallocate command to allocate extents
  2018-05-09  2:06 [RFC PATCH 0/6] btrfs send stream version 2 Howard McLauchlan
                   ` (2 preceding siblings ...)
  2018-05-09  2:06 ` [RFC PATCH 3/6] btrfs: send, use fallocate command to punch holes Howard McLauchlan
@ 2018-05-09  2:06 ` Howard McLauchlan
  2018-05-09  2:06 ` [RFC PATCH 5/6] btrfs: add send_stream_version attribute to sysfs Howard McLauchlan
  2018-05-09  2:06 ` [RFC PATCH 6/6] btrfs: add chattr support for send/receive Howard McLauchlan
  5 siblings, 0 replies; 10+ messages in thread
From: Howard McLauchlan @ 2018-05-09  2:06 UTC (permalink / raw)
  To: linux-btrfs
  Cc: Chris Mason, Josef Bacik, David Sterba, Filipe Manana,
	Omar Sandoval, Filipe David Borba Manana, Howard McLauchlan

From: Filipe David Borba Manana <fdmanana@gmail.com>

The send stream version 2 adds the fallocate command, which can be used to
allocate extents for a file or punch holes in a file. Previously we were
ignoring file prealloc extents or treating them as extents filled with 0
bytes and sending a regular write command to the stream.

After this change, together with my previous change titled:

    "Btrfs: send, use fallocate command to punch holes"

an incremental send preserves the hole and data structure of files, which can
be seen via calls to lseek with the whence parameter set to SEEK_DATA or SEEK_HOLE,
as the example below shows:

    mkfs.btrfs -f /dev/sdc
    mount /dev/sdc /mnt
    xfs_io -f -c "pwrite -S 0x01 -b 300000 0 300000" /mnt/foo
    btrfs subvolume snapshot -r /mnt /mnt/mysnap1

    xfs_io -c "fpunch 100000 50000" /mnt/foo
    xfs_io -c "falloc 100000 50000" /mnt/foo
    xfs_io -c "pwrite -S 0xff -b 1000 120000 1000" /mnt/foo
    xfs_io -c "fpunch 250000 20000" /mnt/foo

    # prealloc extents that start beyond the inode's size
    xfs_io -c "falloc -k 300000 1000000" /mnt/foo
    xfs_io -c "falloc -k 9000000 2000000" /mnt/foo

    btrfs subvolume snapshot -r /mnt /mnt/mysnap2

    btrfs send /mnt/mysnap1 -f /tmp/1.snap
    btrfs send -p /mnt/mysnap1 /mnt/mysnap2 -f /tmp/2.snap

    mkfs.btrfs -f /dev/sdd
    mount /dev/sdd /mnt2
    btrfs receive /mnt2 -f /tmp/1.snap
    btrfs receive /mnt2 -f /tmp/2.snap

Before this change the hole/data structure differed between both filesystems:

    $ xfs_io -r -c 'seek -r -a 0' /mnt/mysnap2/foo
    Whence  Result
    DATA    0
    HOLE    102400
    DATA    118784
    HOLE    122880
    DATA    147456
    HOLE    253952
    DATA    266240
    HOLE    300000

    $ xfs_io -r -c 'seek -r -a 0' /mnt2/mysnap2/foo
    Whence  Result
    DATA    0
    HOLE    300000

After this change the second filesystem (/dev/sdd) ends up with the same hole/data
structure as the first filesystem.

Also, after this change, prealloc extents that lie beyond the inode's size (were
allocated with fallocate + keep size flag) are also replicated by an incremental
send. For the above test, it can be observed via fiemap (or btrfs-debug-tree):

    $ xfs_io -r -c 'fiemap -l' /mnt2/mysnap2/foo
	0: [0..191]: 25096..25287 192 blocks
	1: [192..199]: 24672..24679 8 blocks
	2: [200..231]: 24584..24615 32 blocks
	3: [232..239]: 24680..24687 8 blocks
	4: [240..287]: 24616..24663 48 blocks
	5: [288..295]: 24688..24695 8 blocks
	6: [296..487]: 25392..25583 192 blocks
	7: [488..495]: 24696..24703 8 blocks
	8: [496..519]: hole 24 blocks
	9: [520..527]: 24704..24711 8 blocks
	10: [528..583]: 25624..25679 56 blocks
	11: [584..591]: 24712..24719 8 blocks
	12: [592..2543]: 26192..28143 1952 blocks
	13: [2544..17575]: hole 15032 blocks
	14: [17576..21487]: 28144..32055 3912 blocks

The proposed xfstest can be found at:

    xfstests: btrfs, test send's ability to punch holes and prealloc extents

This test verifies that send-stream version 2 does space pre-allocation
and hole punching.

[Howard: rebased on 4.17-rc4]
Signed-off-by: Howard McLauchlan <hmclauchlan@fb.com>
Signed-off-by: Filipe David Borba Manana <fdmanana@gmail.com>
---
 fs/btrfs/send.c | 71 ++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 53 insertions(+), 18 deletions(-)

diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c
index 328c7a2857ae..c8ea1ccaa3d8 100644
--- a/fs/btrfs/send.c
+++ b/fs/btrfs/send.c
@@ -98,9 +98,10 @@ struct send_ctx {
 	 */
 	u64 cur_ino;
 	u64 cur_inode_gen;
-	int cur_inode_new;
-	int cur_inode_new_gen;
-	int cur_inode_deleted;
+	u8 cur_inode_new:1;
+	u8 cur_inode_new_gen:1;
+	u8 cur_inode_skip_truncate:1;
+	u8 cur_inode_deleted:1;
 	u64 cur_inode_size;
 	u64 cur_inode_mode;
 	u64 cur_inode_rdev;
@@ -5313,6 +5314,19 @@ static int clone_range(struct send_ctx *sctx,
 	return ret;
 }
 
+static int truncate_before_falloc(struct send_ctx *sctx)
+{
+	int ret = 0;
+
+	if (!sctx->cur_inode_skip_truncate) {
+		ret = send_truncate(sctx, sctx->cur_ino,
+				    sctx->cur_inode_gen,
+				    sctx->cur_inode_size);
+		sctx->cur_inode_skip_truncate = 1;
+	}
+	return ret;
+}
+
 static int send_write_or_clone(struct send_ctx *sctx,
 			       struct btrfs_path *path,
 			       struct btrfs_key *key,
@@ -5354,8 +5368,7 @@ static int send_write_or_clone(struct send_ctx *sctx,
 	}
 
 	if (sctx->phase == SEND_PHASE_COMPUTE_DATA_SIZE) {
-		if (offset < sctx->cur_inode_size)
-			sctx->total_data_size += len;
+		sctx->total_data_size += len;
 		goto out;
 	}
 
@@ -5373,6 +5386,21 @@ static int send_write_or_clone(struct send_ctx *sctx,
 		 offset < sctx->cur_inode_size) {
 		ret = send_fallocate(sctx, BTRFS_SEND_PUNCH_HOLE_FALLOC_FLAGS,
 				     offset, len);
+	} else if (type == BTRFS_FILE_EXTENT_PREALLOC &&
+		   (sctx->flags & BTRFS_SEND_FLAG_STREAM_V2)) {
+		u32 flags = 0;
+		if (offset < sctx->cur_inode_size) {
+			ret = send_fallocate(sctx,
+					     BTRFS_SEND_PUNCH_HOLE_FALLOC_FLAGS,
+					     offset, len);
+		} else {
+			flags |= BTRFS_SEND_A_FALLOCATE_FLAG_KEEP_SIZE;
+			ret = truncate_before_falloc(sctx);
+		}
+		if (ret)
+			goto out;
+		ret = send_fallocate(sctx, flags, offset, len);
+
 	} else {
 		ret = send_extent_data(sctx, offset, len);
 	}
@@ -5775,19 +5803,24 @@ static int process_extent(struct send_ctx *sctx,
 		ei = btrfs_item_ptr(path->nodes[0], path->slots[0],
 				    struct btrfs_file_extent_item);
 		type = btrfs_file_extent_type(path->nodes[0], ei);
-		if (type == BTRFS_FILE_EXTENT_PREALLOC ||
-		    type == BTRFS_FILE_EXTENT_REG) {
-			/*
-			 * The send spec does not have a prealloc command yet,
-			 * so just leave a hole for prealloc'ed extents until
-			 * we have enough commands queued up to justify rev'ing
-			 * the send spec.
-			 */
-			if (type == BTRFS_FILE_EXTENT_PREALLOC) {
-				ret = 0;
-				goto out;
+		if (type == BTRFS_FILE_EXTENT_PREALLOC &&
+		    (sctx->flags & BTRFS_SEND_FLAG_STREAM_V2)) {
+			u64 len;
+			u32 flags = 0;
+
+			len = btrfs_file_extent_num_bytes(path->nodes[0], ei);
+			if (key->offset >= sctx->cur_inode_size) {
+				flags |= BTRFS_SEND_A_FALLOCATE_FLAG_KEEP_SIZE;
+				ret = truncate_before_falloc(sctx);
+				if (ret)
+					goto out;
 			}
-
+			ret = send_fallocate(sctx, flags, key->offset, len);
+			goto out;
+		} else if (type == BTRFS_FILE_EXTENT_PREALLOC) {
+			ret = 0;
+			goto out;
+		} else if (type == BTRFS_FILE_EXTENT_REG) {
 			/* Have a hole, just skip it. */
 			if (btrfs_file_extent_disk_bytenr(path->nodes[0], ei) == 0) {
 				ret = 0;
@@ -5982,7 +6015,8 @@ static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
 					goto out;
 			}
 		}
-		if (need_truncate) {
+		if (need_truncate && !sctx->cur_inode_skip_truncate
+		    && sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE) {
 			ret = send_truncate(sctx, sctx->cur_ino,
 					    sctx->cur_inode_gen,
 					    sctx->cur_inode_size);
@@ -6044,6 +6078,7 @@ static int changed_inode(struct send_ctx *sctx,
 	sctx->cur_inode_new_gen = 0;
 	sctx->cur_inode_last_extent = (u64)-1;
 	sctx->cur_inode_next_write_offset = 0;
+	sctx->cur_inode_skip_truncate = 0;
 
 	/*
 	 * Set send_progress to current inode. This will tell all get_cur_xxx
-- 
2.17.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC PATCH 5/6] btrfs: add send_stream_version attribute to sysfs
  2018-05-09  2:06 [RFC PATCH 0/6] btrfs send stream version 2 Howard McLauchlan
                   ` (3 preceding siblings ...)
  2018-05-09  2:06 ` [RFC PATCH 4/6] btrfs: send, use fallocate command to allocate extents Howard McLauchlan
@ 2018-05-09  2:06 ` Howard McLauchlan
  2018-05-16 19:04   ` Omar Sandoval
  2018-05-09  2:06 ` [RFC PATCH 6/6] btrfs: add chattr support for send/receive Howard McLauchlan
  5 siblings, 1 reply; 10+ messages in thread
From: Howard McLauchlan @ 2018-05-09  2:06 UTC (permalink / raw)
  To: linux-btrfs
  Cc: Chris Mason, Josef Bacik, David Sterba, Filipe Manana,
	Omar Sandoval, Filipe David Borba Manana, Howard McLauchlan

From: Filipe David Borba Manana <fdmanana@gmail.com>

So that applications can find out what's the highest send stream
version supported/implemented by the running kernel:

    $ cat /sys/fs/btrfs/send/stream_version
    2

[Howard: rebased on 4.17-rc4]
Signed-off-by: Howard McLauchlan <hmclauchlan@fb.com>
Signed-off-by: Filipe David Borba Manana <fdmanana@gmail.com>
Reviewed-by: David Sterba <dsterba@suse.cz>
---
 fs/btrfs/send.h  |  1 +
 fs/btrfs/sysfs.c | 29 +++++++++++++++++++++++++++++
 2 files changed, 30 insertions(+)

diff --git a/fs/btrfs/send.h b/fs/btrfs/send.h
index a5830d216ac1..2f5e25e03def 100644
--- a/fs/btrfs/send.h
+++ b/fs/btrfs/send.h
@@ -12,6 +12,7 @@
 #define BTRFS_SEND_STREAM_MAGIC "btrfs-stream"
 #define BTRFS_SEND_STREAM_VERSION_1 1
 #define BTRFS_SEND_STREAM_VERSION_2 2
+#define BTRFS_SEND_STREAM_VERSION_LATEST BTRFS_SEND_STREAM_VERSION_2
 
 #define BTRFS_SEND_BUF_SIZE SZ_64K
 #define BTRFS_SEND_READ_SIZE (48 * SZ_1K)
diff --git a/fs/btrfs/sysfs.c b/fs/btrfs/sysfs.c
index 4848a4318fb5..3c82cba91ff6 100644
--- a/fs/btrfs/sysfs.c
+++ b/fs/btrfs/sysfs.c
@@ -18,6 +18,7 @@
 #include "transaction.h"
 #include "sysfs.h"
 #include "volumes.h"
+#include "send.h"
 
 static inline struct btrfs_fs_info *to_fs_info(struct kobject *kobj);
 static inline struct btrfs_fs_devices *to_fs_devs(struct kobject *kobj);
@@ -884,6 +885,28 @@ static int btrfs_init_debugfs(void)
 	return 0;
 }
 
+static ssize_t send_stream_version_show(struct kobject *kobj,
+					struct kobj_attribute *a,
+					char *buf)
+{
+	return snprintf(buf, PAGE_SIZE, "%d\n",
+			BTRFS_SEND_STREAM_VERSION_LATEST);
+}
+
+BTRFS_ATTR(, stream_version, send_stream_version_show);
+
+static struct attribute *btrfs_send_attrs[] = {
+	BTRFS_ATTR_PTR(, stream_version),
+	NULL
+};
+
+static const struct attribute_group btrfs_send_attr_group = {
+	.name = "send",
+	.attrs = btrfs_send_attrs,
+};
+
+
+
 int __init btrfs_init_sysfs(void)
 {
 	int ret;
@@ -900,8 +923,13 @@ int __init btrfs_init_sysfs(void)
 	ret = sysfs_create_group(&btrfs_kset->kobj, &btrfs_feature_attr_group);
 	if (ret)
 		goto out2;
+	ret = sysfs_create_group(&btrfs_kset->kobj, &btrfs_send_attr_group);
+	if (ret)
+		goto out3;
 
 	return 0;
+out3:
+	sysfs_remove_group(&btrfs_kset->kobj, &btrfs_feature_attr_group);
 out2:
 	debugfs_remove_recursive(btrfs_debugfs_root_dentry);
 out1:
@@ -913,6 +941,7 @@ int __init btrfs_init_sysfs(void)
 void __cold btrfs_exit_sysfs(void)
 {
 	sysfs_remove_group(&btrfs_kset->kobj, &btrfs_feature_attr_group);
+	sysfs_remove_group(&btrfs_kset->kobj, &btrfs_send_attr_group);
 	kset_unregister(btrfs_kset);
 	debugfs_remove_recursive(btrfs_debugfs_root_dentry);
 }
-- 
2.17.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC PATCH 6/6] btrfs: add chattr support for send/receive
  2018-05-09  2:06 [RFC PATCH 0/6] btrfs send stream version 2 Howard McLauchlan
                   ` (4 preceding siblings ...)
  2018-05-09  2:06 ` [RFC PATCH 5/6] btrfs: add send_stream_version attribute to sysfs Howard McLauchlan
@ 2018-05-09  2:06 ` Howard McLauchlan
  2018-05-16 18:59   ` Omar Sandoval
  5 siblings, 1 reply; 10+ messages in thread
From: Howard McLauchlan @ 2018-05-09  2:06 UTC (permalink / raw)
  To: linux-btrfs
  Cc: Chris Mason, Josef Bacik, David Sterba, Filipe Manana,
	Omar Sandoval, Howard McLauchlan

From: Howard McLauchlan <hmclauchlan@fb.com>

Presently btrfs send/receive does not propagate inode attribute flags;
all chattr operations are effectively discarded upon transmission.

This patch adds kernel support for inode attribute flags. Userspace
support can be found under the commit:

    btrfs-progs: add chattr support for send/receive

An associated xfstest can be found at:

    btrfs: add verify chattr support for send/receive test

These changes are only enabled for send stream version 2

Signed-off-by: Howard McLauchlan <hmclauchlan@fb.com>
---
 fs/btrfs/ctree.h |   2 +
 fs/btrfs/ioctl.c |   2 +-
 fs/btrfs/send.c  | 181 ++++++++++++++++++++++++++++++++++++++++-------
 fs/btrfs/send.h  |   4 +-
 4 files changed, 159 insertions(+), 30 deletions(-)

diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h
index 2771cc56a622..0a2359144b18 100644
--- a/fs/btrfs/ctree.h
+++ b/fs/btrfs/ctree.h
@@ -1461,6 +1461,8 @@ struct btrfs_map_token {
 	unsigned long offset;
 };
 
+unsigned int btrfs_flags_to_ioctl(unsigned int flags);
+
 #define BTRFS_BYTES_TO_BLKS(fs_info, bytes) \
 				((bytes) >> (fs_info)->sb->s_blocksize_bits)
 
diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
index 632e26d6f7ce..36ce1e589f9e 100644
--- a/fs/btrfs/ioctl.c
+++ b/fs/btrfs/ioctl.c
@@ -106,7 +106,7 @@ static unsigned int btrfs_mask_flags(umode_t mode, unsigned int flags)
 /*
  * Export inode flags to the format expected by the FS_IOC_GETFLAGS ioctl.
  */
-static unsigned int btrfs_flags_to_ioctl(unsigned int flags)
+unsigned int btrfs_flags_to_ioctl(unsigned int flags)
 {
 	unsigned int iflags = 0;
 
diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c
index c8ea1ccaa3d8..fa7db1474a7f 100644
--- a/fs/btrfs/send.c
+++ b/fs/btrfs/send.c
@@ -108,6 +108,13 @@ struct send_ctx {
 	u64 cur_inode_last_extent;
 	u64 cur_inode_next_write_offset;
 
+	/*
+	 * state for chattr purposes
+	 */
+	u64 cur_inode_flip_flags;
+	u64 cur_inode_receive_flags;
+	int receive_flags_valid;
+
 	u64 send_progress;
 	u64 total_data_size;
 
@@ -815,7 +822,7 @@ static int send_rmdir(struct send_ctx *sctx, struct fs_path *path)
  */
 static int __get_inode_info(struct btrfs_root *root, struct btrfs_path *path,
 			  u64 ino, u64 *size, u64 *gen, u64 *mode, u64 *uid,
-			  u64 *gid, u64 *rdev)
+			  u64 *gid, u64 *rdev, u64 *flags)
 {
 	int ret;
 	struct btrfs_inode_item *ii;
@@ -845,6 +852,8 @@ static int __get_inode_info(struct btrfs_root *root, struct btrfs_path *path,
 		*gid = btrfs_inode_gid(path->nodes[0], ii);
 	if (rdev)
 		*rdev = btrfs_inode_rdev(path->nodes[0], ii);
+	if (flags)
+		*flags = btrfs_inode_flags(path->nodes[0], ii);
 
 	return ret;
 }
@@ -852,7 +861,7 @@ static int __get_inode_info(struct btrfs_root *root, struct btrfs_path *path,
 static int get_inode_info(struct btrfs_root *root,
 			  u64 ino, u64 *size, u64 *gen,
 			  u64 *mode, u64 *uid, u64 *gid,
-			  u64 *rdev)
+			  u64 *rdev, u64 *flags)
 {
 	struct btrfs_path *path;
 	int ret;
@@ -861,7 +870,7 @@ static int get_inode_info(struct btrfs_root *root,
 	if (!path)
 		return -ENOMEM;
 	ret = __get_inode_info(root, path, ino, size, gen, mode, uid, gid,
-			       rdev);
+			       rdev, flags);
 	btrfs_free_path(path);
 	return ret;
 }
@@ -1250,7 +1259,7 @@ static int __iterate_backrefs(u64 ino, u64 offset, u64 root, void *ctx_)
 	 * accept clones from these extents.
 	 */
 	ret = __get_inode_info(found->root, bctx->path, ino, &i_size, NULL, NULL,
-			       NULL, NULL, NULL);
+			       NULL, NULL, NULL, NULL);
 	btrfs_release_path(bctx->path);
 	if (ret < 0)
 		return ret;
@@ -1610,7 +1619,7 @@ static int get_cur_inode_state(struct send_ctx *sctx, u64 ino, u64 gen)
 	u64 right_gen;
 
 	ret = get_inode_info(sctx->send_root, ino, NULL, &left_gen, NULL, NULL,
-			NULL, NULL);
+			NULL, NULL, NULL);
 	if (ret < 0 && ret != -ENOENT)
 		goto out;
 	left_ret = ret;
@@ -1619,7 +1628,7 @@ static int get_cur_inode_state(struct send_ctx *sctx, u64 ino, u64 gen)
 		right_ret = -ENOENT;
 	} else {
 		ret = get_inode_info(sctx->parent_root, ino, NULL, &right_gen,
-				NULL, NULL, NULL, NULL);
+				NULL, NULL, NULL, NULL, NULL);
 		if (ret < 0 && ret != -ENOENT)
 			goto out;
 		right_ret = ret;
@@ -1788,7 +1797,7 @@ static int get_first_ref(struct btrfs_root *root, u64 ino,
 
 	if (dir_gen) {
 		ret = get_inode_info(root, parent_dir, NULL, dir_gen, NULL,
-				     NULL, NULL, NULL);
+				     NULL, NULL, NULL, NULL);
 		if (ret < 0)
 			goto out;
 	}
@@ -1861,7 +1870,7 @@ static int will_overwrite_ref(struct send_ctx *sctx, u64 dir, u64 dir_gen,
 	 */
 	if (sctx->parent_root && dir != BTRFS_FIRST_FREE_OBJECTID) {
 		ret = get_inode_info(sctx->parent_root, dir, NULL, &gen, NULL,
-				     NULL, NULL, NULL);
+				     NULL, NULL, NULL, NULL);
 		if (ret < 0 && ret != -ENOENT)
 			goto out;
 		if (ret) {
@@ -1889,7 +1898,7 @@ static int will_overwrite_ref(struct send_ctx *sctx, u64 dir, u64 dir_gen,
 	if (other_inode > sctx->send_progress ||
 	    is_waiting_for_move(sctx, other_inode)) {
 		ret = get_inode_info(sctx->parent_root, other_inode, NULL,
-				who_gen, who_mode, NULL, NULL, NULL);
+				who_gen, who_mode, NULL, NULL, NULL, NULL);
 		if (ret < 0)
 			goto out;
 
@@ -1929,7 +1938,7 @@ static int did_overwrite_ref(struct send_ctx *sctx,
 
 	if (dir != BTRFS_FIRST_FREE_OBJECTID) {
 		ret = get_inode_info(sctx->send_root, dir, NULL, &gen, NULL,
-				     NULL, NULL, NULL);
+				     NULL, NULL, NULL, NULL);
 		if (ret < 0 && ret != -ENOENT)
 			goto out;
 		if (ret) {
@@ -1952,7 +1961,7 @@ static int did_overwrite_ref(struct send_ctx *sctx,
 	}
 
 	ret = get_inode_info(sctx->send_root, ow_inode, NULL, &gen, NULL, NULL,
-			NULL, NULL);
+			NULL, NULL, NULL);
 	if (ret < 0)
 		goto out;
 
@@ -2525,6 +2534,42 @@ static int send_chown(struct send_ctx *sctx, u64 ino, u64 gen, u64 uid, u64 gid)
 	return ret;
 }
 
+static int send_chattr(struct send_ctx *sctx, u64 ino, u64 gen, u64 flags)
+{
+	struct btrfs_fs_info *fs_info = sctx->send_root->fs_info;
+	int ret = 0;
+	int __flags;
+	struct fs_path *p;
+
+	if (sctx->phase == SEND_PHASE_COMPUTE_DATA_SIZE)
+		return 0;
+
+	__flags = btrfs_flags_to_ioctl(flags);
+
+	btrfs_debug(fs_info, "send_chattr %llu flags=%llu", ino, flags);
+
+	p = fs_path_alloc();
+	if (!p)
+		return -ENOMEM;
+
+	ret = begin_cmd(sctx, BTRFS_SEND_C_CHATTR);
+	if (ret < 0)
+		goto out;
+
+	ret = get_cur_path(sctx, ino, gen, p);
+	if (ret < 0)
+		goto out;
+	TLV_PUT_PATH(sctx, BTRFS_SEND_A_PATH, p);
+	TLV_PUT_U64(sctx, BTRFS_SEND_A_CHATTR, __flags);
+
+	ret = send_cmd(sctx);
+
+tlv_put_failure:
+out:
+	fs_path_free(p);
+	return ret;
+}
+
 static int send_utimes(struct send_ctx *sctx, u64 ino, u64 gen)
 {
 	struct btrfs_fs_info *fs_info = sctx->send_root->fs_info;
@@ -2610,7 +2655,7 @@ static int send_create_inode(struct send_ctx *sctx, u64 ino)
 
 	if (ino != sctx->cur_ino) {
 		ret = get_inode_info(sctx->send_root, ino, NULL, &gen, &mode,
-				     NULL, NULL, &rdev);
+				     NULL, NULL, &rdev, NULL);
 		if (ret < 0)
 			goto out;
 	} else {
@@ -3332,7 +3377,7 @@ static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm)
 		 * The parent inode might have been deleted in the send snapshot
 		 */
 		ret = get_inode_info(sctx->send_root, cur->dir, NULL,
-				     NULL, NULL, NULL, NULL, NULL);
+				     NULL, NULL, NULL, NULL, NULL, NULL);
 		if (ret == -ENOENT) {
 			ret = 0;
 			continue;
@@ -3502,11 +3547,11 @@ static int wait_for_dest_dir_move(struct send_ctx *sctx,
 	}
 
 	ret = get_inode_info(sctx->parent_root, di_key.objectid, NULL,
-			     &left_gen, NULL, NULL, NULL, NULL);
+			     &left_gen, NULL, NULL, NULL, NULL, NULL);
 	if (ret < 0)
 		goto out;
 	ret = get_inode_info(sctx->send_root, di_key.objectid, NULL,
-			     &right_gen, NULL, NULL, NULL, NULL);
+			     &right_gen, NULL, NULL, NULL, NULL, NULL);
 	if (ret < 0) {
 		if (ret == -ENOENT)
 			ret = 0;
@@ -3650,7 +3695,7 @@ static int is_ancestor(struct btrfs_root *root,
 			}
 
 			ret = get_inode_info(root, parent, NULL, &parent_gen,
-					     NULL, NULL, NULL, NULL);
+					     NULL, NULL, NULL, NULL, NULL);
 			if (ret < 0)
 				goto out;
 			ret = check_ino_in_path(root, ino1, ino1_gen,
@@ -3740,7 +3785,7 @@ static int wait_for_parent_move(struct send_ctx *sctx,
 
 			ret = get_inode_info(sctx->parent_root, ino, NULL,
 					     &parent_ino_gen, NULL, NULL, NULL,
-					     NULL);
+					     NULL, NULL);
 			if (ret < 0)
 				goto out;
 			if (ino_gen == parent_ino_gen) {
@@ -4220,7 +4265,7 @@ static int record_ref(struct btrfs_root *root, u64 dir, struct fs_path *name,
 		return -ENOMEM;
 
 	ret = get_inode_info(root, dir, NULL, &gen, NULL, NULL,
-			NULL, NULL);
+			NULL, NULL, NULL);
 	if (ret < 0)
 		goto out;
 
@@ -4308,7 +4353,7 @@ static int __find_iref(int num, u64 dir, int index,
 		 * else matches.
 		 */
 		ret = get_inode_info(ctx->root, dir, NULL, &dir_gen, NULL,
-				     NULL, NULL, NULL);
+				     NULL, NULL, NULL, NULL);
 		if (ret)
 			return ret;
 		if (dir_gen != ctx->dir_gen)
@@ -4352,7 +4397,7 @@ static int __record_changed_new_ref(int num, u64 dir, int index,
 	struct send_ctx *sctx = ctx;
 
 	ret = get_inode_info(sctx->send_root, dir, NULL, &dir_gen, NULL,
-			     NULL, NULL, NULL);
+			     NULL, NULL, NULL, NULL);
 	if (ret)
 		return ret;
 
@@ -4375,7 +4420,7 @@ static int __record_changed_deleted_ref(int num, u64 dir, int index,
 	struct send_ctx *sctx = ctx;
 
 	ret = get_inode_info(sctx->parent_root, dir, NULL, &dir_gen, NULL,
-			     NULL, NULL, NULL);
+			     NULL, NULL, NULL, NULL);
 	if (ret)
 		return ret;
 
@@ -4975,7 +5020,7 @@ static int send_clone(struct send_ctx *sctx,
 
 	if (clone_root->root == sctx->send_root) {
 		ret = get_inode_info(sctx->send_root, clone_root->ino, NULL,
-				&gen, NULL, NULL, NULL, NULL);
+				&gen, NULL, NULL, NULL, NULL, NULL);
 		if (ret < 0)
 			goto out;
 		ret = get_cur_path(sctx, clone_root->ino, gen, p);
@@ -5934,9 +5979,11 @@ static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
 	u64 right_mode;
 	u64 right_uid;
 	u64 right_gid;
+	u64 left_flags;
 	int need_chmod = 0;
 	int need_chown = 0;
 	int need_truncate = 1;
+	int need_chattr = 0;
 	int pending_move = 0;
 	int refs_processed = 0;
 
@@ -5944,7 +5991,6 @@ static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
 					      &refs_processed);
 	if (ret < 0)
 		goto out;
-
 	/*
 	 * We have processed the refs and thus need to advance send_progress.
 	 * Now, calls to get_cur_xxx will take the updated refs of the current
@@ -5962,6 +6008,64 @@ static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
 
 	if (sctx->cur_ino == 0 || sctx->cur_inode_deleted)
 		goto out;
+
+	/*
+	 * If possible, we want to know what flags are set for this inode on the
+	 * receiving end.
+	 */
+	if (sctx->parent_root && !sctx->receive_flags_valid && sctx->flags &
+	    BTRFS_SEND_FLAG_STREAM_V2) {
+		ret = get_inode_info(sctx->parent_root, sctx->cur_ino, NULL,
+				     NULL, NULL, NULL, NULL, NULL,
+				     &sctx->cur_inode_receive_flags);
+		if (ret < 0)
+			goto out;
+		sctx->receive_flags_valid = 1;
+	}
+
+	/*
+	 * The change is going to modify data and the inode already exists
+	 * !at_end prevents unnecessary chattr.
+	 */
+	if (!at_end && sctx->parent_root && !sctx->cur_inode_new &&
+	    (sctx->cmp_key->type == BTRFS_EXTENT_DATA_KEY ||
+	    sctx->cmp_key->type == BTRFS_XATTR_ITEM_KEY) &&
+	    sctx->flags & BTRFS_SEND_FLAG_STREAM_V2) {
+
+		ret = get_inode_info(sctx->send_root, sctx->cur_ino, NULL, NULL,
+				NULL, NULL, NULL, NULL, &left_flags);
+		if (ret < 0)
+			goto out;
+		/*
+		 * We check against the receive flags first; then check against
+		 * the left flags to see if we can save a chattr later on
+		 */
+		if (sctx->cur_inode_receive_flags & BTRFS_INODE_IMMUTABLE) {
+			sctx->cur_inode_flip_flags |= (left_flags &
+						       BTRFS_INODE_IMMUTABLE);
+			left_flags &= ~BTRFS_INODE_IMMUTABLE;
+			need_chattr = 1;
+		}
+		if (sctx->cur_inode_receive_flags & BTRFS_INODE_APPEND) {
+			sctx->cur_inode_flip_flags |= (left_flags &
+						       BTRFS_INODE_APPEND);
+			left_flags &= ~BTRFS_INODE_APPEND;
+			need_chattr = 1;
+		}
+		if (need_chattr) {
+			need_chattr = 0;
+			ret = send_chattr(sctx, sctx->cur_ino,
+					  sctx->cur_inode_gen, left_flags);
+			if (ret < 0)
+				goto out;
+			/*
+			 * left_flags is now an accurate rep of what the
+			 * receiving inode's flags are
+			 */
+			sctx->cur_inode_receive_flags = left_flags;
+		}
+	}
+
 	if (!at_end && sctx->cmp_key->objectid == sctx->cur_ino)
 		goto out;
 
@@ -5969,7 +6073,7 @@ static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
 		goto truncate_inode;
 
 	ret = get_inode_info(sctx->send_root, sctx->cur_ino, NULL, NULL,
-			&left_mode, &left_uid, &left_gid, NULL);
+			&left_mode, &left_uid, &left_gid, NULL, &left_flags);
 	if (ret < 0)
 		goto out;
 
@@ -5979,12 +6083,14 @@ static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
 			need_chmod = 1;
 		if (sctx->cur_inode_next_write_offset == sctx->cur_inode_size)
 			need_truncate = 0;
+		if (left_flags)
+			need_chattr = 1;
 	} else {
 		u64 old_size;
 
 		ret = get_inode_info(sctx->parent_root, sctx->cur_ino,
 				&old_size, NULL, &right_mode, &right_uid,
-				&right_gid, NULL);
+				&right_gid, NULL, NULL);
 		if (ret < 0)
 			goto out;
 
@@ -6060,6 +6166,27 @@ static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
 			goto out;
 	}
 
+	/*
+	 * At this point, if we toggled stuff earlier, untoggle it
+	 * force a chattr and fix the flags
+	 */
+	if (sctx->cur_inode_flip_flags)
+		left_flags |= sctx->cur_inode_flip_flags;
+
+	/*
+	 * We either need a chattr because this inode is new, or we need to make
+	 * a change due to a discrepancy between left_flags and receive_flags
+	 */
+	if ((need_chattr || (sctx->cur_inode_receive_flags != left_flags)) &&
+	    sctx->flags & BTRFS_SEND_FLAG_STREAM_V2) {
+		ret = send_chattr(sctx, sctx->cur_ino, sctx->cur_inode_gen,
+				  left_flags);
+		if (ret < 0)
+			goto out;
+	}
+	sctx->cur_inode_flip_flags = 0;
+	sctx->cur_inode_receive_flags = 0;
+	sctx->receive_flags_valid = 0;
 out:
 	return ret;
 }
@@ -6380,12 +6507,12 @@ static int dir_changed(struct send_ctx *sctx, u64 dir)
 	int ret;
 
 	ret = get_inode_info(sctx->send_root, dir, NULL, &new_gen, NULL, NULL,
-			     NULL, NULL);
+			     NULL, NULL, NULL);
 	if (ret)
 		return ret;
 
 	ret = get_inode_info(sctx->parent_root, dir, NULL, &orig_gen, NULL,
-			     NULL, NULL, NULL);
+			     NULL, NULL, NULL, NULL);
 	if (ret)
 		return ret;
 
diff --git a/fs/btrfs/send.h b/fs/btrfs/send.h
index 2f5e25e03def..664272ced1af 100644
--- a/fs/btrfs/send.h
+++ b/fs/btrfs/send.h
@@ -85,8 +85,8 @@ enum btrfs_send_cmd {
 	 */
 	BTRFS_SEND_C_TOTAL_DATA_SIZE,
 	BTRFS_SEND_C_FALLOCATE,
-	BTRFS_SEND_C_INODE_SET_FLAGS,
 	BTRFS_SEND_C_UTIMES2, /* Same as UTIMES, but it includes OTIME too. */
+	BTRFS_SEND_C_CHATTR,
 
 	__BTRFS_SEND_C_MAX,
 };
@@ -130,7 +130,7 @@ enum {
 	 * The following attributes were added in stream version 2.
 	 */
 	BTRFS_SEND_A_FALLOCATE_FLAGS,
-	BTRFS_SEND_A_INODE_FLAGS,
+	BTRFS_SEND_A_CHATTR,
 
 	__BTRFS_SEND_A_MAX,
 };
-- 
2.17.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [RFC PATCH 1/6] btrfs: send, bump stream version
  2018-05-09  2:06 ` [RFC PATCH 1/6] btrfs: send, bump stream version Howard McLauchlan
@ 2018-05-16 18:25   ` Omar Sandoval
  0 siblings, 0 replies; 10+ messages in thread
From: Omar Sandoval @ 2018-05-16 18:25 UTC (permalink / raw)
  To: Howard McLauchlan
  Cc: linux-btrfs, Chris Mason, Josef Bacik, David Sterba,
	Filipe Manana, Filipe David Borba Manana, Howard McLauchlan

On Tue, May 08, 2018 at 10:06:46PM -0400, Howard McLauchlan wrote:
> From: Filipe David Borba Manana <fdmanana@gmail.com>
> 
> This increases the send stream version from version 1 to version 2, adding
> new commands:
> 
> 1) total data size - used to tell the receiver how much file data the stream
>    will add or update;
> 
> 2) fallocate - used to pre-allocate space for files and to punch holes in files;
> 
> 3) inode set flags;
> 
> 4) set inode otime.
> 
> This is preparation work for subsequent changes that implement the new features.
> 
> A version 2 stream is only produced if the send ioctl caller passes in one of the
> new flags (BTRFS_SEND_FLAG_CALCULATE_DATA_SIZE | BTRFS_SEND_FLAG_STREAM_V2), meaning
> old clients are unaffected.
> 
> [Howard: rebased on 4.17-rc4]
> Signed-off-by: Howard McLauchlan <hmclauchlan@fb.com>
> Signed-off-by: Filipe David Borba Manana <fdmanana@gmail.com>
> ---
>  fs/btrfs/send.c            |  7 ++++++-
>  fs/btrfs/send.h            | 21 ++++++++++++++++++++-
>  include/uapi/linux/btrfs.h | 21 ++++++++++++++++++++-
>  3 files changed, 46 insertions(+), 3 deletions(-)
> 
> diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c
> index c0074d2d7d6d..eccd69387065 100644
> --- a/fs/btrfs/send.c
> +++ b/fs/btrfs/send.c
> @@ -649,7 +649,10 @@ static int send_header(struct send_ctx *sctx)
>  	struct btrfs_stream_header hdr;
>  
>  	strcpy(hdr.magic, BTRFS_SEND_STREAM_MAGIC);
> -	hdr.version = cpu_to_le32(BTRFS_SEND_STREAM_VERSION);
> +	if (sctx->flags & BTRFS_SEND_FLAG_STREAM_V2)
> +		hdr.version = cpu_to_le32(BTRFS_SEND_STREAM_VERSION_2);
> +	else
> +		hdr.version = cpu_to_le32(BTRFS_SEND_STREAM_VERSION_1);
>  
>  	return write_buf(sctx->send_filp, &hdr, sizeof(hdr),
>  					&sctx->send_off);
> @@ -6535,6 +6538,8 @@ long btrfs_ioctl_send(struct file *mnt_file, struct btrfs_ioctl_send_args *arg)
>  	INIT_LIST_HEAD(&sctx->name_cache_list);
>  
>  	sctx->flags = arg->flags;
> +	if (sctx->flags & BTRFS_SEND_FLAG_CALCULATE_DATA_SIZE)
> +		sctx->flags |= BTRFS_SEND_FLAG_STREAM_V2;
>  
>  	sctx->send_filp = fget(arg->send_fd);
>  	if (!sctx->send_filp) {
> diff --git a/fs/btrfs/send.h b/fs/btrfs/send.h
> index ead397f7034f..a9b5489d690e 100644
> --- a/fs/btrfs/send.h
> +++ b/fs/btrfs/send.h
> @@ -10,7 +10,8 @@
>  #include "ctree.h"
>  
>  #define BTRFS_SEND_STREAM_MAGIC "btrfs-stream"
> -#define BTRFS_SEND_STREAM_VERSION 1
> +#define BTRFS_SEND_STREAM_VERSION_1 1
> +#define BTRFS_SEND_STREAM_VERSION_2 2
>  
>  #define BTRFS_SEND_BUF_SIZE SZ_64K
>  #define BTRFS_SEND_READ_SIZE (48 * SZ_1K)
> @@ -77,6 +78,15 @@ enum btrfs_send_cmd {
>  
>  	BTRFS_SEND_C_END,
>  	BTRFS_SEND_C_UPDATE_EXTENT,
> +
> +	/*
> +	 * The following commands were added in stream version 2.
> +	 */
> +	BTRFS_SEND_C_TOTAL_DATA_SIZE,
> +	BTRFS_SEND_C_FALLOCATE,
> +	BTRFS_SEND_C_INODE_SET_FLAGS,
> +	BTRFS_SEND_C_UTIMES2, /* Same as UTIMES, but it includes OTIME too. */

I've had feature requests for the ability to send compressed data
without decompressing it on the send side or the receive side. Could you
add a placeholder BTRFS_SEND_C_WRITE_COMPRESSED here? This way when I
get around to implementing it I won't have to do a v3.

Other than that,

Reviewed-by: Omar Sandoval <osandov@fb.com>

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [RFC PATCH 6/6] btrfs: add chattr support for send/receive
  2018-05-09  2:06 ` [RFC PATCH 6/6] btrfs: add chattr support for send/receive Howard McLauchlan
@ 2018-05-16 18:59   ` Omar Sandoval
  0 siblings, 0 replies; 10+ messages in thread
From: Omar Sandoval @ 2018-05-16 18:59 UTC (permalink / raw)
  To: Howard McLauchlan
  Cc: linux-btrfs, Chris Mason, Josef Bacik, David Sterba,
	Filipe Manana, Howard McLauchlan

On Tue, May 08, 2018 at 10:06:51PM -0400, Howard McLauchlan wrote:
> From: Howard McLauchlan <hmclauchlan@fb.com>
> 
> Presently btrfs send/receive does not propagate inode attribute flags;
> all chattr operations are effectively discarded upon transmission.
> 
> This patch adds kernel support for inode attribute flags. Userspace
> support can be found under the commit:
> 
>     btrfs-progs: add chattr support for send/receive
> 
> An associated xfstest can be found at:
> 
>     btrfs: add verify chattr support for send/receive test
> 
> These changes are only enabled for send stream version 2
> 
> Signed-off-by: Howard McLauchlan <hmclauchlan@fb.com>
> ---
>  fs/btrfs/ctree.h |   2 +
>  fs/btrfs/ioctl.c |   2 +-
>  fs/btrfs/send.c  | 181 ++++++++++++++++++++++++++++++++++++++++-------
>  fs/btrfs/send.h  |   4 +-
>  4 files changed, 159 insertions(+), 30 deletions(-)
> 
> diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h
> index 2771cc56a622..0a2359144b18 100644
> --- a/fs/btrfs/ctree.h
> +++ b/fs/btrfs/ctree.h
> @@ -1461,6 +1461,8 @@ struct btrfs_map_token {
>  	unsigned long offset;
>  };
>  
> +unsigned int btrfs_flags_to_ioctl(unsigned int flags);
> +
>  #define BTRFS_BYTES_TO_BLKS(fs_info, bytes) \
>  				((bytes) >> (fs_info)->sb->s_blocksize_bits)
>  
> diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
> index 632e26d6f7ce..36ce1e589f9e 100644
> --- a/fs/btrfs/ioctl.c
> +++ b/fs/btrfs/ioctl.c
> @@ -106,7 +106,7 @@ static unsigned int btrfs_mask_flags(umode_t mode, unsigned int flags)
>  /*
>   * Export inode flags to the format expected by the FS_IOC_GETFLAGS ioctl.
>   */
> -static unsigned int btrfs_flags_to_ioctl(unsigned int flags)
> +unsigned int btrfs_flags_to_ioctl(unsigned int flags)
>  {
>  	unsigned int iflags = 0;
>  
> diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c
> index c8ea1ccaa3d8..fa7db1474a7f 100644
> --- a/fs/btrfs/send.c
> +++ b/fs/btrfs/send.c
> @@ -108,6 +108,13 @@ struct send_ctx {
>  	u64 cur_inode_last_extent;
>  	u64 cur_inode_next_write_offset;
>  
> +	/*
> +	 * state for chattr purposes
> +	 */
> +	u64 cur_inode_flip_flags;
> +	u64 cur_inode_receive_flags;
> +	int receive_flags_valid;

I'd use bool here (and change the places that set it to use true or
false).

> +
>  	u64 send_progress;
>  	u64 total_data_size;
>  
> @@ -815,7 +822,7 @@ static int send_rmdir(struct send_ctx *sctx, struct fs_path *path)
>   */
>  static int __get_inode_info(struct btrfs_root *root, struct btrfs_path *path,
>  			  u64 ino, u64 *size, u64 *gen, u64 *mode, u64 *uid,
> -			  u64 *gid, u64 *rdev)
> +			  u64 *gid, u64 *rdev, u64 *flags)
>  {
>  	int ret;
>  	struct btrfs_inode_item *ii;
> @@ -845,6 +852,8 @@ static int __get_inode_info(struct btrfs_root *root, struct btrfs_path *path,
>  		*gid = btrfs_inode_gid(path->nodes[0], ii);
>  	if (rdev)
>  		*rdev = btrfs_inode_rdev(path->nodes[0], ii);
> +	if (flags)
> +		*flags = btrfs_inode_flags(path->nodes[0], ii);
>  
>  	return ret;
>  }
> @@ -852,7 +861,7 @@ static int __get_inode_info(struct btrfs_root *root, struct btrfs_path *path,
>  static int get_inode_info(struct btrfs_root *root,
>  			  u64 ino, u64 *size, u64 *gen,
>  			  u64 *mode, u64 *uid, u64 *gid,
> -			  u64 *rdev)
> +			  u64 *rdev, u64 *flags)
>  {
>  	struct btrfs_path *path;
>  	int ret;
> @@ -861,7 +870,7 @@ static int get_inode_info(struct btrfs_root *root,
>  	if (!path)
>  		return -ENOMEM;
>  	ret = __get_inode_info(root, path, ino, size, gen, mode, uid, gid,
> -			       rdev);
> +			       rdev, flags);
>  	btrfs_free_path(path);
>  	return ret;
>  }
> @@ -1250,7 +1259,7 @@ static int __iterate_backrefs(u64 ino, u64 offset, u64 root, void *ctx_)
>  	 * accept clones from these extents.
>  	 */
>  	ret = __get_inode_info(found->root, bctx->path, ino, &i_size, NULL, NULL,
> -			       NULL, NULL, NULL);
> +			       NULL, NULL, NULL, NULL);
>  	btrfs_release_path(bctx->path);
>  	if (ret < 0)
>  		return ret;
> @@ -1610,7 +1619,7 @@ static int get_cur_inode_state(struct send_ctx *sctx, u64 ino, u64 gen)
>  	u64 right_gen;
>  
>  	ret = get_inode_info(sctx->send_root, ino, NULL, &left_gen, NULL, NULL,
> -			NULL, NULL);
> +			NULL, NULL, NULL);
>  	if (ret < 0 && ret != -ENOENT)
>  		goto out;
>  	left_ret = ret;
> @@ -1619,7 +1628,7 @@ static int get_cur_inode_state(struct send_ctx *sctx, u64 ino, u64 gen)
>  		right_ret = -ENOENT;
>  	} else {
>  		ret = get_inode_info(sctx->parent_root, ino, NULL, &right_gen,
> -				NULL, NULL, NULL, NULL);
> +				NULL, NULL, NULL, NULL, NULL);
>  		if (ret < 0 && ret != -ENOENT)
>  			goto out;
>  		right_ret = ret;
> @@ -1788,7 +1797,7 @@ static int get_first_ref(struct btrfs_root *root, u64 ino,
>  
>  	if (dir_gen) {
>  		ret = get_inode_info(root, parent_dir, NULL, dir_gen, NULL,
> -				     NULL, NULL, NULL);
> +				     NULL, NULL, NULL, NULL);
>  		if (ret < 0)
>  			goto out;
>  	}
> @@ -1861,7 +1870,7 @@ static int will_overwrite_ref(struct send_ctx *sctx, u64 dir, u64 dir_gen,
>  	 */
>  	if (sctx->parent_root && dir != BTRFS_FIRST_FREE_OBJECTID) {
>  		ret = get_inode_info(sctx->parent_root, dir, NULL, &gen, NULL,
> -				     NULL, NULL, NULL);
> +				     NULL, NULL, NULL, NULL);
>  		if (ret < 0 && ret != -ENOENT)
>  			goto out;
>  		if (ret) {
> @@ -1889,7 +1898,7 @@ static int will_overwrite_ref(struct send_ctx *sctx, u64 dir, u64 dir_gen,
>  	if (other_inode > sctx->send_progress ||
>  	    is_waiting_for_move(sctx, other_inode)) {
>  		ret = get_inode_info(sctx->parent_root, other_inode, NULL,
> -				who_gen, who_mode, NULL, NULL, NULL);
> +				who_gen, who_mode, NULL, NULL, NULL, NULL);
>  		if (ret < 0)
>  			goto out;
>  
> @@ -1929,7 +1938,7 @@ static int did_overwrite_ref(struct send_ctx *sctx,
>  
>  	if (dir != BTRFS_FIRST_FREE_OBJECTID) {
>  		ret = get_inode_info(sctx->send_root, dir, NULL, &gen, NULL,
> -				     NULL, NULL, NULL);
> +				     NULL, NULL, NULL, NULL);
>  		if (ret < 0 && ret != -ENOENT)
>  			goto out;
>  		if (ret) {
> @@ -1952,7 +1961,7 @@ static int did_overwrite_ref(struct send_ctx *sctx,
>  	}
>  
>  	ret = get_inode_info(sctx->send_root, ow_inode, NULL, &gen, NULL, NULL,
> -			NULL, NULL);
> +			NULL, NULL, NULL);
>  	if (ret < 0)
>  		goto out;
>  
> @@ -2525,6 +2534,42 @@ static int send_chown(struct send_ctx *sctx, u64 ino, u64 gen, u64 uid, u64 gid)
>  	return ret;
>  }
>  
> +static int send_chattr(struct send_ctx *sctx, u64 ino, u64 gen, u64 flags)
> +{
> +	struct btrfs_fs_info *fs_info = sctx->send_root->fs_info;
> +	int ret = 0;
> +	int __flags;
> +	struct fs_path *p;
> +
> +	if (sctx->phase == SEND_PHASE_COMPUTE_DATA_SIZE)
> +		return 0;

The other send_foo() commands have
ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE) instead, why is this
one different? You'll want to enable CONFIG_BTRFS_ASSERT when you test,
otherwise ASSERT() is a noop.

> +	__flags = btrfs_flags_to_ioctl(flags);
> +
> +	btrfs_debug(fs_info, "send_chattr %llu flags=%llu", ino, flags);
> +
> +	p = fs_path_alloc();
> +	if (!p)
> +		return -ENOMEM;
> +
> +	ret = begin_cmd(sctx, BTRFS_SEND_C_CHATTR);
> +	if (ret < 0)
> +		goto out;
> +
> +	ret = get_cur_path(sctx, ino, gen, p);
> +	if (ret < 0)
> +		goto out;
> +	TLV_PUT_PATH(sctx, BTRFS_SEND_A_PATH, p);
> +	TLV_PUT_U64(sctx, BTRFS_SEND_A_CHATTR, __flags);
> +
> +	ret = send_cmd(sctx);
> +
> +tlv_put_failure:
> +out:
> +	fs_path_free(p);
> +	return ret;
> +}
> +
>  static int send_utimes(struct send_ctx *sctx, u64 ino, u64 gen)
>  {
>  	struct btrfs_fs_info *fs_info = sctx->send_root->fs_info;
> @@ -2610,7 +2655,7 @@ static int send_create_inode(struct send_ctx *sctx, u64 ino)
>  
>  	if (ino != sctx->cur_ino) {
>  		ret = get_inode_info(sctx->send_root, ino, NULL, &gen, &mode,
> -				     NULL, NULL, &rdev);
> +				     NULL, NULL, &rdev, NULL);
>  		if (ret < 0)
>  			goto out;
>  	} else {
> @@ -3332,7 +3377,7 @@ static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm)
>  		 * The parent inode might have been deleted in the send snapshot
>  		 */
>  		ret = get_inode_info(sctx->send_root, cur->dir, NULL,
> -				     NULL, NULL, NULL, NULL, NULL);
> +				     NULL, NULL, NULL, NULL, NULL, NULL);
>  		if (ret == -ENOENT) {
>  			ret = 0;
>  			continue;
> @@ -3502,11 +3547,11 @@ static int wait_for_dest_dir_move(struct send_ctx *sctx,
>  	}
>  
>  	ret = get_inode_info(sctx->parent_root, di_key.objectid, NULL,
> -			     &left_gen, NULL, NULL, NULL, NULL);
> +			     &left_gen, NULL, NULL, NULL, NULL, NULL);
>  	if (ret < 0)
>  		goto out;
>  	ret = get_inode_info(sctx->send_root, di_key.objectid, NULL,
> -			     &right_gen, NULL, NULL, NULL, NULL);
> +			     &right_gen, NULL, NULL, NULL, NULL, NULL);
>  	if (ret < 0) {
>  		if (ret == -ENOENT)
>  			ret = 0;
> @@ -3650,7 +3695,7 @@ static int is_ancestor(struct btrfs_root *root,
>  			}
>  
>  			ret = get_inode_info(root, parent, NULL, &parent_gen,
> -					     NULL, NULL, NULL, NULL);
> +					     NULL, NULL, NULL, NULL, NULL);
>  			if (ret < 0)
>  				goto out;
>  			ret = check_ino_in_path(root, ino1, ino1_gen,
> @@ -3740,7 +3785,7 @@ static int wait_for_parent_move(struct send_ctx *sctx,
>  
>  			ret = get_inode_info(sctx->parent_root, ino, NULL,
>  					     &parent_ino_gen, NULL, NULL, NULL,
> -					     NULL);
> +					     NULL, NULL);
>  			if (ret < 0)
>  				goto out;
>  			if (ino_gen == parent_ino_gen) {
> @@ -4220,7 +4265,7 @@ static int record_ref(struct btrfs_root *root, u64 dir, struct fs_path *name,
>  		return -ENOMEM;
>  
>  	ret = get_inode_info(root, dir, NULL, &gen, NULL, NULL,
> -			NULL, NULL);
> +			NULL, NULL, NULL);
>  	if (ret < 0)
>  		goto out;
>  
> @@ -4308,7 +4353,7 @@ static int __find_iref(int num, u64 dir, int index,
>  		 * else matches.
>  		 */
>  		ret = get_inode_info(ctx->root, dir, NULL, &dir_gen, NULL,
> -				     NULL, NULL, NULL);
> +				     NULL, NULL, NULL, NULL);
>  		if (ret)
>  			return ret;
>  		if (dir_gen != ctx->dir_gen)
> @@ -4352,7 +4397,7 @@ static int __record_changed_new_ref(int num, u64 dir, int index,
>  	struct send_ctx *sctx = ctx;
>  
>  	ret = get_inode_info(sctx->send_root, dir, NULL, &dir_gen, NULL,
> -			     NULL, NULL, NULL);
> +			     NULL, NULL, NULL, NULL);
>  	if (ret)
>  		return ret;
>  
> @@ -4375,7 +4420,7 @@ static int __record_changed_deleted_ref(int num, u64 dir, int index,
>  	struct send_ctx *sctx = ctx;
>  
>  	ret = get_inode_info(sctx->parent_root, dir, NULL, &dir_gen, NULL,
> -			     NULL, NULL, NULL);
> +			     NULL, NULL, NULL, NULL);
>  	if (ret)
>  		return ret;
>  
> @@ -4975,7 +5020,7 @@ static int send_clone(struct send_ctx *sctx,
>  
>  	if (clone_root->root == sctx->send_root) {
>  		ret = get_inode_info(sctx->send_root, clone_root->ino, NULL,
> -				&gen, NULL, NULL, NULL, NULL);
> +				&gen, NULL, NULL, NULL, NULL, NULL);
>  		if (ret < 0)
>  			goto out;
>  		ret = get_cur_path(sctx, clone_root->ino, gen, p);
> @@ -5934,9 +5979,11 @@ static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
>  	u64 right_mode;
>  	u64 right_uid;
>  	u64 right_gid;
> +	u64 left_flags;
>  	int need_chmod = 0;
>  	int need_chown = 0;
>  	int need_truncate = 1;
> +	int need_chattr = 0;

Sigh, I was going to say to use bool here, but the other ones are int,
so it's better to be consistent.

>  	int pending_move = 0;
>  	int refs_processed = 0;
>  
> @@ -5944,7 +5991,6 @@ static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
>  					      &refs_processed);
>  	if (ret < 0)
>  		goto out;
> -
>  	/*
>  	 * We have processed the refs and thus need to advance send_progress.
>  	 * Now, calls to get_cur_xxx will take the updated refs of the current
> @@ -5962,6 +6008,64 @@ static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
>  
>  	if (sctx->cur_ino == 0 || sctx->cur_inode_deleted)
>  		goto out;
> +
> +	/*
> +	 * If possible, we want to know what flags are set for this inode on the
> +	 * receiving end.
> +	 */
> +	if (sctx->parent_root && !sctx->receive_flags_valid && sctx->flags &
> +	    BTRFS_SEND_FLAG_STREAM_V2) {

Wrap this after the && instead of breaking up the operands to &. I also
had to look up the operator precedence of && vs & here, which probably
means this should just have parentheses :) So like so:

	if (sctx->parent_root && !sctx->receive_flags_valid &&
	    (sctx->flags & BTRFS_SEND_FLAG_STREAM_V2)) {

> +		ret = get_inode_info(sctx->parent_root, sctx->cur_ino, NULL,
> +				     NULL, NULL, NULL, NULL, NULL,
> +				     &sctx->cur_inode_receive_flags);
> +		if (ret < 0)
> +			goto out;
> +		sctx->receive_flags_valid = 1;
> +	}
> +
> +	/*
> +	 * The change is going to modify data and the inode already exists
> +	 * !at_end prevents unnecessary chattr.
> +	 */
> +	if (!at_end && sctx->parent_root && !sctx->cur_inode_new &&
> +	    (sctx->cmp_key->type == BTRFS_EXTENT_DATA_KEY ||
> +	    sctx->cmp_key->type == BTRFS_XATTR_ITEM_KEY) &&
> +	    sctx->flags & BTRFS_SEND_FLAG_STREAM_V2) {

Same comment about parentheses around sctx->flags & BTRFS_SEND_FLAG_STREAM_V2.

> +
> +		ret = get_inode_info(sctx->send_root, sctx->cur_ino, NULL, NULL,
> +				NULL, NULL, NULL, NULL, &left_flags);
> +		if (ret < 0)
> +			goto out;
> +		/*
> +		 * We check against the receive flags first; then check against
> +		 * the left flags to see if we can save a chattr later on
> +		 */
> +		if (sctx->cur_inode_receive_flags & BTRFS_INODE_IMMUTABLE) {
> +			sctx->cur_inode_flip_flags |= (left_flags &
> +						       BTRFS_INODE_IMMUTABLE);
> +			left_flags &= ~BTRFS_INODE_IMMUTABLE;
> +			need_chattr = 1;
> +		}
> +		if (sctx->cur_inode_receive_flags & BTRFS_INODE_APPEND) {
> +			sctx->cur_inode_flip_flags |= (left_flags &
> +						       BTRFS_INODE_APPEND);
> +			left_flags &= ~BTRFS_INODE_APPEND;
> +			need_chattr = 1;
> +		}
> +		if (need_chattr) {
> +			need_chattr = 0;
> +			ret = send_chattr(sctx, sctx->cur_ino,
> +					  sctx->cur_inode_gen, left_flags);
> +			if (ret < 0)
> +				goto out;
> +			/*
> +			 * left_flags is now an accurate rep of what the
> +			 * receiving inode's flags are
> +			 */
> +			sctx->cur_inode_receive_flags = left_flags;
> +		}
> +	}
> +
>  	if (!at_end && sctx->cmp_key->objectid == sctx->cur_ino)
>  		goto out;
>  
> @@ -5969,7 +6073,7 @@ static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
>  		goto truncate_inode;
>  
>  	ret = get_inode_info(sctx->send_root, sctx->cur_ino, NULL, NULL,
> -			&left_mode, &left_uid, &left_gid, NULL);
> +			&left_mode, &left_uid, &left_gid, NULL, &left_flags);
>  	if (ret < 0)
>  		goto out;
>  
> @@ -5979,12 +6083,14 @@ static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
>  			need_chmod = 1;
>  		if (sctx->cur_inode_next_write_offset == sctx->cur_inode_size)
>  			need_truncate = 0;
> +		if (left_flags)
> +			need_chattr = 1;

Why is need_chattr necessary? Will this not be covered by the
sctx->cur_inode_receive_flags != left_flags check?

>  	} else {
>  		u64 old_size;
>  
>  		ret = get_inode_info(sctx->parent_root, sctx->cur_ino,
>  				&old_size, NULL, &right_mode, &right_uid,
> -				&right_gid, NULL);
> +				&right_gid, NULL, NULL);
>  		if (ret < 0)
>  			goto out;
>  
> @@ -6060,6 +6166,27 @@ static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
>  			goto out;
>  	}
>  
> +	/*
> +	 * At this point, if we toggled stuff earlier, untoggle it
> +	 * force a chattr and fix the flags
> +	 */
> +	if (sctx->cur_inode_flip_flags)
> +		left_flags |= sctx->cur_inode_flip_flags;
> +
> +	/*
> +	 * We either need a chattr because this inode is new, or we need to make
> +	 * a change due to a discrepancy between left_flags and receive_flags
> +	 */
> +	if ((need_chattr || (sctx->cur_inode_receive_flags != left_flags)) &&
> +	    sctx->flags & BTRFS_SEND_FLAG_STREAM_V2) {

Same parentheses thing.

> +		ret = send_chattr(sctx, sctx->cur_ino, sctx->cur_inode_gen,
> +				  left_flags);
> +		if (ret < 0)
> +			goto out;
> +	}
> +	sctx->cur_inode_flip_flags = 0;
> +	sctx->cur_inode_receive_flags = 0;
> +	sctx->receive_flags_valid = 0;
>  out:
>  	return ret;
>  }
> @@ -6380,12 +6507,12 @@ static int dir_changed(struct send_ctx *sctx, u64 dir)
>  	int ret;
>  
>  	ret = get_inode_info(sctx->send_root, dir, NULL, &new_gen, NULL, NULL,
> -			     NULL, NULL);
> +			     NULL, NULL, NULL);
>  	if (ret)
>  		return ret;
>  
>  	ret = get_inode_info(sctx->parent_root, dir, NULL, &orig_gen, NULL,
> -			     NULL, NULL, NULL);
> +			     NULL, NULL, NULL, NULL);
>  	if (ret)
>  		return ret;
>  
> diff --git a/fs/btrfs/send.h b/fs/btrfs/send.h
> index 2f5e25e03def..664272ced1af 100644
> --- a/fs/btrfs/send.h
> +++ b/fs/btrfs/send.h
> @@ -85,8 +85,8 @@ enum btrfs_send_cmd {
>  	 */
>  	BTRFS_SEND_C_TOTAL_DATA_SIZE,
>  	BTRFS_SEND_C_FALLOCATE,
> -	BTRFS_SEND_C_INODE_SET_FLAGS,
>  	BTRFS_SEND_C_UTIMES2, /* Same as UTIMES, but it includes OTIME too. */
> +	BTRFS_SEND_C_CHATTR,

I don't care too much about the naming, but whichever one you do should
be there from patch 1 and not get renamed or renumbered.

>  	__BTRFS_SEND_C_MAX,
>  };
> @@ -130,7 +130,7 @@ enum {
>  	 * The following attributes were added in stream version 2.
>  	 */
>  	BTRFS_SEND_A_FALLOCATE_FLAGS,
> -	BTRFS_SEND_A_INODE_FLAGS,
> +	BTRFS_SEND_A_CHATTR,

Same comment here re: naming.

>  
>  	__BTRFS_SEND_A_MAX,
>  };
> -- 
> 2.17.0
> 

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [RFC PATCH 5/6] btrfs: add send_stream_version attribute to sysfs
  2018-05-09  2:06 ` [RFC PATCH 5/6] btrfs: add send_stream_version attribute to sysfs Howard McLauchlan
@ 2018-05-16 19:04   ` Omar Sandoval
  0 siblings, 0 replies; 10+ messages in thread
From: Omar Sandoval @ 2018-05-16 19:04 UTC (permalink / raw)
  To: Howard McLauchlan
  Cc: linux-btrfs, Chris Mason, Josef Bacik, David Sterba,
	Filipe Manana, Filipe David Borba Manana, Howard McLauchlan

On Tue, May 08, 2018 at 10:06:50PM -0400, Howard McLauchlan wrote:
> From: Filipe David Borba Manana <fdmanana@gmail.com>
> 
> So that applications can find out what's the highest send stream
> version supported/implemented by the running kernel:
> 
>     $ cat /sys/fs/btrfs/send/stream_version
>     2
> 
> [Howard: rebased on 4.17-rc4]
> Signed-off-by: Howard McLauchlan <hmclauchlan@fb.com>
> Signed-off-by: Filipe David Borba Manana <fdmanana@gmail.com>
> Reviewed-by: David Sterba <dsterba@suse.cz>
> ---
>  fs/btrfs/send.h  |  1 +
>  fs/btrfs/sysfs.c | 29 +++++++++++++++++++++++++++++
>  2 files changed, 30 insertions(+)
> 
> diff --git a/fs/btrfs/send.h b/fs/btrfs/send.h
> index a5830d216ac1..2f5e25e03def 100644
> --- a/fs/btrfs/send.h
> +++ b/fs/btrfs/send.h
> @@ -12,6 +12,7 @@
>  #define BTRFS_SEND_STREAM_MAGIC "btrfs-stream"
>  #define BTRFS_SEND_STREAM_VERSION_1 1
>  #define BTRFS_SEND_STREAM_VERSION_2 2
> +#define BTRFS_SEND_STREAM_VERSION_LATEST BTRFS_SEND_STREAM_VERSION_2
>  
>  #define BTRFS_SEND_BUF_SIZE SZ_64K
>  #define BTRFS_SEND_READ_SIZE (48 * SZ_1K)
> diff --git a/fs/btrfs/sysfs.c b/fs/btrfs/sysfs.c
> index 4848a4318fb5..3c82cba91ff6 100644
> --- a/fs/btrfs/sysfs.c
> +++ b/fs/btrfs/sysfs.c
> @@ -18,6 +18,7 @@
>  #include "transaction.h"
>  #include "sysfs.h"
>  #include "volumes.h"
> +#include "send.h"
>  
>  static inline struct btrfs_fs_info *to_fs_info(struct kobject *kobj);
>  static inline struct btrfs_fs_devices *to_fs_devs(struct kobject *kobj);
> @@ -884,6 +885,28 @@ static int btrfs_init_debugfs(void)
>  	return 0;
>  }
>  
> +static ssize_t send_stream_version_show(struct kobject *kobj,
> +					struct kobj_attribute *a,
> +					char *buf)
> +{
> +	return snprintf(buf, PAGE_SIZE, "%d\n",
> +			BTRFS_SEND_STREAM_VERSION_LATEST);
> +}
> +
> +BTRFS_ATTR(, stream_version, send_stream_version_show);
> +
> +static struct attribute *btrfs_send_attrs[] = {
> +	BTRFS_ATTR_PTR(, stream_version),
> +	NULL
> +};
> +
> +static const struct attribute_group btrfs_send_attr_group = {
> +	.name = "send",
> +	.attrs = btrfs_send_attrs,
> +};
> +
> +
> +

I know this is Filipe's code, but there are two extra newlines there ;)
Otherwise, assuming you tested this and it works as advertised,

Reviewed-by: Omar Sandoval <osandov@fb.com>

>  int __init btrfs_init_sysfs(void)
>  {
>  	int ret;
> @@ -900,8 +923,13 @@ int __init btrfs_init_sysfs(void)
>  	ret = sysfs_create_group(&btrfs_kset->kobj, &btrfs_feature_attr_group);
>  	if (ret)
>  		goto out2;
> +	ret = sysfs_create_group(&btrfs_kset->kobj, &btrfs_send_attr_group);
> +	if (ret)
> +		goto out3;
>  
>  	return 0;
> +out3:
> +	sysfs_remove_group(&btrfs_kset->kobj, &btrfs_feature_attr_group);
>  out2:
>  	debugfs_remove_recursive(btrfs_debugfs_root_dentry);
>  out1:
> @@ -913,6 +941,7 @@ int __init btrfs_init_sysfs(void)
>  void __cold btrfs_exit_sysfs(void)
>  {
>  	sysfs_remove_group(&btrfs_kset->kobj, &btrfs_feature_attr_group);
> +	sysfs_remove_group(&btrfs_kset->kobj, &btrfs_send_attr_group);
>  	kset_unregister(btrfs_kset);
>  	debugfs_remove_recursive(btrfs_debugfs_root_dentry);
>  }
> -- 
> 2.17.0
> 

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2018-05-16 19:04 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-05-09  2:06 [RFC PATCH 0/6] btrfs send stream version 2 Howard McLauchlan
2018-05-09  2:06 ` [RFC PATCH 1/6] btrfs: send, bump stream version Howard McLauchlan
2018-05-16 18:25   ` Omar Sandoval
2018-05-09  2:06 ` [RFC PATCH 2/6] btrfs: send, implement total data size command to allow for progress estimation Howard McLauchlan
2018-05-09  2:06 ` [RFC PATCH 3/6] btrfs: send, use fallocate command to punch holes Howard McLauchlan
2018-05-09  2:06 ` [RFC PATCH 4/6] btrfs: send, use fallocate command to allocate extents Howard McLauchlan
2018-05-09  2:06 ` [RFC PATCH 5/6] btrfs: add send_stream_version attribute to sysfs Howard McLauchlan
2018-05-16 19:04   ` Omar Sandoval
2018-05-09  2:06 ` [RFC PATCH 6/6] btrfs: add chattr support for send/receive Howard McLauchlan
2018-05-16 18:59   ` Omar Sandoval

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).