public inbox for linux-btrfs@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/4] btrfs-progs: new --inode-flags option
@ 2025-05-21  9:51 Qu Wenruo
  2025-05-21  9:51 ` [PATCH 1/4] btrfs-progs: allow new inodes to inherit flags from their parents Qu Wenruo
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Qu Wenruo @ 2025-05-21  9:51 UTC (permalink / raw)
  To: linux-btrfs

The new --inode-flags option allows us to specify certain btrfs specific
flags to each inode.

Currently we only support *nodatacow* and *nodatasum*.

But in the future compression flag can also be added, allowing more
accurate per-file compression.

Furthermore child inodes will inherit the flag from their parents,
meaning one only needs to specify the flag to the parent directory, then
all children files/directories will have the flag.

This new option also works well with --subvol, although one has to
note that, the inode flag inheritance does not cross subvolume boundary
(the same as the kernel).

Finally, nodatacow and nodatasum will disable compression, just like the
kernel.

Qu Wenruo (4):
  btrfs-progs: allow new inodes to inherit flags from their parents
  btrfs-progs: do not generate checksum nor compress if the inode has
    NODATACOW or NODATASUM
  btrfs-progs: mkfs: add --inode-flags option
  btrfs-progs: mkfs-tests: a new test case for --inode-flags

 Documentation/mkfs.btrfs.rst             |  35 +++++++
 kernel-shared/inode.c                    |  64 ++++++++++++
 mkfs/main.c                              | 120 ++++++++++++++++++++++-
 mkfs/rootdir.c                           |  95 ++++++++++++++++--
 mkfs/rootdir.h                           |  15 +++
 tests/mkfs-tests/038-inode-flags/test.sh |  55 +++++++++++
 6 files changed, 375 insertions(+), 9 deletions(-)
 create mode 100755 tests/mkfs-tests/038-inode-flags/test.sh

--
2.49.0


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

* [PATCH 1/4] btrfs-progs: allow new inodes to inherit flags from their parents
  2025-05-21  9:51 [PATCH 0/4] btrfs-progs: new --inode-flags option Qu Wenruo
@ 2025-05-21  9:51 ` Qu Wenruo
  2025-05-21  9:51 ` [PATCH 2/4] btrfs-progs: do not generate checksum nor compress if the inode has NODATACOW or NODATASUM Qu Wenruo
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Qu Wenruo @ 2025-05-21  9:51 UTC (permalink / raw)
  To: linux-btrfs

Inside btrfs-progs (mostly 'mkfs/rootdir.c') new inodes are created in a
different way than the kernel:

- A new orphan inode item is created
  Without knowing the parent inode, thus it will always have the default
  flags (0).

- Link the new inode to a parent

Meanwhile inside the kernel, we know exactly the parent inode at new
inode creation time, and can inherit the inode flags
(NODATACOW/NODATASUM/COMPRESS/etc).

Address the missing ability by:

- Inherit the parent inode flags when linking an orphan inode
  The function btrfs_add_link() is called when linking an inode.

  It can be called to creating the initial link if it's a new and orphan
  inode.
  It can also be called to creating extra hard links.

  If the inode is already orphan, we know it's newly created and should
  inherit the inode flag from the parent.

With this new ability, it will be much easier to implement new per-inode
flags (like NODATACOW/NODATASUM) and get them properly passed down.

Signed-off-by: Qu Wenruo <wqu@suse.com>
---
 kernel-shared/inode.c | 64 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 64 insertions(+)

diff --git a/kernel-shared/inode.c b/kernel-shared/inode.c
index 97bcbf82225c..66a57b333906 100644
--- a/kernel-shared/inode.c
+++ b/kernel-shared/inode.c
@@ -158,6 +158,59 @@ out:
 	return ret;
 }
 
+/* Similiar to btrfs_inherit_iflags(), but different interfaces. */
+static int inherit_inode_flags(struct btrfs_trans_handle *trans,
+			       struct btrfs_root *root, u64 ino, u64 parent_ino)
+{
+	struct btrfs_path path = { 0 };
+	struct btrfs_key key;
+	struct btrfs_inode_item *iitem;
+	u64 parent_inode_flags;
+	u64 child_inode_flags;
+	int ret;
+
+	key.objectid = parent_ino;
+	key.type = BTRFS_INODE_ITEM_KEY;
+	key.offset = 0;
+
+	ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0);
+	if (ret > 0)
+		ret = -ENOENT;
+	if (ret < 0)
+		goto out;
+
+	iitem = btrfs_item_ptr(path.nodes[0], path.slots[0], struct btrfs_inode_item);
+	parent_inode_flags = btrfs_inode_flags(path.nodes[0], iitem);
+	btrfs_release_path(&path);
+
+	key.objectid = ino;
+
+	ret = btrfs_search_slot(trans, root, &key, &path, 0, 1);
+	if (ret > 0)
+		ret = -ENOENT;
+	if (ret < 0)
+		goto out;
+	iitem = btrfs_item_ptr(path.nodes[0], path.slots[0], struct btrfs_inode_item);
+	child_inode_flags = btrfs_inode_flags(path.nodes[0], iitem);
+
+	if (parent_inode_flags & BTRFS_INODE_NOCOMPRESS) {
+		child_inode_flags &= ~BTRFS_INODE_COMPRESS;
+		child_inode_flags |= BTRFS_INODE_NOCOMPRESS;
+	} else if (parent_inode_flags & BTRFS_INODE_COMPRESS){
+		child_inode_flags &= ~BTRFS_INODE_NOCOMPRESS;
+		child_inode_flags |= BTRFS_INODE_COMPRESS;
+	}
+	if (parent_inode_flags & BTRFS_INODE_NODATACOW) {
+		child_inode_flags |= BTRFS_INODE_NODATACOW;
+		if (S_ISREG(btrfs_inode_mode(path.nodes[0], iitem)))
+			child_inode_flags |= BTRFS_INODE_NODATASUM;
+	}
+	btrfs_set_inode_flags(path.nodes[0], iitem, child_inode_flags);
+out:
+	btrfs_release_path(&path);
+	return ret;
+}
+
 /*
  * Add dir_item/index for 'parent_ino' if add_backref is true, also insert a
  * backref from the ino to parent dir and update the nlink(Kernel version does
@@ -220,6 +273,17 @@ int btrfs_add_link(struct btrfs_trans_handle *trans, struct btrfs_root *root,
 					      nlink);
 			btrfs_mark_buffer_dirty(path->nodes[0]);
 			btrfs_release_path(path);
+			/*
+			 * If this is the first nlink of the inode, meaning the
+			 * inode is newly created under the parent inode, this
+			 * new child inode should inherit the inode flags from
+			 * the parent.
+			 */
+			if (nlink == 1) {
+				ret = inherit_inode_flags(trans, root, ino, parent_ino);
+				if (ret < 0)
+					goto out;
+			}
 		}
 	}
 
-- 
2.49.0


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

* [PATCH 2/4] btrfs-progs: do not generate checksum nor compress if the inode has NODATACOW or NODATASUM
  2025-05-21  9:51 [PATCH 0/4] btrfs-progs: new --inode-flags option Qu Wenruo
  2025-05-21  9:51 ` [PATCH 1/4] btrfs-progs: allow new inodes to inherit flags from their parents Qu Wenruo
