diff --git a/libappfuse/Android.bp b/libappfuse/Android.bp new file mode 100644 index 000000000..8d24af10d --- /dev/null +++ b/libappfuse/Android.bp @@ -0,0 +1,26 @@ +// Copyright 2016 The Android Open Source Project + +cc_defaults { + name: "libappfuse_defaults", + local_include_dirs: ["include"], + shared_libs: ["libbase"], + cflags: [ + "-Wall", + "-Werror", + ], + clang: true +} + +cc_library_shared { + name: "libappfuse", + defaults: ["libappfuse_defaults"], + export_include_dirs: ["include"], + srcs: ["AppFuse.cc"] +} + +cc_test { + name: "libappfuse_test", + defaults: ["libappfuse_defaults"], + shared_libs: ["libappfuse"], + srcs: ["tests/AppFuseTest.cc"] +} diff --git a/libappfuse/AppFuse.cc b/libappfuse/AppFuse.cc new file mode 100644 index 000000000..8701c4800 --- /dev/null +++ b/libappfuse/AppFuse.cc @@ -0,0 +1,136 @@ +/* + * 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/AppFuse.h" + +#include +#include +#include + +#include + +#include +#include + +namespace android { + +template +bool FuseMessage::CheckHeaderLength() const { + if (sizeof(Header) <= header.len && header.len <= sizeof(T)) { + return true; + } else { + LOG(ERROR) << "Packet size is invalid=" << header.len; + return false; + } +} + +template +bool FuseMessage::CheckResult( + int result, const char* operation_name) const { + if (result >= 0 && static_cast(result) == header.len) { + return true; + } else { + PLOG(ERROR) << "Failed to " << operation_name + << " a packet from FD. result=" << result << " header.len=" + << header.len; + return false; + } +} + +template +bool FuseMessage::Read(int fd) { + const ssize_t result = TEMP_FAILURE_RETRY(::read(fd, this, sizeof(T))); + return CheckHeaderLength() && CheckResult(result, "read"); +} + +template +bool FuseMessage::Write(int fd) const { + if (!CheckHeaderLength()) { + return false; + } + const ssize_t result = TEMP_FAILURE_RETRY(::write(fd, this, header.len)); + return CheckResult(result, "write"); +} + +template struct FuseMessage; +template struct FuseMessage; + +void FuseResponse::ResetHeader( + uint32_t data_length, int32_t error, uint64_t unique) { + CHECK_LE(error, 0) << "error should be zero or negative."; + header.len = sizeof(fuse_out_header) + data_length; + header.error = error; + header.unique = unique; +} + +void FuseResponse::Reset(uint32_t data_length, int32_t error, uint64_t unique) { + memset(this, 0, sizeof(fuse_out_header) + data_length); + ResetHeader(data_length, error, unique); +} + +void FuseBuffer::HandleInit() { + const fuse_init_in* const in = &request.init_in; + + // Before writing |out|, we need to copy data from |in|. + const uint64_t unique = request.header.unique; + const uint32_t minor = in->minor; + const uint32_t max_readahead = in->max_readahead; + + // Kernel 2.6.16 is the first stable kernel with struct fuse_init_out + // defined (fuse version 7.6). The structure is the same from 7.6 through + // 7.22. Beginning with 7.23, the structure increased in size and added + // new parameters. + if (in->major != FUSE_KERNEL_VERSION || in->minor < 6) { + LOG(ERROR) << "Fuse kernel version mismatch: Kernel version " << in->major + << "." << in->minor << " Expected at least " << FUSE_KERNEL_VERSION + << ".6"; + response.Reset(0, -EPERM, unique); + return; + } + + // We limit ourselves to 15 because we don't handle BATCH_FORGET yet + size_t response_size = sizeof(fuse_init_out); +#if defined(FUSE_COMPAT_22_INIT_OUT_SIZE) + // FUSE_KERNEL_VERSION >= 23. + + // If the kernel only works on minor revs older than or equal to 22, + // then use the older structure size since this code only uses the 7.22 + // version of the structure. + if (minor <= 22) { + response_size = FUSE_COMPAT_22_INIT_OUT_SIZE; + } +#endif + + response.Reset(response_size, kFuseSuccess, unique); + fuse_init_out* const out = &response.init_out; + out->major = FUSE_KERNEL_VERSION; + // We limit ourselves to 15 because we don't handle BATCH_FORGET yet. + out->minor = std::min(minor, 15u); + out->max_readahead = max_readahead; + out->flags = FUSE_ATOMIC_O_TRUNC | FUSE_BIG_WRITES; + out->max_background = 32; + out->congestion_threshold = 32; + out->max_write = kFuseMaxWrite; +} + +void FuseBuffer::HandleNotImpl() { + LOG(VERBOSE) << "NOTIMPL op=" << request.header.opcode << " uniq=" + << request.header.unique << " nid=" << request.header.nodeid; + const uint64_t unique = request.header.unique; + response.Reset(0, -ENOSYS, unique); +} + +} // namespace android diff --git a/libappfuse/include/libappfuse/AppFuse.h b/libappfuse/include/libappfuse/AppFuse.h new file mode 100644 index 000000000..b6af48d78 --- /dev/null +++ b/libappfuse/include/libappfuse/AppFuse.h @@ -0,0 +1,76 @@ +/* + * 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_APPFUSE_H_ +#define ANDROID_LIBAPPFUSE_APPFUSE_H_ + +#include + +namespace android { + +// The numbers came from sdcard.c. +// Maximum number of bytes to write/read in one request/one reply. +constexpr size_t kFuseMaxWrite = 256 * 1024; +constexpr size_t kFuseMaxRead = 128 * 1024; +constexpr int32_t kFuseSuccess = 0; + +template +struct FuseMessage { + Header header; + bool Read(int fd); + bool Write(int fd) const; + private: + bool CheckHeaderLength() const; + bool CheckResult(int result, const char* operation_name) const; +}; + +struct FuseRequest : public FuseMessage { + union { + struct { + fuse_write_in write_in; + char write_data[kFuseMaxWrite]; + }; + fuse_open_in open_in; + fuse_init_in init_in; + fuse_read_in read_in; + char lookup_name[]; + }; +}; + +struct FuseResponse : public FuseMessage { + union { + fuse_init_out init_out; + fuse_entry_out entry_out; + fuse_attr_out attr_out; + fuse_open_out open_out; + char read_data[kFuseMaxRead]; + 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 { + FuseRequest request; + FuseResponse response; + + void HandleInit(); + void HandleNotImpl(); +}; + +} // namespace android + +#endif // ANDROID_LIBAPPFUSE_APPFUSE_H_ diff --git a/libappfuse/tests/AppFuseTest.cc b/libappfuse/tests/AppFuseTest.cc new file mode 100644 index 000000000..8c2cc4712 --- /dev/null +++ b/libappfuse/tests/AppFuseTest.cc @@ -0,0 +1,187 @@ +/* + * 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/AppFuse.h" + +#include +#include +#include + +#include +#include + +namespace android { + +constexpr char kTempFile[] = "/data/local/tmp/appfuse_test_dump"; + +void OpenTempFile(android::base::unique_fd* fd) { + fd->reset(open(kTempFile, O_CREAT | O_RDWR)); + ASSERT_NE(-1, *fd) << strerror(errno); + unlink(kTempFile); + ASSERT_NE(-1, *fd) << strerror(errno); +} + +void TestReadInvalidLength(size_t headerSize, size_t write_size) { + android::base::unique_fd fd; + OpenTempFile(&fd); + + char buffer[std::max(headerSize, sizeof(FuseRequest))]; + FuseRequest* const packet = reinterpret_cast(buffer); + packet->header.len = headerSize; + ASSERT_NE(-1, write(fd, packet, write_size)) << strerror(errno); + + lseek(fd, 0, SEEK_SET); + EXPECT_FALSE(packet->Read(fd)); +} + +void TestWriteInvalidLength(size_t size) { + android::base::unique_fd fd; + OpenTempFile(&fd); + + char buffer[std::max(size, sizeof(FuseRequest))]; + FuseRequest* const packet = reinterpret_cast(buffer); + packet->header.len = size; + EXPECT_FALSE(packet->Write(fd)); +} + +// Use FuseRequest as a template instance of FuseMessage. + +TEST(FuseMessageTest, ReadAndWrite) { + android::base::unique_fd fd; + OpenTempFile(&fd); + + FuseRequest request; + request.header.len = sizeof(FuseRequest); + request.header.opcode = 1; + request.header.unique = 2; + request.header.nodeid = 3; + request.header.uid = 4; + request.header.gid = 5; + request.header.pid = 6; + strcpy(request.lookup_name, "test"); + + ASSERT_TRUE(request.Write(fd)); + + memset(&request, 0, sizeof(FuseRequest)); + lseek(fd, 0, SEEK_SET); + + ASSERT_TRUE(request.Read(fd)); + EXPECT_EQ(sizeof(FuseRequest), request.header.len); + EXPECT_EQ(1u, request.header.opcode); + EXPECT_EQ(2u, request.header.unique); + EXPECT_EQ(3u, request.header.nodeid); + EXPECT_EQ(4u, request.header.uid); + EXPECT_EQ(5u, request.header.gid); + EXPECT_EQ(6u, request.header.pid); + EXPECT_STREQ("test", request.lookup_name); +} + +TEST(FuseMessageTest, Read_InconsistentLength) { + TestReadInvalidLength(sizeof(fuse_in_header), sizeof(fuse_in_header) + 1); +} + +TEST(FuseMessageTest, Read_TooLong) { + TestReadInvalidLength(sizeof(FuseRequest) + 1, sizeof(FuseRequest) + 1); +} + +TEST(FuseMessageTest, Read_TooShort) { + TestReadInvalidLength(sizeof(fuse_in_header) - 1, sizeof(fuse_in_header) - 1); +} + +TEST(FuseMessageTest, Write_TooLong) { + TestWriteInvalidLength(sizeof(FuseRequest) + 1); +} + +TEST(FuseMessageTest, Write_TooShort) { + TestWriteInvalidLength(sizeof(fuse_in_header) - 1); +} + +TEST(FuseResponseTest, Reset) { + FuseResponse response; + // Write 1 to the first ten bytes. + memset(response.read_data, 'a', 10); + + response.Reset(0, -1, 2); + EXPECT_EQ(sizeof(fuse_out_header), response.header.len); + EXPECT_EQ(-1, response.header.error); + EXPECT_EQ(2u, response.header.unique); + EXPECT_EQ('a', response.read_data[0]); + EXPECT_EQ('a', response.read_data[9]); + + response.Reset(5, -4, 3); + EXPECT_EQ(sizeof(fuse_out_header) + 5, response.header.len); + EXPECT_EQ(-4, response.header.error); + EXPECT_EQ(3u, response.header.unique); + EXPECT_EQ(0, response.read_data[0]); + EXPECT_EQ(0, response.read_data[1]); + EXPECT_EQ(0, response.read_data[2]); + EXPECT_EQ(0, response.read_data[3]); + EXPECT_EQ(0, response.read_data[4]); + EXPECT_EQ('a', response.read_data[5]); +} + +TEST(FuseResponseTest, ResetHeader) { + FuseResponse response; + // Write 1 to the first ten bytes. + memset(response.read_data, 'a', 10); + + response.ResetHeader(0, -1, 2); + EXPECT_EQ(sizeof(fuse_out_header), response.header.len); + EXPECT_EQ(-1, response.header.error); + EXPECT_EQ(2u, response.header.unique); + EXPECT_EQ('a', response.read_data[0]); + EXPECT_EQ('a', response.read_data[9]); + + response.ResetHeader(5, -4, 3); + EXPECT_EQ(sizeof(fuse_out_header) + 5, response.header.len); + EXPECT_EQ(-4, response.header.error); + EXPECT_EQ(3u, response.header.unique); + EXPECT_EQ('a', response.read_data[0]); + EXPECT_EQ('a', response.read_data[9]); +} + +TEST(FuseBufferTest, HandleInit) { + FuseBuffer buffer; + memset(&buffer, 0, sizeof(FuseBuffer)); + + buffer.request.header.opcode = FUSE_INIT; + buffer.request.init_in.major = FUSE_KERNEL_VERSION; + buffer.request.init_in.minor = FUSE_KERNEL_MINOR_VERSION; + + buffer.HandleInit(); + + ASSERT_EQ(sizeof(fuse_out_header) + sizeof(fuse_init_out), + buffer.response.header.len); + EXPECT_EQ(kFuseSuccess, buffer.response.header.error); + EXPECT_EQ(static_cast(FUSE_KERNEL_VERSION), + buffer.response.init_out.major); + EXPECT_EQ(15u, buffer.response.init_out.minor); + EXPECT_EQ(static_cast(FUSE_ATOMIC_O_TRUNC | FUSE_BIG_WRITES), + buffer.response.init_out.flags); + EXPECT_EQ(kFuseMaxWrite, buffer.response.init_out.max_write); +} + +TEST(FuseBufferTest, HandleNotImpl) { + FuseBuffer buffer; + memset(&buffer, 0, sizeof(FuseBuffer)); + + buffer.HandleNotImpl(); + + ASSERT_EQ(sizeof(fuse_out_header), buffer.response.header.len); + EXPECT_EQ(-ENOSYS, buffer.response.header.error); +} +} + // namespace android