[PATCH v12 7/8] fs/acl: Add extended attributes test

Sachin Sant <[email protected]>
Newsgroups gmane.linux.ltp
Message-ID <[email protected]>
Add xattr_test01 to validate extended attribute operations on
filesystems.

The test covers:
- Setting and retrieving extended attributes on files and directories
- Removing extended attributes and verifying removal
- Backing up extended attributes to a file
- Restoring extended attributes from backup
- Verifying restored attributes match original values

The test runs on ext2/ext3/ext4 (with user_xattr mount option),
xfs, and btrfs filesystems. It properly handles EOPNOTSUPP for
filesystems without extended attribute support.

Suggested-by: Cyril Hrubis <[email protected]>
Signed-off-by: Sachin Sant <[email protected]>
---
No changes in v10 to v12

V9 changes:
- Use TEST() method as appropriate.
- v8 link https://lore.kernel.org/ltp/20260613090543.78643-1-sachinp-tEXmvtCZX7AybS5Ee8rs3A@public.gmane.org/T/#t

V8 changes:
- Use reset_test_path() instead of chown variant.
- v7 link https://lore.kernel.org/ltp/20260612171930.11964-1-sachinp-tEXmvtCZX7AybS5Ee8rs3A@public.gmane.org/T/#t

V7 changes:
- No change

V6 changes:
- Added HAVE_SYS_XATTR_H guard
- Switched from TST_EXP_PASS_SILENT to TEST() with TTERRNO for better
  diagnostics
- Updated algorithm documentation with correct format
- Removed unnecessary .forks_child flag
- v5 link https://lore.kernel.org/ltp/20260608092200.92827-1-sachinp-tEXmvtCZX7AybS5Ee8rs3A@public.gmane.org/T/#t

V5 changes:
- Switch to kernel only test validation to remove dependency on libacl
  and useradd/del commands.
- v3 link https://lore.kernel.org/ltp/20260604065417.25924-1-sachinp-tEXmvtCZX7AybS5Ee8rs3A@public.gmane.org/T/#t

V4 changes:
- No change

V3 changes:
- Updated copyright header as per LTP format.
- Updated commit message as per review comments.
- v2 link https://lore.kernel.org/ltp/20260603065744.47106-1-sachinp-tEXmvtCZX7AybS5Ee8rs3A@public.gmane.org/T/#t

V2 changes:
- Use reset_test_path_no_chown variant to skip chown step
  and removed needs_cmd tag to avoid useradd/userdel dependency
- v1 link https://lore.kernel.org/ltp/20260602121958.27494-1-sachinp-tEXmvtCZX7AybS5Ee8rs3A@public.gmane.org/T/#t

V1 changes:
- Use HAVE_LIBACL guards in .c code
- Report TCONF when libacl is not available
- rfc link https://lore.kernel.org/ltp/477836fd-80c8-4168-bfe6-00b374bb2534-tEXmvtCZX7AybS5Ee8rs3A@public.gmane.org/T/#t

---
 runtest/fs                             |   1 +
 testcases/kernel/fs/acl/.gitignore     |   1 +
 testcases/kernel/fs/acl/xattr_test01.c | 380 +++++++++++++++++++++++++
 3 files changed, 382 insertions(+)
 create mode 100644 testcases/kernel/fs/acl/xattr_test01.c

diff --git a/runtest/fs b/runtest/fs
index 64deb56e6..f9acac387 100644
--- a/runtest/fs
+++ b/runtest/fs
@@ -95,3 +95,4 @@ acl_other01 acl_other01
 acl_inherit01 acl_inherit01
 acl_file_ops01 acl_file_ops01
 acl_link01 acl_link01
+xattr_test01 xattr_test01
diff --git a/testcases/kernel/fs/acl/.gitignore b/testcases/kernel/fs/acl/.gitignore
index 4a071d516..62ccd0457 100644
--- a/testcases/kernel/fs/acl/.gitignore
+++ b/testcases/kernel/fs/acl/.gitignore
@@ -4,3 +4,4 @@
 /acl_inherit01
 /acl_file_ops01
 /acl_link01
