From 4dd0028928a9b63da53d54601345b7ccb4f2606b Mon Sep 17 00:00:00 2001
From: Jeff Seibert <jseibert@google.com>
Date: Wed, 8 May 2019 21:12:12 +0000
Subject: [PATCH] [fshost] Reformat zxcrypt and minfs partitions when missing.

If the disk format is unknown but the GPT GUID values dictate that it
should be a data format, it determines if the volume should be a zxcrypt
or minfs partition and formats it as such.

Added a unit test that failed without the changes. I added a test that
would have caught the vim2 breakage, but I also manually tested that it
boots up properly.

Change-Id: I276d55fc8dbcc8377d6a6b7d5dfe7b0fd9019eb4
---
 .../devmgr/fshost/block-device-interface.h    |   8 +
 .../core/devmgr/fshost/block-device-test.cpp  |  73 +++++--
 .../core/devmgr/fshost/block-device.cpp       |  57 ++++++
 .../system/core/devmgr/fshost/block-device.h  |   2 +
 .../core/devmgr/fshost/block-watcher-test.cpp | 133 +++++++++++++
 zircon/system/utest/BUILD.gn                  |   1 +
 zircon/system/utest/fs-recovery/BUILD.gn      |  33 ++++
 zircon/system/utest/fs-recovery/recovery.cpp  | 181 ++++++++++++++++++
 .../utest/fs-test-utils/perftest_test.cpp     |   2 +-
 9 files changed, 471 insertions(+), 19 deletions(-)
 create mode 100644 zircon/system/utest/fs-recovery/BUILD.gn
 create mode 100644 zircon/system/utest/fs-recovery/recovery.cpp

diff --git a/zircon/system/core/devmgr/fshost/block-device-interface.h b/zircon/system/core/devmgr/fshost/block-device-interface.h
index 99c6302b893..dcde052ee28 100644
--- a/zircon/system/core/devmgr/fshost/block-device-interface.h
+++ b/zircon/system/core/devmgr/fshost/block-device-interface.h
@@ -17,6 +17,7 @@ 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 kZxcryptDriverPath[] = "/boot/driver/zxcrypt.so";
 constexpr char kBootpartDriverPath[] = "/boot/driver/bootpart.so";
 
 // An abstract class representing the operations which may be performed
@@ -58,6 +59,13 @@ private:
     // Unseals the underlying zxcrypt volume.
     virtual zx_status_t UnsealZxcrypt() = 0;
 
+    // Creates the zxcrypt partition.
+    virtual zx_status_t FormatZxcrypt() = 0;
+
+    // Determines if the underlying volume is unsealed zxcrypt. Assumes the device
+    // has the data GUID.
+    virtual zx_status_t IsUnsealedZxcrypt(bool* is_unsealed_zxcrypt) = 0;
+
     // Returns true if the consistency of filesystems should be validated before
     // mounting.
     virtual bool ShouldCheckFilesystems() = 0;
diff --git a/zircon/system/core/devmgr/fshost/block-device-test.cpp b/zircon/system/core/devmgr/fshost/block-device-test.cpp
index 292367e4948..ace6237cd13 100644
--- a/zircon/system/core/devmgr/fshost/block-device-test.cpp
+++ b/zircon/system/core/devmgr/fshost/block-device-test.cpp
@@ -4,6 +4,7 @@
 
 #include <fcntl.h>
 
+#include <lib/devmgr-integration-test/fixture.h>
 #include <lib/fdio/namespace.h>
 #include <ramdevice-client/ramdisk.h>
 #include <zircon/assert.h>
