Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1326648f authored by David Anderson's avatar David Anderson
Browse files

libfiemap_writer: Remove Flush and Write methods.

We no longer use these methods since they require write access to the
userdata block device. This patch removes them to simplify FiemapWriter,
and avoid opening userdata with write permissions.

Bug: 122556707
Test: fiemap_writer_test gtest
Change-Id: I0091eb5ff92253533c67c89b0059c34953aae211
parent 161c439f
Loading
Loading
Loading
Loading
+1 −121
Original line number Diff line number Diff line
@@ -374,14 +374,6 @@ bool FiemapWriter::HasPinnedExtents(const std::string& file_path) {
    return IsFilePinned(fd, file_path, sfs.f_type);
}

static void LogExtent(uint32_t num, const struct fiemap_extent& ext) {
    LOG(INFO) << "Extent #" << num;
    LOG(INFO) << "  fe_logical:  " << ext.fe_logical;
    LOG(INFO) << "  fe_physical: " << ext.fe_physical;
    LOG(INFO) << "  fe_length:   " << ext.fe_length;
    LOG(INFO) << "  fe_flags:    0x" << std::hex << ext.fe_flags;
}

static bool ReadFiemap(int file_fd, const std::string& file_path,
                       std::vector<struct fiemap_extent>* extents) {
    uint64_t fiemap_size =
@@ -473,7 +465,7 @@ FiemapUniquePtr FiemapWriter::Open(const std::string& file_path, uint64_t file_s
    }

    ::android::base::unique_fd bdev_fd(
            TEMP_FAILURE_RETRY(open(bdev_path.c_str(), O_RDWR | O_CLOEXEC)));
            TEMP_FAILURE_RETRY(open(bdev_path.c_str(), O_RDONLY | O_CLOEXEC)));
    if (bdev_fd < 0) {
        PLOG(ERROR) << "Failed to open block device: " << bdev_path;
        cleanup(file_path, create);
@@ -530,7 +522,6 @@ FiemapUniquePtr FiemapWriter::Open(const std::string& file_path, uint64_t file_s
    fmap->file_path_ = abs_path;
    fmap->bdev_path_ = bdev_path;
    fmap->file_fd_ = std::move(file_fd);
    fmap->bdev_fd_ = std::move(bdev_fd);
    fmap->file_size_ = file_size;
    fmap->bdev_size_ = bdevsz;
    fmap->fs_type_ = fs_type;
@@ -541,120 +532,9 @@ FiemapUniquePtr FiemapWriter::Open(const std::string& file_path, uint64_t file_s
    return fmap;
}

bool FiemapWriter::Flush() const {
    if (fsync(bdev_fd_)) {
        PLOG(ERROR) << "Failed to flush " << bdev_path_ << " with fsync";
        return false;
    }
    return true;
}

// TODO: Test with fs block_size > bdev block_size
bool FiemapWriter::Write(off64_t off, uint8_t* buffer, uint64_t size) {
    if (!size || size > file_size_) {
        LOG(ERROR) << "Failed write: size " << size << " is invalid for file's size " << file_size_;
        return false;
    }

    if (off + size > file_size_) {
        LOG(ERROR) << "Failed write: Invalid offset " << off << " or size " << size
                   << " for file size " << file_size_;
        return false;
    }

    if ((off & (block_size_ - 1)) || (size & (block_size_ - 1))) {
        LOG(ERROR) << "Failed write: Unaligned offset " << off << " or size " << size
                   << " for block size " << block_size_;
        return false;
    }

    if (!IsFilePinned(file_fd_, file_path_, fs_type_)) {
        LOG(ERROR) << "Failed write: file " << file_path_ << " is not pinned";
        return false;
    }

    // find extents that must be written to and then write one at a time.
    uint32_t num_extent = 1;
    uint32_t buffer_offset = 0;
    for (auto& extent : extents_) {
        uint64_t e_start = extent.fe_logical;
        uint64_t e_end = extent.fe_logical + extent.fe_length;
        // Do we write in this extent ?
        if (off >= e_start && off < e_end) {
            uint64_t written = WriteExtent(extent, buffer + buffer_offset, off, size);
            if (written == 0) {
                return false;
            }

            buffer_offset += written;
            off += written;
            size -= written;

            // Paranoid check to make sure we are done with this extent now
            if (size && (off >= e_start && off < e_end)) {
                LOG(ERROR) << "Failed to write extent fully";
                LogExtent(num_extent, extent);
                return false;
            }

            if (size == 0) {
                // done
                break;
            }
        }
        num_extent++;
    }

    return true;
}

bool FiemapWriter::Read(off64_t off, uint8_t* buffer, uint64_t size) {
    return false;
}

// private helpers

// WriteExtent() Returns the total number of bytes written. It will always be multiple of
// block_size_. 0 is returned in one of the two cases.
//  1. Any write failed between logical_off & logical_off + length.
//  2. The logical_offset + length doesn't overlap with the extent passed.
// The function can either partially for fully write the extent depending on the
// logical_off + length. It is expected that alignment checks for size and offset are
// performed before calling into this function.
uint64_t FiemapWriter::WriteExtent(const struct fiemap_extent& ext, uint8_t* buffer,
                                   off64_t logical_off, uint64_t length) {
    uint64_t e_start = ext.fe_logical;
    uint64_t e_end = ext.fe_logical + ext.fe_length;
    if (logical_off < e_start || logical_off >= e_end) {
        LOG(ERROR) << "Failed write extent, invalid offset " << logical_off << " and size "
                   << length;
        LogExtent(0, ext);
        return 0;
    }

    off64_t bdev_offset = ext.fe_physical + (logical_off - e_start);
    if (bdev_offset >= bdev_size_) {
        LOG(ERROR) << "Failed write extent, invalid block # " << bdev_offset << " for block device "
                   << bdev_path_ << " of size " << bdev_size_ << " bytes";
        return 0;
    }
    if (TEMP_FAILURE_RETRY(lseek64(bdev_fd_, bdev_offset, SEEK_SET)) == -1) {
        PLOG(ERROR) << "Failed write extent, seek offset for " << bdev_path_ << " offset "
                    << bdev_offset;
        return 0;
    }

    // Determine how much we want to write at once.
    uint64_t logical_end = logical_off + length;
    uint64_t write_size = (e_end <= logical_end) ? (e_end - logical_off) : length;
    if (!android::base::WriteFully(bdev_fd_, buffer, write_size)) {
        PLOG(ERROR) << "Failed write extent, write " << bdev_path_ << " at " << bdev_offset
                    << " size " << write_size;
        return 0;
    }

    return write_size;
}

}  // namespace fiemap_writer
}  // namespace android
+19 −131
Original line number Diff line number Diff line
@@ -138,36 +138,31 @@ TEST_F(FiemapWriterTest, CheckFileExtents) {
    EXPECT_GT(fptr->extents().size(), 0);
}

