| /* |
| * 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<IncFsFileMap> GetFileMap(off64_t offset, size_t length) { |
| auto map = std::make_unique<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, ReadNull) { |
| 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()); |
| |
| p1 = nullptr; |
| ASSERT_FALSE(p1); |
| |
| p1 = map->data<uint32_t>(); |
| ASSERT_TRUE(p1); |
| ASSERT_EQ(0U, p1.value()); |
| } |
| |
| 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(11U * sizeof(uint32_t)).convert<TwoValues>(); |
| 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(missing_page_start - off).convert<uint32_t>(); |
| 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(missing_page_end - off).convert<uint32_t>(); |
| ASSERT_TRUE(p8); |
| ASSERT_EQ(4096U, p8.value()); |
| |
| ASSERT_FALSE(p8 - 1U); |
| ASSERT_SIGBUS((p8 - 1U).value()); |
| } |
| } |