[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