[LTP] [PATCH v4 7/8] fs/acl: Add extended attributes test
Sachin Sant
sachinp@linux.ibm.com
Mon Jun 8 11:21:59 CEST 2026
Add xattr_test01 implementing extended attributes testing
- Test xattr set/get/remove operations on files and directories
- Test xattr backup and restore functionality
- Support multiple filesystems (ext2/3/4, xfs, btrfs)
Suggested-by: Cyril Hrubis <chrubis@suse.cz>
Signed-off-by: Sachin Sant <sachinp@linux.ibm.com>
---
V4 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@linux.ibm.com/T/#t
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@linux.ibm.com/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@linux.ibm.com/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@linux.ibm.com/T/#t
---
runtest/fs | 1 +
testcases/kernel/fs/acl/.gitignore | 1 +
testcases/kernel/fs/acl/xattr_test01.c | 381 +++++++++++++++++++++++++
3 files changed, 383 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..99ef0f12b
--- /dev/null
+++ b/testcases/kernel/fs/acl/xattr_test01.c
@@ -0,0 +1,381 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 IBM
+ *
+ * Original shell test by Kai Zhao (ltcd3@cn.ibm.com)
+ * Converted to C by Sachin Sant <sachinp@linux.ibm.com>
+ */
+
+/*\
+ * 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]
+ *
+ * 1. Set extended attributes on directories and files
+ * 2. Verify attributes can be retrieved correctly
+ * 3. Test attribute removal
+ * 4. Create backup of extended attributes to a file
+ * 5. Remove original attributes
+ * 6. Restore attributes from backup file
+ * 7. Verify restored attributes match original values
+ */
+
+#include "acl_lib.h"
+
+/*
+ * Test extended attributes.
+ * Set, get, and remove extended attributes on files and directories.
+ */
+static void test_xattr(void)
+{
+ char value[256];
+ ssize_t size;
+ int fd = -1;
+ int file_created = 0;
+
+ tst_res(TINFO, "Testing extended attributes");
+ reset_test_path_no_chown();
+
+ TST_EXP_PASS_SILENT(setxattr(TESTDIR, XATTR_TEST_DIR_NAME,
+ XATTR_TEST_DIR_VALUE,
+ XATTR_TEST_DIR_SIZE, 0));
+ if (!TST_PASS) {
+ if (TST_ERR == EOPNOTSUPP) {
+ tst_res(TCONF, "Extended attributes not supported");
+ return;
+ }
+ tst_res(TFAIL, "setxattr on directory failed");
+ return;
+ }
+
+ size = getxattr(TESTDIR, XATTR_TEST_DIR_NAME, value, sizeof(value));
+ if (size < 0) {
+ tst_res(TFAIL | TERRNO, "getxattr on directory failed");
+ goto cleanup_dir_xattr;
+ }
+
+ if (size != 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;
+
+ TST_EXP_PASS_SILENT(setxattr(TESTFILE, XATTR_TEST_FILE_NAME,
+ XATTR_TEST_FILE_VALUE,
+ XATTR_TEST_FILE_SIZE, 0));
+ if (!TST_PASS) {
+ if (TST_ERR == EOPNOTSUPP) {
+ tst_res(TCONF, "Extended attributes not supported");
+ goto cleanup_file_and_dir;
+ }
+ tst_res(TFAIL, "setxattr on file failed");
+ goto cleanup_file_and_dir;
+ }
+
+ size = getxattr(TESTFILE, XATTR_TEST_FILE_NAME, value,
+ sizeof(value));
+ if (size < 0) {
+ tst_res(TFAIL | TERRNO, "getxattr on file failed");
+ goto cleanup_file_and_dir;
+ }
+
+ if (size != 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;
+ }
+
+ if (removexattr(TESTFILE, XATTR_TEST_FILE_NAME) == -1) {
+ tst_res(TFAIL | TERRNO, "removexattr failed");
+ goto cleanup_file_and_dir;
+ }
+
+ size = getxattr(TESTFILE, XATTR_TEST_FILE_NAME, value,
+ sizeof(value));
+ if (size >= 0 || errno != 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];
+ ssize_t size, list_size;
+ size_t name_len, value_len;
+ 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_no_chown();
+
+ fd = SAFE_OPEN(TESTFILE, O_CREAT | O_WRONLY, 0644);
+ SAFE_CLOSE(fd);
+
+ TST_EXP_PASS_SILENT(setxattr(TESTFILE, XATTR_TEST1_NAME,
+ XATTR_TEST1_VALUE,
+ XATTR_TEST1_SIZE, 0));
+ if (!TST_PASS) {
+ cleanup_testfile();
+ if (TST_ERR == EOPNOTSUPP) {
+ tst_res(TCONF, "Extended attributes not supported");
+ return;
+ }
+ tst_res(TFAIL, "setxattr failed");
+ return;
+ }
+
+ TST_EXP_PASS_SILENT(setxattr(TESTFILE, XATTR_TEST2_NAME,
+ XATTR_TEST2_VALUE,
+ XATTR_TEST2_SIZE, 0));
+ if (!TST_PASS)
+ 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;
+ }
+
+ /* List all extended attributes */
+ list_size = listxattr(TESTFILE, list, sizeof(list));
+ if (list_size < 0) {
+ tst_res(TFAIL | TERRNO, "listxattr failed");
+ goto cleanup_backup;
+ }
+
+ /* Iterate through attribute list and backup each one */
+ 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;
+ }
+
+ /* Get attribute value from filesystem */
+ size = getxattr(TESTFILE, attr_ptr, value, sizeof(value));
+ if (size < 0) {
+ tst_res(TFAIL | TERRNO, "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)size,
+ value) < 0) {
+ tst_res(TFAIL | TERRNO, "fprintf failed");
+ goto cleanup_backup;
+ }
+
+ /* Move to next attribute name in list */
+ 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 */
+ size = getxattr(TESTFILE, XATTR_TEST1_NAME, value, sizeof(value));
+ if (size < 0) {
+ tst_res(TFAIL | TERRNO, "getxattr failed for %s",
+ XATTR_TEST1_NAME);
+ goto cleanup_backup;
+ }
+
+ if (size != 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;
+ }
+
+ size = getxattr(TESTFILE, XATTR_TEST2_NAME, value, sizeof(value));
+ if (size < 0) {
+ tst_res(TFAIL | TERRNO, "getxattr failed for %s",
+ XATTR_TEST2_NAME);
+ goto cleanup_backup;
+ }
+
+ if (size != 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_no_chown();
+}
+
+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,
+ .forks_child = 1,
+ .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"},
+ {}
+ }
+};
--
2.39.1
More information about the ltp
mailing list