blob: 69999260f4b8b99f8d8278be84aa9c8d56c985fc [file] [log] [blame]
// 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 <time.h>
#include <iostream>
#include <vector>
#include "src/storage/host_fs_test/fixture.h"
namespace fs_test {
namespace {
void CheckFileContains(const char* filename, const void* data, ssize_t len) {
char buf[4096];
struct stat st;
ASSERT_EQ(emu_stat(filename, &st), 0);
ASSERT_EQ(st.st_size, len);
int fd = emu_open(filename, O_RDWR, 0644);
ASSERT_GT(fd, 0);
ASSERT_TRUE(CheckStreamAll(emu_read, fd, buf, len));
ASSERT_EQ(memcmp(buf, data, len), 0);
ASSERT_EQ(emu_close(fd), 0);
}
void CheckFileEmpty(const char* filename) {
struct stat st;
ASSERT_EQ(emu_stat(filename, &st), 0);
ASSERT_EQ(st.st_size, 0);
}
// Test that the really simple cases of truncate are operational
TEST_F(HostFilesystemTest, TruncateSmall) {
const char* str = "Hello, World!\n";
const char* filename = "::alpha";
// Try writing a string to a file
int fd = emu_open(filename, O_RDWR | O_CREAT, 0644);
ASSERT_GT(fd, 0);
ASSERT_TRUE(CheckStreamAll(emu_write, fd, str, strlen(str)));
ASSERT_NO_FATAL_FAILURE(CheckFileContains(filename, str, strlen(str)));
// Check that opening a file with O_TRUNC makes it empty
int fd2 = emu_open(filename, O_RDWR | O_TRUNC, 0644);
ASSERT_GT(fd2, 0);
ASSERT_NO_FATAL_FAILURE(CheckFileEmpty(filename));
// Check that we can still write to a file that has been truncated
ASSERT_EQ(emu_lseek(fd, 0, SEEK_SET), 0);
ASSERT_TRUE(CheckStreamAll(emu_write, fd, str, strlen(str)));
ASSERT_NO_FATAL_FAILURE(CheckFileContains(filename, str, strlen(str)));
// Check that we can truncate the file using the "truncate" function
ASSERT_EQ(emu_ftruncate(fd, 5), 0);
ASSERT_NO_FATAL_FAILURE(CheckFileContains(filename, str, 5));
ASSERT_EQ(emu_ftruncate(fd, 0), 0);
ASSERT_NO_FATAL_FAILURE(CheckFileEmpty(filename));
// Check that truncating an already empty file does not cause problems
ASSERT_EQ(emu_ftruncate(fd, 0), 0);
ASSERT_NO_FATAL_FAILURE(CheckFileEmpty(filename));
// Check that we can use truncate to extend a file
char empty[5] = {0, 0, 0, 0, 0};
ASSERT_EQ(emu_ftruncate(fd, 5), 0);
ASSERT_NO_FATAL_FAILURE(CheckFileContains(filename, empty, 5));
ASSERT_EQ(emu_close(fd), 0);
ASSERT_EQ(emu_close(fd2), 0);
}
void CheckedTruncate(const char* filename, uint8_t* u8, ssize_t new_len) {
// Acquire the old size
struct stat st;
ASSERT_EQ(emu_stat(filename, &st), 0);
ssize_t old_len = st.st_size;
// Truncate the file, verify the size gets updated
int fd = emu_open(filename, O_RDWR, 0644);
ASSERT_GT(fd, 0);
ASSERT_EQ(emu_ftruncate(fd, new_len), 0);
ASSERT_EQ(emu_stat(filename, &st), 0);
ASSERT_EQ(st.st_size, new_len);
// close and reopen the file; verify the inode stays updated
ASSERT_EQ(emu_close(fd), 0);
fd = emu_open(filename, O_RDWR, 0644);
ASSERT_GT(fd, 0);
ASSERT_EQ(emu_stat(filename, &st), 0);
ASSERT_EQ(st.st_size, new_len);
std::vector<uint8_t> readbuf(new_len);
if (new_len > old_len) { // Expanded the file
// Verify that the file is unchanged up to old_len
ASSERT_EQ(emu_lseek(fd, 0, SEEK_SET), 0);
ASSERT_TRUE(CheckStreamAll(emu_read, fd, readbuf.data(), old_len));
if (old_len > 0) {
// It's undefined behavior to call memcmp on a nullptr, so only do this
// check if old_len is nonzero and thus readbuf.data() is expected to
// contain a real pointer.
ASSERT_EQ(memcmp(readbuf.data(), u8, old_len), 0);
}
// Verify that the file is filled with zeroes from old_len to new_len
ASSERT_EQ(emu_lseek(fd, old_len, SEEK_SET), old_len);
ASSERT_TRUE(CheckStreamAll(emu_read, fd, readbuf.data(), new_len - old_len));
for (ssize_t n = 0; n < (new_len - old_len); ++n) {
ASSERT_EQ(readbuf[n], 0);
}
// Overwrite those zeroes with the contents of u8
ASSERT_EQ(emu_lseek(fd, old_len, SEEK_SET), old_len);
ASSERT_TRUE(CheckStreamAll(emu_write, fd, u8 + old_len, new_len - old_len));
} else { // Shrunk the file (or kept it the same length)
// Verify that the file is unchanged up to new_len
ASSERT_EQ(emu_lseek(fd, 0, SEEK_SET), 0);
ASSERT_TRUE(CheckStreamAll(emu_read, fd, readbuf.data(), new_len));
if (new_len > 0) {
// It's undefined behavior to call memcmp on a nullptr, so only do this
// check if old_len is nonzero and thus readbuf.data() is expected to
// contain a real pointer.
ASSERT_EQ(memcmp(readbuf.data(), u8, new_len), 0);
}
}
ASSERT_EQ(emu_close(fd), 0);
}
struct TestParam {
size_t buf_size;
size_t iterations;
};
class TruncateLargeHostFilesystemTest : public HostFilesystemTest,
public testing::WithParamInterface<TestParam> {};
// Test that truncate doesn't have issues dealing with larger files
// Repeatedly write to / truncate a file.
TEST_P(TruncateLargeHostFilesystemTest, Large) {
// Fill a test buffer with data
std::vector<uint8_t> buf(GetParam().buf_size);
unsigned seed = static_cast<unsigned>(time(nullptr));
std::cerr << "Truncate test using seed: " << seed << std::endl;
srand(seed);
for (unsigned n = 0; n < GetParam().buf_size; ++n) {
buf[n] = static_cast<uint8_t>(rand_r(&seed));
}
// Start a file filled with the u8 buffer
const char* filename = "::alpha";
int fd = emu_open(filename, O_RDWR | O_CREAT, 0644);
ASSERT_GT(fd, 0);
ASSERT_TRUE(CheckStreamAll(emu_write, fd, buf.data(), GetParam().buf_size));
ASSERT_EQ(emu_close(fd), 0);
// Repeatedly truncate / write to the file
for (size_t i = 0; i < GetParam().iterations; ++i) {
size_t len = rand_r(&seed) % GetParam().buf_size;
ASSERT_NO_FATAL_FAILURE(CheckedTruncate(filename, buf.data(), len));
ASSERT_EQ(RunFsck(), 0);
}
}
std::string GetParamDescription(const testing::TestParamInfo<TestParam>& param) {
std::stringstream s;
s << "BufSize" << param.param.buf_size << "Iterations" << param.param.iterations;
return s.str();
}
INSTANTIATE_TEST_SUITE_P(/*no prefix*/, TruncateLargeHostFilesystemTest,
testing::Values(TestParam{1 << 10, 100}, TestParam{1 << 15, 100},
TestParam{1 << 20, 100}, TestParam{1 << 25, 10}),
GetParamDescription);
} // namespace
} // namespace fs_test