| // 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/virtualization/bin/termina_guest_manager/block_devices.h" |
| |
| #include <dirent.h> |
| #include <fcntl.h> |
| #include <fidl/fuchsia.fxfs/cpp/wire.h> |
| #include <fidl/fuchsia.io/cpp/wire.h> |
| #include <lib/component/incoming/cpp/protocol.h> |
| #include <lib/fdio/cpp/caller.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/fdio/namespace.h> |
| #include <lib/fit/defer.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/trace/event.h> |
| #include <zircon/hw/gpt.h> |
| |
| #include <filesystem> |
| |
| #include <fbl/unique_fd.h> |
| |
| #include "src/lib/fxl/strings/string_printf.h" |
| |
| namespace { |
| |
| namespace fio = fuchsia_io; |
| |
| // Information about a disk image. |
| struct DiskImage { |
| const char* path; // Path to the file containing the image |
| bool read_only; |
| bool create_file; |
| }; |
| |
| #if defined(USE_VOLATILE_BLOCK) |
| constexpr bool kForceVolatileWrites = true; |
| #else |
| constexpr bool kForceVolatileWrites = false; |
| #endif |
| |
| constexpr DiskImage kBlockFileStatefulImage = DiskImage{ |
| // NOTE: This assumes the /data directory is using Fxfs |
| .path = "/data/fxfs_virtualization_guest_image", |
| .read_only = false, |
| .create_file = true, |
| }; |
| constexpr DiskImage kFileStatefulImage = DiskImage{ |
| .path = "/data/fxfs_virtualization_guest_image", |
| .read_only = false, |
| .create_file = true, |
| }; |
| |
| constexpr DiskImage kExtrasImage = DiskImage{ |
| .path = "/pkg/data/termina_extras.img", |
| .read_only = true, |
| .create_file = false, |
| }; |
| |
| // Opens the given disk image. |
| zx::result<fuchsia::io::FileHandle> GetPartition(const DiskImage& image) { |
| TRACE_DURATION("termina_guest_manager", "GetPartition"); |
| fuchsia::io::Flags flags = fuchsia::io::PERM_READABLE; |
| if (!image.read_only) { |
| flags |= fuchsia::io::PERM_WRITABLE; |
| } |
| if (image.create_file) { |
| flags |= fuchsia::io::Flags::FLAG_MUST_CREATE; |
| } |
| fuchsia::io::FileHandle file; |
| zx_status_t status = fdio_open3(image.path, static_cast<uint64_t>(flags), |
| file.NewRequest().TakeChannel().release()); |
| if (status) { |
| return zx::error(status); |
| } |
| return zx::ok(std::move(file)); |
| } |
| |
| // Opens the given disk image. |
| zx::result<fidl::InterfaceHandle<fuchsia::hardware::block::Block>> GetFxfsPartition( |
| const DiskImage& image, const size_t image_size_bytes) { |
| TRACE_DURATION("linux_runner", "GetFxfsPartition"); |
| |
| // First, use regular file operations to make a huge file at image.path |
| // NOTE: image.path is assumed to be a path on an Fxfs filesystem |
| fbl::unique_fd fd(open(image.path, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR)); |
| if (!fd) { |
| FX_LOGS(ERROR) << "open(image.path) failed with errno: " << strerror(errno); |
| return zx::error(ZX_ERR_IO); |
| } |
| |
| // Make sure the file is the requested size (image_size_bytes). |
| // NOTE: This is usually a huge size (e.g. 40 gigabytes). |
| off_t existingFilesize = lseek(fd.get(), 0, SEEK_END); |
| if (existingFilesize == static_cast<off_t>(-1) || |
| static_cast<size_t>(existingFilesize) < image_size_bytes) { |
| if (ftruncate(fd.get(), image_size_bytes) == -1) { |
| FX_LOGS(ERROR) << "ftruncate(image.path) failed with errno: " << strerror(errno); |
| return zx::error(ZX_ERR_IO); |
| } |
| } |
| if (close(fd.release()) == -1) { |
| FX_LOGS(ERROR) << "close(image.path) failed with errno: " << strerror(errno); |
| return zx::error(ZX_ERR_IO); |
| } |
| |
| /// Now we can try to reopen the file, but in block device mode |
| |
| /// First we have to open the parent directory and get a token... |
| auto [dir_client, dir_server] = fidl::Endpoints<fuchsia_io::Directory>::Create(); |
| std::filesystem::path image_path(image.path); |
| uint64_t dir_flags = static_cast<uint64_t>(fio::wire::kPermReadable | fio::wire::kPermWritable | |
| fio::Flags::kProtocolDirectory); |
| zx_status_t dir_open_status = |
| fdio_open3(image_path.parent_path().c_str(), dir_flags, dir_server.TakeChannel().release()); |
| if (dir_open_status != ZX_OK) { |
| FX_PLOGS(ERROR, dir_open_status) << "fdio_open3(Fxfs image.path.parent) failed"; |
| return zx::error(dir_open_status); |
| } |
| fidl::WireResult token = fidl::WireCall(dir_client)->GetToken(); |
| if (!token.ok()) { |
| FX_LOGS(ERROR) << "Failed to GetToken" << token.FormatDescription(); |
| return zx::error(token.error().status()); |
| } |
| |
| /// Then, we can open the file in block mode. |
| auto [device_client, device_server] = |
| fidl::Endpoints<fuchsia_hardware_block_volume::Volume>::Create(); |
| auto [provider_client, provider_server] = |
| fidl::Endpoints<fuchsia_fxfs::FileBackedVolumeProvider>::Create(); |
| if (zx::result result = component::Connect(std::move(provider_server)); result.is_error()) { |
| return result.take_error(); |
| } |
| if (fidl::OneWayStatus status = |
| fidl::WireCall(provider_client) |
| ->Open(std::move(token->token), |
| fidl::StringView::FromExternal(image_path.filename().c_str()), |
| std::move(device_server)); |
| !status.ok()) { |
| FX_LOGS(ERROR) << "FileBackedVolumeProvider::Open failed: " << status.FormatDescription(); |
| return zx::error(status.status()); |
| } |
| |
| if (fidl::WireResult info = fidl::WireCall(device_client)->GetInfo(); |
| !info.ok() || info->is_error()) { |
| FX_LOGS(ERROR) << "Failed to GetInfo on block device: " << info.FormatDescription(); |
| return zx::error(info->error_value()); |
| } |
| |
| return zx::ok(fuchsia::hardware::block::BlockHandle(device_client.TakeChannel())); |
| } |
| |
| } // namespace |
| |
| fit::result<std::string, std::vector<fuchsia::virtualization::BlockSpec>> GetBlockDevices( |
| const termina_config::Config& structured_config, size_t min_size) { |
| TRACE_DURATION("termina_guest_manager", "Guest::GetBlockDevices"); |
| |
| std::vector<fuchsia::virtualization::BlockSpec> devices; |
| |
| const uint64_t stateful_image_size_bytes = structured_config.stateful_partition_size(); |
| |
| // Get/create the stateful partition. |
| fuchsia::virtualization::BlockSpec stateful_spec; |
| stateful_spec.id = "stateful"; |
| FX_LOGS(INFO) << "Adding stateful partition type: " |
| << structured_config.stateful_partition_type(); |
| if (structured_config.stateful_partition_type() == "block-file") { |
| // Use a file opened with OpenFlags.BLOCK_DEVICE. |
| auto handle = GetFxfsPartition(kBlockFileStatefulImage, stateful_image_size_bytes); |
| if (handle.is_error()) { |
| return fit::error( |
| fxl::StringPrintf("Failed to open or create stateful Fxfs file / block device: %s", |
| zx_status_get_string(handle.error_value()))); |
| } |
| stateful_spec.mode = fuchsia::virtualization::BlockMode::READ_WRITE; |
| stateful_spec.format.set_block(std::move(handle.value())); |
| } else if (structured_config.stateful_partition_type() == "file") { |
| // Simple files. |
| auto handle = GetPartition(kFileStatefulImage); |
| if (handle.is_error()) { |
| return fit::error(fxl::StringPrintf("Failed to open or create stateful file: %s", |
| zx_status_get_string(handle.error_value()))); |
| } |
| |
| auto ptr = handle->BindSync(); |
| fuchsia::io::File_Resize_Result resize_result; |
| zx_status_t status = ptr->Resize(stateful_image_size_bytes, &resize_result); |
| if (status != ZX_OK || resize_result.is_err()) { |
| return fit::error(fxl::StringPrintf("Failed resize stateful file: %s/%s", |
| zx_status_get_string(status), |
| zx_status_get_string(resize_result.err()))); |
| } |
| |
| stateful_spec.mode = fuchsia::virtualization::BlockMode::READ_WRITE; |
| stateful_spec.format.set_file(ptr.Unbind()); |
| } else { |
| return fit::error(fxl::StringPrintf("Unsupported partition type: %s", |
| structured_config.stateful_partition_type().c_str())); |
| } |
| if (kForceVolatileWrites) { |
| stateful_spec.mode = fuchsia::virtualization::BlockMode::VOLATILE_WRITE; |
| } |
| devices.push_back(std::move(stateful_spec)); |
| |
| // Add the extras partition if it exists. |
| auto extras = GetPartition(kExtrasImage); |
| if (extras.is_ok()) { |
| devices.push_back({ |
| .id = "extras", |
| .mode = fuchsia::virtualization::BlockMode::VOLATILE_WRITE, |
| .format = fuchsia::virtualization::BlockFormat::WithFile(std::move(extras.value())), |
| }); |
| } |
| |
| return fit::success(std::move(devices)); |
| } |
| |
| void DropDevNamespace() { |
| // Drop access to /dev, in order to prevent any further access. |
| fdio_ns_t* ns; |
| zx_status_t status = fdio_ns_get_installed(&ns); |
| FX_CHECK(status == ZX_OK) << "Failed to get installed namespace"; |
| if (fdio_ns_is_bound(ns, "/dev")) { |
| status = fdio_ns_unbind(ns, "/dev"); |
| FX_CHECK(status == ZX_OK) << "Failed to unbind '/dev' from the installed namespace"; |
| } |
| } |