@ 2025-05-21  9:51 ` Qu Wenruo
  2025-05-21  9:51 ` [PATCH 3/4] btrfs-progs: mkfs: add --inode-flags option Qu Wenruo
  2025-05-21  9:51 ` [PATCH 4/4] btrfs-progs: mkfs-tests: a new test case for --inode-flags Qu Wenruo
  3 siblings, 0 replies; 5+ messages in thread
From: Qu Wenruo @ 2025-05-21  9:51 UTC (permalink / raw)
  To: linux-btrfs

Currently mkfs.btrfs --rootdir is implying data checksum, but soon we
will support per-inode NODATACOW|NODATASUM flags.

To support per-inode NODATACOW|NODATASUM flags:

- Avoid compression if the inode has either NODATACOW|NODATASUM flag

- Do not generate data checksum if the inode has either
  NODATACOW|NODATASUM flag.

Both behaviors are the following the kernel ones.

Signed-off-by: Qu Wenruo <wqu@suse.com>
---
 mkfs/rootdir.c | 17 +++++++++++++----
 1 file changed, 13 insertions(+), 4 deletions(-)

diff --git a/mkfs/rootdir.c b/mkfs/rootdir.c
index 5f4cfb93c7c4..eafad426295c 100644
--- a/mkfs/rootdir.c
+++ b/mkfs/rootdir.c
@@ -716,12 +716,19 @@ static int add_file_item_extent(struct btrfs_trans_handle *trans,
 	u64 buf_size;
 	char *write_buf;
 	bool do_comp = g_compression != BTRFS_COMPRESS_NONE;
+	bool datasum = true;
 	ssize_t comp_ret;
 	u64 flags = btrfs_stack_inode_flags(btrfs_inode);
 
 	if (flags & BTRFS_INODE_NOCOMPRESS)
 		do_comp = false;
 
+	if (flags & BTRFS_INODE_NODATACOW ||
+	    flags & BTRFS_INODE_NODATASUM) {
+		datasum = false;
+		do_comp = false;
+	}
+
 	buf_size = do_comp ? BTRFS_MAX_COMPRESSED : MAX_EXTENT_SIZE;
 	to_read = min(file_pos + buf_size, source->size) - file_pos;
 
@@ -852,13 +859,15 @@ static int add_file_item_extent(struct btrfs_trans_handle *trans,
 		return ret;
 	}
 
-	for (unsigned int i = 0; i < to_write / sectorsize; i++) {
-		ret = btrfs_csum_file_block(trans, first_block + (i * sectorsize),
+	if (datasum) {
+		for (unsigned int i = 0; i < to_write / sectorsize; i++) {
+			ret = btrfs_csum_file_block(trans, first_block + (i * sectorsize),
 					BTRFS_EXTENT_CSUM_OBJECTID,
 					root->fs_info->csum_type,
 					write_buf + (i * sectorsize));
-		if (ret)
-			return ret;
+			if (ret)
+				return ret;
+		}
 	}
 
 	btrfs_set_stack_file_extent_type(&stack_fi, BTRFS_FILE_EXTENT_REG);
-- 
2.49.0


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

* [PATCH 3/4] btrfs-progs: mkfs: add --inode-flags option
  2025-05-21  9:51 [PATCH 0/4] btrfs-progs: new --inode-flags option Qu Wenruo
  2025-05-21  9:51 ` [PATCH 1/4] btrfs-progs: allow new inodes to inherit flags from their parents Qu Wenruo
  2025-05-21  9:51 ` [PATCH 2/4] btrfs-progs: do not generate checksum nor compress if the inode has NODATACOW or NODATASUM Qu Wenruo
@ 2025-05-21  9:51 ` Qu Wenruo
  2025-05-21  9:51 ` [PATCH 4/4] btrfs-progs: mkfs-tests: a new test case for --inode-flags Qu Wenruo
  3 siblings, 0 replies; 5+ messages in thread
From: Qu Wenruo @ 2025-05-21  9:51 UTC (permalink / raw)
  To: linux-btrfs

This new option allows end users to specify certain per-inode flags for
specified file/directory inside rootdir.

And mkfs will follow the kernel behavior by inheriting the inode flag
from the parent.

For example:

 rootdir
 |- file1
 |- file2
 |- dir1/
 |  |- file3
 |- subv/     << will be created as a subvolume using --subvol option
    |- dir2/
    |  |- file4
    |- file5

When `mkfs.btrfs --rootdir rootdir --subvol subv --inode-flags
nodatacow:dir1 --inode-flags nodatacow:subv", then the following files
and directory will have *nodatacow* flag set:

