summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
authorBoyuan He <heboyuan@google.com>2020-08-18 01:46:39 +0000
committerAndrei Vagin <avagin@gmail.com>2020-09-11 13:35:25 -0700
commitfc1196ee65a53004e3e8b97ec5d57404552b90b8 (patch)
treed20b9aa20ece6e0ffe36ed5fe3a9b4becca2cb70 /test
parent2c974036d664e0eb3e5b567f4d1082af19626889 (diff)
Implement FUSE_OPEN/OPENDIR
Fixes #3174
Diffstat (limited to 'test')
-rw-r--r--test/fuse/BUILD5
-rw-r--r--test/fuse/linux/BUILD13
-rw-r--r--test/fuse/linux/fuse_base.cc50
-rw-r--r--test/fuse/linux/fuse_base.h9
-rw-r--r--test/fuse/linux/open_test.cc124
-rw-r--r--test/util/BUILD1
-rw-r--r--test/util/fuse_util.cc59
-rw-r--r--test/util/fuse_util.h4
8 files changed, 239 insertions, 26 deletions
diff --git a/test/fuse/BUILD b/test/fuse/BUILD
index 385920e17..209030ff1 100644
--- a/test/fuse/BUILD
+++ b/test/fuse/BUILD
@@ -6,3 +6,8 @@ syscall_test(
fuse = "True",
test = "//test/fuse/linux:stat_test",
)
+
+syscall_test(
+ fuse = "True",
+ test = "//test/fuse/linux:open_test",
+)
diff --git a/test/fuse/linux/BUILD b/test/fuse/linux/BUILD
index e4a614e11..1d989a2f4 100644
--- a/test/fuse/linux/BUILD
+++ b/test/fuse/linux/BUILD
@@ -18,6 +18,19 @@ cc_binary(
],
)
+cc_binary(
+ name = "open_test",
+ testonly = 1,
+ srcs = ["open_test.cc"],
+ deps = [
+ gtest,
+ ":fuse_base",
+ "//test/util:fuse_util",
+ "//test/util:test_main",
+ "//test/util:test_util",
+ ],
+)
+
cc_library(
name = "fuse_base",
testonly = 1,
diff --git a/test/fuse/linux/fuse_base.cc b/test/fuse/linux/fuse_base.cc
index a9fe1044e..e734100b1 100644
--- a/test/fuse/linux/fuse_base.cc
+++ b/test/fuse/linux/fuse_base.cc
@@ -117,6 +117,16 @@ uint32_t FuseTest::GetServerTotalReceivedBytes() {
static_cast<uint32_t>(FuseTestCmd::kGetTotalReceivedBytes));
}
+// Sends the `kSkipRequest` command to the FUSE server, which would skip
+// current stored request data.
+void FuseTest::SkipServerActualRequest() {
+ uint32_t cmd = static_cast<uint32_t>(FuseTestCmd::kSkipRequest);
+ EXPECT_THAT(RetryEINTR(write)(sock_[0], &cmd, sizeof(cmd)),
+ SyscallSucceedsWithValue(sizeof(cmd)));
+
+ WaitServerComplete();
+}
+
// Sends the `kSetInodeLookup` command, expected mode, and the path of the
// inode to create under the mount point.
void FuseTest::SetServerInodeLookup(const std::string& path, mode_t mode) {
@@ -284,6 +294,9 @@ void FuseTest::ServerHandleCommand() {
case FuseTestCmd::kGetNumUnsentResponses:
ServerSendData(static_cast<uint32_t>(responses_.RemainingBlocks()));
break;
+ case FuseTestCmd::kSkipRequest:
+ ServerSkipReceivedRequest();
+ break;
default:
FAIL() << "Unknown FuseTestCmd " << cmd;
break;
@@ -314,32 +327,7 @@ void FuseTest::ServerReceiveInodeLookup() {
.len = out_len,
.error = 0,
};
- struct fuse_entry_out out_payload = {
- .nodeid = nodeid_,
- .generation = 0,
- .entry_valid = 0,
- .attr_valid = 0,
- .entry_valid_nsec = 0,
- .attr_valid_nsec = 0,
- .attr =
- (struct fuse_attr){
- .ino = nodeid_,
- .size = 512,
- .blocks = 4,
- .atime = 0,
- .mtime = 0,
- .ctime = 0,
- .atimensec = 0,
- .mtimensec = 0,
- .ctimensec = 0,
- .mode = mode,
- .nlink = 2,
- .uid = 1234,
- .gid = 4321,
- .rdev = 12,
- .blksize = 4096,
- },
- };
+ struct fuse_entry_out out_payload = DefaultEntryOut(mode, nodeid_);
// Since this is only used in test, nodeid_ is simply increased by 1 to
// comply with the unqiueness of different path.
++nodeid_;
@@ -363,6 +351,15 @@ void FuseTest::ServerSendReceivedRequest() {
SyscallSucceedsWithValue(mem_block.len));
}
+// Skip the request pointed by current cursor.
+void FuseTest::ServerSkipReceivedRequest() {
+ if (requests_.End()) {
+ FAIL() << "No more received request.";
+ return;
+ }
+ requests_.Next();
+}
+
// Handles FUSE request. Reads request from /dev/fuse, checks if it has the
// same opcode as expected, and responds with the saved fake FUSE response.
// The FUSE request is copied to the serial buffer and can be retrieved one-
@@ -390,6 +387,7 @@ void FuseTest::ServerProcessFuseRequest() {
requests_.AddMemBlock(in_header->opcode, buf.data(), len);
+ if (in_header->opcode == FUSE_RELEASE) return;
// Check if there is a corresponding response.
if (responses_.End()) {
GTEST_NONFATAL_FAILURE_("No more FUSE response is expected");
diff --git a/test/fuse/linux/fuse_base.h b/test/fuse/linux/fuse_base.h
index a21b4bb8d..2474b763f 100644
--- a/test/fuse/linux/fuse_base.h
+++ b/test/fuse/linux/fuse_base.h
@@ -42,6 +42,7 @@ enum class FuseTestCmd {
kGetNumUnconsumedRequests,
kGetNumUnsentResponses,
kGetTotalReceivedBytes,
+ kSkipRequest,
};
// Holds the information of a memory block in a serial buffer.
@@ -158,6 +159,10 @@ class FuseTest : public ::testing::Test {
// bytes from /dev/fuse.
uint32_t GetServerTotalReceivedBytes();
+ // Called by the testing thread to ask the FUSE server to skip stored
+ // request data.
+ void SkipServerActualRequest();
+
protected:
TempPath mount_point_;
@@ -211,6 +216,10 @@ class FuseTest : public ::testing::Test {
// Sends a uint32_t data via socket.
inline void ServerSendData(uint32_t data);
+ // The FUSE server side's corresponding code of `SkipServerActualRequest()`.
+ // Handles `kSkipRequest` command. Skip the request pointed by current cursor.
+ void ServerSkipReceivedRequest();
+
// Handles FUSE request sent to /dev/fuse by its saved responses.
void ServerProcessFuseRequest();
diff --git a/test/fuse/linux/open_test.cc b/test/fuse/linux/open_test.cc
new file mode 100644
index 000000000..ed0641587
--- /dev/null
+++ b/test/fuse/linux/open_test.cc
@@ -0,0 +1,124 @@
+// Copyright 2020 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/fuse.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <string>
+
+#include "gtest/gtest.h"
+#include "test/fuse/linux/fuse_base.h"
+#include "test/util/fuse_util.h"
+#include "test/util/test_util.h"
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+class OpenTest : public FuseTest {
+ protected:
+ const std::string test_file_ = "test_file";
+ const mode_t regular_file_ = S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO;
+
+ struct fuse_open_out out_payload_ = {
+ .fh = 1,
+ .open_flags = O_RDWR,
+ };
+};
+
+TEST_F(OpenTest, RegularFile) {
+ const std::string test_file_path =
+ JoinPath(mount_point_.path().c_str(), test_file_);
+ SetServerInodeLookup(test_file_, regular_file_);
+
+ struct fuse_out_header out_header = {
+ .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_open_out),
+ };
+ auto iov_out = FuseGenerateIovecs(out_header, out_payload_);
+ SetServerResponse(FUSE_OPEN, iov_out);
+ FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_path.c_str(), O_RDWR));
+
+ struct fuse_in_header in_header;
+ struct fuse_open_in in_payload;
+ auto iov_in = FuseGenerateIovecs(in_header, in_payload);
+ GetServerActualRequest(iov_in);
+
+ EXPECT_EQ(in_header.len, sizeof(in_header) + sizeof(in_payload));
+ EXPECT_EQ(in_header.opcode, FUSE_OPEN);
+ EXPECT_EQ(in_payload.flags, O_RDWR);
+ EXPECT_THAT(fcntl(fd.get(), F_GETFL), SyscallSucceedsWithValue(O_RDWR));
+}
+
+TEST_F(OpenTest, SetNoOpen) {
+ const std::string test_file_path =
+ JoinPath(mount_point_.path().c_str(), test_file_);
+ SetServerInodeLookup(test_file_, regular_file_);
+
+ // ENOSYS indicates open is not implemented.
+ struct fuse_out_header out_header = {
+ .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_open_out),
+ .error = -ENOSYS,
+ };
+ auto iov_out = FuseGenerateIovecs(out_header, out_payload_);
+ SetServerResponse(FUSE_OPEN, iov_out);
+ ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_path.c_str(), O_RDWR));
+ SkipServerActualRequest();
+
+ // check open doesn't send new request.
+ uint32_t recieved_before = GetServerTotalReceivedBytes();
+ ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_path.c_str(), O_RDWR));
+ EXPECT_EQ(GetServerTotalReceivedBytes(), recieved_before);
+}
+
+TEST_F(OpenTest, OpenFail) {
+ struct fuse_out_header out_header = {
+ .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_open_out),
+ .error = -ENOENT,
+ };
+
+ auto iov_out = FuseGenerateIovecs(out_header, out_payload_);
+ SetServerResponse(FUSE_OPENDIR, iov_out);
+ ASSERT_THAT(open(mount_point_.path().c_str(), O_RDWR),
+ SyscallFailsWithErrno(ENOENT));
+
+ struct fuse_in_header in_header;
+ struct fuse_open_in in_payload;
+ auto iov_in = FuseGenerateIovecs(in_header, in_payload);
+ GetServerActualRequest(iov_in);
+
+ EXPECT_EQ(in_header.len, sizeof(in_header) + sizeof(in_payload));
+ EXPECT_EQ(in_header.opcode, FUSE_OPENDIR);
+ EXPECT_EQ(in_payload.flags, O_RDWR);
+}
+
+TEST_F(OpenTest, DirectoryFlagOnRegularFile) {
+ const std::string test_file_path =
+ JoinPath(mount_point_.path().c_str(), test_file_);
+
+ SetServerInodeLookup(test_file_, regular_file_);
+ ASSERT_THAT(open(test_file_path.c_str(), O_RDWR | O_DIRECTORY),
+ SyscallFailsWithErrno(ENOTDIR));
+}
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/util/BUILD b/test/util/BUILD
index b0c2c2a5a..fc5fb3a8d 100644
--- a/test/util/BUILD
+++ b/test/util/BUILD
@@ -48,6 +48,7 @@ cc_library(
cc_library(
name = "fuse_util",
testonly = 1,
+ srcs = ["fuse_util.cc"],
hdrs = ["fuse_util.h"],
)
diff --git a/test/util/fuse_util.cc b/test/util/fuse_util.cc
new file mode 100644
index 000000000..5b10a9e45
--- /dev/null
+++ b/test/util/fuse_util.cc
@@ -0,0 +1,59 @@
+// Copyright 2020 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "test/util/fuse_util.h"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <string>
+
+namespace gvisor {
+namespace testing {
+
+// Create response body with specified mode and nodeID.
+fuse_entry_out DefaultEntryOut(mode_t mode, uint64_t node_id) {
+ const int time_sec = 1595436289;
+ const int time_nsec = 134150844;
+ struct fuse_entry_out default_entry_out = {
+ .nodeid = node_id,
+ .generation = 0,
+ .entry_valid = time_sec,
+ .attr_valid = time_sec,
+ .entry_valid_nsec = time_nsec,
+ .attr_valid_nsec = time_nsec,
+ .attr =
+ (struct fuse_attr){
+ .ino = node_id,
+ .size = 512,
+ .blocks = 4,
+ .atime = time_sec,
+ .mtime = time_sec,
+ .ctime = time_sec,
+ .atimensec = time_nsec,
+ .mtimensec = time_nsec,
+ .ctimensec = time_nsec,
+ .mode = mode,
+ .nlink = 2,
+ .uid = 1234,
+ .gid = 4321,
+ .rdev = 12,
+ .blksize = 4096,
+ },
+ };
+ return default_entry_out;
+};
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/util/fuse_util.h b/test/util/fuse_util.h
index 5f5182b96..1f1bf64a4 100644
--- a/test/util/fuse_util.h
+++ b/test/util/fuse_util.h
@@ -15,6 +15,7 @@
#ifndef GVISOR_TEST_UTIL_FUSE_UTIL_H_
#define GVISOR_TEST_UTIL_FUSE_UTIL_H_
+#include <linux/fuse.h>
#include <sys/uio.h>
#include <string>
@@ -62,6 +63,9 @@ std::vector<struct iovec> FuseGenerateIovecs(T &first, Types &...args) {
return first_iovec;
}
+// Return a fuse_entry_out FUSE server response body.
+fuse_entry_out DefaultEntryOut(mode_t mode, uint64_t nodeId);
+
} // namespace testing
} // namespace gvisor
#endif // GVISOR_TEST_UTIL_FUSE_UTIL_H_