diff mbox series

[v2] unlinkat: Add negative tests for unlinkat

Message ID 20240411092155.18018-1-xuyang2018.jy@fujitsu.com
State Superseded
Headers show
Series [v2] unlinkat: Add negative tests for unlinkat | expand

Commit Message

Yang Xu April 11, 2024, 9:21 a.m. UTC
Add negative cases for unlink(), including following errnos:
EACCES, EFAULT, EISDIR, ENAMETOOLONG ENOENT, ENOTDIR, EPERM, EROFS, EBADF,
EINVAL

Signed-off-by: Yang Xu <xuyang2018.jy@fujitsu.com>
---
 runtest/syscalls                              |   1 +
 testcases/kernel/syscalls/unlinkat/.gitignore |   1 +
 .../kernel/syscalls/unlinkat/unlinkat02.c     | 234 ++++++++++++++++++
 3 files changed, 236 insertions(+)
 create mode 100644 testcases/kernel/syscalls/unlinkat/unlinkat02.c

Comments

Avinesh Kumar April 16, 2024, 10:35 a.m. UTC | #1
Hi Yang Xu,
few nits below.

otherwise looks fine to me now.
But Petr or Li can take a look before merging it.


On Thursday, April 11, 2024 11:21:55 AM GMT+2 Yang Xu via ltp wrote:
> Add negative cases for unlink(), including following errnos:
> EACCES, EFAULT, EISDIR, ENAMETOOLONG ENOENT, ENOTDIR, EPERM, EROFS, EBADF,
> EINVAL
> 
> Signed-off-by: Yang Xu <xuyang2018.jy@fujitsu.com>
> ---
>  runtest/syscalls                              |   1 +
>  testcases/kernel/syscalls/unlinkat/.gitignore |   1 +
>  .../kernel/syscalls/unlinkat/unlinkat02.c     | 234 ++++++++++++++++++
>  3 files changed, 236 insertions(+)
>  create mode 100644 testcases/kernel/syscalls/unlinkat/unlinkat02.c
> 
> diff --git a/runtest/syscalls b/runtest/syscalls
> index b99ce7170..ed5eab1a9 100644
> --- a/runtest/syscalls
> +++ b/runtest/syscalls
> @@ -1655,6 +1655,7 @@ unlink09 unlink09
> 
>  #unlinkat test cases
>  unlinkat01 unlinkat01
> +unlinkat02 unlinkat02
> 
>  unshare01 unshare01
>  unshare02 unshare02
> diff --git a/testcases/kernel/syscalls/unlinkat/.gitignore
> b/testcases/kernel/syscalls/unlinkat/.gitignore index 76ed551f2..450063051
> 100644
> --- a/testcases/kernel/syscalls/unlinkat/.gitignore
> +++ b/testcases/kernel/syscalls/unlinkat/.gitignore
> @@ -1 +1,2 @@
>  /unlinkat01
> +/unlinkat02
> diff --git a/testcases/kernel/syscalls/unlinkat/unlinkat02.c
> b/testcases/kernel/syscalls/unlinkat/unlinkat02.c new file mode 100644
> index 000000000..bda792e6f
> --- /dev/null
> +++ b/testcases/kernel/syscalls/unlinkat/unlinkat02.c
> @@ -0,0 +1,234 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (c) 2024 FUJITSU LIMITED. All Rights Reserved.
> + * Author: Yang Xu <xuyang2018.jy@fujitsu.com>
> + */
> +
> +/*\
> + * [Description]
> + *
> + * Verify that unlinkat(2) fails with
> + *
> + * - EACCES when write access to the directory containing pathname not
> allowed + * - EACCES when one of directories in pathname did not allow
> search permission + * - EFAULT when pathname points outside acessible
> address space
> + * - EISDIR when pathname refers to a directory
> + * - ENAMETOOLONG when pathname is too long
> + * - ENOENT when a component of the pathname does not exist
> + * - ENOENT when pathname is empty
> + * - ENOTDIR when a component of pathname used as dicrectory is not a
> directory + * - EPERM when file to be unlinked is marked immutable
> + * - EPERM when file to be unlinked is marked append-only
> + * - EROFS when pathname refers to a file on a read-only filesystem
> + * - EBADF when pathname is relative but dirfd is neither AT_FDCWD nor
> valid + * - EINVAL when an invalid flag is specified
> + * - ENOTDIR when pathname is relative and dirfd refers to a file
> + */
> +
> +#define _GNU_SOURCE
> +
> +#include <fcntl.h>
> +#include <pwd.h>
> +#include <sys/ioctl.h>
> +#include "tst_test.h"
> +#include "lapi/fs.h"
> +
> +#define DIR_EACCES_NOWRITE "nowrite"
> +#define DIR_EACCES_NOSEARCH "nosearch"
> +#define TEST_EACCES "test_eacces"
> +#define DIR_NORMAL "normal"
> +#define TEST_NORMAL "test_normal"
> +#define TEST_EFAULT "test_efault"
> +#define DIR_EISDIR "isdir"
> +#define TEST_ENOENT_NOTEXIST "test_enoent_notexist"
> +#define TEST_ENOENT_FILE "test_enoent_file"
> +#define TEST_ENOTDIR "enotdir/file"
> +#define DIR_ENOTDIR "enotdir"
> +#define TEST_EPERM_IMMUTABLE "test_eperm_immutable"
> +#define TEST_EPERM_APPEND_ONLY "test_eperm_append_only"
> +#define DIR_EROFS "erofs"
> +#define TEST_EROFS "test_erofs"
> +#define DIR_EBADF "ebadf"
> +#define TEST_EBADF "test_ebadf"
> +#define DIR_ENOTDIR2 "enotdir2"
> +#define TEST_ENOTDIR2 "test_enotdir2"
> +
> +static struct passwd *pw;
> +static char longfilename[PATH_MAX + 1];
> +static int fd_immutable;
> +static int fd_append_only;
> +
> +static struct test_case_t {
> +	char *dirname;
> +	char *filename;
> +	int *fd;
> +	int ioctl_flag;
> +	int flags;
> +	int user;
> +	int expected_errno;
> +	char *desc;
> +} tcases[] = {
> +	{DIR_EACCES_NOWRITE, TEST_EACCES, NULL, 0, 0, 1, EACCES,
> +		"unlinkat() in directory with no write access"},
> +	{DIR_EACCES_NOSEARCH, TEST_EACCES, NULL, 0, 0, 1, EACCES,
> +		"unlinkat() in directory with no search access"},
> +	{DIR_NORMAL, NULL, NULL, 0, 0, 0, EFAULT,
> +		"unlinkat() access pathname outside address space"},
> +	{DIR_NORMAL, DIR_EISDIR, NULL, 0, 0, 0, EISDIR,
> +		"unlinkat() pathname is a directory"},
> +	{DIR_NORMAL, longfilename, NULL, 0, 0, 0, ENAMETOOLONG,
> +		"unlinkat() pathname is too long"},
> +	{DIR_NORMAL, TEST_ENOENT_NOTEXIST, NULL, 0, 0, 0, ENOENT,
> +		"unlinkat() pathname does not exist"},
> +	{DIR_NORMAL, "", NULL, 0, 0, 0, ENOENT,
> +		"unlinkat() pathname is a empty"},
> +	{DIR_NORMAL, TEST_ENOTDIR, NULL, 0, 0, 0, ENOTDIR,
> +		"unlinkat() component of pathname used as directory "
> +		"is not directory"},
> +	{DIR_NORMAL, TEST_EPERM_IMMUTABLE, &fd_immutable, FS_IMMUTABLE_FL,
> +		0, 0, EPERM,
> +		"unlinkat() pathname is immutable"},
> +	{DIR_NORMAL, TEST_EPERM_APPEND_ONLY, &fd_append_only, FS_APPEND_FL,
> +		0, 0, EPERM,
> +		"unlinkat() pathname is append-only"},
> +	{DIR_EROFS, TEST_EROFS, NULL, 0, 0, 0, EROFS,
> +		"unlinkat() pathname in read-only filesystem"},
> +	{DIR_EBADF, TEST_EBADF, NULL, 0, 0, 0, EBADF,
> +		"unlinkat() dirfd is not valid"},
> +	{DIR_NORMAL, TEST_NORMAL, NULL, 0, -1, 0, EINVAL,
> +		"unlinkat() flag is not valid"},
> +	{DIR_ENOTDIR2, TEST_ENOTDIR2, NULL, 0, 0, 0, ENOTDIR,
> +		"unlinkat() dirfd is not a directory"},
> +};
> +
> +static void setup(void)
> +{
> +	int attr;
> +
> +	pw = SAFE_GETPWNAM("nobody");
> +
> +	SAFE_MKDIR(DIR_EACCES_NOWRITE, 0777);
> +	SAFE_TOUCH(DIR_EACCES_NOWRITE "/" TEST_EACCES, 0777, NULL);
> +	SAFE_CHMOD(DIR_EACCES_NOWRITE, 0555);
> +
> +	SAFE_MKDIR(DIR_EACCES_NOSEARCH, 0777);
> +	SAFE_TOUCH(DIR_EACCES_NOSEARCH "/" TEST_EACCES, 0777, NULL);
> +	SAFE_CHMOD(DIR_EACCES_NOSEARCH, 0666);
> +
> +	SAFE_MKDIR(DIR_NORMAL, 0777);
> +	SAFE_TOUCH(DIR_NORMAL "/" TEST_NORMAL, 0777, NULL);
> +	SAFE_TOUCH(DIR_NORMAL "/" TEST_EFAULT, 0777, NULL);
> +
> +	SAFE_MKDIR(DIR_NORMAL "/" DIR_EISDIR, 0777);
> +
> +	memset(longfilename, '1', PATH_MAX + 1);
> +
> +	SAFE_TOUCH(DIR_NORMAL "/" DIR_ENOTDIR, 0777, NULL);
> +
> +	fd_immutable = SAFE_OPEN(DIR_NORMAL "/" TEST_EPERM_IMMUTABLE,
> +			O_CREAT, 0777);
> +	SAFE_IOCTL(fd_immutable, FS_IOC_GETFLAGS, &attr);
> +	attr |= FS_IMMUTABLE_FL;
> +	SAFE_IOCTL(fd_immutable, FS_IOC_SETFLAGS, &attr);
> +	SAFE_CLOSE(fd_immutable);
> +
> +	fd_append_only = SAFE_OPEN(DIR_NORMAL "/" TEST_EPERM_APPEND_ONLY,
> +			O_CREAT, 0777);
> +	SAFE_IOCTL(fd_append_only, FS_IOC_GETFLAGS, &attr);
> +	attr |= FS_APPEND_FL;
> +	SAFE_IOCTL(fd_append_only, FS_IOC_SETFLAGS, &attr);
> +	SAFE_CLOSE(fd_append_only);
> +
> +	SAFE_TOUCH(DIR_ENOTDIR2, 0777, NULL);
> +}
> +
> +static void cleanup(void)
> +{
> +	int attr;
> +
> +	fd_immutable = SAFE_OPEN(DIR_NORMAL "/" TEST_EPERM_IMMUTABLE,
> +			O_RDONLY, 0777);
> +	SAFE_IOCTL(fd_immutable, FS_IOC_GETFLAGS, &attr);
> +	attr &= ~FS_IMMUTABLE_FL;
> +	SAFE_IOCTL(fd_immutable, FS_IOC_SETFLAGS, &attr);
> +	SAFE_CLOSE(fd_immutable);
> +
> +	fd_append_only = SAFE_OPEN(DIR_NORMAL "/" TEST_EPERM_APPEND_ONLY,
> +			O_RDONLY, 0777);
> +	SAFE_IOCTL(fd_append_only, FS_IOC_GETFLAGS, &attr);
> +	attr &= ~FS_APPEND_FL;
> +	SAFE_IOCTL(fd_append_only, FS_IOC_SETFLAGS, &attr);
> +	SAFE_CLOSE(fd_append_only);
> +}
> +
> +static void do_unlinkat(struct test_case_t *tc)
> +{
> +	int attr;
> +	char fullpath[PATH_MAX];
> +	int dirfd = open(tc->dirname, O_DIRECTORY);
> +
> +	if (dirfd < 0) {
> +		if (tc->expected_errno != EBADF) {
> +			/* Special situation: dirfd refers to a file */
> +			if (errno == ENOTDIR)
> +				dirfd = open(tc->dirname, O_APPEND);
we can use SAFE_OPEN() here.
> +			else {
> +				tst_res(TFAIL | TERRNO, "Cannot open dirfd");
> +				return;
> +			}
> +		}
> +	}
> +
> +	TST_EXP_FAIL(unlinkat(dirfd, tc->filename, tc->flags),
> +		tc->expected_errno,
> +		"%s", tc->desc);
> +
> +	/* If unlinkat() suceeded unexpectedly, test file should be retored. */
s/suceeded/succeeded
s/retored/restored
> +	if (!TST_RET) {
> +		snprintf(fullpath, sizeof(fullpath), "%s/%s", tc->dirname,
> +			tc->filename);
> +		if (tc->fd) {
> +			*(tc->fd) = SAFE_OPEN(fullpath, O_CREAT, 0600);
> +			if (tc->ioctl_flag) {
> +				SAFE_IOCTL(*(tc->fd), FS_IOC_GETFLAGS, &attr);
> +				attr |= tc->ioctl_flag;
> +				SAFE_IOCTL(*(tc->fd), FS_IOC_SETFLAGS, &attr);
> +			}
> +			SAFE_CLOSE(*(tc->fd));
> +		} else {
> +			SAFE_TOUCH(fullpath, 0777, 0);
> +		}
> +	}
> +
> +	if (dirfd > 0)
> +		SAFE_CLOSE(dirfd);
> +}
> +
> +static void verify_unlinkat(unsigned int i)
> +{
> +	struct test_case_t *tc = &tcases[i];
> +	pid_t pid;
> +
> +	if (tc->user) {
> +		pid = SAFE_FORK();
> +		if (!pid) {
> +			SAFE_SETUID(pw->pw_uid);
> +			do_unlinkat(tc);
> +			exit(0);
> +		}
> +		SAFE_WAITPID(pid, NULL, 0);
> +	} else {
> +		do_unlinkat(tc);
> +	}
> +}
> +
> +static struct tst_test test = {
> +	.setup = setup,
> +	.tcnt = ARRAY_SIZE(tcases),
> +	.cleanup = cleanup,
> +	.test = verify_unlinkat,
> +	.needs_rofs = 1,
> +	.mntpoint = DIR_EROFS,
> +	.needs_root = 1,
> +	.forks_child = 1,
> +};

