diff options
| author | Ryan Mitchell <rtmitchell@google.com> | 2020-09-17 21:00:10 +0000 |
|---|---|---|
| committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-09-17 21:00:10 +0000 |
| commit | fec2547ee70d363b5b8faa6717d2a6aa0cfcca12 (patch) | |
| tree | bbaab994c60fe810f4e6d89db45db1b6cf7f36fb | |
| parent | 608073afad3e394c5db3d1b6074e7a35e3a2c03e (diff) | |
| parent | d918ca2fc86ce595c536a44f22a0c28c93f63eea (diff) | |
| download | platform_system_incremental_delivery-fec2547ee70d363b5b8faa6717d2a6aa0cfcca12.tar.gz platform_system_incremental_delivery-fec2547ee70d363b5b8faa6717d2a6aa0cfcca12.tar.bz2 platform_system_incremental_delivery-fec2547ee70d363b5b8faa6717d2a6aa0cfcca12.zip | |
Add util::map_ptr to incFs am: 97c05a314c am: f0fd3f117d am: d918ca2fc8
Original change: https://android-review.googlesource.com/c/platform/system/incremental_delivery/+/1430128
Change-Id: I911aee9a7f79a460fa71a3b89bb5e08bf5c3921a
| -rw-r--r-- | incfs/Android.bp | 97 | ||||
| -rw-r--r-- | incfs/incfs.cpp | 14 | ||||
| -rw-r--r-- | incfs/include/incfs.h | 1 | ||||
| -rw-r--r-- | incfs/include/incfs_inline.h | 4 | ||||
| -rw-r--r-- | incfs/include/incfs_ndk.h | 1 | ||||
| -rw-r--r-- | incfs/tests/incfs_test.cpp | 91 | ||||
| -rw-r--r-- | incfs/tests/include/IncFsTestBase.h | 96 | ||||
| -rw-r--r-- | incfs/tests/util/map_ptr_test.cpp | 291 | ||||
| -rw-r--r-- | incfs/util/include/util/map_ptr.h | 383 | ||||
| -rw-r--r-- | incfs/util/map_ptr.cpp | 135 |
10 files changed, 1022 insertions, 91 deletions
diff --git a/incfs/Android.bp b/incfs/Android.bp index 74ae415..1b97719 100644 --- a/incfs/Android.bp +++ b/incfs/Android.bp @@ -1,13 +1,48 @@ +// Copyright (C) 2020 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 specific language governing permissions and +// limitations under the License. + cc_defaults { - name: "libincfs_defaults", + name: "libincfs_defaults_common", cpp_std: "c++2a", cflags: ["-Werror", "-Wall", "-Wextra"], - defaults: ["linux_bionic_supported"], - export_include_dirs: ["include/", "kernel-headers",], - local_include_dirs: ["include/"], - header_libs: [ - "libincfs_headers", + tidy: true, + tidy_checks: [ + "android-*", + "cert-*", + "clang-analyzer-security*", + "-cert-err34-c", + "clang-analyzer-security*", + // Disabling due to many unavoidable warnings from POSIX API usage. + "-google-runtime-int", + "-google-explicit-constructor", + // operator=() does not handle self-assignment properly - all protobuf-generated classes + "-cert-oop54-cpp", + ], + target: { + linux_bionic: { + enabled: true, + }, + }, +} + +cc_defaults { + name: "libincfs_defaults", + defaults: [ + "libincfs_defaults_common", + "linux_bionic_supported", ], + header_libs: ["libincfs_headers"], export_header_lib_headers: ["libincfs_headers"], static_libs: [ "libbase", @@ -23,23 +58,7 @@ cc_defaults { "com.android.sysprop.incremental", ], }, - linux_bionic: { - enabled: true, - } }, - tidy: true, - tidy_checks: [ - "android-*", - "cert-*", - "clang-analyzer-security*", - "-cert-err34-c", - "clang-analyzer-security*", - // Disabling due to many unavoidable warnings from POSIX API usage. - "-google-runtime-int", - "-google-explicit-constructor", - // operator=() does not handle self-assignment properly - all protobuf-generated classes - "-cert-oop54-cpp", - ], } cc_library { @@ -53,6 +72,36 @@ cc_library { ], } +cc_library_static { + name: "libincfs-utils", + defaults: ["libincfs_defaults_common"], + local_include_dirs: ["util/include"], + export_include_dirs: ["util/include"], + host_supported: true, + srcs: [ + "util/map_ptr.cpp", + ], + target: { + android: { + header_libs: ["libincfs_headers"], + shared_libs: [ + "libbase", + "libincfs", + "libutils", + ], + }, + host: { + static_libs: [ + "libbase", + "libutils", + ], + }, + windows: { + enabled: true, + }, + }, +} + cc_library_headers { name: "libincfs_headers", export_include_dirs: ["include/", "kernel-headers",], @@ -67,13 +116,17 @@ cc_library_headers { cc_test { name: "libincfs-test", defaults: ["libincfs_defaults"], + local_include_dirs: ["tests/include"], static_libs: [ "libincfs", + "libincfs-utils", ], shared_libs: [ "libbase", + "libutils", ], srcs: [ + "tests/util/map_ptr_test.cpp", "tests/incfs_test.cpp", "tests/MountRegistry_test.cpp", ], diff --git a/incfs/incfs.cpp b/incfs/incfs.cpp index 5f4990e..5fe675b 100644 --- a/incfs/incfs.cpp +++ b/incfs/incfs.cpp @@ -227,6 +227,16 @@ bool IncFs_IsEnabled() { return init().enabled(); } +bool isIncFsFd(int fd) { + struct statfs fs = {}; + if (::fstatfs(fd, &fs) != 0) { + PLOG(WARNING) << __func__ << "(): could not fstatfs fd " << fd; + return false; + } + + return fs.f_type == (decltype(fs.f_type))INCFS_MAGIC_NUMBER; +} + bool isIncFsPath(const char* path) { struct statfs fs = {}; if (::statfs(path, &fs) != 0) { @@ -1190,6 +1200,10 @@ IncFsErrorCode IncFs_Unmount(const char* dir) { return 0; } +bool IncFs_IsIncFsFd(int fd) { + return isIncFsFd(fd); +} + bool IncFs_IsIncFsPath(const char* path) { return isIncFsPath(path); } diff --git a/incfs/include/incfs.h b/incfs/include/incfs.h index 79016d8..1ecefaa 100644 --- a/incfs/include/incfs.h +++ b/incfs/include/incfs.h @@ -191,6 +191,7 @@ Features features(); bool isValidFileId(FileId fileId); std::string toString(FileId fileId); IncFsFileId toFileId(std::string_view str); +bool isIncFsFd(int fd); bool isIncFsPath(std::string_view path); UniqueControl mount(std::string_view backingPath, std::string_view targetDir, diff --git a/incfs/include/incfs_inline.h b/incfs/include/incfs_inline.h index bcb7f15..946255c 100644 --- a/incfs/include/incfs_inline.h +++ b/incfs/include/incfs_inline.h @@ -70,6 +70,10 @@ inline Features features() { return Features(IncFs_Features()); } +inline bool isIncFsFd(int fd) { + return IncFs_IsIncFsFd(fd); +} + inline bool isIncFsPath(std::string_view path) { return IncFs_IsIncFsPath(details::c_str(path)); } diff --git a/incfs/include/incfs_ndk.h b/incfs/include/incfs_ndk.h index 0d9e8a6..cfc8f5d 100644 --- a/incfs/include/incfs_ndk.h +++ b/incfs/include/incfs_ndk.h @@ -140,6 +140,7 @@ typedef struct { bool IncFs_IsEnabled(); IncFsFeatures IncFs_Features(); +bool IncFs_IsIncFsFd(int fd); bool IncFs_IsIncFsPath(const char* path); static inline bool IncFs_IsValidFileId(IncFsFileId fileId) { diff --git a/incfs/tests/incfs_test.cpp b/incfs/tests/incfs_test.cpp index 1dc419f..bc02d37 100644 --- a/incfs/tests/incfs_test.cpp +++ b/incfs/tests/incfs_test.cpp @@ -14,86 +14,29 @@ * limitations under the License. */ -#include "incfs.h" - #include <android-base/file.h> -#include <android-base/logging.h> -#include <android-base/unique_fd.h> -#include <gtest/gtest.h> -#include <selinux/selinux.h> #include <sys/select.h> + #include <unistd.h> #include <optional> #include <thread> -#include "path.h" +#include "IncFsTestBase.h" using namespace android::incfs; using namespace std::literals; -static bool exists(std::string_view path) { - return access(path.data(), F_OK) == 0; -} - struct ScopedUnmount { std::string path_; explicit ScopedUnmount(std::string&& path) : path_(std::move(path)) {} ~ScopedUnmount() { unmount(path_); } }; -class IncFsTest : public ::testing::Test { +class IncFsTest : public IncFsTestBase { protected: - virtual void SetUp() { - tmp_dir_for_mount_.emplace(); - mount_dir_path_ = tmp_dir_for_mount_->path; - tmp_dir_for_image_.emplace(); - image_dir_path_ = tmp_dir_for_image_->path; - ASSERT_TRUE(exists(image_dir_path_)); - ASSERT_TRUE(exists(mount_dir_path_)); - if (!enabled()) { - GTEST_SKIP() << "test not supported: IncFS is not enabled"; - } else { - control_ = - mount(image_dir_path_, mount_dir_path_, - MountOptions{.readLogBufferPages = 4, - .defaultReadTimeoutMs = std::chrono::duration_cast< - std::chrono::milliseconds>( - kDefaultReadTimeout) - .count()}); - ASSERT_TRUE(control_.cmd() >= 0) << "Expected >= 0 got " << control_.cmd(); - ASSERT_TRUE(control_.pendingReads() >= 0); - ASSERT_TRUE(control_.logs() >= 0); - checkRestoreconResult(mountPath(INCFS_PENDING_READS_FILENAME)); - checkRestoreconResult(mountPath(INCFS_LOG_FILENAME)); - } - } - - static void checkRestoreconResult(std::string_view path) { - char* ctx = nullptr; - ASSERT_NE(-1, getfilecon(path.data(), &ctx)); - ASSERT_EQ("u:object_r:shell_data_file:s0", std::string(ctx)); - freecon(ctx); - } - - virtual void TearDown() { - unmount(mount_dir_path_); - tmp_dir_for_image_.reset(); - tmp_dir_for_mount_.reset(); - EXPECT_FALSE(exists(image_dir_path_)); - EXPECT_FALSE(exists(mount_dir_path_)); - } - - template <class... Paths> - std::string mountPath(Paths&&... paths) const { - return path::join(mount_dir_path_, std::forward<Paths>(paths)...); - } - - static IncFsFileId fileId(uint64_t i) { - IncFsFileId id = {}; - static_assert(sizeof(id) >= sizeof(i)); - memcpy(&id, &i, sizeof(i)); - return id; + virtual int32_t getReadTimeout() { + return std::chrono::duration_cast<std::chrono::milliseconds>(kDefaultReadTimeout).count(); } static IncFsSpan metadata(std::string_view sv) { @@ -186,14 +129,7 @@ protected: ASSERT_EQ((int)std::size(blocks), writeBlocks({blocks, std::size(blocks)})); } - std::string mount_dir_path_; - std::optional<TemporaryDir> tmp_dir_for_mount_; - std::string image_dir_path_; - std::optional<TemporaryDir> tmp_dir_for_image_; - inline static const std::string_view test_file_name_ = "test.txt"sv; - inline static const std::string_view test_dir_name_ = "test_dir"sv; inline static const int test_file_size_ = INCFS_DATA_FILE_BLOCK_SIZE; - Control control_; }; TEST_F(IncFsTest, GetIncfsFeatures) { @@ -217,6 +153,23 @@ TEST_F(IncFsTest, TrueIncfsPathForBindMount) { ASSERT_TRUE(isIncFsPath(tmp_dir_to_bind.path)); } +TEST_F(IncFsTest, FalseIncfsPathFile) { + TemporaryFile test_file; + ASSERT_FALSE(isIncFsFd(test_file.fd)); + ASSERT_FALSE(isIncFsPath(test_file.path)); +} + +TEST_F(IncFsTest, TrueIncfsPathForBindMountFile) { + ASSERT_EQ(0, + makeFile(control_, mountPath(test_file_name_), 0555, fileId(1), + {.size = test_file_size_})); + const auto file_path = mountPath(test_file_name_); + const android::base::unique_fd fd(open(file_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY)); + ASSERT_GE(fd.get(), 0); + ASSERT_TRUE(isIncFsFd(fd.get())); + ASSERT_TRUE(isIncFsPath(file_path)); +} + TEST_F(IncFsTest, Control) { ASSERT_TRUE(control_); EXPECT_GE(IncFs_GetControlFd(control_, CMD), 0); diff --git a/incfs/tests/include/IncFsTestBase.h b/incfs/tests/include/IncFsTestBase.h new file mode 100644 index 0000000..520c736 --- /dev/null +++ b/incfs/tests/include/IncFsTestBase.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2020 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 specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <android-base/logging.h> +#include <android-base/unique_fd.h> +#include <gtest/gtest.h> +#include <selinux/selinux.h> + +#include "incfs.h" +#include "path.h" + +namespace android::incfs { + +static bool exists(std::string_view path) { + return access(path.data(), F_OK) == 0; +} + +class IncFsTestBase : public ::testing::Test { +protected: + virtual void SetUp() { + tmp_dir_for_mount_.emplace(); + mount_dir_path_ = tmp_dir_for_mount_->path; + tmp_dir_for_image_.emplace(); + image_dir_path_ = tmp_dir_for_image_->path; + ASSERT_TRUE(exists(image_dir_path_)); + ASSERT_TRUE(exists(mount_dir_path_)); + if (!enabled()) { + GTEST_SKIP() << "test not supported: IncFS is not enabled"; + } else { + control_ = mount(image_dir_path_, mount_dir_path_, + MountOptions{.readLogBufferPages = 4, + .defaultReadTimeoutMs = getReadTimeout()}); + ASSERT_TRUE(control_.cmd() >= 0) << "Expected >= 0 got " << control_.cmd(); + ASSERT_TRUE(control_.pendingReads() >= 0); + ASSERT_TRUE(control_.logs() >= 0); + checkRestoreconResult(mountPath(INCFS_PENDING_READS_FILENAME)); + checkRestoreconResult(mountPath(INCFS_LOG_FILENAME)); + } + } + + virtual void TearDown() { + unmount(mount_dir_path_); + tmp_dir_for_image_.reset(); + tmp_dir_for_mount_.reset(); + EXPECT_FALSE(exists(image_dir_path_)); + EXPECT_FALSE(exists(mount_dir_path_)); + } + + static void checkRestoreconResult(std::string_view path) { + char* ctx = nullptr; + ASSERT_NE(-1, getfilecon(path.data(), &ctx)); + ASSERT_EQ("u:object_r:shell_data_file:s0", std::string(ctx)); + freecon(ctx); + } + + static IncFsFileId fileId(uint64_t i) { + IncFsFileId id = {}; + static_assert(sizeof(id) >= sizeof(i)); + memcpy(&id, &i, sizeof(i)); + return id; + } + + virtual int32_t getReadTimeout() { + return std::chrono::duration_cast<std::chrono::milliseconds>(kDefaultReadTimeout).count(); + } + + template <class... Paths> + std::string mountPath(Paths&&... paths) const { + return path::join(mount_dir_path_, std::forward<Paths>(paths)...); + } + + std::string mount_dir_path_; + std::optional<TemporaryDir> tmp_dir_for_mount_; + std::string image_dir_path_; + std::optional<TemporaryDir> tmp_dir_for_image_; + inline static const std::string_view test_file_name_ = "test.txt"; + inline static const std::string_view test_dir_name_ = "test_dir"; + Control control_; +}; + +} // namespace android::incfs
\ No newline at end of file diff --git a/incfs/tests/util/map_ptr_test.cpp b/incfs/tests/util/map_ptr_test.cpp new file mode 100644 index 0000000..3ca7fb0 --- /dev/null +++ b/incfs/tests/util/map_ptr_test.cpp @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2020 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 specific language governing permissions and + * limitations under the License. + */ + +#include <android-base/file.h> +#include <sys/select.h> + +#include <setjmp.h> +#include <unistd.h> + +#include "IncFsTestBase.h" + +#include "util/map_ptr.h" + +using namespace android::incfs; +using namespace std::literals; + +constexpr int FILE_PAGES = 5U; +constexpr int FILE_SIZE = INCFS_DATA_FILE_BLOCK_SIZE * FILE_PAGES; +constexpr int FILE_MISSING_PAGE = 3U; + +class MapPtrTest : public IncFsTestBase { +protected: + virtual void SetUp() override { + IncFsTestBase::SetUp(); + + const auto id = fileId(1); + ASSERT_TRUE(control_.logs() >= 0); + ASSERT_EQ(0, makeFile(control_, mountPath(test_file_name_), 0555, id, {.size = FILE_SIZE})); + auto fd = openForSpecialOps(control_, fileId(1)); + ASSERT_GE(fd.get(), 0); + + // Generate the file data. + std::vector<uint32_t> data(INCFS_DATA_FILE_BLOCK_SIZE); + for (int i = 0; i < FILE_SIZE; i++) { + data[i] = i; + } + + // Write the file, but leave one page missing. + for (int p = 0; p < FILE_PAGES; p++) { + if (p == FILE_MISSING_PAGE) { + continue; + } + auto block = DataBlock{ + .fileFd = fd.get(), + .pageIndex = p, + .compression = INCFS_COMPRESSION_KIND_NONE, + .dataSize = (uint32_t)INCFS_DATA_FILE_BLOCK_SIZE, + .data = reinterpret_cast<const char *>(data.data()) + + INCFS_DATA_FILE_BLOCK_SIZE * p, + }; + ASSERT_EQ(1, writeBlocks({&block, 1})); + } + + const auto file_path = mountPath(test_file_name_); + fd_ = open(file_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY); + } + + void TearDown() override { + IncFsTestBase::TearDown(); + ::close(fd_); + fd_ = -1; + } + + int32_t getReadTimeout() override { return 1; } + + std::unique_ptr<util::IncFsFileMap> GetFileMap(off64_t offset, size_t length) { + auto map = std::make_unique<util::IncFsFileMap>(); + return map->Create(fd_, offset, length, nullptr) ? std::move(map) : nullptr; + } + +private: + int fd_; +}; + +struct TwoValues { + uint32_t first; + uint32_t second; +}; + +TEST_F(MapPtrTest, ReadAtStart) { + auto map = GetFileMap(0U /* offset */, FILE_SIZE); + ASSERT_NE(nullptr, map); + + auto p1 = map->data<uint32_t>(); + ASSERT_TRUE(p1); + ASSERT_EQ(0U, p1.value()); + + auto p2 = map->data<TwoValues>(); + ASSERT_TRUE(p2); + ASSERT_EQ(0U, p2->first); + ASSERT_EQ(1U, p2->second); +} + +TEST_F(MapPtrTest, ReadAtStartWithOffset) { + auto map = GetFileMap(sizeof(uint32_t) * 4U /* offset */, FILE_SIZE); + ASSERT_NE(nullptr, map); + + auto p1 = map->data<uint32_t>(); + ASSERT_TRUE(p1); + ASSERT_EQ(4U, p1.value()); + + auto p2 = map->data<TwoValues>(); + ASSERT_TRUE(p2); + ASSERT_EQ(4U, p2->first); + ASSERT_EQ(5U, p2->second); +} + +TEST_F(MapPtrTest, PointerArithmetic) { + auto map = GetFileMap(0U /* offset */, FILE_SIZE); + ASSERT_NE(nullptr, map); + + auto p1 = map->data<uint32_t>() + 11U; + ASSERT_TRUE(p1); + ASSERT_EQ(11U, p1.value()); + + auto p2 = p1 - 5U; + ASSERT_TRUE(p2); + ASSERT_EQ(6U, p2.value()); + + auto dis = p1 - p2; + ASSERT_EQ((ptrdiff_t)5U, dis); +} + +TEST_F(MapPtrTest, PointerIncrement) { + auto map = GetFileMap(0U /* offset */, FILE_SIZE); + ASSERT_NE(nullptr, map); + + auto p1 = map->data<uint32_t>(); + ASSERT_TRUE(p1); + ASSERT_EQ(0U, p1.value()); + + auto p2 = p1++; + ASSERT_TRUE(p1); + ASSERT_TRUE(p2); + ASSERT_EQ(1U, p1.value()); + ASSERT_EQ(0U, p2.value()); + + auto p3 = ++p2; + ASSERT_TRUE(p2); + ASSERT_TRUE(p3); + ASSERT_EQ(1U, p2.value()); + ASSERT_EQ(1U, p3.value()); +} + +TEST_F(MapPtrTest, PointerComparison) { + auto map = GetFileMap(0U /* offset */, FILE_SIZE); + ASSERT_NE(nullptr, map); + + auto p1 = map->data<uint32_t>(); + ASSERT_TRUE(p1); + ASSERT_EQ(0U, p1.value()); + + auto p2 = p1; + ASSERT_TRUE(p1 == p2); + ASSERT_TRUE(p1 < p2 + 1U); + ASSERT_TRUE(p2 != p2 + 1U); +} + +TEST_F(MapPtrTest, PointerConvert) { + auto map = GetFileMap(0U /* offset */, FILE_SIZE); + ASSERT_NE(nullptr, map); + + auto p1 = (map->data<uint32_t>() + 11U).convert<TwoValues>(); + ASSERT_TRUE(p1); + ASSERT_EQ(11U, p1->first); + ASSERT_EQ(12U, p1->second); +} + +TEST_F(MapPtrTest, PointerOffset) { + auto map = GetFileMap(0U /* offset */, FILE_SIZE); + ASSERT_NE(nullptr, map); + + auto p1 = map->data().offset<TwoValues>(11U * sizeof(uint32_t)); + ASSERT_TRUE(p1); + ASSERT_EQ(11U, p1->first); + ASSERT_EQ(12U, p1->second); +} + +TEST_F(MapPtrTest, Iterator) { + auto map = GetFileMap(0U /* offset */, FILE_SIZE); + ASSERT_NE(nullptr, map); + + auto it = map->data<uint32_t>().iterator(); + ASSERT_TRUE(*it); + ASSERT_EQ(0U, (*it).value()); + + auto it2 = it; + ASSERT_EQ(it, it2); + + auto it3 = it++; + ASSERT_TRUE(*it3); + ASSERT_EQ(0U, (*it3).value()); + + ASSERT_NE(it, it2); + ASSERT_EQ(1, it - it2); + ASSERT_EQ(-1, it2 - it); + + auto it4 = ++it; + ASSERT_TRUE(*it4); + ASSERT_EQ(2U, (*it4).value()); + + it += 10; + ASSERT_EQ(12U, (*it).value()); +} + +static jmp_buf buf; + +void sigbus_handler(int sig) { + if (sig == SIGBUS) { + siglongjmp(buf, 1); + } else { + FAIL(); + } +} + +#define ASSERT_SIGBUS(test) \ + do { \ + signal(SIGBUS, &sigbus_handler); \ + if (sigsetjmp(buf, 1) == 0) { \ + ASSERT_EQ(0U, (test)); \ + FAIL() << "No signal raised"; \ + } \ + } while (0) + +TEST_F(MapPtrTest, VerifyMissingPageFails) { + for (uint32_t off : + std::vector<uint32_t>{0U, INCFS_DATA_FILE_BLOCK_SIZE / 2 - 1, + INCFS_DATA_FILE_BLOCK_SIZE / 2 + 1, INCFS_DATA_FILE_BLOCK_SIZE, + INCFS_DATA_FILE_BLOCK_SIZE * 3 / 2 - 1, + INCFS_DATA_FILE_BLOCK_SIZE * 3 / 2 + 1}) { + auto map = GetFileMap(off /* offset */, FILE_SIZE); + ASSERT_NE(nullptr, map); + + auto missing_page_start = INCFS_DATA_FILE_BLOCK_SIZE * FILE_MISSING_PAGE; + auto p1 = map->data().offset<uint32_t>(missing_page_start - off); + ASSERT_FALSE(p1); + ASSERT_SIGBUS(p1.value()); + + const auto p2 = p1; + ASSERT_FALSE(p2); + ASSERT_SIGBUS(p2.value()); + + const auto p3 = p2 - 1U; + ASSERT_TRUE(p3); + ASSERT_EQ(3071U, p3.value()); + + auto p4 = p3; + ASSERT_TRUE(p4); + ASSERT_EQ(3071U, p4.value()); + + ASSERT_FALSE(p4 + 1U); + ASSERT_SIGBUS((p4 + 1U).value()); + + auto p5 = p4++; + ASSERT_TRUE(p5); + ASSERT_EQ(3071U, p5.value()); + ASSERT_FALSE(p4); + ASSERT_SIGBUS(p4.value()); + + auto p6 = p3; + ASSERT_TRUE(p6); + ASSERT_EQ(3071U, p6.value()); + + auto p7 = ++p6; + ASSERT_FALSE(p7); + ASSERT_SIGBUS(p7.value()); + ASSERT_FALSE(p6); + ASSERT_SIGBUS(p6.value()); + + auto missing_page_end = INCFS_DATA_FILE_BLOCK_SIZE * (FILE_MISSING_PAGE + 1); + auto p8 = map->data().offset<uint32_t>(missing_page_end - off); + ASSERT_TRUE(p8); + ASSERT_EQ(4096U, p8.value()); + + ASSERT_FALSE(p8 - 1U); + ASSERT_SIGBUS((p8 - 1U).value()); + } +}
\ No newline at end of file diff --git a/incfs/util/include/util/map_ptr.h b/incfs/util/include/util/map_ptr.h new file mode 100644 index 0000000..4f25ead --- /dev/null +++ b/incfs/util/include/util/map_ptr.h @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2020 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 specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <memory> +#include <shared_mutex> +#include <vector> + +#include <android-base/logging.h> + +#ifdef __ANDROID__ +#include <linux/incrementalfs.h> +#endif + +namespace android { + +class FileMap; + +namespace incfs::util { + +template <typename T, bool Verified = false> +struct map_ptr; + +// This class represents a memory-mapped, read-only file that may exist on an IncFs file system. +// +// Files stored on IncFs may not be fully present. This class is able to return a smart pointer +// (map_ptr<T>) that is able to verify whether the contents of the pointer are fully present on +// IncFs. +// +// This always uses MAP_SHARED. +class IncFsFileMap final { + // Controls whether not verifying the presence of data before de-referencing the pointer aborts + // program execution. + static constexpr bool DEBUG = false; + + template <typename, bool> + friend struct map_ptr; + + using bucket_t = uint8_t; + static constexpr size_t kBucketBits = sizeof(bucket_t) * 8U; + +public: + IncFsFileMap(); + ~IncFsFileMap(); + + // Initializes the map. Does not take ownership of the file descriptor. + // Returns whether or not the file was able to be memory-mapped. + bool Create(int fd, off64_t offset, size_t length, const char* file_name); + + template <typename T = void> + map_ptr<T> data() const { + return map_ptr<T>(IsVerificationEnabled() ? this : nullptr, + reinterpret_cast<const T*>(unsafe_data())); + } + + const void* unsafe_data() const; + size_t length() const; + off64_t offset() const; + const char* file_name() const; + +private: + // Returns whether pointers created from this map should run verification of data presence + // to protect against SIGBUS signals. + bool IsVerificationEnabled() const; + +#ifdef __ANDROID__ + // Returns whether the data range is entirely present on IncFs. + bool Verify(const uint8_t* const& data_start, const uint8_t* const& data_end, + const uint8_t** prev_verified_block) const; +#endif + + // File descriptor of the memory-mapped file (not owned). + int fd_ = -1; + size_t start_block_offset_ = 0; + const uint8_t* start_block_ptr_ = 0; + + std::unique_ptr<android::FileMap> map_; + + // Bitwise cache for storing whether a block has already been verified. This cache relies on + // IncFs not deleting blocks of a file that is currently memory mapped. + mutable std::vector<std::atomic<bucket_t>> loaded_blocks_; +}; + +// Variant of map_ptr that statically guarantees that the pointed to data is fully present and +// reading data will not result in IncFs raising a SIGBUS. +template <typename T> +using verified_map_ptr = map_ptr<T, true>; + +// Smart pointer that is able to verify whether the contents of the pointer are fully present on +// the file system before using the pointer. Files residing on IncFs may not be fully present. +// +// Before attempting to use the data represented by the smart pointer, the caller should always use +// the bool operator to verify the presence of the data. The bool operator is not thread-safe. If +// this pointer must be used in multiple threads concurrently, use verified_map_ptr instead. +// +// map_ptr created from raw pointers have less overhead than when created from IncFsFileMap. +template <typename T, bool Verified> +struct map_ptr final { +private: + friend class IncFsFileMap; + + // To access internals of map_ptr with a different type + template <typename, bool> + friend struct map_ptr; + + template <typename T1> + using IsVoid = typename std::enable_if_t<std::is_void<T1>::value, int>; + + template <typename T1> + using NotVoid = typename std::enable_if_t<!std::is_void<T1>::value, int>; + + template <bool V> + using IsVerified = typename std::enable_if_t<V, int>; + + template <bool V> + using IsUnverified = typename std::enable_if_t<!V, int>; + +public: + class const_iterator final { + public: + friend struct map_ptr<T, Verified>; + using iterator_category = std::random_access_iterator_tag; + using value_type = const map_ptr<T>; + using difference_type = std::ptrdiff_t; + using pointer = void; + using reference = value_type; + + const_iterator() = default; + const_iterator(const const_iterator& it) = default; + + bool operator==(const const_iterator& other) const { return safe_ptr_ == other.safe_ptr_; } + bool operator!=(const const_iterator& other) const { return safe_ptr_ != other.safe_ptr_; } + std::ptrdiff_t operator-(const const_iterator& other) const { + return safe_ptr_ - other.safe_ptr_; + } + + reference operator*() const { return safe_ptr_; } + + const const_iterator& operator++() { + safe_ptr_++; + return *this; + } + + const_iterator& operator+=(int n) { + safe_ptr_ = safe_ptr_ + n; + return *this; + } + + const const_iterator operator++(int) { + const_iterator temp(*this); + safe_ptr_++; + return temp; + } + + private: + explicit const_iterator(const map_ptr<T>& ptr) : safe_ptr_(ptr) {} + map_ptr<T> safe_ptr_; + }; + + // Default constructor + map_ptr() = default; + + // Implicit conversion from raw pointer + map_ptr(const T* ptr) : map_ptr(nullptr, ptr, nullptr) {} + + // Copy constructor + map_ptr(const map_ptr& other) = default; + + // Implicit copy conversion from verified to unverified map_ptr<T> + template <bool V2, bool V1 = Verified, IsUnverified<V1> = 0, IsVerified<V2> = 0> + map_ptr(const map_ptr<T, V2>& other) : map_ptr(other.map_, other.ptr_, other.verified_block_) {} + + // Move constructor + map_ptr(map_ptr&& other) noexcept = default; + + // Implicit move conversion from verified to unverified map_ptr<T> + template <bool V2, bool V1 = Verified, IsUnverified<V1> = 0, IsVerified<V2> = 0> + map_ptr(map_ptr&& other) : map_ptr(other.map_, other.ptr_, other.verified_block_) {} + + // Implicit conversion to unverified map_ptr<void> + template <typename U, bool V2, typename T1 = T, bool V1 = Verified, IsVoid<T1> = 0, + NotVoid<U> = 0, IsUnverified<V1> = 0> + map_ptr(const map_ptr<U, V2>& other) + : map_ptr(other.map_, reinterpret_cast<const void*>(other.ptr_), other.verified_block_) {} + + // Implicit conversion from regular raw pointer + map_ptr& operator=(const T* ptr) { + map_ = nullptr; + ptr_ = ptr; + verified_block_ = nullptr; + verified_ = Verified; + return *this; + } + + // Copy assignment operator + map_ptr& operator=(const map_ptr& other) = default; + + // Copy assignment operator + template <bool V2, bool V1 = Verified, IsUnverified<V1> = 0, IsVerified<V2> = 0> + map_ptr& operator=(const map_ptr<T, V2>& other) { + map_ = other.map_; + ptr_ = other.ptr_; + verified_block_ = other.verified_block_; + verified_ = other.verified_; + return *this; + } + + template <bool V2> + bool operator==(const map_ptr<T, V2>& other) const { + return ptr_ == other.ptr_; + } + + template <bool V2> + bool operator!=(const map_ptr<T, V2>& other) const { + return ptr_ != other.ptr_; + } + + template <bool V2> + bool operator<(const map_ptr<T, V2>& other) const { + return ptr_ < other.ptr_; + } + + template <bool V2> + std::ptrdiff_t operator-(const map_ptr<T, V2>& other) const { + return ptr_ - other.ptr_; + } + + template <typename U> + map_ptr<U> convert() const { + return map_ptr<U>(map_, reinterpret_cast<const U*>(ptr_), verified_block_); + } + + // Retrieves a map_ptr<T> offset from an original map_ptr<U> by the specified number of `offset` + // bytes. + template <typename U> + map_ptr<U> offset(std::ptrdiff_t offset) const { + return map_ptr<U>(map_, + reinterpret_cast<const U*>(reinterpret_cast<const uint8_t*>(ptr_) + + offset), + verified_block_); + } + + // Returns a raw pointer to the value of this pointer. + const T* unsafe() const { return ptr_; } + + // Start T == void methods + + template <typename T1 = T, IsVoid<T1> = 0> + operator bool() const { + return ptr_ != nullptr; + } + + // End T == void methods + // Start T != void methods + + template <typename T1 = T, NotVoid<T1> = 0> + const_iterator iterator() const { + return const_iterator(*this); + } + + template <typename T1 = T, NotVoid<T1> = 0> + operator bool() const { + return Verified ? ptr_ != nullptr : verify() != nullptr; + } + + template <typename T1 = T, NotVoid<T1> = 0> + const map_ptr<T1>& operator++() { + verified_ = false; + ++ptr_; + return *this; + } + + template <typename T1 = T, NotVoid<T1> = 0> + const map_ptr<T1> operator++(int) { + map_ptr<T1> temp = *this; + verified_ = false; + ++ptr_; + return temp; + } + + template <typename S, typename T1 = T, NotVoid<T1> = 0> + map_ptr<T1> operator+(const S n) const { + return map_ptr<T1>(map_, ptr_ + n, verified_block_); + } + + template <typename S, typename T1 = T, NotVoid<T1> = 0> + map_ptr<T1> operator-(const S n) const { + return map_ptr<T1>(map_, ptr_ - n, verified_block_); + } + + // Returns the value of the pointer. + // The caller should verify the presence of the pointer data before calling this method. + template <typename T1 = T, NotVoid<T1> = 0> + const T1& value() const { + CHECK(!IncFsFileMap::DEBUG || verified_) + << "Did not verify presence before de-referencing safe pointer"; + return *ptr_; + } + + // Returns a raw pointer to the value this pointer. + // The caller should verify the presence of the pointer data before calling this method. + template <typename T1 = T, NotVoid<T1> = 0> + const T1* const& operator->() const { + CHECK(!IncFsFileMap::DEBUG || verified_) + << "Did not verify presence before de-referencing safe pointer"; + return ptr_; + } + + // Verifies the presence of `n` elements of `T`. + // + // Returns a raw pointer to the value of this pointer if the elements are completely present; + // otherwise, returns + // nullptr. + template <typename N = int, typename T1 = T, NotVoid<T1> = 0> + const T1* verify(N n = 1) const { + verified_ = true; + +#ifdef __ANDROID__ + if (!map_) { + return ptr_; + } + + const auto data_start = reinterpret_cast<const uint8_t*>(ptr_); + const auto data_end = reinterpret_cast<const uint8_t*>(ptr_ + n); + + // If the data is entirely within the block beginning at the previous verified block + // pointer, then the data can safely be used. + if (LIKELY(data_start >= verified_block_ && + data_end <= verified_block_ + INCFS_DATA_FILE_BLOCK_SIZE)) { + return ptr_; + } + + if (LIKELY(map_->Verify(data_start, data_end, &verified_block_))) { + return ptr_; + } + + verified_ = false; + return nullptr; +#else + (void)n; + return ptr_; +#endif + } + + // Returns a verified version of this pointer. + // The caller should verify the presence of the pointer data before calling this method. + template <typename T1 = T, NotVoid<T1> = 0> + verified_map_ptr<T1> verified() const { + CHECK(!IncFsFileMap::DEBUG || verified_) + << "Did not verify presence before de-referencing safe pointer"; + return verified_map_ptr<T1>(map_, ptr_, verified_block_); + } + + // End T != void type methods +private: + map_ptr(const IncFsFileMap* map, const T* ptr) + : ptr_(ptr), map_(map), verified_block_(nullptr) {} + map_ptr(const IncFsFileMap* map, const T* ptr, const uint8_t* verified_block) + : ptr_(ptr), map_(map), verified_block_(verified_block) {} + + const T* ptr_ = nullptr; + mutable const IncFsFileMap* map_ = nullptr; + mutable const uint8_t* verified_block_; + mutable bool verified_ = Verified; +}; + +} // namespace incfs::util + +} // namespace android
\ No newline at end of file diff --git a/incfs/util/map_ptr.cpp b/incfs/util/map_ptr.cpp new file mode 100644 index 0000000..f32bc89 --- /dev/null +++ b/incfs/util/map_ptr.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2020 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 specific language governing permissions and + * limitations under the License. + */ + +#include <android-base/file.h> +#include <android-base/stringprintf.h> +#include <utils/FileMap.h> + +#ifdef __ANDROID__ +#include "incfs_inline.h" +#endif + +#include "util/map_ptr.h" + +namespace android::incfs::util { + +IncFsFileMap::IncFsFileMap() = default; +IncFsFileMap::~IncFsFileMap() = default; + +const void* IncFsFileMap::unsafe_data() const { + return map_->getDataPtr(); +} + +size_t IncFsFileMap::length() const { + return map_->getDataLength(); +} + +off64_t IncFsFileMap::offset() const { + return map_->getDataOffset(); +} + +const char* IncFsFileMap::file_name() const { + return map_->getFileName(); +} + +#ifndef __ANDROID__ +bool IncFsFileMap::Create(int fd, off64_t offset, size_t length, const char* debug_file_name) { + if (fd < 0) return false; + map_ = std::make_unique<android::FileMap>(); + return map_->create(debug_file_name, fd, offset, length, true /* readOnly */); +} + +bool IncFsFileMap::IsVerificationEnabled() const { + return false; +} + +#else +using data_block_index_t = uint32_t; + +data_block_index_t get_block_index(const uint8_t* ptr, const uint8_t* start_block_ptr) { + return (ptr - start_block_ptr) / INCFS_DATA_FILE_BLOCK_SIZE; +} + +bool IncFsFileMap::Create(int fd, off64_t offset, size_t length, const char* file_name) { + map_ = std::make_unique<android::FileMap>(); + if (!map_->create(file_name, fd, offset, length, true /* readOnly */)) { + return false; + } + + // Initialize the block cache with enough buckets to hold all of the blocks within the + // memory-mapped region. + fd_ = fd; + size_t offset_diff = offset % INCFS_DATA_FILE_BLOCK_SIZE; + size_t base_length_ = length + offset_diff; + start_block_offset_ = offset - offset_diff; + start_block_ptr_ = reinterpret_cast<const uint8_t*>(map_->getDataPtr()) - offset_diff; + + const size_t bucket_count = (base_length_ / INCFS_DATA_FILE_BLOCK_SIZE) / kBucketBits; + loaded_blocks_ = std::vector<std::atomic<bucket_t> >(bucket_count + 1U); + return true; +} + +bool IncFsFileMap::IsVerificationEnabled() const { + return isIncFsFd(fd_) && isFullyLoaded(fd_) != LoadingState::Full; +} + +bool IncFsFileMap::Verify(const uint8_t* const& data_start, const uint8_t* const& data_end, + const uint8_t** prev_verified_block) const { + const data_block_index_t start_index = get_block_index(data_start, start_block_ptr_); + const data_block_index_t end_index = get_block_index(data_end - 1U, start_block_ptr_); + + bool success = true; + // Retrieve the set of the required blocks that must be present in order to read the data + // safely. + for (data_block_index_t curr_index = start_index; curr_index <= end_index; ++curr_index) { + const size_t i = curr_index / kBucketBits; + const auto present_bit = 1U << (curr_index % kBucketBits); + std::atomic<bucket_t>& bucket = loaded_blocks_[i]; + if (LIKELY(bucket.load(std::memory_order_relaxed) & present_bit)) { + continue; + } + + // Touch all of the blocks with pread to ensure that the region of data is fully present. + uint8_t value; + const off64_t read_offset = (curr_index * INCFS_DATA_FILE_BLOCK_SIZE) + start_block_offset_; + if (UNLIKELY(!android::base::ReadFullyAtOffset(fd_, &value, 1U, read_offset))) { + success = false; + break; + } + + bucket.fetch_or(present_bit, std::memory_order_relaxed); + } + + if (UNLIKELY(!success)) { + // Log the region of the file that could not be fully loaded. + size_t start_offset = (data_start - start_block_ptr_) + map_->getDataOffset(); + size_t end_offset = (data_end - start_block_ptr_) + map_->getDataOffset(); + std::string location = file_name() ? base::StringPrintf("path %s", file_name()) + : base::StringPrintf("fd %d", fd_); + const std::string message = + base::StringPrintf("region 0x%016zx - 0x%016zx of %s not fully loaded", + start_offset, end_offset, location.c_str()); + LOG(WARNING) << message; + return false; + } + + // Update the previous verified block pointer to optimize repeated verifies on the same block. + *prev_verified_block = start_block_ptr_ + (end_index * INCFS_DATA_FILE_BLOCK_SIZE); + return true; +} +#endif + +} // namespace android::incfs::util
\ No newline at end of file |
