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

Commit 6cef6905 authored by David Anderson's avatar David Anderson
Browse files

Move ENOSPC tests to libfiemap.

These tests are still giving us trouble. Move them to libfiemap, which
(1) is closer to the source of implementation, and (2) allows us to
re-use the temporary filesystem code. This won't perturb the state of
the actual device.

The new test creates a 64MB ext4 or f2fs mount point as a sandbox, which
should be much safer.

Bug: 285197715
Test: fiemap_writer_test
Change-Id: I33502d49613be4f269a80e5c632514fc56a0246a
parent bce8618a
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -93,6 +93,9 @@ cc_test {
    test_options: {
        min_shipping_api_level: 29,
    },
    header_libs: [
        "libstorage_literals_headers",
    ],
    require_root: true,
}

+78 −40
Original line number Diff line number Diff line
@@ -22,21 +22,25 @@
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/types.h>
#include <sys/vfs.h>
#include <unistd.h>

#include <string>
#include <utility>

#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
#include <fstab/fstab.h>
#include <gtest/gtest.h>
#include <libdm/loop_control.h>
#include <libfiemap/fiemap_writer.h>
#include <libfiemap/split_fiemap_writer.h>
#include <libgsi/libgsi.h>
#include <storage_literals/storage_literals.h>

#include "utility.h"

@@ -46,6 +50,7 @@ namespace fiemap {
using namespace std;
using namespace std::string_literals;
using namespace android::fiemap;
using namespace android::storage_literals;
using unique_fd = android::base::unique_fd;
using LoopDevice = android::dm::LoopDevice;

@@ -427,90 +432,123 @@ TEST_F(SplitFiemapTest, WritePastEnd) {
    ASSERT_FALSE(ptr->Write(buffer.get(), kSize));
}

class VerifyBlockWritesExt4 : public ::testing::Test {
// Get max file size and free space.
std::pair<uint64_t, uint64_t> GetBigFileLimit(const std::string& mount_point) {
    struct statvfs fs;
    if (statvfs(mount_point.c_str(), &fs) < 0) {
        PLOG(ERROR) << "statfs failed";
        return {0, 0};
    }

    auto fs_limit = static_cast<uint64_t>(fs.f_blocks) * (fs.f_bsize - 1);
    auto fs_free = static_cast<uint64_t>(fs.f_bfree) * fs.f_bsize;

    LOG(INFO) << "Big file limit: " << fs_limit << ", free space: " << fs_free;

    return {fs_limit, fs_free};
}

class FsTest : public ::testing::Test {
  protected:
    // 2GB Filesystem and 4k block size by default
    static constexpr uint64_t block_size = 4096;
    static constexpr uint64_t fs_size = 2147483648;
    static constexpr uint64_t fs_size = 64 * 1024 * 1024;

  protected:
    void SetUp() override {
        fs_path = std::string(getenv("TMPDIR")) + "/ext4_2G.img";
    void SetUp() {
        android::fs_mgr::Fstab fstab;
        ASSERT_TRUE(android::fs_mgr::ReadFstabFromFile("/proc/mounts", &fstab));

        ASSERT_EQ(access(tmpdir_.path, F_OK), 0);
        fs_path_ = tmpdir_.path + "/fs_image"s;
        mntpoint_ = tmpdir_.path + "/mnt_point"s;

        auto entry = android::fs_mgr::GetEntryForMountPoint(&fstab, "/data");
        ASSERT_NE(entry, nullptr);
        if (entry->fs_type == "ext4") {
            SetUpExt4();
        } else if (entry->fs_type == "f2fs") {
            SetUpF2fs();
        } else {
            FAIL() << "Unrecognized fs_type: " << entry->fs_type;
        }
    }

    void SetUpExt4() {
        uint64_t count = fs_size / block_size;
        std::string dd_cmd =
                ::android::base::StringPrintf("/system/bin/dd if=/dev/zero of=%s bs=%" PRIu64
                                              " count=%" PRIu64 " > /dev/null 2>&1",
                                              fs_path.c_str(), block_size, count);
                                              fs_path_.c_str(), block_size, count);
        std::string mkfs_cmd =
                ::android::base::StringPrintf("/system/bin/mkfs.ext4 -q %s", fs_path.c_str());
                ::android::base::StringPrintf("/system/bin/mkfs.ext4 -q %s", fs_path_.c_str());
        // create mount point
        mntpoint = std::string(getenv("TMPDIR")) + "/fiemap_mnt";
        ASSERT_EQ(mkdir(mntpoint.c_str(), S_IRWXU), 0);
        ASSERT_EQ(mkdir(mntpoint_.c_str(), S_IRWXU), 0);
        // create file for the file system
        int ret = system(dd_cmd.c_str());
        ASSERT_EQ(ret, 0);
        // Get and attach a loop device to the filesystem we created
        LoopDevice loop_dev(fs_path, 10s);
        LoopDevice loop_dev(fs_path_, 10s);
        ASSERT_TRUE(loop_dev.valid());
        // create file system
        ret = system(mkfs_cmd.c_str());
        ASSERT_EQ(ret, 0);

        // mount the file system
        ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint.c_str(), "ext4", 0, nullptr), 0);
        ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint_.c_str(), "ext4", 0, nullptr), 0);
    }

    void TearDown() override {
        umount(mntpoint.c_str());
        rmdir(mntpoint.c_str());
        unlink(fs_path.c_str());
    }

    std::string mntpoint;
    std::string fs_path;
};