- dir1
- file3
- subv
- dir2
- file4
- file5

For now only two flags are supported:

- nodatacow
  Disable data COW, implies *nodatasum* for regular files

- nodatasum
  Disable data checksum only.

This also works with --compress option, and files with nodatasum or
nodatacow flag will skip compression.

Signed-off-by: Qu Wenruo <wqu@suse.com>
---
 Documentation/mkfs.btrfs.rst |  35 ++++++++++
 mkfs/main.c                  | 120 ++++++++++++++++++++++++++++++++++-
 mkfs/rootdir.c               |  78 +++++++++++++++++++++--
 mkfs/rootdir.h               |  15 +++++
 4 files changed, 243 insertions(+), 5 deletions(-)

diff --git a/Documentation/mkfs.btrfs.rst b/Documentation/mkfs.btrfs.rst
index 119e18b47541..e9458f70350e 100644
--- a/Documentation/mkfs.btrfs.rst
+++ b/Documentation/mkfs.btrfs.rst
@@ -213,6 +213,41 @@ OPTIONS
         :file:`hardlink1` and :file:`hardlink2` because :file:`hardlink3` will
         be inside a new subvolume.
 
+--inode-flags <flags>:<path>
+	Specify that *path* to have inode *flags*, other than the default one (which
+	implies data CoW and data checksum).  The option *--rootdir* must also be
+	specified.  This option can be speicified multiple times.
+
+	The supported flag(s) are:
+
+	* *nodatacow*: disable data CoW, implies *nodatasum* for regular files.
+	* *nodatasum*: disable data checksum only.
+
+	*flags* can be separated by comma (',').
+
+	Children inodes will inherit the flags from their parent inodes, like the
+	following case:
+
+        .. code-block:: none
+
+		rootdir/
+		|- file1
+		|- file2
+		|- dir/
+		   |- file3
+
+	In that case, if *--inode-flags nodatacow:dir* is specified, both
+	:file:`dir` and :file:`file3` will have the *nodatacow* flag.
+
+	And this option also works with *--subvol* option, but the inode flag of
+	each subvolume is independent and will not inherit from the parent directory.
+	(The same as the kernel behavior)
+
+        .. note::
+                Both *--inode-flags* and *--subvol* options are memory hungry,
+		will consume at least 8KiB for each option.
+		Please keep the usage of both options to minimal.
+
 --shrink
         Shrink the filesystem to its minimal size, only works with *--rootdir* option.
 
