blob: 4a389d5a2cdc50fb104ce3c1346656d7217bd6dc [file] [log] [blame]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "arc/appfuse/data_filter.h"
#include <linux/fuse.h>
#include <sys/socket.h>
#include <base/bind.h>
#include <base/posix/eintr_wrapper.h>
#include <base/run_loop.h>
#include <base/test/task_environment.h>
#include <gtest/gtest.h>
namespace arc {
namespace appfuse {
class DataFilterTest : public testing::Test {
public:
DataFilterTest() = default;
~DataFilterTest() override = default;
void SetUp() override {
data_filter_.set_on_stopped_callback(
base::Bind(&DataFilterTest::OnStopped, base::Unretained(this)));
int raw_socks[2];
ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, raw_socks));
fd_dev_ = base::ScopedFD(raw_socks[0]);
fd_app_ = data_filter_.Start(base::ScopedFD(raw_socks[1]));
ASSERT_TRUE(fd_app_.is_valid());
}
void TearDown() override {
if (stop_expected_) {
run_loop_.Run();
EXPECT_TRUE(was_stopped_);
}
}
// Reads filtered data which /dev/fuse sent to app.
bool ReadInData(fuse_in_header* header, void* body, size_t body_size) {
std::vector<char> buf(sizeof(*header) + body_size);
if (HANDLE_EINTR(read(fd_app_.get(), buf.data(), buf.size())) != buf.size())
return false;
memcpy(header, buf.data(), sizeof(*header));
memcpy(body, buf.data() + sizeof(*header), body_size);
return true;
}
// Reads filtered data which app sent to /dev/fuse.
bool ReadOutData(fuse_out_header* header, void* body, size_t body_size) {
std::vector<char> buf(sizeof(*header) + body_size);
if (HANDLE_EINTR(read(fd_dev_.get(), buf.data(), buf.size())) != buf.size())
return false;
memcpy(header, buf.data(), sizeof(*header));
memcpy(body, buf.data() + sizeof(*header), body_size);
return true;
}
// Writes data to the filter which will be sent to app.
bool WriteInData(const fuse_in_header& header,
const void* body,
size_t body_size) {
std::vector<char> buf(sizeof(header) + body_size);
memcpy(buf.data(), &header, sizeof(header));
memcpy(buf.data() + sizeof(header), body, body_size);
return HANDLE_EINTR(write(fd_dev_.get(), buf.data(), buf.size())) ==
buf.size();
}
// Writes data to the filter which will be sent to /dev/fuse.
bool WriteOutData(const fuse_out_header& header,
const void* body,
size_t body_size) {
std::vector<char> buf(sizeof(header) + body_size);
memcpy(buf.data(), &header, sizeof(header));
memcpy(buf.data() + sizeof(header), body, body_size);
return HANDLE_EINTR(write(fd_app_.get(), buf.data(), buf.size())) ==
buf.size();
}
void ExpectStop() { stop_expected_ = true; }
private:
void OnStopped() {
EXPECT_FALSE(was_stopped_);
was_stopped_ = true;
run_loop_.Quit();
}
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::ThreadingMode::MAIN_THREAD_ONLY};
base::RunLoop run_loop_;
DataFilter data_filter_;
base::ScopedFD fd_app_; // App-side FD connected to the filter.
base::ScopedFD fd_dev_; // /dev/fuse-side FD connected to the filter.
bool was_stopped_ = false;
bool stop_expected_ = false;
DISALLOW_COPY_AND_ASSIGN(DataFilterTest);
};
TEST_F(DataFilterTest, ValidRequestAndResponse) {
constexpr int kUnique = 123;
constexpr mode_t kMode = S_IFREG | 0777;
{ // GETATTR request from /dev/fuse to DataFitler.
fuse_in_header header = {};
fuse_getattr_in body = {};
header.len = sizeof(header) + sizeof(body);
header.opcode = FUSE_GETATTR;
header.unique = kUnique;
ASSERT_TRUE(WriteInData(header, &body, sizeof(body)));
}
{ // DataFilter passes GETATTR to the app.
fuse_in_header header = {};
fuse_getattr_in body = {};
ASSERT_TRUE(ReadInData(&header, &body, sizeof(body)));
EXPECT_EQ(FUSE_GETATTR, header.opcode);
EXPECT_EQ(kUnique, header.unique);
}
{ // GETATTR response from app to DataFilter.
fuse_out_header header = {};
fuse_attr_out body = {};
header.len = sizeof(header) + sizeof(body);
header.unique = kUnique;
body.attr.mode = kMode;
ASSERT_TRUE(WriteOutData(header, &body, sizeof(body)));
}
{ // DataFilter passes the response to /dev/fuse.
fuse_out_header header = {};
fuse_attr_out body = {};
ASSERT_TRUE(ReadOutData(&header, &body, sizeof(body)));
EXPECT_EQ(kUnique, header.unique);
EXPECT_EQ(kMode, body.attr.mode);
}
}
TEST_F(DataFilterTest, ValidRequestAndErrorResponse) {
constexpr int kUnique = 123;
{ // GETATTR request from /dev/fuse to DataFitler.
fuse_in_header header = {};
fuse_getattr_in body = {};
header.len = sizeof(header) + sizeof(body);
header.opcode = FUSE_GETATTR;
header.unique = kUnique;
ASSERT_TRUE(WriteInData(header, &body, sizeof(body)));
}
{ // DataFilter passes GETATTR to the app.
fuse_in_header header = {};
fuse_getattr_in body = {};
ASSERT_TRUE(ReadInData(&header, &body, sizeof(body)));
EXPECT_EQ(FUSE_GETATTR, header.opcode);
EXPECT_EQ(kUnique, header.unique);
}
{ // ENOENT response from app to DataFilter.
fuse_out_header header = {};
header.len = sizeof(header);
header.unique = kUnique;
header.error = -ENOENT;
ASSERT_TRUE(WriteOutData(header, nullptr, 0));
}
{ // DataFilter passes the response to /dev/fuse.
fuse_out_header header = {};
ASSERT_TRUE(ReadOutData(&header, nullptr, 0));
EXPECT_EQ(kUnique, header.unique);
EXPECT_EQ(-ENOENT, header.error);
}
}
TEST_F(DataFilterTest, InvalidFileMode) {
constexpr int kUnique = 123;
constexpr mode_t kMode = S_IFBLK | 0777; // S_IFBLK is not allowed.
ExpectStop();
{ // GETATTR request from /dev/fuse to DataFitler.
fuse_in_header header = {};
fuse_getattr_in body = {};
header.len = sizeof(header) + sizeof(body);
header.opcode = FUSE_GETATTR;
header.unique = kUnique;
ASSERT_TRUE(WriteInData(header, &body, sizeof(body)));
}
{ // DataFilter passes GETATTR to the app.
fuse_in_header header = {};
fuse_getattr_in body = {};
ASSERT_TRUE(ReadInData(&header, &body, sizeof(body)));
EXPECT_EQ(FUSE_GETATTR, header.opcode);
EXPECT_EQ(kUnique, header.unique);
}
{ // GETATTR response from app to DataFilter.
fuse_out_header header = {};
fuse_attr_out body = {};
header.len = sizeof(header) + sizeof(body);
header.unique = kUnique;
body.attr.mode = kMode;
ASSERT_TRUE(WriteOutData(header, &body, sizeof(body)));
}
{ // DataFilter rejects response because of the invalid mode value.
fuse_out_header header = {};
fuse_attr_out body = {};
EXPECT_FALSE(ReadOutData(&header, &body, sizeof(body)));
}
}
TEST_F(DataFilterTest, InvalidInHeader) {
constexpr int kUnique = 123;
ExpectStop();
{ // GETATTR request from /dev/fuse to DataFitler.
fuse_in_header header = {};
fuse_getattr_in body = {};
header.len = sizeof(header) - 1; // Invalid len.
header.opcode = FUSE_GETATTR;
header.unique = kUnique;
ASSERT_TRUE(WriteInData(header, &body, sizeof(body)));
}
{ // DataFilter rejects it because the header is invalid.
fuse_in_header header = {};
fuse_getattr_in body = {};
ASSERT_FALSE(ReadInData(&header, &body, sizeof(body)));
}
}
TEST_F(DataFilterTest, InvalidOutHeader) {
constexpr int kUnique = 123;
constexpr mode_t kMode = S_IFREG | 0777;
ExpectStop();
{ // GETATTR request from /dev/fuse to DataFitler.
fuse_in_header header = {};
fuse_getattr_in body = {};
header.len = sizeof(header) + sizeof(body);
header.opcode = FUSE_GETATTR;
header.unique = kUnique;
ASSERT_TRUE(WriteInData(header, &body, sizeof(body)));
}
{ // DataFilter passes GETATTR to the app.
fuse_in_header header = {};
fuse_getattr_in body = {};
ASSERT_TRUE(ReadInData(&header, &body, sizeof(body)));
EXPECT_EQ(FUSE_GETATTR, header.opcode);
EXPECT_EQ(kUnique, header.unique);
}
{ // GETATTR response from app to DataFilter.
fuse_out_header header = {};
fuse_attr_out body = {};
header.len = sizeof(header) - 1; // Invalid len.
header.unique = kUnique;
body.attr.mode = kMode;
ASSERT_TRUE(WriteOutData(header, &body, sizeof(body)));
}
{ // DataFilter rejects response because of the invalid header.
fuse_out_header header = {};
fuse_attr_out body = {};
EXPECT_FALSE(ReadOutData(&header, &body, sizeof(body)));
}
}
TEST_F(DataFilterTest, NotSupported) {
constexpr int kUnique = 123;
{ // MKNOD should not be supported.
fuse_in_header header = {};
fuse_mknod_in body = {};
header.len = sizeof(header) + sizeof(body);
header.opcode = FUSE_MKNOD;
header.unique = kUnique;
ASSERT_TRUE(WriteInData(header, &body, sizeof(body)));
}
{ // ENOSYS is written to /dev/fuse.
fuse_out_header header = {};
ASSERT_TRUE(ReadOutData(&header, nullptr, 0));
EXPECT_EQ(-ENOSYS, header.error);
EXPECT_EQ(kUnique, header.unique);
}
}
} // namespace appfuse
} // namespace arc