TEST_F(FiemapWriterTest, CheckWriteError) {
    FiemapUniquePtr fptr = FiemapWriter::Open(testfile, testfile_size);
    ASSERT_NE(fptr, nullptr);

    // prepare buffer for writing the pattern - 0xa0
    uint64_t blocksize = fptr->block_size();
    auto buffer = std::unique_ptr<void, decltype(&free)>(calloc(1, blocksize), free);
    ASSERT_NE(buffer, nullptr);
    memset(buffer.get(), 0xa0, blocksize);

    uint8_t* p = static_cast<uint8_t*>(buffer.get());
    for (off64_t off = 0; off < testfile_size; off += blocksize) {
        ASSERT_TRUE(fptr->Write(off, p, blocksize));
    }

    EXPECT_TRUE(fptr->Flush());
}

class TestExistingFile : public ::testing::Test {
  protected:
    void SetUp() override {
        std::string exec_dir = ::android::base::GetExecutableDirectory();
        std::string unaligned_file = exec_dir + "/testdata/unaligned_file";
        std::string file_4k = exec_dir + "/testdata/file_4k";
        std::string file_32k = exec_dir + "/testdata/file_32k";
        fptr_unaligned = FiemapWriter::Open(unaligned_file, 4097, false);
        fptr_4k = FiemapWriter::Open(file_4k, 4096, false);
        fptr_32k = FiemapWriter::Open(file_32k, 32768, false);
        unaligned_file_ = exec_dir + "/testdata/unaligned_file";
        file_4k_ = exec_dir + "/testdata/file_4k";
        file_32k_ = exec_dir + "/testdata/file_32k";

        CleanupFiles();
        fptr_unaligned = FiemapWriter::Open(unaligned_file_, 4097);
        fptr_4k = FiemapWriter::Open(file_4k_, 4096);
        fptr_32k = FiemapWriter::Open(file_32k_, 32768);
    }