Regards,
Avinesh
diff mbox series

Patch

diff --git a/runtest/syscalls b/runtest/syscalls
index b99ce7170..ed5eab1a9 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -1655,6 +1655,7 @@  unlink09 unlink09
 
 #unlinkat test cases
 unlinkat01 unlinkat01
+unlinkat02 unlinkat02
 
 unshare01 unshare01
 unshare02 unshare02
diff --git a/testcases/kernel/syscalls/unlinkat/.gitignore b/testcases/kernel/syscalls/unlinkat/.gitignore
index 76ed551f2..450063051 100644
--- a/testcases/kernel/syscalls/unlinkat/.gitignore
+++ b/testcases/kernel/syscalls/unlinkat/.gitignore
@@ -1 +1,2 @@ 
 /unlinkat01
+/unlinkat02
diff --git a/testcases/kernel/syscalls/unlinkat/unlinkat02.c b/testcases/kernel/syscalls/unlinkat/unlinkat02.c
new file mode 100644
index 000000000..bda792e6f
--- /dev/null
+++ b/testcases/kernel/syscalls/unlinkat/unlinkat02.c
@@ -0,0 +1,234 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2024 FUJITSU LIMITED. All Rights Reserved.
+ * Author: Yang Xu <xuyang2018.jy@fujitsu.com>
+ */
+
+/*\
+ * [Description]
+ *
+ * Verify that unlinkat(2) fails with
+ *
+ * - EACCES when write access to the directory containing pathname not allowed
+ * - EACCES when one of directories in pathname did not allow search permission
+ * - EFAULT when pathname points outside acessible address space
+ * - EISDIR when pathname refers to a directory
+ * - ENAMETOOLONG when pathname is too long
+ * - ENOENT when a component of the pathname does not exist
+ * - ENOENT when pathname is empty
+ * - ENOTDIR when a component of pathname used as dicrectory is not a directory
+ * - EPERM when file to be unlinked is marked immutable
+ * - EPERM when file to be unlinked is marked append-only
+ * - EROFS when pathname refers to a file on a read-only filesystem
+ * - EBADF when pathname is relative but dirfd is neither AT_FDCWD nor valid
+ * - EINVAL when an invalid flag is specified
+ * - ENOTDIR when pathname is relative and dirfd refers to a file
+ */
+
+#define _GNU_SOURCE
+
+#include <fcntl.h>
+#include <pwd.h>
+#include <sys/ioctl.h>
+#include "tst_test.h"
+#include "lapi/fs.h"
+
+#define DIR_EACCES_NOWRITE "nowrite"
+#define DIR_EACCES_NOSEARCH "nosearch"
+#define TEST_EACCES "test_eacces"
+#define DIR_NORMAL "normal"
+#define TEST_NORMAL "test_normal"
+#define TEST_EFAULT "test_efault"
+#define DIR_EISDIR "isdir"
+#define TEST_ENOENT_NOTEXIST "test_enoent_notexist"
+#define TEST_ENOENT_FILE "test_enoent_file"
+#define TEST_ENOTDIR "enotdir/file"
+#define DIR_ENOTDIR "enotdir"
+#define TEST_EPERM_IMMUTABLE "test_eperm_immutable"
+#define TEST_EPERM_APPEND_ONLY "test_eperm_append_only"
+#define DIR_EROFS "erofs"
+#define TEST_EROFS "test_erofs"
+#define DIR_EBADF "ebadf"
+#define TEST_EBADF "test_ebadf"
+#define DIR_ENOTDIR2 "enotdir2"
+#define TEST_ENOTDIR2 "test_enotdir2"
+
+static struct passwd *pw;
+static char longfilename[PATH_MAX + 1];
+static int fd_immutable;
+static int fd_append_only;
+
+static struct test_case_t {
+	char *dirname;
+	char *filename;
+	int *fd;
+	int ioctl_flag;
+	int flags;
+	int user;
+	int expected_errno;
+	char *desc;
+} tcases[] = {
+	{DIR_EACCES_NOWRITE, TEST_EACCES, NULL, 0, 0, 1, EACCES,
+		"unlinkat() in directory with no write access"},
+	{DIR_EACCES_NOSEARCH, TEST_EACCES, NULL, 0, 0, 1, EACCES,
+		"unlinkat() in directory with no search access"},
+	{DIR_NORMAL, NULL, NULL, 0, 0, 0, EFAULT,
+		"unlinkat() access pathname outside address space"},
+	{DIR_NORMAL, DIR_EISDIR, NULL, 0, 0, 0, EISDIR,
+		"unlinkat() pathname is a directory"},
+	{DIR_NORMAL, longfilename, NULL, 0, 0, 0, ENAMETOOLONG,
+		"unlinkat() pathname is too long"},
+	{DIR_NORMAL, TEST_ENOENT_NOTEXIST, NULL, 0, 0, 0, ENOENT,
+		"unlinkat() pathname does not exist"},
+	{DIR_NORMAL, "", NULL, 0, 0, 0, ENOENT,
+		"unlinkat() pathname is a empty"},
+	{DIR_NORMAL, TEST_ENOTDIR, NULL, 0, 0, 0, ENOTDIR,
+		"unlinkat() component of pathname used as directory "
+		"is not directory"},
+	{DIR_NORMAL, TEST_EPERM_IMMUTABLE, &fd_immutable, FS_IMMUTABLE_FL,
+		0, 0, EPERM,
+		"unlinkat() pathname is immutable"},
+	{DIR_NORMAL, TEST_EPERM_APPEND_ONLY, &fd_append_only, FS_APPEND_FL,
+		0, 0, EPERM,
+		"unlinkat() pathname is append-only"},
+	{DIR_EROFS, TEST_EROFS, NULL, 0, 0, 0, EROFS,
+		"unlinkat() pathname in read-only filesystem"},
+	{DIR_EBADF, TEST_EBADF, NULL, 0, 0, 0, EBADF,
+		"unlinkat() dirfd is not valid"},
+	{DIR_NORMAL, TEST_NORMAL, NULL, 0, -1, 0, EINVAL,
+		"unlinkat() flag is not valid"},
+	{DIR_ENOTDIR2, TEST_ENOTDIR2, NULL, 0, 0, 0, ENOTDIR,
+		"unlinkat() dirfd is not a directory"},
+};
+
+static void setup(void)
+{
+	int attr;
+
+	pw = SAFE_GETPWNAM("nobody");
+
+	SAFE_MKDIR(DIR_EACCES_NOWRITE, 0777);
+	SAFE_TOUCH(DIR_EACCES_NOWRITE "/" TEST_EACCES, 0777, NULL);
+	SAFE_CHMOD(DIR_EACCES_NOWRITE, 0555);
+
+	SAFE_MKDIR(DIR_EACCES_NOSEARCH, 0777);
+	SAFE_TOUCH(DIR_EACCES_NOSEARCH "/" TEST_EACCES, 0777, NULL);
+	SAFE_CHMOD(DIR_EACCES_NOSEARCH, 0666);
+
+	SAFE_MKDIR(DIR_NORMAL, 0777);
+	SAFE_TOUCH(DIR_NORMAL "/" TEST_NORMAL, 0777, NULL);
+	SAFE_TOUCH(DIR_NORMAL "/" TEST_EFAULT, 0777, NULL);
+
+	SAFE_MKDIR(DIR_NORMAL "/" DIR_EISDIR, 0777);
+
+	memset(longfilename, '1', PATH_MAX + 1);
+
+	SAFE_TOUCH(DIR_NORMAL "/" DIR_ENOTDIR, 0777, NULL);
+
+	fd_immutable = SAFE_OPEN(DIR_NORMAL "/" TEST_EPERM_IMMUTABLE,
+			O_CREAT, 0777);
+	SAFE_IOCTL(fd_immutable, FS_IOC_GETFLAGS, &attr);
+	attr |= FS_IMMUTABLE_FL;
+	SAFE_IOCTL(fd_immutable, FS_IOC_SETFLAGS, &attr);
+	SAFE_CLOSE(fd_immutable);
+
+	fd_append_only = SAFE_OPEN(DIR_NORMAL "/" TEST_EPERM_APPEND_ONLY,
+			O_CREAT, 0777);
+	SAFE_IOCTL(fd_append_only, FS_IOC_GETFLAGS, &attr);
+	attr |= FS_APPEND_FL;
+	SAFE_IOCTL(fd_append_only, FS_IOC_SETFLAGS, &attr);
+	SAFE_CLOSE(fd_append_only);
+
+	SAFE_TOUCH(DIR_ENOTDIR2, 0777, NULL);
+}
+
+static void cleanup(void)
+{
+	int attr;
+
+	fd_immutable = SAFE_OPEN(DIR_NORMAL "/" TEST_EPERM_IMMUTABLE,
+			O_RDONLY, 0777);
+	SAFE_IOCTL(fd_immutable, FS_IOC_GETFLAGS, &attr);
+	attr &= ~FS_IMMUTABLE_FL;
+	SAFE_IOCTL(fd_immutable, FS_IOC_SETFLAGS, &attr);
+	SAFE_CLOSE(fd_immutable);
+
+	fd_append_only = SAFE_OPEN(DIR_NORMAL "/" TEST_EPERM_APPEND_ONLY,
+			O_RDONLY, 0777);
+	SAFE_IOCTL(fd_append_only, FS_IOC_GETFLAGS, &attr);
+	attr &= ~FS_APPEND_FL;
+	SAFE_IOCTL(fd_append_only, FS_IOC_SETFLAGS, &attr);
+	SAFE_CLOSE(fd_append_only);
+}
+
+static void do_unlinkat(struct test_case_t *tc)
+{
+	int attr;
+	char fullpath[PATH_MAX];
+	int dirfd = open(tc->dirname, O_DIRECTORY);
+
+	if (dirfd < 0) {
+		if (tc->expected_errno != EBADF) {
+			/* Special situation: dirfd refers to a file */
+			if (errno == ENOTDIR)
+				dirfd = open(tc->dirname, O_APPEND);
+			else {
+				tst_res(TFAIL | TERRNO, "Cannot open dirfd");
+				return;
+			}
+		}
+	}
+
+	TST_EXP_FAIL(unlinkat(dirfd, tc->filename, tc->flags),
+		tc->expected_errno,
+		"%s", tc->desc);
+
+	/* If unlinkat() suceeded unexpectedly, test file should be retored. */
+	if (!TST_RET) {
+		snprintf(fullpath, sizeof(fullpath), "%s/%s", tc->dirname,
+			tc->filename);
+		if (tc->fd) {
+			*(tc->fd) = SAFE_OPEN(fullpath, O_CREAT, 0600);
+			if (tc->ioctl_flag) {
+				SAFE_IOCTL(*(tc->fd), FS_IOC_GETFLAGS, &attr);
+				attr |= tc->ioctl_flag;
+				SAFE_IOCTL(*(tc->fd), FS_IOC_SETFLAGS, &attr);
+			}
+			SAFE_CLOSE(*(tc->fd));
+		} else {
+			SAFE_TOUCH(fullpath, 0777, 0);
+		}
+	}
+
+	if (dirfd > 0)
+		SAFE_CLOSE(dirfd);
+}
+
+static void verify_unlinkat(unsigned int i)
+{
+	struct test_case_t *tc = &tcases[i];
+	pid_t pid;
+
+	if (tc->user) {
+		pid = SAFE_FORK();
+		if (!pid) {
+			SAFE_SETUID(pw->pw_uid);
+			do_unlinkat(tc);
+			exit(0);
+		}
+		SAFE_WAITPID(pid, NULL, 0);
+	} else {
+		do_unlinkat(tc);
+	}
+}
+
+static struct tst_test test = {
+	.setup = setup,
+	.tcnt = ARRAY_SIZE(tcases),
+	.cleanup = cleanup,
+	.test = verify_unlinkat,
+	.needs_rofs = 1,
+	.mntpoint = DIR_EROFS,
+	.needs_root = 1,
+	.forks_child = 1,
+};