blob: e51b9eee710be85f19df5fda7937ca26c194eb04 [file] [log] [blame] [edit]
// 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 <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <zircon/syscalls.h>
#include <algorithm>
#include <iostream>
#include <fbl/algorithm.h>
#include <fbl/unique_fd.h>
#include "fs_test_fixture.h"
namespace fs_test {
namespace {
using ParamType = std::tuple<TestFilesystemOptions, /*remount=*/bool>;
constexpr int kMb = 1 << 20;
constexpr int kPrintSize = 100 * kMb;
class MaxFileTest : public BaseFilesystemTest, public testing::WithParamInterface<ParamType> {
public:
MaxFileTest() : BaseFilesystemTest(std::get<0>(GetParam())) {}
bool ShouldRemount() const { return std::get<1>(GetParam()); }
};
// Test writing as much as we can to a file until we run out of space.
TEST_P(MaxFileTest, ReadAfterWriteMaxFileSucceeds) {
// TODO(fxbug.dev/31604): We avoid making files that consume more than half
// of physical memory. When we can page out files, this restriction
// should be removed.
const size_t physmem = zx_system_get_physmem();
const size_t max_cap = physmem / 2;
fbl::unique_fd fd(open(GetPath("bigfile").c_str(), O_CREAT | O_RDWR, 0644));
ASSERT_TRUE(fd);
char data_a[8192];
char data_b[8192];
char data_c[8192];
memset(data_a, 0xaa, sizeof(data_a));
memset(data_b, 0xbb, sizeof(data_b));
memset(data_c, 0xcc, sizeof(data_c));
size_t sz = 0;
ssize_t r;
auto rotate = [&](const char* data) {
if (data == data_a) {
return data_b;
} else if (data == data_b) {
return data_c;
} else {
return data_a;
}
};
const char* data = data_a;
for (;;) {
if (sz >= max_cap) {
std::cout << "Approaching physical memory capacity: " << sz << " bytes" << std::endl;
r = 0;
break;
}
const int offset = sz % sizeof(data_a);
const int len = sizeof(data_a) - offset;
if ((r = write(fd.get(), data + offset, len)) < 0) {
std::cout << "bigfile received error: " << strerror(errno) << std::endl;
if ((errno == EFBIG) || (errno == ENOSPC)) {
// Either the file should be too big (EFBIG) or the file should
// consume the whole volume (ENOSPC).
std::cout << "(This was an expected error)" << std::endl;
r = 0;
}
break;
}
if ((sz + r) % kPrintSize < (sz % kPrintSize)) {
std::cout << "wrote " << (sz + r) / kMb << " MB" << std::endl;
}
sz += r;
if (r == len) {
// Rotate which data buffer we use
data = rotate(data);
} else {
ASSERT_LT(r, len);
}
}
ASSERT_EQ(r, 0) << "Saw an unexpected error from write";
std::cout << "wrote " << sz << " bytes" << std::endl;
struct stat buf;
ASSERT_EQ(fstat(fd.get(), &buf), 0);
ASSERT_EQ(buf.st_size, static_cast<ssize_t>(sz));
// Try closing, re-opening, and verifying the file
ASSERT_EQ(close(fd.release()), 0);
if (ShouldRemount()) {
EXPECT_EQ(fs().Unmount().status_value(), ZX_OK);
EXPECT_EQ(fs().Mount().status_value(), ZX_OK);
}
fd.reset(open(GetPath("bigfile").c_str(), O_RDWR, 0644));
ASSERT_TRUE(fd);
ASSERT_EQ(fstat(fd.get(), &buf), 0);
ASSERT_EQ(buf.st_size, static_cast<ssize_t>(sz));
char readbuf[8192];
size_t bytes_read = 0;
data = data_a;
while (bytes_read < sz) {
r = read(fd.get(), readbuf, sizeof(readbuf));
ASSERT_EQ(r, static_cast<ssize_t>(std::min(sz - bytes_read, sizeof(readbuf))));
ASSERT_EQ(memcmp(readbuf, data, r), 0);
data = rotate(data);
bytes_read += r;
}
ASSERT_EQ(bytes_read, sz);
ASSERT_EQ(unlink(GetPath("bigfile").c_str()), 0);
ASSERT_EQ(close(fd.release()), 0);
}
using MaxFileSizeTest = MaxFileTest;
TEST_P(MaxFileSizeTest, WritingToMaxSupportedOffset) {
fbl::unique_fd fd(open(GetPath("foo").c_str(), O_CREAT | O_RDWR));
ASSERT_TRUE(fd);
ASSERT_EQ(pwrite(fd.get(), "h", 1, fs().GetTraits().max_file_size - 1), 1);
}
TEST_P(MaxFileTest, WritingBeyondMaxSupportedOffset) {
// Fat does not support sparse files. Maybe test is not worth the time spent writing a huge file.
if (fs().GetTraits().is_fat) {
return;
}
fbl::unique_fd fd(open(GetPath("foo").c_str(), O_CREAT | O_RDWR));
ASSERT_TRUE(fd);
ASSERT_EQ(pwrite(fd.get(), "h", 1, fs().GetTraits().max_file_size), -1);
}
TEST_P(MaxFileTest, TruncatingToMaxSupportedOffset) {
// Fat does not support sparse files. Maybe test is not worth the time spent writing a huge file.
if (fs().GetTraits().is_fat) {
return;
}
fbl::unique_fd fd(open(GetPath("foo").c_str(), O_CREAT | O_RDWR));
ASSERT_TRUE(fd);
ASSERT_EQ(ftruncate(fd.get(), fs().GetTraits().max_file_size), 0);
}
TEST_P(MaxFileTest, TruncatingBeyondMaxSupportedOffset) {
// Fat does not support sparse files. Maybe test is not worth the time spent writing a huge file.
if (fs().GetTraits().is_fat) {
return;
}
fbl::unique_fd fd(open(GetPath("foo").c_str(), O_CREAT | O_RDWR));
ASSERT_TRUE(fd);
ASSERT_EQ(ftruncate(fd.get(), fs().GetTraits().max_file_size + 1), -1);
}
// Test writing to two files, in alternation, until we run out of space. For trivial (sequential)
// block allocation policies, this will create two large files with non-contiguous block
// allocations.
TEST_P(MaxFileTest, ReadAfterNonContiguousWritesSuceeds) {
// TODO(fxbug.dev/31604): We avoid making files that consume more than half
// of physical memory. When we can page out files, this restriction
// should be removed.
const size_t physmem = zx_system_get_physmem();
const size_t max_cap = physmem / 4;
fbl::unique_fd fda(open(GetPath("bigfile-A").c_str(), O_CREAT | O_RDWR, 0644));
fbl::unique_fd fdb(open(GetPath("bigfile-B").c_str(), O_CREAT | O_RDWR, 0644));
ASSERT_TRUE(fda);
ASSERT_TRUE(fdb);
char data_a[8192];
char data_b[8192];
memset(data_a, 0xaa, sizeof(data_a));
memset(data_b, 0xbb, sizeof(data_b));
size_t sz_a = 0;
size_t sz_b = 0;
ssize_t r;
size_t* sz = &sz_a;
int fd = fda.get();
const char* data = data_a;
for (;;) {
if (*sz >= max_cap) {
std::cout << "Approaching physical memory capacity: " << *sz << " bytes" << std::endl;
r = 0;
break;
}
const int offset = *sz % sizeof(data_a);
const int len = sizeof(data_a) - offset;
if ((r = write(fd, data + offset, len)) <= 0) {
std::cout << "bigfile received error: " << strerror(errno);
// Either the file should be too big (EFBIG) or the file should
// consume the whole volume (ENOSPC).
ASSERT_TRUE(errno == EFBIG || errno == ENOSPC);
std::cout << "(This was an expected error)";
break;
}
if ((*sz + r) % kPrintSize < (*sz % kPrintSize)) {
std::cout << "wrote " << (*sz + r) / kMb << " MB";
}
*sz += r;
if (r == len) {
fd = (fd == fda.get()) ? fdb.get() : fda.get();
data = (data == data_a) ? data_b : data_a;
sz = (sz == &sz_a) ? &sz_b : &sz_a;
} else {
ASSERT_LT(r, len);
}
}
std::cout << "wrote " << sz_a << " bytes (to A)";
std::cout << "wrote " << sz_b << " bytes (to B)";
struct stat buf;
ASSERT_EQ(fstat(fda.get(), &buf), 0);
ASSERT_EQ(buf.st_size, static_cast<ssize_t>(sz_a));
ASSERT_EQ(fstat(fdb.get(), &buf), 0);
ASSERT_EQ(buf.st_size, static_cast<ssize_t>(sz_b));
// Try closing, re-opening, and verifying the file
ASSERT_EQ(close(fda.release()), 0);
ASSERT_EQ(close(fdb.release()), 0);
if (ShouldRemount()) {
EXPECT_EQ(fs().Unmount().status_value(), ZX_OK);
EXPECT_EQ(fs().Mount().status_value(), ZX_OK);
}
fda.reset(open(GetPath("bigfile-A").c_str(), O_RDWR, 0644));
fdb.reset(open(GetPath("bigfile-B").c_str(), O_RDWR, 0644));
ASSERT_TRUE(fda);
ASSERT_TRUE(fdb);
char readbuf[8192];
size_t bytes_read_a = 0;
size_t bytes_read_b = 0;
fd = fda.get();
data = data_a;
sz = &sz_a;
size_t* bytes_read = &bytes_read_a;
while (*bytes_read < *sz) {
r = read(fd, readbuf, sizeof(readbuf));
ASSERT_EQ(r, static_cast<ssize_t>(std::min(*sz - *bytes_read, sizeof(readbuf))));
ASSERT_EQ(memcmp(readbuf, data, r), 0);
*bytes_read += r;
fd = (fd == fda.get()) ? fdb.get() : fda.get();
data = (data == data_a) ? data_b : data_a;
sz = (sz == &sz_a) ? &sz_b : &sz_a;
bytes_read = (bytes_read == &bytes_read_a) ? &bytes_read_b : &bytes_read_a;
}
ASSERT_EQ(bytes_read_a, sz_a);
ASSERT_EQ(bytes_read_b, sz_b);
ASSERT_EQ(unlink(GetPath("bigfile-A").c_str()), 0);
ASSERT_EQ(unlink(GetPath("bigfile-B").c_str()), 0);
ASSERT_EQ(close(fda.release()), 0);
ASSERT_EQ(close(fdb.release()), 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> GetTestCombinations() {
std::vector<ParamType> test_combinations;
for (TestFilesystemOptions options : AllTestFilesystems()) {
// Fatfs is slow and there's no real benefit from having a larger ram-disk.
if (options.filesystem->GetTraits().name != "fatfs") {
// Use a larger ram-disk than the default so that the maximum transaction limit is exceeded
// for during delayed data allocation on non-FVM-backed Minfs partitions.
options.device_block_size = 512;
options.device_block_count = 1'048'576;
options.fvm_slice_size = 8'388'608;
}
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> GetCombinationWithSparseFileSupport() {
std::vector<ParamType> test_combinations;
for (TestFilesystemOptions options : AllTestFilesystems()) {
// For those filesystems that don't support sparse files, it is not worth the time spent
// writing a huge file.
if (!options.filesystem->GetTraits().supports_sparse_files) {
continue;
}
// Use a larger ram-disk than the default so that the maximum transaction limit is exceeded
// for during delayed data allocation on non-FVM-backed Minfs partitions.
options.device_block_size = 512;
options.device_block_count = 1'048'576;
options.fvm_slice_size = 8'388'608;
test_combinations.emplace_back(options, false);
if (options.filesystem->GetTraits().can_unmount) {
test_combinations.emplace_back(options, true);
}
}
return test_combinations;
}
INSTANTIATE_TEST_SUITE_P(/*no prefix*/, MaxFileTest, testing::ValuesIn(GetTestCombinations()),
GetParamDescription);
INSTANTIATE_TEST_SUITE_P(/*no prefix*/, MaxFileSizeTest,
testing::ValuesIn(GetCombinationWithSparseFileSupport()),
GetParamDescription);
} // namespace
} // namespace fs_test