svn commit: r345082 - projects/fuse2/tests/sys/fs/fuse

Alan Somers asomers at FreeBSD.org
Tue Mar 12 22:26:01 UTC 2019


Author: asomers
Date: Tue Mar 12 22:25:59 2019
New Revision: 345082
URL: https://svnweb.freebsd.org/changeset/base/345082

Log:
  fuse(4): add tests for opendir and readdir
  
  Sponsored by:	The FreeBSD Foundation

Added:
  projects/fuse2/tests/sys/fs/fuse/readdir.cc   (contents, props changed)
Modified:
  projects/fuse2/tests/sys/fs/fuse/Makefile
  projects/fuse2/tests/sys/fs/fuse/mockfs.cc
  projects/fuse2/tests/sys/fs/fuse/mockfs.hh
  projects/fuse2/tests/sys/fs/fuse/opendir.cc

Modified: projects/fuse2/tests/sys/fs/fuse/Makefile
==============================================================================
--- projects/fuse2/tests/sys/fs/fuse/Makefile	Tue Mar 12 21:03:56 2019	(r345081)
+++ projects/fuse2/tests/sys/fs/fuse/Makefile	Tue Mar 12 22:25:59 2019	(r345082)
@@ -17,6 +17,7 @@ ATF_TESTS_CXX+=	mknod
 ATF_TESTS_CXX+=	open
 ATF_TESTS_CXX+=	opendir
 ATF_TESTS_CXX+=	read
+ATF_TESTS_CXX+=	readdir
 ATF_TESTS_CXX+=	readlink
 ATF_TESTS_CXX+=	release
 ATF_TESTS_CXX+=	rename
@@ -91,6 +92,11 @@ SRCS.read+=	getmntopts.c
 SRCS.read+=	mockfs.cc
 SRCS.read+=	read.cc
 SRCS.read+=	utils.cc
+
+SRCS.readdir+=	getmntopts.c
+SRCS.readdir+=	mockfs.cc
+SRCS.readdir+=	readdir.cc
+SRCS.readdir+=	utils.cc
 
 SRCS.readlink+=	getmntopts.c
 SRCS.readlink+=	mockfs.cc

Modified: projects/fuse2/tests/sys/fs/fuse/mockfs.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fuse/mockfs.cc	Tue Mar 12 21:03:56 2019	(r345081)
+++ projects/fuse2/tests/sys/fs/fuse/mockfs.cc	Tue Mar 12 22:25:59 2019	(r345082)
@@ -157,9 +157,17 @@ void debug_fuseop(const mockfs_buf_in *in)
 			printf(" flags=%#x mode=%#o",
 				in->body.open.flags, in->body.open.mode);
 			break;
+		case FUSE_OPENDIR:
+			printf(" flags=%#x mode=%#o",
+				in->body.opendir.flags, in->body.opendir.mode);
+			break;
 		case FUSE_READ:
 			printf(" offset=%lu size=%u", in->body.read.offset,
 				in->body.read.size);
+			break;
+		case FUSE_READDIR:
+			printf(" offset=%lu size=%u", in->body.readdir.offset,
+				in->body.readdir.size);
 			break;
 		case FUSE_SETATTR:
 			printf(" valid=%#x", in->body.setattr.valid);

Modified: projects/fuse2/tests/sys/fs/fuse/mockfs.hh
==============================================================================
--- projects/fuse2/tests/sys/fs/fuse/mockfs.hh	Tue Mar 12 21:03:56 2019	(r345081)
+++ projects/fuse2/tests/sys/fs/fuse/mockfs.hh	Tue Mar 12 22:25:59 2019	(r345082)
@@ -84,7 +84,9 @@ union fuse_payloads_in {
 	fuse_mkdir_in	mkdir;
 	fuse_mknod_in	mknod;
 	fuse_open_in	open;
+	fuse_open_in	opendir;
 	fuse_read_in	read;
+	fuse_read_in	readdir;
 	fuse_release_in	release;
 	fuse_rename_in	rename;
 	char		rmdir[0];

Modified: projects/fuse2/tests/sys/fs/fuse/opendir.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fuse/opendir.cc	Tue Mar 12 21:03:56 2019	(r345081)
+++ projects/fuse2/tests/sys/fs/fuse/opendir.cc	Tue Mar 12 22:25:59 2019	(r345082)
@@ -29,6 +29,7 @@
  */
 
 extern "C" {
+#include <dirent.h>
 #include <fcntl.h>
 }
 
@@ -100,7 +101,7 @@ TEST_F(Opendir, eperm)
 	EXPECT_EQ(EPERM, errno);
 }
 
