| // 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/devmgr-integration-test/fixture.h> |
| #include <lib/fdio/cpp/caller.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/zx/vmo.h> |
| |
| #include <memory> |
| |
| #include <fs-management/fvm.h> |
| #include <fs-management/mount.h> |
| #include <ramdevice-client/ramdisk.h> |
| #include <zxtest/zxtest.h> |
| |
| #include "src/storage/fvm/format.h" |
| |
| namespace { |
| |
| using devmgr_integration_test::IsolatedDevmgr; |
| |
| constexpr uint32_t kBlockCount = 1024 * 256; |
| constexpr uint32_t kBlockSize = 512; |
| constexpr uint32_t kSliceSize = (1 << 20); |
| constexpr size_t kDeviceSize = kBlockCount * kBlockSize; |
| const char* kDataName = "minfs"; |
| const char* kRamdiskPath = "misc/ramctl"; |
| |
| // Test fixture that builds a ramdisk and destroys it when destructed. |
| class FsRecoveryTest : public zxtest::Test { |
| public: |
| // Create an IsolatedDevmgr that can load device drivers such as fvm, zxcrypt, etc. |
| zx_status_t Initialize() { |
| auto args = IsolatedDevmgr::DefaultArgs(); |
| args.disable_block_watcher = false; |
| args.sys_device_driver = devmgr_integration_test::IsolatedDevmgr::kSysdevDriver; |
| args.load_drivers.push_back(devmgr_integration_test::IsolatedDevmgr::kSysdevDriver); |
| args.driver_search_paths.push_back("/boot/driver"); |
| |
| return IsolatedDevmgr::Create(std::move(args), &devmgr_); |
| } |
| |
| ~FsRecoveryTest() { |
| if (ramdisk_client_) { |
| ramdisk_destroy(ramdisk_client_); |
| } |
| } |
| |
| // Create a ram disk that is back by a VMO, which is formatted to look like an FVM volume. |
| void CreateFvmRamdisk(size_t device_size, size_t block_size) { |
| // Calculate total size of data + metadata. |
| size_t slice_count = fbl::round_up(device_size, fvm::kBlockSize) / fvm::kBlockSize; |
| device_size = |
| fvm::Header::FromSliceCount(fvm::kMaxUsablePartitions, slice_count, fvm::kBlockSize) |
| .fvm_partition_size; |
| |
| zx::vmo disk; |
| ASSERT_EQ(zx::vmo::create(device_size, 0, &disk), ZX_OK); |
| int fd = -1; |
| ASSERT_EQ(fdio_fd_create(disk.get(), &fd), ZX_OK); |
| ASSERT_GE(fd, 0); |
| ASSERT_EQ(fvm_init_with_size(fd, device_size, kSliceSize), ZX_OK); |
| |
| fbl::unique_fd ramdisk = WaitForDevice(kRamdiskPath); |
| ASSERT_TRUE(ramdisk); |
| |
| ASSERT_OK(ramdisk_create_at_from_vmo(devmgr_.devfs_root().get(), disk.get(), &ramdisk_client_)); |
| } |
| |
| // Create a partition in the FVM volume that has the data guid. Returns the path to the FVM block |
| // device. |
| std::string CreateFvmPartition() { |
| std::string fvm_path = std::string(ramdisk_get_path(ramdisk_client_)) + "/fvm"; |
| fbl::unique_fd fvm_fd = WaitForDevice(fvm_path); |
| EXPECT_TRUE(fvm_fd); |
| |
| // Allocate a FVM partition with the data guid but don't actually format the partition. |
| alloc_req_t req; |
| memset(&req, 0, sizeof(alloc_req_t)); |
| req.slice_count = 1; |
| static const uint8_t data_guid[GPT_GUID_LEN] = GUID_DATA_VALUE; |
| memcpy(req.type, data_guid, BLOCK_GUID_LEN); |
| |
| fuchsia_hardware_block_partition_GUID type_guid; |
| memcpy(type_guid.value, req.type, BLOCK_GUID_LEN); |
| fuchsia_hardware_block_partition_GUID instance_guid; |
| memcpy(instance_guid.value, req.guid, BLOCK_GUID_LEN); |
| |
| fdio_cpp::UnownedFdioCaller caller(fvm_fd.get()); |
| zx_status_t status; |
| EXPECT_OK(fuchsia_hardware_block_volume_VolumeManagerAllocatePartition( |
| caller.borrow_channel(), req.slice_count, &type_guid, &instance_guid, kDataName, |
| strlen(kDataName), req.flags, &status)); |
| EXPECT_OK(status); |
| |
| std::string fvm_block_path = fvm_path + "/" + kDataName + "-p-1/block"; |
| fbl::unique_fd fvm_block_fd = WaitForDevice(fvm_block_path); |
| EXPECT_TRUE(fvm_block_fd); |
| |
| return fvm_block_path; |
| } |
| |
| // Wait for the device to be available and then check to make sure it is formatted of the passed |
| // in type. Since formatting can take some time after the device becomes available, we must |
| // recheck. |
| bool WaitForDiskFormat(const std::string& path, disk_format_t format, zx::duration deadline) { |
| fbl::unique_fd fd = WaitForDevice(path); |
| if (!fd) { |
| return false; |
| } |
| const zx::time absolute_deadline = zx::deadline_after(deadline); |
| for (;;) { |
| fd.reset(openat(devmgr_.devfs_root().get(), path.c_str(), O_RDONLY)); |
| if (detect_disk_format(fd.get()) == format) { |
| return true; |
| } |
| zx::time next_deadline = zx::deadline_after(zx::duration(zx::sec(1))); |
| if (next_deadline > absolute_deadline) { |
| break; |
| } |
| zx::nanosleep(next_deadline); |
| } |
| return false; |
| } |
| |
| private: |
| fbl::unique_fd WaitForDevice(const std::string& path) { |
| fbl::unique_fd result; |
| EXPECT_OK( |
| devmgr_integration_test::RecursiveWaitForFile(devmgr_.devfs_root(), path.c_str(), &result)); |
| return result; |
| } |
| |
| ramdisk_client_t* ramdisk_client_ = nullptr; |
| devmgr_integration_test::IsolatedDevmgr devmgr_; |
| }; |
| |
| TEST_F(FsRecoveryTest, EmptyPartitionRecoveryTest) { |
| ASSERT_OK(Initialize()); |
| |
| // Creates an FVM partition under an isolated devmgr. It creates, but does not properly format the |
| // data partition. |
| CreateFvmRamdisk(kDeviceSize, kBlockSize); |
| std::string fvm_block_path = CreateFvmPartition(); |
| |
| // We then expect the devmgr to self-recover, i.e., format the zxcrypt/data partitions as expected |
| // from the FVM partition. |
| |
| // First, wait for the zxcrypt partition to be formatted. |
| EXPECT_TRUE(WaitForDiskFormat(fvm_block_path, DISK_FORMAT_ZXCRYPT, zx::duration(zx::sec(100)))); |
| |
| // Second, wait for the data partition to be formatted. |
| std::string data_path = fvm_block_path + "/zxcrypt/unsealed/block"; |
| EXPECT_TRUE(WaitForDiskFormat(data_path, DISK_FORMAT_MINFS, zx::duration(zx::sec(100)))); |
| } |
| |
| } // namespace |