class VerifyBlockWritesF2fs : public ::testing::Test {
    // 2GB Filesystem and 4k block size by default
    static constexpr uint64_t block_size = 4096;
    static constexpr uint64_t fs_size = 2147483648;

  protected:
    void SetUp() override {
        fs_path = std::string(getenv("TMPDIR")) + "/f2fs_2G.img";
    void SetUpF2fs() {
        uint64_t count = fs_size / block_size;
        std::string dd_cmd =
                ::android::base::StringPrintf("/system/bin/dd if=/dev/zero of=%s bs=%" PRIu64
                                              " count=%" PRIu64 " > /dev/null 2>&1",
                                              fs_path.c_str(), block_size, count);
                                              fs_path_.c_str(), block_size, count);
        std::string mkfs_cmd =
                ::android::base::StringPrintf("/system/bin/make_f2fs -q %s", fs_path.c_str());
                ::android::base::StringPrintf("/system/bin/make_f2fs -q %s", fs_path_.c_str());
        // create mount point
        mntpoint = std::string(getenv("TMPDIR")) + "/fiemap_mnt";
        ASSERT_EQ(mkdir(mntpoint.c_str(), S_IRWXU), 0);
        ASSERT_EQ(mkdir(mntpoint_.c_str(), S_IRWXU), 0);
        // create file for the file system
        int ret = system(dd_cmd.c_str());
        ASSERT_EQ(ret, 0);
        // Get and attach a loop device to the filesystem we created
        LoopDevice loop_dev(fs_path, 10s);
        LoopDevice loop_dev(fs_path_, 10s);
        ASSERT_TRUE(loop_dev.valid());
        // create file system
        ret = system(mkfs_cmd.c_str());
        ASSERT_EQ(ret, 0);

        // mount the file system
        ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint.c_str(), "f2fs", 0, nullptr), 0);
        ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint_.c_str(), "f2fs", 0, nullptr), 0);
    }

    void TearDown() override {
        umount(mntpoint.c_str());
        rmdir(mntpoint.c_str());
        unlink(fs_path.c_str());
        umount(mntpoint_.c_str());
        rmdir(mntpoint_.c_str());
        unlink(fs_path_.c_str());
    }

    std::string mntpoint;
    std::string fs_path;
    TemporaryDir tmpdir_;
    std::string mntpoint_;
    std::string fs_path_;
};

TEST_F(FsTest, LowSpaceError) {
    auto limits = GetBigFileLimit(mntpoint_);
    ASSERT_GE(limits.first, 0);

    FiemapUniquePtr ptr;

    auto test_file = mntpoint_ + "/big_file";
    auto status = FiemapWriter::Open(test_file, limits.first, &ptr);
    ASSERT_FALSE(status.is_ok());
    ASSERT_EQ(status.error_code(), FiemapStatus::ErrorCode::NO_SPACE);

    // Also test for EFBIG.
    status = FiemapWriter::Open(test_file, 16_TiB, &ptr);
    ASSERT_FALSE(status.is_ok());
    ASSERT_NE(status.error_code(), FiemapStatus::ErrorCode::NO_SPACE);
}

