[fs-managment] Add support for spawning ram-nand-ctl in an isolated devmgr.

Tests will be converted to use this in future changes. It will allow
tests to be more hermetic.

Tested: runtests -t install-disk-image-test; runtests -t nand-test
Change-Id: I7ddb199ab5325ed7853e8329b5f74b6a00073dc2
diff --git a/system/dev/nand/broker/rules.mk b/system/dev/nand/broker/rules.mk
index 5b3de27..6ef1b3d 100644
--- a/system/dev/nand/broker/rules.mk
+++ b/system/dev/nand/broker/rules.mk
@@ -53,6 +53,8 @@
     $(TEST_DIR)/parent.cpp \
 
 MODULE_STATIC_LIBS := \
+    system/ulib/devmgr-integration-test \
+    system/ulib/devmgr-launcher \
     system/ulib/fbl \
     system/ulib/fzl \
     system/ulib/zx \
@@ -85,8 +87,8 @@
     $(TEST_DIR)/parent.cpp \
 
 MODULE_STATIC_LIBS := \
-    system/ulib/devmgr-integration-test \
-    system/ulib/devmgr-launcher \
+	system/ulib/devmgr-integration-test \
+	system/ulib/devmgr-launcher \
     system/ulib/fbl \
     system/ulib/fzl \
     system/ulib/zx \
diff --git a/system/dev/nand/broker/test/parent.h b/system/dev/nand/broker/test/parent.h
index 75ce323..ac8a878 100644
--- a/system/dev/nand/broker/test/parent.h
+++ b/system/dev/nand/broker/test/parent.h
@@ -36,7 +36,7 @@
     bool IsBroker() const { return config_.is_broker; }
 
     // Returns a file descriptor for the device.
-    int get() const { return ram_nand_ ? ram_nand_->fd() : device_.get(); }
+    int get() { return ram_nand_ ? ram_nand_->fd().get() : device_.get(); }
 
     const zircon_nand_Info& Info() const { return config_.info; }
     void SetInfo(const zircon_nand_Info& info);
@@ -48,7 +48,7 @@
     uint32_t FirstBlock() const { return config_.first_block; }
 
   private:
-    std::unique_ptr<fs_mgmt::RamNand> ram_nand_;
+    std::optional<fs_mgmt::RamNand> ram_nand_;
     fbl::unique_fd device_;
     TestConfig config_;
     fbl::StringBuffer<PATH_MAX> path_;
diff --git a/system/dev/nand/ram-nand/rules.mk b/system/dev/nand/ram-nand/rules.mk
index 1e61496..b8a0347 100644
--- a/system/dev/nand/ram-nand/rules.mk
+++ b/system/dev/nand/ram-nand/rules.mk
@@ -54,6 +54,8 @@
 
 MODULE_STATIC_LIBS := \
     system/dev/lib/fake_ddk \
+    system/ulib/devmgr-integration-test \
+    system/ulib/devmgr-launcher \
     system/ulib/fbl \
     system/ulib/fzl \
     system/ulib/ddk \
diff --git a/system/dev/nand/ram-nand/test/ram-nand-ctl.cpp b/system/dev/nand/ram-nand/test/ram-nand-ctl.cpp
index 9264d5e..9dc22a7 100644
--- a/system/dev/nand/ram-nand/test/ram-nand-ctl.cpp
+++ b/system/dev/nand/ram-nand/test/ram-nand-ctl.cpp
@@ -30,7 +30,7 @@
         if (fs_mgmt::RamNand::Create(&config, &ram_nand_) == ZX_OK) {
             // caller_ want's to own the device, so we re-open it even though
             // ram_nand_ already has it open.
-            fbl::unique_fd device(dup(ram_nand_->fd()));
+            fbl::unique_fd device(dup(ram_nand_->fd().get()));
             caller_.reset(std::move(device));
         }
     }
@@ -39,11 +39,11 @@
 
     bool IsValid() const { return caller_ ? true : false; }
 
-    const char* path() const { return ram_nand_->path(); }
+    const char* path() { return ram_nand_->path(); }
 
   private:
 
-    std::unique_ptr<fs_mgmt::RamNand> ram_nand_;
+    std::optional<fs_mgmt::RamNand> ram_nand_;
     fzl::FdioCaller caller_;
     DISALLOW_COPY_ASSIGN_AND_MOVE(NandDevice);
 };
