From 41c8da6a22ad4c8f552be202c1f9eb7ade3ddb95 Mon Sep 17 00:00:00 2001
From: Sean Klein <smklein@google.com>
Date: Sat, 27 Apr 2019 00:23:24 +0000
Subject: [PATCH] [fshost][minfs] Add fshost tests for reformatting, verifying
 filesystems

Additionally, some minor related changes:
- Remove unused "FormatMinfs" function.
- Plumb the "Should we check filesystems?" boolean through explicit
arguments instead of environment variables.
- Make minfs superblock validation more strict. This validates that
clients can't format a filesystem with N blocks, and either mount
of check the consistency of a filesystem with M blocks, for M < N.
This change, along with the fshost tests, acts as a regression for
a bug where the consistency checker only inspected a subsection of the
filesystem.

Change-Id: I15470e9529553526011c8fba2d8a6f5987b8e1a6
---
 .../devmgr/fshost/block-device-interface.h    |  4 +
 .../core/devmgr/fshost/block-device-test.cpp  | 85 +++++++++++++++++--
 .../core/devmgr/fshost/block-device.cpp       | 26 ++----
 .../system/core/devmgr/fshost/block-device.h  |  1 +
 .../core/devmgr/fshost/block-watcher-test.cpp |  3 +
 .../core/devmgr/fshost/block-watcher.cpp      |  4 +-
 .../system/core/devmgr/fshost/block-watcher.h |  2 +-
 .../core/devmgr/fshost/filesystem-mounter.h   |  9 +-
 zircon/system/core/devmgr/fshost/fs-manager.h |  2 +-
 zircon/system/core/devmgr/fshost/main.cpp     |  5 +-
 zircon/system/ulib/minfs/minfs.cpp            |  2 +-
 11 files changed, 106 insertions(+), 37 deletions(-)

diff --git a/zircon/system/core/devmgr/fshost/block-device-interface.h b/zircon/system/core/devmgr/fshost/block-device-interface.h
index f774fd681fc..99c6302b893 100644
--- a/zircon/system/core/devmgr/fshost/block-device-interface.h
+++ b/zircon/system/core/devmgr/fshost/block-device-interface.h
@@ -58,6 +58,10 @@ private:
     // Unseals the underlying zxcrypt volume.
     virtual zx_status_t UnsealZxcrypt() = 0;
 
