Merge "Add FuseAppLoop to libappfuse." am: e1d24f8465 am: 1eda78472a
am: 5fc0b1ed1f

Change-Id: Idf224e48aa8a1900e341c51ffcb0e5e0d4eb8ee3
diff --git a/libappfuse/Android.bp b/libappfuse/Android.bp
index 8b46154..f729faf 100644
--- a/libappfuse/Android.bp
+++ b/libappfuse/Android.bp
@@ -15,12 +15,20 @@
     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 0000000..a31880e
--- /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 <sys/stat.h>
+
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+
+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<uint64_t>(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 332556d..acb963c 100644
--- a/libappfuse/FuseBridgeLoop.cc
+++ b/libappfuse/FuseBridgeLoop.cc
@@ -25,14 +25,15 @@
     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 @@
       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 @@
   }
 }
 
+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 45280a5..ca47aa8 100644
--- a/libappfuse/FuseBuffer.cc
+++ b/libappfuse/FuseBuffer.cc
@@ -26,6 +26,7 @@
 #include <android-base/macros.h>
 
 namespace android {
+namespace fuse {
 
 template <typename T, typename Header>
 bool FuseMessage<T, Header>::CheckHeaderLength() const {
@@ -44,7 +45,7 @@
     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 @@
 template struct FuseMessage<FuseRequest, fuse_in_header>;
 template struct FuseMessage<FuseResponse, fuse_out_header>;
 
+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 @@
   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 0000000..c3edfcc
--- /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 2006532..38043bc 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 @@
   };
 
   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 071b777..1464142 100644
--- a/libappfuse/include/libappfuse/FuseBuffer.h
+++ b/libappfuse/include/libappfuse/FuseBuffer.h
@@ -20,6 +20,7 @@
 #include <linux/fuse.h>
 
 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 @@
   bool CheckResult(int result, const char* operation_name) const;
 };
 
-struct FuseRequest : public FuseMessage<FuseRequest, fuse_in_header> {
+// 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<FuseRequest, fuse_in_header> {
   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, fuse_out_header> {
+// 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<FuseResponse, fuse_out_header> {
   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 @@
   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 0000000..25906cf
--- /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 <sys/socket.h>
+
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+#include <thread>
+
+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<CallbackRequest> 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<unsigned>(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 31e3690..bd503eb 100644
--- a/libappfuse/tests/FuseBridgeLoopTest.cc
+++ b/libappfuse/tests/FuseBridgeLoopTest.cc
@@ -21,11 +21,15 @@
 #include <sstream>
 #include <thread>
 
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
 #include <gtest/gtest.h>
 
 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 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 @@
   }
 
   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 @@
   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 @@
   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 1aacfe3..17f1306 100644
--- a/libappfuse/tests/FuseBufferTest.cc
+++ b/libappfuse/tests/FuseBufferTest.cc
@@ -24,6 +24,7 @@
 #include <gtest/gtest.h>
 
 namespace android {
+namespace fuse {
 
 constexpr char kTempFile[] = "/data/local/tmp/appfuse_test_dump";
 
@@ -183,5 +184,6 @@
   ASSERT_EQ(sizeof(fuse_out_header), buffer.response.header.len);
   EXPECT_EQ(-ENOSYS, buffer.response.header.error);
 }
-}
-  // namespace android
+
+} // namespace fuse
+} // namespace android