diff --git a/mkfs/main.c b/mkfs/main.c
index 4c2ce98c784c..872f6872de77 100644
--- a/mkfs/main.c
+++ b/mkfs/main.c
@@ -1164,6 +1164,63 @@ static int parse_subvolume(const char *path, struct list_head *subvols,
 	return 0;
 }
 
+static int parse_inode_flags(const char *option, struct list_head *inode_flags_list)
+{
+	struct rootdir_inode_flags_entry *entry = NULL;
+	char *colon;
+	char *dumpped = NULL;
+	char *token;
+	int ret;
+
+	dumpped = strdup(option);
+	if (!dumpped) {
+		ret = -ENOMEM;
+		error_msg(ERROR_MSG_MEMORY, NULL);
+		goto cleanup;
+	}
+	entry = calloc(1, sizeof(*entry));
+	if (!entry) {
+		ret = -ENOMEM;
+		error_msg(ERROR_MSG_MEMORY, NULL);
+		goto cleanup;
+	}
+	colon = strstr(dumpped, ":");
+	if (!colon) {
+		error("invalid option: %s", option);
+		ret = -EINVAL;
+		goto cleanup;
+	}
+	*colon = '\0';
+
+	token = strtok(dumpped, ",");
+	while (token) {
+		if (token == NULL)
+			break;
+		if (strcmp(token, "nodatacow") == 0) {
+			entry->nodatacow = true;
+		} else if (strcmp(token, "nodatasum") == 0) {
+			entry->nodatasum = true;
+		} else {
+			error("unknown flag: %s", token);
+			ret = -EINVAL;
+			goto cleanup;
+		}
+		token = strtok(NULL, ",");
+	}
+
+	if (arg_copy_path(entry->inode_path, colon + 1, sizeof(entry->inode_path))) {
+		error("--inode-flags path too long");
+		ret = -E2BIG;
+		goto cleanup;
+	}
+	list_add_tail(&entry->list, inode_flags_list);
+	return 0;
+cleanup:
+	free(dumpped);
+	free(entry);
+	return ret;
+}
+
 int BOX_MAIN(mkfs)(int argc, char **argv)
 {
 	char *file;
@@ -1206,10 +1263,12 @@ int BOX_MAIN(mkfs)(int argc, char **argv)
 	int nr_global_roots = sysconf(_SC_NPROCESSORS_ONLN);
 	char *source_dir = NULL;
 	struct rootdir_subvol *rds;
+	struct rootdir_inode_flags_entry *rif;
 	bool has_default_subvol = false;
 	enum btrfs_compression_type compression = BTRFS_COMPRESS_NONE;
 	unsigned int compression_level = 0;
 	LIST_HEAD(subvols);
+	LIST_HEAD(inode_flags_list);
 
 	cpu_detect_flags();
 	hash_init_accel();
@@ -1223,6 +1282,7 @@ int BOX_MAIN(mkfs)(int argc, char **argv)
 			GETOPT_VAL_CHECKSUM,
 			GETOPT_VAL_GLOBAL_ROOTS,
 			GETOPT_VAL_DEVICE_UUID,
+			GETOPT_VAL_INODE_FLAGS,
 			GETOPT_VAL_COMPRESS,
 		};
 		static const struct option long_options[] = {
@@ -1241,6 +1301,7 @@ int BOX_MAIN(mkfs)(int argc, char **argv)
 			{ "version", no_argument, NULL, 'V' },
 			{ "rootdir", required_argument, NULL, 'r' },
 			{ "subvol", required_argument, NULL, 'u' },
+			{ "inode-flags", required_argument, NULL, GETOPT_VAL_INODE_FLAGS },
 			{ "nodiscard", no_argument, NULL, 'K' },
 			{ "features", required_argument, NULL, 'O' },
 			{ "runtime-features", required_argument, NULL, 'R' },
@@ -1374,6 +1435,11 @@ int BOX_MAIN(mkfs)(int argc, char **argv)
 			case 'q':
 				bconf_be_quiet();
 				break;
+			case GETOPT_VAL_INODE_FLAGS:
+				ret = parse_inode_flags(optarg, &inode_flags_list);
+				if (ret)
+					goto error;
+				break;
 			case GETOPT_VAL_COMPRESS:
 				if (parse_compression(optarg, &compression, &compression_level)) {
 					ret = 1;
@@ -1438,6 +1504,11 @@ int BOX_MAIN(mkfs)(int argc, char **argv)
 		ret = 1;
 		goto error;
 	}
+	if (!list_empty(&inode_flags_list) && source_dir == NULL) {
+		error("option --inode-flags must be used with --rootdir");
+		ret = 1;
+		goto error;
+	}
 
 	if (source_dir) {
 		char *canonical = realpath(source_dir, NULL);
@@ -1503,6 +1574,41 @@ int BOX_MAIN(mkfs)(int argc, char **argv)
 		}
 	}
 
