diff --git a/libappfuse/Android.bp b/libappfuse/Android.bp index 8d24af10d..8b4615405 100644 --- a/libappfuse/Android.bp +++ b/libappfuse/Android.bp @@ -15,12 +15,12 @@ cc_library_shared { name: "libappfuse", defaults: ["libappfuse_defaults"], export_include_dirs: ["include"], - srcs: ["AppFuse.cc"] + srcs: ["FuseBuffer.cc", "FuseBridgeLoop.cc"] } cc_test { name: "libappfuse_test", defaults: ["libappfuse_defaults"], shared_libs: ["libappfuse"], - srcs: ["tests/AppFuseTest.cc"] + srcs: ["tests/FuseBridgeLoopTest.cc", "tests/FuseBufferTest.cc"] } diff --git a/libappfuse/FuseBridgeLoop.cc b/libappfuse/FuseBridgeLoop.cc new file mode 100644 index 000000000..332556dd1 --- /dev/null +++ b/libappfuse/FuseBridgeLoop.cc @@ -0,0 +1,79 @@ +/* + * 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/FuseBridgeLoop.h" + +#include +#include + +namespace android { + +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); + + LOG(DEBUG) << "Start fuse loop."; + while (true) { + if (!buffer_.request.Read(dev_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: + case FUSE_GETATTR: + case FUSE_OPEN: + case FUSE_READ: + case FUSE_WRITE: + case FUSE_RELEASE: + case FUSE_FLUSH: + if (!buffer_.request.Write(proxy_fd)) { + LOG(ERROR) << "Failed to write a request to the proxy."; + return false; + } + if (!buffer_.response.Read(proxy_fd)) { + LOG(ERROR) << "Failed to read a response from the proxy."; + return false; + } + break; + + case FUSE_INIT: + buffer_.HandleInit(); + break; + + default: + buffer_.HandleNotImpl(); + break; + } + + if (!buffer_.response.Write(dev_fd)) { + LOG(ERROR) << "Failed to write a response to the device."; + return false; + } + + if (opcode == FUSE_INIT) { + callback->OnMount(); + } + } +} + +} // namespace android diff --git a/libappfuse/AppFuse.cc b/libappfuse/FuseBuffer.cc similarity index 99% rename from libappfuse/AppFuse.cc rename to libappfuse/FuseBuffer.cc index 8701c4800..45280a5cf 100644 --- a/libappfuse/AppFuse.cc +++ b/libappfuse/FuseBuffer.cc @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "libappfuse/AppFuse.h" +#include "libappfuse/FuseBuffer.h" #include #include diff --git a/libappfuse/include/libappfuse/FuseBridgeLoop.h b/libappfuse/include/libappfuse/FuseBridgeLoop.h new file mode 100644 index 000000000..200653252 --- /dev/null +++ b/libappfuse/include/libappfuse/FuseBridgeLoop.h @@ -0,0 +1,40 @@ +/* + * 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_FUSEBRIDGELOOP_H_ +#define ANDROID_LIBAPPFUSE_FUSEBRIDGELOOP_H_ + +#include "libappfuse/FuseBuffer.h" + +namespace android { + +class FuseBridgeLoop { + public: + class Callback { + public: + virtual void OnMount() = 0; + virtual ~Callback() = default; + }; + + bool Start(int dev_fd, int proxy_fd, Callback* callback); + + private: + FuseBuffer buffer_; +}; + +} // namespace android + +#endif // ANDROID_LIBAPPFUSE_FUSEBRIDGELOOP_H_ diff --git a/libappfuse/include/libappfuse/AppFuse.h b/libappfuse/include/libappfuse/FuseBuffer.h similarity index 82% rename from libappfuse/include/libappfuse/AppFuse.h rename to libappfuse/include/libappfuse/FuseBuffer.h index b6af48d78..071b77715 100644 --- a/libappfuse/include/libappfuse/AppFuse.h +++ b/libappfuse/include/libappfuse/FuseBuffer.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_LIBAPPFUSE_APPFUSE_H_ -#define ANDROID_LIBAPPFUSE_APPFUSE_H_ +#ifndef ANDROID_LIBAPPFUSE_FUSEBUFFER_H_ +#define ANDROID_LIBAPPFUSE_FUSEBUFFER_H_ #include @@ -46,7 +46,7 @@ struct FuseRequest : public FuseMessage { fuse_open_in open_in; fuse_init_in init_in; fuse_read_in read_in; - char lookup_name[]; + char lookup_name[0]; }; }; @@ -71,6 +71,19 @@ 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 android -#endif // ANDROID_LIBAPPFUSE_APPFUSE_H_ +#endif // ANDROID_LIBAPPFUSE_FUSEBUFFER_H_ diff --git a/libappfuse/tests/FuseBridgeLoopTest.cc b/libappfuse/tests/FuseBridgeLoopTest.cc new file mode 100644 index 000000000..31e369041 --- /dev/null +++ b/libappfuse/tests/FuseBridgeLoopTest.cc @@ -0,0 +1,196 @@ +/* + * 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/FuseBridgeLoop.h" + +#include + +#include +#include + +#include + +namespace android { + +class Callback : public FuseBridgeLoop::Callback { + public: + bool mounted; + Callback() : mounted(false) {} + void OnMount() override { + mounted = true; + } +}; + +class FuseBridgeLoopTest : public ::testing::Test { + protected: + int dev_sockets_[2]; + int 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_)); + thread_ = std::thread([this] { + FuseBridgeLoop loop; + loop.Start(dev_sockets_[1], proxy_sockets_[0], &callback_); + }); + } + + void CheckNotImpl(uint32_t opcode) { + SCOPED_TRACE((std::ostringstream() << "opcode: " << opcode).str()); + + memset(&request_, 0, sizeof(FuseRequest)); + request_.header.opcode = opcode; + request_.header.len = sizeof(fuse_in_header); + ASSERT_TRUE(request_.Write(dev_sockets_[0])); + + memset(&response_, 0, sizeof(FuseResponse)); + ASSERT_TRUE(response_.Read(dev_sockets_[0])); + EXPECT_EQ(-ENOSYS, response_.header.error); + } + + void CheckProxy(uint32_t opcode) { + SCOPED_TRACE((std::ostringstream() << "opcode: " << opcode).str()); + + memset(&request_, 0, sizeof(FuseRequest)); + request_.header.opcode = opcode; + request_.header.unique = opcode; // Use opcode as unique. + request_.header.len = sizeof(fuse_in_header); + ASSERT_TRUE(request_.Write(dev_sockets_[0])); + + memset(&request_, 0, sizeof(FuseRequest)); + ASSERT_TRUE(request_.Read(proxy_sockets_[1])); + EXPECT_EQ(opcode, request_.header.opcode); + EXPECT_EQ(opcode, request_.header.unique); + + memset(&response_, 0, sizeof(FuseResponse)); + response_.header.len = sizeof(fuse_out_header); + response_.header.unique = opcode; // Use opcode as unique. + response_.header.error = kFuseSuccess; + ASSERT_TRUE(response_.Write(proxy_sockets_[1])); + + memset(&response_, 0, sizeof(FuseResponse)); + ASSERT_TRUE(response_.Read(dev_sockets_[0])); + EXPECT_EQ(opcode, response_.header.unique); + EXPECT_EQ(kFuseSuccess, response_.header.error); + } + + void SendInitRequest(uint64_t unique) { + memset(&request_, 0, sizeof(FuseRequest)); + request_.header.opcode = FUSE_INIT; + request_.header.unique = unique; + request_.header.len = sizeof(fuse_in_header) + sizeof(fuse_init_in); + request_.init_in.major = FUSE_KERNEL_VERSION; + request_.init_in.minor = FUSE_KERNEL_MINOR_VERSION; + ASSERT_TRUE(request_.Write(dev_sockets_[0])); + } + + void Close() { + close(dev_sockets_[0]); + close(dev_sockets_[1]); + close(proxy_sockets_[0]); + close(proxy_sockets_[1]); + if (thread_.joinable()) { + thread_.join(); + } + } + + void TearDown() { + Close(); + } +}; + +TEST_F(FuseBridgeLoopTest, FuseInit) { + SendInitRequest(1u); + + memset(&response_, 0, sizeof(FuseResponse)); + ASSERT_TRUE(response_.Read(dev_sockets_[0])); + EXPECT_EQ(kFuseSuccess, response_.header.error); + EXPECT_EQ(1u, response_.header.unique); + + // Unmount. + Close(); + EXPECT_TRUE(callback_.mounted); +} + +TEST_F(FuseBridgeLoopTest, FuseForget) { + memset(&request_, 0, sizeof(FuseRequest)); + request_.header.opcode = FUSE_FORGET; + request_.header.unique = 1u; + request_.header.len = sizeof(fuse_in_header) + sizeof(fuse_forget_in); + ASSERT_TRUE(request_.Write(dev_sockets_[0])); + + SendInitRequest(2u); + + memset(&response_, 0, sizeof(FuseResponse)); + ASSERT_TRUE(response_.Read(dev_sockets_[0])); + EXPECT_EQ(2u, response_.header.unique) << + "The loop must not respond to FUSE_FORGET"; +} + +TEST_F(FuseBridgeLoopTest, FuseNotImpl) { + CheckNotImpl(FUSE_SETATTR); + CheckNotImpl(FUSE_READLINK); + CheckNotImpl(FUSE_SYMLINK); + CheckNotImpl(FUSE_MKNOD); + CheckNotImpl(FUSE_MKDIR); + CheckNotImpl(FUSE_UNLINK); + CheckNotImpl(FUSE_RMDIR); + 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_OPENDIR); + CheckNotImpl(FUSE_READDIR); + CheckNotImpl(FUSE_RELEASEDIR); + CheckNotImpl(FUSE_FSYNCDIR); + CheckNotImpl(FUSE_GETLK); + CheckNotImpl(FUSE_SETLK); + CheckNotImpl(FUSE_SETLKW); + CheckNotImpl(FUSE_ACCESS); + CheckNotImpl(FUSE_CREATE); + CheckNotImpl(FUSE_INTERRUPT); + CheckNotImpl(FUSE_BMAP); + CheckNotImpl(FUSE_DESTROY); + CheckNotImpl(FUSE_IOCTL); + CheckNotImpl(FUSE_POLL); + CheckNotImpl(FUSE_NOTIFY_REPLY); + CheckNotImpl(FUSE_BATCH_FORGET); + CheckNotImpl(FUSE_FALLOCATE); + CheckNotImpl(FUSE_READDIRPLUS); + CheckNotImpl(FUSE_RENAME2); + CheckNotImpl(FUSE_LSEEK); +} + +TEST_F(FuseBridgeLoopTest, Proxy) { + CheckProxy(FUSE_LOOKUP); + CheckProxy(FUSE_GETATTR); + CheckProxy(FUSE_OPEN); + CheckProxy(FUSE_READ); + CheckProxy(FUSE_WRITE); + CheckProxy(FUSE_RELEASE); + CheckProxy(FUSE_FLUSH); +} + +} // android diff --git a/libappfuse/tests/AppFuseTest.cc b/libappfuse/tests/FuseBufferTest.cc similarity index 99% rename from libappfuse/tests/AppFuseTest.cc rename to libappfuse/tests/FuseBufferTest.cc index 8c2cc4712..1aacfe303 100644 --- a/libappfuse/tests/AppFuseTest.cc +++ b/libappfuse/tests/FuseBufferTest.cc @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "libappfuse/AppFuse.h" +#include "libappfuse/FuseBuffer.h" #include #include