summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRyan Mitchell <rtmitchell@google.com>2020-09-18 00:39:33 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2020-09-18 00:39:33 +0000
commita92e47889ac119ca8cb415d176a9f760ad2b7d67 (patch)
treebbaab994c60fe810f4e6d89db45db1b6cf7f36fb
parent9ee8ce3a1c8b14f84e53b7bcd40d235291a3a24f (diff)
parentfec2547ee70d363b5b8faa6717d2a6aa0cfcca12 (diff)
downloadplatform_system_incremental_delivery-a92e47889ac119ca8cb415d176a9f760ad2b7d67.tar.gz
platform_system_incremental_delivery-a92e47889ac119ca8cb415d176a9f760ad2b7d67.tar.bz2
platform_system_incremental_delivery-a92e47889ac119ca8cb415d176a9f760ad2b7d67.zip
Add util::map_ptr to incFs am: 97c05a314c am: f0fd3f117d am: d918ca2fc8 am: fec2547ee7
Original change: https://android-review.googlesource.com/c/platform/system/incremental_delivery/+/1430128 Change-Id: I7784048aa5e51611b68f885b0e8f26d67a4f32c0
-rw-r--r--incfs/Android.bp97
-rw-r--r--incfs/incfs.cpp14
-rw-r--r--incfs/include/incfs.h1
-rw-r--r--incfs/include/incfs_inline.h4
-rw-r--r--incfs/include/incfs_ndk.h1
-rw-r--r--incfs/tests/incfs_test.cpp91
-rw-r--r--incfs/tests/include/IncFsTestBase.h96
-rw-r--r--incfs/tests/util/map_ptr_test.cpp291
-rw-r--r--incfs/util/include/util/map_ptr.h383
-rw-r--r--incfs/util/map_ptr.cpp135
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