+	list_for_each_entry(rif, &inode_flags_list, list) {
+		char path[PATH_MAX];
+		struct rootdir_inode_flags_entry *rif2;
+
+		if (path_cat_out(path, source_dir, rif->inode_path)) {
+			ret = -EINVAL;
+			error("path invalid: %s", path);
+			goto error;
+		}
+		if (!realpath(path, rif->full_path)) {
+			ret = -errno;
+			error("could not get canonical path: %s: %m", path);
+			goto error;
+		}
+		if (!path_exists(rif->full_path)) {
+			ret = -ENOENT;
+			error("inode path does not exist: %s", rif->full_path);
+			goto error;
+		}
+		list_for_each_entry(rif2, &inode_flags_list, list) {
+			/*
+			 * Only compare entryies before us. So we won't compare
+			 * the same pair twice.
+			 */
+			if (rif2 == rif)
+				break;
+			if (strcmp(rif2->full_path, rif->full_path) == 0) {
+				error("duplicated inode flag entries for %s",
+					rif->full_path);
+				ret = -EEXIST;
+				goto error;
+			}
+		}
+	}
+
 	if (*fs_uuid) {
 		uuid_t dummy_uuid;
 
@@ -2084,9 +2190,15 @@ raid_groups:
 				   rds->is_default ? "" : " ",
 				   rds->dir);
 		}