bool DetermineBlockSize() {
    struct statfs s;
    if (statfs(gTestDir.c_str(), &s)) {
+0 −86
Original line number Diff line number Diff line
@@ -2372,60 +2372,6 @@ TEST_F(SnapshotUpdateTest, Overflow) {
            << "FinishedSnapshotWrites should detect overflow of CoW device.";
}

// Get max file size and free space.
std::pair<uint64_t, uint64_t> GetBigFileLimit() {
    struct statvfs fs;
    if (statvfs("/data", &fs) < 0) {
        PLOG(ERROR) << "statfs failed";
        return {0, 0};
    }

    auto fs_limit = static_cast<uint64_t>(fs.f_blocks) * (fs.f_bsize - 1);
    auto fs_free = static_cast<uint64_t>(fs.f_bfree) * fs.f_bsize;

    LOG(INFO) << "Big file limit: " << fs_limit << ", free space: " << fs_free;

    return {fs_limit, fs_free};
}

TEST_F(SnapshotUpdateTest, LowSpace) {
    // To make the low space test more reliable, we force a large cow estimate.
    // However legacy VAB ignores the COW estimate and uses InstallOperations
    // to compute the exact size required for dm-snapshot. It's difficult to
    // make this work reliably (we'd need to somehow fake an extremely large
    // super partition, and we don't have that level of dependency injection).
    //
    // For now, just skip this test on legacy VAB.
    if (!snapuserd_required_) {
        GTEST_SKIP() << "Skipping test on legacy VAB";
    }

    auto fs = GetBigFileLimit();
    ASSERT_NE(fs.first, 0);

    constexpr uint64_t partition_size = 10_MiB;
    SetSize(sys_, partition_size);
    SetSize(vnd_, partition_size);
    SetSize(prd_, partition_size);
    sys_->set_estimate_cow_size(fs.first);
    vnd_->set_estimate_cow_size(fs.first);
    prd_->set_estimate_cow_size(fs.first);

    AddOperationForPartitions();

    // Execute the update.
    ASSERT_TRUE(sm->BeginUpdate());
    auto res = sm->CreateUpdateSnapshots(manifest_);
    ASSERT_FALSE(res);
    ASSERT_EQ(Return::ErrorCode::NO_SPACE, res.error_code());

    // It's hard to predict exactly how much free space is needed, since /data
    // is writable and the test is not the only process running. Divide by two
    // as a rough lower bound, and adjust this in the future as necessary.
    auto expected_delta = fs.first - fs.second;
    ASSERT_GE(res.required_size(), expected_delta / 2);
}

TEST_F(SnapshotUpdateTest, AddPartition) {
    group_->add_partition_names("dlkm");

@@ -2796,38 +2742,6 @@ INSTANTIATE_TEST_SUITE_P(Snapshot, FlashAfterUpdateTest, Combine(Values(0, 1), B
                                    "Merge"s;
                         });

class ImageManagerTest : public SnapshotTest {
  protected:
    void SetUp() override {
        SKIP_IF_NON_VIRTUAL_AB();
        SnapshotTest::SetUp();
    }
    void TearDown() override {
        RETURN_IF_NON_VIRTUAL_AB();
        CleanUp();
        SnapshotTest::TearDown();
    }
    void CleanUp() {
        if (!image_manager_) {
            return;
        }
        EXPECT_TRUE(!image_manager_->BackingImageExists(kImageName) ||
                    image_manager_->DeleteBackingImage(kImageName));
    }

    static constexpr const char* kImageName = "my_image";
};

TEST_F(ImageManagerTest, CreateImageNoSpace) {
    auto fs = GetBigFileLimit();
    ASSERT_NE(fs.first, 0);

    auto res = image_manager_->CreateBackingImage(kImageName, fs.first,
                                                  IImageManager::CREATE_IMAGE_DEFAULT);
    ASSERT_FALSE(res);
    ASSERT_EQ(res.error_code(), FiemapStatus::ErrorCode::NO_SPACE) << res.string();
}

bool Mkdir(const std::string& path) {
    if (mkdir(path.c_str(), 0700) && errno != EEXIST) {
        std::cerr << "Could not mkdir " << path << ": " << strerror(errno) << std::endl;
+6 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ using B = Size<0>;
using KiB = Size<10>;
using MiB = Size<20>;
using GiB = Size<30>;
using TiB = Size<40>;

constexpr B operator""_B(unsigned long long v) {  // NOLINT
    return B{v};
@@ -54,6 +55,10 @@ constexpr GiB operator""_GiB(unsigned long long v) { // NOLINT
    return GiB{v};
}

constexpr TiB operator""_TiB(unsigned long long v) {  // NOLINT
    return TiB{v};
}

template <typename Dest, typename Src>
constexpr Dest size_cast(Src src) {
    if (Src::power < Dest::power) {
@@ -69,6 +74,7 @@ static_assert(1_B == 1);
static_assert(1_KiB == 1 << 10);
static_assert(1_MiB == 1 << 20);
static_assert(1_GiB == 1 << 30);
static_assert(1_TiB == 1ULL << 40);
static_assert(size_cast<KiB>(1_B).count() == 0);
static_assert(size_cast<KiB>(1024_B).count() == 1);
static_assert(size_cast<KiB>(1_MiB).count() == 1024);