blob: cd52519d065351b94371ac858c1e59a4147a3b37 [file] [log] [blame]
// Copyright 2019 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 <dirent.h>
#include <fcntl.h>
#include <fuchsia/io/llcpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fdio/fd.h>
#include <sys/stat.h>
#include <sys/statfs.h>
#include <sys/statvfs.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <utility>
#include <block-client/cpp/block-device.h>
#include <block-client/cpp/remote-block-device.h>
#include <fbl/auto_call.h>
#include <fbl/string.h>
#include <fbl/string_buffer.h>
#include <fbl/unique_fd.h>
#include <fs-management/mount.h>
#include <fs-test-utils/fixture.h>
#include <fs/test_support/environment.h>
#include <ramdevice-client/ramdisk.h>
#include <zxtest/zxtest.h>
#include "src/storage/minfs/format.h"
#include "src/storage/minfs/fsck.h"
#include "src/storage/minfs/minfs.h"
namespace {
namespace fio = ::llcpp::fuchsia::io;
template <bool repairable>
class MountTestTemplate : public zxtest::Test {
public:
void SetUp() final {
ASSERT_EQ(ramdisk_create_at(fs::g_environment->devfs_root().get(), 512, 1 << 16, &ramdisk_),
ZX_OK);
ramdisk_path_ = std::string("/fake/dev/") + ramdisk_get_path(ramdisk_);
ASSERT_OK(
mkfs(ramdisk_path_.c_str(), DISK_FORMAT_MINFS, launch_stdio_sync, &default_mkfs_options));
int ramdisk_block_fd = ramdisk_get_block_fd(ramdisk_);
zx::channel block_channel;
ASSERT_OK(fdio_fd_clone(ramdisk_block_fd, block_channel.reset_and_get_address()));
std::unique_ptr<block_client::RemoteBlockDevice> device;
ASSERT_OK(block_client::RemoteBlockDevice::Create(std::move(block_channel), &device));
bool readonly_device = false;
ASSERT_OK(minfs::CreateBcache(std::move(device), &readonly_device, &bcache_));
ASSERT_FALSE(readonly_device);
ASSERT_OK(zx::channel::create(0, &root_client_end_, &root_server_end_));
ASSERT_OK(loop_.StartThread("minfs test dispatcher"));
}
void ReadSuperblock(minfs::Superblock* out) {
fbl::unique_fd fd(open(ramdisk_path_.c_str(), O_RDONLY));
EXPECT_TRUE(fd);
EXPECT_EQ(pread(fd.get(), out, sizeof(*out), minfs::kSuperblockStart * minfs::kMinfsBlockSize),
sizeof(*out));
}
void Unmount() {
if (unmounted_) {
return;
}
// Unmount the filesystem, thereby terminating the minfs instance.
// TODO(fxbug.dev/34531): After deprecating the DirectoryAdmin interface, switch to unmount
// using the admin service found within the export directory.
EXPECT_OK(fio::DirectoryAdmin::Call::Unmount(zx::unowned_channel(root_client_end())).status());
unmounted_ = true;
}
void TearDown() final {
Unmount();
ASSERT_OK(ramdisk_destroy(ramdisk_));
}
protected:
ramdisk_client_t* ramdisk() const { return ramdisk_; }
const char* ramdisk_path() const { return ramdisk_path_.c_str(); }
std::unique_ptr<minfs::Bcache> bcache() { return std::move(bcache_); }
minfs::MountOptions mount_options() const {
return minfs::MountOptions{.readonly_after_initialization = false,
.metrics = false,
.verbose = true,
.repair_filesystem = repairable,
.fvm_data_slices = default_mkfs_options.fvm_data_slices};
}
const zx::channel& root_client_end() { return root_client_end_; }
zx::channel clone_root_client_end() {
zx::channel clone_root_client_end, clone_root_server_end;
ZX_ASSERT(zx::channel::create(0, &clone_root_client_end, &clone_root_server_end) == ZX_OK);
ZX_ASSERT(fio::Node::Call::Clone(zx::unowned_channel(root_client_end()),
fio::CLONE_FLAG_SAME_RIGHTS, std::move(clone_root_server_end))
.ok());
return clone_root_client_end;
}
fbl::unique_fd clone_root_as_fd() {
zx::channel clone_client_end = clone_root_client_end();
fbl::unique_fd root_fd;
EXPECT_OK(fdio_fd_create(clone_client_end.release(), root_fd.reset_and_get_address()));
EXPECT_TRUE(root_fd.is_valid());
return root_fd;
}
zx::channel& root_server_end() { return root_server_end_; }
async::Loop& loop() { return loop_; }
zx_status_t MountAndServe(minfs::ServeLayout serve_layout) {
return minfs::MountAndServe(
mount_options(), loop().dispatcher(), bcache(), std::move(root_server_end()),
[this]() { loop().Quit(); }, serve_layout);
}
private:
bool unmounted_ = false;
ramdisk_client_t* ramdisk_ = nullptr;
std::string ramdisk_path_;
std::unique_ptr<minfs::Bcache> bcache_ = nullptr;
zx::channel root_client_end_ = {};
zx::channel root_server_end_ = {};
async::Loop loop_ = async::Loop(&kAsyncLoopConfigNoAttachToCurrentThread);
};
using MountTest = MountTestTemplate<false>;
TEST_F(MountTest, ServeDataRootCheckInode) {
ASSERT_OK(MountAndServe(minfs::ServeLayout::kDataRootOnly));
// Verify that |root_client_end| corresponds to the root of the filesystem.
auto attr_result = fio::Node::Call::GetAttr(zx::unowned_channel(root_client_end()));
ASSERT_OK(attr_result.status());
ASSERT_OK(attr_result->s);
EXPECT_EQ(attr_result->attributes.id, minfs::kMinfsRootIno);
}
TEST_F(MountTest, ServeDataRootAllowFileCreationInRoot) {
ASSERT_OK(MountAndServe(minfs::ServeLayout::kDataRootOnly));
// Adding a file is allowed here...
fbl::unique_fd root_fd = clone_root_as_fd();
ASSERT_TRUE(root_fd.is_valid());
{
fbl::unique_fd foo_fd(openat(root_fd.get(), "foo", O_CREAT));
EXPECT_TRUE(foo_fd.is_valid());
}
}
TEST_F(MountTest, ServeExportDirectoryExportRootDirectoryEntries) {
ASSERT_OK(MountAndServe(minfs::ServeLayout::kExportDirectory));
fbl::unique_fd root_fd = clone_root_as_fd();
ASSERT_TRUE(root_fd.is_valid());
// Verify that |root_client_end| corresponds to the export directory.
struct dirent* entry = nullptr;
fbl::unique_fd dir_fd(dup(root_fd.get()));
ASSERT_TRUE(dir_fd.is_valid());
DIR* dir = fdopendir(dir_fd.get());
ASSERT_NOT_NULL(dir);
dir_fd.release();
fbl::AutoCall close_dir([&]() { closedir(dir); });
int count = 0;
// Verify that there is exactly one entry called "root".
// TODO(fxbug.dev/34531): Adjust this test accordingly when the admin service is added.
while ((entry = readdir(dir)) != nullptr) {
if ((strcmp(entry->d_name, ".") != 0) && (strcmp(entry->d_name, "..") != 0)) {
EXPECT_STR_EQ(entry->d_name, "root");
EXPECT_EQ(entry->d_type, DT_DIR);
EXPECT_EQ(count, 0);
count++;
}
}
EXPECT_EQ(count, 1);
}
TEST_F(MountTest, ServeExportDirectoryDisallowFileCreationInExportRoot) {
ASSERT_OK(MountAndServe(minfs::ServeLayout::kExportDirectory));
fbl::unique_fd root_fd = clone_root_as_fd();
ASSERT_TRUE(root_fd.is_valid());
// Adding a file is disallowed here...
fbl::unique_fd foo_fd(openat(root_fd.get(), "foo", O_CREAT | O_EXCL | O_RDWR));
EXPECT_FALSE(foo_fd.is_valid());
}
TEST_F(MountTest, ServeExportDirectoryAllowFileCreationInDataRoot) {
ASSERT_OK(MountAndServe(minfs::ServeLayout::kExportDirectory));
fbl::unique_fd root_fd = clone_root_as_fd();
ASSERT_TRUE(root_fd.is_valid());
// Adding a file in "root/" is allowed, since "root/" is within the mutable minfs filesystem.
fbl::unique_fd foo_fd(openat(root_fd.get(), "root/foo", O_CREAT | O_EXCL | O_RDWR));
EXPECT_TRUE(foo_fd.is_valid());
}
using RepairableMountTest = MountTestTemplate<true>;
// After successful mount, superblock's clean bit should be cleared and
// persisted to the disk. Reading superblock from raw disk should return cleared
// clean bit.
TEST_F(RepairableMountTest, SyncDuringMount) {
minfs::Superblock info;
ReadSuperblock(&info);
ASSERT_EQ(minfs::kMinfsFlagClean & info.flags, minfs::kMinfsFlagClean);
ASSERT_OK(MountAndServe(minfs::ServeLayout::kExportDirectory));
// Reading raw device after mount should get us superblock with clean bit
// unset.
ReadSuperblock(&info);
ASSERT_EQ(minfs::kMinfsFlagClean & info.flags, 0);
}
// After successful unmount, superblock's clean bit should be set and persisted
// to the disk. Reading superblock from raw disk should return set clean bit.
TEST_F(RepairableMountTest, SyncDuringUnmount) {
minfs::Superblock info;
ASSERT_OK(MountAndServe(minfs::ServeLayout::kExportDirectory));
// Reading raw device after mount should get us superblock with clean bit
// unset.
ReadSuperblock(&info);
ASSERT_EQ(minfs::kMinfsFlagClean & info.flags, 0);
Unmount();
// Reading raw device after unmount should get us superblock with clean bit
// set.
ReadSuperblock(&info);
ASSERT_EQ(minfs::kMinfsFlagClean & info.flags, minfs::kMinfsFlagClean);
}
} // namespace