-TEST_F(Opendir, ok)
+TEST_F(Opendir, open)
 {
 	const char FULLPATH[] = "mountpoint/some_dir";
 	const char RELPATH[] = "some_dir";
@@ -120,4 +121,36 @@ TEST_F(Opendir, ok)
 	}));
 
 	EXPECT_LE(0, open(FULLPATH, O_DIRECTORY)) << strerror(errno);
+}
+
+TEST_F(Opendir, opendir)
+{
+	const char FULLPATH[] = "mountpoint/some_dir";
+	const char RELPATH[] = "some_dir";
+	uint64_t ino = 42;
+
+	expect_lookup(RELPATH, ino);
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([](auto in) {
+			return (in->header.opcode == FUSE_STATFS);
+		}, Eq(true)),
+		_)
+	).WillOnce(Invoke([=](auto in, auto out) {
+		out->header.unique = in->header.unique;
+		SET_OUT_HEADER_LEN(out, statfs);
+	}));
+
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in->header.opcode == FUSE_OPENDIR &&
+				in->header.nodeid == ino);
+		}, Eq(true)),
+		_)
+	).WillOnce(Invoke([=](auto in, auto out) {
+		out->header.unique = in->header.unique;
+		SET_OUT_HEADER_LEN(out, open);
+	}));
+
+	errno = 0;
+	EXPECT_NE(NULL, opendir(FULLPATH)) << strerror(errno);
 }

