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
Bug: 298346574
Bug: 299142557
Test: fiemap_writer_test
Merged-In: I33502d49613be4f269a80e5c632514fc56a0246a
Ignore-AOSP-First: cherry-pick
Change-Id: Iedb7c32a594c3b1fca2904f3441029aaed7edf2a
This commit is contained in:
David Anderson 2023-06-05 15:00:30 -07:00
parent c742bfda6b
commit 3a60e82512
4 changed files with 87 additions and 110 deletions

View file

@ -93,6 +93,9 @@ cc_test {
test_options: {
min_shipping_api_level: 29,
},
header_libs: [
"libstorage_literals_headers",
],
require_root: true,
}

View file

@ -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)) {

View file

@ -2312,32 +2312,6 @@ TEST_F(SnapshotUpdateTest, Overflow) {
<< "FinishedSnapshotWrites should detect overflow of CoW device.";
}
TEST_F(SnapshotUpdateTest, LowSpace) {
static constexpr auto kMaxFree = 10_MiB;
auto userdata = std::make_unique<LowSpaceUserdata>();
ASSERT_TRUE(userdata->Init(kMaxFree));
// Grow all partitions to 10_MiB, total 30_MiB. This requires 30 MiB of CoW space. After
// using the empty space in super (< 1 MiB), it uses 30 MiB of /userdata space.
constexpr uint64_t partition_size = 10_MiB;
SetSize(sys_, partition_size);
SetSize(vnd_, partition_size);
SetSize(prd_, partition_size);
sys_->set_estimate_cow_size(partition_size);
vnd_->set_estimate_cow_size(partition_size);
prd_->set_estimate_cow_size(partition_size);
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());
ASSERT_GE(res.required_size(), 14_MiB);
ASSERT_LT(res.required_size(), 40_MiB);
}
TEST_F(SnapshotUpdateTest, AddPartition) {
group_->add_partition_names("dlkm");
@ -2699,50 +2673,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();
}
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) {
bool at_least_one_failure = false;
for (uint64_t size = 1_MiB; size <= 512_MiB; size *= 2) {
auto userdata = std::make_unique<LowSpaceUserdata>();
ASSERT_TRUE(userdata->Init(size));
uint64_t to_allocate = userdata->free_space() + userdata->bsize();
auto res = image_manager_->CreateBackingImage(kImageName, to_allocate,
IImageManager::CREATE_IMAGE_DEFAULT);
if (!res) {
at_least_one_failure = true;
} else {
ASSERT_EQ(res.error_code(), FiemapStatus::ErrorCode::NO_SPACE) << res.string();
}
CleanUp();
}
ASSERT_TRUE(at_least_one_failure)
<< "We should have failed to allocate at least one over-sized image";
}
bool Mkdir(const std::string& path) {
if (mkdir(path.c_str(), 0700) && errno != EEXIST) {
std::cerr << "Could not mkdir " << path << ": " << strerror(errno) << std::endl;

View file

@ -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);