+		list_for_each_entry(rif, &inode_flags_list, list) {
+			pr_verbose(LOG_DEFAULT, "  Inode flags (%s):  %s\n",
+				   rif->nodatacow ? "NODATACOW" : "",
+				   rif->inode_path);
+		}
 
 		ret = btrfs_mkfs_fill_dir(trans, source_dir, root,
-					  &subvols, compression,
+					  &subvols, &inode_flags_list,
+					  compression,
 					  compression_level);
 		if (ret) {
 			errno = -ret;
@@ -2229,6 +2341,12 @@ error:
 		list_del(&head->list);
 		free(head);
 	}
+	while (!list_empty(&inode_flags_list)) {
+		rif = list_entry(inode_flags_list.next,
+				 struct rootdir_inode_flags_entry, list);
+		list_del(&rif->list);
+		free(rif);
+	}
 
 	return !!ret;
 
diff --git a/mkfs/rootdir.c b/mkfs/rootdir.c
index eafad426295c..e9af72d3bf23 100644
--- a/mkfs/rootdir.c
+++ b/mkfs/rootdir.c
@@ -153,6 +153,7 @@ static struct rootdir_path current_path = {
 
 static struct btrfs_trans_handle *g_trans = NULL;
 static struct list_head *g_subvols;
+static struct list_head *g_inode_flags_list;
 static u64 next_subvol_id = BTRFS_FIRST_FREE_OBJECTID;
 static u64 default_subvol_id;
 static enum btrfs_compression_type g_compression;
@@ -1296,6 +1297,40 @@ static u8 ftype_to_btrfs_type(mode_t ftype)
 	return BTRFS_FT_UNKNOWN;
 }
 
+static void update_inode_flags(const struct rootdir_inode_flags_entry *rif,
+			       struct btrfs_inode_item *stack_inode)
+{
+	u64 inode_flags;
+
+	inode_flags = btrfs_stack_inode_flags(stack_inode);
+	if (rif->nodatacow) {
+		inode_flags |= BTRFS_INODE_NODATACOW;
+
+		if (S_ISREG(btrfs_stack_inode_mode(stack_inode)))
+			inode_flags |= BTRFS_INODE_NODATASUM;
+	}
+	if (rif->nodatasum)
+		inode_flags |= BTRFS_INODE_NODATASUM;
+
+	btrfs_set_stack_inode_flags(stack_inode, inode_flags);
+}
+
+static void search_and_update_inode_flags(struct btrfs_inode_item *stack_inode,
+					 const char *full_path)
+{
+	struct rootdir_inode_flags_entry *rif;
+
+	list_for_each_entry(rif, g_inode_flags_list, list) {
+		if (strcmp(rif->full_path, full_path) == 0) {
+			update_inode_flags(rif, stack_inode);
+
+			list_del(&rif->list);
+			free(rif);
+			return;
+		}
+	}
+}
+
 static int ftw_add_subvol(const char *full_path, const struct stat *st,
 			  int typeflag, struct FTW *ftwbuf,
 			  struct rootdir_subvol *subvol)
@@ -1354,6 +1389,7 @@ static int ftw_add_subvol(const char *full_path, const struct stat *st,
 	}
 	stat_to_inode_item(&inode_item, st);
 