Added: projects/fuse2/tests/sys/fs/fuse/readdir.cc
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ projects/fuse2/tests/sys/fs/fuse/readdir.cc	Tue Mar 12 22:25:59 2019	(r345082)
@@ -0,0 +1,334 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2019 The FreeBSD Foundation
+ *
+ * This software was developed by BFF Storage Systems, LLC under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+extern "C" {
+#include <dirent.h>
+#include <fcntl.h>
+}
+
+#include "mockfs.hh"
+#include "utils.hh"
+
+using namespace testing;
+using namespace std;
+
+class Readdir: public FuseTest {
+const static uint64_t FH = 0xdeadbeef1a7ebabe;
+public:
+void expect_lookup(const char *relpath, uint64_t ino)
+{
+	EXPECT_LOOKUP(1, relpath).WillRepeatedly(Invoke([=](auto in, auto out) {
+		out->header.unique = in->header.unique;
+		SET_OUT_HEADER_LEN(out, entry);
+		out->body.entry.attr.mode = S_IFDIR | 0755;
+		out->body.entry.nodeid = ino;
+		out->body.entry.attr_valid = UINT64_MAX;
+	}));
+}
+
+void expect_opendir(uint64_t ino)
+{
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([](auto in) {
+			return (in->header.opcode == FUSE_STATFS);
+		}, Eq(true)),
+		_)
+	).WillOnce(Invoke([=](auto in, auto out) {
+		out->header.unique = in->header.unique;
+		SET_OUT_HEADER_LEN(out, statfs);
+	}));
+
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in->header.opcode == FUSE_OPENDIR &&
+				in->header.nodeid == ino);
+		}, Eq(true)),
+		_)
+	).WillOnce(Invoke([=](auto in, auto out) {
+		out->header.unique = in->header.unique;
+		out->header.len = sizeof(out->header);
+		SET_OUT_HEADER_LEN(out, open);
+		out->body.open.fh = FH;
+	}));
+}
+
+void expect_readdir(uint64_t ino, uint64_t off, vector<struct dirent> &ents)
+{
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in->header.opcode == FUSE_READDIR &&
+				in->header.nodeid == ino &&
+				in->body.readdir.offset == off);
+		}, Eq(true)),
+		_)
+	).WillRepeatedly(Invoke([=](auto in, auto out) {
+		struct fuse_dirent *fde = (struct fuse_dirent*)out->body.bytes;
+		int i = 0;
+
+		out->header.unique = in->header.unique;
+		out->header.error = 0;
+		out->header.len = 0;
+
+		for (const auto& it: ents) {
+			size_t entlen, entsize;
+
+			fde->ino = it.d_fileno;
+			fde->off = it.d_off;
+			fde->type = it.d_type;
+			fde->namelen = it.d_namlen;
+			strncpy(fde->name, it.d_name, it.d_namlen);
+			entlen = FUSE_NAME_OFFSET + fde->namelen;
+			entsize = FUSE_DIRENT_SIZE(fde);
+			/* 
+			 * The FUSE protocol does not require zeroing out the
+			 * unused portion of the name.  But it's a good
+			 * practice to prevent information disclosure to the
+			 * FUSE client, even though the client is usually the
+			 * kernel
+			 */
+			memset(fde->name + fde->namelen, 0, entsize - entlen);
+			if (out->header.len + entsize > in->body.read.size) {
+				printf("Overflow in readdir expectation: i=%d\n"
+					, i);
+				break;
+			}
+			out->header.len += entsize;
+			fde = (struct fuse_dirent*)
+				((long*)fde + entsize / sizeof(long));
+			i++;
+		}
+		out->header.len += sizeof(out->header);
+	}));
+
+}
+};
+
+/* FUSE_READDIR returns nothing but "." and ".." */
+TEST_F(Readdir, dots)
+{
+	const char FULLPATH[] = "mountpoint/some_dir";
+	const char RELPATH[] = "some_dir";
+	uint64_t ino = 42;
+	DIR *dir;
+	struct dirent *de;
+	vector<struct dirent> ents(2);
+	vector<struct dirent> empty_ents(0);
+	const char *dot = ".";
+	const char *dotdot = "..";
+
+	expect_lookup(RELPATH, ino);
+	expect_opendir(ino);
+	ents[0].d_fileno = 2;
+	ents[0].d_off = 2000;
+	ents[0].d_namlen = strlen(dotdot);
+	ents[0].d_type = DT_DIR;
+	strncpy(ents[0].d_name, dotdot, ents[0].d_namlen);
+	ents[1].d_fileno = 3;
+	ents[1].d_off = 3000;
+	ents[1].d_namlen = strlen(dot);
+	ents[1].d_type = DT_DIR;
+	strncpy(ents[1].d_name, dot, ents[1].d_namlen);
+	expect_readdir(ino, 0, ents);
+	expect_readdir(ino, 3000, empty_ents);
+
+	errno = 0;
+	dir = opendir(FULLPATH);
+	ASSERT_NE(NULL, dir) << strerror(errno);
+
+	errno = 0;
+	de = readdir(dir);
+	ASSERT_NE(NULL, de) << strerror(errno);
+	EXPECT_EQ(2ul, de->d_fileno);
+	/*
+	 * fuse(4) doesn't actually set d_off, which is ok for now because
+	 * nothing uses it.
+	 */
+	//EXPECT_EQ(2000, de->d_off);
+	EXPECT_EQ(DT_DIR, de->d_type);
+	EXPECT_EQ(2, de->d_namlen);
+	EXPECT_EQ(0, strcmp("..", de->d_name));
+
+	errno = 0;
+	de = readdir(dir);
+	ASSERT_NE(NULL, de) << strerror(errno);
+	EXPECT_EQ(3ul, de->d_fileno);
+	//EXPECT_EQ(3000, de->d_off);
+	EXPECT_EQ(DT_DIR, de->d_type);
+	EXPECT_EQ(1, de->d_namlen);
+	EXPECT_EQ(0, strcmp(".", de->d_name));
+
+	ASSERT_EQ(NULL, readdir(dir));
+	ASSERT_EQ(0, errno);
+
+	/* Deliberately leak dir.  RELEASEDIR will be tested separately */
+}
+
+TEST_F(Readdir, eio)
+{
+	const char FULLPATH[] = "mountpoint/some_dir";
+	const char RELPATH[] = "some_dir";
+	uint64_t ino = 42;
+	DIR *dir;
+	struct dirent *de;
+
+	expect_lookup(RELPATH, ino);
+	expect_opendir(ino);
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in->header.opcode == FUSE_READDIR &&
+				in->header.nodeid == ino &&
+				in->body.readdir.offset == 0);
+		}, Eq(true)),
+		_)
+	).WillOnce(Invoke(ReturnErrno(EIO)));
+
+	errno = 0;
+	dir = opendir(FULLPATH);
+	ASSERT_NE(NULL, dir) << strerror(errno);
+
+	errno = 0;
+	de = readdir(dir);
+	ASSERT_EQ(NULL, de);
+	ASSERT_EQ(EIO, errno);
+
+	/* Deliberately leak dir.  RELEASEDIR will be tested separately */
+}
+
+/*
+ * FUSE_READDIR returns nothing, not even "." and "..".  This is legal, though
+ * the filesystem obviously won't be fully functional.
+ */
+TEST_F(Readdir, nodots)
+{
+	const char FULLPATH[] = "mountpoint/some_dir";
+	const char RELPATH[] = "some_dir";
+	uint64_t ino = 42;
+	DIR *dir;
+
+	expect_lookup(RELPATH, ino);
+	expect_opendir(ino);
+
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in->header.opcode == FUSE_READDIR &&
+				in->header.nodeid == ino);
+		}, Eq(true)),
+		_)
+	).WillOnce(Invoke([=](auto in, auto out) {
+		out->header.unique = in->header.unique;
+		out->header.error = 0;
+		out->header.len = sizeof(out->header);
+	}));
+
+	errno = 0;
+	dir = opendir(FULLPATH);
+	ASSERT_NE(NULL, dir) << strerror(errno);
+	errno = 0;
+	ASSERT_EQ(NULL, readdir(dir));
+	ASSERT_EQ(0, errno);
+
+	/* Deliberately leak dir.  RELEASEDIR will be tested separately */
+}
+
+/* telldir(3) and seekdir(3) should work with fuse */
+TEST_F(Readdir, seekdir)
+{
+	const char FULLPATH[] = "mountpoint/some_dir";
+	const char RELPATH[] = "some_dir";
+	uint64_t ino = 42;
+	DIR *dir;
+	struct dirent *de;
+	/*
+	 * use enough entries to be > 4096 bytes, so getdirentries must be
+	 * called
+	 * multiple times.
+	 */
+	vector<struct dirent> ents0(122), ents1(102), ents2(30);
+	long bookmark;
+	int i = 0;
+
+	for (auto& it: ents0) {
+		snprintf(it.d_name, MAXNAMLEN, "file.%d", i);
+		it.d_fileno = 2 + i;
+		it.d_off = (2 + i) * 1000;
+		it.d_namlen = strlen(it.d_name);
+		it.d_type = DT_REG;
+		i++;
+	}
+	for (auto& it: ents1) {
+		snprintf(it.d_name, MAXNAMLEN, "file.%d", i);
+		it.d_fileno = 2 + i;
+		it.d_off = (2 + i) * 1000;
+		it.d_namlen = strlen(it.d_name);
+		it.d_type = DT_REG;
+		i++;
+	}
+	for (auto& it: ents2) {
+		snprintf(it.d_name, MAXNAMLEN, "file.%d", i);
+		it.d_fileno = 2 + i;
+		it.d_off = (2 + i) * 1000;
+		it.d_namlen = strlen(it.d_name);
+		it.d_type = DT_REG;
+		i++;
+	}
+
+	expect_lookup(RELPATH, ino);
+	expect_opendir(ino);
+
+	expect_readdir(ino, 0, ents0);
+	expect_readdir(ino, 123000, ents1);
+	expect_readdir(ino, 225000, ents2);
+
+	errno = 0;
+	dir = opendir(FULLPATH);
+	ASSERT_NE(NULL, dir) << strerror(errno);
+
+	for (i=0; i < 128; i++) {
+		errno = 0;
+		de = readdir(dir);
+		ASSERT_NE(NULL, de) << strerror(errno);
+		EXPECT_EQ(2 + (ino_t)i, de->d_fileno);
+	}
+	bookmark = telldir(dir);
+
+	for (; i < 232; i++) {
+		errno = 0;
+		de = readdir(dir);
+		ASSERT_NE(NULL, de) << strerror(errno);
+		EXPECT_EQ(2 + (ino_t)i, de->d_fileno);
+	}
+
+	seekdir(dir, bookmark);
+	de = readdir(dir);
+	ASSERT_NE(NULL, de) << strerror(errno);
+	EXPECT_EQ(130ul, de->d_fileno);
+
+	/* Deliberately leak dir.  RELEASEDIR will be tested separately */
+}


More information about the svn-src-projects mailing list