blob: f8fc805340ae4441970763c762f2df7f24ded732 [file] [log] [blame] [edit]
// Copyright 2017 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 <errno.h>
#include <fcntl.h>
#include <fuchsia/io/llcpp/fidl.h>
#include <lib/fdio/cpp/caller.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <zircon/errors.h>
#include <iostream>
#include <string>
#include <tuple>
#include <vector>
#include <fbl/unique_fd.h>
#include "src/storage/fs_test/fs_test_fixture.h"
#include "src/storage/fvm/format.h"
#include "src/storage/minfs/format.h"
namespace fs_test {
namespace {
namespace fio = ::llcpp::fuchsia::io;
using ParamType = std::tuple<TestFilesystemOptions, /*remount=*/bool>;
class ResizeTest : public BaseFilesystemTest, public testing::WithParamInterface<ParamType> {
public:
ResizeTest() : BaseFilesystemTest(std::get<0>(GetParam())) {}
bool ShouldRemount() const { return std::get<1>(GetParam()); }
protected:
void QueryInfo(uint64_t* out_free_pool_size) {
fbl::unique_fd fd(open(fs().mount_path().c_str(), O_RDONLY | O_DIRECTORY));
ASSERT_TRUE(fd);
fdio_cpp::FdioCaller caller(std::move(fd));
auto query_result = fio::DirectoryAdmin::Call::QueryFilesystem(caller.channel());
ASSERT_EQ(query_result.status(), ZX_OK);
ASSERT_NE(query_result.Unwrap()->info, nullptr);
fio::FilesystemInfo* info = query_result.Unwrap()->info.get();
// This should always be true, for all filesystems.
ASSERT_GT(info->total_bytes, info->used_bytes);
*out_free_pool_size = info->free_shared_pool_bytes;
}
void EnsureCanGrow() {
uint64_t free_pool_size;
ASSERT_NO_FATAL_FAILURE(QueryInfo(&free_pool_size));
// This tests expects to run with free FVM space.
ASSERT_GT(free_pool_size, 0ul);
}
void EnsureCannotGrow() {
uint64_t free_pool_size;
ASSERT_NO_FATAL_FAILURE(QueryInfo(&free_pool_size));
ASSERT_EQ(free_pool_size, 0ul);
}
};
using MaxInodeTest = ResizeTest;
TEST_P(MaxInodeTest, UseAllInodes) {
ASSERT_NO_FATAL_FAILURE(EnsureCanGrow());
// Create 100,000 inodes.
// We expect that this will force enough inodes to cause the
// filesystem structures to resize partway through.
constexpr size_t kFilesPerDirectory = 100;
size_t d = 0;
while (true) {
if (d % 100 == 0) {
std::cout << "Creating directory (containing 100 files): " << d << std::endl;
}
const std::string dname = GetPath(std::to_string(d));
if (mkdir(dname.c_str(), 0666) < 0) {
ASSERT_EQ(errno, ENOSPC);
break;
}
bool stop = false;
for (size_t f = 0; f < kFilesPerDirectory; f++) {
const std::string fname = dname + "/" + std::to_string(f);
fbl::unique_fd fd(open(fname.c_str(), O_CREAT | O_RDWR | O_EXCL));
if (!fd) {
ASSERT_EQ(errno, ENOSPC);
stop = true;
break;
}
}
if (stop) {
break;
}
d++;
}
ASSERT_NO_FATAL_FAILURE(EnsureCannotGrow());
if (ShouldRemount()) {
std::cout << "Unmounting, Verifying, Re-mounting..." << std::endl;
EXPECT_EQ(fs().Unmount().status_value(), ZX_OK);
EXPECT_EQ(fs().Fsck().status_value(), ZX_OK);
EXPECT_EQ(fs().Mount().status_value(), ZX_OK);
}
size_t directory_count = d;
for (size_t d = 0; d < directory_count; d++) {
if (d % 100 == 0) {
std::cout << "Deleting directory (containing 100 files): " << d << std::endl;
}
const std::string dname = GetPath(std::to_string(d));
for (size_t f = 0; f < kFilesPerDirectory; f++) {
const std::string fname = dname + "/" + std::to_string(f);
ASSERT_EQ(unlink(fname.c_str()), 0);
}
ASSERT_EQ(rmdir(dname.c_str()), 0);
}
}
using MaxDataTest = ResizeTest;
TEST_P(MaxDataTest, UseAllData) {
constexpr size_t kBufSize = 1 << 20;
constexpr size_t kFileSize = 20 * kBufSize;
ASSERT_NO_FATAL_FAILURE(EnsureCanGrow());
uint64_t disk_size = fs().options().device_block_count * fs().options().device_block_size;
// Counts both copies of the metadata.
// TODO(jfsulliv) change to use MetadataBuffer::BytesNeeded() when that's landed.
size_t metadata_size =
fvm::Header::FromDiskSize(fvm::kMaxUsablePartitions, disk_size, fs().options().fvm_slice_size)
.GetDataStartOffset();
ASSERT_GT(disk_size, metadata_size);
disk_size -= metadata_size;
ASSERT_GT(disk_size, minfs::kMinfsMinimumSlices * fs().options().fvm_slice_size);
disk_size -= minfs::kMinfsMinimumSlices * fs().options().fvm_slice_size;
std::vector<uint8_t> buf(kBufSize);
size_t f = 0;
while (true) {
std::cout << "Creating 20 MB file " << f << std::endl;
const std::string fname = GetPath(std::to_string(f));
fbl::unique_fd fd(open(fname.c_str(), O_CREAT | O_RDWR | O_EXCL));
if (!fd) {
ASSERT_EQ(errno, ENOSPC);
break;
}
f++;
bool stop = false;
ASSERT_EQ(ftruncate(fd.get(), kFileSize), 0);
for (size_t done = 0; done < kFileSize;) {
ssize_t r = write(fd.get(), buf.data(), std::min(kBufSize, kFileSize - done));
if (r < 0) {
ASSERT_EQ(errno, ENOSPC);
stop = true;
break;
}
done += r;
}
if (stop) {
break;
}
}
ASSERT_NO_FATAL_FAILURE(EnsureCannotGrow());
if (ShouldRemount()) {
std::cout << "Unmounting, Verifying, Re-mounting..." << std::endl;
EXPECT_EQ(fs().Unmount().status_value(), ZX_OK);
EXPECT_EQ(fs().Fsck().status_value(), ZX_OK);
EXPECT_EQ(fs().Mount().status_value(), ZX_OK);
}
size_t file_count = f;
for (size_t f = 0; f < file_count; f++) {
const std::string fname = GetPath(std::to_string(f));
ASSERT_EQ(unlink(fname.c_str()), 0);
}
}
std::string GetParamDescription(const testing::TestParamInfo<ParamType>& param) {
std::stringstream s;
s << std::get<0>(param.param) << (std::get<1>(param.param) ? "WithRemount" : "WithoutRemount");
return s.str();
}
std::vector<ParamType> GetTestCombinationsForMaxInodeTest() {
std::vector<ParamType> test_combinations;
for (TestFilesystemOptions options : AllTestFilesystems()) {
if (options.use_fvm && options.filesystem->GetTraits().supports_resize) {
options.device_block_count = 1LLU << 15;
options.device_block_size = 1LLU << 9;
options.fvm_slice_size = 1LLU << 20;
test_combinations.push_back(ParamType{options, false});
if (options.filesystem->GetTraits().can_unmount) {
test_combinations.push_back(ParamType{options, true});
}
}
}
return test_combinations;
}
std::vector<ParamType> GetTestCombinationsForMaxDataTest() {
std::vector<ParamType> test_combinations;
for (TestFilesystemOptions options : AllTestFilesystems()) {
if (options.use_fvm && options.filesystem->GetTraits().supports_resize) {
options.device_block_count = 1LLU << 17;
options.device_block_size = 1LLU << 9;
options.fvm_slice_size = 1LLU << 20;
test_combinations.push_back(ParamType{options, false});
if (options.filesystem->GetTraits().can_unmount) {
test_combinations.push_back(ParamType{options, true});
}
}
}
return test_combinations;
}
INSTANTIATE_TEST_SUITE_P(/*no prefix*/, MaxInodeTest,
testing::ValuesIn(GetTestCombinationsForMaxInodeTest()),
GetParamDescription);
INSTANTIATE_TEST_SUITE_P(/*no prefix*/, MaxDataTest,
testing::ValuesIn(GetTestCombinationsForMaxDataTest()),
GetParamDescription);
} // namespace
} // namespace fs_test