diff --git a/system/uapp/disk-pave/rules.mk b/system/uapp/disk-pave/rules.mk
index 36d5623..c4de6db 100644
--- a/system/uapp/disk-pave/rules.mk
+++ b/system/uapp/disk-pave/rules.mk
@@ -76,6 +76,8 @@
     system/ulib/block-client \
     system/ulib/chromeos-disk-setup \
     system/ulib/ddk \
+    system/ulib/devmgr-integration-test \
+    system/ulib/devmgr-launcher \
     system/ulib/fbl \
     system/ulib/fs-management \
     system/ulib/fzl \
diff --git a/system/uapp/disk-pave/test/device-partitioner-test.cpp b/system/uapp/disk-pave/test/device-partitioner-test.cpp
index f81acc9..ccbc4cf 100644
--- a/system/uapp/disk-pave/test/device-partitioner-test.cpp
+++ b/system/uapp/disk-pave/test/device-partitioner-test.cpp
@@ -250,7 +250,7 @@
 
         zircon_nand_RamNandInfo info = kNandInfo;
         info.vmo = dup.release();
-        std::unique_ptr<fs_mgmt::RamNand> ram_nand;
+        std::optional<fs_mgmt::RamNand> ram_nand;
         ASSERT_EQ(fs_mgmt::RamNand::Create(&info, &ram_nand), ZX_OK);
         ASSERT_TRUE(InsertTestDevices(ram_nand->path(), true));
         device->reset(new SkipBlockDevice(std::move(*ram_nand), std::move(mapper)));
diff --git a/system/uapp/nand-loader/main.cpp b/system/uapp/nand-loader/main.cpp
index 4a9f868..bc3d7ab 100644
--- a/system/uapp/nand-loader/main.cpp
+++ b/system/uapp/nand-loader/main.cpp
@@ -153,7 +153,7 @@
         return -1;
     }
 
