Re: [PATCHv2 2/3] selftests: Add test of O_BENEATH & openat(2)

From: Dave Chinner
Date: Tue Nov 11 2014 - 00:36:53 EST


[cc fstests@xxxxxxxxxxxxxxx]

On Tue, Nov 04, 2014 at 09:54:43AM +0000, David Drysdale wrote:
> Add simple tests of openat(2) variations, including examples that
> check the new O_BENEATH flag.
>
> Signed-off-by: David Drysdale <drysdale@xxxxxxxxxx>

Wouldn't this be better added to fstests? That's the regression
test suite used by filesystem developers and most distro QA
organisations and where the fs developers aggregate all their new
regression tests.

IMO, the fewer places we aggregate VFS/filesystem tests the better.
I really don't think the kernel tree is the best place for adding
VFS behavioural tests because it has none of the infrastructure
around it to test arbitrary filesystems and configurations and hence
is not particularly useful to the people whoa re likely to notice
and care about fs regression tests suddenly breaking.

As an example, the recent renameat() syscall additions (e.g.
RENAME_EXCHANGE, RENAME_NOREPLACE) have unit tests in fstests, so
these new O_BENEATH tests should really follow the same model...

Cheers,

Dave.

> ---
> tools/testing/selftests/Makefile | 1 +
> tools/testing/selftests/openat/.gitignore | 4 +
> tools/testing/selftests/openat/Makefile | 28 +++++
> tools/testing/selftests/openat/openat.c | 180 ++++++++++++++++++++++++++++++
> 4 files changed, 213 insertions(+)
> create mode 100644 tools/testing/selftests/openat/.gitignore
> create mode 100644 tools/testing/selftests/openat/Makefile
> create mode 100644 tools/testing/selftests/openat/openat.c
>
> diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
> index 36ff2e4c7b6f..812e973233d2 100644
> --- a/tools/testing/selftests/Makefile
> +++ b/tools/testing/selftests/Makefile
> @@ -14,6 +14,7 @@ TARGETS += powerpc
> TARGETS += user
> TARGETS += sysctl
> TARGETS += firmware
> +TARGETS += openat
>
> TARGETS_HOTPLUG = cpu-hotplug
> TARGETS_HOTPLUG += memory-hotplug
> diff --git a/tools/testing/selftests/openat/.gitignore b/tools/testing/selftests/openat/.gitignore
> new file mode 100644
> index 000000000000..835b2dcd8678
> --- /dev/null
> +++ b/tools/testing/selftests/openat/.gitignore
> @@ -0,0 +1,4 @@
> +openat
> +subdir
> +topfile
> +symlinkdown
> \ No newline at end of file
> diff --git a/tools/testing/selftests/openat/Makefile b/tools/testing/selftests/openat/Makefile
> new file mode 100644
> index 000000000000..84cd06e7ee82
> --- /dev/null
> +++ b/tools/testing/selftests/openat/Makefile
> @@ -0,0 +1,28 @@
> +CC = $(CROSS_COMPILE)gcc
> +CFLAGS = -Wall
> +BINARIES = openat
> +DEPS = subdir topfile symlinkdown subdir/bottomfile subdir/symlinkup subdir/symlinkout subdir/symlinkin
> +all: $(BINARIES) $(DEPS)
> +
> +subdir:
> + mkdir -p subdir
> +topfile:
> + echo 0123456789 > $@
> +subdir/bottomfile: | subdir
> + echo 0123456789 > $@
> +subdir/symlinkup: | subdir
> + ln -s ../topfile $@
> +subdir/symlinkout: | subdir
> + ln -s /etc/passwd $@
> +subdir/symlinkin: | subdir
> + ln -s bottomfile $@
> +symlinkdown:
> + ln -s subdir/bottomfile $@
> +%: %.c
> + $(CC) $(CFLAGS) -o $@ $^
> +
> +run_tests: all
> + ./openat
> +
> +clean:
> + rm -rf $(BINARIES) $(DEPS)
> diff --git a/tools/testing/selftests/openat/openat.c b/tools/testing/selftests/openat/openat.c
> new file mode 100644
> index 000000000000..0c030cbd10dc
> --- /dev/null
> +++ b/tools/testing/selftests/openat/openat.c
> @@ -0,0 +1,180 @@
> +#include <sys/types.h>
> +#include <sys/stat.h>
> +#include <sys/syscall.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <string.h>
> +#include <errno.h>
> +
> +#include <linux/fcntl.h>
> +
> +/* Bypass glibc */
> +static int openat_(int dirfd, const char *pathname, int flags)
> +{
> + return syscall(__NR_openat, dirfd, pathname, flags);
> +}
> +
> +static int openat_or_die(int dfd, const char *path, int flags)
> +{
> + int fd = openat_(dfd, path, flags);
> +
> + if (fd < 0) {
> + printf("Failed to openat(%d, '%s'); "
> + "check prerequisites are available\n", dfd, path);
> + exit(1);
> + }
> + return fd;
> +}
> +
> +static int check_openat(int dfd, const char *path, int flags)
> +{
> + int rc;
> + int fd;
> + char buffer[4];
> +
> + errno = 0;
> + printf("Check success of openat(%d, '%s', %x)... ",
> + dfd, path?:"(null)", flags);
> + fd = openat_(dfd, path, flags);
> + if (fd < 0) {
> + printf("[FAIL]: openat() failed, rc=%d errno=%d (%s)\n",
> + fd, errno, strerror(errno));
> + return 1;
> + }
> + errno = 0;
> + rc = read(fd, buffer, sizeof(buffer));
> + if (rc < 0) {
> + printf("[FAIL]: read() failed, rc=%d errno=%d (%s)\n",
> + rc, errno, strerror(errno));
> + return 1;
> + }
> + close(fd);
> + printf("[OK]\n");
> + return 0;
> +}
> +
> +#define check_openat_fail(dfd, path, flags, errno) \
> + _check_openat_fail(dfd, path, flags, errno, #errno)
> +static int _check_openat_fail(int dfd, const char *path, int flags,
> + int expected_errno, const char *errno_str)
> +{
> + int rc;
> +
> + errno = 0;
> + printf("Check failure of openat(%d, '%s', %x) with %s... ",
> + dfd, path?:"(null)", flags, errno_str);
> + rc = openat_(dfd, path, flags);
> + if (rc > 0) {
> + printf("[FAIL] (unexpected success from openat(2))\n");
> + close(rc);
> + return 1;
> + }
> + if (errno != expected_errno) {
> + printf("[FAIL] (expected errno %d (%s) not %d (%s)\n",
> + expected_errno, strerror(expected_errno),
> + errno, strerror(errno));
> + return 1;
> + }
> + printf("[OK]\n");
> + return 0;
> +}
> +
> +int check_proc(void)
> +{
> + int proc_dfd = openat_(AT_FDCWD, "/proc/self", O_RDONLY);
> + int fail = 0;
> +
> + if (proc_dfd < 0) {
> + printf("'/proc/self' unavailable (errno=%d '%s'), skipping\n",
> + errno, strerror(errno));
> + return 1;
> + }
> + fail |= check_openat(proc_dfd, "root/etc/passwd", O_RDONLY);
> +#ifdef O_BENEATH
> + fail |= check_openat_fail(proc_dfd, "root/etc/passwd",
> + O_RDONLY|O_BENEATH, EPERM);
> +#endif
> + return fail;
> +}
> +
> +int main(int argc, char *argv[])
> +{
> + int fail = 0;
> + int dot_dfd = openat_or_die(AT_FDCWD, ".", O_RDONLY);
> + int subdir_dfd = openat_or_die(AT_FDCWD, "subdir", O_RDONLY);
> + int file_fd = openat_or_die(AT_FDCWD, "topfile", O_RDONLY);
> +
> + /* Sanity check normal behavior */
> + fail |= check_openat(AT_FDCWD, "topfile", O_RDONLY);
> + fail |= check_openat(AT_FDCWD, "subdir/bottomfile", O_RDONLY);
> +
> + fail |= check_openat(dot_dfd, "topfile", O_RDONLY);
> + fail |= check_openat(dot_dfd, "subdir/bottomfile", O_RDONLY);
> + fail |= check_openat(dot_dfd, "subdir/../topfile", O_RDONLY);
> +
> + fail |= check_openat(subdir_dfd, "../topfile", O_RDONLY);
> + fail |= check_openat(subdir_dfd, "bottomfile", O_RDONLY);
> + fail |= check_openat(subdir_dfd, "../subdir/bottomfile", O_RDONLY);
> + fail |= check_openat(subdir_dfd, "symlinkup", O_RDONLY);
> + fail |= check_openat(subdir_dfd, "symlinkout", O_RDONLY);
> +
> + fail |= check_openat(AT_FDCWD, "/etc/passwd", O_RDONLY);
> + fail |= check_openat(dot_dfd, "/etc/passwd", O_RDONLY);
> + fail |= check_openat(subdir_dfd, "/etc/passwd", O_RDONLY);
> +
> + fail |= check_openat_fail(AT_FDCWD, "bogus", O_RDONLY, ENOENT);
> + fail |= check_openat_fail(dot_dfd, "bogus", O_RDONLY, ENOENT);
> + fail |= check_openat_fail(999, "bogus", O_RDONLY, EBADF);
> + fail |= check_openat_fail(file_fd, "bogus", O_RDONLY, ENOTDIR);
> +
> +#ifdef O_BENEATH
> + /* Test out O_BENEATH */
> + fail |= check_openat(AT_FDCWD, "topfile", O_RDONLY|O_BENEATH);
> + fail |= check_openat(AT_FDCWD, "subdir/bottomfile",
> + O_RDONLY|O_BENEATH);
> +
> + fail |= check_openat(dot_dfd, "topfile", O_RDONLY|O_BENEATH);
> + fail |= check_openat(dot_dfd, "subdir/bottomfile",
> + O_RDONLY|O_BENEATH);
> + fail |= check_openat(subdir_dfd, "bottomfile", O_RDONLY|O_BENEATH);
> +
> + /* Symlinks without .. or leading / are OK */
> + fail |= check_openat(dot_dfd, "symlinkdown", O_RDONLY|O_BENEATH);
> + fail |= check_openat(dot_dfd, "subdir/symlinkin", O_RDONLY|O_BENEATH);
> + fail |= check_openat(subdir_dfd, "symlinkin", O_RDONLY|O_BENEATH);
> + /* ... unless of course we specify O_NOFOLLOW */
> + fail |= check_openat_fail(dot_dfd, "symlinkdown",
> + O_RDONLY|O_BENEATH|O_NOFOLLOW, ELOOP);
> + fail |= check_openat_fail(dot_dfd, "subdir/symlinkin",
> + O_RDONLY|O_BENEATH|O_NOFOLLOW, ELOOP);
> + fail |= check_openat_fail(subdir_dfd, "symlinkin",
> + O_RDONLY|O_BENEATH|O_NOFOLLOW, ELOOP);
> +
> + /* Can't open paths with ".." in them */
> + fail |= check_openat_fail(dot_dfd, "subdir/../topfile",
> + O_RDONLY|O_BENEATH, EPERM);
> + fail |= check_openat_fail(subdir_dfd, "../topfile",
> + O_RDONLY|O_BENEATH, EPERM);
> + fail |= check_openat_fail(subdir_dfd, "../subdir/bottomfile",
> + O_RDONLY|O_BENEATH, EPERM);
> +
> + /* Can't open paths starting with "/" */
> + fail |= check_openat_fail(AT_FDCWD, "/etc/passwd",
> + O_RDONLY|O_BENEATH, EPERM);
> + fail |= check_openat_fail(dot_dfd, "/etc/passwd",
> + O_RDONLY|O_BENEATH, EPERM);
> + fail |= check_openat_fail(subdir_dfd, "/etc/passwd",
> + O_RDONLY|O_BENEATH, EPERM);
> + /* Can't sneak around constraints with symlinks */
> + fail |= check_openat_fail(subdir_dfd, "symlinkup",
> + O_RDONLY|O_BENEATH, EPERM);
> + fail |= check_openat_fail(subdir_dfd, "symlinkout",
> + O_RDONLY|O_BENEATH, EPERM);
> +#else
> + printf("Skipping O_BENEATH tests due to missing #define\n");
> +#endif
> + fail |= check_proc();
> +
> + return fail ? -1 : 0;
> +}
> --
> 2.1.0.rc2.206.gedb03e5
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@xxxxxxxxxxxxxxx
> More majordomo info at http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at http://www.tux.org/lkml/
>

--
Dave Chinner
david@xxxxxxxxxxxxx
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/