blob: 60ffe31eb68f6d692255624c92e3a46ff0b10638 [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 "src/storage/lib/fs_management/cpp/admin.h"
#include <fidl/fuchsia.io/cpp/wire.h>
#include <lib/fdio/directory.h>
#include <lib/zx/channel.h>
#include <vector>
#include <gtest/gtest.h>
#include <ramdevice-client/ramdisk.h>
#include "src/storage/fs_test/crypt_service.h"
#include "src/storage/lib/fs_management/cpp/format.h"
#include "src/storage/lib/fs_management/cpp/mkfs_with_default.h"
#include "src/storage/lib/fs_management/cpp/mount.h"
#include "src/storage/testing/ram_disk.h"
namespace fs_management {
namespace {
namespace fio = fuchsia_io;
using fuchsia_io::Directory;
enum State {
kFormatted,
kStarted,
};
enum class Mode {
// A statically routed component.
kStatic,
// A dynamically routed component.
kDynamic,
};
constexpr char kTestFilePath[] = "test_file";
class OutgoingDirectoryFixture : public testing::Test {
public:
explicit OutgoingDirectoryFixture(DiskFormat format, Mode mode) : format_(format), mode_(mode) {}
void TearDown() final { ASSERT_NO_FATAL_FAILURE(StopFilesystem()); }
fidl::ClientEnd<Directory> DataRoot() {
ZX_ASSERT(component_); // Ensure this isn't used after stopping the filesystem.
auto data = fs_->DataRoot();
ZX_ASSERT_MSG(data.is_ok(), "Invalid data root: %s", data.status_string());
return std::move(*data);
}
const fidl::ClientEnd<Directory>& ExportRoot() {
ZX_ASSERT(component_); // Ensure this isn't used after stopping the filesystem.
return fs_->ExportRoot();
}
protected:
void StartFilesystem(MountOptions options) {
ASSERT_FALSE(component_);
switch (mode_) {
case Mode::kStatic: {
std::string name = "static-test-";
name.append(DiskFormatString(format_));
component_ = fs_management::FsComponent::StaticChild(name, format_);
break;
}
case Mode::kDynamic:
component_ = fs_management::FsComponent::FromDiskFormat(format_);
}
if (!ramdisk_.client()) {
auto ramdisk_or = storage::RamDisk::Create(512, 1 << 17);
ASSERT_EQ(ramdisk_or.status_value(), ZX_OK);
ramdisk_ = std::move(*ramdisk_or);
zx_status_t status;
if (format_ == kDiskFormatFxfs) {
zx::result service = fs_test::GetCryptService();
ASSERT_TRUE(service.is_ok());
zx::result status =
MkfsWithDefault(ramdisk_.path().c_str(), *component_, {}, *std::move(service));
ASSERT_TRUE(status.is_ok()) << status.status_string();
} else {
ASSERT_EQ(status = Mkfs(ramdisk_.path().c_str(), *component_, {}), ZX_OK)
<< zx_status_get_string(status);
}
ASSERT_EQ(status = Fsck(ramdisk_.path().c_str(), *component_, {}), ZX_OK)
<< zx_status_get_string(status);
}
zx::result device = ramdisk_.channel();
ASSERT_EQ(device.status_value(), ZX_OK);
if (format_ == kDiskFormatFxfs) {
options.crypt_client = [] {
if (auto service = fs_test::GetCryptService(); service.is_error()) {
ADD_FAILURE() << "Unable to get crypt service";
return zx::channel();
} else {
return *std::move(service);
}
};
auto fs = MountMultiVolumeWithDefault(std::move(device.value()), *component_, options);
ASSERT_TRUE(fs.is_ok()) << fs.status_string();
fs_ = std::make_unique<StartedSingleVolumeMultiVolumeFilesystem>(std::move(*fs));
} else {
auto fs = Mount(std::move(device.value()), *component_, options);
ASSERT_TRUE(fs.is_ok()) << fs.status_string();
fs_ = std::make_unique<StartedSingleVolumeFilesystem>(std::move(*fs));
}
}
void StopFilesystem() {
if (!component_)
return;
ASSERT_EQ(fs_->Unmount().status_value(), ZX_OK);
fs_.reset();
if (mode_ == Mode::kDynamic)
component_.reset();
}
private:
storage::RamDisk ramdisk_;
DiskFormat format_;
Mode mode_;
std::optional<fs_management::FsComponent> component_;
std::unique_ptr<SingleVolumeFilesystemInterface> fs_;
};
// Generalized Admin Tests
std::string PrintTestSuffix(const testing::TestParamInfo<std::tuple<DiskFormat, Mode>> params) {
std::stringstream out;
out << DiskFormatString(std::get<0>(params.param));
switch (std::get<1>(params.param)) {
case Mode::kDynamic:
out << "_dynamic";
__FALLTHROUGH;
case Mode::kStatic:
out << "_component";
break;
}
return out.str();
}
// Generalized outgoing directory tests which should work in both mutable and read-only modes.
class OutgoingDirectoryTest : public OutgoingDirectoryFixture,
public testing::WithParamInterface<std::tuple<DiskFormat, Mode>> {
public:
OutgoingDirectoryTest()
: OutgoingDirectoryFixture(std::get<0>(GetParam()), std::get<1>(GetParam())) {}
};
TEST_P(OutgoingDirectoryTest, DataRootIsValid) {
ASSERT_NO_FATAL_FAILURE(StartFilesystem({}));
std::string_view format_str = DiskFormatString(std::get<0>(GetParam()));
auto resp = fidl::WireCall(DataRoot())->QueryFilesystem();
ASSERT_TRUE(resp.ok()) << resp.status_string();
ASSERT_EQ(resp.value().s, ZX_OK) << zx_status_get_string(resp.value().s);
ASSERT_STREQ(format_str.data(), reinterpret_cast<char*>(resp.value().info->name.data()));
}
using Combinations = std::vector<std::tuple<DiskFormat, Mode>>;
Combinations TestCombinations() {
Combinations c;
auto add = [&](DiskFormat format, std::initializer_list<Mode> modes) {
for (Mode mode : modes) {
c.emplace_back(format, mode);
}
};
add(kDiskFormatBlobfs, {Mode::kDynamic, Mode::kStatic});
add(kDiskFormatMinfs, {Mode::kDynamic, Mode::kStatic});
add(kDiskFormatFxfs, {Mode::kDynamic, Mode::kStatic});
add(kDiskFormatF2fs, {Mode::kDynamic, Mode::kStatic});
return c;
}
INSTANTIATE_TEST_SUITE_P(OutgoingDirectoryTest, OutgoingDirectoryTest,
testing::ValuesIn(TestCombinations()), PrintTestSuffix);
// Minfs-Specific Tests (can be generalized to work with any mutable filesystem by parameterizing
// on the disk format if required).
// Launches the filesystem and creates a file called kTestFilePath in the data root.
class OutgoingDirectoryMinfs : public OutgoingDirectoryFixture {
public:
// Some test cases involve remounting, which will not work for Mode::kStatic.
OutgoingDirectoryMinfs() : OutgoingDirectoryFixture(kDiskFormatMinfs, Mode::kDynamic) {}
protected:
void WriteTestFile() {
auto test_file_ends = fidl::CreateEndpoints<fio::File>();
ASSERT_TRUE(test_file_ends.is_ok()) << test_file_ends.status_string();
fidl::ServerEnd<fio::Node> test_file_server(test_file_ends->server.TakeChannel());
fio::wire::OpenFlags file_flags = fio::wire::OpenFlags::kRightReadable |
fio::wire::OpenFlags::kRightWritable |
fio::wire::OpenFlags::kCreate;
ASSERT_EQ(fidl::WireCall(DataRoot())
->Open(file_flags, {}, kTestFilePath, std::move(test_file_server))
.status(),
ZX_OK);
fidl::WireSyncClient<fio::File> file_client(std::move(test_file_ends->client));
std::vector<uint8_t> content{1, 2, 3, 4};
const fidl::WireResult res =
file_client->Write(fidl::VectorView<uint8_t>::FromExternal(content));
ASSERT_TRUE(res.ok()) << res.status_string();
const fit::result resp = res.value();
ASSERT_TRUE(resp.is_ok()) << zx_status_get_string(resp.error_value());
ASSERT_EQ(resp.value()->actual_count, content.size());
auto resp2 = file_client->Close();
ASSERT_TRUE(resp2.ok()) << resp2.status_string();
ASSERT_TRUE(resp2->is_ok()) << zx_status_get_string(resp2->error_value());
}
};
TEST_F(OutgoingDirectoryMinfs, CannotWriteToReadOnlyDataRoot) {
ASSERT_NO_FATAL_FAILURE(StartFilesystem({}));
ASSERT_NO_FATAL_FAILURE(WriteTestFile());
// Restart the filesystem in read-only mode
ASSERT_NO_FATAL_FAILURE(StopFilesystem());
ASSERT_NO_FATAL_FAILURE(StartFilesystem({.readonly = true}));
auto data_root = DataRoot();
auto fail_file_ends = fidl::CreateEndpoints<fio::File>();
ASSERT_TRUE(fail_file_ends.is_ok()) << fail_file_ends.status_string();
fidl::ServerEnd<fio::Node> fail_test_file_server(fail_file_ends->server.TakeChannel());
fio::wire::OpenFlags fail_file_flags =
fio::wire::OpenFlags::kRightReadable | fio::wire::OpenFlags::kRightWritable;
// open "succeeds" but...
auto open_resp = fidl::WireCall(data_root)->Open(fail_file_flags, {}, kTestFilePath,
std::move(fail_test_file_server));
ASSERT_TRUE(open_resp.ok()) << open_resp.status_string();
// ...we can't actually use the channel
fidl::WireSyncClient<fio::File> fail_file_client(std::move(fail_file_ends->client));
const fidl::WireResult res1 = fail_file_client->Read(4);
ASSERT_EQ(res1.status(), ZX_ERR_PEER_CLOSED) << res1.status_string();
// the channel will be valid if we open the file read-only though
auto test_file_ends = fidl::CreateEndpoints<fio::File>();
ASSERT_TRUE(test_file_ends.is_ok()) << test_file_ends.status_string();
fidl::ServerEnd<fio::Node> test_file_server(test_file_ends->server.TakeChannel());
fio::wire::OpenFlags file_flags = fio::wire::OpenFlags::kRightReadable;
auto open_resp2 =
fidl::WireCall(data_root)->Open(file_flags, {}, kTestFilePath, std::move(test_file_server));
ASSERT_TRUE(open_resp2.ok()) << open_resp2.status_string();
fidl::WireSyncClient<fio::File> file_client(std::move(test_file_ends->client));
const fidl::WireResult res2 = file_client->Read(4);
ASSERT_TRUE(res2.ok()) << res2.status_string();
const fit::result resp2 = res2.value();
ASSERT_TRUE(resp2.is_ok()) << zx_status_get_string(resp2.error_value());
ASSERT_EQ(resp2.value()->data[0], 1);
auto close_resp = file_client->Close();
ASSERT_TRUE(close_resp.ok()) << close_resp.status_string();
ASSERT_TRUE(close_resp->is_ok()) << zx_status_get_string(close_resp->error_value());
}
TEST_F(OutgoingDirectoryMinfs, CannotWriteToOutgoingDirectory) {
ASSERT_NO_FATAL_FAILURE(StartFilesystem({}));
ASSERT_NO_FATAL_FAILURE(WriteTestFile());
auto test_file_ends = fidl::CreateEndpoints<fio::File>();
ASSERT_TRUE(test_file_ends.is_ok()) << test_file_ends.status_string();
fidl::ServerEnd<fio::Node> test_file_server(test_file_ends->server.TakeChannel());
fio::wire::OpenFlags file_flags = fio::wire::OpenFlags::kRightReadable |
fio::wire::OpenFlags::kRightWritable |
fio::wire::OpenFlags::kCreate;
auto open_resp = fidl::WireCall(ExportRoot())
->Open(file_flags, {}, kTestFilePath, std::move(test_file_server));
ASSERT_TRUE(open_resp.ok()) << open_resp.status_string();
fidl::WireSyncClient<fio::File> file_client(std::move(test_file_ends->client));
std::vector<uint8_t> content{1, 2, 3, 4};
auto write_resp = file_client->Write(fidl::VectorView<uint8_t>::FromExternal(content));
ASSERT_EQ(write_resp.status(), ZX_ERR_PEER_CLOSED) << write_resp.status_string();
}
} // namespace
} // namespace fs_management