blob: e466518dc7566ea10930914a70244cfd5fc92f5d [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 "src/storage/lib/fs_management/cpp/mount.h"
#include <errno.h>
#include <fcntl.h>
#include <fidl/fuchsia.fs/cpp/wire.h>
#include <fidl/fuchsia.hardware.block.volume/cpp/wire.h>
#include <fidl/fuchsia.hardware.block/cpp/wire.h>
#include <fidl/fuchsia.io/cpp/wire.h>
#include <fuchsia/hardware/block/driver/c/banjo.h>
#include <lib/component/incoming/cpp/clone.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/fdio/vfs.h>
#include <lib/syslog/cpp/macros.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/statfs.h>
#include <sys/statvfs.h>
#include <unistd.h>
#include <zircon/syscalls.h>
#include <utility>
#include <fbl/unique_fd.h>
#include <gtest/gtest.h>
#include <ramdevice-client/ramdisk.h>
#include "src/storage/lib/fs_management/cpp/admin.h"
#include "src/storage/testing/fvm.h"
#include "src/storage/testing/ram_disk.h"
namespace fs_management {
namespace {
const char* kTestMountPath = "/test/mount";
void CheckMountedFs(const char* path, const char* fs_name) {
fbl::unique_fd fd(open(path, O_RDONLY | O_DIRECTORY));
ASSERT_TRUE(fd);
fdio_cpp::FdioCaller caller(std::move(fd));
auto result = fidl::WireCall(caller.directory())->QueryFilesystem();
ASSERT_EQ(result.status(), ZX_OK);
ASSERT_EQ(result.value().s, ZX_OK);
fuchsia_io::wire::FilesystemInfo info = *result.value().info;
ASSERT_EQ(strncmp(fs_name, reinterpret_cast<char*>(info.name.data()), strlen(fs_name)), 0);
ASSERT_LE(info.used_nodes, info.total_nodes) << "Used nodes greater than free nodes";
ASSERT_LE(info.used_bytes, info.total_bytes) << "Used bytes greater than free bytes";
// TODO(planders): eventually check that total/used counts are > 0
}
class RamdiskTestFixture : public testing::Test {
public:
void SetUp() override {
auto ramdisk_or = storage::RamDisk::Create(512, 1 << 16);
ASSERT_EQ(ramdisk_or.status_value(), ZX_OK);
ramdisk_ = std::move(*ramdisk_or);
auto component = FsComponent::FromDiskFormat(kDiskFormatMinfs);
ASSERT_EQ(Mkfs(ramdisk_path().c_str(), component, {}), ZX_OK);
}
std::string ramdisk_path() const { return ramdisk_.path(); }
ramdisk_client_t* ramdisk_client() const { return ramdisk_.client(); }
struct MountResult {
FsComponent component;
StartedSingleVolumeFilesystem fs;
NamespaceBinding binding;
};
// Mounts a minfs formatted partition to the desired point.
zx::result<MountResult> MountMinfs(bool read_only) {
MountOptions options{.readonly = read_only};
zx::result<fidl::ClientEnd<fuchsia_hardware_block::Block>> block_client =
component::Connect<fuchsia_hardware_block::Block>(ramdisk_path());
if (block_client.is_error()) {
return block_client.take_error();
}
auto component = FsComponent::FromDiskFormat(kDiskFormatMinfs);
auto mounted_filesystem = Mount(std::move(block_client.value()), component, options);
if (mounted_filesystem.is_error())
return mounted_filesystem.take_error();
auto data_root = mounted_filesystem->DataRoot();
if (data_root.is_error())
return data_root.take_error();
auto binding = NamespaceBinding::Create(kTestMountPath, *std::move(data_root));
if (binding.is_error())
return binding.take_error();
CheckMountedFs(kTestMountPath, "minfs");
return zx::ok(MountResult{.component = std::move(component),
.fs = std::move(*mounted_filesystem),
.binding = std::move(*binding)});
}
// Formats the ramdisk with minfs, and writes a small file to it.
void CreateTestFile(const char* file_name) {
auto mounted_filesystem_or = MountMinfs(/*read_only=*/false);
ASSERT_EQ(mounted_filesystem_or.status_value(), ZX_OK);
fbl::unique_fd root_fd(open(kTestMountPath, O_RDONLY | O_DIRECTORY));
ASSERT_TRUE(root_fd);
fbl::unique_fd fd(openat(root_fd.get(), file_name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR));
ASSERT_TRUE(fd);
ASSERT_EQ(write(fd.get(), "hello", 6), 6);
}
private:
storage::RamDisk ramdisk_;
};
using MountTest = RamdiskTestFixture;
TEST_F(MountTest, MountRemount) {
// We should still be able to mount and unmount the filesystem multiple times
for (size_t i = 0; i < 10; i++) {
auto fs = MountMinfs(/*read_only=*/false);
ASSERT_EQ(fs.status_value(), ZX_OK);
}
}
TEST_F(MountTest, MountFsck) {
{
auto mounted_filesystem_or = MountMinfs(/*read_only=*/false);
ASSERT_EQ(mounted_filesystem_or.status_value(), ZX_OK);
}
// Fsck shouldn't require any user input for a newly mkfs'd filesystem.
auto component = FsComponent::FromDiskFormat(kDiskFormatMinfs);
ASSERT_EQ(Fsck(ramdisk_path(), component, FsckOptions()), ZX_OK);
}
// Tests that setting read-only on the mount options works as expected.
TEST_F(MountTest, MountReadonly) {
const char file_name[] = "some_file";
CreateTestFile(file_name);
bool read_only = true;
auto mounted_filesystem_or = MountMinfs(read_only);
ASSERT_EQ(mounted_filesystem_or.status_value(), ZX_OK);
fbl::unique_fd root_fd(open(kTestMountPath, O_RDONLY | O_DIRECTORY));
ASSERT_TRUE(root_fd);
fbl::unique_fd fd(openat(root_fd.get(), file_name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR));
// We can no longer open the file as writable
ASSERT_FALSE(fd);
// We CAN open it as readable though
fd.reset(openat(root_fd.get(), file_name, O_RDONLY));
ASSERT_TRUE(fd);
ASSERT_LT(write(fd.get(), "hello", 6), 0);
char buf[6];
ASSERT_EQ(read(fd.get(), buf, 6), 6);
ASSERT_EQ(memcmp(buf, "hello", 6), 0);
ASSERT_LT(renameat(root_fd.get(), file_name, root_fd.get(), "new_file"), 0);
ASSERT_LT(unlinkat(root_fd.get(), file_name, 0), 0);
}
TEST_F(MountTest, StatfsTest) {
auto mounted_filesystem_or = MountMinfs(/*read_only=*/false);
ASSERT_EQ(mounted_filesystem_or.status_value(), ZX_OK);
errno = 0;
struct statfs stats;
int rc = statfs("", &stats);
int err = errno;
ASSERT_EQ(rc, -1);
ASSERT_EQ(err, ENOENT);
rc = statfs(kTestMountPath, &stats);
ASSERT_EQ(rc, 0);
// Verify that at least some values make sense, without making the test too brittle.
ASSERT_EQ(stats.f_type, fidl::ToUnderlying(fuchsia_fs::VfsType::kMinfs));
ASSERT_NE(stats.f_fsid.__val[0] | stats.f_fsid.__val[1], 0);
ASSERT_EQ(stats.f_bsize, 8192u);
ASSERT_EQ(stats.f_namelen, 255u);
ASSERT_GT(stats.f_bavail, 0u);
ASSERT_GT(stats.f_ffree, 0u);
}
TEST_F(MountTest, StatvfsTest) {
auto mounted_filesystem_or = MountMinfs(/*read_only=*/false);
ASSERT_EQ(mounted_filesystem_or.status_value(), ZX_OK);
errno = 0;
struct statvfs stats;
int rc = statvfs("", &stats);
int err = errno;
ASSERT_EQ(rc, -1);
ASSERT_EQ(err, ENOENT);
rc = statvfs(kTestMountPath, &stats);
ASSERT_EQ(rc, 0);
// Verify that at least some values make sense, without making the test too brittle.
ASSERT_NE(stats.f_fsid, 0ul);
ASSERT_EQ(stats.f_bsize, 8192u);
ASSERT_EQ(stats.f_frsize, 8192u);
ASSERT_EQ(stats.f_namemax, 255u);
ASSERT_GT(stats.f_bavail, 0u);
ASSERT_GT(stats.f_ffree, 0u);
ASSERT_GT(stats.f_favail, 0u);
}
void GetPartitionSliceCount(fidl::UnownedClientEnd<fuchsia_hardware_block_volume::Volume> volume,
size_t* out_count) {
auto res = fidl::WireCall(volume)->GetVolumeInfo();
ASSERT_EQ(res.status(), ZX_OK);
ASSERT_EQ(res.value().status, ZX_OK);
size_t allocated_slices = 0;
std::vector<uint64_t> start_slices = {0};
while (start_slices[0] < res.value().manager->max_virtual_slice) {
auto res =
fidl::WireCall(volume)->QuerySlices(fidl::VectorView<uint64_t>::FromExternal(start_slices));
ASSERT_EQ(res.status(), ZX_OK);
ASSERT_EQ(res.value().status, ZX_OK);
start_slices[0] += res.value().response[0].count;
if (res.value().response[0].allocated) {
allocated_slices += res.value().response[0].count;
}
}
// The two methods of getting the partition slice count should agree.
ASSERT_EQ(res.value().volume->partition_slice_count, allocated_slices);
*out_count = allocated_slices;
}
class PartitionOverFvmWithRamdiskFixture : public testing::Test {
public:
const char* partition_path() const { return partition_path_.c_str(); }
protected:
static constexpr uint64_t kBlockSize = 512;
void SetUp() override {
size_t ramdisk_block_count = zx_system_get_physmem() / (1024);
auto ramdisk_or = storage::RamDisk::Create(kBlockSize, ramdisk_block_count);
ASSERT_EQ(ramdisk_or.status_value(), ZX_OK);
ramdisk_ = std::move(*ramdisk_or);
uint64_t slice_size = kBlockSize * (2 << 10);
auto partition_or =
storage::CreateFvmPartition(ramdisk_.path(), static_cast<size_t>(slice_size));
ASSERT_TRUE(partition_or.is_ok()) << partition_or.status_string();
partition_path_ = std::move(partition_or).value();
}
private:
storage::RamDisk ramdisk_;
std::string partition_path_;
};
using PartitionOverFvmWithRamdiskCase = PartitionOverFvmWithRamdiskFixture;
// Reformat the partition using a number of slices and verify that there are as many slices as
// originally pre-allocated.
TEST_F(PartitionOverFvmWithRamdiskCase, MkfsMinfsWithMinFvmSlices) {
size_t base_slices = 0;
auto component = fs_management::FsComponent::FromDiskFormat(kDiskFormatMinfs);
MkfsOptions options;
ASSERT_EQ(Mkfs(partition_path(), component, options), ZX_OK);
zx::result volume = component::Connect<fuchsia_hardware_block_volume::Volume>(partition_path());
ASSERT_TRUE(volume.is_ok()) << volume.status_string();
GetPartitionSliceCount(volume.value(), &base_slices);
options.fvm_data_slices += 10;
ASSERT_EQ(Mkfs(partition_path(), component, options), ZX_OK);
size_t allocated_slices = 0;
GetPartitionSliceCount(volume.value(), &allocated_slices);
EXPECT_GE(allocated_slices, base_slices + 10);
DiskFormat actual_format = DetectDiskFormat(
fidl::UnownedClientEnd<fuchsia_hardware_block::Block>(volume.value().channel().borrow()));
ASSERT_EQ(actual_format, kDiskFormatMinfs);
}
} // namespace
} // namespace fs_management