|  | // Copyright 2016 The Fuchsia Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include <fcntl.h> | 
|  | #include <stdio.h> | 
|  | #include <stdlib.h> | 
|  | #include <unistd.h> | 
|  | #include <zircon/syscalls.h> | 
|  |  | 
|  | #include <iostream> | 
|  | #include <tuple> | 
|  | #include <vector> | 
|  |  | 
|  | #include <fbl/unique_fd.h> | 
|  |  | 
|  | #include "src/storage/fs_test/fs_test_fixture.h" | 
|  |  | 
|  | namespace fs_test { | 
|  | namespace { | 
|  |  | 
|  | using ParamType = | 
|  | std::tuple<TestFilesystemOptions, | 
|  | std::tuple</*write_offset=*/size_t, /*read_offset=*/size_t, /*write_size=*/size_t>>; | 
|  |  | 
|  | class SparseTest : public BaseFilesystemTest, public testing::WithParamInterface<ParamType> { | 
|  | public: | 
|  | SparseTest() : BaseFilesystemTest(std::get<0>(GetParam())) {} | 
|  |  | 
|  | size_t write_offset() const { return std::get<0>(std::get<1>(GetParam())); } | 
|  | size_t read_offset() const { return std::get<1>(std::get<1>(GetParam())); } | 
|  | size_t write_size() const { return std::get<2>(std::get<1>(GetParam())); } | 
|  | }; | 
|  |  | 
|  | TEST_P(SparseTest, ReadAfterSparseWriteReturnsCorrectData) { | 
|  | const std::string my_file = GetPath("my_file"); | 
|  | fbl::unique_fd fd(open(my_file.c_str(), O_RDWR | O_CREAT, 0644)); | 
|  | ASSERT_TRUE(fd); | 
|  |  | 
|  | // Create a random write buffer of data | 
|  | std::vector<uint8_t> wbuf(write_size()); | 
|  | unsigned int seed = static_cast<unsigned int>(zx_ticks_get()); | 
|  | std::cout << "Sparse test using seed: " << seed << std::endl; | 
|  | for (size_t i = 0; i < write_size(); i++) { | 
|  | wbuf[i] = (uint8_t)rand_r(&seed); | 
|  | } | 
|  |  | 
|  | // Dump write buffer to file | 
|  | ASSERT_EQ(pwrite(fd.get(), wbuf.data(), write_size(), write_offset()), | 
|  | static_cast<ssize_t>(write_size())); | 
|  |  | 
|  | // Reopen file | 
|  | ASSERT_EQ(close(fd.release()), 0); | 
|  | fd.reset(open(my_file.c_str(), O_RDWR, 0644)); | 
|  | ASSERT_TRUE(fd); | 
|  |  | 
|  | // Access read buffer from file | 
|  | const size_t file_size = write_offset() + write_size(); | 
|  | const size_t bytes_to_read = | 
|  | (file_size - read_offset()) > write_size() ? write_size() : (file_size - read_offset()); | 
|  | ASSERT_GT(bytes_to_read, 0ul) << "We want to test writing AND reading"; | 
|  | std::vector<uint8_t> rbuf(bytes_to_read); | 
|  | ASSERT_EQ(pread(fd.get(), rbuf.data(), bytes_to_read, read_offset()), | 
|  | static_cast<ssize_t>(bytes_to_read)); | 
|  |  | 
|  | const size_t sparse_length = (read_offset() < write_offset()) | 
|  | ? std::min(bytes_to_read, write_offset() - read_offset()) | 
|  | : 0; | 
|  |  | 
|  | if (sparse_length > 0) { | 
|  | for (size_t i = 0; i < sparse_length; i++) { | 
|  | ASSERT_EQ(rbuf[i], 0) << "This portion of file should be sparse; but isn't"; | 
|  | } | 
|  | } | 
|  |  | 
|  | const size_t wbuf_offset = (read_offset() < write_offset()) ? 0 : read_offset() - write_offset(); | 
|  | const size_t valid_length = bytes_to_read - sparse_length; | 
|  |  | 
|  | if (valid_length > 0) { | 
|  | for (size_t i = 0; i < valid_length; i++) { | 
|  | ASSERT_EQ(rbuf[sparse_length + i], wbuf[wbuf_offset + i]); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Clean up | 
|  | ASSERT_EQ(close(fd.release()), 0); | 
|  | ASSERT_EQ(unlink(my_file.c_str()), 0); | 
|  | } | 
|  |  | 
|  | std::string GetParamDescription(const testing::TestParamInfo<ParamType>& param) { | 
|  | std::stringstream s; | 
|  | s << std::get<0>(param.param) << "WithWriteOffset" << std::get<0>(std::get<1>(param.param)) | 
|  | << "ReadOffset" << std::get<1>(std::get<1>(param.param)) << "WriteSize" | 
|  | << std::get<2>(std::get<1>(param.param)); | 
|  | return s.str(); | 
|  | } | 
|  |  | 
|  | std::vector<TestFilesystemOptions> AllTestFilesystemsThatSupportSparseFilesWithCustomDisk() { | 
|  | std::vector<TestFilesystemOptions> filesystems; | 
|  | for (TestFilesystemOptions options : AllTestFilesystems()) { | 
|  | if (!options.filesystem->GetTraits().supports_sparse_files) | 
|  | continue; | 
|  | options.device_block_count = 1LLU << 24; | 
|  | options.device_block_size = 1LLU << 9; | 
|  | options.fvm_slice_size = 1LLU << 23; | 
|  | options.zero_fill = true; | 
|  | filesystems.push_back(options); | 
|  | } | 
|  | return filesystems; | 
|  | } | 
|  |  | 
|  | constexpr size_t kBlockSize = 8192; | 
|  | constexpr size_t kDirectBlocks = 16; | 
|  |  | 
|  | INSTANTIATE_TEST_SUITE_P( | 
|  | /*no prefix*/, SparseTest, | 
|  | testing::Combine( | 
|  | testing::ValuesIn(AllTestFilesystemsThatSupportSparseFilesWithCustomDisk()), | 
|  | testing::Values(std::make_tuple(0, 0, kBlockSize), | 
|  | std::make_tuple(kBlockSize / 2, 0, kBlockSize), | 
|  | std::make_tuple(kBlockSize / 2, kBlockSize, kBlockSize), | 
|  | std::make_tuple(kBlockSize, 0, kBlockSize), | 
|  | std::make_tuple(kBlockSize, kBlockSize / 2, kBlockSize), | 
|  | std::make_tuple(kBlockSize* kDirectBlocks, | 
|  | kBlockSize* kDirectBlocks - kBlockSize, kBlockSize * 2), | 
|  | std::make_tuple(kBlockSize* kDirectBlocks, | 
|  | kBlockSize* kDirectBlocks - kBlockSize, kBlockSize * 32), | 
|  | std::make_tuple(kBlockSize* kDirectBlocks + kBlockSize, | 
|  | kBlockSize* kDirectBlocks - kBlockSize, kBlockSize * 32), | 
|  | std::make_tuple(kBlockSize* kDirectBlocks + kBlockSize, | 
|  | kBlockSize* kDirectBlocks + 2 * kBlockSize, | 
|  | kBlockSize * 32))), | 
|  | GetParamDescription); | 
|  |  | 
|  | // For filesystems that don't support sparse files, run with a reduced set of tests because | 
|  | // they will run more slowly. | 
|  | INSTANTIATE_TEST_SUITE_P( | 
|  | NonSparseFilesystems, SparseTest, | 
|  | testing::Combine( | 
|  | testing::ValuesIn(MapAndFilterAllTestFilesystems( | 
|  | [](const TestFilesystemOptions& options) -> std::optional<TestFilesystemOptions> { | 
|  | if (options.filesystem->GetTraits().supports_sparse_files) { | 
|  | return std::nullopt; | 
|  | } else { | 
|  | return options; | 
|  | } | 
|  | })), | 
|  | testing::Values(std::make_tuple(0, 0, 100), std::make_tuple(500, 100, 100), | 
|  | std::make_tuple(500, 550, 100), std::make_tuple(0, 0, 10000), | 
|  | std::make_tuple(5000, 0, 10000), std::make_tuple(5000, 10000, 10000), | 
|  | std::make_tuple(10000, 0, 10000), std::make_tuple(10000, 5000, 10000))), | 
|  | GetParamDescription); | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace fs_test |