[PATCH v5 6/8] fs/acl: Add symlink ACL operations test
Sachin Sant <[email protected]>
| Newsgroups | gmane.linux.ltp |
|---|---|
| Message-ID | <[email protected]> |
Add acl_link01 test to verify that ACL operations on symlinks follow the symlink to the target file. The test validates that setting and getting ACLs through a symlink path affects the target file, not the symlink itself. Test coverage: - Create regular file with mode 0600 (rw-------) - Set distinct ACL (rwxrw----) through symlink path - Verify ACL was set on target file by reading directly - Get ACL through symlink path - Verify both ACLs match and differ from initial 0600 mode The test uses distinct permissions to ensure it can detect if symlink-following fails. Test returns TCONF if ACL is not supported by the filesystem. Suggested-by: Cyril Hrubis <[email protected]> Signed-off-by: Sachin Sant <[email protected]> --- V5 changes: - Switch to kernel only test validation to remove dependency on libacl and useradd/del commands. - v4 link https://lore.kernel.org/ltp/20260604065417.25924-1-sachinp-tEXmvtCZX7AybS5Ee8rs3A@public.gmane.org/T/#t V4 changes: - Rewrite test logic to use distinct ACL (rwxrw----) that differs from initial mode. - Update commit message to reflect the implementation. - v3 link https://lore.kernel.org/ltp/20260603140147.50738-1-sachinp-tEXmvtCZX7AybS5Ee8rs3A@public.gmane.org/T/#t V3 changes: - Updated the test to read the ACL from both TESTSYMLINK and TESTFILE, and verify the expected entries/permissions match. - Updated commit message to reflect this change. - Updated copyright header as per LTP format. - v2 link https://lore.kernel.org/ltp/20260603065744.47106-1-sachinp-tEXmvtCZX7AybS5Ee8rs3A@public.gmane.org/T/#t V2 changes: - Updated incorrect TCONF message and description text - Updated commit message to remove incorrect symlinks wording - 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/acl_link01.c | 217 +++++++++++++++++++++++++++ 3 files changed, 219 insertions(+) create mode 100644 testcases/kernel/fs/acl/acl_link01.c diff --git a/runtest/fs b/runtest/fs index f1eea055b..64deb56e6 100644 --- a/runtest/fs +++ b/runtest/fs @@ -94,3 +94,4 @@ acl_mask01 acl_mask01 acl_other01 acl_other01 acl_inherit01 acl_inherit01 acl_file_ops01 acl_file_ops01 +acl_link01 acl_link01 diff --git a/testcases/kernel/fs/acl/.gitignore b/testcases/kernel/fs/acl/.gitignore index eb4b4a227..4a071d516 100644 --- a/testcases/kernel/fs/acl/.gitignore +++ b/testcases/kernel/fs/acl/.gitignore @@ -3,3 +3,4 @@ /acl_other01 /acl_inherit01 /acl_file_ops01 +/acl_link01 diff --git a/testcases/kernel/fs/acl/acl_link01.c b/testcases/kernel/fs/acl/acl_link01.c new file mode 100644 index 000000000..b55ef6c96 --- /dev/null +++ b/testcases/kernel/fs/acl/acl_link01.c @@ -0,0 +1,217 @@ +// 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 ACL operations on symlinks using direct xattr manipulation. + * + * Verify that ACL operations on symlinks follow the symlink to the target + * file. When setting or getting ACLs through a symlink path, the operation + * should affect the target file, not the symlink itself. + * + * Note: Some filesystems may not support ACLs on the target file and will + * return EOPNOTSUPP, which is treated as TCONF (test not applicable). + * + * This test uses direct xattr manipulation without creating actual users, + * testing only the kernel ACL implementation. + * + * [Algorithm] + * + * 1. Create a regular file with mode 0600 (rw-------) + * 2. Create a symlink pointing to the file + * 3. Set a distinct ACL through the symlink path (rwxrw----) + * 4. Verify the ACL was set on the target file by reading it directly + * 5. Get ACL through the symlink path + * 6. Verify both ACLs match and differ from the initial 0600 mode + */ + +#include "acl_lib.h" + +static void run(void) +{ + struct acl *acl = NULL, *target_acl = NULL, *symlink_acl = NULL; + int fd = -1; + int match; + + tst_res(TINFO, "Testing ACL operations on symlinks"); + reset_test_path_no_chown(); + + fd = SAFE_OPEN(TESTFILE, O_CREAT | O_WRONLY, 0600); + SAFE_CLOSE(fd); + + SAFE_SYMLINK("testfile", TESTSYMLINK); + + acl = acl_init(); + if (!acl) + goto cleanup; + + if (acl_add_entry(acl, ACL_USER_OBJ, + ACL_READ | ACL_WRITE | ACL_EXECUTE, 0) < 0) + goto cleanup_acl; + + if (acl_add_entry(acl, ACL_GROUP_OBJ, ACL_READ | ACL_WRITE, 0) < 0) + goto cleanup_acl; + + if (acl_add_entry(acl, ACL_OTHER, 0, 0) < 0) + goto cleanup_acl; + + if (acl_set_file(TESTSYMLINK, ACL_TYPE_ACCESS, acl) < 0) { + if (errno == EOPNOTSUPP) { + acl_free(acl); + if (unlink(TESTSYMLINK) == -1) + tst_res(TWARN | TERRNO, "unlink symlink failed"); + cleanup_testfile(); + tst_res(TCONF, + "ACL not supported by this filesystem"); + return; + } + goto cleanup_acl; + } + + acl_free(acl); + acl = NULL; + + /* Verify ACL was actually set on target file with expected values */ + target_acl = acl_get_file(TESTFILE, ACL_TYPE_ACCESS); + if (!target_acl) + goto cleanup; + + /* Verify expected ACL entries: USER_OBJ=rwx, GROUP_OBJ=rw, OTHER=--- */ + if (target_acl->count != 3) { + int count = target_acl->count; + + acl_free(target_acl); + if (unlink(TESTSYMLINK) == -1) + tst_res(TWARN | TERRNO, "unlink symlink failed"); + cleanup_testfile(); + tst_res(TFAIL, "Expected 3 ACL entries, got %d", count); + return; + } + + struct acl_entry *user_obj = acl_find_entry(target_acl, ACL_USER_OBJ, 0); + struct acl_entry *group_obj = acl_find_entry(target_acl, ACL_GROUP_OBJ, 0); + struct acl_entry *other = acl_find_entry(target_acl, ACL_OTHER, 0); + + if (!user_obj || !group_obj || !other) { + acl_free(target_acl); + if (unlink(TESTSYMLINK) == -1) + tst_res(TWARN | TERRNO, "unlink symlink failed"); + cleanup_testfile(); + tst_res(TFAIL, "Missing required ACL entries"); + return; + } + + if (user_obj->perm != (ACL_READ | ACL_WRITE | ACL_EXECUTE)) { + acl_free(target_acl); + if (unlink(TESTSYMLINK) == -1) + tst_res(TWARN | TERRNO, "unlink symlink failed"); + cleanup_testfile(); + tst_res(TFAIL, "USER_OBJ has wrong permissions: %o (expected rwx)", + user_obj->perm); + return; + } + + if (group_obj->perm != (ACL_READ | ACL_WRITE)) { + acl_free(target_acl); + if (unlink(TESTSYMLINK) == -1) + tst_res(TWARN | TERRNO, "unlink symlink failed"); + cleanup_testfile(); + tst_res(TFAIL, "GROUP_OBJ has wrong permissions: %o (expected rw-)", + group_obj->perm); + return; + } + + if (other->perm != 0) { + acl_free(target_acl); + if (unlink(TESTSYMLINK) == -1) + tst_res(TWARN | TERRNO, "unlink symlink failed"); + cleanup_testfile(); + tst_res(TFAIL, "OTHER has wrong permissions: %o (expected ---)", + other->perm); + return; + } + + /* Now verify that reading via symlink gives the same result */ + symlink_acl = acl_get_file(TESTSYMLINK, ACL_TYPE_ACCESS); + if (!symlink_acl) { + acl_free(target_acl); + if (unlink(TESTSYMLINK) == -1) + tst_res(TWARN | TERRNO, "unlink symlink failed"); + cleanup_testfile(); + tst_brk(TBROK | TERRNO, "acl_get_file on symlink failed"); + } + + /* Compare ACLs */ + match = 1; + if (target_acl->count != symlink_acl->count) { + match = 0; + } else { + for (int i = 0; i < target_acl->count; i++) { + struct acl_entry *t = &target_acl->entries[i]; + struct acl_entry *s = &symlink_acl->entries[i]; + + if (t->tag != s->tag || t->perm != s->perm || + t->id != s->id) { + match = 0; + break; + } + } + } + + acl_free(symlink_acl); + acl_free(target_acl); + + if (unlink(TESTSYMLINK) == -1) + tst_res(TWARN | TERRNO, "unlink symlink failed"); + cleanup_testfile(); + + if (!match) { + tst_res(TFAIL, + "ACL via symlink differs from ACL on target file"); + return; + } + + tst_res(TPASS, + "ACL set via symlink was applied to target file (rwxrw----)"); + return; + +cleanup_acl: + acl_free(acl); +cleanup: + if (unlink(TESTSYMLINK) == -1) + tst_res(TWARN | TERRNO, "unlink symlink failed"); + cleanup_testfile(); + tst_brk(TBROK | TERRNO, "ACL setup failed"); +} + +static void setup(void) +{ + reset_test_path_no_chown(); +} + +static void cleanup(void) +{ + cleanup_test_paths(); +} + +static struct tst_test test = { + .test_all = run, + .setup = setup, + .cleanup = cleanup, + .needs_root = 1, + .mount_device = 1, + .mntpoint = MNTPOINT, + .forks_child = 1, + .filesystems = (struct tst_fs[]) { + {.type = "ext2", .mnt_data = "acl"}, + {.type = "ext3", .mnt_data = "acl"}, + {.type = "ext4", .mnt_data = "acl"}, + {.type = "xfs"}, + {.type = "btrfs"}, + {} + } +}; -- 2.39.1 -- Mailing list info: https://lists.linux.it/listinfo/ltp