@@ -17,6 +18,8 @@
 namespace devmgr {
 namespace {
 
+using devmgr_integration_test::IsolatedDevmgr;
+
 class BlockDeviceHarness : public zxtest::Test {
 public:
     void SetUp() override {
@@ -36,6 +39,18 @@ public:
         ASSERT_OK(fdio_ns_get_installed(&ns));
         ASSERT_OK(fdio_ns_bind(ns, "/fs", client.release()));
         manager_->WatchExit();
+
+        devmgr_launcher::Args args;
+        args.disable_block_watcher = true;
+        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");
+        ASSERT_OK(IsolatedDevmgr::Create(std::move(args), &devmgr_));
+        fbl::unique_fd ctl;
+        ASSERT_EQ(devmgr_integration_test::RecursiveWaitForFile(devmgr_.devfs_root(), "misc/ramctl",
+                                                                zx::deadline_after(zx::sec(5)),
+                                                                &ctl),
+                  ZX_OK);
     }
 
     void TearDown() override {
@@ -48,9 +63,14 @@ public:
         return std::move(manager_);
     }
 
+    fbl::unique_fd devfs_root() {
+        return devmgr_.devfs_root().duplicate();
+    }
+
 private:
     zx::event event_;
     std::unique_ptr<FsManager> manager_;
+    IsolatedDevmgr devmgr_;
 };
 
 TEST_F(BlockDeviceHarness, TestBadHandleDevice) {
@@ -89,9 +109,12 @@ TEST_F(BlockDeviceHarness, TestEmptyDevice) {
     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_OK(ramdisk_create_at(devfs_root().get(), kBlockSize, kBlockCount, &ramdisk));
+    fbl::unique_fd fd;
+    ASSERT_EQ(devmgr_integration_test::RecursiveWaitForFile(devfs_root(),
+                                                            ramdisk_get_path(ramdisk),
+                                                            zx::deadline_after(zx::sec(5)), &fd),
+              ZX_OK);
     ASSERT_TRUE(fd);
 
     BlockDevice device(&mounter, std::move(fd));
@@ -127,9 +150,12 @@ TEST_F(BlockDeviceHarness, TestMinfsBadGUID) {
     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_OK(ramdisk_create_at(devfs_root().get(), kBlockSize, kBlockCount, &ramdisk));
+    fbl::unique_fd fd;
+    ASSERT_EQ(devmgr_integration_test::RecursiveWaitForFile(devfs_root(),
+                                                            ramdisk_get_path(ramdisk),
+                                                            zx::deadline_after(zx::sec(5)), &fd),
+              ZX_OK);
     ASSERT_TRUE(fd);
 
     // We started with an empty block device, but let's lie and say it
@@ -158,10 +184,13 @@ TEST_F(BlockDeviceHarness, TestMinfsGoodGUID) {
     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_OK(ramdisk_create_at_with_guid(devfs_root().get(), kBlockSize, kBlockCount,
+                                          data_guid, sizeof(data_guid), &ramdisk));
+    fbl::unique_fd fd;
+    ASSERT_EQ(devmgr_integration_test::RecursiveWaitForFile(devfs_root(),
+                                                            ramdisk_get_path(ramdisk),
+                                                            zx::deadline_after(zx::sec(5)), &fd),
+              ZX_OK);
     ASSERT_TRUE(fd);
 
     BlockDevice device(&mounter, std::move(fd));
@@ -187,10 +216,15 @@ TEST_F(BlockDeviceHarness, TestMinfsReformat) {
     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_OK(ramdisk_create_at_with_guid(devfs_root().get(), kBlockSize, kBlockCount,
+                                          data_guid,
+                                          sizeof(data_guid), &ramdisk));
+    fbl::unique_fd fd;
+    ASSERT_EQ(devmgr_integration_test::RecursiveWaitForFile(devfs_root(),
+                                                            ramdisk_get_path(ramdisk),
+                                                            zx::deadline_after(zx::sec(5)), &fd),
+              ZX_OK);
+
     ASSERT_TRUE(fd);
 
     BlockDevice device(&mounter, std::move(fd));
@@ -222,10 +256,13 @@ TEST_F(BlockDeviceHarness, TestBlobfs) {
     constexpr uint64_t kBlockCount = 1 << 20;
     ramdisk_client_t* ramdisk;
     const uint8_t data_guid[GPT_GUID_LEN] = GUID_BLOB_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_OK(ramdisk_create_at_with_guid(devfs_root().get(), kBlockSize, kBlockCount, data_guid,
+                                          sizeof(data_guid), &ramdisk));
+    fbl::unique_fd fd;
+    ASSERT_EQ(devmgr_integration_test::RecursiveWaitForFile(devfs_root(),
+                                                            ramdisk_get_path(ramdisk),
+                                                            zx::deadline_after(zx::sec(5)), &fd),
+              ZX_OK);
     ASSERT_TRUE(fd);
 
     BlockDevice device(&mounter, std::move(fd));
diff --git a/zircon/system/core/devmgr/fshost/block-device.cpp b/zircon/system/core/devmgr/fshost/block-device.cpp
index c5e64c85fc9..63866bdb585 100644
--- a/zircon/system/core/devmgr/fshost/block-device.cpp
+++ b/zircon/system/core/devmgr/fshost/block-device.cpp
@@ -189,6 +189,38 @@ zx_status_t BlockDevice::UnsealZxcrypt() {
     return ZX_OK;
 }
 
+zx_status_t BlockDevice::IsUnsealedZxcrypt(bool* is_unsealed_zxcrypt) {
+    zx_status_t call_status;
+    fbl::StringBuffer<PATH_MAX> path;
+    path.Resize(path.capacity());
+    size_t path_len;
+    fzl::UnownedFdioCaller disk_connection(fd_.get());
+    // Both the zxcrypt and minfs partitions have the same gpt guid, so here we
+    // determine which it actually is. We do this by looking up the topological
+    // path.
+    if (fuchsia_device_ControllerGetTopologicalPath(disk_connection.borrow_channel(), &call_status,
+                                                    path.data(), path.capacity(),
+                                                    &path_len) != ZX_OK) {
+        return ZX_ERR_NOT_FOUND;
+    }
+    if (call_status != ZX_OK) {
+        return call_status;
+    }
+    const fbl::StringPiece kZxcryptPath("/zxcrypt/unsealed/block");
+    if (path_len < kZxcryptPath.length()) {
+        *is_unsealed_zxcrypt = false;
+    } else {
+        *is_unsealed_zxcrypt = fbl::StringPiece(path.begin() + path_len -
+                                                kZxcryptPath.length())
+                                   .compare(kZxcryptPath) == 0;
+    }
+    return ZX_OK;
+}
+
+zx_status_t BlockDevice::FormatZxcrypt() {
+  return zxcrypt::FdioVolume::CreateWithDeviceKey(fd_.duplicate(), nullptr);
+}
+
 bool BlockDevice::ShouldCheckFilesystems() {
     return mounter_->ShouldCheckFilesystems();
 }
@@ -418,6 +450,31 @@ zx_status_t BlockDeviceInterface::Add() {
         return ZX_OK;
     }
     default:
+        // If the disk format is unknown but we know it should be the data
+        // partition, format the disk properly.
+        if (gpt_is_data_guid(guid.value, GPT_GUID_LEN)) {
+            printf("fshost: Data partition has unknown format\n");
+            bool is_unsealed_zxcrypt;
+            if (IsUnsealedZxcrypt(&is_unsealed_zxcrypt) != ZX_OK) {
+                return ZX_ERR_NOT_SUPPORTED;
+            }
+            if (is_unsealed_zxcrypt) {
+                printf("fshost: Formatting as minfs partition\n");
+                SetFormat(DISK_FORMAT_MINFS);
+                status = FormatFilesystem();
+                if (status != ZX_OK) {
+                  return status;
+                }
+            } else {
+                printf("fshost: Formatting as zxcrypt partition\n");
+                SetFormat(DISK_FORMAT_ZXCRYPT);
+                status = FormatZxcrypt();
+                if (status != ZX_OK) {
+                  return status;
+                }
+            }
+            return Add();
+        }
         return ZX_ERR_NOT_SUPPORTED;
     }
 }
diff --git a/zircon/system/core/devmgr/fshost/block-device.h b/zircon/system/core/devmgr/fshost/block-device.h
index 980973641b5..a5d4ff67e54 100644
--- a/zircon/system/core/devmgr/fshost/block-device.h
+++ b/zircon/system/core/devmgr/fshost/block-device.h
@@ -30,6 +30,8 @@ public:
     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 IsUnsealedZxcrypt(bool* is_unsealed_zxcrypt) final;
+    zx_status_t FormatZxcrypt() final;
     bool ShouldCheckFilesystems() final;
     zx_status_t CheckFilesystem() final;
     zx_status_t FormatFilesystem() final;
diff --git a/zircon/system/core/devmgr/fshost/block-watcher-test.cpp b/zircon/system/core/devmgr/fshost/block-watcher-test.cpp
index b971e9a571d..129d3dede6c 100644
--- a/zircon/system/core/devmgr/fshost/block-watcher-test.cpp
+++ b/zircon/system/core/devmgr/fshost/block-watcher-test.cpp
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include <zircon/assert.h>
+#include <zircon/device/block.h>
 #include <zircon/hw/gpt.h>
 #include <zxtest/zxtest.h>
 
@@ -34,6 +35,12 @@ public:
     zx_status_t UnsealZxcrypt() override {
         ZX_PANIC("Test should not invoke function %s\n", __FUNCTION__);
     }
+    zx_status_t IsUnsealedZxcrypt(bool* is_unsealed_zxcrypt) override {
+        ZX_PANIC("Test should not invoke function %s\n", __FUNCTION__);
+    }
+    zx_status_t FormatZxcrypt() override {
+        ZX_PANIC("Test should not invoke function %s\n", __FUNCTION__);
+    }
     bool ShouldCheckFilesystems() override {
         ZX_PANIC("Test should not invoke function %s\n", __FUNCTION__);
     }
@@ -261,4 +268,130 @@ TEST(AddDeviceTestCase, AddInvalidMinfsDevice) {
     EXPECT_TRUE(device.mounted);
 }
 
+// Tests adding minfs with a valid type GUID and invalid format. Observe that
+// the filesystem reformats itself.
+TEST(AddDeviceTestCase, AddUnknownFormatMinfsDevice) {
+    class MinfsDevice : public MockBlockDevice {
+    public:
+        disk_format_t GetFormat() final {
+            return format;
+        }
+        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 FormatFilesystem() final {
+            formatted = true;
+            return ZX_OK;
+        }
+        zx_status_t CheckFilesystem() final {
+            return ZX_OK;
+        }
+        zx_status_t MountFilesystem() final {
+            EXPECT_TRUE(formatted);
+            mounted = true;
+            return ZX_OK;
+        }
+        zx_status_t IsUnsealedZxcrypt(bool* is_unsealed_zxcrypt) final {
+            *is_unsealed_zxcrypt = true;
+            return ZX_OK;
+        }
+        void SetFormat(disk_format_t f) final {
+            format = f;
+        }
+
+        disk_format_t format = DISK_FORMAT_UNKNOWN;
+        bool formatted = false;
+        bool mounted = false;
+    };
+    MinfsDevice device;
+    EXPECT_FALSE(device.formatted);
+    EXPECT_FALSE(device.mounted);
+    EXPECT_EQ(ZX_OK, device.Add());
+    EXPECT_TRUE(device.formatted);
+    EXPECT_TRUE(device.mounted);
+}
+
+// Tests adding zxcrypt with a valid type GUID and invalid format. Observe that
+// the partition reformats itself.
+TEST(AddDeviceTestCase, AddUnknownFormatZxcryptDevice) {
+    class ZxcryptDevice : public MockBlockDevice {
+    public:
+        disk_format_t GetFormat() final {
+            return format;
+        }
+        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 FormatZxcrypt() final {
+            formatted_zxcrypt = true;
+            return ZX_OK;
+        }
+        zx_status_t FormatFilesystem() final {
+            formatted_filesystem = true;
+            return ZX_OK;
+        }
+        zx_status_t CheckFilesystem() final {
+            return ZX_OK;
+        }
+        zx_status_t UnsealZxcrypt() final {
+            return ZX_OK;
+        }
+        zx_status_t IsUnsealedZxcrypt(bool* is_unsealed_zxcrypt) final {
+            *is_unsealed_zxcrypt = false;
+            return ZX_OK;
+        }
+        void SetFormat(disk_format_t f) final {
+            format = f;
+        }
+        zx_status_t AttachDriver(const fbl::StringPiece& driver) final {
+            EXPECT_STR_EQ(devmgr::kZxcryptDriverPath, driver.data());
+            return ZX_OK;
+        }
+
+        disk_format_t format = DISK_FORMAT_UNKNOWN;
+        bool formatted_zxcrypt = false;
+        bool formatted_filesystem = false;
+    };
+    ZxcryptDevice device;
+    EXPECT_EQ(ZX_OK, device.Add());
+    EXPECT_TRUE(device.formatted_zxcrypt);
+    EXPECT_FALSE(device.formatted_filesystem);
+}
+
+// Tests adding a boot partition device with unknown format can be added with
+// the correct driver.
+TEST(AddDeviceTestCase, AddUnknownFormatBootPartitionDevice) {
+    class BootPartDevice : public MockBlockDevice {
+    public:
+        disk_format_t GetFormat() final {
+            return DISK_FORMAT_UNKNOWN;
+        }
+        zx_status_t GetInfo(fuchsia_hardware_block_BlockInfo* out_info) override {
+            fuchsia_hardware_block_BlockInfo info = {};
+            info.flags = BLOCK_FLAG_BOOTPART;
+            info.block_size = 512;
+            info.block_count = 1024;
+            *out_info = info;
+            return ZX_OK;
+        }
+        zx_status_t AttachDriver(const fbl::StringPiece& driver) final {
+            EXPECT_STR_EQ(devmgr::kBootpartDriverPath, driver.data());
+            return ZX_OK;
+        }
+        zx_status_t IsUnsealedZxcrypt(bool* is_unsealed_zxcrypt) final {
+            *is_unsealed_zxcrypt = false;
+            checked_unsealed_zxcrypt = true;
+            return ZX_OK;
+        }
+        bool checked_unsealed_zxcrypt = false;
+    };
+    BootPartDevice device;
+    EXPECT_EQ(ZX_OK, device.Add());
+    EXPECT_FALSE(device.checked_unsealed_zxcrypt);
+}
+
 } // namespace
diff --git a/zircon/system/utest/BUILD.gn b/zircon/system/utest/BUILD.gn
index b5c3075ae6c..a0f2a7012d7 100644
--- a/zircon/system/utest/BUILD.gn
+++ b/zircon/system/utest/BUILD.gn
@@ -119,6 +119,7 @@ if (current_cpu != "") {
       "fs",
       "fs-bench",
       "fs-management",
+      "fs-recovery",
       "fs-test-utils",
       "fs-vnode",
       "futex-ownership",
diff --git a/zircon/system/utest/fs-recovery/BUILD.gn b/zircon/system/utest/fs-recovery/BUILD.gn
new file mode 100644
index 00000000000..f2e51667ce9
--- /dev/null
+++ b/zircon/system/utest/fs-recovery/BUILD.gn
@@ -0,0 +1,33 @@
+# 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.
+
+import("$zx/public/gn/resource.gni")
+
+generated_resource("zxcrypt_config.txt") {
+  testonly = true
+  contents = "null"
+  outputs = [
+    "config/zxcrypt",
+  ]
+}
+
+
+test("fs-recovery") {
+  sources = [
+    "recovery.cpp",
+  ]
+  deps = [
+    "$zx/system/ulib/devmgr-integration-test",
+    "$zx/system/ulib/fbl",
+    "$zx/system/ulib/fdio",
+    "$zx/system/ulib/fs-management",
+    "$zx/system/ulib/fvm",
+    "$zx/system/ulib/fzl",
+    "$zx/system/ulib/ramdevice-client",
+    "$zx/system/ulib/unittest",
+    "$zx/system/ulib/zx",
+    ":zxcrypt_config.txt",
+  ]
+}
+
diff --git a/zircon/system/utest/fs-recovery/recovery.cpp b/zircon/system/utest/fs-recovery/recovery.cpp
new file mode 100644
index 00000000000..bec94583f11
--- /dev/null
+++ b/zircon/system/utest/fs-recovery/recovery.cpp
@@ -0,0 +1,181 @@
+// 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 <fs-management/fvm.h>
+#include <fs-management/mount.h>
+#include <fvm/format.h>
+#include <lib/devmgr-integration-test/fixture.h>
+#include <lib/fdio/fd.h>
+#include <lib/fdio/fdio.h>
+#include <lib/fzl/fdio.h>
+#include <lib/zx/vmo.h>
+#include <ramdevice-client/ramdisk.h>
+#include <unittest/unittest.h>
+
+namespace {
+
+using devmgr_integration_test::IsolatedDevmgr;
+
+const uint32_t kBlockCount = 1024 * 256;
+const uint32_t kBlockSize = 512;
+const uint32_t kSliceSize = (1 << 20);
+const size_t kDeviceSize = kBlockCount * kBlockSize;
+const char* kDataName = "fs-recovery-data";
+const char* kRamdiskPath = "misc/ramctl";
+
+// Test fixture that builds a ramdisk and destroys it when destructed.
+class FsRecoveryTest {
+public:
+    // Create an IsolatedDevmgr that can load device drivers such as fvm,
+    // zxcrypt, etc.
+    bool Initialize() {
+        BEGIN_HELPER;
+        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");
+        ASSERT_EQ(IsolatedDevmgr::Create(std::move(args), &devmgr_), ZX_OK);
+        END_HELPER;
+    }
+
+    // Create a ram disk that is back by a VMO, which is formatted to look like
+    // an FVM volume.
+    bool CreateFvmRamdisk(size_t device_size, size_t block_size) {
+        BEGIN_HELPER;
+
+        // Calculate total size of data + metadata.
+        device_size = fbl::round_up(device_size, fvm::kBlockSize);
+        size_t old_meta = fvm::MetadataSize(device_size, fvm::kBlockSize);
+        size_t new_meta = fvm::MetadataSize(old_meta + device_size, fvm::kBlockSize);
+        while (old_meta != new_meta) {
+            old_meta = new_meta;
+            new_meta = fvm::MetadataSize(old_meta + device_size, fvm::kBlockSize);
+        }
+        device_size = device_size + (new_meta * 2);
+
+        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;
+        ASSERT_TRUE(WaitForDevice(kRamdiskPath, &ramdisk));
+
+        ASSERT_EQ(ramdisk_create_at_from_vmo(devmgr_.devfs_root().get(), disk.get(),
+                                             &ramdisk_client_),
+                  ZX_OK);
+        END_HELPER;
+    }
+
+    // Create a partition in the FVM volume that has the data guid.
+    bool CreateFvmPartition(char* fvm_block_path) {
+        BEGIN_HELPER;
+
+        char fvm_path[PATH_MAX];
+        snprintf(fvm_path, PATH_MAX, "%s/fvm", ramdisk_get_path(ramdisk_client_));
+        fbl::unique_fd fvm_fd;
+        ASSERT_TRUE(WaitForDevice(fvm_path, &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, GUID_LEN);
+        snprintf(req.name, NAME_LEN, "%s", kDataName);
+
+        fuchsia_hardware_block_partition_GUID type_guid;
+        memcpy(type_guid.value, req.type, GUID_LEN);
+        fuchsia_hardware_block_partition_GUID instance_guid;
+        memcpy(instance_guid.value, req.guid, GUID_LEN);
+
+        fzl::UnownedFdioCaller caller(fvm_fd.get());
+        zx_status_t status;
+        ASSERT_EQ(fuchsia_hardware_block_volume_VolumeManagerAllocatePartition(
+                      caller.borrow_channel(), req.slice_count, &type_guid, &instance_guid,
+                      req.name, NAME_LEN, req.flags, &status),
+                  ZX_OK);
+        ASSERT_EQ(status, ZX_OK);
+
+        snprintf(fvm_block_path, PATH_MAX, "%s/%s-p-1/block", fvm_path, kDataName);
+        fbl::unique_fd fvm_block_fd;
+        ASSERT_TRUE(WaitForDevice(fvm_block_path, &fvm_block_fd));
+
+        END_HELPER;
+    }
+
+    // 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 char* path, disk_format_t format, zx::duration deadline) {
+        fbl::unique_fd fd;
+        if (!WaitForDevice(path, &fd)) {
+            return false;
+        }
+        while (deadline.get() > 0) {
+            fd.reset(openat(devmgr_.devfs_root().get(), path, O_RDONLY));
+            if (detect_disk_format(fd.get()) == format)
+                return true;
+            sleep(1);
+            deadline -= zx::duration(ZX_SEC(1));
+        }
+        return false;
+    }
+
+private:
+    bool WaitForDevice(const char* path, fbl::unique_fd* fd) {
+        BEGIN_HELPER;
+        printf("Wait for device %s\n", path);
+        ASSERT_EQ(devmgr_integration_test::RecursiveWaitForFile(devmgr_.devfs_root(), path,
+                                                                zx::deadline_after(zx::sec(5)), fd),
+                  ZX_OK);
+
+        ASSERT_TRUE(*fd);
+        END_HELPER;
+    }
+
+    ramdisk_client_t* ramdisk_client_;
+    devmgr_integration_test::IsolatedDevmgr devmgr_;
+};
+
+bool EmptyPartitionRecoveryTest() {
+    BEGIN_TEST;
+
+    char fvm_block_path[PATH_MAX];
+    fbl::unique_ptr<FsRecoveryTest> recovery(new FsRecoveryTest());
+    ASSERT_TRUE(recovery->Initialize());
+    // Creates an FVM partition under an isolated devmgr. It creates, but does
+    // not properly format the data partition.
+    ASSERT_TRUE(recovery->CreateFvmRamdisk(kDeviceSize, kBlockSize));
+    ASSERT_TRUE(recovery->CreateFvmPartition(fvm_block_path));
+
+    // 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(recovery->WaitForDiskFormat(fvm_block_path, DISK_FORMAT_ZXCRYPT,
+                                            zx::duration(ZX_SEC(3))));
+
+    // Second, wait for the data partition to be formatted.
+    char data_path[PATH_MAX];
+    snprintf(data_path, sizeof(data_path),
+             "%s/zxcrypt/unsealed/block", fvm_block_path);
+    EXPECT_TRUE(recovery->WaitForDiskFormat(data_path, DISK_FORMAT_MINFS,
+                                            zx::duration(ZX_SEC(3))));
+
+    END_TEST;
+}
+
+BEGIN_TEST_CASE(FsRecoveryTest)
+RUN_TEST(EmptyPartitionRecoveryTest)
+END_TEST_CASE(FsRecoveryTest)
+
+} // namespace
+
diff --git a/zircon/system/utest/fs-test-utils/perftest_test.cpp b/zircon/system/utest/fs-test-utils/perftest_test.cpp
index 1ab4e33cd91..2f718eee534 100644
--- a/zircon/system/utest/fs-test-utils/perftest_test.cpp
+++ b/zircon/system/utest/fs-test-utils/perftest_test.cpp
@@ -20,7 +20,7 @@ namespace fs_test_utils {
 namespace {
 
 // File used to dump libs stdout. Allows verifying certain options.
-constexpr char kFakeStdout[] = "/data/fake_stdout.txt";
+constexpr char kFakeStdout[] = "/tmp/fake_stdout.txt";
 
 bool ResultSetIsValid() {
     BEGIN_TEST;
-- 
GitLab