-    std::unique_ptr<fs_mgmt::RamNand> ram_nand;
+    std::optional<fs_mgmt::RamNand> ram_nand;
     if (fs_mgmt::RamNand::Create(&ram_nand_config, &ram_nand) != ZX_OK) {
         printf("Unable to load device\n");
         return -1;
diff --git a/system/uapp/nand-loader/rules.mk b/system/uapp/nand-loader/rules.mk
index fb7569b..beb5857 100644
--- a/system/uapp/nand-loader/rules.mk
+++ b/system/uapp/nand-loader/rules.mk
@@ -13,6 +13,8 @@
     $(LOCAL_DIR)/main.cpp \
 
 MODULE_STATIC_LIBS := \
+    system/ulib/devmgr-integration-test \
+    system/ulib/devmgr-launcher \
     system/ulib/fbl \
     system/ulib/fzl \
     system/ulib/zx \
diff --git a/system/ulib/fs-management/include/fs-management/ram-nand.h b/system/ulib/fs-management/include/fs-management/ram-nand.h
index 0bb039a..f80b372 100644
--- a/system/ulib/fs-management/include/fs-management/ram-nand.h
+++ b/system/ulib/fs-management/include/fs-management/ram-nand.h
@@ -5,25 +5,56 @@
 #pragma once
 
 #include <memory>
+#include <optional>
 
 #include <inttypes.h>
 
+#include <fbl/ref_counted.h>
+#include <fbl/ref_ptr.h>
 #include <fbl/string.h>
-#include <fbl/string_piece.h>
-#include <fbl/unique_fd.h>
+#include <lib/devmgr-integration-test/fixture.h>
 #include <zircon/compiler.h>
 #include <zircon/nand/c/fidl.h>
 
 namespace fs_mgmt {
 
+class RamNand;
+
+class RamNandCtl : public fbl::RefCounted<RamNandCtl> {
+public:
+    // Creates an isolated devmgr and spawns a ram_nand_ctl device in it.
+    static zx_status_t Create(fbl::RefPtr<RamNandCtl>* out);
+
+    ~RamNandCtl() = default;
+
+    const fbl::unique_fd& fd() { return ctl_; }
+    const fbl::unique_fd& devfs_root() { return devmgr_->devfs_root(); }
+
+private:
+    RamNandCtl(fbl::unique_ptr<devmgr_integration_test::IsolatedDevmgr> devmgr,
+               fbl::unique_fd ctl)
+        : devmgr_(std::move(devmgr)), ctl_(std::move(ctl)) {}
+
+    fbl::unique_ptr<devmgr_integration_test::IsolatedDevmgr> devmgr_;
+    fbl::unique_fd ctl_;
+};
+
 class RamNand {
 public:
-    // Creates a ram_nand.
-    static zx_status_t Create(const zircon_nand_RamNandInfo* config, std::unique_ptr<RamNand>* out);
+    // Creates a ram_nand under ram_nand_ctl running under the main devmgr.
+    static zx_status_t Create(const zircon_nand_RamNandInfo* config, std::optional<RamNand>* out);
+
+    // Creates a ram_nand device underneath the ram_nand_ctl.
+    static zx_status_t Create(fbl::RefPtr<RamNandCtl> ctl, const zircon_nand_RamNandInfo* config,
+                              std::optional<RamNand>* out);
+
+    // Creates a ram_nand_ctl device and then a ram_device underneath.
+    static zx_status_t CreateIsolated(const zircon_nand_RamNandInfo* config,
+                                      std::optional<RamNand>* out);
 
     // Not copyable.
-    RamNand(RamNand&) = delete;
-    RamNand& operator=(RamNand&) = delete;
+    RamNand(const RamNand&) = delete;
+    RamNand& operator=(const RamNand&) = delete;
 
     // Movable.
     RamNand(RamNand&&) = default;
@@ -34,16 +65,29 @@
     // Don't unbind in destructor.
     void NoUnbind() { unbind = false; }
 
-    int fd() { return fd_.get(); }
-    const char* path() { return path_.c_str(); }
+    const fbl::unique_fd& fd() { return fd_; }
+    const char* path() {
+        if (path_) {
+            return path_->c_str();
+        }
+        return nullptr;
+    }
 
 private:
-    RamNand(fbl::StringPiece path, fbl::unique_fd fd)
-        : path_(std::move(path)), fd_(std::move(fd)) {}
+    RamNand(fbl::unique_fd fd, fbl::RefPtr<RamNandCtl> ctl)
+        : fd_(std::move(fd)), path_(std::nullopt), parent_(ctl) {}
 
-    fbl::String path_;
+    RamNand(fbl::unique_fd fd, fbl::String path)
+        : fd_(std::move(fd)), path_(path), parent_(nullptr) {}
+
     fbl::unique_fd fd_;
     bool unbind = true;
+
+    // Only valid if not spawned in an isolated devmgr.
+    std::optional<fbl::String> path_;
+
+    // Optional parent if spawned in an isolated devmgr.
+    fbl::RefPtr<RamNandCtl> parent_;
 };
 
 } // namespace fs_mgmt
diff --git a/system/ulib/fs-management/ram-nand.cpp b/system/ulib/fs-management/ram-nand.cpp
index 2c06da1..babff5a 100644
--- a/system/ulib/fs-management/ram-nand.cpp
+++ b/system/ulib/fs-management/ram-nand.cpp
@@ -8,11 +8,12 @@
 #include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <string.h>
 
+#include <fbl/auto_call.h>
 #include <fbl/string_buffer.h>
 #include <fbl/unique_fd.h>
 #include <lib/fzl/fdio.h>
+#include <lib/fdio/util.h>
 #include <zircon/device/device.h>
 #include <zircon/types.h>
 #include <zircon/nand/c/fidl.h>
@@ -27,23 +28,53 @@
 
 namespace fs_mgmt {
 
-zx_status_t RamNand::Create(const zircon_nand_RamNandInfo* config, std::unique_ptr<RamNand>* out) {
+zx_status_t RamNandCtl::Create(fbl::RefPtr<RamNandCtl>* out) {
+    devmgr_launcher::Args args;
+    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");
+
+    fbl::unique_ptr<devmgr_integration_test::IsolatedDevmgr> devmgr;
+    zx_status_t st = devmgr_integration_test::IsolatedDevmgr::Create(std::move(args), &devmgr);
+    if (st != ZX_OK) {
+        fprintf(stderr, "Could not create ram_nand_ctl device, %d\n", st);
+        return st;
+    }
+
+    fbl::unique_fd ctl;
+    st = devmgr_integration_test::RecursiveWaitForFile(devmgr->devfs_root(), "misc/nand-ctl",
+                                                       zx::deadline_after(zx::sec(5)), &ctl);
+    if (st != ZX_OK) {
+        fprintf(stderr, "ram_nand_ctl device failed enumerated, %d\n", st);
+        return st;
+    }
+
+    *out = fbl::AdoptRef(new RamNandCtl(std::move(devmgr), std::move(ctl)));
+    return ZX_OK;
+}
+
+zx_status_t RamNand::Create(const zircon_nand_RamNandInfo* config, std::optional<RamNand>* out) {
     fbl::unique_fd control(open(kBasePath, O_RDWR));
-    fzl::FdioCaller caller(std::move(control));
+
+    zx::channel ctl_svc;
+    zx_status_t st = fdio_get_service_handle(control.release(),
+                                             ctl_svc.reset_and_get_address());
+
     char name[zircon_nand_NAME_LEN + 1];
     size_t out_name_size;
     zx_status_t status;
-    zx_status_t st = zircon_nand_RamNandCtlCreateDevice(caller.borrow_channel(), config, &status,
-                                                        name, zircon_nand_NAME_LEN, &out_name_size);
+    st = zircon_nand_RamNandCtlCreateDevice(ctl_svc.get(), config, &status, name,
+                                            zircon_nand_NAME_LEN, &out_name_size);
     if (st != ZX_OK || status != ZX_OK) {
         st = st != ZX_OK ? st : status;
         fprintf(stderr, "Could not create ram_nand device, %d\n", st);
         return st;
     }
     name[out_name_size] = '\0';
-
     fbl::StringBuffer<PATH_MAX> path;
-    path.AppendPrintf("%s/%s", kBasePath, name);
+    path.Append(kBasePath);
+    path.Append("/");
+    path.Append(name);
 
     fbl::unique_fd ram_nand(open(path.c_str(), O_RDWR));
     if (!ram_nand) {
@@ -51,10 +82,63 @@
         return ZX_ERR_INTERNAL;
     }
 
-    out->reset(new RamNand(path.ToStringPiece(), std::move(ram_nand)));
+    *out = RamNand(std::move(ram_nand), path.ToString());
     return ZX_OK;
 }
 
+zx_status_t RamNand::Create(fbl::RefPtr<RamNandCtl> ctl, const zircon_nand_RamNandInfo* config,
+                            std::optional<RamNand>* out) {
+
+    fdio_t* io = fdio_unsafe_fd_to_io(ctl->fd().get());
+    if (io == NULL) {
+        fprintf(stderr, "Could not get fdio object\n");
+        return ZX_ERR_INTERNAL;
+    }
+    zx_handle_t ctl_svc = fdio_unsafe_borrow_channel(io);
+
+    char name[zircon_nand_NAME_LEN + 1];
+    size_t out_name_size;
+    zx_status_t status;
+    zx_status_t st = zircon_nand_RamNandCtlCreateDevice(ctl_svc, config, &status, name,
+                                                        zircon_nand_NAME_LEN, &out_name_size);
+    fdio_unsafe_release(io);
+    if (st != ZX_OK || status != ZX_OK) {
+        st = st != ZX_OK ? st : status;
+        fprintf(stderr, "Could not create ram_nand device, %d\n", st);
+        return st;
+    }
+    name[out_name_size] = '\0';
+
+    // TODO(ZX-3193): We should be able to open relative to ctl->fd(), but
+    // due to a bug, we have to be relative to devfs_root instead.
+    fbl::StringBuffer<PATH_MAX> path;
+    path.Append("misc/nand-ctl/");
+    path.Append(name);
+    fprintf(stderr, "Trying to open (%s)\n", path.c_str());
+
+    // TODO(ZX-3192): We should use RecursiveWaitForFile here but it doesn't seem to
+    // work, so we sleep instead.
+    sleep(1);
+    fbl::unique_fd fd(openat(ctl->devfs_root().get(), path.c_str(), O_RDWR));
+    if (!fd) {
+        fprintf(stderr, "Could not open ram_nand\n");
+        return ZX_ERR_IO;
+    }
+
+    *out = RamNand(std::move(fd), std::move(ctl));
+    return ZX_OK;
+}
+
+zx_status_t RamNand::CreateIsolated(const zircon_nand_RamNandInfo* config,
+                                    std::optional<RamNand>* out) {
+    fbl::RefPtr<RamNandCtl> ctl;
+    zx_status_t st = RamNandCtl::Create(&ctl);
+    if (st != ZX_OK) {
+        return st;
+    }
+    return Create(std::move(ctl), config, out);
+}
+
 RamNand::~RamNand() {
     if (unbind && fd_) {
       zx_status_t status = static_cast<zx_status_t>(ioctl_device_unbind(fd_.get()));
diff --git a/system/ulib/fs-management/rules.mk b/system/ulib/fs-management/rules.mk
index 23e3db3..2915ec5 100644
--- a/system/ulib/fs-management/rules.mk
+++ b/system/ulib/fs-management/rules.mk
@@ -21,6 +21,8 @@
     system/ulib/fbl \
     system/ulib/fvm \
     system/ulib/fs \
+    system/ulib/devmgr-integration-test \
+    system/ulib/devmgr-launcher \
     system/ulib/digest \
     system/ulib/ddk \
     system/ulib/gpt \