blob: 31725dc141c5b63bd4c8d4f6ebc9971ee634140f [file] [log] [blame]
// Copyright 2022 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/bin/start-storage-benchmark/block-device.h"
#include <fcntl.h>
#include <fidl/fuchsia.fxfs/cpp/wire.h>
#include <fidl/fuchsia.hardware.block.volume/cpp/wire.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/spawn.h>
#include <lib/service/llcpp/service.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/process.h>
#include <zircon/device/block.h>
#include <zircon/hw/gpt.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <filesystem>
#include <memory>
#include <string_view>
#include <fbl/algorithm.h>
#include <fbl/unique_fd.h>
#include "src/lib/storage/fs_management/cpp/admin.h"
#include "src/lib/storage/fs_management/cpp/format.h"
#include "src/lib/storage/fs_management/cpp/fvm.h"
#include "src/security/zxcrypt/client.h"
#include "src/storage/bin/start-storage-benchmark/running-filesystem.h"
#include "src/storage/fs_test/crypt_service.h"
#include "src/storage/lib/utils/topological_path.h"
namespace storage_benchmark {
namespace {
using fuchsia_hardware_block_volume::Volume;
using fuchsia_hardware_block_volume::VolumeManager;
zx::status<uint64_t> GetFvmSliceSize(fidl::ClientEnd<VolumeManager> &fvm_client) {
auto response = fidl::WireCall(fvm_client)->GetInfo();
if (!response.ok()) {
fprintf(stderr, "Failed to get fvm slice size: %s\n", response.status_string());
return zx::error(response.status());
}
if (response.value().status != ZX_OK) {
fprintf(stderr, "Failed to get fvm slice size: %s\n",
zx_status_get_string(response.value().status));
return zx::error(response.value().status);
}
return zx::ok(response.value().info->slice_size);
}
// Returns the number of slices required to create a volume of |volume_size| in fvm.
zx::status<uint64_t> GetSliceCount(fidl::ClientEnd<VolumeManager> &fvm_client,
uint64_t volume_size) {
if (volume_size == 0) {
// If no volume size was specified then only use 1 slice and let the filesystem grow within
// fvm as needed. This only works fvm-aware filesystems like blobfs and minfs.
return zx::ok(1);
}
auto slice_size = GetFvmSliceSize(fvm_client);
if (slice_size.is_error()) {
return slice_size.take_error();
}
return zx::ok(fbl::round_up(volume_size, *slice_size) / *slice_size);
}
// Wrapper around a |MountedFilesystem| to meet the |RunningFilesystem| interface.
class BlockDeviceFilesystem : public RunningFilesystem {
public:
// Takes ownership of the volume to ensure that the volume outlives the mounted filesystem.
explicit BlockDeviceFilesystem(fs_management::MountedFilesystem filesystem, FvmVolume volume)
: volume_(std::move(volume)), filesystem_(std::move(filesystem)) {}
zx::status<fidl::ClientEnd<fuchsia_io::Directory>> GetFilesystemRoot() const override {
return fs_management::FsRootHandle(filesystem_.export_root());
}
private:
FvmVolume volume_;
fs_management::MountedFilesystem filesystem_;
};
// Whilst this runs as a v1 component, we use this slightly hacky way of launching the crypt
// service. Once we have migrated to a v2 component, then we should be able to instantiate the
// crypt service via the manifest and run it as a child.
zx::status<zx::channel> GetCryptService() {
static zx_handle_t svc_directory = [] {
zx::process process;
char error_message[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
constexpr char kFxfsCryptPath[] = "/pkg/bin/fxfs_crypt";
const char *argv[] = {kFxfsCryptPath, nullptr};
auto outgoing_directory_or = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (outgoing_directory_or.is_error()) {
fprintf(stderr, "Unable to create endpoints: %s\n", outgoing_directory_or.status_string());
return ZX_HANDLE_INVALID;
}
fdio_spawn_action_t actions[] = {
{.action = FDIO_SPAWN_ACTION_ADD_HANDLE,
.h = {.id = PA_DIRECTORY_REQUEST,
.handle = outgoing_directory_or->server.TakeChannel().release()}}};
if (fdio_spawn_etc(ZX_HANDLE_INVALID, FDIO_SPAWN_CLONE_ALL, kFxfsCryptPath, argv, nullptr, 1,
actions, process.reset_and_get_address(), error_message)) {
fprintf(stderr, "Failed to launch crypt service: %s\n", error_message);
return ZX_HANDLE_INVALID;
}
auto service_endpoints_or = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (service_endpoints_or.is_error()) {
fprintf(stderr, "Unable to create endpoints: %s\n", service_endpoints_or.status_string());
return ZX_HANDLE_INVALID;
}
if (auto result = fidl::WireCall(outgoing_directory_or->client)
->Open(fuchsia_io::wire::OpenFlags::kRightReadable |
fuchsia_io::wire::OpenFlags::kRightWritable,
0, "svc",
fidl::ServerEnd<fuchsia_io::Node>(
service_endpoints_or->server.TakeChannel()));
result.status() != ZX_OK) {
fprintf(stderr, "Failed to open svc directory: %s\n", result.status_string());
return ZX_HANDLE_INVALID;
}
if (auto status = fs_test::SetUpCryptWithRandomKeys(service_endpoints_or->client);
status.is_error()) {
fprintf(stderr, "Unable to set up the crypt service: %s\n", status.status_string());
return ZX_HANDLE_INVALID;
}
return service_endpoints_or->client.TakeChannel().release();
}();
if (svc_directory == ZX_HANDLE_INVALID)
return zx::error(ZX_ERR_INTERNAL);
if (auto crypt_service_or = service::ConnectAt<fuchsia_fxfs::Crypt>(
fidl::UnownedClientEnd<fuchsia_io::Directory>(svc_directory));
crypt_service_or.is_error()) {
fprintf(stderr, "Unable to connect to crypt service: %s\n", crypt_service_or.status_string());
return crypt_service_or.take_error();
} else {
return zx::ok(crypt_service_or->TakeChannel());
}
}
} // namespace
FvmVolume::FvmVolume(FvmVolume &&other) noexcept : path_(std::move(other.path_)) {
other.path_.clear();
}
FvmVolume &FvmVolume::operator=(FvmVolume &&other) noexcept {
path_ = std::move(other.path_);
other.path_.clear();
return *this;
}
FvmVolume::~FvmVolume() {
if (path_.empty()) {
return;
}
auto volume_client = service::Connect<Volume>(path_.c_str());
if (volume_client.is_error()) {
fprintf(stderr, "Failed to connect to volume: %s %s\n", path_.c_str(),
volume_client.status_string());
return;
}
auto response = fidl::WireCall(*volume_client)->Destroy();
if (!response.ok()) {
fprintf(stderr, "Failed to destroy volume: %s\n", response.status_string());
return;
}
if (response.value().status != ZX_OK) {
fprintf(stderr, "Failed to destroy volume: %s\n",
zx_status_get_string(response.value().status));
}
}
zx::status<FvmVolume> FvmVolume::Create(fidl::ClientEnd<VolumeManager> &fvm_client,
uint64_t partition_size) {
zx::status<uint64_t> slice_count = GetSliceCount(fvm_client, partition_size);
if (slice_count.is_error()) {
return slice_count.take_error();
}
uuid::Uuid unique_instance = uuid::Uuid::Generate();
fuchsia_hardware_block_partition::wire::Guid instance_guid;
memcpy(instance_guid.value.data(), unique_instance.bytes(), BLOCK_GUID_LEN);
fuchsia_hardware_block_partition::wire::Guid type_guid = GUID_TEST_VALUE;
auto response =
fidl::WireCall(fvm_client)
->AllocatePartition(*slice_count, type_guid, instance_guid, fidl::StringView("benchmark"),
fuchsia_hardware_block_volume::wire::kAllocatePartitionFlagInactive);
if (!response.ok()) {
fprintf(stderr, "Failed to create volume: %s\n", response.status_string());
return zx::error(response.status());
}
if (response.value().status != ZX_OK) {
fprintf(stderr, "Failed to create volume: %s\n", zx_status_get_string(response.value().status));
return zx::error(response.value().status);
}
fs_management::PartitionMatcher matcher{
.type_guid = type_guid.value.data(),
.instance_guid = instance_guid.value.data(),
};
std::string path;
auto fd = fs_management::OpenPartition(&matcher, ZX_SEC(10), &path);
if (fd.is_error()) {
fprintf(stderr, "Failed to find newly created volume\n");
return zx::error(ZX_ERR_TIMED_OUT);
}
return zx::ok(FvmVolume(std::move(path)));
}
zx::status<std::string> FindFvmBlockDevicePath() {
for (const auto &block_device : std::filesystem::directory_iterator("/dev/class/block")) {
fbl::unique_fd fd(open(block_device.path().c_str(), O_RDWR));
if (!fd.is_valid()) {
continue;
}
auto disk_format = fs_management::DetectDiskFormat(fd.get());
if (disk_format == fs_management::DiskFormat::kDiskFormatFvm) {
return zx::ok(block_device.path());
}
}
FX_LOGS(ERROR) << "Failed to find fvm's block device";
return zx::error(ZX_ERR_NOT_FOUND);
}
zx::status<fidl::ClientEnd<VolumeManager>> ConnectToFvm(const std::string &fvm_block_device_path) {
auto fvm_block_topological_path = storage::GetTopologicalPath(fvm_block_device_path);
if (fvm_block_topological_path.is_error()) {
fprintf(stderr, "Failed to get the topological path to fvm's block device: %s\n",
fvm_block_topological_path.status_string());
return fvm_block_topological_path.take_error();
}
std::string fvm_path = *fvm_block_topological_path + "/fvm";
return service::Connect<VolumeManager>(fvm_path.c_str());
}
zx::status<> FormatBlockDevice(const std::string &block_device_path,
fs_management::DiskFormat format) {
fs_management::MkfsOptions mkfs_options;
if (format == fs_management::kDiskFormatFxfs) {
mkfs_options.crypt_client = [] {
if (auto service_or = GetCryptService(); service_or.is_error()) {
fprintf(stderr, "Failed to get crypt service: %s\n", service_or.status_string());
return zx::channel();
} else {
return *std::move(service_or);
}
};
mkfs_options.component_url = "#meta/fxfs";
mkfs_options.component_child_name = "fxfs";
}
zx::status<> status = zx::make_status(fs_management::Mkfs(
block_device_path.c_str(), format, fs_management::LaunchStdioSync, mkfs_options));
if (status.is_error()) {
// Convert the std::string_view to std::string to guarantee that it's null terminated.
std::string format_name(DiskFormatString(format));
fprintf(stderr, "Failed to format %s with %s\n", block_device_path.c_str(),
format_name.c_str());
}
return status;
}
zx::status<std::unique_ptr<RunningFilesystem>> StartBlockDeviceFilesystem(
const std::string &block_device_path, fs_management::DiskFormat format, FvmVolume fvm_volume) {
fs_management::MountOptions mount_options;
fbl::unique_fd volume_fd(open(block_device_path.c_str(), O_RDWR));
if (format == fs_management::kDiskFormatFxfs) {
mount_options.crypt_client = [] {
if (auto service_or = GetCryptService(); service_or.is_error()) {
fprintf(stderr, "Failed to get crypt service: %s\n", service_or.status_string());
return zx::channel();
} else {
return *std::move(service_or);
}
};
mount_options.component_url = "#meta/fxfs";
mount_options.component_child_name = "fxfs";
}
auto mounted_filesystem = fs_management::Mount(std::move(volume_fd), nullptr, format,
mount_options, fs_management::LaunchStdioAsync);
if (mounted_filesystem.is_error()) {
std::string format_name(DiskFormatString(format));
fprintf(stderr, "Failed to mount %s as %s\n", block_device_path.c_str(), format_name.c_str());
return mounted_filesystem.take_error();
}
return zx::ok(std::make_unique<BlockDeviceFilesystem>(std::move(mounted_filesystem).value(),
std::move(fvm_volume)));
}
zx::status<std::string> CreateZxcryptVolume(const std::string &device_path) {
fbl::unique_fd fd(open(device_path.c_str(), O_RDWR));
if (!fd) {
fprintf(stderr, "Failed to open %s\n", device_path.c_str());
return zx::error(ZX_ERR_BAD_STATE);
}
fbl::unique_fd dev_fd(open("/dev", O_RDONLY));
if (!dev_fd) {
fprintf(stderr, "Failed to open /dev\n");
return zx::error(ZX_ERR_BAD_STATE);
}
zxcrypt::VolumeManager volume_manager(std::move(fd), std::move(dev_fd));
zx::channel driver_chan;
auto status = zx::make_status(volume_manager.OpenClient(zx::sec(2), driver_chan));
if (status.is_error()) {
fprintf(stderr, "Failed to bind zxcrypt driver on %s\n", device_path.c_str());
return status.take_error();
}
zxcrypt::EncryptedVolumeClient volume(std::move(driver_chan));
status = zx::make_status(volume.FormatWithImplicitKey(0));
if (status.is_error()) {
fprintf(stderr, "Failed to create zxcrypt volume on %s\n", device_path.c_str());
return status.take_error();
}
status = zx::make_status(volume.UnsealWithImplicitKey(0));
if (status.is_error()) {
fprintf(stderr, "Failed to unseal zxcrypt volume: %s\n", status.status_string());
return status.take_error();
}
auto topological_path = storage::GetTopologicalPath(device_path);
if (topological_path.is_error()) {
fprintf(stderr, "Failed to get topological path for %s: %s\n", device_path.c_str(),
topological_path.status_string());
return topological_path.take_error();
}
return zx::ok(topological_path.value() + "/zxcrypt/unsealed/block");
}
} // namespace storage_benchmark