* [LTP] [PATCH v5 0/8] Convert shell-based ACL test (tacl_xattr.sh) to C
@ 2026-06-08 9:21 Sachin Sant
2026-06-08 9:21 ` [LTP] [PATCH v5 1/8] fs/acl: Add ACL_USER_OBJ permission test Sachin Sant
` (7 more replies)
0 siblings, 8 replies; 13+ messages in thread
From: Sachin Sant @ 2026-06-08 9:21 UTC (permalink / raw)
To: ltp
This patch series converts the existing shell-based ACL
test (tacl_xattr.sh) to C implementation, providing better
integration with the LTP test framework and more precise
control over test execution.
v4 -> v5
Updated patches 1 to 7 to address review comments. Tests rely on
kernel only implementation and removes dependeny on acl library
and useradd/del commands
v3 -> v4
Updated patches 1/8, 4/8 & 6/8 to address review comments.
No changes to other patches in the series.
v2 -> v3
Updated patches 1 through 7 to address review comments.
v1 -> v2
Updated patches 1/8, 6/8 and 7/8 to address review comments.
Remaining patches remain unchanged
Partially closes(convert tacl_xattr.sh to C)
https://github.com/linux-test-project/ltp/issues/1265
The conversion splits functionality into seven focused test files.
Patch 1/8 adds acl_user_obj01.c
Patch 2/8 adds acl_mask01.c
Patch 3/8 adds acl_other01.c
Patch 4/8 adds acl_inherit01.c
Patch 5/8 adds acl_file_ops01.c
Patch 6/8 adds acl_link01.c
Patch 7/8 adds xattr_test01.c
The code has been successfully tested on SLES (kernel 6.12)
and fedora (kernel 7.0) releases on ppc64le arch.
Test build is clean in various distros. CI build report at
https://github.com/sacsant/ltp/actions/runs/27126245958
Sachin Sant (8):
fs/acl: Add ACL_USER_OBJ permission test
fs/acl: Add ACL mask interaction tests
fs/acl: Add ACL_OTHER permissions test
fs/acl: Add default ACL inheritance test
fs/acl: Add chmod/chown ACL interaction tests
fs/acl: Add symlink ACL operations test
fs/acl: Add extended attributes test
fs/acl: Remove old shell-based ACL test
runtest/fs | 9 +
testcases/kernel/fs/acl/.gitignore | 7 +
testcases/kernel/fs/acl/Makefile | 8 +
testcases/kernel/fs/acl/acl_file_ops01.c | 277 ++++++++
testcases/kernel/fs/acl/acl_inherit01.c | 125 ++++
testcases/kernel/fs/acl/acl_lib.h | 483 ++++++++++++++
testcases/kernel/fs/acl/acl_link01.c | 217 ++++++
testcases/kernel/fs/acl/acl_mask01.c | 372 +++++++++++
testcases/kernel/fs/acl/acl_other01.c | 120 ++++
testcases/kernel/fs/acl/acl_user_obj01.c | 154 +++++
testcases/kernel/fs/acl/tacl_xattr.sh | 807 -----------------------
testcases/kernel/fs/acl/xattr_test01.c | 381 +++++++++++
12 files changed, 2153 insertions(+), 807 deletions(-)
create mode 100644 testcases/kernel/fs/acl/.gitignore
create mode 100644 testcases/kernel/fs/acl/Makefile
create mode 100644 testcases/kernel/fs/acl/acl_file_ops01.c
create mode 100644 testcases/kernel/fs/acl/acl_inherit01.c
create mode 100644 testcases/kernel/fs/acl/acl_lib.h
create mode 100644 testcases/kernel/fs/acl/acl_link01.c
create mode 100644 testcases/kernel/fs/acl/acl_mask01.c
create mode 100644 testcases/kernel/fs/acl/acl_other01.c
create mode 100644 testcases/kernel/fs/acl/acl_user_obj01.c
delete mode 100755 testcases/kernel/fs/acl/tacl_xattr.sh
create mode 100644 testcases/kernel/fs/acl/xattr_test01.c
--
2.39.1
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 13+ messages in thread
* [LTP] [PATCH v5 1/8] fs/acl: Add ACL_USER_OBJ permission test
2026-06-08 9:21 [LTP] [PATCH v5 0/8] Convert shell-based ACL test (tacl_xattr.sh) to C Sachin Sant
@ 2026-06-08 9:21 ` Sachin Sant
2026-06-08 11:01 ` [LTP] " linuxtestproject.agent
2026-06-08 11:57 ` [LTP] [PATCH v5 1/8] " Cyril Hrubis
2026-06-08 9:21 ` [LTP] [PATCH v3 2/8] fs/acl: Add ACL mask interaction tests Sachin Sant
` (6 subsequent siblings)
7 siblings, 2 replies; 13+ messages in thread
From: Sachin Sant @ 2026-06-08 9:21 UTC (permalink / raw)
To: ltp
Add acl_user_obj01 test to validate ACL_USER_OBJ permissions:
- Owner permissions correctly control file/directory access
- ACL_USER_OBJ=rwx via setxattr() overrides chmod restrictions
- Owner permissions work independently of group/other permissions
- Tests use arbitrary UIDs without requiring actual user creation
The patch also adds acl_lib.h containing shared helpers for ACL
manipulation via xattr API, including:
- ACL structure management (acl_init, acl_free, acl_add_entry)
- ACL serialization/deserialization for kernel xattr format
- ACL get/set operations using getxattr/setxattr
- permission testing and file operations
- Support for both ACCESS and DEFAULT ACL types
The implementation uses direct xattr API (getxattr/setxattr) to
test kernel ACL behavior directly. Tests run on ext2/3/4,
XFS, and Btrfs filesystems with ACL support.
Suggested-by: Cyril Hrubis <chrubis@suse.cz>
Signed-off-by: Sachin Sant <sachinp@linux.ibm.com>
---
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@linux.ibm.com/T/#t
V4 changes:
- Add -U flag in create_user_if_needed() to useradd for guaranteed
user-private groups.
- Move EOPNOTSUPP handling into set_acl_file() helper
- v3 link https://lore.kernel.org/ltp/20260603140147.50738-1-sachinp@linux.ibm.com/T/#t
V3 changes:
- Updated copyright header as per LTP format.
- v2 link https://lore.kernel.org/ltp/20260603065744.47106-1-sachinp@linux.ibm.com/T/#t
V2 changes:
- Added reset_test_path_no_chown variant to skip chown step.
acl_link01 and xattr_test01 tests are updated to use this
variant.
- Updated acl_user_obj01.c to correct incorrect description
- v1 link https://lore.kernel.org/ltp/20260602121958.27494-1-sachinp@linux.ibm.com/T/#t
V1 changes:
- Use ACL_LIBS variable instead of hardcoded -lacl in Makefile
- Move ACL header includes inside feature guards in acl_lib.h
- 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 | 3 +
testcases/kernel/fs/acl/.gitignore | 1 +
testcases/kernel/fs/acl/Makefile | 8 +
testcases/kernel/fs/acl/acl_lib.h | 483 +++++++++++++++++++++++
testcases/kernel/fs/acl/acl_user_obj01.c | 154 ++++++++
5 files changed, 649 insertions(+)
create mode 100644 testcases/kernel/fs/acl/.gitignore
create mode 100644 testcases/kernel/fs/acl/Makefile
create mode 100644 testcases/kernel/fs/acl/acl_lib.h
create mode 100644 testcases/kernel/fs/acl/acl_user_obj01.c
diff --git a/runtest/fs b/runtest/fs
index 1d753e0dd..2a878744b 100644
--- a/runtest/fs
+++ b/runtest/fs
@@ -87,3 +87,6 @@ binfmt_misc01 binfmt_misc01.sh
binfmt_misc02 binfmt_misc02.sh
squashfs01 squashfs01
+
+# Run the acl tests
+acl_user_obj01 acl_user_obj01
diff --git a/testcases/kernel/fs/acl/.gitignore b/testcases/kernel/fs/acl/.gitignore
new file mode 100644
index 000000000..d9c46db11
--- /dev/null
+++ b/testcases/kernel/fs/acl/.gitignore
@@ -0,0 +1 @@
+/acl_user_obj01
diff --git a/testcases/kernel/fs/acl/Makefile b/testcases/kernel/fs/acl/Makefile
new file mode 100644
index 000000000..2d9cba46d
--- /dev/null
+++ b/testcases/kernel/fs/acl/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 IBM
+
+top_srcdir ?= ../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/fs/acl/acl_lib.h b/testcases/kernel/fs/acl/acl_lib.h
new file mode 100644
index 000000000..717c9ff1e
--- /dev/null
+++ b/testcases/kernel/fs/acl/acl_lib.h
@@ -0,0 +1,483 @@
+/* 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>
+ *
+ * Common library for ACL and extended attribute tests using xattr API
+ */
+
+#ifndef ACL_LIB_H
+#define ACL_LIB_H
+
+#include <pwd.h>
+#include <grp.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdint.h>
+#include <endian.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+
+#include "config.h"
+#include "tst_test.h"
+#include "tst_safe_stdio.h"
+
+#define MNTPOINT "mntpoint"
+#define TESTDIR MNTPOINT "/testdir"
+#define TESTFILE TESTDIR "/testfile"
+#define TESTSYMLINK TESTDIR "/testsymlink"
+#define XATTR_BACKUP_FILE MNTPOINT "/xattr_backup.txt"
+
+/* Extended attribute test values */
+#define XATTR_TEST_DIR_NAME "user.test_attr"
+#define XATTR_TEST_DIR_VALUE "test_value"
+#define XATTR_TEST_DIR_SIZE 10
+#define XATTR_TEST_FILE_NAME "user.file_attr"
+#define XATTR_TEST_FILE_VALUE "file_val"
+#define XATTR_TEST_FILE_SIZE 8
+#define XATTR_TEST1_NAME "user.test1"
+#define XATTR_TEST1_VALUE "value1"
+#define XATTR_TEST1_SIZE 6
+#define XATTR_TEST2_NAME "user.test2"
+#define XATTR_TEST2_VALUE "value2"
+#define XATTR_TEST2_SIZE 6
+
+/*
+ * POSIX ACL xattr format definitions
+ * These match the kernel's internal representation
+ */
+#define POSIX_ACL_XATTR_VERSION 0x0002
+
+/* ACL entry tag types */
+#define ACL_UNDEFINED_TAG 0x00
+#define ACL_USER_OBJ 0x01
+#define ACL_USER 0x02
+#define ACL_GROUP_OBJ 0x04
+#define ACL_GROUP 0x08
+#define ACL_MASK 0x10
+#define ACL_OTHER 0x20
+
+/* ACL permissions */
+#define ACL_READ 0x04
+#define ACL_WRITE 0x02
+#define ACL_EXECUTE 0x01
+
+/* ACL xattr names */
+#define XATTR_NAME_POSIX_ACL_ACCESS "system.posix_acl_access"
+#define XATTR_NAME_POSIX_ACL_DEFAULT "system.posix_acl_default"
+
+/* ACL type for set/get operations */
+#define ACL_TYPE_ACCESS 1
+#define ACL_TYPE_DEFAULT 2
+
+/* Convert host to little-endian */
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define cpu_to_le16(x) (x)
+#define cpu_to_le32(x) (x)
+#define le16_to_cpu(x) (x)
+#define le32_to_cpu(x) (x)
+#else
+#define cpu_to_le16(x) __builtin_bswap16(x)
+#define cpu_to_le32(x) __builtin_bswap32(x)
+#define le16_to_cpu(x) __builtin_bswap16(x)
+#define le32_to_cpu(x) __builtin_bswap32(x)
+#endif
+
+/*
+ * POSIX ACL xattr format as stored in kernel
+ * This is the on-disk/in-xattr representation
+ */
+struct posix_acl_xattr_header {
+ uint32_t a_version;
+};
+
+struct posix_acl_xattr_entry {
+ uint16_t e_tag;
+ uint16_t e_perm;
+ uint32_t e_id;
+};
+
+/*
+ * In-memory ACL representation for building ACLs
+ */
+#define MAX_ACL_ENTRIES 32
+
+struct acl_entry {
+ uint16_t tag;
+ uint16_t perm;
+ uint32_t id;
+};
+
+struct acl {
+ int count;
+ struct acl_entry entries[MAX_ACL_ENTRIES];
+};
+
+/* Helper functions */
+static inline void reset_test_path_no_chown(void)
+{
+ if (unlink(TESTSYMLINK) == -1 && errno != ENOENT)
+ tst_res(TWARN | TERRNO, "unlink(%s) failed", TESTSYMLINK);
+
+ if (unlink(TESTFILE) == -1 && errno != ENOENT)
+ tst_res(TWARN | TERRNO, "unlink(%s) failed", TESTFILE);
+
+ if (rmdir(TESTDIR) == -1 && errno != ENOENT)
+ tst_res(TWARN | TERRNO, "rmdir(%s) failed", TESTDIR);
+
+ SAFE_MKDIR(TESTDIR, 0755);
+}
+
+static inline void reset_test_path(void)
+{
+ reset_test_path_no_chown();
+}
+
+static inline void cleanup_testfile(void)
+{
+ if (unlink(TESTFILE) == -1 && errno != ENOENT)
+ tst_res(TWARN | TERRNO, "unlink(%s) failed", TESTFILE);
+}
+
+/*
+ * Initialize an empty ACL structure
+ */
+static inline struct acl *acl_init(void)
+{
+ struct acl *acl = malloc(sizeof(struct acl));
+
+ if (!acl)
+ return NULL;
+
+ acl->count = 0;
+ return acl;
+}
+
+/*
+ * Free an ACL structure
+ */
+static inline void acl_free(struct acl *acl)
+{
+ free(acl);
+}
+
+/*
+ * Add an ACL entry to the ACL structure
+ */
+static inline int acl_add_entry(struct acl *acl, uint16_t tag, uint16_t perm,
+ uint32_t id)
+{
+ if (acl->count >= MAX_ACL_ENTRIES) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ acl->entries[acl->count].tag = tag;
+ acl->entries[acl->count].perm = perm;
+ acl->entries[acl->count].id = id;
+ acl->count++;
+
+ return 0;
+}
+
+/*
+ * Set ACL on a file using xattr.
+ *
+ * The kernel stores access ACLs only when they differ from the file mode.
+ * If the ACL is equivalent to st_mode, the xattr is removed and future
+ * getxattr() calls return ENODATA. Mirror libacl semantics by treating
+ * ENODATA as a valid minimal ACL derived from st_mode.
+ */
+static inline int acl_set_file(const char *path, int type, struct acl *acl)
+{
+ const char *xattr_name;
+ size_t size;
+ char *buf;
+ struct posix_acl_xattr_header *header;
+ struct posix_acl_xattr_entry *entries;
+ int i, ret;
+
+ if (type == ACL_TYPE_ACCESS)
+ xattr_name = XATTR_NAME_POSIX_ACL_ACCESS;
+ else if (type == ACL_TYPE_DEFAULT)
+ xattr_name = XATTR_NAME_POSIX_ACL_DEFAULT;
+ else {
+ errno = EINVAL;
+ return -1;
+ }
+
+ size = sizeof(struct posix_acl_xattr_header) +
+ acl->count * sizeof(struct posix_acl_xattr_entry);
+
+ buf = malloc(size);
+ if (!buf)
+ return -1;
+
+ header = (struct posix_acl_xattr_header *)buf;
+ header->a_version = cpu_to_le32(POSIX_ACL_XATTR_VERSION);
+
+ entries = (struct posix_acl_xattr_entry *)(buf + sizeof(*header));
+
+ for (i = 0; i < acl->count; i++) {
+ entries[i].e_tag = cpu_to_le16(acl->entries[i].tag);
+ entries[i].e_perm = cpu_to_le16(acl->entries[i].perm);
+ entries[i].e_id = cpu_to_le32(acl->entries[i].id);
+ }
+
+ ret = setxattr(path, xattr_name, buf, size, 0);
+ free(buf);
+
+ return ret;
+}
+
+static inline int acl_add_mode_entries(struct acl *acl, mode_t mode)
+{
+ if (acl_add_entry(acl, ACL_USER_OBJ, (mode >> 6) & 07, 0) < 0)
+ return -1;
+
+ if (acl_add_entry(acl, ACL_GROUP_OBJ, (mode >> 3) & 07, 0) < 0)
+ return -1;
+
+ if (acl_add_entry(acl, ACL_OTHER, mode & 07, 0) < 0)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * Synthesize an ACL from file mode bits.
+ * Used when no xattr exists for an access ACL.
+ */
+static inline struct acl *acl_from_mode(const char *path)
+{
+ struct acl *acl;
+ struct stat st;
+
+ if (stat(path, &st) < 0)
+ return NULL;
+
+ acl = acl_init();
+ if (!acl)
+ return NULL;
+
+ if (acl_add_mode_entries(acl, st.st_mode) < 0) {
+ acl_free(acl);
+ return NULL;
+ }
+
+ return acl;
+}
+
+/*
+ * Get ACL from a file using xattr.
+ *
+ * Access ACLs equivalent to file mode may not have a backing xattr at all.
+ * In that case synthesize the base ACL from st_mode so callers observe the
+ * same behavior as acl_get_file(3).
+ */
+static inline struct acl *acl_get_file(const char *path, int type)
+{
+ const char *xattr_name;
+ ssize_t size;
+ char *buf;
+ struct posix_acl_xattr_header *header;
+ struct posix_acl_xattr_entry *entries;
+ struct acl *acl;
+ int i, count;
+
+ if (type == ACL_TYPE_ACCESS)
+ xattr_name = XATTR_NAME_POSIX_ACL_ACCESS;
+ else if (type == ACL_TYPE_DEFAULT)
+ xattr_name = XATTR_NAME_POSIX_ACL_DEFAULT;
+ else {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ size = getxattr(path, xattr_name, NULL, 0);
+ if (size < 0) {
+ if (errno != ENODATA || type != ACL_TYPE_ACCESS)
+ return NULL;
+
+ return acl_from_mode(path);
+ }
+
+ /* Handle race: xattr removed between size check and actual read */
+ if (size == 0)
+ return acl_from_mode(path);
+
+ buf = malloc(size);
+ if (!buf)
+ return NULL;
+
+ size = getxattr(path, xattr_name, buf, size);
+ if (size < 0) {
+ free(buf);
+ /* Handle race: xattr removed between size check and read */
+ if (errno == ENODATA && type == ACL_TYPE_ACCESS)
+ return acl_from_mode(path);
+ return NULL;
+ }
+
+ header = (struct posix_acl_xattr_header *)buf;
+ if (le32_to_cpu(header->a_version) != POSIX_ACL_XATTR_VERSION) {
+ free(buf);
+ errno = EINVAL;
+ return NULL;
+ }
+
+ count = (size - sizeof(*header)) / sizeof(struct posix_acl_xattr_entry);
+ entries = (struct posix_acl_xattr_entry *)(buf + sizeof(*header));
+
+ acl = acl_init();
+ if (!acl) {
+ free(buf);
+ return NULL;
+ }
+
+ for (i = 0; i < count; i++) {
+ uint16_t tag = le16_to_cpu(entries[i].e_tag);
+ uint16_t perm = le16_to_cpu(entries[i].e_perm);
+ uint32_t id = le32_to_cpu(entries[i].e_id);
+
+ if (acl_add_entry(acl, tag, perm, id) < 0) {
+ acl_free(acl);
+ free(buf);
+ return NULL;
+ }
+ }
+
+ free(buf);
+ return acl;
+}
+
+/*
+ * Check if an ACL entry has a specific permission
+ */
+static inline int acl_entry_has_perm(struct acl_entry *entry, uint16_t perm)
+{
+ return (entry->perm & perm) == perm;
+}
+
+/*
+ * Check if an ACL entry has all rwx permissions
+ */
+static inline int acl_entry_has_rwx(struct acl_entry *entry)
+{
+ return acl_entry_has_perm(entry, ACL_READ) &&
+ acl_entry_has_perm(entry, ACL_WRITE) &&
+ acl_entry_has_perm(entry, ACL_EXECUTE);
+}
+
+/*
+ * Find an ACL entry by tag type
+ */
+static inline struct acl_entry *acl_find_entry(struct acl *acl, uint16_t tag,
+ uint32_t id)
+{
+ int i;
+
+ for (i = 0; i < acl->count; i++) {
+ if (acl->entries[i].tag == tag) {
+ if (tag == ACL_USER || tag == ACL_GROUP) {
+ if (acl->entries[i].id == id)
+ return &acl->entries[i];
+ } else {
+ return &acl->entries[i];
+ }
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Update ACL mask permissions
+ */
+static inline int acl_set_mask_perms(struct acl *acl, uint16_t perm)
+{
+ struct acl_entry *mask_entry = acl_find_entry(acl, ACL_MASK, 0);
+
+ if (!mask_entry) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ mask_entry->perm = perm;
+ return 0;
+}
+
+static inline int create_file_as(uid_t uid, gid_t gid, mode_t mode,
+ int use_umask, mode_t mask)
+{
+ pid_t pid;
+ int status;
+
+ pid = SAFE_FORK();
+ if (!pid) {
+ int fd, err;
+
+ if (setgroups(0, NULL) == -1) {
+ err = errno;
+ _exit(err);
+ }
+
+ if (setgid(gid) == -1) {
+ err = errno;
+ _exit(err);
+ }
+
+ if (setuid(uid) == -1) {
+ err = errno;
+ _exit(err);
+ }
+
+ if (use_umask)
+ umask(mask);
+
+ fd = open(TESTFILE, O_CREAT | O_WRONLY, mode);
+ if (fd >= 0) {
+ close(fd);
+ _exit(0);
+ }
+
+ err = errno;
+ _exit(err);
+ }
+
+ SAFE_WAITPID(pid, &status, 0);
+
+ if (!WIFEXITED(status))
+ tst_brk(TBROK, "Child terminated abnormally");
+
+ return WEXITSTATUS(status);
+}
+
+static inline int try_create_as(uid_t uid, gid_t gid, mode_t mode)
+{
+ return create_file_as(uid, gid, mode, 0, 0);
+}
+
+static inline int create_with_umask_as(uid_t uid, gid_t gid, mode_t mode,
+ mode_t mask)
+{
+ return create_file_as(uid, gid, mode, 1, mask);
+}
+
+static inline void cleanup_test_paths(void)
+{
+ if (unlink(TESTSYMLINK) == -1 && errno != ENOENT)
+ tst_res(TWARN | TERRNO, "unlink(%s) failed", TESTSYMLINK);
+
+ if (unlink(TESTFILE) == -1 && errno != ENOENT)
+ tst_res(TWARN | TERRNO, "unlink(%s) failed", TESTFILE);
+
+ if (rmdir(TESTDIR) == -1 && errno != ENOENT)
+ tst_res(TWARN | TERRNO, "rmdir(%s) failed", TESTDIR);
+}
+
+#endif /* ACL_LIB_H */
diff --git a/testcases/kernel/fs/acl/acl_user_obj01.c b/testcases/kernel/fs/acl/acl_user_obj01.c
new file mode 100644
index 000000000..01abfbf4e
--- /dev/null
+++ b/testcases/kernel/fs/acl/acl_user_obj01.c
@@ -0,0 +1,154 @@
+// 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 ACL_USER_OBJ permissions using direct xattr manipulation.
+ *
+ * Verify that owner permissions (ACL_USER_OBJ) correctly control access
+ * to files and directories. The test validates that:
+ * - ACL_USER_OBJ permissions are applied directly as the owner bits
+ * - Setting ACL_USER_OBJ=rwx via setxattr() overrides a previous
+ * chmod restriction
+ * - Owner permissions work independently of group and other permissions
+ *
+ * This test uses arbitrary UIDs without creating actual users, testing
+ * only the kernel ACL implementation.
+ */
+
+#include "acl_lib.h"
+
+#define TEST_UID 1000
+#define TEST_GID 1000
+
+/*
+ * Test permission bits deny access.
+ * Owner should be denied write access when mode is 0500.
+ */
+static void test_deny_by_mode(void)
+{
+ int err;
+
+ tst_res(TINFO, "Testing permission bits deny access");
+ reset_test_path();
+
+ SAFE_CHOWN(TESTDIR, TEST_UID, TEST_GID);
+ SAFE_CHMOD(TESTDIR, 0500);
+
+ err = try_create_as(TEST_UID, TEST_GID, 0644);
+ if (!err) {
+ cleanup_testfile();
+ tst_res(TFAIL, "Created file without write permission");
+ return;
+ }
+
+ if (err != EACCES) {
+ errno = err;
+ tst_res(TFAIL | TERRNO, "Expected EACCES from owner create");
+ return;
+ }
+
+ tst_res(TPASS, "File creation denied by permission bits");
+}
+
+/*
+ * Test ACL_USER_OBJ grants access.
+ * Setting ACL_USER_OBJ=rwx should override previous chmod restriction.
+ */
+static void test_grant_by_acl(void)
+{
+ struct acl *acl = NULL;
+ int err;
+
+ tst_res(TINFO, "Testing ACL_USER_OBJ grants access");
+ reset_test_path();
+
+ SAFE_CHOWN(TESTDIR, TEST_UID, TEST_GID);
+ SAFE_CHMOD(TESTDIR, 0500);
+
+ acl = acl_init();
+ if (!acl)
+ tst_brk(TBROK | TERRNO, "acl_init failed");
+
+ 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, 0, 0) < 0)
+ goto cleanup_acl;
+
+ if (acl_add_entry(acl, ACL_OTHER, 0, 0) < 0)
+ goto cleanup_acl;
+
+ if (acl_set_file(TESTDIR, ACL_TYPE_ACCESS, acl) < 0) {
+ if (errno == EOPNOTSUPP) {
+ acl_free(acl);
+ tst_brk(TCONF | TERRNO, "ACL not supported");
+ }
+ goto cleanup_acl;
+ }
+
+ acl_free(acl);
+ acl = NULL;
+
+ err = try_create_as(TEST_UID, TEST_GID, 0644);
+ if (err) {
+ errno = err;
+ tst_res(TFAIL | TERRNO,
+ "Failed to create file with ACL_USER_OBJ rwx");
+ return;
+ }
+
+ cleanup_testfile();
+ tst_res(TPASS, "ACL_USER_OBJ permissions work correctly");
+ return;
+
+cleanup_acl:
+ acl_free(acl);
+ tst_brk(TBROK | TERRNO, "ACL setup failed");
+}
+
+static void run(unsigned int n)
+{
+ switch (n) {
+ case 0:
+ test_deny_by_mode();
+ break;
+ case 1:
+ test_grant_by_acl();
+ break;
+ }
+}
+
+static void setup(void)
+{
+ reset_test_path();
+}
+
+static void cleanup(void)
+{
+ cleanup_test_paths();
+}
+
+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 = "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
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [LTP] [PATCH v3 2/8] fs/acl: Add ACL mask interaction tests
2026-06-08 9:21 [LTP] [PATCH v5 0/8] Convert shell-based ACL test (tacl_xattr.sh) to C Sachin Sant
2026-06-08 9:21 ` [LTP] [PATCH v5 1/8] fs/acl: Add ACL_USER_OBJ permission test Sachin Sant
@ 2026-06-08 9:21 ` Sachin Sant
2026-06-08 9:21 ` [LTP] [PATCH v3 3/8] fs/acl: Add ACL_OTHER permissions test Sachin Sant
` (5 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: Sachin Sant @ 2026-06-08 9:21 UTC (permalink / raw)
To: ltp
Add acl_mask01 test with 3 test cases to validate ACL_MASK
interaction with named user and group entries:
1. ACL_USER with mask - Named user permissions restricted by mask
2. ACL_GROUP with mask - Named group permissions restricted by mask
3. ACL_GROUP_OBJ with mask - Group owner permissions restricted by mask
Each test verifies that:
- With mask set to rwx, access is granted
- With mask cleared (---), access is denied with EACCES
This validates that ACL_MASK correctly restricts permissions for
ACL_USER, ACL_GROUP, and ACL_GROUP_OBJ entries.
Suggested-by: Cyril Hrubis <chrubis@suse.cz>
Signed-off-by: Sachin Sant <sachinp@linux.ibm.com>
---
V3 changes:
- Switch to kernel only test validation to remove dependency on libacl
and useradd/del commands.
- v2 link https://lore.kernel.org/ltp/20260604065417.25924-1-sachinp@linux.ibm.com/T/#t
V2 changes:
- Updated copyright header as per LTP format.
- 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/acl_mask01.c | 372 +++++++++++++++++++++++++++
3 files changed, 374 insertions(+)
create mode 100644 testcases/kernel/fs/acl/acl_mask01.c
diff --git a/runtest/fs b/runtest/fs
index 2a878744b..69ecb8647 100644
--- a/runtest/fs
+++ b/runtest/fs
@@ -90,3 +90,4 @@ squashfs01 squashfs01
# Run the acl tests
acl_user_obj01 acl_user_obj01
+acl_mask01 acl_mask01
diff --git a/testcases/kernel/fs/acl/.gitignore b/testcases/kernel/fs/acl/.gitignore
index d9c46db11..bfcdee93d 100644
--- a/testcases/kernel/fs/acl/.gitignore
+++ b/testcases/kernel/fs/acl/.gitignore
@@ -1 +1,2 @@
/acl_user_obj01
+/acl_mask01
diff --git a/testcases/kernel/fs/acl/acl_mask01.c b/testcases/kernel/fs/acl/acl_mask01.c
new file mode 100644
index 000000000..ca690ba6a
--- /dev/null
+++ b/testcases/kernel/fs/acl/acl_mask01.c
@@ -0,0 +1,372 @@
+// 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 ACL mask interaction with named users and groups using direct xattr
+ * manipulation.
+ *
+ * Verify that ACL_MASK correctly restricts permissions for:
+ * - ACL_USER (named user) entries
+ * - ACL_GROUP (named group) entries
+ * - ACL_GROUP_OBJ (group owner) entries
+ *
+ * The mask acts as an upper bound on permissions for these entry types.
+ * Even if an entry grants full permissions, the mask can restrict them.
+ * ACL_USER_OBJ and ACL_OTHER are not affected by the mask.
+ *
+ * This test uses arbitrary UIDs without creating actual users, testing
+ * only the kernel ACL implementation.
+ *
+ * [Algorithm]
+ *
+ * For each entry type (ACL_USER, ACL_GROUP, ACL_GROUP_OBJ):
+ * 1. Set up ACL with full permissions for the entry
+ * 2. Set mask to allow full permissions (rwx)
+ * 3. Verify access is granted
+ * 4. Clear mask permissions (---)
+ * 5. Verify access is denied despite entry having full permissions
+ */
+
+#include "acl_lib.h"
+
+#define TEST_UID 1000
+#define TEST_GID 1000
+#define USER2_UID 2000
+#define USER2_GID 2000
+#define USER3_UID 3000
+#define USER3_GID 3000
+
+/*
+ * Test ACL_USER permissions with mask.
+ * Named user permissions should be restricted by ACL_MASK.
+ */
+static void test_acl_user_with_mask(void)
+{
+ struct acl *acl = NULL;
+ int err;
+
+ tst_res(TINFO, "Testing ACL_USER with mask");
+ reset_test_path();
+
+ SAFE_CHOWN(TESTDIR, TEST_UID, TEST_GID);
+
+ acl = acl_init();
+ if (!acl)
+ tst_brk(TBROK | TERRNO, "acl_init failed");
+
+ 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_USER,
+ ACL_READ | ACL_WRITE | ACL_EXECUTE, USER3_UID) < 0)
+ goto cleanup_acl;
+
+ if (acl_add_entry(acl, ACL_GROUP_OBJ, 0, 0) < 0)
+ goto cleanup_acl;
+
+ if (acl_add_entry(acl, ACL_MASK,
+ ACL_READ | ACL_WRITE | ACL_EXECUTE, 0) < 0)
+ goto cleanup_acl;
+
+ if (acl_add_entry(acl, ACL_OTHER, 0, 0) < 0)
+ goto cleanup_acl;
+
+ if (acl_set_file(TESTDIR, ACL_TYPE_ACCESS, acl) < 0) {
+ if (errno == EOPNOTSUPP) {
+ acl_free(acl);
+ tst_brk(TCONF | TERRNO, "ACL not supported");
+ }
+ goto cleanup_acl;
+ }
+
+ acl_free(acl);
+ acl = NULL;
+
+ err = try_create_as(USER3_UID, USER3_GID, 0644);
+ if (err) {
+ errno = err;
+ tst_res(TFAIL | TERRNO,
+ "ACL_USER with mask rwx should allow access");
+ return;
+ }
+
+ cleanup_testfile();
+
+ /* Clear mask permissions */
+ acl = acl_get_file(TESTDIR, ACL_TYPE_ACCESS);
+ if (!acl)
+ tst_brk(TBROK | TERRNO, "acl_get_file failed");
+
+ if (acl_set_mask_perms(acl, 0) < 0) {
+ acl_free(acl);
+ tst_brk(TBROK | TERRNO, "acl_set_mask_perms failed");
+ }
+
+ if (acl_set_file(TESTDIR, ACL_TYPE_ACCESS, acl) < 0) {
+ acl_free(acl);
+ tst_brk(TBROK | TERRNO, "acl_set_file failed");
+ }
+
+ acl_free(acl);
+
+ err = try_create_as(USER3_UID, USER3_GID, 0644);
+ if (!err) {
+ cleanup_testfile();
+ tst_res(TFAIL, "ACL_USER with mask --- should deny access");
+ return;
+ }
+
+ if (err != EACCES) {
+ errno = err;
+ tst_res(TFAIL | TERRNO, "Expected EACCES from named user");
+ return;
+ }
+
+ tst_res(TPASS, "ACL_USER with mask works correctly");
+ return;
+
+cleanup_acl:
+ acl_free(acl);
+ tst_brk(TBROK | TERRNO, "ACL setup failed");
+}
+
+/*
+ * Test ACL_GROUP permissions with mask.
+ * Named group permissions should be restricted by ACL_MASK.
+ */
+static void test_acl_group_with_mask(void)
+{
+ struct acl *acl = NULL;
+ int err;
+
+ tst_res(TINFO, "Testing ACL_GROUP with mask");
+ reset_test_path();
+
+ SAFE_CHOWN(TESTDIR, TEST_UID, TEST_GID);
+
+ acl = acl_init();
+ if (!acl)
+ tst_brk(TBROK | TERRNO, "acl_init failed");
+
+ 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, 0, 0) < 0)
+ goto cleanup_acl;
+
+ if (acl_add_entry(acl, ACL_GROUP,
+ ACL_READ | ACL_WRITE | ACL_EXECUTE, USER2_GID) < 0)
+ goto cleanup_acl;
+
+ if (acl_add_entry(acl, ACL_MASK,
+ ACL_READ | ACL_WRITE | ACL_EXECUTE, 0) < 0)
+ goto cleanup_acl;
+
+ if (acl_add_entry(acl, ACL_OTHER, 0, 0) < 0)
+ goto cleanup_acl;
+
+ if (acl_set_file(TESTDIR, ACL_TYPE_ACCESS, acl) < 0) {
+ if (errno == EOPNOTSUPP) {
+ acl_free(acl);
+ tst_brk(TCONF | TERRNO, "ACL not supported");
+ }
+ goto cleanup_acl;
+ }
+
+ acl_free(acl);
+ acl = NULL;
+
+ err = try_create_as(USER2_UID, USER2_GID, 0644);
+ if (err) {
+ errno = err;
+ tst_res(TFAIL | TERRNO,
+ "ACL_GROUP with mask rwx should allow access");
+ return;
+ }
+
+ cleanup_testfile();
+
+ /* Clear mask permissions */
+ acl = acl_get_file(TESTDIR, ACL_TYPE_ACCESS);
+ if (!acl)
+ tst_brk(TBROK | TERRNO, "acl_get_file failed");
+
+ if (acl_set_mask_perms(acl, 0) < 0) {
+ acl_free(acl);
+ tst_brk(TBROK | TERRNO, "acl_set_mask_perms failed");
+ }
+
+ if (acl_set_file(TESTDIR, ACL_TYPE_ACCESS, acl) < 0) {
+ acl_free(acl);
+ tst_brk(TBROK | TERRNO, "acl_set_file failed");
+ }
+
+ acl_free(acl);
+
+ err = try_create_as(USER2_UID, USER2_GID, 0644);
+ if (!err) {
+ cleanup_testfile();
+ tst_res(TFAIL, "ACL_GROUP with mask --- should deny access");
+ return;
+ }
+
+ if (err != EACCES) {
+ errno = err;
+ tst_res(TFAIL | TERRNO, "Expected EACCES from named group");
+ return;
+ }
+
+ tst_res(TPASS, "ACL_GROUP with mask works correctly");
+ return;
+
+cleanup_acl:
+ acl_free(acl);
+ tst_brk(TBROK | TERRNO, "ACL setup failed");
+}
+
+/*
+ * Test ACL_GROUP_OBJ permissions with mask.
+ * Group owner permissions should be restricted by ACL_MASK.
+ */
+static void test_acl_group_obj_with_mask(void)
+{
+ struct acl *acl = NULL;
+ int err;
+
+ tst_res(TINFO, "Testing ACL_GROUP_OBJ with mask");
+ reset_test_path();
+
+ SAFE_CHOWN(TESTDIR, TEST_UID, USER2_GID);
+
+ acl = acl_init();
+ if (!acl)
+ tst_brk(TBROK | TERRNO, "acl_init failed");
+
+ 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 | ACL_EXECUTE, 0) < 0)
+ goto cleanup_acl;
+
+ if (acl_add_entry(acl, ACL_MASK,
+ ACL_READ | ACL_WRITE | ACL_EXECUTE, 0) < 0)
+ goto cleanup_acl;
+
+ if (acl_add_entry(acl, ACL_OTHER, 0, 0) < 0)
+ goto cleanup_acl;
+
+ if (acl_set_file(TESTDIR, ACL_TYPE_ACCESS, acl) < 0) {
+ if (errno == EOPNOTSUPP) {
+ acl_free(acl);
+ tst_brk(TCONF | TERRNO, "ACL not supported");
+ }
+ goto cleanup_acl;
+ }
+
+ acl_free(acl);
+ acl = NULL;
+
+ err = try_create_as(USER2_UID, USER2_GID, 0644);
+ if (err) {
+ errno = err;
+ tst_res(TFAIL | TERRNO,
+ "ACL_GROUP_OBJ with mask rwx should allow access");
+ return;
+ }
+
+ cleanup_testfile();
+
+ /* Clear mask permissions */
+ acl = acl_get_file(TESTDIR, ACL_TYPE_ACCESS);
+ if (!acl)
+ tst_brk(TBROK | TERRNO, "acl_get_file failed");
+
+ if (acl_set_mask_perms(acl, 0) < 0) {
+ acl_free(acl);
+ tst_brk(TBROK | TERRNO, "acl_set_mask_perms failed");
+ }
+
+ if (acl_set_file(TESTDIR, ACL_TYPE_ACCESS, acl) < 0) {
+ acl_free(acl);
+ tst_brk(TBROK | TERRNO, "acl_set_file failed");
+ }
+
+ acl_free(acl);
+
+ err = try_create_as(USER2_UID, USER2_GID, 0644);
+ if (!err) {
+ cleanup_testfile();
+ SAFE_CHOWN(TESTDIR, TEST_UID, TEST_GID);
+ tst_res(TFAIL,
+ "ACL_GROUP_OBJ with mask --- should deny access");
+ return;
+ }
+
+ if (err != EACCES) {
+ SAFE_CHOWN(TESTDIR, TEST_UID, TEST_GID);
+ errno = err;
+ tst_res(TFAIL | TERRNO, "Expected EACCES from group owner");
+ return;
+ }
+
+ SAFE_CHOWN(TESTDIR, TEST_UID, TEST_GID);
+ tst_res(TPASS, "ACL_GROUP_OBJ with mask works correctly");
+ return;
+
+cleanup_acl:
+ acl_free(acl);
+ tst_brk(TBROK | TERRNO, "ACL setup failed");
+}
+
+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_acl_user_with_mask();
+ break;
+ case 1:
+ test_acl_group_with_mask();
+ break;
+ case 2:
+ test_acl_group_obj_with_mask();
+ break;
+ }
+}
+
+static struct tst_test test = {
+ .test = run,
+ .tcnt = 3,
+ .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
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [LTP] [PATCH v3 3/8] fs/acl: Add ACL_OTHER permissions test
2026-06-08 9:21 [LTP] [PATCH v5 0/8] Convert shell-based ACL test (tacl_xattr.sh) to C Sachin Sant
2026-06-08 9:21 ` [LTP] [PATCH v5 1/8] fs/acl: Add ACL_USER_OBJ permission test Sachin Sant
2026-06-08 9:21 ` [LTP] [PATCH v3 2/8] fs/acl: Add ACL mask interaction tests Sachin Sant
@ 2026-06-08 9:21 ` Sachin Sant
2026-06-08 9:21 ` [LTP] [PATCH v4 4/8] fs/acl: Add default ACL inheritance test Sachin Sant
` (4 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: Sachin Sant @ 2026-06-08 9:21 UTC (permalink / raw)
To: ltp
Add acl_other01 test to validate that ACL_OTHER permissions are
not affected by ACL_MASK.
The test verifies that:
- ACL_OTHER entry with rwx permissions allows access
- ACL_MASK set to --- does not restrict ACL_OTHER
- Users not matching owner, named users, or groups use ACL_OTHER
This confirms that ACL_MASK only affects ACL_USER, ACL_GROUP_OBJ,
and ACL_GROUP entries, but not ACL_OTHER.
Suggested-by: Cyril Hrubis <chrubis@suse.cz>
Signed-off-by: Sachin Sant <sachinp@linux.ibm.com>
---
V3 changes:
- Switch to kernel only test validation to remove dependency on libacl
and useradd/del commands.
- v2 link https://lore.kernel.org/ltp/20260604065417.25924-1-sachinp@linux.ibm.com/T/#t
V2 changes:
- Updated copyright header as per LTP format.
- 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/acl_other01.c | 120 ++++++++++++++++++++++++++
3 files changed, 122 insertions(+)
create mode 100644 testcases/kernel/fs/acl/acl_other01.c
diff --git a/runtest/fs b/runtest/fs
index 69ecb8647..f25487a33 100644
--- a/runtest/fs
+++ b/runtest/fs
@@ -91,3 +91,4 @@ squashfs01 squashfs01
# Run the acl tests
acl_user_obj01 acl_user_obj01
acl_mask01 acl_mask01
+acl_other01 acl_other01
diff --git a/testcases/kernel/fs/acl/.gitignore b/testcases/kernel/fs/acl/.gitignore
index bfcdee93d..c3ec0fad3 100644
--- a/testcases/kernel/fs/acl/.gitignore
+++ b/testcases/kernel/fs/acl/.gitignore
@@ -1,2 +1,3 @@
/acl_user_obj01
/acl_mask01
+/acl_other01
diff --git a/testcases/kernel/fs/acl/acl_other01.c b/testcases/kernel/fs/acl/acl_other01.c
new file mode 100644
index 000000000..68d5026d3
--- /dev/null
+++ b/testcases/kernel/fs/acl/acl_other01.c
@@ -0,0 +1,120 @@
+// 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 ACL_OTHER permissions using direct xattr manipulation.
+ *
+ * Verify that ACL_OTHER permissions work correctly and are not affected
+ * by ACL_MASK. The ACL_OTHER entry controls access for users who don't
+ * match any other ACL entry (not the owner, not in any named user entry,
+ * not in the owning group, and not in any named group entry).
+ *
+ * Unlike ACL_USER, ACL_GROUP, and ACL_GROUP_OBJ entries, ACL_OTHER
+ * permissions are not restricted by the ACL_MASK.
+ *
+ * This test uses arbitrary UIDs without creating actual users, testing
+ * only the kernel ACL implementation.
+ *
+ * [Algorithm]
+ *
+ * 1. Set up ACL with rwx permissions for ACL_OTHER
+ * 2. Set ACL_MASK to --- (no permissions)
+ * 3. Attempt file creation as a user matching ACL_OTHER
+ * 4. Verify access is granted despite restrictive mask
+ */
+
+#include "acl_lib.h"
+
+#define TEST_UID 1000
+#define TEST_GID 1000
+#define OTHER_UID 2000
+#define OTHER_GID 2000
+
+static void run(void)
+{
+ struct acl *acl = NULL;
+ int err;
+
+ tst_res(TINFO, "Testing ACL_OTHER permissions");
+ reset_test_path();
+
+ SAFE_CHOWN(TESTDIR, TEST_UID, TEST_GID);
+
+ acl = acl_init();
+ if (!acl)
+ tst_brk(TBROK | TERRNO, "acl_init failed");
+
+ 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, 0, 0) < 0)
+ goto cleanup_acl;
+
+ if (acl_add_entry(acl, ACL_MASK, 0, 0) < 0)
+ goto cleanup_acl;
+
+ if (acl_add_entry(acl, ACL_OTHER,
+ ACL_READ | ACL_WRITE | ACL_EXECUTE, 0) < 0)
+ goto cleanup_acl;
+
+ if (acl_set_file(TESTDIR, ACL_TYPE_ACCESS, acl) < 0) {
+ if (errno == EOPNOTSUPP) {
+ acl_free(acl);
+ tst_brk(TCONF | TERRNO, "ACL not supported");
+ }
+ goto cleanup_acl;
+ }
+
+ acl_free(acl);
+ acl = NULL;
+
+ err = try_create_as(OTHER_UID, OTHER_GID, 0644);
+ if (err) {
+ errno = err;
+ tst_res(TFAIL | TERRNO,
+ "ACL_OTHER rwx should allow access despite mask");
+ return;
+ }
+
+ cleanup_testfile();
+ tst_res(TPASS, "ACL_OTHER not affected by mask");
+ return;
+
+cleanup_acl:
+ acl_free(acl);
+ tst_brk(TBROK | TERRNO, "ACL setup failed");
+}
+
+static void setup(void)
+{
+ reset_test_path();
+}
+
+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
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [LTP] [PATCH v4 4/8] fs/acl: Add default ACL inheritance test
2026-06-08 9:21 [LTP] [PATCH v5 0/8] Convert shell-based ACL test (tacl_xattr.sh) to C Sachin Sant
` (2 preceding siblings ...)
2026-06-08 9:21 ` [LTP] [PATCH v3 3/8] fs/acl: Add ACL_OTHER permissions test Sachin Sant
@ 2026-06-08 9:21 ` Sachin Sant
2026-06-08 9:21 ` [LTP] [PATCH v3 5/8] fs/acl: Add chmod/chown ACL interaction tests Sachin Sant
` (3 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: Sachin Sant @ 2026-06-08 9:21 UTC (permalink / raw)
To: ltp
Add acl_inherit01 test to validate default ACL inheritance from
parent directory to newly created files.
The test verifies that:
- Default ACLs set on a directory are inherited by new files
- New file permissions reflect the default ACL entries
- File created with umask 0 gets permissions from default ACL
This test sets default ACL with read-only permissions (r--r--r--)
on the parent directory, creates a new file with umask 0, and
verifies the file has 0444 permissions inherited from the
default ACL.
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:
- Remove unused acl_get_file() block
- keep mode-bit validation with explanatory comment
- v2 link https://lore.kernel.org/ltp/20260603065744.47106-1-sachinp@linux.ibm.com/T/#t
V2 changes:
- Updated copyright header as per LTP format.
- 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/acl_inherit01.c | 125 ++++++++++++++++++++++++
3 files changed, 127 insertions(+)
create mode 100644 testcases/kernel/fs/acl/acl_inherit01.c
diff --git a/runtest/fs b/runtest/fs
index f25487a33..fd295edc7 100644
--- a/runtest/fs
+++ b/runtest/fs
@@ -92,3 +92,4 @@ squashfs01 squashfs01
acl_user_obj01 acl_user_obj01
acl_mask01 acl_mask01
acl_other01 acl_other01
+acl_inherit01 acl_inherit01
diff --git a/testcases/kernel/fs/acl/.gitignore b/testcases/kernel/fs/acl/.gitignore
index c3ec0fad3..bc03ba1fd 100644
--- a/testcases/kernel/fs/acl/.gitignore
+++ b/testcases/kernel/fs/acl/.gitignore
@@ -1,3 +1,4 @@
/acl_user_obj01
/acl_mask01
/acl_other01
+/acl_inherit01
diff --git a/testcases/kernel/fs/acl/acl_inherit01.c b/testcases/kernel/fs/acl/acl_inherit01.c
new file mode 100644
index 000000000..76423308b
--- /dev/null
+++ b/testcases/kernel/fs/acl/acl_inherit01.c
@@ -0,0 +1,125 @@
+// 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 default ACL inheritance using direct xattr manipulation.
+ *
+ * Verify that files created in a directory with default ACLs inherit
+ * those ACLs as their access ACLs. Default ACLs are only applicable
+ * to directories and define the access ACLs that files and subdirectories
+ * created within that directory will inherit.
+ *
+ * This test uses arbitrary UIDs without creating actual users, testing
+ * only the kernel ACL implementation.
+ *
+ * [Algorithm]
+ *
+ * 1. Set default ACL on parent directory with read-only permissions
+ * 2. Create a new file in that directory with umask 0
+ * 3. Verify the file inherits the default ACL as its access ACL
+ * 4. Check that file permissions match the inherited ACL (0444)
+ */
+
+#include "acl_lib.h"
+
+#define TEST_UID 1000
+#define TEST_GID 1000
+
+static void run(void)
+{
+ struct acl *acl = NULL;
+ struct stat st;
+ int err;
+
+ tst_res(TINFO, "Testing default ACL inheritance");
+ reset_test_path();
+
+ SAFE_CHOWN(TESTDIR, TEST_UID, TEST_GID);
+
+ acl = acl_init();
+ if (!acl)
+ tst_brk(TBROK | TERRNO, "acl_init failed");
+
+ if (acl_add_entry(acl, ACL_USER_OBJ, ACL_READ, 0) < 0)
+ goto cleanup_acl;
+
+ if (acl_add_entry(acl, ACL_GROUP_OBJ, ACL_READ, 0) < 0)
+ goto cleanup_acl;
+
+ if (acl_add_entry(acl, ACL_OTHER, ACL_READ, 0) < 0)
+ goto cleanup_acl;
+
+ if (acl_set_file(TESTDIR, ACL_TYPE_DEFAULT, acl) < 0) {
+ if (errno == EOPNOTSUPP) {
+ acl_free(acl);
+ tst_brk(TCONF | TERRNO, "ACL not supported");
+ }
+ goto cleanup_acl;
+ }
+
+ acl_free(acl);
+ acl = NULL;
+
+ err = create_with_umask_as(TEST_UID, TEST_GID, 0666, 0);
+ if (err) {
+ errno = err;
+ tst_res(TFAIL | TERRNO, "Failed to create test file");
+ return;
+ }
+
+ SAFE_STAT(TESTFILE, &st);
+
+ /*
+ * For a minimal ACL (containing only ACL_USER_OBJ, ACL_GROUP_OBJ,
+ * and ACL_OTHER), the mode bits are the canonical representation.
+ * Verifying the mode bits confirms the inherited ACL was applied.
+ */
+ if ((st.st_mode & 0777) != 0444) {
+ tst_res(TFAIL,
+ "File permissions 0%o, expected 0444 from default ACL",
+ st.st_mode & 0777);
+ cleanup_testfile();
+ return;
+ }
+
+ cleanup_testfile();
+ tst_res(TPASS, "Default ACL inheritance works correctly");
+ return;
+
+cleanup_acl:
+ acl_free(acl);
+ tst_brk(TBROK | TERRNO, "ACL setup failed");
+}
+
+static void setup(void)
+{
+ reset_test_path();
+}
+
+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
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [LTP] [PATCH v3 5/8] fs/acl: Add chmod/chown ACL interaction tests
2026-06-08 9:21 [LTP] [PATCH v5 0/8] Convert shell-based ACL test (tacl_xattr.sh) to C Sachin Sant
` (3 preceding siblings ...)
2026-06-08 9:21 ` [LTP] [PATCH v4 4/8] fs/acl: Add default ACL inheritance test Sachin Sant
@ 2026-06-08 9:21 ` Sachin Sant
2026-06-08 9:21 ` [LTP] [PATCH v5 6/8] fs/acl: Add symlink ACL operations test Sachin Sant
` (2 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: Sachin Sant @ 2026-06-08 9:21 UTC (permalink / raw)
To: ltp
Add acl_file_ops01 test with 2 test cases to validate chmod and
chown interaction with ACL entries:
chmod interaction - Verifies that chmod updates ACL_USER_OBJ,
ACL_GROUP_OBJ, and ACL_OTHER entries to match new permission bits.
Sets ACL with read-only, then chmod 0777, and confirms all three
ACL entries now have rwx permissions.
chown interaction - Verifies that chown changes file owner and
group without modifying ACL entry permissions. Sets ACL entries,
performs chown, and confirms ACL permissions are preserved.
These tests ensure chmod and chown work correctly with ACL-enabled
files.
Suggested-by: Cyril Hrubis <chrubis@suse.cz>
Signed-off-by: Sachin Sant <sachinp@linux.ibm.com>
---
V3 changes:
- Switch to kernel only test validation to remove dependency on libacl
and useradd/del commands.
- v2 link https://lore.kernel.org/ltp/20260604065417.25924-1-sachinp@linux.ibm.com/T/#t
V2 changes:
- Updated copyright header as per LTP format.
- 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/acl_file_ops01.c | 277 +++++++++++++++++++++++
3 files changed, 279 insertions(+)
create mode 100644 testcases/kernel/fs/acl/acl_file_ops01.c
diff --git a/runtest/fs b/runtest/fs
index fd295edc7..f1eea055b 100644
--- a/runtest/fs
+++ b/runtest/fs
@@ -93,3 +93,4 @@ acl_user_obj01 acl_user_obj01
acl_mask01 acl_mask01
acl_other01 acl_other01
acl_inherit01 acl_inherit01
+acl_file_ops01 acl_file_ops01
diff --git a/testcases/kernel/fs/acl/.gitignore b/testcases/kernel/fs/acl/.gitignore
index bc03ba1fd..eb4b4a227 100644
--- a/testcases/kernel/fs/acl/.gitignore
+++ b/testcases/kernel/fs/acl/.gitignore
@@ -2,3 +2,4 @@
/acl_mask01
/acl_other01
/acl_inherit01
+/acl_file_ops01
diff --git a/testcases/kernel/fs/acl/acl_file_ops01.c b/testcases/kernel/fs/acl/acl_file_ops01.c
new file mode 100644
index 000000000..d92f71e27
--- /dev/null
+++ b/testcases/kernel/fs/acl/acl_file_ops01.c
@@ -0,0 +1,277 @@
+// 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 chmod and chown interaction with ACLs using direct xattr manipulation.
+ *
+ * Verify that standard file operations (chmod, chown) interact correctly
+ * with ACLs:
+ * - chmod should update ACL_USER_OBJ, ACL_GROUP_OBJ, and ACL_OTHER entries
+ * - chown should change file owner/group without affecting ACL entries
+ * - ACL permissions should be preserved after ownership changes
+ *
+ * This test uses arbitrary UIDs without creating actual users, testing
+ * only the kernel ACL implementation.
+ *
+ * [Algorithm]
+ *
+ * Test 1 - chmod interaction:
+ * 1. Create file with read-only ACL entries
+ * 2. Use chmod to set permissions to 0777
+ * 3. Verify ACL entries are updated to rwx for user, group, and other
+ *
+ * Test 2 - chown interaction:
+ * 1. Create file with specific ACL entries (rw for user, r for group/other)
+ * 2. Use chown to change owner and group
+ * 3. Verify ownership changed correctly
+ * 4. Verify ACL entries preserved their permissions after chown
+ */
+
+#include "acl_lib.h"
+
+#define TEST_UID 1000
+#define TEST_GID 1000
+#define USER2_UID 2000
+#define USER2_GID 2000
+
+/*
+ * Test chmod interaction with ACLs.
+ * chmod should update ACL_USER_OBJ, ACL_GROUP_OBJ, and ACL_OTHER.
+ */
+static void test_chmod_acl(void)
+{
+ struct acl *acl = NULL;
+ int fd = -1;
+ int user_ok = 0, group_ok = 0, other_ok = 0;
+
+ tst_res(TINFO, "Testing chmod interaction with ACLs");
+ reset_test_path();
+
+ fd = SAFE_OPEN(TESTFILE, O_CREAT | O_WRONLY, 0644);
+ SAFE_CLOSE(fd);
+
+ acl = acl_init();
+ if (!acl)
+ goto cleanup;
+
+ if (acl_add_entry(acl, ACL_USER_OBJ, ACL_READ, 0) < 0)
+ goto cleanup_acl;
+
+ if (acl_add_entry(acl, ACL_GROUP_OBJ, ACL_READ, 0) < 0)
+ goto cleanup_acl;
+
+ if (acl_add_entry(acl, ACL_OTHER, ACL_READ, 0) < 0)
+ goto cleanup_acl;
+
+ if (acl_set_file(TESTFILE, ACL_TYPE_ACCESS, acl) < 0) {
+ if (errno == EOPNOTSUPP) {
+ acl_free(acl);
+ cleanup_testfile();
+ tst_brk(TCONF | TERRNO, "ACL not supported");
+ }
+ goto cleanup_acl;
+ }
+
+ acl_free(acl);
+ acl = NULL;
+
+ SAFE_CHMOD(TESTFILE, 0777);
+
+ acl = acl_get_file(TESTFILE, ACL_TYPE_ACCESS);
+ if (!acl)
+ goto cleanup;
+
+ /* Check if all entries have rwx permissions */
+ for (int i = 0; i < acl->count; i++) {
+ struct acl_entry *entry = &acl->entries[i];
+
+ if (entry->tag == ACL_USER_OBJ) {
+ if (acl_entry_has_rwx(entry))
+ user_ok = 1;
+ } else if (entry->tag == ACL_GROUP_OBJ) {
+ if (acl_entry_has_rwx(entry))
+ group_ok = 1;
+ } else if (entry->tag == ACL_OTHER) {
+ if (acl_entry_has_rwx(entry))
+ other_ok = 1;
+ }
+ }
+
+ acl_free(acl);
+ cleanup_testfile();
+
+ if (user_ok && group_ok && other_ok)
+ tst_res(TPASS, "chmod correctly updated ACL entries");
+ else
+ tst_res(TFAIL, "chmod did not update ACL entries correctly");
+ return;
+
+cleanup_acl:
+ acl_free(acl);
+cleanup:
+ cleanup_testfile();
+ tst_brk(TBROK | TERRNO, "ACL setup failed");
+}
+
+/*
+ * Test chown interaction with ACLs.
+ * chown should change file owner and group without affecting ACL entries.
+ */
+static void test_chown_acl(void)
+{
+ struct acl *acl;
+ struct stat st;
+ int fd = -1;
+ int found_user_obj = 0, found_group_obj = 0, found_other = 0;
+
+ tst_res(TINFO, "Testing chown interaction with ACLs");
+ reset_test_path();
+
+ fd = SAFE_OPEN(TESTFILE, O_CREAT | O_WRONLY, 0644);
+ SAFE_CLOSE(fd);
+
+ acl = acl_init();
+ if (!acl) {
+ cleanup_testfile();
+ tst_brk(TBROK | TERRNO, "acl_init failed");
+ }
+
+ if (acl_add_entry(acl, ACL_USER_OBJ, ACL_READ | ACL_WRITE, 0) < 0) {
+ acl_free(acl);
+ cleanup_testfile();
+ tst_brk(TBROK | TERRNO, "acl_add_entry failed");
+ }
+
+ if (acl_add_entry(acl, ACL_GROUP_OBJ, ACL_READ, 0) < 0) {
+ acl_free(acl);
+ cleanup_testfile();
+ tst_brk(TBROK | TERRNO, "acl_add_entry failed");
+ }
+
+ if (acl_add_entry(acl, ACL_OTHER, ACL_READ, 0) < 0) {
+ acl_free(acl);
+ cleanup_testfile();
+ tst_brk(TBROK | TERRNO, "acl_add_entry failed");
+ }
+
+ if (acl_set_file(TESTFILE, ACL_TYPE_ACCESS, acl) < 0) {
+ acl_free(acl);
+ cleanup_testfile();
+ if (errno == EOPNOTSUPP)
+ tst_brk(TCONF | TERRNO, "ACL not supported");
+ tst_brk(TBROK | TERRNO, "acl_set_file failed");
+ }
+
+ acl_free(acl);
+
+ SAFE_CHOWN(TESTFILE, USER2_UID, USER2_GID);
+
+ SAFE_STAT(TESTFILE, &st);
+
+ if (st.st_uid != USER2_UID || st.st_gid != USER2_GID) {
+ cleanup_testfile();
+ tst_res(TFAIL, "chown did not change owner/group correctly");
+ return;
+ }
+
+ /* Verify ACL entries are preserved after chown */
+ acl = acl_get_file(TESTFILE, ACL_TYPE_ACCESS);
+ if (!acl) {
+ cleanup_testfile();
+ tst_brk(TBROK | TERRNO, "acl_get_file failed");
+ }
+
+ for (int i = 0; i < acl->count; i++) {
+ struct acl_entry *entry = &acl->entries[i];
+
+ if (entry->tag == ACL_USER_OBJ) {
+ found_user_obj = 1;
+ if (!acl_entry_has_perm(entry, ACL_READ) ||
+ !acl_entry_has_perm(entry, ACL_WRITE)) {
+ acl_free(acl);
+ cleanup_testfile();
+ tst_res(TFAIL,
+ "ACL_USER_OBJ perms changed after chown");
+ return;
+ }
+ } else if (entry->tag == ACL_GROUP_OBJ) {
+ found_group_obj = 1;
+ if (!acl_entry_has_perm(entry, ACL_READ) ||
+ acl_entry_has_perm(entry, ACL_WRITE)) {
+ acl_free(acl);
+ cleanup_testfile();
+ tst_res(TFAIL,
+ "ACL_GROUP_OBJ perms changed after chown");
+ return;
+ }
+ } else if (entry->tag == ACL_OTHER) {
+ found_other = 1;
+ if (!acl_entry_has_perm(entry, ACL_READ) ||
+ acl_entry_has_perm(entry, ACL_WRITE)) {
+ acl_free(acl);
+ cleanup_testfile();
+ tst_res(TFAIL,
+ "ACL_OTHER perms changed after chown");
+ return;
+ }
+ }
+ }
+
+ acl_free(acl);
+
+ if (!found_user_obj || !found_group_obj || !found_other) {
+ cleanup_testfile();
+ tst_res(TFAIL, "ACL entries missing after chown");
+ return;
+ }
+
+ cleanup_testfile();
+ tst_res(TPASS, "chown preserved ACL entries correctly");
+}
+
+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_chmod_acl();
+ break;
+ case 1:
+ test_chown_acl();
+ 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 = "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
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [LTP] [PATCH v5 6/8] fs/acl: Add symlink ACL operations test
2026-06-08 9:21 [LTP] [PATCH v5 0/8] Convert shell-based ACL test (tacl_xattr.sh) to C Sachin Sant
` (4 preceding siblings ...)
2026-06-08 9:21 ` [LTP] [PATCH v3 5/8] fs/acl: Add chmod/chown ACL interaction tests Sachin Sant
@ 2026-06-08 9:21 ` Sachin Sant
2026-06-08 9:21 ` [LTP] [PATCH v4 7/8] fs/acl: Add extended attributes test Sachin Sant
2026-06-08 9:22 ` [LTP] [PATCH v1 8/8] fs/acl: Remove old shell-based ACL test Sachin Sant
7 siblings, 0 replies; 13+ messages in thread
From: Sachin Sant @ 2026-06-08 9:21 UTC (permalink / raw)
To: ltp
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 <chrubis@suse.cz>
Signed-off-by: Sachin Sant <sachinp@linux.ibm.com>
---
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@linux.ibm.com/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@linux.ibm.com/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@linux.ibm.com/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@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/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 (ltcd3@cn.ibm.com)
+ * Converted to C by Sachin Sant <sachinp@linux.ibm.com>
+ */
+
+/*\
+ * 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
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [LTP] [PATCH v4 7/8] fs/acl: Add extended attributes test
2026-06-08 9:21 [LTP] [PATCH v5 0/8] Convert shell-based ACL test (tacl_xattr.sh) to C Sachin Sant
` (5 preceding siblings ...)
2026-06-08 9:21 ` [LTP] [PATCH v5 6/8] fs/acl: Add symlink ACL operations test Sachin Sant
@ 2026-06-08 9:21 ` Sachin Sant
2026-06-08 9:22 ` [LTP] [PATCH v1 8/8] fs/acl: Remove old shell-based ACL test Sachin Sant
7 siblings, 0 replies; 13+ messages in thread
From: Sachin Sant @ 2026-06-08 9:21 UTC (permalink / raw)
To: ltp
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
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [LTP] [PATCH v1 8/8] fs/acl: Remove old shell-based ACL test
2026-06-08 9:21 [LTP] [PATCH v5 0/8] Convert shell-based ACL test (tacl_xattr.sh) to C Sachin Sant
` (6 preceding siblings ...)
2026-06-08 9:21 ` [LTP] [PATCH v4 7/8] fs/acl: Add extended attributes test Sachin Sant
@ 2026-06-08 9:22 ` Sachin Sant
7 siblings, 0 replies; 13+ messages in thread
From: Sachin Sant @ 2026-06-08 9:22 UTC (permalink / raw)
To: ltp
Remove tacl_xattr.sh, the old shell-based ACL and extended attribute
test script. This test has been replaced by the new modular C-based
test suite:
- acl_user_obj01: ACL_USER_OBJ permissions
- acl_mask01: ACL mask interactions
- acl_other01: ACL_OTHER permissions
- acl_inherit01: Default ACL inheritance
- acl_file_ops01: chmod/chown interactions
- acl_link01: Symlink ACL operations
- xattr_test01: Extended attributes
Signed-off-by: Sachin Sant <sachinp@linux.ibm.com>
---
V1 changes:
- Updated commit message to reflect correct number of
test scenario in xattr_test01
- rfc link https://lore.kernel.org/ltp/477836fd-80c8-4168-bfe6-00b374bb2534@linux.ibm.com/T/#t
---
testcases/kernel/fs/acl/tacl_xattr.sh | 807 --------------------------
1 file changed, 807 deletions(-)
delete mode 100755 testcases/kernel/fs/acl/tacl_xattr.sh
diff --git a/testcases/kernel/fs/acl/tacl_xattr.sh b/testcases/kernel/fs/acl/tacl_xattr.sh
deleted file mode 100755
index c2383fdd9..000000000
--- a/testcases/kernel/fs/acl/tacl_xattr.sh
+++ /dev/null
@@ -1,807 +0,0 @@
-#!/bin/bash
-##############################################################
-#
-# Copyright (c) International Business Machines Corp., 2003
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
-# the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation,
-#
-# FILE : tacl_xattr.sh
-# USAGE : ./tacl_xattr.sh
-#
-# DESCRIPTION : A script that will test ACL and Extend Attribute on Linux system.
-# REQUIREMENTS:
-# 1) Kernel with loop device support
-# 2) A spare (scratch) disk partition of 100MB or larger.
-# 3) Kernel with ACL and Extend Attribute function support
-#
-# HISTORY :
-# 10/23/2003 Kai Zhao (ltcd3@cn.ibm.com)
-# 07/06/2004 Jacky Malcles enable ext3 & clean users home dir.
-#
-# CODE COVERAGE:
-# 76.3% - fs/posix_acl.c
-# 80.9% - xattr_acl.c
-# 73.0% - xattr.c
-#
-##############################################################
-
-CUR_PATH=""
-CONTENT=""
-RES=""
-USER_PERMISSION=""
-GROUP_PERMISSION=""
-OTHER_PERMISSION=""
-ITEM_OWNER=""
-ITEM_GROUP=""
-
-################################################################
-#
-# Make sure that uid=root is running this script.
-# Make sure that loop device is built into the kernel
-# Make sure that ACL(Access Control List) and Extended Attribute are
-# built into the kernel
-#
-################################################################
-
-if [ $UID != 0 ]
-then
- echo "FAILED: Must have root access to execute this script"
- exit 1
-fi
-
-#################################################################
-#
-# Prepare Ext2 file system for ACL and Extended Attribute test
-# Make some directory , file and symlink for the test
-# Add three users for the test
-#
-#################################################################
-
-if [ ! -e tacl ]
-then
- mkdir -m 777 tacl
-else
- echo "FAILED: Directory tacl are exist"
- exit 1
-fi
-
-dd if=/dev/zero of=tacl/blkext2 bs=1k count=10240
-chmod 777 tacl/blkext2
-
-losetup /dev/loop0 tacl/blkext2 >/dev/null 2>&1
-if [ $? != 0 ]
-then
- printf "\nFAILED: [ losetup ] Must have loop device support by kernel\n"
- printf "\t to execute this script\n"
- exit 1
-fi
-
-mount | grep ext2
-if [ $? != 0 ]
-then
- mkfs -t ext3 /dev/loop0
- mkdir -m 777 tacl/mount-ext2
- mount -t ext3 -o defaults,acl,user_xattr /dev/loop0 tacl/mount-ext2
- if [ $? != 0 ]
- then
- printf "\nFAILED: [ mount ] Make sure that ACL (Access Control List)\n"
- printf "\t and Extended Attribute are built into the kernel\n"
- printf "\t Can not mount ext2 file system with acl and user_xattr options\n"
- exit 1
- fi
-
-else
- mkfs -t ext2 /dev/loop0
- mkdir -m 777 tacl/mount-ext2
- mount -t ext2 -o defaults,acl,user_xattr /dev/loop0 tacl/mount-ext2
- if [ $? != 0 ]
- then
- printf "\nFAILED: [ mount ] Make sure that ACL (Access Control List)\n"
- printf "\t and Extended Attribute are built into the kernel\n"
- printf "\t Can not mount ext2 file system with acl and user_xattr options\n"
- exit 1
- fi
-fi
-
-chmod 777 tacl/mount-ext2
-
-useradd -d `pwd`/tacl/tacluser1 tacluser1
-useradd -d `pwd`/tacl/tacluser2 tacluser2
-useradd -d `pwd`/tacl/tacluser3 tacluser3
-useradd -d `pwd`/tacl/tacluser4 tacluser4
-
-if [ ! -e tacl/mount-ext2/shared ]
-then
- mkdir -p -m 777 tacl/mount-ext2/shared
-fi
-
-CUR_PATH=`pwd`
-
-su - tacluser1 << TACL_USER1
-
- mkdir $CUR_PATH/tacl/mount-ext2/shared/team1
- touch $CUR_PATH/tacl/mount-ext2/shared/team1/file1
-
- cd $CUR_PATH/tacl/mount-ext2/shared/team1
- ln -sf file1 symlinkfile1
- cd $CUR_PATH
-
- cd $CUR_PATH/tacl/mount-ext2/shared
- ln -sf team1 symlinkdir1
- cd $CUR_PATH
-
-TACL_USER1
-
-su - tacluser2 << TACL_USER2
-
- mkdir $CUR_PATH/tacl/mount-ext2/shared/team2
- touch $CUR_PATH/tacl/mount-ext2/shared/team2/file1
-
- cd $CUR_PATH/tacl/mount-ext2/shared/team2
- ln -sf file1 symlinkfile1
- cd $CUR_PATH
-
- cd $CUR_PATH/tacl/mount-ext2/shared
- ln -sf team2 symlinkdir2
- cd $CUR_PATH
-
-TACL_USER2
-
-#############################################################################################
-#
-# The permissions bit limit user's act
-# lrwxrwxrwx 1 tacluser1 tacluser1 5 Jun 23 13:39 symlinkdir1 -> team1
-# lrwxrwxrwx 1 tacluser2 tacluser2 5 Jun 23 13:39 symlinkdir2 -> team2
-# dr-x------ 2 tacluser1 tacluser1 1024 Jun 23 13:39 team1
-# drwxrwxr-x 2 tacluser2 tacluser2 1024 Jun 23 13:39 team2
-#
-#############################################################################################
-
-chmod 500 tacl/mount-ext2/shared/team1
-
-su - tacluser1 << TACL_USER1
-
- touch $CUR_PATH/tacl/mount-ext2/shared/team1/newfil1 2> /dev/null
- if [ -e $CUR_PATH/tacl/mount-ext2/shared/team1/newfile1 ]
- then
- printf "\nFAILED: [ touch ] Create file must be denied by file permission bits\n"
- printf "\t [ Physical Directory ]\n"
- else
- printf "\nSUCCESS: Create file denied by file permission bits [ Physical directory ]\n"
- fi
-
- touch $CUR_PATH/tacl/mount-ext2/shared/symlinkdir1/newfil2 2> /dev/null
- if [ -e $CUR_PATH/tacl/mount-ext2/shared/team1/newfile2 ]
- then
- printf "\nFAILED: [ touch ] Create file must be denied by file permission bits\n"
- printf "\t [ Symlink Directory ]\n"
- else
- printf "\nSUCCESS: Create file denied by file permission bits [ Symlink directory ]\n"
- fi
-
-TACL_USER1
-
-#################################################################
-#
-# ACL_USER_OBJ are a superset of the permissions specified
-# by the file permission bits.
-# The effective user ID of the process matches the user ID of
-# the file object owner.
-# Owner's act are based ACL_USER_OBJ
-#
-#################################################################
-
-setfacl -m u::rx tacl/mount-ext2/shared/team1
-su - tacluser1 << TACL_USER1
-
- cd $CUR_PATH/tacl/mount-ext2/shared/team1/ 2> /dev/null
- if [ $? != 0 ]
- then
- printf "\nFAILED: [ touch ] ACL_USER_OBJ entry already contains the owner execute\n"
- printf "\t permissions, but operation failed [ Physical Directory ]\n"
- else
- printf "\nSUCCESS: ACL_USER_OBJ entry contains the owner execute permissions,\n"
- printf "\t operation success [ Physical Directory ]\n"
- fi
-
- cd $CUR_PATH/tacl/mount-ext2/shared/symlinkdir1/ 2> /dev/null
- if [ $? != 0 ]
- then
- printf "\nFAILED: [ touch ] ACL_USER_OBJ entry already contains the owner execute\n"
- printf "\t permissions, but operation failed [ Symlink Directory ]\n"
- else
- printf "\nSUCCESS: ACL_USER_OBJ entry contains the owner execute permissions,\n"
- printf "\t operation success [ Symlink Directory ]\n"
- fi
-
-TACL_USER1
-
-setfacl -m u::rwx tacl/mount-ext2/shared/team1
-
-su - tacluser1 << TACL_USER1
-
- touch $CUR_PATH/tacl/mount-ext2/shared/team1/newfil1 2> /dev/null
- if [ -e $CUR_PATH/tacl/mount-ext2/shared/team1/newfile1 ]
- then
- printf "\nFAILED: [ touch ] ACL_USER_OBJ entry already contains the owner write \n"
- printf "\t permissions, but operation failed [ Physical Directory ]\n"
- else
- printf "\nSUCCESS: ACL_USER_OBJ entry contains the owner write permissions,\n"
- printf "\t operation success [ Physical Directory ]\n"
- fi
-
- touch $CUR_PATH/tacl/mount-ext2/shared/symlinkdir1/newfil2 2> /dev/null
- if [ -e $CUR_PATH/tacl/mount-ext2/shared/team1/newfile2 ]
- then
- printf "\nFAILED: [ touch ] ACL_USER_OBJ entry already contains the owner write \n"
- printf "\t permissions, but operation failed [ Symlink Directory ]\n"
- else
- printf "\nSUCCESS: ACL_USER_OBJ entry contains the owner write permissions,\n"
- printf "\t operation success [ Symlink Directory ]\n"
- fi
-
-TACL_USER1
-
-#################################################################
-#
-# The effective user ID of the process matches the qualifier of
-# any entry of type ACL_USER
-# IF the matching ACL_USER entry and the ACL_MASK
-# entry contain the requested permissions,# access is granted,
-# ELSE access is denied.
-#
-#################################################################
-
-setfacl -m u:tacluser3:rwx tacl/mount-ext2/shared/team1
-
-su - tacluser3 << TACL_USER3
-
- touch $CUR_PATH/tacl/mount-ext2/shared/team1/newfile3 2> /dev/null
- if [ -e $CUR_PATH/tacl/mount-ext2/shared/team1/newfile3 ]
- then
- printf "\nSUCCESS: ACL_USER entry contains the user permissions,\n"
- printf "\t operation success [ Physical Directory ]\n"
- else
- printf "\nFAILED: ACL_USER entry contains the user permissions,\n"
- printf "\t but operation denied [ Physical Directory ]\n"
- fi
-
- touch $CUR_PATH/tacl/mount-ext2/shared/symlinkdir1/newfile4 2> /dev/null
- if [ -e $CUR_PATH/tacl/mount-ext2/shared/symlinkdir1/newfile4 ]
- then
- printf "\nSUCCESS: ACL_USER entry contains the user permissions,\n"
- printf "\t operation success [ Symlink Directory ]\n"
- else
- printf "\nFAILED: ACL_USER entry contains the user permissions,\n"
- printf "\t but operation denied [ Symlink Directory ]\n"
- fi
-
-TACL_USER3
-
-setfacl -m mask:--- tacl/mount-ext2/shared/team1
-
-su - tacluser3 << TACL_USER3
-
- touch $CUR_PATH/tacl/mount-ext2/shared/team1/newfile5 2> /dev/null
- if [ -e $CUR_PATH/tacl/mount-ext2/shared/team1/newfile5 ]
- then
- printf "\nFAILED: [ touch ] ACL_USER entry contains the user permissions\n"
- printf "\t but ACL_MASK are set --- ,\n"
- printf "\t operation must be denied [ Physical Directory ]\n"
- else
- printf "\nSUCCESS: ACL_USER entry contains the user permissions,\n"
- printf "\t but ACL_MASK are set ___ ,\n"
- printf "\t operation success [ Physical Directory ]\n"
- fi
-
- touch $CUR_PATH/tacl/mount-ext2/shared/symlinkdir1/newfile6 2> /dev/null
- if [ -e $CUR_PATH/tacl/mount-ext2/shared/symlinkdir1/newfile6 ]
- then
- printf "\nFAILED: [ touch ] ACL_USER entry contains the user permissions\n"
- printf "\t but ACL_MASK are set --- ,\n"
- printf "\t operation must be denied [ Symlink Directory ]\n"
- else
- printf "\nSUCCESS: ACL_USER entry contains the user permissions,\n"
- printf "\t but ACL_MASK are set ___ ,\n"
- printf "\t operation success [ Symlink Directory ]\n"
- fi
-
-TACL_USER3
-
-###########################################################################################
-#
-# The effective group ID or any of the supplementary group IDs of the process match the
-# qualifier of the entry of type ACL_GROUP_OBJ, or the qualifier of any entry of type
-# ACL_GROUP
-#
-# IF the ACL contains an ACL_MASK entry, THEN
-# if the ACL_MASK entry and any of the matching ACL_GROUP_OBJ
-# or ACL_GROUP entries contain the requested permissions,
-# access is granted,
-#
-# else access is denied.
-#
-# ELSE (note that there can be no ACL_GROUP entries without an ACL_MASK entry)
-# if the ACL_GROUP_OBJ entry contains the requested permis-
-# sions, access is granted,
-#
-# else access is denied.
-#
-###########################################################################################
-
-setfacl -m g:tacluser2:rwx tacl/mount-ext2/shared/team1
-
-su - tacluser2 << TACL_USER2
- touch $CUR_PATH/tacl/mount-ext2/shared/team1/newfile7 2> /dev/null
- if [ -e $CUR_PATH/tacl/mount-ext2/shared/team1/newfile7 ]
- then
- printf "\nSUCCESS: ACL_GROUP entry contains the group permissions,\n"
- printf "\t option success [ Physical Directory ]\n"
- else
- printf "\nFAILED: [ touch ] ACL_GROUP entry already contains the group permissions,\n"
- printf "\t but option success [ Physical Directory ]\n"
- fi
-
- touch $CUR_PATH/tacl/mount-ext2/shared/symlinkdir1/newfile8 2> /dev/null
- if [ -e $CUR_PATH/tacl/mount-ext2/shared/symlinkdir1/newfile8 ]
- then
- printf "\nSUCCESS: ACL_GROUP entry contains the group permissions,\n"
- printf "\t option success [ Symlink Directory ]\n"
- else
- printf "\nFAILED: [ touch ] ACL_GROUP entry already contains the group permissions,\n"
- printf "\t but option success [ Symlink Directory ]\n"
- fi
-
-TACL_USER2
-
-setfacl -m mask:--- tacl/mount-ext2/shared/team1
-
-su - tacluser2 << TACL_USER2
- touch $CUR_PATH/tacl/mount-ext2/shared/team1/newfile9 2> /dev/null
- if [ -e $CUR_PATH/tacl/mount-ext2/shared/team1/newfile9 ]
- then
- printf "\nFAILED: [ touch ] ACL_GROUP entry contains the group permissions\n"
- printf "\t and ACL_MASK entry are set ---,\n"
- printf "\t option must no be success [ Physical Directory ]\n"
- else
- printf "\nSUCCESS: ACL_GROUP entry already contains the group permissions\n"
- printf "\t and ACL_MASK entry are set ---,\n"
- printf "\t option success [ Physical Directory ]\n"
- fi
-
- touch $CUR_PATH/tacl/mount-ext2/shared/symlinkdir1/newfile10 2> /dev/null
- if [ -e $CUR_PATH/tacl/mount-ext2/shared/symlinkdir1/newfile10 ]
- then
- printf "\nFAILED: [ touch ] ACL_GROUP entry contains the group permissions\n"
- printf "\t and ACL_MASK entry are set ---,\n"
- printf "\t option must no be success [ Symlink Directory ]\n"
- else
- printf "\nSUCCESS: ACL_GROUP entry already contains the group permissions\n"
- printf "\t and ACL_MASK entry are set ---,\n"
- printf "\t option success [ Symlink Directory ]\n"
- fi
-
-TACL_USER2
-
-setfacl -m g::rwx tacl/mount-ext2/shared/team1
-usermod -g tacluser1 tacluser2
-
-su - tacluser2 << TACL_USER2
-
- touch $CUR_PATH/tacl/mount-ext2/shared/team1/newfile11 2> /dev/null
- if [ -e $CUR_PATH/tacl/mount-ext2/shared/team1/newfile11 ]
- then
- printf "\nSUCCESS: ACL_GROUP_OBJ entry contains the group owner permissions,\n"
- printf "\t option success [ Physical Directory ]\n"
- else
- printf "\nFAILED: [ touch ] ACL_GROUP_OBJ entry already contains the group owner,\n"
- printf "\t but option denied [ Physical Directory ]\n"
- fi
-
- touch $CUR_PATH/tacl/mount-ext2/shared/symlinkdir1/newfile12 2> /dev/null
- if [ -e $CUR_PATH/tacl/mount-ext2/shared/symlinkdir1/newfile12 ]
- then
- printf "\nSUCCESS: ACL_GROUP_OBJ entry contains the group owner permissions,\n"
- printf "\t option success [ Symlink Directory ]\n"
- else
- printf "\nFAILED: [ touch ] ACL_GROUP_OBJ entry already contains the group owner,\n"
- printf "\t but option denied [ Symlink Directory ]\n"
- fi
-
-TACL_USER2
-
-setfacl -m mask:--- tacl/mount-ext2/shared/team1
-
-su - tacluser2 << TACL_USER2
- touch $CUR_PATH/tacl/mount-ext2/shared/team1/newfile13 2> /dev/null
- if [ -e $CUR_PATH/tacl/mount-ext2/shared/team1/newfile13 ]
- then
- printf "\nFAILED: [ touch ] ACL_GROUP_OBJ entry contains the group owner permissions\n"
- printf "\t and ACL_MASK entry are set ---,\n"
- printf "\t option must no be success [ Physical Directory ]\n"
- else
- printf "\nSUCCESS: ACL_GROUP_OBJ entry already contains the group owner permissions\n"
- printf "\t and ACL_MASK entry are set ---,\n"
- printf "\t option success [ Physical Directory ]\n"
- fi
-
- touch $CUR_PATH/tacl/mount-ext2/shared/symlinkdir1/newfile14 2> /dev/null
- if [ -e $CUR_PATH/tacl/mount-ext2/shared/symlinkdir1/newfile14 ]
- then
- printf "\nFAILED: [ touch ] ACL_GROUP_OBJ entry contains the group owner permissions\n"
- printf "\t and ACL_MASK entry are set ---,\n"
- printf "\t option must no be success [ Symlink Directory ]\n"
- else
- printf "\nSUCCESS: ACL_GROUP_OBJ entry already contains the group owner permissions\n"
- printf "\t and ACL_MASK entry are set ---,\n"
- printf "\t option success [ Symlink Directory ]\n"
- fi
-
-TACL_USER2
-
-usermod -g tacluser2 tacluser2
-
-###################################################################################
-#
-# IF the ACL_OTHER entry contains the requested permissions, access is granted
-#
-###################################################################################
-
-setfacl -m o::rwx tacl/mount-ext2/shared/team1
-
-su - tacluser4 << TACL_USER4
-
- touch $CUR_PATH/tacl/mount-ext2/shared/team1/newfile15 2> /dev/null
- if [ -e $CUR_PATH/tacl/mount-ext2/shared/team1/newfile15 ]
- then
- printf "\nSUCCESS: ACL_OTHER entry contains the user permissions,\n"
- printf "\t operation success [ Physical Directory ]\n"
- else
- printf "\nFAILED: ACL_OTHER entry contains the user permissions,\n"
- printf "\t but operation denied [ Physical Directory ]\n"
- fi
-
- touch $CUR_PATH/tacl/mount-ext2/shared/symlinkdir1/newfile16 2> /dev/null
- if [ -e $CUR_PATH/tacl/mount-ext2/shared/symlinkdir1/newfile16 ]
- then
- printf "\nSUCCESS: ACL_OTHER entry contains the user permissions,\n"
- printf "\t operation success [ Symlink Directory ]\n"
- else
- printf "\nFAILED: ACL_OTHER entry contains the user permissions,\n"
- printf "\t but operation denied [ Symlink Directory ]\n"
- fi
-
-TACL_USER4
-
-setfacl -m mask:--- tacl/mount-ext2/shared/team1
-
-su - tacluser4 << TACL_USER4
-
- touch $CUR_PATH/tacl/mount-ext2/shared/team1/newfile17 2> /dev/null
- if [ -e $CUR_PATH/tacl/mount-ext2/shared/team1/newfile17 ]
- then
- printf "\nSUCCESS: [ touch ] ACL_OTHER do not strick by ACL_MASK [ Physical Directory ]\n"
- else
- printf "\nFAILED: ACL_OTHER do not strick by ACL_MASK [ Physical Directory ]\n"
- fi
-
- touch $CUR_PATH/tacl/mount-ext2/shared/symlinkdir1/newfile18 2> /dev/null
- if [ -e $CUR_PATH/tacl/mount-ext2/shared/symlinkdir1/newfile18 ]
- then
- printf "\nSUCCESS: [ touch ] ACL_OTHER do not strick by ACL_MASK [ Symlink Directory ]\n"
- else
- printf "\nFAILED: ACL_OTHER do not strick by ACL_MASK [ Symlink Directory ]\n"
- fi
-
-TACL_USER4
-
-############################################################################
-#
-# OBJECT CREATION AND DEFAULT ACLs
-# The new object inherits the default ACL of the containing directory as its access ACL.
-#
-############################################################################
-
-rm -f tacl/mount-ext2/shared/team1/newfil*
-
-#
-# Test ACL_USER_OBJ default ACLs
-#
-setfacl -m d:u::r -m d:g::r -m d:o::r tacl/mount-ext2/shared/team1
-
-su - tacluser1 << TACL_USER1
-
- MASK=`umask`
- umask 0
- touch $CUR_PATH/tacl/mount-ext2/shared/team1/newfile1
- umask $MASK > /dev/null
-
-TACL_USER1
-
-CONTENT=""
-CONTENT=`ls -l tacl/mount-ext2/shared/team1/newfile1`
-RES=`echo $CONTENT | grep ".r--r--r--" | awk '{print $1}'`
-
-if [ $RES != "" ]
-then
- printf "\nSUCCESS: With default ACLs set, new file permission set correct.\n"
-else
- printf "\nFAILED: With default ACLs set, new file permission set not correct\n"
-fi
-
-
-
-#
-# Test ACL_USER and ACL_GROUP defaults ACLs
-#
-setfacl -m d:u:tacluser3:rw -m d:g:tacluser3:rw tacl/mount-ext2/shared/team1
-su - tacluser3 << TACL_USER3
-
- MASK=`umask`
- umask 0
- touch $CUR_PATH/tacl/mount-ext2/shared/team1/newfile2
- umask $MASK > /dev/null
-
-TACL_USER3
-
-CONTENT=""
-CONTENT=`ls -l tacl/mount-ext2/shared/team1/newfile2`
-RES=`echo $CONTENT | grep ".r--rw-r--" | awk '{print $1}'`
-
-if [ $RES != "" ]
-then
- printf "\nSUCCESS: With default ACLs set, new file permission set correct.\n"
-else
- printf "\nFAILED: With default ACLs set, new file permission set not correct\n"
-fi
-
-#
-# Test ACL_GROUP default ACLs
-#
-
-setfacl -m d:u::rwx -m d:g::rwx -m d:o::rwx tacl/mount-ext2/shared/team1
-su - tacluser3 << TACL_USER3
-
- MASK=`umask`
- umask 0
- touch $CUR_PATH/tacl/mount-ext2/shared/team1/newfile3
- umask $MASK > /dev/null
-
-TACL_USER3
-
-CONTENT=""
-CONTENT=`ls -l tacl/mount-ext2/shared/team1/newfile3`
-RES=`echo $CONTENT | grep ".rw-rw-rw-" | awk '{print \$1}'`
-
-if [ $RES != "" ]
-then
- printf "\nSUCCESS: With default ACLs set, new file permission set correct.\n"
-else
- printf "\nFAILED: With default ACLs set, new file permission set not correct\n"
-fi
-
-
-#################################################################################
-#
-# Chmod also change ACL_USER_OBJ ACL_GROUP_OBJ and ACL_OTHER permissions
-#
-#################################################################################
-su - tacluser3 << TACL_USER3
- MASK=`umask`
- umask 0
-
- chmod 777 $CUR_PATH/tacl/mount-ext2/shared/team1/newfile3
- umask $MASK > /dev/null
-TACL_USER3
-
-CONTENT=""
-CONTENT=`getfacl tacl/mount-ext2/shared/team1/newfile3`
-
-USER_PERMISSION=`echo $CONTENT | awk '{print \$10}'`
-
-GROUP_PERMISSION=`echo $CONTENT | awk '{print \$12}'`
-OTHER_PERMISSION=`echo $CONTENT | awk '{print \$15}'`
-
-if [ $USER_PERMISSION = "user::rwx" ]
-then
- if [ $GROUP_PERMISSION = "group::rwx" ]
- then
- if [ $OTHER_PERMISSION = "other::rwx" ]
- then
- printf "\nSUCCESS: Chmod with ACL_USER_OBJ ACL_GROUP_OBJ and ACL_OTHER are correct\n"
- else
- printf "\nFAILED: Chmod with ACL_USER_OBJ ACL_GROUP_OBJ and ACL_OTHER are not correct\n"
- fi
- else
- printf "\nFAILED: Chmod with ACL_USER_OBJ ACL_GROUP_OBJ and ACL_OTHER are not correct\n"
- fi
-else
- printf "\nFAILED: Chmod with ACL_USER_OBJ ACL_GROUP_OBJ and ACL_OTHER are not correct\n"
-fi
-
-
-#####################################################################################
-#
-# Chown only change object owner and group
-#
-#####################################################################################
-
-chown tacluser2.tacluser2 tacl/mount-ext2/shared/team1/newfile2
-CONTENT=""
-CONTENT=`getfacl tacl/mount-ext2/shared/team1/newfile2`
-
-ITEM_OWNER=`echo $CONTENT | awk '{print \$6}'`
-ITEM_GROUP=`echo $CONTENT | awk '{print \$9}'`
-
-if [ $ITEM_OWNER = "tacluser2" ]
-then
- if [ $ITEM_GROUP = "tacluser2" ]
- then
- printf "\nSUCCESS: Chown correct\n"
- else
- printf "\nFAILED: Chown are not correct\n"
- fi
-else
- echo "FAILED: Chown are not correct"
-fi
-
-#####################################################
-#
-# Test ACLs backup and restore
-#
-#####################################################
-
-getfacl -RL tacl/mount-ext2/ > tacl/tmp1
-setfacl -m u::--- -m g::--- -m o::--- tacl/mount-ext2/shared/team1
-setfacl --restore tacl/tmp1
-getfacl -RL tacl/mount-ext2/ > tacl/tmp2
-
-if [ `diff tacl/tmp1 tacl/tmp2` ]
-then
- printf "\nFAILED: ACLs backup and restore are not correct\n"
-else
- printf "\nSUCCESS: ACLs backup and restore are correct\n"
-fi
-
-printf "\n\tEnd ACLs Test\n"
-
-#####################################################
-#
-# Now begin Extend Attribute test
-#
-#####################################################
-
-printf "\nNow begin Extend Attribute Test\n"
-
-# dir
-printf "\nAttach name:value pair to object dir\n\n"
-attr -s attrname1 -V attrvalue1 tacl/mount-ext2/shared/team2
-if [ $? != 0 ]
-then
- echo "FAILED: Attach name:value pair to object dir"
-fi
-
-#file
-echo
-echo "Attach name:value pair to object file "
-echo ""
-attr -s attrname2 -V attrvalue2 tacl/mount-ext2/shared/team2/file1
-if [ $? != 0 ]
-then
- echo "FAILED: Attach name:value pair to object file"
-fi
-
-#symlink file
-echo
-echo "Attach name:value pair to object symlink file"
-echo ""
-attr -s attrname3 -V attrvalue3 tacl/mount-ext2/shared/team2/symlinkfile1
-if [ $? != 0 ]
-then
- echo "INFO: Can't attach name:value pair to object symlink file"
-fi
-
-echo ""
-ls -lRt tacl/mount-ext2/shared/team2
-
-echo
-echo "get extended attributes of filesystem objects"
-echo ""
-
-echo "Dump the values"
-getfattr -d tacl/mount-ext2/shared/team2
-if [ $? != 0 ]
-then
- echo "FAILED: getfattr: Dump the values"
-fi
-
-echo "Recursively dump the values"
-getfattr -dR tacl/mount-ext2/*
-if [ $? != 0 ]
-then
- echo "FAILED: getfattr: Recursively Dump the values"
-fi
-
-echo "Do not follow symlinks."
-echo "but extended user attributes are disallowed for symbolic links"
-getfattr -h --no-dereference tacl/mount-ext2/shared/team2/symlinkfile1
-if [ $? != 0 ]
-then
- echo "FAILED: getfattr: Do not follow symlinks."
-fi
-echo
-
-echo "Logical walk, follow symbolic links"
-getfattr -L tacl/mount-ext2/shared/team2/*
-if [ $? != 0 ]
-then
- echo "FAILED: getfattr: Logical walk"
-fi
-
-echo "Physical walk, skip all symbolic links"
-getfattr -P tacl/mount-ext2/shared/team2/*
-if [ $? != 0 ]
-then
- echo "FAILED: getfattr: Physical walk"
-fi
-
-echo "attr -g to search the named object"
-attr -g attrname1 tacl/mount-ext2/shared/team2
-if [ $? != 0 ]
-then
- echo "FAILED: attr: to search the named object"
-fi
-echo
-
-echo "attr -r to remove the named object"
-attr -r attrname2 tacl/mount-ext2/shared/team2/file1
-if [ $? != 0 ]
-then
- echo "FAILED: attr: to remove the named object"
-fi
-
-
-#################################
-#
-# Backup and Restore
-#
-#################################
-getfattr -dhR -m- -e hex tacl/mount-ext2 > tacl/backup.ea
-setfattr -h --restore=tacl/backup.ea
-
-getfattr -dhR -m- -e hex tacl/mount-ext2 > tacl/backup.ea1
-if [ `diff tacl/backup.ea1 tacl/backup.ea` ]
-then
- printf "\nFAILED: EAs backup and restore are not correct\n"
-else
- printf "\nSUCCESS: EAs backup and restore are correct\n"
-fi
-
-printf "\n\tEnd EAs Test\n"
-
-
-
-#####################################################
-#
-# Clean up
-#
-#####################################################
-
-userdel tacluser1
-userdel tacluser2
-userdel tacluser3
-userdel tacluser4
-umount -d tacl/mount-ext2
-rm -rf tacl
--
2.39.1
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [LTP] fs/acl: Add ACL_USER_OBJ permission test
2026-06-08 9:21 ` [LTP] [PATCH v5 1/8] fs/acl: Add ACL_USER_OBJ permission test Sachin Sant
@ 2026-06-08 11:01 ` linuxtestproject.agent
2026-06-08 11:57 ` [LTP] [PATCH v5 1/8] " Cyril Hrubis
1 sibling, 0 replies; 13+ messages in thread
From: linuxtestproject.agent @ 2026-06-08 11:01 UTC (permalink / raw)
To: Sachin Sant; +Cc: ltp
Hi Sachin,
On Mon, 8 Jun 2026 14:51:53 +0530, Sachin Sant wrote:
> fs/acl: Add ACL_USER_OBJ permission test
--- [PATCH 1/8] ---
> +#include <sys/xattr.h>
acl_lib.h includes <sys/xattr.h> unconditionally. LTP's configure.ac
checks for this header and defines HAVE_SYS_XATTR_H. Other xattr tests
(e.g. setxattr01, setxattr02, setxattr03) guard their code with
#ifdef HAVE_SYS_XATTR_H.
Each .c test file should wrap all test code in a file-level guard with
TST_TEST_TCONF in the #else branch, e.g.:
#include "acl_lib.h"
#ifdef HAVE_SYS_XATTR_H
/* ... all test code ... */
static struct tst_test test = { ... };
#else
TST_TEST_TCONF("sys/xattr.h is not available");
#endif
Without this, the tests will fail to compile on systems where
sys/xattr.h is not available.
--- [PATCH 6/8] ---
> +static struct tst_test test = {
> + .test_all = run,
> + .setup = setup,
> + .cleanup = cleanup,
> + .needs_root = 1,
> + .mount_device = 1,
> + .mntpoint = MNTPOINT,
> + .forks_child = 1,
acl_link01 never forks a child process. It does not call SAFE_FORK(),
try_create_as(), or create_file_as(). Should .forks_child be dropped?
--- [PATCH 7/8] ---
> + 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;
> + }
TST_EXP_PASS_SILENT already reports TFAIL on failure. Then the code
reports TFAIL again (or TCONF for EOPNOTSUPP), resulting in
double-reporting. For the EOPNOTSUPP case the output will contain a
contradictory TFAIL followed by TCONF.
Since custom failure handling is needed here (EOPNOTSUPP -> TCONF),
TEST() should be used instead:
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;
}
The same pattern appears for the second setxattr call in test_xattr()
and the first setxattr call in test_xattr_backup_restore().
> + .forks_child = 1,
Same as patch 6/8: xattr_test01 never forks a child process.
Should .forks_child be dropped?
--- Applies to patches 2/8 through 7/8 ---
> + * [Algorithm]
> + *
> + * 1. Set up ACL with full permissions for the entry
The [Algorithm] section must use a bulleted list with "-" markers,
not numbered lists. This applies to all six tests that have an
[Algorithm] block (acl_mask01, acl_other01, acl_inherit01,
acl_file_ops01, acl_link01, xattr_test01).
Verdict: Needs revision
---
Note:
The agent can sometimes produce false positives although often its
findings are genuine. If you find issues with the review, please
comment this email or ignore the suggestions.
Regards,
LTP AI Reviewer
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [LTP] [PATCH v5 1/8] fs/acl: Add ACL_USER_OBJ permission test
2026-06-08 9:21 ` [LTP] [PATCH v5 1/8] fs/acl: Add ACL_USER_OBJ permission test Sachin Sant
2026-06-08 11:01 ` [LTP] " linuxtestproject.agent
@ 2026-06-08 11:57 ` Cyril Hrubis
2026-06-08 13:40 ` Sachin Sant
1 sibling, 1 reply; 13+ messages in thread
From: Cyril Hrubis @ 2026-06-08 11:57 UTC (permalink / raw)
To: Sachin Sant; +Cc: ltp
Hi!
> Add acl_user_obj01 test to validate ACL_USER_OBJ permissions:
> - Owner permissions correctly control file/directory access
> - ACL_USER_OBJ=rwx via setxattr() overrides chmod restrictions
> - Owner permissions work independently of group/other permissions
> - Tests use arbitrary UIDs without requiring actual user creation
>
> The patch also adds acl_lib.h containing shared helpers for ACL
> manipulation via xattr API, including:
> - ACL structure management (acl_init, acl_free, acl_add_entry)
> - ACL serialization/deserialization for kernel xattr format
> - ACL get/set operations using getxattr/setxattr
> - permission testing and file operations
> - Support for both ACCESS and DEFAULT ACL types
>
> The implementation uses direct xattr API (getxattr/setxattr) to
> test kernel ACL behavior directly. Tests run on ext2/3/4,
> XFS, and Btrfs filesystems with ACL support.
>
> Suggested-by: Cyril Hrubis <chrubis@suse.cz>
> Signed-off-by: Sachin Sant <sachinp@linux.ibm.com>
> ---
> 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@linux.ibm.com/T/#t
>
> V4 changes:
> - Add -U flag in create_user_if_needed() to useradd for guaranteed
> user-private groups.
> - Move EOPNOTSUPP handling into set_acl_file() helper
> - v3 link https://lore.kernel.org/ltp/20260603140147.50738-1-sachinp@linux.ibm.com/T/#t
>
> V3 changes:
> - Updated copyright header as per LTP format.
> - v2 link https://lore.kernel.org/ltp/20260603065744.47106-1-sachinp@linux.ibm.com/T/#t
>
> V2 changes:
> - Added reset_test_path_no_chown variant to skip chown step.
> acl_link01 and xattr_test01 tests are updated to use this
> variant.
> - Updated acl_user_obj01.c to correct incorrect description
> - v1 link https://lore.kernel.org/ltp/20260602121958.27494-1-sachinp@linux.ibm.com/T/#t
>
> V1 changes:
> - Use ACL_LIBS variable instead of hardcoded -lacl in Makefile
> - Move ACL header includes inside feature guards in acl_lib.h
> - 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 | 3 +
> testcases/kernel/fs/acl/.gitignore | 1 +
> testcases/kernel/fs/acl/Makefile | 8 +
> testcases/kernel/fs/acl/acl_lib.h | 483 +++++++++++++++++++++++
> testcases/kernel/fs/acl/acl_user_obj01.c | 154 ++++++++
> 5 files changed, 649 insertions(+)
> create mode 100644 testcases/kernel/fs/acl/.gitignore
> create mode 100644 testcases/kernel/fs/acl/Makefile
> create mode 100644 testcases/kernel/fs/acl/acl_lib.h
> create mode 100644 testcases/kernel/fs/acl/acl_user_obj01.c
>
> diff --git a/runtest/fs b/runtest/fs
> index 1d753e0dd..2a878744b 100644
> --- a/runtest/fs
> +++ b/runtest/fs
> @@ -87,3 +87,6 @@ binfmt_misc01 binfmt_misc01.sh
> binfmt_misc02 binfmt_misc02.sh
>
> squashfs01 squashfs01
> +
> +# Run the acl tests
> +acl_user_obj01 acl_user_obj01
> diff --git a/testcases/kernel/fs/acl/.gitignore b/testcases/kernel/fs/acl/.gitignore
> new file mode 100644
> index 000000000..d9c46db11
> --- /dev/null
> +++ b/testcases/kernel/fs/acl/.gitignore
> @@ -0,0 +1 @@
> +/acl_user_obj01
> diff --git a/testcases/kernel/fs/acl/Makefile b/testcases/kernel/fs/acl/Makefile
> new file mode 100644
> index 000000000..2d9cba46d
> --- /dev/null
> +++ b/testcases/kernel/fs/acl/Makefile
> @@ -0,0 +1,8 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +# Copyright (c) 2026 IBM
> +
> +top_srcdir ?= ../../../..
> +
> +include $(top_srcdir)/include/mk/testcases.mk
> +
> +include $(top_srcdir)/include/mk/generic_leaf_target.mk
> diff --git a/testcases/kernel/fs/acl/acl_lib.h b/testcases/kernel/fs/acl/acl_lib.h
> new file mode 100644
> index 000000000..717c9ff1e
> --- /dev/null
> +++ b/testcases/kernel/fs/acl/acl_lib.h
> @@ -0,0 +1,483 @@
> +/* 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>
> + *
> + * Common library for ACL and extended attribute tests using xattr API
> + */
> +
> +#ifndef ACL_LIB_H
> +#define ACL_LIB_H
> +
> +#include <pwd.h>
> +#include <grp.h>
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <string.h>
> +#include <stdint.h>
> +#include <endian.h>
> +#include <sys/stat.h>
> +#include <sys/types.h>
> +#include <sys/wait.h>
> +#include <sys/xattr.h>
> +
> +#include "config.h"
> +#include "tst_test.h"
> +#include "tst_safe_stdio.h"
> +
> +#define MNTPOINT "mntpoint"
> +#define TESTDIR MNTPOINT "/testdir"
> +#define TESTFILE TESTDIR "/testfile"
> +#define TESTSYMLINK TESTDIR "/testsymlink"
> +#define XATTR_BACKUP_FILE MNTPOINT "/xattr_backup.txt"
> +
> +/* Extended attribute test values */
> +#define XATTR_TEST_DIR_NAME "user.test_attr"
> +#define XATTR_TEST_DIR_VALUE "test_value"
> +#define XATTR_TEST_DIR_SIZE 10
> +#define XATTR_TEST_FILE_NAME "user.file_attr"
> +#define XATTR_TEST_FILE_VALUE "file_val"
> +#define XATTR_TEST_FILE_SIZE 8
> +#define XATTR_TEST1_NAME "user.test1"
> +#define XATTR_TEST1_VALUE "value1"
> +#define XATTR_TEST1_SIZE 6
> +#define XATTR_TEST2_NAME "user.test2"
> +#define XATTR_TEST2_VALUE "value2"
> +#define XATTR_TEST2_SIZE 6
> +
> +/*
> + * POSIX ACL xattr format definitions
> + * These match the kernel's internal representation
> + */
> +#define POSIX_ACL_XATTR_VERSION 0x0002
> +
> +/* ACL entry tag types */
> +#define ACL_UNDEFINED_TAG 0x00
> +#define ACL_USER_OBJ 0x01
> +#define ACL_USER 0x02
> +#define ACL_GROUP_OBJ 0x04
> +#define ACL_GROUP 0x08
> +#define ACL_MASK 0x10
> +#define ACL_OTHER 0x20
> +
> +/* ACL permissions */
> +#define ACL_READ 0x04
> +#define ACL_WRITE 0x02
> +#define ACL_EXECUTE 0x01
> +
> +/* ACL xattr names */
> +#define XATTR_NAME_POSIX_ACL_ACCESS "system.posix_acl_access"
> +#define XATTR_NAME_POSIX_ACL_DEFAULT "system.posix_acl_default"
> +
> +/* ACL type for set/get operations */
> +#define ACL_TYPE_ACCESS 1
> +#define ACL_TYPE_DEFAULT 2
> +
> +/* Convert host to little-endian */
> +#if __BYTE_ORDER == __LITTLE_ENDIAN
> +#define cpu_to_le16(x) (x)
> +#define cpu_to_le32(x) (x)
> +#define le16_to_cpu(x) (x)
> +#define le32_to_cpu(x) (x)
> +#else
> +#define cpu_to_le16(x) __builtin_bswap16(x)
> +#define cpu_to_le32(x) __builtin_bswap32(x)
> +#define le16_to_cpu(x) __builtin_bswap16(x)
> +#define le32_to_cpu(x) __builtin_bswap32(x)
> +#endif
> +
> +/*
> + * POSIX ACL xattr format as stored in kernel
> + * This is the on-disk/in-xattr representation
> + */
> +struct posix_acl_xattr_header {
> + uint32_t a_version;
> +};
> +
> +struct posix_acl_xattr_entry {
> + uint16_t e_tag;
> + uint16_t e_perm;
> + uint32_t e_id;
> +};
> +
> +/*
> + * In-memory ACL representation for building ACLs
> + */
> +#define MAX_ACL_ENTRIES 32
> +
> +struct acl_entry {
> + uint16_t tag;
> + uint16_t perm;
> + uint32_t id;
> +};
> +
> +struct acl {
> + int count;
> + struct acl_entry entries[MAX_ACL_ENTRIES];
> +};
> +
> +/* Helper functions */
> +static inline void reset_test_path_no_chown(void)
> +{
> + if (unlink(TESTSYMLINK) == -1 && errno != ENOENT)
> + tst_res(TWARN | TERRNO, "unlink(%s) failed", TESTSYMLINK);
> +
> + if (unlink(TESTFILE) == -1 && errno != ENOENT)
> + tst_res(TWARN | TERRNO, "unlink(%s) failed", TESTFILE);
> +
> + if (rmdir(TESTDIR) == -1 && errno != ENOENT)
> + tst_res(TWARN | TERRNO, "rmdir(%s) failed", TESTDIR);
> +
> + SAFE_MKDIR(TESTDIR, 0755);
> +}
> +
> +static inline void reset_test_path(void)
> +{
> + reset_test_path_no_chown();
> +}
> +
> +static inline void cleanup_testfile(void)
> +{
> + if (unlink(TESTFILE) == -1 && errno != ENOENT)
> + tst_res(TWARN | TERRNO, "unlink(%s) failed", TESTFILE);
> +}
> +
> +/*
> + * Initialize an empty ACL structure
> + */
> +static inline struct acl *acl_init(void)
> +{
> + struct acl *acl = malloc(sizeof(struct acl));
> +
> + if (!acl)
> + return NULL;
> +
> + acl->count = 0;
> + return acl;
> +}
> +
> +/*
> + * Free an ACL structure
> + */
> +static inline void acl_free(struct acl *acl)
> +{
> + free(acl);
> +}
> +
> +/*
> + * Add an ACL entry to the ACL structure
> + */
> +static inline int acl_add_entry(struct acl *acl, uint16_t tag, uint16_t perm,
> + uint32_t id)
> +{
> + if (acl->count >= MAX_ACL_ENTRIES) {
> + errno = ENOMEM;
> + return -1;
> + }
> +
> + acl->entries[acl->count].tag = tag;
> + acl->entries[acl->count].perm = perm;
> + acl->entries[acl->count].id = id;
> + acl->count++;
> +
> + return 0;
> +}
> +
> +/*
> + * Set ACL on a file using xattr.
> + *
> + * The kernel stores access ACLs only when they differ from the file mode.
> + * If the ACL is equivalent to st_mode, the xattr is removed and future
> + * getxattr() calls return ENODATA. Mirror libacl semantics by treating
> + * ENODATA as a valid minimal ACL derived from st_mode.
> + */
> +static inline int acl_set_file(const char *path, int type, struct acl *acl)
> +{
> + const char *xattr_name;
> + size_t size;
> + char *buf;
> + struct posix_acl_xattr_header *header;
> + struct posix_acl_xattr_entry *entries;
> + int i, ret;
> +
> + if (type == ACL_TYPE_ACCESS)
> + xattr_name = XATTR_NAME_POSIX_ACL_ACCESS;
> + else if (type == ACL_TYPE_DEFAULT)
> + xattr_name = XATTR_NAME_POSIX_ACL_DEFAULT;
> + else {
> + errno = EINVAL;
> + return -1;
> + }
> +
> + size = sizeof(struct posix_acl_xattr_header) +
> + acl->count * sizeof(struct posix_acl_xattr_entry);
> +
> + buf = malloc(size);
> + if (!buf)
> + return -1;
> +
> + header = (struct posix_acl_xattr_header *)buf;
> + header->a_version = cpu_to_le32(POSIX_ACL_XATTR_VERSION);
> +
> + entries = (struct posix_acl_xattr_entry *)(buf + sizeof(*header));
> +
> + for (i = 0; i < acl->count; i++) {
> + entries[i].e_tag = cpu_to_le16(acl->entries[i].tag);
> + entries[i].e_perm = cpu_to_le16(acl->entries[i].perm);
> + entries[i].e_id = cpu_to_le32(acl->entries[i].id);
> + }
> +
> + ret = setxattr(path, xattr_name, buf, size, 0);
> + free(buf);
> +
> + return ret;
> +}
> +
> +static inline int acl_add_mode_entries(struct acl *acl, mode_t mode)
> +{
> + if (acl_add_entry(acl, ACL_USER_OBJ, (mode >> 6) & 07, 0) < 0)
> + return -1;
> +
> + if (acl_add_entry(acl, ACL_GROUP_OBJ, (mode >> 3) & 07, 0) < 0)
> + return -1;
> +
> + if (acl_add_entry(acl, ACL_OTHER, mode & 07, 0) < 0)
> + return -1;
> +
> + return 0;
> +}
> +
> +/*
> + * Synthesize an ACL from file mode bits.
> + * Used when no xattr exists for an access ACL.
> + */
> +static inline struct acl *acl_from_mode(const char *path)
> +{
> + struct acl *acl;
> + struct stat st;
> +
> + if (stat(path, &st) < 0)
> + return NULL;
> +
> + acl = acl_init();
> + if (!acl)
> + return NULL;
> +
> + if (acl_add_mode_entries(acl, st.st_mode) < 0) {
> + acl_free(acl);
> + return NULL;
> + }
> +
> + return acl;
> +}
> +
> +/*
> + * Get ACL from a file using xattr.
> + *
> + * Access ACLs equivalent to file mode may not have a backing xattr at all.
> + * In that case synthesize the base ACL from st_mode so callers observe the
> + * same behavior as acl_get_file(3).
> + */
> +static inline struct acl *acl_get_file(const char *path, int type)
> +{
> + const char *xattr_name;
> + ssize_t size;
> + char *buf;
> + struct posix_acl_xattr_header *header;
> + struct posix_acl_xattr_entry *entries;
> + struct acl *acl;
> + int i, count;
> +
> + if (type == ACL_TYPE_ACCESS)
> + xattr_name = XATTR_NAME_POSIX_ACL_ACCESS;
> + else if (type == ACL_TYPE_DEFAULT)
> + xattr_name = XATTR_NAME_POSIX_ACL_DEFAULT;
> + else {
> + errno = EINVAL;
> + return NULL;
> + }
We are using this in two places, maybe it's worth having an
acl_type_to_name() functions and use that instead.
> + size = getxattr(path, xattr_name, NULL, 0);
> + if (size < 0) {
> + if (errno != ENODATA || type != ACL_TYPE_ACCESS)
> + return NULL;
> +
> + return acl_from_mode(path);
> + }
> +
> + /* Handle race: xattr removed between size check and actual read */
> + if (size == 0)
> + return acl_from_mode(path);
> +
> + buf = malloc(size);
> + if (!buf)
> + return NULL;
> +
> + size = getxattr(path, xattr_name, buf, size);
> + if (size < 0) {
> + free(buf);
> + /* Handle race: xattr removed between size check and read */
> + if (errno == ENODATA && type == ACL_TYPE_ACCESS)
> + return acl_from_mode(path);
> + return NULL;
> + }
> +
> + header = (struct posix_acl_xattr_header *)buf;
> + if (le32_to_cpu(header->a_version) != POSIX_ACL_XATTR_VERSION) {
> + free(buf);
> + errno = EINVAL;
> + return NULL;
> + }
> +
> + count = (size - sizeof(*header)) / sizeof(struct posix_acl_xattr_entry);
> + entries = (struct posix_acl_xattr_entry *)(buf + sizeof(*header));
Maybe we can define a structure with both header and entries in order to
avoid some of the casts here. Maybe this would work:
struct acl_xattr {
struct posix_acl_xattr_header header;
struct posix_acl_xattr_entry entries[];
};
And then we could do:
struct acl_xattr *ax = (void*)buf;
if (le32_to_cpu(ax->header.a_version) == ACL_TYPE_ACCESS) {
...
}
...
for (i = 0; i < conut; i++) {
uint16_t tag = le16_to_cpu(ax->entries[i].e_tag);
...
}
> + acl = acl_init();
> + if (!acl) {
> + free(buf);
> + return NULL;
> + }
> +
> + for (i = 0; i < count; i++) {
> + uint16_t tag = le16_to_cpu(entries[i].e_tag);
> + uint16_t perm = le16_to_cpu(entries[i].e_perm);
> + uint32_t id = le32_to_cpu(entries[i].e_id);
> +
> + if (acl_add_entry(acl, tag, perm, id) < 0) {
> + acl_free(acl);
> + free(buf);
> + return NULL;
> + }
> + }
> +
> + free(buf);
> + return acl;
> +}
> +
> +/*
> + * Check if an ACL entry has a specific permission
> + */
> +static inline int acl_entry_has_perm(struct acl_entry *entry, uint16_t perm)
> +{
> + return (entry->perm & perm) == perm;
> +}
> +
> +/*
> + * Check if an ACL entry has all rwx permissions
> + */
> +static inline int acl_entry_has_rwx(struct acl_entry *entry)
> +{
> + return acl_entry_has_perm(entry, ACL_READ) &&
> + acl_entry_has_perm(entry, ACL_WRITE) &&
> + acl_entry_has_perm(entry, ACL_EXECUTE);
Can't we pass all the bits to a single call of the function like
acl_entry_has_perm(entry, ACL_READ | ACL_WRITE | ACL_EXECUTE) ?
> +}
> +
> +/*
> + * Find an ACL entry by tag type
> + */
> +static inline struct acl_entry *acl_find_entry(struct acl *acl, uint16_t tag,
> + uint32_t id)
> +{
> + int i;
> +
> + for (i = 0; i < acl->count; i++) {
> + if (acl->entries[i].tag == tag) {
> + if (tag == ACL_USER || tag == ACL_GROUP) {
> + if (acl->entries[i].id == id)
> + return &acl->entries[i];
> + } else {
> + return &acl->entries[i];
> + }
> + }
> + }
> +
> + return NULL;
> +}
> +
> +/*
> + * Update ACL mask permissions
> + */
> +static inline int acl_set_mask_perms(struct acl *acl, uint16_t perm)
> +{
> + struct acl_entry *mask_entry = acl_find_entry(acl, ACL_MASK, 0);
> +
> + if (!mask_entry) {
> + errno = ENOENT;
> + return -1;
> + }
> +
> + mask_entry->perm = perm;
> + return 0;
> +}
> +
> +static inline int create_file_as(uid_t uid, gid_t gid, mode_t mode,
> + int use_umask, mode_t mask)
> +{
> + pid_t pid;
> + int status;
> +
> + pid = SAFE_FORK();
> + if (!pid) {
> + int fd, err;
> +
> + if (setgroups(0, NULL) == -1) {
> + err = errno;
> + _exit(err);
> + }
> +
> + if (setgid(gid) == -1) {
> + err = errno;
> + _exit(err);
> + }
> +
> + if (setuid(uid) == -1) {
> + err = errno;
> + _exit(err);
> + }
These should be SAFE_MACROS().
> + if (use_umask)
> + umask(mask);
> +
> + fd = open(TESTFILE, O_CREAT | O_WRONLY, mode);
> + if (fd >= 0) {
> + close(fd);
> + _exit(0);
> + }
Generally in LTP we want to check the test conditions right after they
happen. So this function should get expected errno as last parameter and
we should check for the result right here with something as:
if (errno)
ST_EXP_FAIL()
else
TST_EXP_FD()
With that we do need to propagate the return value manually, which is
prone to errors, and do not need to process the child exit value
manually, we can just let the library collect the children with
tst_reap_children().
> + err = errno;
> + _exit(err);
> + }
> +
> + SAFE_WAITPID(pid, &status, 0);
> +
> + if (!WIFEXITED(status))
> + tst_brk(TBROK, "Child terminated abnormally");
> +
> + return WEXITSTATUS(status);
> +}
> +
> +static inline int try_create_as(uid_t uid, gid_t gid, mode_t mode)
> +{
> + return create_file_as(uid, gid, mode, 0, 0);
> +}
> +
> +static inline int create_with_umask_as(uid_t uid, gid_t gid, mode_t mode,
> + mode_t mask)
> +{
> + return create_file_as(uid, gid, mode, 1, mask);
> +}
> +
> +static inline void cleanup_test_paths(void)
> +{
> + if (unlink(TESTSYMLINK) == -1 && errno != ENOENT)
> + tst_res(TWARN | TERRNO, "unlink(%s) failed", TESTSYMLINK);
> +
> + if (unlink(TESTFILE) == -1 && errno != ENOENT)
> + tst_res(TWARN | TERRNO, "unlink(%s) failed", TESTFILE);
> +
> + if (rmdir(TESTDIR) == -1 && errno != ENOENT)
> + tst_res(TWARN | TERRNO, "rmdir(%s) failed", TESTDIR);
Maybe we need SAFE_TRY_UNLINK() and SAFE_TRY_RMDIR() that wouldn't TBROK
on ENOENT and then we could use it here.
> +}
> +
> +#endif /* ACL_LIB_H */
> diff --git a/testcases/kernel/fs/acl/acl_user_obj01.c b/testcases/kernel/fs/acl/acl_user_obj01.c
> new file mode 100644
> index 000000000..01abfbf4e
> --- /dev/null
> +++ b/testcases/kernel/fs/acl/acl_user_obj01.c
> @@ -0,0 +1,154 @@
> +// 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 ACL_USER_OBJ permissions using direct xattr manipulation.
> + *
> + * Verify that owner permissions (ACL_USER_OBJ) correctly control access
> + * to files and directories. The test validates that:
> + * - ACL_USER_OBJ permissions are applied directly as the owner bits
> + * - Setting ACL_USER_OBJ=rwx via setxattr() overrides a previous
> + * chmod restriction
> + * - Owner permissions work independently of group and other permissions
> + *
> + * This test uses arbitrary UIDs without creating actual users, testing
> + * only the kernel ACL implementation.
> + */
> +
> +#include "acl_lib.h"
> +
> +#define TEST_UID 1000
> +#define TEST_GID 1000
> +
> +/*
> + * Test permission bits deny access.
> + * Owner should be denied write access when mode is 0500.
> + */
> +static void test_deny_by_mode(void)
> +{
> + int err;
> +
> + tst_res(TINFO, "Testing permission bits deny access");
> + reset_test_path();
> +
> + SAFE_CHOWN(TESTDIR, TEST_UID, TEST_GID);
> + SAFE_CHMOD(TESTDIR, 0500);
> +
> + err = try_create_as(TEST_UID, TEST_GID, 0644);
> + if (!err) {
> + cleanup_testfile();
> + tst_res(TFAIL, "Created file without write permission");
> + return;
> + }
> +
> + if (err != EACCES) {
> + errno = err;
> + tst_res(TFAIL | TERRNO, "Expected EACCES from owner create");
> + return;
> + }
> +
> + tst_res(TPASS, "File creation denied by permission bits");
> +}
> +
> +/*
> + * Test ACL_USER_OBJ grants access.
> + * Setting ACL_USER_OBJ=rwx should override previous chmod restriction.
> + */
> +static void test_grant_by_acl(void)
> +{
> + struct acl *acl = NULL;
> + int err;
> +
> + tst_res(TINFO, "Testing ACL_USER_OBJ grants access");
> + reset_test_path();
> +
> + SAFE_CHOWN(TESTDIR, TEST_UID, TEST_GID);
> + SAFE_CHMOD(TESTDIR, 0500);
> +
> + acl = acl_init();
> + if (!acl)
> + tst_brk(TBROK | TERRNO, "acl_init failed");
> +
> + 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, 0, 0) < 0)
> + goto cleanup_acl;
> +
> + if (acl_add_entry(acl, ACL_OTHER, 0, 0) < 0)
> + goto cleanup_acl;
Maybe we can just call SAFE_MALLOC() and tst_brk() internally in these
functions so that we don't have to complicate the code here. We do call
tst_brk() in cleanup_acl: anyways.
> + if (acl_set_file(TESTDIR, ACL_TYPE_ACCESS, acl) < 0) {
> + if (errno == EOPNOTSUPP) {
> + acl_free(acl);
> + tst_brk(TCONF | TERRNO, "ACL not supported");
> + }
> + goto cleanup_acl;
> + }
> +
> + acl_free(acl);
> + acl = NULL;
> +
> + err = try_create_as(TEST_UID, TEST_GID, 0644);
> + if (err) {
> + errno = err;
> + tst_res(TFAIL | TERRNO,
> + "Failed to create file with ACL_USER_OBJ rwx");
> + return;
> + }
> +
> + cleanup_testfile();
> + tst_res(TPASS, "ACL_USER_OBJ permissions work correctly");
> + return;
> +
> +cleanup_acl:
> + acl_free(acl);
> + tst_brk(TBROK | TERRNO, "ACL setup failed");
> +}
> +
> +static void run(unsigned int n)
> +{
> + switch (n) {
> + case 0:
> + test_deny_by_mode();
> + break;
> + case 1:
> + test_grant_by_acl();
> + break;
> + }
> +}
> +
> +static void setup(void)
> +{
> + reset_test_path();
> +}
> +
> +static void cleanup(void)
> +{
> + cleanup_test_paths();
> +}
> +
> +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 = "acl"},
> + {.type = "ext3", .mnt_data = "acl"},
> + {.type = "ext4", .mnt_data = "acl"},
> + {.type = "xfs"},
> + {.type = "btrfs"},
> + {}
> + }
> +};
> --
> 2.39.1
>
--
Cyril Hrubis
chrubis@suse.cz
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [LTP] [PATCH v5 1/8] fs/acl: Add ACL_USER_OBJ permission test
2026-06-08 11:57 ` [LTP] [PATCH v5 1/8] " Cyril Hrubis
@ 2026-06-08 13:40 ` Sachin Sant
2026-06-08 14:25 ` Cyril Hrubis
0 siblings, 1 reply; 13+ messages in thread
From: Sachin Sant @ 2026-06-08 13:40 UTC (permalink / raw)
To: Cyril Hrubis; +Cc: ltp
On 08/06/26 5:27 pm, Cyril Hrubis wrote:
>> +
>> + if (type == ACL_TYPE_ACCESS)
>> + xattr_name = XATTR_NAME_POSIX_ACL_ACCESS;
>> + else if (type == ACL_TYPE_DEFAULT)
>> + xattr_name = XATTR_NAME_POSIX_ACL_DEFAULT;
>> + else {
>> + errno = EINVAL;
>> + return NULL;
>> + }
> We are using this in two places, maybe it's worth having an
> acl_type_to_name() functions and use that instead.
Makes sense. Will make the change.
>> + header = (struct posix_acl_xattr_header *)buf;
>> + if (le32_to_cpu(header->a_version) != POSIX_ACL_XATTR_VERSION) {
>> + free(buf);
>> + errno = EINVAL;
>> + return NULL;
>> + }
>> +
>> + count = (size - sizeof(*header)) / sizeof(struct posix_acl_xattr_entry);
>> + entries = (struct posix_acl_xattr_entry *)(buf + sizeof(*header));
> Maybe we can define a structure with both header and entries in order to
> avoid some of the casts here. Maybe this would work:
>
> struct acl_xattr {
> struct posix_acl_xattr_header header;
> struct posix_acl_xattr_entry entries[];
> };
>
> And then we could do:
>
> struct acl_xattr *ax = (void*)buf;
>
> if (le32_to_cpu(ax->header.a_version) == ACL_TYPE_ACCESS) {
> ...
> }
>
> ...
>
> for (i = 0; i < conut; i++) {
> uint16_t tag = le16_to_cpu(ax->entries[i].e_tag);
> ...
>
> }
Okay, will check.
>> + acl = acl_init();
>> + if (!acl) {
>> + free(buf);
>> + return NULL;
>> + }
>> +
>> + for (i = 0; i < count; i++) {
>> + uint16_t tag = le16_to_cpu(entries[i].e_tag);
>> + uint16_t perm = le16_to_cpu(entries[i].e_perm);
>> + uint32_t id = le32_to_cpu(entries[i].e_id);
>> +
>> + if (acl_add_entry(acl, tag, perm, id) < 0) {
>> + acl_free(acl);
>> + free(buf);
>> + return NULL;
>> + }
>> + }
>> +
>> + free(buf);
>> + return acl;
>> +}
>> +
>> +/*
>> + * Check if an ACL entry has a specific permission
>> + */
>> +static inline int acl_entry_has_perm(struct acl_entry *entry, uint16_t perm)
>> +{
>> + return (entry->perm & perm) == perm;
>> +}
>> +
>> +/*
>> + * Check if an ACL entry has all rwx permissions
>> + */
>> +static inline int acl_entry_has_rwx(struct acl_entry *entry)
>> +{
>> + return acl_entry_has_perm(entry, ACL_READ) &&
>> + acl_entry_has_perm(entry, ACL_WRITE) &&
>> + acl_entry_has_perm(entry, ACL_EXECUTE);
> Can't we pass all the bits to a single call of the function like
> acl_entry_has_perm(entry, ACL_READ | ACL_WRITE | ACL_EXECUTE) ?
Yes, will change.
>> +}
>> +
>> +/*
>> + * Find an ACL entry by tag type
>> + */
>> +static inline struct acl_entry *acl_find_entry(struct acl *acl, uint16_t tag,
>> + uint32_t id)
>> +{
>> + int i;
>> +
>> + for (i = 0; i < acl->count; i++) {
>> + if (acl->entries[i].tag == tag) {
>> + if (tag == ACL_USER || tag == ACL_GROUP) {
>> + if (acl->entries[i].id == id)
>> + return &acl->entries[i];
>> + } else {
>> + return &acl->entries[i];
>> + }
>> + }
>> + }
>> +
>> + return NULL;
>> +}
>> +
>> +/*
>> + * Update ACL mask permissions
>> + */
>> +static inline int acl_set_mask_perms(struct acl *acl, uint16_t perm)
>> +{
>> + struct acl_entry *mask_entry = acl_find_entry(acl, ACL_MASK, 0);
>> +
>> + if (!mask_entry) {
>> + errno = ENOENT;
>> + return -1;
>> + }
>> +
>> + mask_entry->perm = perm;
>> + return 0;
>> +}
>> +
>> +static inline int create_file_as(uid_t uid, gid_t gid, mode_t mode,
>> + int use_umask, mode_t mask)
>> +{
>> + pid_t pid;
>> + int status;
>> +
>> + pid = SAFE_FORK();
>> + if (!pid) {
>> + int fd, err;
>> +
>> + if (setgroups(0, NULL) == -1) {
>> + err = errno;
>> + _exit(err);
>> + }
>> +
>> + if (setgid(gid) == -1) {
>> + err = errno;
>> + _exit(err);
>> + }
>> +
>> + if (setuid(uid) == -1) {
>> + err = errno;
>> + _exit(err);
>> + }
> These should be SAFE_MACROS().
Can you elaborate on this?
This code runs in a forked child process that intentionally tests
permission scenarios.
The function expects these calls to potentially fail as part of testing
ACL behavior.
The current pattern of capturing errno and exiting with it is correct for
communicating failure back to the parent.
SAFE_* macros would call tst_brk(TBROK) on failure, which is
inappropriate in
a child process testing permissions.
The parent process checks the exit status to determine if the operation
succeeded or failed (line 442: return WEXITSTATUS(status))
>
>> + if (use_umask)
>> + umask(mask);
>> +
>> + fd = open(TESTFILE, O_CREAT | O_WRONLY, mode);
>> + if (fd >= 0) {
>> + close(fd);
>> + _exit(0);
>> + }
> Generally in LTP we want to check the test conditions right after they
> happen. So this function should get expected errno as last parameter and
> we should check for the result right here with something as:
>
> if (errno)
> ST_EXP_FAIL()
> else
> TST_EXP_FD()
>
> With that we do need to propagate the return value manually, which is
> prone to errors, and do not need to process the child exit value
> manually, we can just let the library collect the children with
> tst_reap_children().
>
>> + err = errno;
>> + _exit(err);
>> + }
>> +
>> + SAFE_WAITPID(pid, &status, 0);
>> +
>> + if (!WIFEXITED(status))
>> + tst_brk(TBROK, "Child terminated abnormally");
>> +
>> + return WEXITSTATUS(status);
>
>> +}
>> +
>> +static inline int try_create_as(uid_t uid, gid_t gid, mode_t mode)
>> +{
>> + return create_file_as(uid, gid, mode, 0, 0);
>> +}
>> +
>> +static inline int create_with_umask_as(uid_t uid, gid_t gid, mode_t mode,
>> + mode_t mask)
>> +{
>> + return create_file_as(uid, gid, mode, 1, mask);
>> +}
>> +
>> +static inline void cleanup_test_paths(void)
>> +{
>> + if (unlink(TESTSYMLINK) == -1 && errno != ENOENT)
>> + tst_res(TWARN | TERRNO, "unlink(%s) failed", TESTSYMLINK);
>> +
>> + if (unlink(TESTFILE) == -1 && errno != ENOENT)
>> + tst_res(TWARN | TERRNO, "unlink(%s) failed", TESTFILE);
>> +
>> + if (rmdir(TESTDIR) == -1 && errno != ENOENT)
>> + tst_res(TWARN | TERRNO, "rmdir(%s) failed", TESTDIR);
> Maybe we need SAFE_TRY_UNLINK() and SAFE_TRY_RMDIR() that wouldn't TBROK
> on ENOENT and then we could use it here.
Good point. Let me check on this.
>> +}
>> +
>> +#endif /* ACL_LIB_H */
>> diff --git a/testcases/kernel/fs/acl/acl_user_obj01.c b/testcases/kernel/fs/acl/acl_user_obj01.c
>> new file mode 100644
>> index 000000000..01abfbf4e
>> --- /dev/null
>> +++ b/testcases/kernel/fs/acl/acl_user_obj01.c
>> @@ -0,0 +1,154 @@
>> +// 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 ACL_USER_OBJ permissions using direct xattr manipulation.
>> + *
>> + * Verify that owner permissions (ACL_USER_OBJ) correctly control access
>> + * to files and directories. The test validates that:
>> + * - ACL_USER_OBJ permissions are applied directly as the owner bits
>> + * - Setting ACL_USER_OBJ=rwx via setxattr() overrides a previous
>> + * chmod restriction
>> + * - Owner permissions work independently of group and other permissions
>> + *
>> + * This test uses arbitrary UIDs without creating actual users, testing
>> + * only the kernel ACL implementation.
>> + */
>> +
>> +#include "acl_lib.h"
>> +
>> +#define TEST_UID 1000
>> +#define TEST_GID 1000
>> +
>> +/*
>> + * Test permission bits deny access.
>> + * Owner should be denied write access when mode is 0500.
>> + */
>> +static void test_deny_by_mode(void)
>> +{
>> + int err;
>> +
>> + tst_res(TINFO, "Testing permission bits deny access");
>> + reset_test_path();
>> +
>> + SAFE_CHOWN(TESTDIR, TEST_UID, TEST_GID);
>> + SAFE_CHMOD(TESTDIR, 0500);
>> +
>> + err = try_create_as(TEST_UID, TEST_GID, 0644);
>> + if (!err) {
>> + cleanup_testfile();
>> + tst_res(TFAIL, "Created file without write permission");
>> + return;
>> + }
>> +
>> + if (err != EACCES) {
>> + errno = err;
>> + tst_res(TFAIL | TERRNO, "Expected EACCES from owner create");
>> + return;
>> + }
>> +
>> + tst_res(TPASS, "File creation denied by permission bits");
>> +}
>> +
>> +/*
>> + * Test ACL_USER_OBJ grants access.
>> + * Setting ACL_USER_OBJ=rwx should override previous chmod restriction.
>> + */
>> +static void test_grant_by_acl(void)
>> +{
>> + struct acl *acl = NULL;
>> + int err;
>> +
>> + tst_res(TINFO, "Testing ACL_USER_OBJ grants access");
>> + reset_test_path();
>> +
>> + SAFE_CHOWN(TESTDIR, TEST_UID, TEST_GID);
>> + SAFE_CHMOD(TESTDIR, 0500);
>> +
>> + acl = acl_init();
>> + if (!acl)
>> + tst_brk(TBROK | TERRNO, "acl_init failed");
>> +
>> + 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, 0, 0) < 0)
>> + goto cleanup_acl;
>> +
>> + if (acl_add_entry(acl, ACL_OTHER, 0, 0) < 0)
>> + goto cleanup_acl;
> Maybe we can just call SAFE_MALLOC() and tst_brk() internally in these
> functions so that we don't have to complicate the code here. We do call
> tst_brk() in cleanup_acl: anyways.
Thanks, yes make sense. Similar code pattern is used in multiple tests.
This should help simplify. Will check on this.
--
Thanks
- Sachin
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [LTP] [PATCH v5 1/8] fs/acl: Add ACL_USER_OBJ permission test
2026-06-08 13:40 ` Sachin Sant
@ 2026-06-08 14:25 ` Cyril Hrubis
0 siblings, 0 replies; 13+ messages in thread
From: Cyril Hrubis @ 2026-06-08 14:25 UTC (permalink / raw)
To: Sachin Sant; +Cc: ltp
Hi!
> >> +static inline int create_file_as(uid_t uid, gid_t gid, mode_t mode,
> >> + int use_umask, mode_t mask)
> >> +{
> >> + pid_t pid;
> >> + int status;
> >> +
> >> + pid = SAFE_FORK();
> >> + if (!pid) {
> >> + int fd, err;
> >> +
> >> + if (setgroups(0, NULL) == -1) {
> >> + err = errno;
> >> + _exit(err);
> >> + }
> >> +
> >> + if (setgid(gid) == -1) {
> >> + err = errno;
> >> + _exit(err);
> >> + }
> >> +
> >> + if (setuid(uid) == -1) {
> >> + err = errno;
> >> + _exit(err);
> >> + }
> > These should be SAFE_MACROS().
> Can you elaborate on this?
> This code runs in a forked child process that intentionally tests
> permission scenarios.
> The function expects these calls to potentially fail as part of testing
> ACL behavior.
>
> The current pattern of capturing errno and exiting with it is correct for
> communicating failure back to the parent.
> SAFE_* macros would call tst_brk(TBROK) on failure, which is
> inappropriate in
> a child process testing permissions.
It does test the permissions for the open() call. It's wrong to check
the errnos from set*() calls.
> The parent process checks the exit status to determine if the operation
> succeeded or failed (line 442: return WEXITSTATUS(status))
And as I described below we do not want to propagate results in LTP. We
should report PASS/FAIL as close to the call that we check as possible.
With that we avoid all possible bugs in result propagation.
> >> + if (use_umask)
> >> + umask(mask);
> >> +
> >> + fd = open(TESTFILE, O_CREAT | O_WRONLY, mode);
> >> + if (fd >= 0) {
> >> + close(fd);
> >> + _exit(0);
> >> + }
> > Generally in LTP we want to check the test conditions right after they
> > happen. So this function should get expected errno as last parameter and
> > we should check for the result right here with something as:
> >
> > if (errno)
> > ST_EXP_FAIL()
> > else
> > TST_EXP_FD()
> >
> > With that we do need to propagate the return value manually, which is
> > prone to errors, and do not need to process the child exit value
> > manually, we can just let the library collect the children with
> > tst_reap_children().
--
Cyril Hrubis
chrubis@suse.cz
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-06-08 14:25 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-08 9:21 [LTP] [PATCH v5 0/8] Convert shell-based ACL test (tacl_xattr.sh) to C Sachin Sant
2026-06-08 9:21 ` [LTP] [PATCH v5 1/8] fs/acl: Add ACL_USER_OBJ permission test Sachin Sant
2026-06-08 11:01 ` [LTP] " linuxtestproject.agent
2026-06-08 11:57 ` [LTP] [PATCH v5 1/8] " Cyril Hrubis
2026-06-08 13:40 ` Sachin Sant
2026-06-08 14:25 ` Cyril Hrubis
2026-06-08 9:21 ` [LTP] [PATCH v3 2/8] fs/acl: Add ACL mask interaction tests Sachin Sant
2026-06-08 9:21 ` [LTP] [PATCH v3 3/8] fs/acl: Add ACL_OTHER permissions test Sachin Sant
2026-06-08 9:21 ` [LTP] [PATCH v4 4/8] fs/acl: Add default ACL inheritance test Sachin Sant
2026-06-08 9:21 ` [LTP] [PATCH v3 5/8] fs/acl: Add chmod/chown ACL interaction tests Sachin Sant
2026-06-08 9:21 ` [LTP] [PATCH v5 6/8] fs/acl: Add symlink ACL operations test Sachin Sant
2026-06-08 9:21 ` [LTP] [PATCH v4 7/8] fs/acl: Add extended attributes test Sachin Sant
2026-06-08 9:22 ` [LTP] [PATCH v1 8/8] fs/acl: Remove old shell-based ACL test Sachin Sant
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.