From a0aecda12b9a76aa15a8c5175e15538574a05af7 Mon Sep 17 00:00:00 2001 From: Daichi Hirono Date: Tue, 8 Nov 2016 10:17:51 +0900 Subject: [PATCH] Add FuseAppLoop to libappfuse. The class is used at the app side (StorageManager) to parse FUSE commands. Bug: 32260320 Test: libappfuse_test Change-Id: I1ae2904d3290a041f1efbf8fc10ba032eda5449c --- libappfuse/Android.bp | 12 +- libappfuse/FuseAppLoop.cc | 221 +++++++++++++ libappfuse/FuseBridgeLoop.cc | 25 +- libappfuse/FuseBuffer.cc | 12 +- libappfuse/include/libappfuse/FuseAppLoop.h | 44 +++ .../include/libappfuse/FuseBridgeLoop.h | 14 +- libappfuse/include/libappfuse/FuseBuffer.h | 39 ++- libappfuse/tests/FuseAppLoopTest.cc | 307 ++++++++++++++++++ libappfuse/tests/FuseBridgeLoopTest.cc | 47 ++- libappfuse/tests/FuseBufferTest.cc | 6 +- 10 files changed, 678 insertions(+), 49 deletions(-) create mode 100644 libappfuse/FuseAppLoop.cc create mode 100644 libappfuse/include/libappfuse/FuseAppLoop.h create mode 100644 libappfuse/tests/FuseAppLoopTest.cc diff --git a/libappfuse/Android.bp b/libappfuse/Android.bp index 8b4615405..f729faf73 100644 --- a/libappfuse/Android.bp +++ b/libappfuse/Android.bp @@ -15,12 +15,20 @@ cc_library_shared { name: "libappfuse", defaults: ["libappfuse_defaults"], export_include_dirs: ["include"], - srcs: ["FuseBuffer.cc", "FuseBridgeLoop.cc"] + srcs: [ + "FuseAppLoop.cc", + "FuseBuffer.cc", + "FuseBridgeLoop.cc", + ] } cc_test { name: "libappfuse_test", defaults: ["libappfuse_defaults"], shared_libs: ["libappfuse"], - srcs: ["tests/FuseBridgeLoopTest.cc", "tests/FuseBufferTest.cc"] + srcs: [ + "tests/FuseAppLoopTest.cc", + "tests/FuseBridgeLoopTest.cc", + "tests/FuseBufferTest.cc", + ] } diff --git a/libappfuse/FuseAppLoop.cc b/libappfuse/FuseAppLoop.cc new file mode 100644 index 000000000..a31880e41 --- /dev/null +++ b/libappfuse/FuseAppLoop.cc @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * 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 specic language governing permissions and + * limitations under the License. + */ + +#include "libappfuse/FuseAppLoop.h" + +#include + +#include +#include + +namespace android { +namespace fuse { + +namespace { + +void HandleLookUp(FuseBuffer* buffer, FuseAppLoopCallback* callback) { + // AppFuse does not support directory structure now. + // It can lookup only files under the mount point. + if (buffer->request.header.nodeid != FUSE_ROOT_ID) { + LOG(ERROR) << "Nodeid is not FUSE_ROOT_ID."; + buffer->response.Reset(0, -ENOENT, buffer->request.header.unique); + return; + } + + // Ensure that the filename ends with 0. + const size_t filename_length = + buffer->request.header.len - sizeof(fuse_in_header); + if (buffer->request.lookup_name[filename_length - 1] != 0) { + LOG(ERROR) << "File name does not end with 0."; + buffer->response.Reset(0, -ENOENT, buffer->request.header.unique); + return; + } + + const uint64_t inode = + static_cast(atol(buffer->request.lookup_name)); + if (inode == 0 || inode == LONG_MAX) { + LOG(ERROR) << "Invalid filename"; + buffer->response.Reset(0, -ENOENT, buffer->request.header.unique); + return; + } + + const int64_t size = callback->OnGetSize(inode); + if (size < 0) { + buffer->response.Reset(0, size, buffer->request.header.unique); + return; + } + + buffer->response.Reset(sizeof(fuse_entry_out), 0, + buffer->request.header.unique); + buffer->response.entry_out.nodeid = inode; + buffer->response.entry_out.attr_valid = 10; + buffer->response.entry_out.entry_valid = 10; + buffer->response.entry_out.attr.ino = inode; + buffer->response.entry_out.attr.mode = S_IFREG | 0777; + buffer->response.entry_out.attr.size = size; +} + +void HandleGetAttr(FuseBuffer* buffer, FuseAppLoopCallback* callback) { + const uint64_t nodeid = buffer->request.header.nodeid; + int64_t size; + uint32_t mode; + if (nodeid == FUSE_ROOT_ID) { + size = 0; + mode = S_IFDIR | 0777; + } else { + size = callback->OnGetSize(buffer->request.header.nodeid); + if (size < 0) { + buffer->response.Reset(0, size, buffer->request.header.unique); + return; + } + mode = S_IFREG | 0777; + } + + buffer->response.Reset(sizeof(fuse_attr_out), 0, + buffer->request.header.unique); + buffer->response.attr_out.attr_valid = 10; + buffer->response.attr_out.attr.ino = nodeid; + buffer->response.attr_out.attr.mode = mode; + buffer->response.attr_out.attr.size = size; +} + +void HandleOpen(FuseBuffer* buffer, FuseAppLoopCallback* callback) { + const int32_t file_handle = callback->OnOpen(buffer->request.header.nodeid); + if (file_handle < 0) { + buffer->response.Reset(0, file_handle, buffer->request.header.unique); + return; + } + buffer->response.Reset(sizeof(fuse_open_out), kFuseSuccess, + buffer->request.header.unique); + buffer->response.open_out.fh = file_handle; +} + +void HandleFsync(FuseBuffer* buffer, FuseAppLoopCallback* callback) { + buffer->response.Reset(0, callback->OnFsync(buffer->request.header.nodeid), + buffer->request.header.unique); +} + +void HandleRelease(FuseBuffer* buffer, FuseAppLoopCallback* callback) { + buffer->response.Reset(0, callback->OnRelease(buffer->request.header.nodeid), + buffer->request.header.unique); +} + +void HandleRead(FuseBuffer* buffer, FuseAppLoopCallback* callback) { + const uint64_t unique = buffer->request.header.unique; + const uint64_t nodeid = buffer->request.header.nodeid; + const uint64_t offset = buffer->request.read_in.offset; + const uint32_t size = buffer->request.read_in.size; + + if (size > kFuseMaxRead) { + buffer->response.Reset(0, -EINVAL, buffer->request.header.unique); + return; + } + + const int32_t read_size = callback->OnRead(nodeid, offset, size, + buffer->response.read_data); + if (read_size < 0) { + buffer->response.Reset(0, read_size, buffer->request.header.unique); + return; + } + + buffer->response.ResetHeader(read_size, kFuseSuccess, unique); +} + +void HandleWrite(FuseBuffer* buffer, FuseAppLoopCallback* callback) { + const uint64_t unique = buffer->request.header.unique; + const uint64_t nodeid = buffer->request.header.nodeid; + const uint64_t offset = buffer->request.write_in.offset; + const uint32_t size = buffer->request.write_in.size; + + if (size > kFuseMaxWrite) { + buffer->response.Reset(0, -EINVAL, buffer->request.header.unique); + return; + } + + const int32_t write_size = callback->OnWrite(nodeid, offset, size, + buffer->request.write_data); + if (write_size < 0) { + buffer->response.Reset(0, write_size, buffer->request.header.unique); + return; + } + + buffer->response.Reset(sizeof(fuse_write_out), kFuseSuccess, unique); + buffer->response.write_out.size = write_size; +} + +} // namespace + +bool StartFuseAppLoop(int raw_fd, FuseAppLoopCallback* callback) { + base::unique_fd fd(raw_fd); + FuseBuffer buffer; + + LOG(DEBUG) << "Start fuse loop."; + while (callback->IsActive()) { + if (!buffer.request.Read(fd)) { + return false; + } + + const uint32_t opcode = buffer.request.header.opcode; + LOG(VERBOSE) << "Read a fuse packet, opcode=" << opcode; + switch (opcode) { + case FUSE_FORGET: + // Do not reply to FUSE_FORGET. + continue; + + case FUSE_LOOKUP: + HandleLookUp(&buffer, callback); + break; + + case FUSE_GETATTR: + HandleGetAttr(&buffer, callback); + break; + + case FUSE_OPEN: + HandleOpen(&buffer, callback); + break; + + case FUSE_READ: + HandleRead(&buffer, callback); + break; + + case FUSE_WRITE: + HandleWrite(&buffer, callback); + break; + + case FUSE_RELEASE: + HandleRelease(&buffer, callback); + break; + + case FUSE_FSYNC: + HandleFsync(&buffer, callback); + break; + + default: + buffer.HandleNotImpl(); + break; + } + + if (!buffer.response.Write(fd)) { + LOG(ERROR) << "Failed to write a response to the device."; + return false; + } + } + + return true; +} + +} // namespace fuse +} // namespace android diff --git a/libappfuse/FuseBridgeLoop.cc b/libappfuse/FuseBridgeLoop.cc index 332556dd1..acb963cfc 100644 --- a/libappfuse/FuseBridgeLoop.cc +++ b/libappfuse/FuseBridgeLoop.cc @@ -25,14 +25,15 @@ bool FuseBridgeLoop::Start( int raw_dev_fd, int raw_proxy_fd, FuseBridgeLoop::Callback* callback) { base::unique_fd dev_fd(raw_dev_fd); base::unique_fd proxy_fd(raw_proxy_fd); + fuse::FuseBuffer buffer; LOG(DEBUG) << "Start fuse loop."; while (true) { - if (!buffer_.request.Read(dev_fd)) { + if (!buffer.request.Read(dev_fd)) { return false; } - const uint32_t opcode = buffer_.request.header.opcode; + const uint32_t opcode = buffer.request.header.opcode; LOG(VERBOSE) << "Read a fuse packet, opcode=" << opcode; switch (opcode) { case FUSE_FORGET: @@ -45,27 +46,27 @@ bool FuseBridgeLoop::Start( case FUSE_READ: case FUSE_WRITE: case FUSE_RELEASE: - case FUSE_FLUSH: - if (!buffer_.request.Write(proxy_fd)) { + case FUSE_FSYNC: + if (!buffer.request.Write(proxy_fd)) { LOG(ERROR) << "Failed to write a request to the proxy."; return false; } - if (!buffer_.response.Read(proxy_fd)) { + if (!buffer.response.Read(proxy_fd)) { LOG(ERROR) << "Failed to read a response from the proxy."; return false; } break; case FUSE_INIT: - buffer_.HandleInit(); + buffer.HandleInit(); break; default: - buffer_.HandleNotImpl(); + buffer.HandleNotImpl(); break; } - if (!buffer_.response.Write(dev_fd)) { + if (!buffer.response.Write(dev_fd)) { LOG(ERROR) << "Failed to write a response to the device."; return false; } @@ -76,4 +77,12 @@ bool FuseBridgeLoop::Start( } } +namespace fuse { + +bool StartFuseBridgeLoop( + int raw_dev_fd, int raw_proxy_fd, FuseBridgeLoopCallback* callback) { + return FuseBridgeLoop().Start(raw_dev_fd, raw_proxy_fd, callback); +} + +} // namespace fuse } // namespace android diff --git a/libappfuse/FuseBuffer.cc b/libappfuse/FuseBuffer.cc index 45280a5cf..ca47aa8c7 100644 --- a/libappfuse/FuseBuffer.cc +++ b/libappfuse/FuseBuffer.cc @@ -26,6 +26,7 @@ #include namespace android { +namespace fuse { template bool FuseMessage::CheckHeaderLength() const { @@ -44,7 +45,7 @@ bool FuseMessage::CheckResult( return true; } else { PLOG(ERROR) << "Failed to " << operation_name - << " a packet from FD. result=" << result << " header.len=" + << " a packet. result=" << result << " header.len=" << header.len; return false; } @@ -68,6 +69,14 @@ bool FuseMessage::Write(int fd) const { template struct FuseMessage; template struct FuseMessage; +void FuseRequest::Reset( + uint32_t data_length, uint32_t opcode, uint64_t unique) { + memset(this, 0, sizeof(fuse_in_header) + data_length); + header.len = sizeof(fuse_in_header) + data_length; + header.opcode = opcode; + header.unique = unique; +} + void FuseResponse::ResetHeader( uint32_t data_length, int32_t error, uint64_t unique) { CHECK_LE(error, 0) << "error should be zero or negative."; @@ -133,4 +142,5 @@ void FuseBuffer::HandleNotImpl() { response.Reset(0, -ENOSYS, unique); } +} // namespace fuse } // namespace android diff --git a/libappfuse/include/libappfuse/FuseAppLoop.h b/libappfuse/include/libappfuse/FuseAppLoop.h new file mode 100644 index 000000000..c3edfcc32 --- /dev/null +++ b/libappfuse/include/libappfuse/FuseAppLoop.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * 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 specic language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_LIBAPPFUSE_FUSEAPPLOOP_H_ +#define ANDROID_LIBAPPFUSE_FUSEAPPLOOP_H_ + +#include "libappfuse/FuseBuffer.h" + +namespace android { +namespace fuse { + +class FuseAppLoopCallback { + public: + virtual bool IsActive() = 0; + virtual int64_t OnGetSize(uint64_t inode) = 0; + virtual int32_t OnFsync(uint64_t inode) = 0; + virtual int32_t OnWrite( + uint64_t inode, uint64_t offset, uint32_t size, const void* data) = 0; + virtual int32_t OnRead( + uint64_t inode, uint64_t offset, uint32_t size, void* data) = 0; + virtual int32_t OnOpen(uint64_t inode) = 0; + virtual int32_t OnRelease(uint64_t inode) = 0; + virtual ~FuseAppLoopCallback() = default; +}; + +bool StartFuseAppLoop(int fd, FuseAppLoopCallback* callback); + +} // namespace fuse +} // namespace android + +#endif // ANDROID_LIBAPPFUSE_FUSEAPPLOOP_H_ diff --git a/libappfuse/include/libappfuse/FuseBridgeLoop.h b/libappfuse/include/libappfuse/FuseBridgeLoop.h index 200653252..38043bce7 100644 --- a/libappfuse/include/libappfuse/FuseBridgeLoop.h +++ b/libappfuse/include/libappfuse/FuseBridgeLoop.h @@ -21,7 +21,9 @@ namespace android { -class FuseBridgeLoop { +// TODO: Remove the class after switching to StartFuseBridgeLoop in the +// framework code. +class FuseBridgeLoop final { public: class Callback { public: @@ -30,11 +32,15 @@ class FuseBridgeLoop { }; bool Start(int dev_fd, int proxy_fd, Callback* callback); - - private: - FuseBuffer buffer_; }; +namespace fuse { + +class FuseBridgeLoopCallback : public FuseBridgeLoop::Callback {}; +bool StartFuseBridgeLoop( + int dev_fd, int proxy_fd, FuseBridgeLoopCallback* callback); + +} // namespace fuse } // namespace android #endif // ANDROID_LIBAPPFUSE_FUSEBRIDGELOOP_H_ diff --git a/libappfuse/include/libappfuse/FuseBuffer.h b/libappfuse/include/libappfuse/FuseBuffer.h index 071b77715..1464142c4 100644 --- a/libappfuse/include/libappfuse/FuseBuffer.h +++ b/libappfuse/include/libappfuse/FuseBuffer.h @@ -20,6 +20,7 @@ #include namespace android { +namespace fuse { // The numbers came from sdcard.c. // Maximum number of bytes to write/read in one request/one reply. @@ -37,33 +38,51 @@ struct FuseMessage { bool CheckResult(int result, const char* operation_name) const; }; -struct FuseRequest : public FuseMessage { +// FuseRequest represents file operation requests from /dev/fuse. It starts +// from fuse_in_header. The body layout depends on the operation code. +struct FuseRequest final : public FuseMessage { union { + // for FUSE_WRITE struct { fuse_write_in write_in; char write_data[kFuseMaxWrite]; }; + // for FUSE_OPEN fuse_open_in open_in; + // for FUSE_INIT fuse_init_in init_in; + // for FUSE_READ fuse_read_in read_in; + // for FUSE_LOOKUP char lookup_name[0]; }; + void Reset(uint32_t data_length, uint32_t opcode, uint64_t unique); }; -struct FuseResponse : public FuseMessage { +// FuseResponse represents file operation responses to /dev/fuse. It starts +// from fuse_out_header. The body layout depends on the operation code. +struct FuseResponse final : public FuseMessage { union { + // for FUSE_INIT fuse_init_out init_out; + // for FUSE_LOOKUP fuse_entry_out entry_out; + // for FUSE_GETATTR fuse_attr_out attr_out; + // for FUSE_OPEN fuse_open_out open_out; + // for FUSE_READ char read_data[kFuseMaxRead]; + // for FUSE_WRITE fuse_write_out write_out; }; void Reset(uint32_t data_length, int32_t error, uint64_t unique); void ResetHeader(uint32_t data_length, int32_t error, uint64_t unique); }; -union FuseBuffer { +// To reduce memory usage, FuseBuffer shares the memory region for request and +// response. +union FuseBuffer final { FuseRequest request; FuseResponse response; @@ -71,19 +90,7 @@ union FuseBuffer { void HandleNotImpl(); }; -class FuseProxyLoop { - class IFuseProxyLoopCallback { - public: - virtual void OnMount() = 0; - virtual ~IFuseProxyLoopCallback() = default; - }; - - bool Start(int dev_fd, int proxy_fd, IFuseProxyLoopCallback* callback); - - private: - FuseBuffer buffer_; -}; - +} // namespace fuse } // namespace android #endif // ANDROID_LIBAPPFUSE_FUSEBUFFER_H_ diff --git a/libappfuse/tests/FuseAppLoopTest.cc b/libappfuse/tests/FuseAppLoopTest.cc new file mode 100644 index 000000000..25906cf1c --- /dev/null +++ b/libappfuse/tests/FuseAppLoopTest.cc @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * 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 specic language governing permissions and + * limitations under the License. + */ + +#include "libappfuse/FuseAppLoop.h" + +#include + +#include +#include +#include +#include + +namespace android { +namespace fuse { +namespace { + +constexpr unsigned int kTestFileSize = 1024; + +struct CallbackRequest { + uint32_t code; + uint64_t inode; +}; + +class Callback : public FuseAppLoopCallback { + public: + std::vector requests; + + bool IsActive() override { + return true; + } + + int64_t OnGetSize(uint64_t inode) override { + if (inode == FUSE_ROOT_ID) { + return 0; + } else { + return kTestFileSize; + } + } + + int32_t OnFsync(uint64_t inode) override { + requests.push_back({ + .code = FUSE_FSYNC, + .inode = inode + }); + return 0; + } + + int32_t OnWrite(uint64_t inode, + uint64_t offset ATTRIBUTE_UNUSED, + uint32_t size ATTRIBUTE_UNUSED, + const void* data ATTRIBUTE_UNUSED) override { + requests.push_back({ + .code = FUSE_WRITE, + .inode = inode + }); + return 0; + } + + int32_t OnRead(uint64_t inode, + uint64_t offset ATTRIBUTE_UNUSED, + uint32_t size ATTRIBUTE_UNUSED, + void* data ATTRIBUTE_UNUSED) override { + requests.push_back({ + .code = FUSE_READ, + .inode = inode + }); + return 0; + } + + int32_t OnOpen(uint64_t inode) override { + requests.push_back({ + .code = FUSE_OPEN, + .inode = inode + }); + return 0; + } + + int32_t OnRelease(uint64_t inode) override { + requests.push_back({ + .code = FUSE_RELEASE, + .inode = inode + }); + return 0; + } +}; + +class FuseAppLoopTest : public ::testing::Test { + private: + std::thread thread_; + + protected: + base::unique_fd sockets_[2]; + Callback callback_; + FuseRequest request_; + FuseResponse response_; + + void SetUp() override { + base::SetMinimumLogSeverity(base::VERBOSE); + int sockets[2]; + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)); + sockets_[0].reset(sockets[0]); + sockets_[1].reset(sockets[1]); + thread_ = std::thread([this] { + StartFuseAppLoop(sockets_[1].release(), &callback_); + }); + } + + void CheckCallback( + size_t data_size, uint32_t code, size_t expected_out_size) { + request_.Reset(data_size, code, 1); + request_.header.nodeid = 10; + + ASSERT_TRUE(request_.Write(sockets_[0])); + ASSERT_TRUE(response_.Read(sockets_[0])); + + Close(); + + EXPECT_EQ(kFuseSuccess, response_.header.error); + EXPECT_EQ(sizeof(fuse_out_header) + expected_out_size, + response_.header.len); + EXPECT_EQ(1u, response_.header.unique); + + ASSERT_EQ(1u, callback_.requests.size()); + EXPECT_EQ(code, callback_.requests[0].code); + EXPECT_EQ(10u, callback_.requests[0].inode); + } + + void Close() { + sockets_[0].reset(); + sockets_[1].reset(); + if (thread_.joinable()) { + thread_.join(); + } + } + + void TearDown() override { + Close(); + } +}; + +} // namespace + +TEST_F(FuseAppLoopTest, LookUp) { + request_.Reset(3u, FUSE_LOOKUP, 1); + request_.header.nodeid = FUSE_ROOT_ID; + strcpy(request_.lookup_name, "10"); + + ASSERT_TRUE(request_.Write(sockets_[0].get())); + ASSERT_TRUE(response_.Read(sockets_[0].get())); + + EXPECT_EQ(kFuseSuccess, response_.header.error); + EXPECT_EQ(sizeof(fuse_out_header) + sizeof(fuse_entry_out), + response_.header.len); + EXPECT_EQ(1u, response_.header.unique); + + EXPECT_EQ(10u, response_.entry_out.nodeid); + EXPECT_EQ(0u, response_.entry_out.generation); + EXPECT_EQ(10u, response_.entry_out.entry_valid); + EXPECT_EQ(10u, response_.entry_out.attr_valid); + EXPECT_EQ(0u, response_.entry_out.entry_valid_nsec); + EXPECT_EQ(0u, response_.entry_out.attr_valid_nsec); + + EXPECT_EQ(10u, response_.entry_out.attr.ino); + EXPECT_EQ(kTestFileSize, response_.entry_out.attr.size); + EXPECT_EQ(0u, response_.entry_out.attr.blocks); + EXPECT_EQ(0u, response_.entry_out.attr.atime); + EXPECT_EQ(0u, response_.entry_out.attr.mtime); + EXPECT_EQ(0u, response_.entry_out.attr.ctime); + EXPECT_EQ(0u, response_.entry_out.attr.atimensec); + EXPECT_EQ(0u, response_.entry_out.attr.mtimensec); + EXPECT_EQ(0u, response_.entry_out.attr.ctimensec); + EXPECT_EQ(S_IFREG | 0777u, response_.entry_out.attr.mode); + EXPECT_EQ(0u, response_.entry_out.attr.nlink); + EXPECT_EQ(0u, response_.entry_out.attr.uid); + EXPECT_EQ(0u, response_.entry_out.attr.gid); + EXPECT_EQ(0u, response_.entry_out.attr.rdev); + EXPECT_EQ(0u, response_.entry_out.attr.blksize); + EXPECT_EQ(0u, response_.entry_out.attr.padding); +} + +TEST_F(FuseAppLoopTest, LookUp_InvalidName) { + request_.Reset(3u, FUSE_LOOKUP, 1); + request_.header.nodeid = FUSE_ROOT_ID; + strcpy(request_.lookup_name, "aa"); + + ASSERT_TRUE(request_.Write(sockets_[0].get())); + ASSERT_TRUE(response_.Read(sockets_[0].get())); + + EXPECT_EQ(sizeof(fuse_out_header), response_.header.len); + EXPECT_EQ(-ENOENT, response_.header.error); + EXPECT_EQ(1u, response_.header.unique); +} + +TEST_F(FuseAppLoopTest, LookUp_TooLargeName) { + request_.Reset(21u, FUSE_LOOKUP, 1); + request_.header.nodeid = FUSE_ROOT_ID; + strcpy(request_.lookup_name, "18446744073709551616"); + + ASSERT_TRUE(request_.Write(sockets_[0].get())); + ASSERT_TRUE(response_.Read(sockets_[0].get())); + + EXPECT_EQ(sizeof(fuse_out_header), response_.header.len); + EXPECT_EQ(-ENOENT, response_.header.error); + EXPECT_EQ(1u, response_.header.unique); +} + +TEST_F(FuseAppLoopTest, GetAttr) { + request_.Reset(sizeof(fuse_getattr_in), FUSE_GETATTR, 1); + request_.header.nodeid = 10; + + ASSERT_TRUE(request_.Write(sockets_[0].get())); + ASSERT_TRUE(response_.Read(sockets_[0].get())); + + EXPECT_EQ(kFuseSuccess, response_.header.error); + EXPECT_EQ(sizeof(fuse_out_header) + sizeof(fuse_attr_out), + response_.header.len); + EXPECT_EQ(1u, response_.header.unique); + + EXPECT_EQ(10u, response_.attr_out.attr_valid); + EXPECT_EQ(0u, response_.attr_out.attr_valid_nsec); + + EXPECT_EQ(10u, response_.attr_out.attr.ino); + EXPECT_EQ(kTestFileSize, response_.attr_out.attr.size); + EXPECT_EQ(0u, response_.attr_out.attr.blocks); + EXPECT_EQ(0u, response_.attr_out.attr.atime); + EXPECT_EQ(0u, response_.attr_out.attr.mtime); + EXPECT_EQ(0u, response_.attr_out.attr.ctime); + EXPECT_EQ(0u, response_.attr_out.attr.atimensec); + EXPECT_EQ(0u, response_.attr_out.attr.mtimensec); + EXPECT_EQ(0u, response_.attr_out.attr.ctimensec); + EXPECT_EQ(S_IFREG | 0777u, response_.attr_out.attr.mode); + EXPECT_EQ(0u, response_.attr_out.attr.nlink); + EXPECT_EQ(0u, response_.attr_out.attr.uid); + EXPECT_EQ(0u, response_.attr_out.attr.gid); + EXPECT_EQ(0u, response_.attr_out.attr.rdev); + EXPECT_EQ(0u, response_.attr_out.attr.blksize); + EXPECT_EQ(0u, response_.attr_out.attr.padding); +} + +TEST_F(FuseAppLoopTest, GetAttr_Root) { + request_.Reset(sizeof(fuse_getattr_in), FUSE_GETATTR, 1); + request_.header.nodeid = FUSE_ROOT_ID; + + ASSERT_TRUE(request_.Write(sockets_[0].get())); + ASSERT_TRUE(response_.Read(sockets_[0].get())); + + EXPECT_EQ(kFuseSuccess, response_.header.error); + EXPECT_EQ(sizeof(fuse_out_header) + sizeof(fuse_attr_out), + response_.header.len); + EXPECT_EQ(1u, response_.header.unique); + + EXPECT_EQ(10u, response_.attr_out.attr_valid); + EXPECT_EQ(0u, response_.attr_out.attr_valid_nsec); + + EXPECT_EQ(static_cast(FUSE_ROOT_ID), response_.attr_out.attr.ino); + EXPECT_EQ(0u, response_.attr_out.attr.size); + EXPECT_EQ(0u, response_.attr_out.attr.blocks); + EXPECT_EQ(0u, response_.attr_out.attr.atime); + EXPECT_EQ(0u, response_.attr_out.attr.mtime); + EXPECT_EQ(0u, response_.attr_out.attr.ctime); + EXPECT_EQ(0u, response_.attr_out.attr.atimensec); + EXPECT_EQ(0u, response_.attr_out.attr.mtimensec); + EXPECT_EQ(0u, response_.attr_out.attr.ctimensec); + EXPECT_EQ(S_IFDIR | 0777u, response_.attr_out.attr.mode); + EXPECT_EQ(0u, response_.attr_out.attr.nlink); + EXPECT_EQ(0u, response_.attr_out.attr.uid); + EXPECT_EQ(0u, response_.attr_out.attr.gid); + EXPECT_EQ(0u, response_.attr_out.attr.rdev); + EXPECT_EQ(0u, response_.attr_out.attr.blksize); + EXPECT_EQ(0u, response_.attr_out.attr.padding); +} + +TEST_F(FuseAppLoopTest, Open) { + CheckCallback(sizeof(fuse_open_in), FUSE_OPEN, sizeof(fuse_open_out)); +} + +TEST_F(FuseAppLoopTest, Fsync) { + CheckCallback(0u, FUSE_FSYNC, 0u); +} + +TEST_F(FuseAppLoopTest, Release) { + CheckCallback(0u, FUSE_RELEASE, 0u); +} + +TEST_F(FuseAppLoopTest, Read) { + CheckCallback(sizeof(fuse_read_in), FUSE_READ, 0u); +} + +TEST_F(FuseAppLoopTest, Write) { + CheckCallback(sizeof(fuse_write_in), FUSE_WRITE, sizeof(fuse_write_out)); +} + +} // namespace fuse +} // namespace android diff --git a/libappfuse/tests/FuseBridgeLoopTest.cc b/libappfuse/tests/FuseBridgeLoopTest.cc index 31e369041..bd503ebfb 100644 --- a/libappfuse/tests/FuseBridgeLoopTest.cc +++ b/libappfuse/tests/FuseBridgeLoopTest.cc @@ -21,11 +21,15 @@ #include #include +#include +#include #include namespace android { +namespace fuse { +namespace { -class Callback : public FuseBridgeLoop::Callback { +class Callback : public FuseBridgeLoopCallback { public: bool mounted; Callback() : mounted(false) {} @@ -36,20 +40,28 @@ class Callback : public FuseBridgeLoop::Callback { class FuseBridgeLoopTest : public ::testing::Test { protected: - int dev_sockets_[2]; - int proxy_sockets_[2]; + base::unique_fd dev_sockets_[2]; + base::unique_fd proxy_sockets_[2]; Callback callback_; std::thread thread_; FuseRequest request_; FuseResponse response_; - void SetUp() { - ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, dev_sockets_)); - ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, proxy_sockets_)); + void SetUp() override { + base::SetMinimumLogSeverity(base::VERBOSE); + int dev_sockets[2]; + int proxy_sockets[2]; + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, dev_sockets)); + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, proxy_sockets)); + dev_sockets_[0].reset(dev_sockets[0]); + dev_sockets_[1].reset(dev_sockets[1]); + proxy_sockets_[0].reset(proxy_sockets[0]); + proxy_sockets_[1].reset(proxy_sockets[1]); + thread_ = std::thread([this] { - FuseBridgeLoop loop; - loop.Start(dev_sockets_[1], proxy_sockets_[0], &callback_); + StartFuseBridgeLoop( + dev_sockets_[1].release(), proxy_sockets_[0].release(), &callback_); }); } @@ -103,20 +115,22 @@ class FuseBridgeLoopTest : public ::testing::Test { } void Close() { - close(dev_sockets_[0]); - close(dev_sockets_[1]); - close(proxy_sockets_[0]); - close(proxy_sockets_[1]); + dev_sockets_[0].reset(); + dev_sockets_[1].reset(); + proxy_sockets_[0].reset(); + proxy_sockets_[1].reset(); if (thread_.joinable()) { thread_.join(); } } - void TearDown() { + void TearDown() override { Close(); } }; +} // namespace + TEST_F(FuseBridgeLoopTest, FuseInit) { SendInitRequest(1u); @@ -156,11 +170,11 @@ TEST_F(FuseBridgeLoopTest, FuseNotImpl) { CheckNotImpl(FUSE_RENAME); CheckNotImpl(FUSE_LINK); CheckNotImpl(FUSE_STATFS); - CheckNotImpl(FUSE_FSYNC); CheckNotImpl(FUSE_SETXATTR); CheckNotImpl(FUSE_GETXATTR); CheckNotImpl(FUSE_LISTXATTR); CheckNotImpl(FUSE_REMOVEXATTR); + CheckNotImpl(FUSE_FLUSH); CheckNotImpl(FUSE_OPENDIR); CheckNotImpl(FUSE_READDIR); CheckNotImpl(FUSE_RELEASEDIR); @@ -190,7 +204,8 @@ TEST_F(FuseBridgeLoopTest, Proxy) { CheckProxy(FUSE_READ); CheckProxy(FUSE_WRITE); CheckProxy(FUSE_RELEASE); - CheckProxy(FUSE_FLUSH); + CheckProxy(FUSE_FSYNC); } -} // android +} // namespace fuse +} // namespace android diff --git a/libappfuse/tests/FuseBufferTest.cc b/libappfuse/tests/FuseBufferTest.cc index 1aacfe303..17f1306fe 100644 --- a/libappfuse/tests/FuseBufferTest.cc +++ b/libappfuse/tests/FuseBufferTest.cc @@ -24,6 +24,7 @@ #include namespace android { +namespace fuse { constexpr char kTempFile[] = "/data/local/tmp/appfuse_test_dump"; @@ -183,5 +184,6 @@ TEST(FuseBufferTest, HandleNotImpl) { ASSERT_EQ(sizeof(fuse_out_header), buffer.response.header.len); EXPECT_EQ(-ENOSYS, buffer.response.header.error); } -} - // namespace android + +} // namespace fuse +} // namespace android