    void TearDown() { CleanupFiles(); }

    void CleanupFiles() {
        unlink(unaligned_file_.c_str());
        unlink(file_4k_.c_str());
        unlink(file_32k_.c_str());
    }

    std::string unaligned_file_;
    std::string file_4k_;
    std::string file_32k_;
    FiemapUniquePtr fptr_unaligned;
    FiemapUniquePtr fptr_4k;
    FiemapUniquePtr fptr_32k;
@@ -184,33 +179,6 @@ TEST_F(TestExistingFile, ErrorChecks) {
    EXPECT_GT(fptr_32k->extents().size(), 0);
}

TEST_F(TestExistingFile, CheckWriteError) {
    ASSERT_NE(fptr_4k, nullptr);
    // prepare buffer for writing the pattern - 0xa0
    uint64_t blocksize = fptr_4k->block_size();
    auto buff_4k = std::unique_ptr<void, decltype(&free)>(calloc(1, blocksize), free);
    ASSERT_NE(buff_4k, nullptr);
    memset(buff_4k.get(), 0xa0, blocksize);

    uint8_t* p = static_cast<uint8_t*>(buff_4k.get());
    for (off64_t off = 0; off < 4096; off += blocksize) {
        ASSERT_TRUE(fptr_4k->Write(off, p, blocksize));
    }
    EXPECT_TRUE(fptr_4k->Flush());

    ASSERT_NE(fptr_32k, nullptr);
    // prepare buffer for writing the pattern - 0xa0
    blocksize = fptr_32k->block_size();
    auto buff_32k = std::unique_ptr<void, decltype(&free)>(calloc(1, blocksize), free);
    ASSERT_NE(buff_32k, nullptr);
    memset(buff_32k.get(), 0xa0, blocksize);
    p = static_cast<uint8_t*>(buff_32k.get());
    for (off64_t off = 0; off < 4096; off += blocksize) {
        ASSERT_TRUE(fptr_32k->Write(off, p, blocksize));
    }
    EXPECT_TRUE(fptr_32k->Flush());
}

class VerifyBlockWritesExt4 : public ::testing::Test {
    // 2GB Filesystem and 4k block size by default
    static constexpr uint64_t block_size = 4096;
@@ -253,46 +221,6 @@ class VerifyBlockWritesExt4 : public ::testing::Test {
    std::string fs_path;
};

TEST_F(VerifyBlockWritesExt4, CheckWrites) {
    EXPECT_EQ(access(fs_path.c_str(), F_OK), 0);

    std::string file_path = mntpoint + "/testfile";
    uint64_t file_size = 100 * 1024 * 1024;
    auto buffer = std::unique_ptr<void, decltype(&free)>(calloc(1, getpagesize()), free);
    ASSERT_NE(buffer, nullptr);
    memset(buffer.get(), 0xa0, getpagesize());
    {
        // scoped fiemap writer
        FiemapUniquePtr fptr = FiemapWriter::Open(file_path, file_size);
        ASSERT_NE(fptr, nullptr);
        uint8_t* p = static_cast<uint8_t*>(buffer.get());
        for (off64_t off = 0; off < file_size / getpagesize(); off += getpagesize()) {
            ASSERT_TRUE(fptr->Write(off, p, getpagesize()));
        }
        EXPECT_TRUE(fptr->Flush());
    }
    // unmount file system here to make sure we invalidated all page cache and
    // remount the filesystem again for verification
    ASSERT_EQ(umount(mntpoint.c_str()), 0);

    LoopDevice loop_dev(fs_path);
    ASSERT_TRUE(loop_dev.valid());
    ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint.c_str(), "ext4", 0, nullptr), 0)
            << "failed to mount: " << loop_dev.device() << " on " << mntpoint << ": "
            << strerror(errno);

    ::android::base::unique_fd fd(open(file_path.c_str(), O_RDONLY | O_SYNC));
    ASSERT_NE(fd, -1);
    auto filebuf = std::unique_ptr<void, decltype(&free)>(calloc(1, getpagesize()), free);
    ASSERT_NE(filebuf, nullptr);
    for (off64_t off = 0; off < file_size / getpagesize(); off += getpagesize()) {
        memset(filebuf.get(), 0x00, getpagesize());
        ASSERT_EQ(pread64(fd, filebuf.get(), getpagesize(), off), getpagesize());
        ASSERT_EQ(memcmp(filebuf.get(), buffer.get(), getpagesize()), 0)
                << "Invalid pattern at offset: " << off << " size " << getpagesize();
    }
}

