blob: 3ca3be0bfe41ce1a8401448c52ef95ec3ba5678b [file] [log] [blame]
// 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 <assert.h>
#include <fcntl.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <zircon/compiler.h>
#include <zircon/syscalls.h>
#include <memory>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include <fbl/array.h>
#include <fbl/string_piece.h>
#include <fbl/unique_fd.h>
#include "filesystems.h"
#include "misc.h"
constexpr bool is_directory(const char* const path) {
return path[fbl::constexpr_strlen(path) - 1] == '/';
}
bool TestPersistSimple(void) {
BEGIN_TEST;
if (!test_info->can_be_mounted) {
fprintf(stderr, "Filesystem cannot be mounted; cannot test persistence\n");
return true;
}
const char* const paths[] = {
"::abc", "::def/", "::def/def_subdir/", "::def/def_subdir/def_subfile",
"::ghi", "::jkl", "::mnopqrstuvxyz"};
for (size_t i = 0; i < fbl::count_of(paths); i++) {
if (is_directory(paths[i])) {
ASSERT_EQ(mkdir(paths[i], 0644), 0);
} else {
fbl::unique_fd fd(open(paths[i], O_RDWR | O_CREAT | O_EXCL, 0644));
ASSERT_TRUE(fd);
}
}
ASSERT_TRUE(check_remount(), "Could not remount filesystem");
// The files should still exist when we remount
for (ssize_t i = fbl::count_of(paths) - 1; i >= 0; i--) {
if (is_directory(paths[i])) {
ASSERT_EQ(rmdir(paths[i]), 0);
} else {
ASSERT_EQ(unlink(paths[i]), 0);
}
}
ASSERT_TRUE(check_remount(), "Could not remount filesystem");
// But they should stay deleted!
for (ssize_t i = fbl::count_of(paths) - 1; i >= 0; i--) {
if (is_directory(paths[i])) {
ASSERT_EQ(rmdir(paths[i]), -1);
} else {
ASSERT_EQ(unlink(paths[i]), -1);
}
}
END_TEST;
}
bool TestPersistRapidRemount(void) {
BEGIN_TEST;
if (!test_info->can_be_mounted) {
fprintf(stderr, "Filesystem cannot be mounted; cannot test persistence\n");
return true;
}
for (size_t i = 0; i < 100; i++) {
ASSERT_TRUE(check_remount(), "Could not remount filesystem");
}
END_TEST;
}
template <size_t BufferSize>
bool TestPersistWithData(void) {
BEGIN_TEST;
if (!test_info->can_be_mounted) {
fprintf(stderr, "Filesystem cannot be mounted; cannot test persistence\n");
return true;
}
const char* const files[] = {
"::abc",
"::def",
"::and-another-file-filled-with-data",
};
std::unique_ptr<uint8_t[]> buffers[fbl::count_of(files)];
unsigned int seed = static_cast<unsigned int>(zx_ticks_get());
unittest_printf("Persistent data test using seed: %u\n", seed);
fbl::AllocChecker ac;
for (size_t i = 0; i < fbl::count_of(files); i++) {
buffers[i].reset(new (&ac) uint8_t[BufferSize]);
ASSERT_TRUE(ac.check());
for (size_t j = 0; j < BufferSize; j++) {
buffers[i][j] = (uint8_t)rand_r(&seed);
}
fbl::unique_fd fd(open(files[i], O_RDWR | O_CREAT, 0644));
ASSERT_TRUE(fd);
ASSERT_EQ(write(fd.get(), &buffers[i][0], BufferSize), BufferSize);
ASSERT_EQ(fsync(fd.get()), 0);
}
ASSERT_TRUE(check_remount(), "Could not remount filesystem");
// Read files
for (size_t i = 0; i < fbl::count_of(files); i++) {
std::unique_ptr<uint8_t[]> rbuf(new (&ac) uint8_t[BufferSize]);
ASSERT_TRUE(ac.check());
fbl::unique_fd fd(open(files[i], O_RDONLY, 0644));
ASSERT_TRUE(fd);
struct stat buf;
ASSERT_EQ(fstat(fd.get(), &buf), 0);
ASSERT_EQ(buf.st_nlink, 1);
ASSERT_EQ(buf.st_size, BufferSize);
ASSERT_EQ(read(fd.get(), &rbuf[0], BufferSize), BufferSize);
for (size_t j = 0; j < BufferSize; j++) {
ASSERT_EQ(rbuf[j], buffers[i][j]);
}
}
ASSERT_TRUE(check_remount(), "Could not remount filesystem");
// Delete all files
for (size_t i = 0; i < fbl::count_of(files); i++) {
ASSERT_EQ(unlink(files[i]), 0);
}
ASSERT_TRUE(check_remount(), "Could not remount filesystem");
// Files should stay deleted
DIR* dirp = opendir("::.");
ASSERT_NONNULL(dirp);
struct dirent* de;
de = readdir(dirp);
ASSERT_NONNULL(de);
ASSERT_EQ(strncmp(de->d_name, ".", 1), 0);
ASSERT_NULL(readdir(dirp));
ASSERT_EQ(closedir(dirp), 0);
END_TEST;
}
constexpr size_t kMaxLoopLength = 26;
template <bool MoveDirectory, size_t LoopLength, size_t Moves>
bool TestRenameLoop(void) {
BEGIN_TEST;
if (!test_info->can_be_mounted) {
fprintf(stderr, "Filesystem cannot be mounted; cannot test persistence\n");
return true;
}
static_assert(LoopLength <= kMaxLoopLength, "Loop length too long");
char src[128];
// Create "LoopLength" directories
for (size_t i = 0; i < LoopLength; i++) {
ASSERT_GT(sprintf(src, "::%c", static_cast<char>('a' + i)), 0);
ASSERT_EQ(mkdir(src, 0644), 0);
}
// Create a 'target'
if (MoveDirectory) {
ASSERT_EQ(mkdir("::a/target", 0644), 0);
} else {
fbl::unique_fd fd(open("::a/target", O_RDWR | O_CREAT));
ASSERT_TRUE(fd);
}
// Move the target through the loop a bunch of times
size_t moves = Moves;
strcpy(src, "::a/target");
size_t char_index = 0;
while (moves--) {
char dst[128];
strcpy(dst, src);
char_index = (char_index + 1) % LoopLength;
dst[2] = static_cast<char>('a' + char_index);
ASSERT_EQ(rename(src, dst), 0);
strcpy(src, dst);
}
ASSERT_TRUE(check_remount(), "Could not remount filesystem");
// Check that the target only exists in ONE directory
bool target_found = false;
for (size_t i = 0; i < LoopLength; i++) {
ASSERT_GT(sprintf(src, "::%c", static_cast<char>('a' + i)), 0);
DIR* dirp = opendir(src);
ASSERT_NONNULL(dirp);
struct dirent* de;
de = readdir(dirp);
ASSERT_NONNULL(de);
ASSERT_EQ(strcmp(de->d_name, "."), 0);
de = readdir(dirp);
if (de != nullptr) {
ASSERT_FALSE(target_found, "Target found twice!");
ASSERT_EQ(strcmp(de->d_name, "target"), 0, "Non-target found");
target_found = true;
}
ASSERT_EQ(closedir(dirp), 0);
}
ASSERT_TRUE(target_found);
ASSERT_TRUE(check_remount(), "Could not remount filesystem");
// Clean up
target_found = false;
for (size_t i = 0; i < LoopLength; i++) {
ASSERT_GT(sprintf(src, "::%c", static_cast<char>('a' + i)), 0);
int ret = unlink(src);
if (ret != 0) {
ASSERT_FALSE(target_found);
ASSERT_GT(sprintf(src, "::%c/target", static_cast<char>('a' + i)), 0);
ASSERT_EQ(unlink(src), 0);
ASSERT_GT(sprintf(src, "::%c", static_cast<char>('a' + i)), 0);
ASSERT_EQ(unlink(src), 0);
target_found = true;
}
}
ASSERT_TRUE(target_found, "Target was never unlinked");
END_TEST;
}
RUN_FOR_ALL_FILESYSTEMS(persistence_tests,
RUN_TEST_MEDIUM(TestPersistSimple) RUN_TEST_LARGE(TestPersistRapidRemount)
RUN_TEST_MEDIUM((TestPersistWithData<1>))
RUN_TEST_MEDIUM((TestPersistWithData<100>))
RUN_TEST_LARGE((TestPersistWithData<8192 - 1>))
RUN_TEST_LARGE((TestPersistWithData<8192>))
RUN_TEST_LARGE((TestPersistWithData<8192 + 1>))
RUN_TEST_LARGE((TestPersistWithData<8192 * 128>))
RUN_TEST_MEDIUM((TestRenameLoop<false, 2, 2>));
RUN_TEST_LARGE((TestRenameLoop<false, 2, 100>));
RUN_TEST_LARGE((TestRenameLoop<false, 15, 100>));
RUN_TEST_LARGE((TestRenameLoop<false, 25, 500>));
RUN_TEST_MEDIUM((TestRenameLoop<true, 2, 2>));
RUN_TEST_LARGE((TestRenameLoop<true, 2, 100>));
RUN_TEST_LARGE((TestRenameLoop<true, 15, 100>));
RUN_TEST_LARGE((TestRenameLoop<true, 25, 500>));)