+    // Returns true if the consistency of filesystems should be validated before
+    // mounting.
+    virtual bool ShouldCheckFilesystems() = 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;
diff --git a/zircon/system/core/devmgr/fshost/block-device-test.cpp b/zircon/system/core/devmgr/fshost/block-device-test.cpp
index 641ef04590f..292367e4948 100644
--- a/zircon/system/core/devmgr/fshost/block-device-test.cpp
+++ b/zircon/system/core/devmgr/fshost/block-device-test.cpp
@@ -56,7 +56,8 @@ private:
 TEST_F(BlockDeviceHarness, TestBadHandleDevice) {
     std::unique_ptr<FsManager> manager = TakeManager();
     bool netboot = false;
-    FilesystemMounter mounter(std::move(manager), netboot);
+    bool check_filesystems = false;
+    FilesystemMounter mounter(std::move(manager), netboot, check_filesystems);
     fbl::unique_fd fd;
     BlockDevice device(&mounter, std::move(fd));
     EXPECT_EQ(device.Netbooting(), netboot);
@@ -71,7 +72,7 @@ TEST_F(BlockDeviceHarness, TestBadHandleDevice) {
     // thread without observing the results.
     EXPECT_OK(device.UnsealZxcrypt());
 
-    // Returns ZX_OK because filesystem checks are only enabled via environment variables.
+    // Returns ZX_OK because filesystem checks are disabled.
     EXPECT_OK(device.CheckFilesystem());
 
     EXPECT_EQ(device.FormatFilesystem(), ZX_ERR_BAD_HANDLE);
@@ -81,7 +82,8 @@ TEST_F(BlockDeviceHarness, TestBadHandleDevice) {
 TEST_F(BlockDeviceHarness, TestEmptyDevice) {
     std::unique_ptr<FsManager> manager = TakeManager();
     bool netboot = false;
-    FilesystemMounter mounter(std::move(manager), netboot);
+    bool check_filesystems = false;
+    FilesystemMounter mounter(std::move(manager), netboot, check_filesystems);
 
     // Initialize Ramdisk.
     constexpr uint64_t kBlockSize = 512;
@@ -118,7 +120,8 @@ TEST_F(BlockDeviceHarness, TestEmptyDevice) {
 TEST_F(BlockDeviceHarness, TestMinfsBadGUID) {
     std::unique_ptr<FsManager> manager = TakeManager();
     bool netboot = false;
-    FilesystemMounter mounter(std::move(manager), netboot);
+    bool check_filesystems = false;
+    FilesystemMounter mounter(std::move(manager), netboot, check_filesystems);
 
     // Initialize Ramdisk with an empty GUID.
     constexpr uint64_t kBlockSize = 512;
@@ -147,7 +150,8 @@ TEST_F(BlockDeviceHarness, TestMinfsGoodGUID) {
     std::unique_ptr<FsManager> manager = TakeManager();
 
     bool netboot = false;
-    FilesystemMounter mounter(std::move(manager), netboot);
+    bool check_filesystems = false;
+    FilesystemMounter mounter(std::move(manager), netboot, check_filesystems);
 
     // Initialize Ramdisk with a data GUID.
     constexpr uint64_t kBlockSize = 512;
@@ -171,8 +175,75 @@ TEST_F(BlockDeviceHarness, TestMinfsGoodGUID) {
     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.
+TEST_F(BlockDeviceHarness, TestMinfsReformat) {
+    std::unique_ptr<FsManager> manager = TakeManager();
+
+    bool netboot = false;
+    bool check_filesystems = true;
+    FilesystemMounter mounter(std::move(manager), netboot, check_filesystems);
+
+    // 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);
+
+    // Before formatting the device, this isn't a valid minfs partition.
+    EXPECT_NOT_OK(device.CheckFilesystem());
+    EXPECT_NOT_OK(device.MountFilesystem());
+
+    // After formatting the device, it is a valid partition. We can check the device,
+    // and also mount it.
+    EXPECT_OK(device.FormatFilesystem());
+    EXPECT_OK(device.CheckFilesystem());
+    EXPECT_OK(device.MountFilesystem());
+
+    ASSERT_OK(ramdisk_destroy(ramdisk));
+}
+
+TEST_F(BlockDeviceHarness, TestBlobfs) {
+    std::unique_ptr<FsManager> manager = TakeManager();
+
+    bool netboot = false;
+    bool check_filesystems = true;
+    FilesystemMounter mounter(std::move(manager), netboot, check_filesystems);
+
+    // 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_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_TRUE(fd);
+
+    BlockDevice device(&mounter, std::move(fd));
+    device.SetFormat(DISK_FORMAT_BLOBFS);
+    EXPECT_EQ(device.GetFormat(), DISK_FORMAT_BLOBFS);
+
+    // Before formatting the device, this isn't a valid blobfs partition.
+    // However, as implemented, we always validate the consistency of the filesystem.
+    EXPECT_OK(device.CheckFilesystem());
+    EXPECT_NOT_OK(device.MountFilesystem());
+
+    // Additionally, blobfs does not yet support reformatting within fshost.
+    EXPECT_NOT_OK(device.FormatFilesystem());
+    EXPECT_OK(device.CheckFilesystem());
+    EXPECT_NOT_OK(device.MountFilesystem());
+
+    ASSERT_OK(ramdisk_destroy(ramdisk));
+}
 
 // TODO: Add tests for Zxcrypt binding.
 
diff --git a/zircon/system/core/devmgr/fshost/block-device.cpp b/zircon/system/core/devmgr/fshost/block-device.cpp
index e4388d73824..c5e64c85fc9 100644
--- a/zircon/system/core/devmgr/fshost/block-device.cpp
+++ b/zircon/system/core/devmgr/fshost/block-device.cpp
@@ -108,26 +108,6 @@ int UnsealZxcryptThread(void* arg) {
     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)
@@ -209,8 +189,12 @@ zx_status_t BlockDevice::UnsealZxcrypt() {
     return ZX_OK;
 }
 
+bool BlockDevice::ShouldCheckFilesystems() {
+    return mounter_->ShouldCheckFilesystems();
+}
+
 zx_status_t BlockDevice::CheckFilesystem() {
-    if (!getenv_bool("zircon.system.filesystem-check", false)) {
+    if (!ShouldCheckFilesystems()) {
         return ZX_OK;
     }
 
diff --git a/zircon/system/core/devmgr/fshost/block-device.h b/zircon/system/core/devmgr/fshost/block-device.h
index c12e9df57f3..980973641b5 100644
--- a/zircon/system/core/devmgr/fshost/block-device.h
+++ b/zircon/system/core/devmgr/fshost/block-device.h
@@ -30,6 +30,7 @@ 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;
+    bool ShouldCheckFilesystems() final;
     zx_status_t CheckFilesystem() final;
     zx_status_t FormatFilesystem() final;
     zx_status_t MountFilesystem() final;
diff --git a/zircon/system/core/devmgr/fshost/block-watcher-test.cpp b/zircon/system/core/devmgr/fshost/block-watcher-test.cpp
index b2a6c475ff8..b971e9a571d 100644
--- a/zircon/system/core/devmgr/fshost/block-watcher-test.cpp
+++ b/zircon/system/core/devmgr/fshost/block-watcher-test.cpp
@@ -34,6 +34,9 @@ public:
     zx_status_t UnsealZxcrypt() override {
         ZX_PANIC("Test should not invoke function %s\n", __FUNCTION__);
     }
+    bool ShouldCheckFilesystems() 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__);
     }
diff --git a/zircon/system/core/devmgr/fshost/block-watcher.cpp b/zircon/system/core/devmgr/fshost/block-watcher.cpp
index 4266d5d4f3f..ac5b37547bd 100644
--- a/zircon/system/core/devmgr/fshost/block-watcher.cpp
+++ b/zircon/system/core/devmgr/fshost/block-watcher.cpp
@@ -66,8 +66,8 @@ zx_status_t BlockDeviceCallback(int dirfd, int event, const char* name, void* co
 
 } // namespace
 
-void BlockDeviceWatcher(std::unique_ptr<FsManager> fshost, bool netboot) {
-    FilesystemMounter mounter(std::move(fshost), netboot);
+void BlockDeviceWatcher(std::unique_ptr<FsManager> fshost, bool netboot, bool check_filesystems) {
+    FilesystemMounter mounter(std::move(fshost), netboot, check_filesystems);
 
     fbl::unique_fd dirfd(open(kPathBlockDeviceRoot, O_DIRECTORY | O_RDONLY));
     if (dirfd) {
diff --git a/zircon/system/core/devmgr/fshost/block-watcher.h b/zircon/system/core/devmgr/fshost/block-watcher.h
index 115a2603629..015946e675e 100644
--- a/zircon/system/core/devmgr/fshost/block-watcher.h
+++ b/zircon/system/core/devmgr/fshost/block-watcher.h
@@ -11,6 +11,6 @@
 namespace devmgr {
 
 // Monitors "/dev/class/block" for new devices indefinitely.
-void BlockDeviceWatcher(std::unique_ptr<FsManager> fshost, bool netboot);
+void BlockDeviceWatcher(std::unique_ptr<FsManager> fshost, bool netboot, bool check_filesystems);
 
 } // namespace devmgr
diff --git a/zircon/system/core/devmgr/fshost/filesystem-mounter.h b/zircon/system/core/devmgr/fshost/filesystem-mounter.h
index 656ab6a420b..092a5eb3c17 100644
--- a/zircon/system/core/devmgr/fshost/filesystem-mounter.h
+++ b/zircon/system/core/devmgr/fshost/filesystem-mounter.h
@@ -19,8 +19,9 @@ namespace devmgr {
 // 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) {}
+    FilesystemMounter(std::unique_ptr<FsManager> fshost, bool netboot,
+                      bool check_filesystems)
+        : fshost_(std::move(fshost)), netboot_(netboot), check_filesystems_(check_filesystems) {}
 
     void FuchsiaStart() const { fshost_->FuchsiaStart(); }
 
@@ -29,6 +30,7 @@ public:
     }
 
     bool Netbooting() const { return netboot_; }
+    bool ShouldCheckFilesystems() const { return check_filesystems_; }
 
     // Attempts to mount a block device backed by |fd| to "/data".
     // Fails if already mounted.
@@ -44,7 +46,8 @@ public:
 
 private:
     std::unique_ptr<FsManager> fshost_;
-    bool netboot_ = false;
+    const bool netboot_ = false;
+    const bool check_filesystems_ = false;
     bool data_mounted_ = false;
     bool install_mounted_ = false;
     bool blob_mounted_ = false;
diff --git a/zircon/system/core/devmgr/fshost/fs-manager.h b/zircon/system/core/devmgr/fshost/fs-manager.h
index cca2ddeb3c0..bc9ec5a8295 100644
--- a/zircon/system/core/devmgr/fshost/fs-manager.h
+++ b/zircon/system/core/devmgr/fshost/fs-manager.h
@@ -14,7 +14,7 @@
 #include <zircon/thread_annotations.h>
 #include <zircon/types.h>
 
-#include "../shared/env.h"
+// Used for fshost signals.
 #include "../shared/fdio.h"
 
 #include "registry.h"
diff --git a/zircon/system/core/devmgr/fshost/main.cpp b/zircon/system/core/devmgr/fshost/main.cpp
index b4391dd29fd..f89984a6ee7 100644
--- a/zircon/system/core/devmgr/fshost/main.cpp
+++ b/zircon/system/core/devmgr/fshost/main.cpp
@@ -22,6 +22,8 @@
 #include <zircon/processargs.h>
 #include <zircon/status.h>
 
+#include "../shared/env.h"
+
 #include "block-watcher.h"
 #include "fs-manager.h"
 
@@ -218,7 +220,8 @@ int main(int argc, char** argv) {
     }
 
     if (!disable_block_watcher) {
-        BlockDeviceWatcher(std::move(fs_manager), netboot);
+        bool check_filesystems = devmgr::getenv_bool("zircon.system.filesystem-check", false);
+        BlockDeviceWatcher(std::move(fs_manager), netboot, check_filesystems);
     } else {
         // Keep the process alive so that the loader service continues to be supplied
         // to the devmgr. Otherwise the devmgr will segfault.
diff --git a/zircon/system/ulib/minfs/minfs.cpp b/zircon/system/ulib/minfs/minfs.cpp
index 0bd712f4479..300099d9c0c 100644
--- a/zircon/system/ulib/minfs/minfs.cpp
+++ b/zircon/system/ulib/minfs/minfs.cpp
@@ -126,7 +126,7 @@ zx_status_t CheckSuperblock(const Superblock* info, Bcache* bc) {
 
     TransactionLimits limits(*info);
     if ((info->flags & kMinfsFlagFVM) == 0) {
-        if (info->dat_block + info->block_count > max) {
+        if (info->dat_block + info->block_count != max) {
             FS_TRACE_ERROR("minfs: too large for device\n");
             return ZX_ERR_INVALID_ARGS;
         }
-- 
GitLab