class VerifyBlockWritesF2fs : public ::testing::Test {
    // 2GB Filesystem and 4k block size by default
    static constexpr uint64_t block_size = 4096;
@@ -335,46 +263,6 @@ class VerifyBlockWritesF2fs : public ::testing::Test {
    std::string fs_path;
};

TEST_F(VerifyBlockWritesF2fs, CheckWrites) {
    EXPECT_EQ(access(fs_path.c_str(), F_OK), 0);

    std::string file_path = mntpoint + "/testfile";
    uint64_t file_size = 100 * 1024 * 1024;
    auto buffer = std::unique_ptr<void, decltype(&free)>(calloc(1, getpagesize()), free);
    ASSERT_NE(buffer, nullptr);
    memset(buffer.get(), 0xa0, getpagesize());
    {
        // scoped fiemap writer
        FiemapUniquePtr fptr = FiemapWriter::Open(file_path, file_size);
        ASSERT_NE(fptr, nullptr);
        uint8_t* p = static_cast<uint8_t*>(buffer.get());
        for (off64_t off = 0; off < file_size / getpagesize(); off += getpagesize()) {
            ASSERT_TRUE(fptr->Write(off, p, getpagesize()));
        }
        EXPECT_TRUE(fptr->Flush());
    }
    // unmount file system here to make sure we invalidated all page cache and
    // remount the filesystem again for verification
    ASSERT_EQ(umount(mntpoint.c_str()), 0);

    LoopDevice loop_dev(fs_path);
    ASSERT_TRUE(loop_dev.valid());
    ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint.c_str(), "f2fs", 0, nullptr), 0)
            << "failed to mount: " << loop_dev.device() << " on " << mntpoint << ": "
            << strerror(errno);

    ::android::base::unique_fd fd(open(file_path.c_str(), O_RDONLY | O_SYNC));
    ASSERT_NE(fd, -1);
    auto filebuf = std::unique_ptr<void, decltype(&free)>(calloc(1, getpagesize()), free);
    ASSERT_NE(filebuf, nullptr);
    for (off64_t off = 0; off < file_size / getpagesize(); off += getpagesize()) {
        memset(filebuf.get(), 0x00, getpagesize());
        ASSERT_EQ(pread64(fd, filebuf.get(), getpagesize(), off), getpagesize());
        ASSERT_EQ(memcmp(filebuf.get(), buffer.get(), getpagesize()), 0)
                << "Invalid pattern at offset: " << off << " size " << getpagesize();
    }
}

int main(int argc, char** argv) {
    ::testing::InitGoogleTest(&argc, argv);
    if (argc <= 1) {
+0 −13
Original line number Diff line number Diff line
@@ -57,15 +57,6 @@ class FiemapWriter final {
    // FiemapWriter::Open).
    static bool HasPinnedExtents(const std::string& file_path);

    // Syncs block device writes.
    bool Flush() const;

    // Writes the file by using its FIEMAP and performing i/o on the raw block device.
    // The return value is success / failure. This will happen in particular if the
    // kernel write returns errors, extents are not writeable or more importantly, if the 'size' is
    // not aligned to the block device's block size.
    bool Write(off64_t off, uint8_t* buffer, uint64_t size);

    // The counter part of Write(). It is an error for the offset to be unaligned with
    // the block device's block size.
    // In case of error, the contents of buffer MUST be discarded.
@@ -93,7 +84,6 @@ class FiemapWriter final {

    // File descriptors for the file and block device
    ::android::base::unique_fd file_fd_;
    ::android::base::unique_fd bdev_fd_;

    // Size in bytes of the file this class is writing
    uint64_t file_size_;
@@ -112,9 +102,6 @@ class FiemapWriter final {
    std::vector<struct fiemap_extent> extents_;

    FiemapWriter() = default;

    uint64_t WriteExtent(const struct fiemap_extent& ext, uint8_t* buffer, off64_t logical_off,
                         uint64_t length);
};

}  // namespace fiemap_writer