diff --git a/zircon/system/core/devmgr/fshost/BUILD.gn b/zircon/system/core/devmgr/fshost/BUILD.gn index eb5a0e03e274c8d2995f97c19c58f6336359ccd9..b766170bfe284e1ef9748200fa0b9b24d80a94c9 100644 --- a/zircon/system/core/devmgr/fshost/BUILD.gn +++ b/zircon/system/core/devmgr/fshost/BUILD.gn @@ -2,7 +2,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -source_set("common") { +source_set("fshost-registry") { sources = [ "fs-manager.cpp", "registry.cpp", @@ -28,20 +28,28 @@ source_set("common") { ] } -executable("fshost") { +source_set("block-watcher") { sources = [ + "block-device.cpp", "block-watcher.cpp", - "main.cpp", + "filesystem-mounter.cpp", "pkgfs-launcher.cpp", ] + public_deps = [ + "$zx/system/fidl/fuchsia-fshost:c", + "$zx/system/ulib/async-loop:async-loop-cpp", + "$zx/system/ulib/fit", + "$zx/system/ulib/fs", + "$zx/system/ulib/memfs", + "$zx/system/ulib/zx", + "../shared", + ] deps = [ - ":common", + ":fshost-registry", "$zx/system/fidl/fuchsia-device:c", "$zx/system/fidl/fuchsia-hardware-block:c", "$zx/system/fidl/fuchsia-hardware-block-partition:c", "$zx/system/fidl/fuchsia-hardware-zxcrypt:c", - "$zx/system/ulib/async-loop:async-loop-cpp", - "$zx/system/ulib/bootdata", "$zx/system/ulib/fbl", "$zx/system/ulib/fdio", "$zx/system/ulib/fit", @@ -50,15 +58,27 @@ executable("fshost") { "$zx/system/ulib/gpt", "$zx/system/ulib/loader-service", "$zx/system/ulib/minfs", - "$zx/system/ulib/ramdevice-client", "$zx/system/ulib/trace", "$zx/system/ulib/trace-engine", "$zx/system/ulib/zircon", "$zx/system/ulib/zx", "$zx/system/ulib/zxcpp", "$zx/system/ulib/zxcrypt", - "$zx/third_party/ulib/cksum", - "$zx/third_party/ulib/lz4", + ] +} + + +executable("fshost") { + sources = [ + "main.cpp", + ] + deps = [ + ":block-watcher", + ":fshost-registry", + "$zx/system/ulib/bootdata", + "$zx/system/ulib/fs-management", + "$zx/system/ulib/loader-service", + "$zx/system/ulib/ramdevice-client", ] } @@ -68,7 +88,21 @@ test("fshost-test") { "fshost-test.cpp", ] deps = [ - ":common", + ":fshost-registry", + "$zx/system/ulib/zxtest", + ] +} + +test("block-watcher-test") { + test_group = "ddk" + sources = [ + "block-device-test.cpp", + "block-watcher-test.cpp", + ] + deps = [ + ":block-watcher", + "$zx/system/ulib/fs-management", + "$zx/system/ulib/ramdevice-client", "$zx/system/ulib/zxtest", ] } diff --git a/zircon/system/core/devmgr/fshost/block-device-interface.h b/zircon/system/core/devmgr/fshost/block-device-interface.h new file mode 100644 index 0000000000000000000000000000000000000000..f774fd681fc5796dac7e698d497cd6679f566bc4 --- /dev/null +++ b/zircon/system/core/devmgr/fshost/block-device-interface.h @@ -0,0 +1,72 @@ +// 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. + +#pragma once + +#include <memory> + +#include <fs-management/mount.h> +#include <fuchsia/hardware/block/c/fidl.h> +#include <fuchsia/hardware/block/partition/c/fidl.h> +#include <lib/zx/channel.h> +#include <zircon/types.h> + +namespace devmgr { + +constexpr char kFVMDriverPath[] = "/boot/driver/fvm.so"; +constexpr char kGPTDriverPath[] = "/boot/driver/gpt.so"; +constexpr char kMBRDriverPath[] = "/boot/driver/mbr.so"; +constexpr char kBootpartDriverPath[] = "/boot/driver/bootpart.so"; + +// An abstract class representing the operations which may be performed +// on a block device, from the perspective of fshost. +class BlockDeviceInterface { +public: + virtual ~BlockDeviceInterface() = default; + + zx_status_t Add(); + +private: + // Returns the expected on-disk format of the underlying device. + // + // If unknown or unreadable, DISK_FORMAT_UNKNOWN should be returned. + virtual disk_format_t GetFormat() = 0; + + // Modifies the expected on-disk format of the underlying device. + // + // This may be useful if the block device data was corrupted, and we want + // to force a new format based on external information. + virtual void SetFormat(disk_format_t format) = 0; + + // Returns "true" if the device is booted from in-memory partitions, + // and expects that filesystems and encrypted partitions will not be + // automatically mounted. + virtual bool Netbooting() = 0; + + // Queries (using the block interface) for info about the underlying device. + virtual zx_status_t GetInfo(fuchsia_hardware_block_BlockInfo* out_info) = 0; + + // Queries (using the partition interface) for the GUID of the underlying device. + virtual zx_status_t GetTypeGUID(fuchsia_hardware_block_partition_GUID* out_guid) = 0; + + // Attempts to directly bind a driver to the device. This is typically used + // by partition drivers, which may be loaded on top of a device exposing the + // block interface. + virtual zx_status_t AttachDriver(const fbl::StringPiece& driver) = 0; + + // Unseals the underlying zxcrypt volume. + virtual zx_status_t UnsealZxcrypt() = 0; + + // Validates the state of the filesystem, and returns ZX_OK if it appears + // consistent (or if the consistency check should be skipped). + virtual zx_status_t CheckFilesystem() = 0; + + // Reformats the underlying block device with the format returned by |GetFormat()|. + virtual zx_status_t FormatFilesystem() = 0; + + // Attempts to mount the filesystem with the format returned by |GetFormat()|. + virtual zx_status_t MountFilesystem() = 0; +}; + +} // namespace devmgr diff --git a/zircon/system/core/devmgr/fshost/block-device-test.cpp b/zircon/system/core/devmgr/fshost/block-device-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..641ef04590fbb3d4ed0a794ba2c2aed2e5f52e9f --- /dev/null +++ b/zircon/system/core/devmgr/fshost/block-device-test.cpp @@ -0,0 +1,180 @@ +// 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 <fcntl.h> + +#include <lib/fdio/namespace.h> +#include <ramdevice-client/ramdisk.h> +#include <zircon/assert.h> +#include <zircon/hw/gpt.h> +#include <zxtest/zxtest.h> + +#include "block-device.h" +#include "filesystem-mounter.h" +#include "fs-manager.h" + +namespace devmgr { +namespace { + +class BlockDeviceHarness : public zxtest::Test { +public: + void SetUp() override { + zx::event event; + ASSERT_OK(zx::event::create(0, &event)); + ASSERT_OK(event.duplicate(ZX_RIGHT_SAME_RIGHTS, &event_)); + + // Initialize FilesystemMounter. + ASSERT_OK(FsManager::Create(std::move(event), &manager_)); + + // Fshost really likes mounting filesystems at "/fs". + // Let's make that available in our namespace. + zx::channel client, server; + ASSERT_OK(zx::channel::create(0, &client, &server)); + ASSERT_OK(manager_->ServeRoot(std::move(server))); + fdio_ns_t* ns; + ASSERT_OK(fdio_ns_get_installed(&ns)); + ASSERT_OK(fdio_ns_bind(ns, "/fs", client.release())); + manager_->WatchExit(); + } + + void TearDown() override { + fdio_ns_t* ns; + ASSERT_OK(fdio_ns_get_installed(&ns)); + fdio_ns_unbind(ns, "/fs"); + } + + std::unique_ptr<FsManager> TakeManager() { + return std::move(manager_); + } + +private: + zx::event event_; + std::unique_ptr<FsManager> manager_; +}; + +TEST_F(BlockDeviceHarness, TestBadHandleDevice) { + std::unique_ptr<FsManager> manager = TakeManager(); + bool netboot = false; + FilesystemMounter mounter(std::move(manager), netboot); + fbl::unique_fd fd; + BlockDevice device(&mounter, std::move(fd)); + EXPECT_EQ(device.Netbooting(), netboot); + EXPECT_EQ(device.GetFormat(), DISK_FORMAT_UNKNOWN); + fuchsia_hardware_block_BlockInfo info; + EXPECT_EQ(device.GetInfo(&info), ZX_ERR_BAD_HANDLE); + fuchsia_hardware_block_partition_GUID guid; + EXPECT_EQ(device.GetTypeGUID(&guid), ZX_ERR_BAD_HANDLE); + EXPECT_EQ(device.AttachDriver("/foobar"), ZX_ERR_BAD_HANDLE); + + // Returns ZX_OK because zxcrypt currently passes the empty fd to a background + // thread without observing the results. + EXPECT_OK(device.UnsealZxcrypt()); + + // Returns ZX_OK because filesystem checks are only enabled via environment variables. + EXPECT_OK(device.CheckFilesystem()); + + EXPECT_EQ(device.FormatFilesystem(), ZX_ERR_BAD_HANDLE); + EXPECT_EQ(device.MountFilesystem(), ZX_ERR_BAD_HANDLE); +} + +TEST_F(BlockDeviceHarness, TestEmptyDevice) { + std::unique_ptr<FsManager> manager = TakeManager(); + bool netboot = false; + FilesystemMounter mounter(std::move(manager), netboot); + + // Initialize Ramdisk. + constexpr uint64_t kBlockSize = 512; + constexpr uint64_t kBlockCount = 1 << 20; + ramdisk_client_t* ramdisk; + ASSERT_OK(ramdisk_create(kBlockSize, kBlockCount, &ramdisk)); + ASSERT_OK(wait_for_device(ramdisk_get_path(ramdisk), zx::sec(5).get())); + fbl::unique_fd fd(open(ramdisk_get_path(ramdisk), O_RDWR)); + ASSERT_TRUE(fd); + + BlockDevice device(&mounter, std::move(fd)); + EXPECT_EQ(device.Netbooting(), netboot); + EXPECT_EQ(device.GetFormat(), DISK_FORMAT_UNKNOWN); + fuchsia_hardware_block_BlockInfo info; + EXPECT_OK(device.GetInfo(&info)); + EXPECT_EQ(info.block_count, kBlockCount); + EXPECT_EQ(info.block_size, kBlockSize); + + // Black-box: Since we're caching info, double check that re-calling GetInfo + // works correctly. + memset(&info, 0, sizeof(info)); + EXPECT_OK(device.GetInfo(&info)); + EXPECT_EQ(info.block_count, kBlockCount); + EXPECT_EQ(info.block_size, kBlockSize); + + fuchsia_hardware_block_partition_GUID guid; + EXPECT_OK(device.GetTypeGUID(&guid)); + + EXPECT_EQ(device.FormatFilesystem(), ZX_ERR_NOT_SUPPORTED); + EXPECT_EQ(device.MountFilesystem(), ZX_ERR_NOT_SUPPORTED); + ASSERT_OK(ramdisk_destroy(ramdisk)); +} + +TEST_F(BlockDeviceHarness, TestMinfsBadGUID) { + std::unique_ptr<FsManager> manager = TakeManager(); + bool netboot = false; + FilesystemMounter mounter(std::move(manager), netboot); + + // Initialize Ramdisk with an empty GUID. + constexpr uint64_t kBlockSize = 512; + constexpr uint64_t kBlockCount = 1 << 20; + ramdisk_client_t* ramdisk; + ASSERT_OK(ramdisk_create(kBlockSize, kBlockCount, &ramdisk)); + ASSERT_OK(wait_for_device(ramdisk_get_path(ramdisk), zx::sec(5).get())); + fbl::unique_fd fd(open(ramdisk_get_path(ramdisk), O_RDWR)); + ASSERT_TRUE(fd); + + // We started with an empty block device, but let's lie and say it + // should have been a minfs device. + BlockDevice device(&mounter, std::move(fd)); + device.SetFormat(DISK_FORMAT_MINFS); + EXPECT_EQ(device.GetFormat(), DISK_FORMAT_MINFS); + EXPECT_OK(device.FormatFilesystem()); + + // Unlike earlier, where we received "ERR_NOT_SUPPORTED", we get "ERR_WRONG_TYPE" + // because the ramdisk doesn't have a data GUID. + EXPECT_EQ(device.MountFilesystem(), ZX_ERR_WRONG_TYPE); + + ASSERT_OK(ramdisk_destroy(ramdisk)); +} + +TEST_F(BlockDeviceHarness, TestMinfsGoodGUID) { + std::unique_ptr<FsManager> manager = TakeManager(); + + bool netboot = false; + FilesystemMounter mounter(std::move(manager), netboot); + + // Initialize Ramdisk with a data GUID. + constexpr uint64_t kBlockSize = 512; + constexpr uint64_t kBlockCount = 1 << 20; + ramdisk_client_t* ramdisk; + const uint8_t data_guid[GPT_GUID_LEN] = GUID_DATA_VALUE; + ASSERT_OK(ramdisk_create_with_guid(kBlockSize, kBlockCount, data_guid, sizeof(data_guid), + &ramdisk)); + ASSERT_OK(wait_for_device(ramdisk_get_path(ramdisk), zx::sec(5).get())); + fbl::unique_fd fd(open(ramdisk_get_path(ramdisk), O_RDWR)); + ASSERT_TRUE(fd); + + BlockDevice device(&mounter, std::move(fd)); + device.SetFormat(DISK_FORMAT_MINFS); + EXPECT_EQ(device.GetFormat(), DISK_FORMAT_MINFS); + EXPECT_OK(device.FormatFilesystem()); + + EXPECT_OK(device.MountFilesystem()); + EXPECT_EQ(device.MountFilesystem(), ZX_ERR_ALREADY_BOUND); + + ASSERT_OK(ramdisk_destroy(ramdisk)); +} + +// TODO(smklein): Plumb through the "check filesystem" decision using +// something other than environment variables, and add a test for it. + +// TODO: Add tests for Zxcrypt binding. + +} // namespace +} // namespace devmgr diff --git a/zircon/system/core/devmgr/fshost/block-device.cpp b/zircon/system/core/devmgr/fshost/block-device.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e4388d73824c1772194a379f2dccc455965dfcf9 --- /dev/null +++ b/zircon/system/core/devmgr/fshost/block-device.cpp @@ -0,0 +1,441 @@ +// 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 <fcntl.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <fbl/algorithm.h> +#include <fbl/auto_call.h> +#include <fbl/string_buffer.h> +#include <fbl/unique_fd.h> +#include <fs-management/mount.h> +#include <fuchsia/device/c/fidl.h> +#include <fuchsia/hardware/block/c/fidl.h> +#include <fuchsia/hardware/block/partition/c/fidl.h> +#include <gpt/gpt.h> +#include <lib/fdio/directory.h> +#include <lib/fdio/fd.h> +#include <lib/fdio/fdio.h> +#include <lib/fdio/unsafe.h> +#include <lib/fdio/watcher.h> +#include <lib/fzl/fdio.h> +#include <lib/fzl/time.h> +#include <lib/zx/channel.h> +#include <lib/zx/time.h> +#include <loader-service/loader-service.h> +#include <minfs/fsck.h> +#include <minfs/minfs.h> +#include <zircon/device/block.h> +#include <zircon/processargs.h> +#include <zircon/status.h> +#include <zircon/syscalls.h> +#include <zxcrypt/fdio-volume.h> + +#include <utility> + +#include "block-device.h" +#include "block-watcher.h" +#include "pkgfs-launcher.h" + +namespace devmgr { +namespace { + +// Attempt to mount the device pointed to be the file descriptor at a known +// location. +// +// Returns ZX_ERR_ALREADY_BOUND if the device could be mounted, but something +// is already mounted at that location. Returns ZX_ERR_INVALID_ARGS if the +// GUID of the device does not match a known valid one. Returns +// ZX_ERR_NOT_SUPPORTED if the GUID is a system GUID. Returns ZX_OK if an +// attempt to mount is made, without checking mount success. +zx_status_t MountMinfs(FilesystemMounter* mounter, fbl::unique_fd fd, mount_options_t* options) { + fuchsia_hardware_block_partition_GUID type_guid; + { + fzl::UnownedFdioCaller disk_connection(fd.get()); + zx::unowned_channel channel(disk_connection.borrow_channel()); + zx_status_t io_status, status; + io_status = fuchsia_hardware_block_partition_PartitionGetTypeGuid(channel->get(), &status, + &type_guid); + if (io_status != ZX_OK) + return io_status; + if (status != ZX_OK) + return status; + } + + if (gpt_is_sys_guid(type_guid.value, GPT_GUID_LEN)) { + return ZX_ERR_NOT_SUPPORTED; + } else if (gpt_is_data_guid(type_guid.value, GPT_GUID_LEN)) { + return mounter->MountData(std::move(fd), options); + } else if (gpt_is_install_guid(type_guid.value, GPT_GUID_LEN)) { + return mounter->MountInstall(std::move(fd), options); + } + printf("fshost: Unrecognized partition GUID for minfs; not mounting\n"); + return ZX_ERR_WRONG_TYPE; +} + +// return value is ignored +int UnsealZxcryptThread(void* arg) { + std::unique_ptr<int> fd_ptr(static_cast<int*>(arg)); + fbl::unique_fd fd(*fd_ptr); + + zx_status_t rc; + std::unique_ptr<zxcrypt::FdioVolume> zxcrypt_volume; + if ((rc = zxcrypt::FdioVolume::Init(std::move(fd), &zxcrypt_volume)) != ZX_OK) { + printf("fshost: couldn't open zxcrypt fdio volume"); + return ZX_OK; + } + + zx::channel zxcrypt_volume_manager_chan; + if ((rc = zxcrypt_volume->OpenManager(zx::sec(2), + zxcrypt_volume_manager_chan.reset_and_get_address())) != + ZX_OK) { + printf("fshost: couldn't open zxcrypt manager device"); + return 0; + } + + zxcrypt::FdioVolumeManager zxcrypt_volume_manager(std::move(zxcrypt_volume_manager_chan)); + uint8_t slot = 0; + if ((rc = zxcrypt_volume_manager.UnsealWithDeviceKey(slot)) != ZX_OK) { + printf("fshost: couldn't unseal zxcrypt manager device"); + return 0; + } + + return 0; +} + +zx_status_t FormatMinfs(const fbl::unique_fd& block_device, + const fuchsia_hardware_block_BlockInfo& info) { + fprintf(stderr, "fshost: Formatting minfs.\n"); + uint64_t device_size = info.block_size * info.block_count / minfs::kMinfsBlockSize; + std::unique_ptr<minfs::Bcache> bc; + zx_status_t status; + if ((status = minfs::Bcache::Create(&bc, block_device.duplicate(), + static_cast<uint32_t>(device_size))) != ZX_OK) { + fprintf(stderr, "fshost: Could not initialize minfs bcache.\n"); + return status; + } + minfs::MountOptions options = {}; + if ((status = Mkfs(options, std::move(bc))) != ZX_OK) { + fprintf(stderr, "fshost: Could not format minfs filesystem.\n"); + return status; + } + printf("fshost: Minfs filesystem re-formatted. Expect data loss.\n"); + return ZX_OK; +} + +} // namespace + +BlockDevice::BlockDevice(FilesystemMounter* mounter, fbl::unique_fd fd) + : mounter_(mounter), fd_(std::move(fd)), + format_(detect_disk_format(fd_.get())) {} + +disk_format_t BlockDevice::GetFormat() { + return format_; +} + +void BlockDevice::SetFormat(disk_format_t format) { + format_ = format; +} + +bool BlockDevice::Netbooting() { + return mounter_->Netbooting(); +} + +zx_status_t BlockDevice::GetInfo(fuchsia_hardware_block_BlockInfo* out_info) { + if (info_.has_value()) { + memcpy(out_info, &*info_, sizeof(*out_info)); + return ZX_OK; + } + fzl::UnownedFdioCaller connection(fd_.get()); + zx_status_t io_status, call_status; + io_status = fuchsia_hardware_block_BlockGetInfo(connection.borrow_channel(), &call_status, + out_info); + if (io_status != ZX_OK) { + return io_status; + } + info_ = *out_info; + return call_status; +} + +zx_status_t BlockDevice::GetTypeGUID(fuchsia_hardware_block_partition_GUID* out_guid) { + fzl::UnownedFdioCaller connection(fd_.get()); + zx_status_t io_status, call_status; + io_status = fuchsia_hardware_block_partition_PartitionGetTypeGuid(connection.borrow_channel(), + &call_status, + out_guid); + if (io_status != ZX_OK) { + return io_status; + } + return call_status; +} + +zx_status_t BlockDevice::AttachDriver(const fbl::StringPiece& driver) { + printf("fshost: Binding: %.*s\n", static_cast<int>(driver.length()), driver.data()); + fzl::UnownedFdioCaller connection(fd_.get()); + zx_status_t io_status, call_status; + io_status = fuchsia_device_ControllerBind(connection.borrow_channel(), driver.data(), + driver.length(), &call_status); + if (io_status != ZX_OK) { + return io_status; + } + return call_status; +} + +zx_status_t BlockDevice::UnsealZxcrypt() { + printf("fshost: unsealing zxcrypt\n"); + // Bind and unseal the driver from a separate thread, since we + // have to wait for a number of devices to do I/O and settle, + // and we don't want to block block-watcher for any nontrivial + // length of time. + + // We transfer fd to the spawned thread. Since it's UB to cast + // ints to pointers and back, we allocate the fd on the heap. + int loose_fd = fd_.release(); + int* raw_fd_ptr = new int(loose_fd); + thrd_t th; + int err = thrd_create_with_name(&th, &UnsealZxcryptThread, raw_fd_ptr, "zxcrypt-unseal"); + if (err != thrd_success) { + printf("fshost: failed to spawn zxcrypt unseal thread"); + close(loose_fd); + delete raw_fd_ptr; + } else { + thrd_detach(th); + } + return ZX_OK; +} + +zx_status_t BlockDevice::CheckFilesystem() { + if (!getenv_bool("zircon.system.filesystem-check", false)) { + return ZX_OK; + } + + zx_status_t status; + fuchsia_hardware_block_BlockInfo info; + if ((status = GetInfo(&info)) != ZX_OK) { + return status; + } + + switch (format_) { + case DISK_FORMAT_BLOBFS: { + fprintf(stderr, "fshost: Skipping blobfs consistency checker.\n"); + return ZX_OK; + } + case DISK_FORMAT_MINFS: { + zx::ticks before = zx::ticks::now(); + auto timer = fbl::MakeAutoCall([before]() { + auto after = zx::ticks::now(); + auto duration = fzl::TicksToNs(after - before); + printf("fshost: fsck took %" PRId64 ".%" PRId64 " seconds\n", duration.to_secs(), + duration.to_msecs() % 1000); + }); + printf("fshost: fsck of %s started\n", disk_format_string_[format_]); + uint64_t device_size = info.block_size * info.block_count / minfs::kMinfsBlockSize; + std::unique_ptr<minfs::Bcache> bc; + zx_status_t status; + if ((status = minfs::Bcache::Create(&bc, fd_.duplicate(), + static_cast<uint32_t>(device_size))) != ZX_OK) { + fprintf(stderr, "fshost: Could not initialize minfs bcache.\n"); + return status; + } + status = minfs::Fsck(std::move(bc)); + + if (status != ZX_OK) { + fprintf(stderr, "--------------------------------------------------------------\n"); + fprintf(stderr, "| \n"); + fprintf(stderr, "| WARNING: fshost fsck failure! \n"); + fprintf(stderr, "| Corrupt %s filesystem\n", disk_format_string_[format_]); + fprintf(stderr, "| \n"); + fprintf(stderr, "| If your system encountered power-loss due to an unclean \n"); + fprintf(stderr, "| shutdown, this error was expected. Journaling in minfs \n"); + fprintf(stderr, "| is being tracked by ZX-2093. Re-paving will reset your \n"); + fprintf(stderr, "| device. \n"); + fprintf(stderr, "| \n"); + fprintf(stderr, "| If your system was shutdown cleanly (via 'dm poweroff' \n"); + fprintf(stderr, "| or an OTA), report this device to the local-storage \n"); + fprintf(stderr, "| team. Please file bugs with logs before and after reboot. \n"); + fprintf(stderr, "| Please use the 'filesystem' and 'minfs' component tag. \n"); + fprintf(stderr, "| \n"); + fprintf(stderr, "--------------------------------------------------------------\n"); + } else { + printf("fshost: fsck of %s completed OK\n", disk_format_string_[format_]); + } + return status; + } + default: + fprintf(stderr, "fshost: Not checking unknown filesystem\n"); + return ZX_ERR_NOT_SUPPORTED; + } +} + +zx_status_t BlockDevice::FormatFilesystem() { + zx_status_t status; + fuchsia_hardware_block_BlockInfo info; + if ((status = GetInfo(&info)) != ZX_OK) { + return status; + } + + switch (format_) { + case DISK_FORMAT_BLOBFS: { + fprintf(stderr, "fshost: Not formatting blobfs.\n"); + return ZX_ERR_NOT_SUPPORTED; + } + case DISK_FORMAT_MINFS: { + fprintf(stderr, "fshost: Formatting minfs.\n"); + uint64_t blocks = info.block_size * info.block_count / minfs::kMinfsBlockSize; + std::unique_ptr<minfs::Bcache> bc; + zx_status_t status; + if ((status = minfs::Bcache::Create(&bc, fd_.duplicate(), + static_cast<uint32_t>(blocks))) != ZX_OK) { + fprintf(stderr, "fshost: Could not initialize minfs bcache.\n"); + return status; + } + minfs::MountOptions options = {}; + if ((status = minfs::Mkfs(options, std::move(bc))) != ZX_OK) { + fprintf(stderr, "fshost: Could not format minfs filesystem.\n"); + return status; + } + printf("fshost: Minfs filesystem re-formatted. Expect data loss.\n"); + return ZX_OK; + } + default: + fprintf(stderr, "fshost: Not formatting unknown filesystem.\n"); + return ZX_ERR_NOT_SUPPORTED; + } +} + +zx_status_t BlockDevice::MountFilesystem() { + // Go through the song-and-dance of cloning our reference to |fd_| + // so we can hand off a cloned connection to the mount functions. + // The mount functions are very possessive of their fds, and don't like + // operating on dup-ed descriptors. + // + // In the future, this could be simplified by passing channels directly, + // and avoiding file descriptors altogether. + fbl::unique_fd cloned_fd; + { + fzl::UnownedFdioCaller disk_connection(fd_.get()); + zx::unowned_channel channel(disk_connection.borrow_channel()); + zx::channel cloned_channel(fdio_service_clone(channel->get())); + fdio_t* io; + zx_status_t status = fdio_create(cloned_channel.release(), &io); + if (status != ZX_OK) { + return status; + } + cloned_fd.reset(fdio_bind_to_fd(io, -1, 0)); + if (!cloned_fd) { + return ZX_ERR_BAD_STATE; + } + } + + switch (format_) { + case DISK_FORMAT_BLOBFS: { + fprintf(stderr, "fshost: BlockDevice::MountFilesystem(blobfs)\n"); + mount_options_t options = default_mount_options; + options.enable_journal = true; + options.collect_metrics = true; + zx_status_t status = mounter_->MountBlob(std::move(cloned_fd), &options); + if (status != ZX_OK) { + printf("fshost: Failed to mount blobfs partition: %s.\n", + zx_status_get_string(status)); + return status; + } else { + LaunchBlobInit(mounter_); + } + return ZX_OK; + } + case DISK_FORMAT_MINFS: { + mount_options_t options = default_mount_options; + fprintf(stderr, "fshost: BlockDevice::MountFilesystem(minfs)\n"); + return MountMinfs(mounter_, std::move(cloned_fd), &options); + } + default: + fprintf(stderr, "fshost: BlockDevice::MountFilesystem(unknown)\n"); + return ZX_ERR_NOT_SUPPORTED; + } +} + +zx_status_t BlockDeviceInterface::Add() { + disk_format_t df = GetFormat(); + fuchsia_hardware_block_BlockInfo info; + zx_status_t status; + if ((status = GetInfo(&info)) != ZX_OK) { + return status; + } + + if (info.flags & BLOCK_FLAG_BOOTPART) { + return AttachDriver(kBootpartDriverPath); + } + + switch (df) { + case DISK_FORMAT_GPT: { + return AttachDriver(kGPTDriverPath); + } + case DISK_FORMAT_FVM: { + return AttachDriver(kFVMDriverPath); + } + case DISK_FORMAT_MBR: { + return AttachDriver(kMBRDriverPath); + } + case DISK_FORMAT_ZXCRYPT: { + if (!Netbooting()) { + return UnsealZxcrypt(); + } + return ZX_OK; + } + default: + break; + } + + fuchsia_hardware_block_partition_GUID guid; + if ((status = GetTypeGUID(&guid)) != ZX_OK) { + return status; + } + + // If we're in netbooting mode, then only bind drivers for partition + // containers and the install partition, not regular filesystems. + if (Netbooting()) { + if (gpt_is_install_guid(guid.value, GPT_GUID_LEN)) { + printf("fshost: mounting install partition\n"); + return MountFilesystem(); + } + return ZX_OK; + } + + switch (df) { + case DISK_FORMAT_BLOBFS: { + const uint8_t expected_guid[GPT_GUID_LEN] = GUID_BLOB_VALUE; + + if (memcmp(guid.value, expected_guid, GPT_GUID_LEN)) { + return ZX_ERR_INVALID_ARGS; + } + if ((status = CheckFilesystem()) != ZX_OK) { + return status; + } + + return MountFilesystem(); + } + case DISK_FORMAT_MINFS: { + printf("fshost: mounting minfs\n"); + if (CheckFilesystem() != ZX_OK) { + if ((status = FormatFilesystem()) != ZX_OK) { + return status; + } + } + status = MountFilesystem(); + if (status != ZX_OK) { + printf("fshost: failed to mount filesystem: %s\n", zx_status_get_string(status)); + return status; + } + return ZX_OK; + } + default: + return ZX_ERR_NOT_SUPPORTED; + } +} + +} // namespace devmgr diff --git a/zircon/system/core/devmgr/fshost/block-device.h b/zircon/system/core/devmgr/fshost/block-device.h new file mode 100644 index 0000000000000000000000000000000000000000..c12e9df57f31b4cad31db0aeaf8cebd53cd89cfa --- /dev/null +++ b/zircon/system/core/devmgr/fshost/block-device.h @@ -0,0 +1,44 @@ +// 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. + +#pragma once + +#include <memory> +#include <optional> + +#include <fs-management/mount.h> +#include <zircon/types.h> + +#include "block-device-interface.h" +#include "filesystem-mounter.h" + +namespace devmgr { + +// A concrete implementation of the block device interface. +// +// Used by fshost to attach either drivers or filesystems to +// incoming block devices. +class BlockDevice final : public BlockDeviceInterface { +public: + BlockDevice(FilesystemMounter* mounter, fbl::unique_fd fd); + + disk_format_t GetFormat() final; + void SetFormat(disk_format_t format) final; + bool Netbooting() final; + zx_status_t GetInfo(fuchsia_hardware_block_BlockInfo* out_info) final; + zx_status_t GetTypeGUID(fuchsia_hardware_block_partition_GUID* out_guid) final; + zx_status_t AttachDriver(const fbl::StringPiece& driver) final; + zx_status_t UnsealZxcrypt() final; + zx_status_t CheckFilesystem() final; + zx_status_t FormatFilesystem() final; + zx_status_t MountFilesystem() final; + +private: + FilesystemMounter* mounter_ = nullptr; + fbl::unique_fd fd_; + std::optional<fuchsia_hardware_block_BlockInfo> info_ = {}; + disk_format_t format_ = DISK_FORMAT_UNKNOWN; +}; + +} // namespace devmgr diff --git a/zircon/system/core/devmgr/fshost/block-watcher-test.cpp b/zircon/system/core/devmgr/fshost/block-watcher-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b2a6c475ff850dfdfa413c22fd342eccd5a14018 --- /dev/null +++ b/zircon/system/core/devmgr/fshost/block-watcher-test.cpp @@ -0,0 +1,261 @@ +// 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 <zircon/assert.h> +#include <zircon/hw/gpt.h> +#include <zxtest/zxtest.h> + +#include "block-device-interface.h" + +namespace { + +class MockBlockDevice : public devmgr::BlockDeviceInterface { +public: + disk_format_t GetFormat() override = 0; + void SetFormat(disk_format_t format) override { + ZX_PANIC("Test should not invoke function %s\n", __FUNCTION__); + } + bool Netbooting() override { return false; } + zx_status_t GetInfo(fuchsia_hardware_block_BlockInfo* out_info) override { + fuchsia_hardware_block_BlockInfo info = {}; + info.flags = 0; + info.block_size = 512; + info.block_count = 1024; + *out_info = info; + return ZX_OK; + } + zx_status_t GetTypeGUID(fuchsia_hardware_block_partition_GUID* out_guid) override { + ZX_PANIC("Test should not invoke function %s\n", __FUNCTION__); + } + zx_status_t AttachDriver(const fbl::StringPiece& driver) override { + ZX_PANIC("Test should not invoke function %s\n", __FUNCTION__); + } + zx_status_t UnsealZxcrypt() override { + ZX_PANIC("Test should not invoke function %s\n", __FUNCTION__); + } + zx_status_t CheckFilesystem() override { + ZX_PANIC("Test should not invoke function %s\n", __FUNCTION__); + } + zx_status_t FormatFilesystem() override { + ZX_PANIC("Test should not invoke function %s\n", __FUNCTION__); + } + zx_status_t MountFilesystem() override { + ZX_PANIC("Test should not invoke function %s\n", __FUNCTION__); + } +}; + +// Tests adding a device which has no GUID and an unknown format. +TEST(AddDeviceTestCase, AddUnknownDevice) { + class UnknownDevice : public MockBlockDevice { + public: + disk_format_t GetFormat() final { + return DISK_FORMAT_UNKNOWN; + } + zx_status_t GetTypeGUID(fuchsia_hardware_block_partition_GUID* out_guid) final { + return ZX_ERR_NOT_SUPPORTED; + } + }; + UnknownDevice device; + EXPECT_EQ(ZX_ERR_NOT_SUPPORTED, device.Add()); +} + +// Tests adding a device with an unknown GUID and unknown format. +TEST(AddDeviceTestCase, AddUnknownPartition) { + class UnknownDevice : public MockBlockDevice { + public: + disk_format_t GetFormat() final { + return DISK_FORMAT_UNKNOWN; + } + zx_status_t GetTypeGUID(fuchsia_hardware_block_partition_GUID* out_guid) final { + const uint8_t expected[GPT_GUID_LEN] = GUID_EMPTY_VALUE; + memcpy(out_guid->value, expected, sizeof(expected)); + return ZX_OK; + } + }; + UnknownDevice device; + EXPECT_EQ(ZX_ERR_NOT_SUPPORTED, device.Add()); +} + +// Tests adding a device with a GPT format. +TEST(AddDeviceTestCase, AddGPTDevice) { + class GptDevice : public MockBlockDevice { + public: + disk_format_t GetFormat() final { + return DISK_FORMAT_GPT; + } + zx_status_t AttachDriver(const fbl::StringPiece& driver) final { + EXPECT_STR_EQ(devmgr::kGPTDriverPath, driver.data()); + attached = true; + return ZX_OK; + } + bool attached = false; + }; + GptDevice device; + EXPECT_EQ(ZX_OK, device.Add()); + EXPECT_TRUE(device.attached); +} + +// Tests adding a device with an FVM format. +TEST(AddDeviceTestCase, AddFVMDevice) { + class FvmDevice : public MockBlockDevice { + public: + disk_format_t GetFormat() final { + return DISK_FORMAT_FVM; + } + zx_status_t AttachDriver(const fbl::StringPiece& driver) final { + EXPECT_STR_EQ(devmgr::kFVMDriverPath, driver.data()); + attached = true; + return ZX_OK; + } + bool attached = false; + }; + FvmDevice device; + EXPECT_EQ(ZX_OK, device.Add()); + EXPECT_TRUE(device.attached); +} + +// Tests adding a device with an MBR format. +TEST(AddDeviceTestCase, AddMBRDevice) { + class MbrDevice : public MockBlockDevice { + public: + disk_format_t GetFormat() final { + return DISK_FORMAT_MBR; + } + zx_status_t AttachDriver(const fbl::StringPiece& driver) final { + EXPECT_STR_EQ(devmgr::kMBRDriverPath, driver.data()); + attached = true; + return ZX_OK; + } + bool attached = false; + }; + MbrDevice device; + EXPECT_EQ(ZX_OK, device.Add()); + EXPECT_TRUE(device.attached); +} + +// Tests adding blobfs which does not not have a type GUID. +TEST(AddDeviceTestCase, AddNoGUIDBlobDevice) { + class BlobDevice : public MockBlockDevice { + public: + disk_format_t GetFormat() final { + return DISK_FORMAT_BLOBFS; + } + zx_status_t GetTypeGUID(fuchsia_hardware_block_partition_GUID* out_guid) final { + return ZX_ERR_NOT_SUPPORTED; + } + }; + BlobDevice device; + EXPECT_EQ(ZX_ERR_NOT_SUPPORTED, device.Add()); +} + +// Tests adding blobfs with a valid type GUID, but invalid metadata. +TEST(AddDeviceTestCase, AddInvalidBlobDevice) { + class BlobDevice : public MockBlockDevice { + public: + disk_format_t GetFormat() final { + return DISK_FORMAT_BLOBFS; + } + zx_status_t GetTypeGUID(fuchsia_hardware_block_partition_GUID* out_guid) final { + const uint8_t expected[GPT_GUID_LEN] = GUID_BLOB_VALUE; + memcpy(out_guid->value, expected, sizeof(expected)); + return ZX_OK; + } + zx_status_t CheckFilesystem() final { + checked = true; + return ZX_ERR_BAD_STATE; + } + zx_status_t FormatFilesystem() final { + formatted = true; + return ZX_OK; + } + zx_status_t MountFilesystem() final { + mounted = true; + return ZX_OK; + } + + bool checked = false; + bool formatted = false; + bool mounted = false; + }; + BlobDevice device; + EXPECT_EQ(ZX_ERR_BAD_STATE, device.Add()); + EXPECT_TRUE(device.checked); + EXPECT_FALSE(device.formatted); + EXPECT_FALSE(device.mounted); +} + +// Tests adding blobfs with a valid type GUID and valid metadata. +TEST(AddDeviceTestCase, AddValidBlobDevice) { + class BlobDevice : public MockBlockDevice { + public: + disk_format_t GetFormat() final { + return DISK_FORMAT_BLOBFS; + } + zx_status_t GetTypeGUID(fuchsia_hardware_block_partition_GUID* out_guid) final { + const uint8_t expected[GPT_GUID_LEN] = GUID_BLOB_VALUE; + memcpy(out_guid->value, expected, sizeof(expected)); + return ZX_OK; + } + zx_status_t CheckFilesystem() final { + checked = true; + return ZX_OK; + } + zx_status_t FormatFilesystem() final { + formatted = true; + return ZX_OK; + } + zx_status_t MountFilesystem() final { + mounted = true; + return ZX_OK; + } + + bool checked = false; + bool formatted = false; + bool mounted = false; + }; + BlobDevice device; + EXPECT_EQ(ZX_OK, device.Add()); + EXPECT_TRUE(device.checked); + EXPECT_FALSE(device.formatted); + EXPECT_TRUE(device.mounted); +} + +// Tests adding minfs with a valid type GUID and invalid metadata. Observe that +// the filesystem reformats itself. +TEST(AddDeviceTestCase, AddInvalidMinfsDevice) { + class MinfsDevice : public MockBlockDevice { + public: + disk_format_t GetFormat() final { + return DISK_FORMAT_MINFS; + } + zx_status_t GetTypeGUID(fuchsia_hardware_block_partition_GUID* out_guid) final { + const uint8_t expected[GPT_GUID_LEN] = GUID_DATA_VALUE; + memcpy(out_guid->value, expected, sizeof(expected)); + return ZX_OK; + } + zx_status_t CheckFilesystem() final { + checked = true; + return ZX_ERR_BAD_STATE; + } + zx_status_t FormatFilesystem() final { + formatted = true; + return ZX_OK; + } + zx_status_t MountFilesystem() final { + mounted = true; + return ZX_OK; + } + + bool checked = false; + bool formatted = false; + bool mounted = false; + }; + MinfsDevice device; + EXPECT_EQ(ZX_OK, device.Add()); + EXPECT_TRUE(device.checked); + EXPECT_TRUE(device.formatted); + EXPECT_TRUE(device.mounted); +} + +} // namespace diff --git a/zircon/system/core/devmgr/fshost/block-watcher.cpp b/zircon/system/core/devmgr/fshost/block-watcher.cpp index 8c9b4e36d8b2234bdc1633993c6815bbac6188bd..4266d5d4f3f07678599f52d67acf4d4982f371c0 100644 --- a/zircon/system/core/devmgr/fshost/block-watcher.cpp +++ b/zircon/system/core/devmgr/fshost/block-watcher.cpp @@ -10,7 +10,10 @@ #include <fbl/algorithm.h> #include <fbl/auto_call.h> +#include <fbl/string.h> #include <fbl/string_buffer.h> +#include <fbl/string_piece.h> +#include <fbl/string_printf.h> #include <fbl/unique_fd.h> #include <fs-management/mount.h> #include <fuchsia/device/c/fidl.h> @@ -29,7 +32,6 @@ #include <lib/zx/time.h> #include <loader-service/loader-service.h> #include <minfs/minfs.h> -#include <ramdevice-client/ramdisk.h> #include <zircon/device/block.h> #include <zircon/processargs.h> #include <zircon/status.h> @@ -38,410 +40,38 @@ #include <utility> +#include "block-device.h" #include "block-watcher.h" #include "pkgfs-launcher.h" namespace devmgr { namespace { -zx_status_t LaunchBlobfs(int argc, const char** argv, zx_handle_t* hnd, uint32_t* ids, - size_t len) { - return devmgr_launch(*zx::job::default_job(), "blobfs:/blob", argv, nullptr, - -1, hnd, ids, len, nullptr, FS_FOR_FSPROC); -} - -zx_status_t LaunchMinfs(int argc, const char** argv, zx_handle_t* hnd, uint32_t* ids, size_t len) { - return devmgr_launch(*zx::job::default_job(), "minfs:/data", argv, nullptr, - -1, hnd, ids, len, nullptr, FS_FOR_FSPROC); -} - -zx_status_t LaunchFAT(int argc, const char** argv, zx_handle_t* hnd, uint32_t* ids, size_t len) { - return devmgr_launch(*zx::job::default_job(), "fatfs:/volume", argv, nullptr, - -1, hnd, ids, len, nullptr, FS_FOR_FSPROC); -} - -// Attempt to mount the device pointed to be the file descriptor at a known -// location. -// -// Returns ZX_ERR_ALREADY_BOUND if the device could be mounted, but something -// is already mounted at that location. Returns ZX_ERR_INVALID_ARGS if the -// GUID of the device does not match a known valid one. Returns -// ZX_ERR_NOT_SUPPORTED if the GUID is a system GUID. Returns ZX_OK if an -// attempt to mount is made, without checking mount success. -zx_status_t MountMinfs(FilesystemMounter* filesystems, fbl::unique_fd fd, mount_options_t* options) { - fuchsia_hardware_block_partition_GUID type_guid; - { - fzl::UnownedFdioCaller disk_connection(fd.get()); - zx::unowned_channel channel(disk_connection.borrow_channel()); - zx_status_t io_status, status; - io_status = fuchsia_hardware_block_partition_PartitionGetTypeGuid(channel->get(), &status, - &type_guid); - if (io_status != ZX_OK) - return io_status; - if (status != ZX_OK) - return status; - } - - if (gpt_is_sys_guid(type_guid.value, GPT_GUID_LEN)) { - return ZX_ERR_NOT_SUPPORTED; - } else if (gpt_is_data_guid(type_guid.value, GPT_GUID_LEN)) { - return filesystems->MountData(std::move(fd), options); - } else if (gpt_is_install_guid(type_guid.value, GPT_GUID_LEN)) { - return filesystems->MountInstall(std::move(fd), options); - } - printf("fshost: Unrecognized partition GUID for minfs; not mounting\n"); - return ZX_ERR_INVALID_ARGS; -} - -#define FVM_DRIVER_LIB "/boot/driver/fvm.so" -#define GPT_DRIVER_LIB "/boot/driver/gpt.so" -#define MBR_DRIVER_LIB "/boot/driver/mbr.so" -#define BOOTPART_DRIVER_LIB "/boot/driver/bootpart.so" -#define ZXCRYPT_DRIVER_LIB "/boot/driver/zxcrypt.so" -#define STRLEN(s) (sizeof(s) / sizeof((s)[0])) - -// return value is ignored -int UnsealZxcrypt(void* arg) { - std::unique_ptr<int> fd_ptr(static_cast<int*>(arg)); - fbl::unique_fd fd(*fd_ptr); - - zx_status_t rc; - std::unique_ptr<zxcrypt::FdioVolume> zxcrypt_volume; - if ((rc = zxcrypt::FdioVolume::Init(std::move(fd), &zxcrypt_volume)) != ZX_OK) { - printf("fshost: couldn't open zxcrypt fdio volume"); - return ZX_OK; - } - - zx::channel zxcrypt_volume_manager_chan; - if ((rc = zxcrypt_volume->OpenManager(zx::sec(2), zxcrypt_volume_manager_chan.reset_and_get_address())) != ZX_OK) { - printf("fshost: couldn't open zxcrypt manager device"); - return 0; - } - - zxcrypt::FdioVolumeManager zxcrypt_volume_manager(std::move(zxcrypt_volume_manager_chan)); - uint8_t slot = 0; - if ((rc = zxcrypt_volume_manager.UnsealWithDeviceKey(slot)) != ZX_OK) { - printf("fshost: couldn't unseal zxcrypt manager device"); - return 0; - } - - return 0; -} - -zx_status_t FormatMinfs(const fbl::unique_fd& block_device, - const fuchsia_hardware_block_BlockInfo& info) { - - fprintf(stderr, "fshost: Formatting minfs.\n"); - uint64_t device_size = info.block_size * info.block_count; - std::unique_ptr<minfs::Bcache> bc; - zx_status_t status; - if ((status = minfs::Bcache::Create(&bc, block_device.duplicate(), - static_cast<uint32_t>(device_size))) != ZX_OK) { - fprintf(stderr, "fshost: Could not initialize minfs bcache.\n"); - return status; - } - minfs::MountOptions options = {}; - if ((status = Mkfs(options, std::move(bc))) != ZX_OK) { - fprintf(stderr, "fshost: Could not format minfs filesystem.\n"); - return status; - } - printf("fshost: Minfs filesystem re-formatted. Expect data loss.\n"); - return ZX_OK; -} - -zx_status_t BlockDeviceAdded(int dirfd, int event, const char* name, void* cookie) { - auto filesystems = static_cast<FilesystemMounter*>(cookie); +constexpr char kPathBlockDeviceRoot[] = "/dev/class/block"; +zx_status_t BlockDeviceCallback(int dirfd, int event, const char* name, void* cookie) { if (event != WATCH_EVENT_ADD_FILE) { return ZX_OK; } - - char device_path[PATH_MAX]; - sprintf(device_path, "%s/%s", PATH_DEV_BLOCK, name); - - fbl::unique_fd fd(openat(dirfd, name, O_RDWR)); - if (!fd) { - return ZX_OK; - } - - disk_format_t df = detect_disk_format(fd.get()); - fuchsia_hardware_block_BlockInfo info; - fuchsia_hardware_block_partition_GUID guid; - { - fzl::UnownedFdioCaller disk_connection(fd.get()); - zx::unowned_channel disk(disk_connection.borrow_channel()); - - zx_status_t io_status, call_status; - io_status = fuchsia_hardware_block_BlockGetInfo(disk->get(), &call_status, &info); - if (io_status != ZX_OK || call_status != ZX_OK) { - return ZX_OK; - } - - if (info.flags & BLOCK_FLAG_BOOTPART) { - fuchsia_device_ControllerBind(disk->get(), BOOTPART_DRIVER_LIB, - STRLEN(BOOTPART_DRIVER_LIB), &call_status); - return ZX_OK; - } - - switch (df) { - case DISK_FORMAT_GPT: { - printf("fshost: %s: GPT?\n", device_path); - // probe for partition table - fuchsia_device_ControllerBind(disk->get(), GPT_DRIVER_LIB, STRLEN(GPT_DRIVER_LIB), - &call_status); - return ZX_OK; - } - case DISK_FORMAT_FVM: { - printf("fshost: /dev/class/block/%s: FVM?\n", name); - // probe for partition table - fuchsia_device_ControllerBind(disk->get(), FVM_DRIVER_LIB, STRLEN(FVM_DRIVER_LIB), - &call_status); - return ZX_OK; - } - case DISK_FORMAT_MBR: { - printf("fshost: %s: MBR?\n", device_path); - // probe for partition table - fuchsia_device_ControllerBind(disk->get(), MBR_DRIVER_LIB, STRLEN(MBR_DRIVER_LIB), - &call_status); - return ZX_OK; - } - case DISK_FORMAT_ZXCRYPT: { - if (!filesystems->Netbooting()) { - printf("fshost: %s: zxcrypt?\n", device_path); - // Bind and unseal the driver from a separate thread, since we - // have to wait for a number of devices to do I/O and settle, - // and we don't want to block block-watcher for any nontrivial - // length of time. - - // We transfer fd to the spawned thread. Since it's UB to cast - // ints to pointers and back, we allocate the fd on the heap. - int loose_fd = fd.release(); - int* raw_fd_ptr = new int(loose_fd); - thrd_t th; - int err = thrd_create_with_name(&th, &UnsealZxcrypt, raw_fd_ptr, "zxcrypt-unseal"); - if (err != thrd_success) { - printf("fshost: failed to spawn zxcrypt unseal thread"); - close(loose_fd); - delete raw_fd_ptr; - } else { - thrd_detach(th); - } - } - return ZX_OK; - } - default: - break; - } - - io_status = fuchsia_hardware_block_partition_PartitionGetTypeGuid(disk->get(), &call_status, - &guid); - if (io_status != ZX_OK || call_status != ZX_OK) { - return ZX_OK; - } - } - - // If we're in netbooting mode, then only bind drivers for partition - // containers and the install partition, not regular filesystems. - if (filesystems->Netbooting()) { - if (gpt_is_install_guid(guid.value, GPT_GUID_LEN)) { - printf("fshost: mounting install partition\n"); - mount_options_t options = default_mount_options; - MountMinfs(filesystems, std::move(fd), &options); - return ZX_OK; - } - + fbl::unique_fd device_fd(openat(dirfd, name, O_RDWR)); + if (!device_fd) { return ZX_OK; } - switch (df) { - case DISK_FORMAT_BLOBFS: { - const uint8_t expected_guid[GPT_GUID_LEN] = GUID_BLOB_VALUE; - - if (memcmp(guid.value, expected_guid, GPT_GUID_LEN)) { - return ZX_OK; - } - fsck_options_t fsck_options = default_fsck_options; - fsck_options.apply_journal = true; - if (filesystems->CheckFilesystem(device_path, DISK_FORMAT_BLOBFS, &fsck_options) != ZX_OK) { - return ZX_OK; - } - - mount_options_t options = default_mount_options; - options.enable_journal = true; - options.collect_metrics = true; - zx_status_t status = filesystems->MountBlob(std::move(fd), &options); - if (status != ZX_OK) { - printf("fshost: Failed to mount blobfs partition %s at %s: %s.\n", device_path, - PATH_BLOB, zx_status_get_string(status)); - } else { - LaunchBlobInit(filesystems); - } - return ZX_OK; - } - case DISK_FORMAT_MINFS: { - printf("fshost: mounting minfs\n"); - fsck_options_t fsck_options = default_fsck_options; - if (filesystems->CheckFilesystem(device_path, DISK_FORMAT_MINFS, &fsck_options) != ZX_OK) { - if (FormatMinfs(fd, info) != ZX_OK) { - return ZX_OK; - } - } - mount_options_t options = default_mount_options; - MountMinfs(filesystems, std::move(fd), &options); - return ZX_OK; - } - case DISK_FORMAT_FAT: { - // Use the GUID to avoid auto-mounting the EFI partition - bool efi = gpt_is_efi_guid(guid.value, GPT_GUID_LEN); - if (efi) { - printf("fshost: not automounting efi\n"); - return ZX_OK; - } - mount_options_t options = default_mount_options; - options.create_mountpoint = true; - static int fat_counter = 0; - char mountpath[FDIO_MAX_FILENAME + 64]; - snprintf(mountpath, sizeof(mountpath), "%s/fat-%d", "/fs" PATH_VOLUME, fat_counter++); - options.wait_until_ready = false; - printf("fshost: mounting fatfs\n"); - mount(fd.release(), mountpath, df, &options, LaunchFAT); - return ZX_OK; - } - default: - return ZX_OK; - } + auto mounter = static_cast<FilesystemMounter*>(cookie); + BlockDevice device(mounter, std::move(device_fd)); + device.Add(); + return ZX_OK; } } // namespace -zx_status_t FilesystemMounter::MountData(fbl::unique_fd fd, mount_options_t* options) { - if (data_mounted_) { - return ZX_ERR_ALREADY_BOUND; - } - options->wait_until_ready = true; - - zx_status_t status = - mount(fd.release(), "/fs" PATH_DATA, DISK_FORMAT_MINFS, options, LaunchMinfs); - if (status != ZX_OK) { - printf("fshost: failed to mount %s: %s.\n", PATH_DATA, zx_status_get_string(status)); - } else { - data_mounted_ = true; - } - return status; -} - -zx_status_t FilesystemMounter::MountInstall(fbl::unique_fd fd, mount_options_t* options) { - if (install_mounted_) { - return ZX_ERR_ALREADY_BOUND; - } - options->readonly = true; - zx_status_t status = - mount(fd.release(), "/fs" PATH_INSTALL, DISK_FORMAT_MINFS, options, LaunchMinfs); - if (status != ZX_OK) { - printf("fshost: failed to mount %s: %s.\n", PATH_INSTALL, zx_status_get_string(status)); - } else { - install_mounted_ = true; - } - return status; -} - -zx_status_t FilesystemMounter::MountBlob(fbl::unique_fd fd, mount_options_t* options) { - if (blob_mounted_) { - return ZX_ERR_ALREADY_BOUND; - } - zx_status_t status = - mount(fd.release(), "/fs" PATH_BLOB, DISK_FORMAT_BLOBFS, options, LaunchBlobfs); - if (status != ZX_OK) { - printf("fshost: failed to mount %s: %s.\n", PATH_BLOB, zx_status_get_string(status)); - } else { - blob_mounted_ = true; - } - return status; -} - -zx_status_t FilesystemMounter::CheckFilesystem(const char* device_path, disk_format_t df, - const fsck_options_t* options) const { - if (!getenv_bool("zircon.system.filesystem-check", false)) { - return ZX_OK; - } - - // TODO(ZX-3793): Blobfs' consistency checker is too slow to execute on boot. - // With journaling, it is also unnecessary, but would be a nice mechanism for sanity - // checking. - if (df == DISK_FORMAT_BLOBFS) { - fprintf(stderr, "fshost: Skipping blobfs consistency checker\n"); - return ZX_OK; - } - - zx::ticks before = zx::ticks::now(); - auto timer = fbl::MakeAutoCall([before]() { - auto after = zx::ticks::now(); - auto duration = fzl::TicksToNs(after - before); - printf("fshost: fsck took %" PRId64 ".%" PRId64 " seconds\n", duration.to_secs(), - duration.to_msecs() % 1000); - }); - - printf("fshost: fsck of %s started\n", disk_format_string_[df]); - - auto launch_fsck = [](int argc, const char** argv, zx_handle_t* hnd, uint32_t* ids, - size_t len) { - zx::process proc; - zx_status_t status = devmgr_launch(*zx::job::default_job(), "fsck", argv, - nullptr, -1, hnd, ids, len, &proc, FS_FOR_FSPROC); - if (status != ZX_OK) { - fprintf(stderr, "fshost: Couldn't launch fsck\n"); - return status; - } - status = proc.wait_one(ZX_PROCESS_TERMINATED, zx::time::infinite(), nullptr); - if (status != ZX_OK) { - fprintf(stderr, "fshost: Error waiting for fsck to terminate\n"); - return status; - } - - zx_info_process_t info; - status = proc.get_info(ZX_INFO_PROCESS, &info, sizeof(info), nullptr, nullptr); - if (status != ZX_OK) { - fprintf(stderr, "fshost: Failed to get process info\n"); - return status; - } - - if (info.return_code != 0) { - fprintf(stderr, "fshost: Fsck return code: %" PRId64 "\n", info.return_code); - return ZX_ERR_BAD_STATE; - } - return ZX_OK; - }; - - zx_status_t status = fsck(device_path, df, options, launch_fsck); - if (status != ZX_OK) { - fprintf(stderr, "--------------------------------------------------------------\n"); - fprintf(stderr, "| \n"); - fprintf(stderr, "| WARNING: fshost fsck failure! \n"); - fprintf(stderr, "| Corrupt %s @ %s \n", disk_format_string_[df], device_path); - fprintf(stderr, "| \n"); - fprintf(stderr, "| If your system encountered power-loss due to an unclean \n"); - fprintf(stderr, "| shutdown, this error was expected. Journaling in minfs \n"); - fprintf(stderr, "| is being tracked by ZX-2093. Re-paving will reset your \n"); - fprintf(stderr, "| device. \n"); - fprintf(stderr, "| \n"); - fprintf(stderr, "| If your system was shutdown cleanly (via 'dm poweroff' \n"); - fprintf(stderr, "| or an OTA), report this device to the local-storage \n"); - fprintf(stderr, "| team. Please file bugs with logs before and after reboot. \n"); - fprintf(stderr, "| Please use the 'filesystem' and 'minfs' component tag. \n"); - fprintf(stderr, "| \n"); - fprintf(stderr, "--------------------------------------------------------------\n"); - } else { - printf("fshost: fsck of %s completed OK\n", disk_format_string_[df]); - } - return status; -} - void BlockDeviceWatcher(std::unique_ptr<FsManager> fshost, bool netboot) { - FilesystemMounter filesystems(std::move(fshost), netboot); + FilesystemMounter mounter(std::move(fshost), netboot); - fbl::unique_fd dirfd(open("/dev/class/block", O_DIRECTORY | O_RDONLY)); + fbl::unique_fd dirfd(open(kPathBlockDeviceRoot, O_DIRECTORY | O_RDONLY)); if (dirfd) { - fdio_watch_directory(dirfd.get(), BlockDeviceAdded, ZX_TIME_INFINITE, &filesystems); + fdio_watch_directory(dirfd.get(), BlockDeviceCallback, ZX_TIME_INFINITE, &mounter); } } diff --git a/zircon/system/core/devmgr/fshost/block-watcher.h b/zircon/system/core/devmgr/fshost/block-watcher.h index d4c01a54e8f88b064884abb33961d8e0f0ead54a..115a2603629228216c7390ed76be24ac1ca3a537 100644 --- a/zircon/system/core/devmgr/fshost/block-watcher.h +++ b/zircon/system/core/devmgr/fshost/block-watcher.h @@ -6,53 +6,10 @@ #include <memory> -#include <fbl/unique_fd.h> -#include <fs-management/mount.h> -#include <lib/zx/channel.h> -#include <zircon/types.h> - #include "fs-manager.h" namespace devmgr { -class FilesystemMounter { -public: - FilesystemMounter(std::unique_ptr<FsManager> fshost, bool netboot) - : fshost_(std::move(fshost)), netboot_(netboot) {} - - void FuchsiaStart() const { fshost_->FuchsiaStart(); } - - zx_status_t InstallFs(const char* path, zx::channel h) { - return fshost_->InstallFs(path, std::move(h)); - } - - bool Netbooting() const { return netboot_; } - - // Optionally checks the filesystem stored on the device at |device_path|, - // if "zircon.system.filesystem-check" is set. - zx_status_t CheckFilesystem(const char* device_path, disk_format_t df, - const fsck_options_t* options) const; - - // Attempts to mount a block device backed by |fd| to "/data". - // Fails if already mounted. - zx_status_t MountData(fbl::unique_fd fd, mount_options_t* options); - - // Attempts to mount a block device backed by |fd| to "/install". - // Fails if already mounted. - zx_status_t MountInstall(fbl::unique_fd fd, mount_options_t* options); - - // Attempts to mount a block device backed by |fd| to "/blob". - // Fails if already mounted. - zx_status_t MountBlob(fbl::unique_fd fd, mount_options_t* options); - -private: - std::unique_ptr<FsManager> fshost_; - bool netboot_ = false; - bool data_mounted_ = false; - bool install_mounted_ = false; - bool blob_mounted_ = false; -}; - // Monitors "/dev/class/block" for new devices indefinitely. void BlockDeviceWatcher(std::unique_ptr<FsManager> fshost, bool netboot); diff --git a/zircon/system/core/devmgr/fshost/filesystem-mounter.cpp b/zircon/system/core/devmgr/fshost/filesystem-mounter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4c1f6ac5d1459ed4426546fdb84ca59d8c4dc247 --- /dev/null +++ b/zircon/system/core/devmgr/fshost/filesystem-mounter.cpp @@ -0,0 +1,75 @@ +// 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 <fbl/unique_fd.h> +#include <fs-management/mount.h> +#include <lib/zx/process.h> +#include <minfs/minfs.h> +#include <zircon/status.h> + +#include "filesystem-mounter.h" +#include "pkgfs-launcher.h" + +namespace devmgr { +namespace { + +zx_status_t LaunchBlobfs(int argc, const char** argv, zx_handle_t* hnd, uint32_t* ids, + size_t len) { + return devmgr_launch(*zx::job::default_job(), "blobfs:/blob", argv, nullptr, + -1, hnd, ids, len, nullptr, FS_FOR_FSPROC); +} + +zx_status_t LaunchMinfs(int argc, const char** argv, zx_handle_t* hnd, uint32_t* ids, size_t len) { + return devmgr_launch(*zx::job::default_job(), "minfs:/data", argv, nullptr, + -1, hnd, ids, len, nullptr, FS_FOR_FSPROC); +} + +} // namespace + +zx_status_t FilesystemMounter::MountData(fbl::unique_fd fd, mount_options_t* options) { + if (data_mounted_) { + return ZX_ERR_ALREADY_BOUND; + } + options->wait_until_ready = true; + + zx_status_t status = + mount(fd.release(), "/fs" PATH_DATA, DISK_FORMAT_MINFS, options, LaunchMinfs); + if (status != ZX_OK) { + printf("fshost: failed to mount %s: %s.\n", PATH_DATA, zx_status_get_string(status)); + } else { + data_mounted_ = true; + } + return status; +} + +zx_status_t FilesystemMounter::MountInstall(fbl::unique_fd fd, mount_options_t* options) { + if (install_mounted_) { + return ZX_ERR_ALREADY_BOUND; + } + options->readonly = true; + zx_status_t status = + mount(fd.release(), "/fs" PATH_INSTALL, DISK_FORMAT_MINFS, options, LaunchMinfs); + if (status != ZX_OK) { + printf("fshost: failed to mount %s: %s.\n", PATH_INSTALL, zx_status_get_string(status)); + } else { + install_mounted_ = true; + } + return status; +} + +zx_status_t FilesystemMounter::MountBlob(fbl::unique_fd fd, mount_options_t* options) { + if (blob_mounted_) { + return ZX_ERR_ALREADY_BOUND; + } + zx_status_t status = + mount(fd.release(), "/fs" PATH_BLOB, DISK_FORMAT_BLOBFS, options, LaunchBlobfs); + if (status != ZX_OK) { + printf("fshost: failed to mount %s: %s.\n", PATH_BLOB, zx_status_get_string(status)); + } else { + blob_mounted_ = true; + } + return status; +} + +} // namespace devmgr diff --git a/zircon/system/core/devmgr/fshost/filesystem-mounter.h b/zircon/system/core/devmgr/fshost/filesystem-mounter.h new file mode 100644 index 0000000000000000000000000000000000000000..656ab6a420bed94a80a67e429f41146764c168fe --- /dev/null +++ b/zircon/system/core/devmgr/fshost/filesystem-mounter.h @@ -0,0 +1,53 @@ +// 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. + +#pragma once + +#include <memory> + +#include <fbl/unique_fd.h> +#include <fs-management/mount.h> +#include <lib/zx/channel.h> +#include <zircon/types.h> + +#include "fs-manager.h" + +namespace devmgr { + +// FilesystemMounter is a utility class which wraps the Fsmanager +// and helps clients mount filesystems within the fshost namespace. +class FilesystemMounter { +public: + FilesystemMounter(std::unique_ptr<FsManager> fshost, bool netboot) + : fshost_(std::move(fshost)), netboot_(netboot) {} + + void FuchsiaStart() const { fshost_->FuchsiaStart(); } + + zx_status_t InstallFs(const char* path, zx::channel h) { + return fshost_->InstallFs(path, std::move(h)); + } + + bool Netbooting() const { return netboot_; } + + // Attempts to mount a block device backed by |fd| to "/data". + // Fails if already mounted. + zx_status_t MountData(fbl::unique_fd fd, mount_options_t* options); + + // Attempts to mount a block device backed by |fd| to "/install". + // Fails if already mounted. + zx_status_t MountInstall(fbl::unique_fd fd, mount_options_t* options); + + // Attempts to mount a block device backed by |fd| to "/blob". + // Fails if already mounted. + zx_status_t MountBlob(fbl::unique_fd fd, mount_options_t* options); + +private: + std::unique_ptr<FsManager> fshost_; + bool netboot_ = false; + bool data_mounted_ = false; + bool install_mounted_ = false; + bool blob_mounted_ = false; +}; + +} // namespace devmgr diff --git a/zircon/system/core/devmgr/fshost/fs-manager.cpp b/zircon/system/core/devmgr/fshost/fs-manager.cpp index 605f31afcbc81f469d7e024a2e2e183258aba3b3..ba904cef1a0216e70765d0e12b0aea92a21d9d5a 100644 --- a/zircon/system/core/devmgr/fshost/fs-manager.cpp +++ b/zircon/system/core/devmgr/fshost/fs-manager.cpp @@ -105,6 +105,16 @@ FsManager::FsManager(zx::event fshost_event) ZX_ASSERT(global_root_ == nullptr); } +// In the event that we haven't been explicitly signalled, tear ourself down. +FsManager::~FsManager() { + if (global_shutdown_.has_handler()) { + event_.signal(0, FSHOST_SIGNAL_EXIT); + auto deadline = zx::deadline_after(zx::sec(2)); + zx_signals_t pending; + event_.wait_one(FSHOST_SIGNAL_EXIT_DONE, deadline, &pending); + } +} + zx_status_t FsManager::Create(zx::event fshost_event, fbl::unique_ptr<FsManager>* out) { auto fs_manager = fbl::unique_ptr<FsManager>(new FsManager(std::move(fshost_event))); zx_status_t status = fs_manager->Initialize(); diff --git a/zircon/system/core/devmgr/fshost/fs-manager.h b/zircon/system/core/devmgr/fshost/fs-manager.h index 776f5e52345785bbf3ba404773cbcadd9be9dfe5..cca2ddeb3c069edce5a23233eb09a0e16952173a 100644 --- a/zircon/system/core/devmgr/fshost/fs-manager.h +++ b/zircon/system/core/devmgr/fshost/fs-manager.h @@ -27,6 +27,8 @@ class FsManager { public: static zx_status_t Create(zx::event fshost_event, fbl::unique_ptr<FsManager>* out); + ~FsManager(); + // Signals that "/system" has been mounted. void FuchsiaStart() const { event_.signal(0, FSHOST_SIGNAL_READY); } diff --git a/zircon/system/core/devmgr/fshost/pkgfs-launcher.h b/zircon/system/core/devmgr/fshost/pkgfs-launcher.h index aa8f01b24c316df87eed45942355ae54be54de2c..c9f4b66bfaace73f957f85dd10d17ee46623d6ee 100644 --- a/zircon/system/core/devmgr/fshost/pkgfs-launcher.h +++ b/zircon/system/core/devmgr/fshost/pkgfs-launcher.h @@ -4,7 +4,7 @@ #pragma once -#include "block-watcher.h" +#include "filesystem-mounter.h" namespace devmgr { diff --git a/zircon/system/utest/BUILD.gn b/zircon/system/utest/BUILD.gn index 8dab404f96c7e023592f6034d54d69ac91858e68..31e1717cbe65cd2a892d8b3b80032eb9b97a2148 100644 --- a/zircon/system/utest/BUILD.gn +++ b/zircon/system/utest/BUILD.gn @@ -14,6 +14,7 @@ if (current_cpu != "") { ":host", # TODO(mcgrathr): reach this differently? "$zx/system/core/bootsvc:bootsvc-tests", "$zx/system/core/devmgr/devcoordinator:devcoordinator-test", + "$zx/system/core/devmgr/fshost:block-watcher-test", "$zx/system/core/devmgr/fshost:fshost-test", "$zx/system/core/virtcon:virtual-console-test", "$zx/system/dev/backlight/sg-micro:sgm37603a-test",