+/xattr_test01
diff --git a/testcases/kernel/fs/acl/xattr_test01.c b/testcases/kernel/fs/acl/xattr_test01.c
new file mode 100644
index 000000000..7b86a43ed
--- /dev/null
+++ b/testcases/kernel/fs/acl/xattr_test01.c
@@ -0,0 +1,380 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 IBM
+ *
+ * Original shell test by Kai Zhao ([email protected])
+ * Converted to C by Sachin Sant <[email protected]>
+ */
+
+/*\
+ * Test Extended Attributes (xattr).
+ *
+ * Some filesystems require explicit user_xattr mount options,
+ * while others (e.g. xfs and btrfs) provide these features without
+ * mount options.
+ *
+ * This test validates:
+ * - Extended attributes set/get/remove operations
+ * - Extended attributes backup and restore operations
+ *
+ * [Algorithm]
+ *
+ * - Set extended attributes on directories and files
+ * - Verify attributes can be retrieved correctly
+ * - Test attribute removal
+ * - Create backup of extended attributes to a file
+ * - Remove original attributes
+ * - Restore attributes from backup file
+ * - Verify restored attributes match original values
+ */
+
+#include "acl_lib.h"
+
+#ifdef HAVE_SYS_XATTR_H
+
+/*
+ * Test extended attributes.
+ * Set, get, and remove extended attributes on files and directories.
+ */
+static void test_xattr(void)
+{
+	char value[256];
+	int fd = -1;
+	int file_created = 0;
+
+	tst_res(TINFO, "Testing extended attributes");
+	reset_test_path();
+
+	TEST(setxattr(TESTDIR, XATTR_TEST_DIR_NAME, XATTR_TEST_DIR_VALUE,
+		      XATTR_TEST_DIR_SIZE, 0));
+	if (TST_RET == -1) {
+		if (TST_ERR == EOPNOTSUPP) {
+			tst_res(TCONF, "Extended attributes not supported");
+			return;
+		}
+		tst_res(TFAIL | TTERRNO, "setxattr on directory failed");
+		return;
+	}
+
+	TEST(getxattr(TESTDIR, XATTR_TEST_DIR_NAME, value, sizeof(value)));
+	if (TST_RET < 0) {
+		tst_res(TFAIL | TTERRNO, "getxattr on directory failed");
+		goto cleanup_dir_xattr;
+	}
+
+	if (TST_RET != XATTR_TEST_DIR_SIZE ||
+	    memcmp(value, XATTR_TEST_DIR_VALUE, XATTR_TEST_DIR_SIZE) != 0) {
+		tst_res(TFAIL, "getxattr returned wrong directory value");
+		goto cleanup_dir_xattr;
+	}
+
+	fd = SAFE_OPEN(TESTFILE, O_CREAT | O_WRONLY, 0644);
+	SAFE_CLOSE(fd);
+	file_created = 1;
+
+	TEST(setxattr(TESTFILE, XATTR_TEST_FILE_NAME, XATTR_TEST_FILE_VALUE,
+		      XATTR_TEST_FILE_SIZE, 0));
+	if (TST_RET == -1) {
+		if (TST_ERR == EOPNOTSUPP) {
+			tst_res(TCONF, "Extended attributes not supported");
+			goto cleanup_file_and_dir;
+		}
+		tst_res(TFAIL | TTERRNO, "setxattr on file failed");
+		goto cleanup_file_and_dir;
+	}
+
+	TEST(getxattr(TESTFILE, XATTR_TEST_FILE_NAME, value, sizeof(value)));
+	if (TST_RET < 0) {
+		tst_res(TFAIL | TTERRNO, "getxattr on file failed");
+		goto cleanup_file_and_dir;
+	}
+
+	if (TST_RET != XATTR_TEST_FILE_SIZE ||
+	    memcmp(value, XATTR_TEST_FILE_VALUE, XATTR_TEST_FILE_SIZE) != 0) {
+		tst_res(TFAIL, "getxattr returned wrong file value");
+		goto cleanup_file_and_dir;
+	}
+
+	TEST(removexattr(TESTFILE, XATTR_TEST_FILE_NAME));
+	if (TST_RET == -1) {
+		tst_res(TFAIL | TTERRNO, "removexattr failed");
+		goto cleanup_file_and_dir;
+	}
+
+	TEST(getxattr(TESTFILE, XATTR_TEST_FILE_NAME, value, sizeof(value)));
+	if (TST_RET >= 0 || TST_ERR != ENODATA) {
+		tst_res(TFAIL, "getxattr after removal should fail with ENODATA");
+		goto cleanup_file_and_dir;
+	}
+
+	tst_res(TPASS, "Extended attributes work correctly");
+
+cleanup_file_and_dir:
+	if (file_created)
+		cleanup_testfile();
+cleanup_dir_xattr:
+	if (removexattr(TESTDIR, XATTR_TEST_DIR_NAME) == -1 &&
+	    errno != ENODATA)
+		tst_res(TWARN | TERRNO, "removexattr failed");
+}
+
+#define XATTR_BACKUP_TEST_COUNT 2
+
+/*
+ * Helper function to cleanup test xattrs.
+ */
+static inline void cleanup_test_xattrs(void)
+{
+	if (removexattr(TESTFILE, XATTR_TEST1_NAME) == -1 &&
+	    errno != ENODATA)
+		tst_res(TWARN | TERRNO, "removexattr XATTR_TEST1_NAME failed");
+	if (removexattr(TESTFILE, XATTR_TEST2_NAME) == -1 &&
+	    errno != ENODATA)
+		tst_res(TWARN | TERRNO, "removexattr XATTR_TEST2_NAME failed");
+}
+
+/*
+ * Test extended attributes backup and restore.
+ * Extended attributes should be preserved through backup/restore.
+ */
+static void test_xattr_backup_restore(void)
+{
+	char value[256];
+	char line[512];
+	char attr_name[256];
+	char attr_value[256];
+	char list[512];
+	size_t name_len, value_len;
+	ssize_t list_size;
+	int fd = -1;
+	FILE *fp = NULL;
+	int restored_count = 0;
+	int backup_created = 0;
+	char *attr_ptr;
+
+	tst_res(TINFO, "Testing extended attributes backup and restore");
+	reset_test_path();
+
+	fd = SAFE_OPEN(TESTFILE, O_CREAT | O_WRONLY, 0644);
+	SAFE_CLOSE(fd);
+
+	TEST(setxattr(TESTFILE, XATTR_TEST1_NAME, XATTR_TEST1_VALUE,
+		      XATTR_TEST1_SIZE, 0));
+	if (TST_RET == -1) {
+		cleanup_testfile();
+		if (TST_ERR == EOPNOTSUPP) {
+			tst_res(TCONF, "Extended attributes not supported");
+			return;
+		}
+		tst_res(TFAIL | TTERRNO, "setxattr failed");
+		return;
+	}
+
+	TEST(setxattr(TESTFILE, XATTR_TEST2_NAME, XATTR_TEST2_VALUE,
+		      XATTR_TEST2_SIZE, 0));
+	if (TST_RET == -1) {
+		tst_res(TFAIL | TTERRNO, "setxattr failed");
+		goto cleanup_xattrs;
+	}
+
+	/* Create genuine backup by reading xattrs from filesystem */
+	fp = SAFE_FOPEN(XATTR_BACKUP_FILE, "w");
+	backup_created = 1;
+
+	if (fprintf(fp, "# file: %s\n", TESTFILE) < 0) {
+		tst_res(TFAIL | TERRNO, "fprintf failed");
+		goto cleanup_backup;
+	}
+
+	TEST(listxattr(TESTFILE, list, sizeof(list)));
+	if (TST_RET < 0) {
+		tst_res(TFAIL | TTERRNO, "listxattr failed");
+		goto cleanup_backup;
+	}
+
+	/* Save list size before it gets overwritten by subsequent TEST() calls */
+	list_size = TST_RET;
+
+	attr_ptr = list;
+	while (attr_ptr < list + list_size) {
+		/* Only backup user.* attributes for this test */
+		if (strncmp(attr_ptr, "user.", 5) != 0) {
+			attr_ptr += strlen(attr_ptr) + 1;
+			continue;
+		}
+
+		TEST(getxattr(TESTFILE, attr_ptr, value, sizeof(value)));
+		if (TST_RET < 0) {
+			tst_res(TFAIL | TTERRNO, "getxattr failed for %s",
+				attr_ptr);
+			goto cleanup_backup;
+		}
+
+		/* Write to backup file with null terminator for string values */
+		if (fprintf(fp, "%s=\"%.*s\"\n", attr_ptr, (int)TST_RET,
+			    value) < 0) {
+			tst_res(TFAIL | TERRNO, "fprintf failed");
+			goto cleanup_backup;
+		}
+
+		attr_ptr += strlen(attr_ptr) + 1;
+	}
+
+	SAFE_FCLOSE(fp);
+	fp = NULL;
+
+	/* Remove xattrs to simulate loss */
+	cleanup_test_xattrs();
+
+	/* Restore from backup file */
+	fp = SAFE_FOPEN(XATTR_BACKUP_FILE, "r");
+	while (fgets(line, sizeof(line), fp)) {
+		char *p, *q;
+
+		if (line[0] == '#' || line[0] == '\n')
+			continue;
+
+		/* Parse: attr_name="attr_value" */
+		p = strchr(line, '=');
+		if (!p)
+			continue;
+
+		/* Safe copy of attribute name with length check */
+		name_len = p - line;
+
+		if (name_len >= sizeof(attr_name)) {
+			tst_res(TWARN, "Attribute name too long, skipping");
+			continue;
+		}
+		memcpy(attr_name, line, name_len);
+		attr_name[name_len] = '\0';
+
+		/* Only restore user.* attributes for this test */
+		if (strncmp(attr_name, "user.", 5) != 0)
+			continue;
+
+		p++;
+		if (*p != '"')
+			continue;
+		p++;
+
+		q = strchr(p, '"');
+		if (!q)
+			continue;
+
+		/* Safe copy of attribute value with length check */
+		value_len = q - p;
+
+		if (value_len >= sizeof(attr_value)) {
+			tst_res(TWARN, "Attribute value too long, skipping");
+			continue;
+		}
+		memcpy(attr_value, p, value_len);
+		attr_value[value_len] = '\0';
+
+		/* Restore the xattr */
+		if (setxattr(TESTFILE, attr_name, attr_value, value_len,
+			     0) == -1) {
+			tst_res(TFAIL | TERRNO,
+				"setxattr restore failed for %s", attr_name);
+			goto cleanup_backup;
+		}
+		restored_count++;
+	}
+	SAFE_FCLOSE(fp);
+	fp = NULL;
+
+	if (restored_count != XATTR_BACKUP_TEST_COUNT) {
+		tst_res(TFAIL, "Expected %d xattrs restored, got %d",
+			XATTR_BACKUP_TEST_COUNT, restored_count);
+		goto cleanup_backup;
+	}
+
+	/* Verify restored xattrs */
+	TEST(getxattr(TESTFILE, XATTR_TEST1_NAME, value, sizeof(value)));
+	if (TST_RET < 0) {
+		tst_res(TFAIL | TTERRNO, "getxattr failed for %s",
+			XATTR_TEST1_NAME);
+		goto cleanup_backup;
+	}
+
+	if (TST_RET != XATTR_TEST1_SIZE ||
+	    memcmp(value, XATTR_TEST1_VALUE, XATTR_TEST1_SIZE) != 0) {
+		tst_res(TFAIL,
+			"Extended attribute %s restore verification failed",
+			XATTR_TEST1_NAME);
+		goto cleanup_backup;
+	}
+
+	TEST(getxattr(TESTFILE, XATTR_TEST2_NAME, value, sizeof(value)));
+	if (TST_RET < 0) {
+		tst_res(TFAIL | TTERRNO, "getxattr failed for %s",
+			XATTR_TEST2_NAME);
+		goto cleanup_backup;
+	}
+
+	if (TST_RET != XATTR_TEST2_SIZE ||
+	    memcmp(value, XATTR_TEST2_VALUE, XATTR_TEST2_SIZE) != 0) {
+		tst_res(TFAIL,
+			"Extended attribute %s restore verification failed",
+			XATTR_TEST2_NAME);
+		goto cleanup_backup;
+	}
+
+	tst_res(TPASS, "Extended attributes backup/restore work correctly");
+
+cleanup_backup:
+	if (fp)
+		SAFE_FCLOSE(fp);
+	if (backup_created) {
+		if (unlink(XATTR_BACKUP_FILE) == -1)
+			tst_res(TWARN | TERRNO, "unlink backup file failed");
+	}
+cleanup_xattrs:
+	cleanup_test_xattrs();
+	cleanup_testfile();
+}
+
+static void setup(void)
+{
+	reset_test_path();
+}
+
+static void cleanup(void)
+{
+	cleanup_test_paths();
+}
+
+static void run(unsigned int n)
+{
+	switch (n) {
+	case 0:
+		test_xattr();
+		break;
+	case 1:
+		test_xattr_backup_restore();
+		break;
+	}
+}
+
+static struct tst_test test = {
+	.test = run,
+	.tcnt = 2,
+	.setup = setup,
+	.cleanup = cleanup,
+	.needs_root = 1,
+	.mount_device = 1,
+	.mntpoint = MNTPOINT,
+	.filesystems = (struct tst_fs[]) {
+		{.type = "ext2", .mnt_data = "user_xattr"},
+		{.type = "ext3", .mnt_data = "user_xattr"},
+		{.type = "ext4", .mnt_data = "user_xattr"},
+		{.type = "xfs"},
+		{.type = "btrfs"},
+		{}
+	}
+};
+
+#else
+	TST_TEST_TCONF("sys/xattr.h is not available");
+#endif
-- 
2.39.1


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.