+	search_and_update_inode_flags(&inode_item, full_path);
 	btrfs_set_stack_inode_nlink(&inode_item, 1);
 	ret = update_inode_item(g_trans, new_root, &inode_item, ino);
 	if (ret < 0) {
@@ -1373,6 +1409,31 @@ static int ftw_add_subvol(const char *full_path, const struct stat *st,
 	return 0;
 }
 
+static int read_inode_item(struct btrfs_root *root, struct btrfs_inode_item *inode_item,
+			   u64 ino)
+{
+	struct btrfs_path path = { 0 };
+	struct btrfs_key key;
+	int ret;
+
+	key.objectid = ino;
+	key.type = BTRFS_INODE_ITEM_KEY;
+	key.offset = 0;
+
+	ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0);
+	if (ret > 0)
+		ret = -ENOENT;
+	if (ret < 0)
+		goto out;
+
+	read_extent_buffer(path.nodes[0], inode_item,
+			   btrfs_item_ptr_offset(path.nodes[0], path.slots[0]),
+			   sizeof(*inode_item));
+out:
+	btrfs_release_path(&path);
+	return ret;
+}
+
 static int ftw_add_inode(const char *full_path, const struct stat *st,
 			 int typeflag, struct FTW *ftwbuf)
 {
@@ -1520,6 +1581,7 @@ static int ftw_add_inode(const char *full_path, const struct stat *st,
 		return ret;
 	}
 	stat_to_inode_item(&inode_item, st);
+	search_and_update_inode_flags(&inode_item, full_path);
 
 	ret = btrfs_insert_inode(g_trans, root, ino, &inode_item);
 	if (ret < 0) {
@@ -1552,11 +1614,17 @@ static int ftw_add_inode(const char *full_path, const struct stat *st,
 	}
 
 	/*
-	 * btrfs_add_link() has increased the nlink to 1 in the metadata.
-	 * Also update the value in case we need to update the inode item
-	 * later.
+	 * btrfs_add_link() has increased the nlink, and may even updated the
+	 * inode flags (inheritted from the parent).
+	 * Read out the latest version of inode item.
 	 */
-	btrfs_set_stack_inode_nlink(&inode_item, 1);
+	ret = read_inode_item(root, &inode_item, ino);
+	if (ret < 0) {
+		errno = -ret;
+		error("failed to read inode item for subvol %llu inode %llu ('%s'): %m",
+			btrfs_root_id(root), ino, full_path);
+		return ret;
+	}
 
 	ret = add_xattr_item(g_trans, root, ino, full_path);
 	if (ret < 0) {
@@ -1649,6 +1717,7 @@ static int set_default_subvolume(struct btrfs_trans_handle *trans)
 
 int btrfs_mkfs_fill_dir(struct btrfs_trans_handle *trans, const char *source_dir,
 			struct btrfs_root *root, struct list_head *subvols,
+			struct list_head *inode_flags_list,
 			enum btrfs_compression_type compression,
 			unsigned int compression_level)
 {
@@ -1695,6 +1764,7 @@ int btrfs_mkfs_fill_dir(struct btrfs_trans_handle *trans, const char *source_dir
 
 	g_trans = trans;
 	g_subvols = subvols;
+	g_inode_flags_list = inode_flags_list;
 	g_compression = compression;
 	g_compression_level = compression_level;
 	INIT_LIST_HEAD(&current_path.inode_list);
diff --git a/mkfs/rootdir.h b/mkfs/rootdir.h
index b32fda5bfda8..f8b959f7a7c8 100644
--- a/mkfs/rootdir.h
+++ b/mkfs/rootdir.h
@@ -45,8 +45,23 @@ struct rootdir_subvol {
 	bool readonly;
 };
 
+/*
+ * Represent a flag for specified inode at @full_path.
+ */
+struct rootdir_inode_flags_entry {
+	struct list_head list;
+	/* Fully canonicalized path to the source file. */
+	char full_path[PATH_MAX];
+	/* Path inside the source directory. */
+	char inode_path[PATH_MAX];
+
+	bool nodatacow;
+	bool nodatasum;
+};
+
 int btrfs_mkfs_fill_dir(struct btrfs_trans_handle *trans, const char *source_dir,
 			struct btrfs_root *root, struct list_head *subvols,
+			struct list_head *inode_flags_list,
 			enum btrfs_compression_type compression,
 			unsigned int compression_level);
 u64 btrfs_mkfs_size_dir(const char *dir_name, u32 sectorsize, u64 min_dev_size,
-- 
2.49.0


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

* [PATCH 4/4] btrfs-progs: mkfs-tests: a new test case for --inode-flags
  2025-05-21  9:51 [PATCH 0/4] btrfs-progs: new --inode-flags option Qu Wenruo
                   ` (2 preceding siblings ...)
  2025-05-21  9:51 ` [PATCH 3/4] btrfs-progs: mkfs: add --inode-flags option Qu Wenruo
@ 2025-05-21  9:51 ` Qu Wenruo
  3 siblings, 0 replies; 5+ messages in thread
From: Qu Wenruo @ 2025-05-21  9:51 UTC (permalink / raw)
  To: linux-btrfs

The simple test will create a layout like the following:

rootdir
|- file1
|- file2
|- subv/		<< Regular subvolume
|  |- file3
|- nocow_subv/		<< NODATACOW subvolume
|  |- file4
|- nocow_dir/		<< NODATACOW directory
|  |- dir2
|  |  |- file5
|  |- file6
|- nocow_file1		<< NODATACOW file

Any files under NODATACOW subvolume/directory should also be NODATACOW.
The explicitly specified single file should also be NODATACOW.

Signed-off-by: Qu Wenruo <wqu@suse.com>
---
 tests/mkfs-tests/038-inode-flags/test.sh | 55 ++++++++++++++++++++++++
 1 file changed, 55 insertions(+)
 create mode 100755 tests/mkfs-tests/038-inode-flags/test.sh

diff --git a/tests/mkfs-tests/038-inode-flags/test.sh b/tests/mkfs-tests/038-inode-flags/test.sh
new file mode 100755
index 000000000000..bb2f61c55605
--- /dev/null
+++ b/tests/mkfs-tests/038-inode-flags/test.sh
@@ -0,0 +1,55 @@
+#!/bin/bash
+# Basic test for mkfs.btrfs --inode-flags --rootdir. Create a dataset and use it as
+# rootdir, then various inode-flags and verify the flag is properly set.
+
+source "$TEST_TOP/common" || exit
+
+check_prereq mkfs.btrfs
+check_prereq btrfs
+check_global_prereq lsattr
+
+setup_root_helper
+prepare_test_dev
+
+tmp=$(_mktemp_dir mkfs-rootdir)
+
+write_file()
+{
+	local path="$1"
+	local size="$2"
+
+	run_check dd if=/dev/zero of="$path" bs="$size" count=1 status=noxfer > /dev/null 2>&1
+}
+
+check_nodatacow()
+{
+	local path="$1"
+
+	lsattr "$TEST_MNT"/"$path" | grep -q C || _fail "missing NODATACOW flag for $path"
+}
+
+write_file "$tmp/file1" 64K
+write_file "$tmp/file2" 64K
+run_check mkdir -p "$tmp/subv" "$tmp/nocow_subv" "$tmp/nocow_dir/dir2"
+write_file "$tmp/subv/file3" 64K
+write_file "$tmp/nocow_subv/file4" 64K
+write_file "$tmp/nocow_dir/dir2/file5" 64K
+write_file "$tmp/nocow_dir/file6" 64K
+write_file "$tmp/nocow_file1" 64K
+
+run_check_mkfs_test_dev --rootdir "$tmp" --inode-flags nodatacow:nocow_subv \
+	--subvol nocow_subv --inode-flags nodatacow:nocow_dir \
+	--inode-flags nodatacow:nocow_file1
+
+run_check $SUDO_HELPER "$TOP/btrfs" check "$TEST_DEV"
+
+run_check_mount_test_dev
+check_nodatacow "nocow_subv"
+check_nodatacow "nocow_subv/file4"
+check_nodatacow "nocow_dir"
+check_nodatacow "nocow_dir/file6"
+check_nodatacow "nocow_dir/dir2/file5"
+check_nodatacow "nocow_file1"
+run_check lsattr -R "$TEST_MNT"
+run_check_umount_test_dev
+run_check rm -rf -- "$tmp"
-- 
2.49.0


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

end of thread, other threads:[~2025-05-21  9:51 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-05-21  9:51 [PATCH 0/4] btrfs-progs: new --inode-flags option Qu Wenruo
2025-05-21  9:51 ` [PATCH 1/4] btrfs-progs: allow new inodes to inherit flags from their parents Qu Wenruo
2025-05-21  9:51 ` [PATCH 2/4] btrfs-progs: do not generate checksum nor compress if the inode has NODATACOW or NODATASUM Qu Wenruo
2025-05-21  9:51 ` [PATCH 3/4] btrfs-progs: mkfs: add --inode-flags option Qu Wenruo
2025-05-21  9:51 ` [PATCH 4/4] btrfs-progs: mkfs-tests: a new test case for --inode-flags Qu Wenruo

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox