[devcoordinator] run fshost and appmgr as components

Remove fshost and appmgr logic from devcoordinator, and run them as v2
components under component manager.

Due to this change the isolated devmgr library must launch a new
component manager which launches devcoordinator and fshost as
components.

Change-Id: I8c919c512e1700d2728ba1e8ce3978051514e1e6
diff --git a/src/devices/coordinator/coordinator.cc b/src/devices/coordinator/coordinator.cc
index 4404800..136352c 100644
--- a/src/devices/coordinator/coordinator.cc
+++ b/src/devices/coordinator/coordinator.cc
@@ -64,7 +64,7 @@
 constexpr char kBootFirmwarePath[] = "/boot/lib/firmware";
 constexpr char kSystemFirmwarePath[] = "/system/lib/firmware";
 constexpr char kItemsPath[] = "/svc/" fuchsia_boot_Items_Name;
-constexpr char kFshostAdminPath[] = "/fshost/svc/fuchsia.fshost.Admin";
+constexpr char kFshostAdminPath[] = "/svc/fuchsia.fshost.Admin";
 
 std::unique_ptr<llcpp::fuchsia::fshost::Admin::SyncClient> ConnectToFshostAdminServer() {
   zx::channel local, remote;
@@ -1336,25 +1336,26 @@
   auto task = SuspendTask::Create(sys_device(), ctx.sflags(), std::move(completion));
   suspend_context().set_task(std::move(task));
 
-  auto status = async::PostDelayedTask(dispatcher(),
-                                       [this, callback_info = std::move(callback_info)] {
-                                         if (!InSuspend()) {
-                                           return;  // Suspend failed to complete.
-                                         }
-                                         auto& ctx = suspend_context();
-                                         log(ERROR, "devcoordinator: DEVICE SUSPEND TIMED OUT\n");
-                                         log(ERROR, "  sflags: 0x%08x\n", ctx.sflags());
-                                         dump_suspend_task_dependencies(ctx.task());
-                                         if (suspend_fallback()) {
-                                           ::suspend_fallback(root_resource(), ctx.sflags());
-                                           // Unless in test env, we should not reach here.
-                                           if (callback_info->callback) {
-                                             callback_info->callback(ZX_ERR_TIMED_OUT);
-                                             callback_info->callback = nullptr;
-                                           }
-                                         }
-                                       },
-                                       config_.suspend_timeout);
+  auto status = async::PostDelayedTask(
+      dispatcher(),
+      [this, callback_info = std::move(callback_info)] {
+        if (!InSuspend()) {
+          return;  // Suspend failed to complete.
+        }
+        auto& ctx = suspend_context();
+        log(ERROR, "devcoordinator: DEVICE SUSPEND TIMED OUT\n");
+        log(ERROR, "  sflags: 0x%08x\n", ctx.sflags());
+        dump_suspend_task_dependencies(ctx.task());
+        if (suspend_fallback()) {
+          ::suspend_fallback(root_resource(), ctx.sflags());
+          // Unless in test env, we should not reach here.
+          if (callback_info->callback) {
+            callback_info->callback(ZX_ERR_TIMED_OUT);
+            callback_info->callback = nullptr;
+          }
+        }
+      },
+      config_.suspend_timeout);
   if (status != ZX_OK) {
     log(ERROR, "devcoordinator: Failed to create suspend timeout watchdog\n");
   }
@@ -1404,19 +1405,20 @@
   }
 
   // Post a delayed task in case drivers do not complete the resume.
-  auto status = async::PostDelayedTask(dispatcher(),
-                                       [this, callback] {
-                                         if (!InResume()) {
-                                           return;
-                                         }
-                                         log(ERROR, "devcoordinator: SYSTEM RESUME TIMED OUT\n");
-                                         callback(ZX_ERR_TIMED_OUT);
-                                         // TODO(ravoorir): Figure out what is the best strategy
-                                         // of for recovery here. Should we put back all devices
-                                         // in suspend? In future, this could be more interactive
-                                         // with the UI.
-                                       },
-                                       config_.resume_timeout);
+  auto status = async::PostDelayedTask(
+      dispatcher(),
+      [this, callback] {
+        if (!InResume()) {
+          return;
+        }
+        log(ERROR, "devcoordinator: SYSTEM RESUME TIMED OUT\n");
+        callback(ZX_ERR_TIMED_OUT);
+        // TODO(ravoorir): Figure out what is the best strategy
+        // of for recovery here. Should we put back all devices
+        // in suspend? In future, this could be more interactive
+        // with the UI.
+      },
+      config_.resume_timeout);
   if (status != ZX_OK) {
     log(ERROR, "devcoordinator: Failure to create resume timeout watchdog\n");
   }
diff --git a/src/devices/coordinator/devhost-loader-service.cc b/src/devices/coordinator/devhost-loader-service.cc
index 054300c..7e59e94 100644
--- a/src/devices/coordinator/devhost-loader-service.cc
+++ b/src/devices/coordinator/devhost-loader-service.cc
@@ -98,7 +98,18 @@
     return status;
   }
   auto defer = fit::defer([ns] { fdio_ns_destroy(ns); });
-  status = fdio_ns_bind(ns, "/boot", system_instance->CloneFs("boot").release());
+  zx::channel boot_client, boot_server;
+  status = zx::channel::create(0, &boot_client, &boot_server);
+  if (status != ZX_OK) {
+    fprintf(stderr, "devcoordinator: failed to create channel %d\n", status);
+    return status;
+  }
+  status = fdio_open("/boot", FS_READONLY_DIR_FLAGS, boot_server.release());
+  if (status != ZX_OK) {
+    fprintf(stderr, "devcoordinator: failed to connect to /boot %d\n", status);
+    return status;
+  }
+  status = fdio_ns_bind(ns, "/boot", boot_client.release());
   if (status != ZX_OK) {
     fprintf(stderr, "devcoordinator: failed to bind namespace %d\n", status);
     return status;
diff --git a/src/devices/coordinator/fdio.cc b/src/devices/coordinator/fdio.cc
index 28f16b9..0a1295f 100644
--- a/src/devices/coordinator/fdio.cc
+++ b/src/devices/coordinator/fdio.cc
@@ -43,30 +43,28 @@
 
 // clang-format off
 
-static struct {
+const static struct {
     const char* mount;
     const char* name;
     uint32_t flags;
     FdioAction action;
 } FSTAB[] = {
-    { "/svc",       "svc",       FS_SVC,      FdioAction::AddNsEntry },
-    { "/hub",       "hub",       FS_HUB,      FdioAction::AddNsEntry },
-    { "/bin",       "bin",       FS_BIN,      FdioAction::AddNsEntry },
-    { "/dev",       "dev",       FS_DEV,      FdioAction::AddNsEntry },
+    { "/bin",       "bin",       FS_BIN,      FdioAction::CloneDir },
+    { "/blob",      "blob",      FS_BLOB,     FdioAction::CloneDir },
     { "/boot",      "boot",      FS_BOOT,     FdioAction::CloneDir },
-    { "/data",      "data",      FS_DATA,     FdioAction::AddNsEntry },
-    { "/system",    "system",    FS_SYSTEM,   FdioAction::AddNsEntry },
-    { "/install",   "install",   FS_INSTALL,  FdioAction::AddNsEntry },
-    { "/volume",    "volume",    FS_VOLUME,   FdioAction::AddNsEntry },
-    { "/blob",      "blob",      FS_BLOB,     FdioAction::AddNsEntry },
-    { "/pkgfs",     "pkgfs",     FS_PKGFS,    FdioAction::AddNsEntry },
-    { "/tmp",       "tmp",       FS_TMP,      FdioAction::AddNsEntry },
+    { "/data",      "data",      FS_DATA,     FdioAction::CloneDir },
+    { "/dev",       "dev",       FS_DEV,      FdioAction::AddNsEntry },
+    { "/hub",       "hub",       FS_HUB,      FdioAction::CloneDir },
+    { "/install",   "install",   FS_INSTALL,  FdioAction::CloneDir },
+    { "/pkgfs",     "pkgfs",     FS_PKGFS,    FdioAction::CloneDir },
+    { "/svc",       "svc",       FS_SVC,      FdioAction::AddNsEntry },
+    { "/system",    "system",    FS_SYSTEM,   FdioAction::CloneDir },
+    { "/tmp",       "tmp",       FS_TMP,      FdioAction::CloneDir },
+    { "/volume",    "volume",    FS_VOLUME,   FdioAction::CloneDir },
 };
 
 // clang-format on
 //
-void devmgr_disable_appmgr_services() { FSTAB[1].flags = 0; }
-
 FsProvider::~FsProvider() {}
 
 DevmgrLauncher::DevmgrLauncher(FsProvider* fs_provider) : fs_provider_(fs_provider) {}
diff --git a/src/devices/coordinator/fdio.h b/src/devices/coordinator/fdio.h
index e56a936..65c98ab 100644
--- a/src/devices/coordinator/fdio.h
+++ b/src/devices/coordinator/fdio.h
@@ -37,10 +37,9 @@
 // clang-format on
 
 #define FS_FOR_FSPROC (FS_SVC)
-#define FS_FOR_APPMGR (FS_ALL & (~FS_HUB))
 
-#define FS_READONLY_DIR_FLAGS \
-  (ZX_FS_RIGHT_READABLE | ZX_FS_RIGHT_ADMIN | ZX_FS_FLAG_DIRECTORY | ZX_FS_FLAG_NOREMOTE)
+// TODO: we might be able to remove NOREMOTE
+#define FS_READONLY_DIR_FLAGS (ZX_FS_RIGHT_READABLE | ZX_FS_FLAG_DIRECTORY)
 
 #define FS_READ_EXEC_DIR_FLAGS (FS_READONLY_DIR_FLAGS | ZX_FS_RIGHT_EXECUTABLE)
 #define FS_READ_WRITE_DIR_FLAGS (FS_READONLY_DIR_FLAGS | ZX_FS_RIGHT_WRITABLE)
@@ -101,8 +100,6 @@
   std::unique_ptr<char[]> raw_bytes_;
 };
 
-void devmgr_disable_appmgr_services();
-
 // The variable to set on the kernel command line to enable ld.so tracing
 // of the processes we launch.
 #define LDSO_TRACE_CMDLINE "ldso.trace"
diff --git a/src/devices/coordinator/main.cc b/src/devices/coordinator/main.cc
index 994f054..0ee60df 100644
--- a/src/devices/coordinator/main.cc
+++ b/src/devices/coordinator/main.cc
@@ -263,18 +263,6 @@
     return 1;
   }
 
-  status = system_instance.CreateFuchsiaJob(root_job);
-  if (status != ZX_OK) {
-    return 1;
-  }
-
-  zx::channel fshost_client, fshost_server;
-  status = zx::channel::create(0, &fshost_client, &fshost_server);
-  if (status != ZX_OK) {
-    fprintf(stderr, "devcoordinator: failed to create fshost channels %s\n",
-            zx_status_get_string(status));
-    return 1;
-  }
   status = system_instance.PrepareChannels();
   if (status != ZX_OK) {
     fprintf(stderr, "devcoordinator: failed to create other system channels %s\n",
@@ -283,8 +271,7 @@
   }
 
   if (devmgr_args.start_svchost) {
-    status = system_instance.StartSvchost(root_job, require_system, &coordinator,
-                                          std::move(fshost_client));
+    status = system_instance.StartSvchost(root_job, require_system, &coordinator);
     if (status != ZX_OK) {
       fprintf(stderr, "devcoordinator: failed to start svchost: %s\n",
               zx_status_get_string(status));
@@ -312,13 +299,7 @@
     }
   }
 
-  system_instance.devmgr_vfs_init(&coordinator, devmgr_args, std::move(fshost_server));
-
-  // If this is not a full Fuchsia build, do not setup appmgr services, as
-  // this will delay startup.
-  if (!require_system) {
-    devmgr::devmgr_disable_appmgr_services();
-  }
+  system_instance.devmgr_vfs_init();
 
   thrd_t t;
 
@@ -351,6 +332,7 @@
     status =
         devmgr::DevhostLoaderService::Create(loop.dispatcher(), &system_instance, &loader_service);
     if (status != ZX_OK) {
+      log(ERROR, "devcoordinator: failed to create loader service\n");
       return 1;
     }
     coordinator.set_loader_service_connector(
@@ -382,18 +364,6 @@
                         fit::bind_member(&coordinator, &devmgr::Coordinator::DriverAddedInit));
   }
 
-  // Special case early handling for the ramdisk boot
-  // path where /system is present before the coordinator
-  // starts.  This avoids breaking the "priority hack" and
-  // can be removed once the real driver priority system
-  // exists.
-  if (coordinator.system_available()) {
-    status = coordinator.ScanSystemDrivers();
-    if (status != ZX_OK) {
-      return 1;
-    }
-  }
-
   if (coordinator.require_system() && !coordinator.system_loaded()) {
     printf(
         "devcoordinator: full system required, ignoring fallback drivers until /system is "
@@ -408,9 +378,9 @@
   coordinator.BindDrivers();
 
   // Expose /dev directory for use in sysinfo service; specifically to connect to /dev/sys/platform
-  auto outgoing_dir = fbl::AdoptRef<fs::PseudoDir>(new fs::PseudoDir());
-  outgoing_dir->AddEntry(
-      "dev", fbl::AdoptRef<fs::RemoteDir>(new fs::RemoteDir(system_instance.CloneFs("dev"))));
+  auto outgoing_dir = fbl::MakeRefCounted<fs::PseudoDir>();
+  outgoing_dir->AddEntry("dev", fbl::MakeRefCounted<fs::RemoteDir>(system_instance.CloneFs("dev")));
+  outgoing_dir->AddEntry("svc", fbl::MakeRefCounted<fs::RemoteDir>(system_instance.CloneFs("svc")));
 
   fs::ManagedVfs outgoing_vfs = fs::ManagedVfs(loop.dispatcher());
   outgoing_vfs.ServeDirectory(outgoing_dir,
diff --git a/src/devices/coordinator/system-instance-test.cc b/src/devices/coordinator/system-instance-test.cc
index b69af41..6290931 100644
--- a/src/devices/coordinator/system-instance-test.cc
+++ b/src/devices/coordinator/system-instance-test.cc
@@ -90,23 +90,4 @@
   std::unique_ptr<SystemInstanceForTest> under_test_;
 };
 
-// Test that asking SystemInstance's FsProvider impl for blob opens /fs/blob from the current
-// installed namespace without the EXEC right
-TEST_F(SystemInstanceFsProvider, CloneBlobNonExec) {
-  // Importantly, this list of expected_flags lacks ZX_FS_RIGHT_EXECUTABLE
-  uint32_t expected_flags = ZX_FS_RIGHT_READABLE | ZX_FS_RIGHT_WRITABLE | ZX_FS_RIGHT_ADMIN |
-                            ZX_FS_FLAG_DIRECTORY | ZX_FS_FLAG_NOREMOTE;
-  CloneFsAndCheckFlags("blob", expected_flags);
-}
-
-// Cloning /pkgfs should provide READ | EXEC rights, as should /bin and /pkgfs which are both paths
-// into pkgfs
-TEST_F(SystemInstanceFsProvider, ClonePkgfs) {
-  uint32_t expected_flags = ZX_FS_RIGHT_READABLE | ZX_FS_RIGHT_EXECUTABLE | ZX_FS_RIGHT_ADMIN |
-                            ZX_FS_FLAG_DIRECTORY | ZX_FS_FLAG_NOREMOTE;
-  CloneFsAndCheckFlags("pkgfs", expected_flags);
-  CloneFsAndCheckFlags("bin", expected_flags);
-  CloneFsAndCheckFlags("system", expected_flags);
-}
-
 }  // namespace
diff --git a/src/devices/coordinator/system-instance.cc b/src/devices/coordinator/system-instance.cc
index 880b239..dc1d53d 100644
--- a/src/devices/coordinator/system-instance.cc
+++ b/src/devices/coordinator/system-instance.cc
@@ -101,56 +101,8 @@
   return ZX_OK;
 }
 
-zx_status_t SystemInstance::CreateFuchsiaJob(const zx::job& root_job) {
-  zx_status_t status = zx::job::create(root_job, 0u, &fuchsia_job_);
-  if (status != ZX_OK) {
-    printf("devcoordinator: unable to create fuchsia job: %d (%s)\n", status,
-           zx_status_get_string(status));
-    return status;
-  }
-
-  fuchsia_job_.set_property(ZX_PROP_NAME, "fuchsia", 7);
-
-  const zx_policy_basic_v2_t basic_policy[] = {
-      // Lock down process creation. Child tasks must use fuchsia.process.Launcher.
-      {.condition = ZX_POL_NEW_PROCESS,
-       .action = ZX_POL_ACTION_DENY,
-       .flags = ZX_POL_OVERRIDE_DENY}};
-
-  status = fuchsia_job_.set_policy(ZX_JOB_POL_RELATIVE, ZX_JOB_POL_BASIC_V2, basic_policy,
-                                   fbl::count_of(basic_policy));
-  if (status != ZX_OK) {
-    printf("devcoordinator: unable to set basic policy for fuchsia job: %d (%s)\n", status,
-           zx_status_get_string(status));
-    return status;
-  }
-
-  // Set the minimum timer slack amount and default mode. The amount should be large enough to
-  // allow for some coalescing of timers, but small enough to ensure applications don't miss
-  // deadlines.
-  //
-  // Why LATE and not CENTER or EARLY? Timers firing a little later than requested is not uncommon
-  // in non-realtime systems. Programs are generally tolerant of some delays. However, timers
-  // firing before their dealine can be unexpected and lead to bugs.
-  const zx_policy_timer_slack_t timer_slack_policy{ZX_USEC(500), ZX_TIMER_SLACK_LATE, {}};
-
-  status =
-      fuchsia_job_.set_policy(ZX_JOB_POL_RELATIVE, ZX_JOB_POL_TIMER_SLACK, &timer_slack_policy, 1);
-  if (status != ZX_OK) {
-    printf("devcoordinator: unable to set timer slack policy for fuchsia job: %d (%s)\n", status,
-           zx_status_get_string(status));
-    return status;
-  }
-
-  return ZX_OK;
-}
-
 zx_status_t SystemInstance::PrepareChannels() {
   zx_status_t status;
-  status = zx::channel::create(0, &appmgr_client_, &appmgr_server_);
-  if (status != ZX_OK) {
-    return status;
-  }
   status = zx::channel::create(0, &miscsvc_client_, &miscsvc_server_);
   if (status != ZX_OK) {
     return status;
@@ -164,8 +116,7 @@
 }
 
 zx_status_t SystemInstance::StartSvchost(const zx::job& root_job, bool require_system,
-                                         devmgr::Coordinator* coordinator,
-                                         zx::channel fshost_client) {
+                                         devmgr::Coordinator* coordinator) {
   zx::channel dir_request, svchost_local;
   zx_status_t status = zx::channel::create(0, &dir_request, &svchost_local);
   if (status != ZX_OK) {
@@ -178,20 +129,6 @@
     return status;
   }
 
-  zx::channel appmgr_svc;
-  {
-    zx::channel appmgr_svc_req;
-    status = zx::channel::create(0, &appmgr_svc_req, &appmgr_svc);
-    if (status != ZX_OK) {
-      return status;
-    }
-
-    status = fdio_service_connect_at(appmgr_client_.get(), "svc", appmgr_svc_req.release());
-    if (status != ZX_OK) {
-      return status;
-    }
-  }
-
   zx::job root_job_copy;
   status =
       root_job.duplicate(ZX_RIGHTS_BASIC | ZX_RIGHTS_IO | ZX_RIGHTS_PROPERTY | ZX_RIGHT_ENUMERATE |
@@ -318,12 +255,6 @@
       .h = {.id = PA_HND(PA_FD, FDIO_FLAG_USE_FOR_STDIO), .handle = logger.release()},
   });
 
-  // Remove once svchost hosts the fuchsia.tracing.provider service itself.
-  actions.push_back((fdio_spawn_action_t){
-      .action = FDIO_SPAWN_ACTION_ADD_HANDLE,
-      .h = {.id = PA_HND(PA_USER0, 0), .handle = appmgr_svc.release()},
-  });
-
   // Give svchost a restricted root job handle. svchost is already a privileged system service
   // as it controls system-wide process launching. With the root job it can consolidate a few
   // services such as crashsvc and the profile service.
@@ -341,21 +272,12 @@
     });
   }
 
-  // TODO(smklein): Merge "coordinator_client" (proxying requests to devmgr) and
-  // "fshost_client" (proxying requests to fshost) into one service provider
-  // PseudoDirectory.
-
   // Add handle to channel to allow svchost to proxy fidl services to us.
   actions.push_back((fdio_spawn_action_t){
       .action = FDIO_SPAWN_ACTION_ADD_HANDLE,
       .h = {.id = PA_HND(PA_USER0, 3), .handle = coordinator_client.release()},
   });
 
-  // Add a handle to allow svchost to proxy services to fshost.
-  actions.push_back((fdio_spawn_action_t){
-      .action = FDIO_SPAWN_ACTION_ADD_HANDLE,
-      .h = {.id = PA_HND(PA_USER0, 4), .handle = fshost_client.release()},
-  });
   if (!coordinator->boot_args().GetBool("virtcon.disable", false)) {
     // Add handle to channel to allow svchost to proxy fidl services to
     // virtcon.
@@ -440,68 +362,7 @@
   return ZX_OK;
 }
 
-// Binds common filesystems from fshost into our namespace. This is a temporary
-// workaround until fshost is run as a v2 component, as once that is complete
-// these paths will exist in devcoordinator's namespace when it is started.
-void bind_fshost_filesystems(zx::channel fshost_out_dir, zx::channel fshost_server, fdio_ns_t* ns) {
-  zx_status_t r;
-  if ((r = fdio_ns_bind(ns, "/fshost", fshost_out_dir.release())) != ZX_OK) {
-    printf("devcoordinator: cannot bind /fshost to namespace: %s\n", zx_status_get_string(r));
-    return;
-  }
-
-  const char* fstab[] = {
-      "/bin", "/data", "/system", "/install", "/volume", "/blob", "/pkgfs", "/tmp",
-  };
-  const uint32_t flags = ZX_FS_RIGHT_READABLE | ZX_FS_RIGHT_WRITABLE | ZX_FS_RIGHT_ADMIN |
-                         ZX_FS_FLAG_DIRECTORY | ZX_FS_RIGHT_EXECUTABLE;
-  for (unsigned n = 0; n < fbl::count_of(fstab); n++) {
-    zx::channel client, server;
-    if ((r = zx::channel::create(0, &server, &client)) != ZX_OK) {
-      printf("devcoordinator: failed to create channel: %s\n", zx_status_get_string(r));
-      return;
-    }
-    fbl::String fshost_path =
-        fbl::String::Concat({fbl::String("/fshost/fs"), fbl::String(fstab[n])});
-    if ((r = fdio_open(fshost_path.c_str(), flags, server.release())) != ZX_OK) {
-      printf("devcoordinator: cannot open %s: %s\n", fshost_path.c_str(), zx_status_get_string(r));
-      return;
-    }
-    if ((r = fdio_ns_bind(ns, fstab[n], client.release())) != ZX_OK) {
-      // Some of these may already exist if devcoordinator is run in a test
-      // environment
-      printf("devcoordinator: cannot bind %s to namespace: %s\n", fstab[n],
-             zx_status_get_string(r));
-      continue;
-    }
-  }
-
-  zx::channel delayed_system_client, delayed_system_server;
-  if ((r = zx::channel::create(0, &delayed_system_server, &delayed_system_client)) != ZX_OK) {
-    printf("devcoordinator: failed to create channel: %s\n", zx_status_get_string(r));
-    return;
-  }
-  printf("devcoordinator: opening /system-delayed\n");
-  if ((r = fdio_open("/fshost/delayed/fs/system", flags, delayed_system_server.release())) !=
-      ZX_OK) {
-    printf("devcoordinator: cannot open %s: %s\n", "/system-delayed", zx_status_get_string(r));
-    return;
-  }
-  printf("devcoordinator: successfully opened /system-delayed\n");
-  if ((r = fdio_ns_bind(ns, "/system-delayed", delayed_system_client.release())) != ZX_OK) {
-    printf("devcoordinator: cannot bind %s to namespace: %s\n", "/system-delayed",
-           zx_status_get_string(r));
-    return;
-  }
-
-  r = fdio_open("/fshost/fs-manager-svc", FS_READ_WRITE_DIR_FLAGS, fshost_server.release());
-  ZX_ASSERT_MSG(r == ZX_OK, "devcoordinator: cannot open /fshost/fs-manager-svc: %s\n",
-                zx_status_get_string(r));
-}
-
-void SystemInstance::devmgr_vfs_init(devmgr::Coordinator* coordinator,
-                                     const devmgr::DevmgrArgs& devmgr_args,
-                                     zx::channel fshost_server) {
+void SystemInstance::devmgr_vfs_init() {
   fdio_ns_t* ns;
   zx_status_t r;
   r = fdio_ns_get_installed(&ns);
@@ -509,9 +370,6 @@
   r = fdio_ns_bind(ns, "/dev", CloneFs("dev").release());
   ZX_ASSERT_MSG(r == ZX_OK, "devcoordinator: cannot bind /dev to namespace: %s\n",
                 zx_status_get_string(r));
-
-  zx::channel fshost_out_dir = fshost_start(coordinator, devmgr_args);
-  bind_fshost_filesystems(std::move(fshost_out_dir), std::move(fshost_server), ns);
 }
 
 // Thread entry point
@@ -718,11 +576,11 @@
   return args->instance->ServiceStarter(args->coordinator);
 }
 
-// Thread trampoline for FuchsiaStarter, which ServiceStarter spawns
-int fuchsia_starter(void* arg) {
+// Thread trampoline for WaitForSystemAvailable, which ServiceStarter spawns
+int wait_for_system_available(void* arg) {
   auto args = std::unique_ptr<SystemInstance::ServiceStarterArgs>(
       static_cast<SystemInstance::ServiceStarterArgs*>(arg));
-  return args->instance->FuchsiaStarter(args->coordinator);
+  return args->instance->WaitForSystemAvailable(args->coordinator);
 }
 
 int SystemInstance::ServiceStarter(devmgr::Coordinator* coordinator) {
@@ -867,7 +725,8 @@
   starter_args->instance = this;
   starter_args->coordinator = coordinator;
   thrd_t t;
-  int ret = thrd_create_with_name(&t, fuchsia_starter, starter_args.release(), "fuchsia-starter");
+  int ret = thrd_create_with_name(&t, wait_for_system_available, starter_args.release(),
+                                  "wait-for-system-available");
   if (ret == thrd_success) {
     thrd_detach(t);
   }
@@ -875,10 +734,13 @@
   return 0;
 }
 
-int SystemInstance::FuchsiaStarter(devmgr::Coordinator* coordinator) {
+int SystemInstance::WaitForSystemAvailable(devmgr::Coordinator* coordinator) {
   // Block this thread until /system-delayed is available. Note that this is
   // only used for coordinating events between fshost and devcoordinator, the
   // /system path is used for loading drivers and appmgr below.
+  // TODO: It's pretty wasteful to create a thread just so it can sit blocked in
+  // sync I/O opening '/system-delayed'. Once a simple async I/O wrapper exists
+  // this should switch to use that
   int fd = open("/system-delayed", O_RDONLY);
   if (!fd) {
     fprintf(stderr,
@@ -888,44 +750,13 @@
   }
   close(fd);
 
-  // we're starting the appmgr because /system is present
-  // so we also signal the device coordinator that those
-  // drivers are now loadable
+  // Load in drivers from /system
   coordinator->set_system_available(true);
   coordinator->ScanSystemDrivers();
 
-  const char* argv_appmgr[] = {
-      "/system/bin/appmgr",
-      nullptr,
-  };
-
-  zx::channel ldsvc;
-  zx_status_t status = clone_fshost_ldsvc(&ldsvc);
-  if (status != ZX_OK) {
-    fprintf(stderr, "devcoordinator: failed to clone fshost loader for appmgr: %d\n", status);
-    return 1;
-  }
-
-  unsigned int appmgr_hnd_count = 0;
-  zx_handle_t appmgr_hnds[2] = {};
-  uint32_t appmgr_ids[2] = {};
-  if (appmgr_server_.is_valid()) {
-    ZX_ASSERT(appmgr_hnd_count < fbl::count_of(appmgr_hnds));
-    appmgr_hnds[appmgr_hnd_count] = appmgr_server_.release();
-    appmgr_ids[appmgr_hnd_count] = PA_DIRECTORY_REQUEST;
-    appmgr_hnd_count++;
-  }
-  status =
-      launcher_.LaunchWithLoader(fuchsia_job_, "appmgr", zx::vmo(), std::move(ldsvc), argv_appmgr,
-                                 nullptr, -1, coordinator->root_resource(), appmgr_hnds, appmgr_ids,
-                                 appmgr_hnd_count, nullptr, FS_FOR_APPMGR);
-  if (status != ZX_OK) {
-    fprintf(stderr, "devcoordinator: failed to launch appmgr: %s\n", zx_status_get_string(status));
-    return 1;
-  }
-
   do_autorun("autorun:system", coordinator->boot_args().Get("zircon.autorun.system"),
              coordinator->root_resource());
+
   return 0;
 }
 
@@ -936,7 +767,7 @@
   if (status != ZX_OK) {
     return status;
   }
-  return fdio_service_connect("/fshost/svc/fuchsia.fshost.Loader", remote.release());
+  return fdio_service_connect("/svc/fuchsia.fshost.Loader", remote.release());
 }
 
 void SystemInstance::do_autorun(const char* name, const char* cmd,
@@ -962,55 +793,6 @@
   }
 }
 
-zx::channel SystemInstance::fshost_start(devmgr::Coordinator* coordinator,
-                                         const devmgr::DevmgrArgs& devmgr_args) {
-  // assemble handles to pass down to fshost
-  zx_handle_t handles[ZX_CHANNEL_MAX_MSG_HANDLES];
-  uint32_t types[fbl::count_of(handles)];
-  size_t n = 0;
-
-  // pass directory request handle to fshost
-  zx::channel dir_request_local, dir_request_remote;
-  if (zx::channel::create(0, &dir_request_local, &dir_request_remote) == ZX_OK) {
-    handles[n] = dir_request_remote.release();
-    types[n++] = PA_HND(PA_DIRECTORY_REQUEST, 0);
-  }
-
-  // pass VDSO VMOS to fshost
-  for (uint32_t m = 0; n < fbl::count_of(handles); m++) {
-    uint32_t type = PA_HND(PA_VMO_VDSO, m);
-    handles[n] = zx_take_startup_handle(type);
-
-    if (handles[n] != ZX_HANDLE_INVALID) {
-      types[n++] = type;
-    } else {
-      break;
-    }
-  }
-
-  // pass command line to the fshost
-  fbl::Vector<const char*> args{"/boot/bin/fshost"};
-  if (devmgr_args.disable_block_watcher) {
-    args.push_back("--disable-block-watcher");
-  }
-  args.push_back(nullptr);
-
-  launcher_.Launch(svc_job_, "fshost", args.data(), nullptr, -1, coordinator->root_resource(),
-                   handles, types, n, nullptr, FS_BOOT | FS_DEV | FS_SVC);
-  return dir_request_local;
-}
-
-static struct {
-  const char* name;
-  uint32_t flags;
-} DIRECTORY_RIGHTS[] = {
-    {"bin", FS_READ_EXEC_DIR_FLAGS},   {"blob", FS_READ_WRITE_DIR_FLAGS},
-    {"boot", ZX_FS_RIGHT_READABLE},    {"data", FS_READ_WRITE_DIR_FLAGS},
-    {"hub", FS_READ_WRITE_DIR_FLAGS},  {"install", FS_READ_WRITE_DIR_FLAGS},
-    {"pkgfs", FS_READ_EXEC_DIR_FLAGS}, {"system", FS_READ_EXEC_DIR_FLAGS},
-    {"tmp", FS_READ_WRITE_DIR_FLAGS},  {"volume", FS_READ_WRITE_DIR_FLAGS},
-};
-
 zx::channel SystemInstance::CloneFs(const char* path) {
   if (!strcmp(path, "dev")) {
     return devmgr::devfs_root_clone();
@@ -1027,22 +809,6 @@
     zx::unowned_channel fs = devmgr::devfs_root_borrow();
     path += 4;
     status = fdio_open_at(fs->get(), path, FS_READ_WRITE_DIR_FLAGS, h1.release());
-  } else if (!strcmp(path, "hub")) {
-    status = fdio_open_at(appmgr_client_.get(), path, FS_READ_WRITE_DIR_FLAGS, h1.release());
-  } else {
-    int flags = 0;
-    for (unsigned n = 0; n < fbl::count_of(DIRECTORY_RIGHTS); n++) {
-      if (!strcmp(path, DIRECTORY_RIGHTS[n].name)) {
-        flags = DIRECTORY_RIGHTS[n].flags;
-        break;
-      }
-    }
-    if (flags == 0) {
-      log(ERROR, "devcoordinator: CloneFs failed for path %s: unexpected path\n", path);
-      return zx::channel();
-    }
-    fbl::String abs_path = fbl::String::Concat({fbl::String("/"), fbl::String(path)});
-    status = fdio_ns_connect(default_ns_, abs_path.c_str(), flags, h1.release());
   }
   if (status != ZX_OK) {
     log(ERROR, "devcoordinator: CloneFs failed for path %s: %s\n", path,
diff --git a/src/devices/coordinator/system-instance.h b/src/devices/coordinator/system-instance.h
index bd14d2e..547de6b 100644
--- a/src/devices/coordinator/system-instance.h
+++ b/src/devices/coordinator/system-instance.h
@@ -33,15 +33,14 @@
   // The heart of the public API, in the order that things get called during
   // startup.
   zx_status_t CreateSvcJob(const zx::job& root_job);
-  zx_status_t CreateFuchsiaJob(const zx::job& root_job);
   zx_status_t PrepareChannels();
 
   zx_status_t StartSvchost(const zx::job& root_job, bool require_system,
-                           devmgr::Coordinator* coordinator, zx::channel fshost_client);
+                           devmgr::Coordinator* coordinator);
   zx_status_t ReuseExistingSvchost();
 
-  void devmgr_vfs_init(devmgr::Coordinator* coordinator, const devmgr::DevmgrArgs& devmgr_args,
-                       zx::channel fshost_server);
+  void devmgr_vfs_init();
+
   // Thread entry point
   static int pwrbtn_monitor_starter(void* arg);
   int PwrbtnMonitorStarter(devmgr::Coordinator* coordinator);
@@ -52,7 +51,7 @@
   // Thread entry point
   static int service_starter(void* arg);
   int ServiceStarter(devmgr::Coordinator* coordinator);
-  int FuchsiaStarter(devmgr::Coordinator* coordinator);
+  int WaitForSystemAvailable(devmgr::Coordinator* coordinator);
 
   // TODO(ZX-4860): DEPRECATED. Do not add new dependencies on the fshost loader service!
   zx_status_t clone_fshost_ldsvc(zx::channel* loader);
@@ -65,14 +64,6 @@
  private:
   // Private helper functions.
   void do_autorun(const char* name, const char* cmd, const zx::resource& root_resource);
-  zx::channel fshost_start(devmgr::Coordinator* coordinator, const devmgr::DevmgrArgs& devmgr_args);
-
-  // The handle used to transmit messages to appmgr.
-  zx::channel appmgr_client_;
-
-  // The handle used by appmgr to serve incoming requests.
-  // If appmgr cannot be launched within a timeout, this handle is closed.
-  zx::channel appmgr_server_;
 
   // The handle used to transmit messages to miscsvc.
   zx::channel miscsvc_client_;
@@ -93,9 +84,6 @@
   // miscsvc, netsvc, the consoles, autorun, and others.
   zx::job svc_job_;
 
-  // The job in which we run appmgr.
-  zx::job fuchsia_job_;
-
   // Used to bind the svchost to the virtual-console binary to provide fidl
   // services.
   zx::channel virtcon_fidl_;
diff --git a/src/sys/appmgr/BUILD.gn b/src/sys/appmgr/BUILD.gn
index 24c9d7e..0034057 100644
--- a/src/sys/appmgr/BUILD.gn
+++ b/src/sys/appmgr/BUILD.gn
@@ -75,8 +75,6 @@
     "//zircon/public/lib/loader-service",
     "//zircon/public/lib/trace-provider-with-fdio",
     "//zircon/public/lib/zx",
-    "//zircon/system/fidl/fuchsia-device",
-    "//zircon/system/fidl/fuchsia-inspect-deprecated",
     "//zircon/system/fidl/fuchsia-process",
   ]
 
@@ -114,14 +112,6 @@
   deps = [
     ":lib",
     "//sdk/lib/sys/cpp",
-    "//zircon/system/fidl/fuchsia-boot",
-    "//zircon/system/fidl/fuchsia-device-manager",
-    "//zircon/system/fidl/fuchsia-hardware-power-statecontrol",
-    "//zircon/system/fidl/fuchsia-hardware-pty",
-    "//zircon/system/fidl/fuchsia-kernel",
-    "//zircon/system/fidl/fuchsia-paver",
-    "//zircon/system/fidl/fuchsia-scheduler",
-    "//zircon/system/fidl/fuchsia-virtualconsole",
   ]
 
   # appmgr starts early in the boot sequence before shared libraries from
@@ -162,21 +152,22 @@
     "//src/lib/fxl/test:gtest_main",
     "//third_party/googletest:gmock",
     "//zircon/public/lib/memfs",
-    "//zircon/system/fidl/fuchsia-device-manager",
-    "//zircon/system/fidl/fuchsia-hardware-power-statecontrol",
-    "//zircon/system/fidl/fuchsia-kernel",
   ]
 }
 
 package("appmgr") {
-  deprecated_system_image = true
-
   deps = [ ":bin" ]
   binaries = [
     {
       name = "appmgr"
     },
   ]
+  meta = [
+    {
+      path = rebase_path("meta/appmgr.cml")
+      dest = "appmgr.cm"
+    },
+  ]
 }
 
 config_data("appmgr_scheme_config") {
diff --git a/src/sys/appmgr/appmgr.cc b/src/sys/appmgr/appmgr.cc
index 00f6044..09b0b32fc 100644
--- a/src/sys/appmgr/appmgr.cc
+++ b/src/sys/appmgr/appmgr.cc
@@ -5,6 +5,7 @@
 #include "src/sys/appmgr/appmgr.h"
 
 #include "fcntl.h"
+#include "lib/fdio/directory.h"
 #include "lib/sys/cpp/termination_reason.h"
 #include "src/lib/fxl/strings/string_printf.h"
 
@@ -39,20 +40,28 @@
   FXL_CHECK(root_realm_) << "Cannot create root realm ";
 
   // 2. Publish outgoing directories.
-  // Publish the root realm's hub directory as 'hub/' and the first nested
-  // realm's (to be created by sysmgr) service directory as 'svc/'.
+  // Connect to the tracing service, and then publish the root realm's hub
+  // directory as 'hub/' and the first nested realm's (to be created by sysmgr)
+  // service directory as 'svc/'.
+  zx::channel svc_client_chan, svc_server_chan;
+  zx_status_t status = zx::channel::create(0, &svc_client_chan, &svc_server_chan);
+  if (status != ZX_OK) {
+    FXL_LOG(ERROR) << "failed to create channel: " << status;
+    return;
+  }
+  status = root_realm_->BindFirstNestedRealmSvc(std::move(svc_server_chan));
+  if (status != ZX_OK) {
+    FXL_LOG(ERROR) << "failed to bind to root realm services: " << status;
+    return;
+  }
+  status = fdio_service_connect_at(svc_client_chan.get(), "fuchsia.tracing.provider.Registry",
+                                   args.trace_server_channel.release());
+  if (status != ZX_OK) {
+    FXL_LOG(ERROR) << "failed to connect to tracing: " << status;
+    // In test environments the tracing registry may not be available. If this
+    // fails, let's still proceed.
+  }
   if (args.pa_directory_request != ZX_HANDLE_INVALID) {
-    zx::channel svc_client_chan, svc_server_chan;
-    zx_status_t status = zx::channel::create(0, &svc_client_chan, &svc_server_chan);
-    if (status != ZX_OK) {
-      FXL_LOG(ERROR) << "failed to create channel: " << status;
-      return;
-    }
-    status = root_realm_->BindFirstNestedRealmSvc(std::move(svc_server_chan));
-    if (status != ZX_OK) {
-      FXL_LOG(ERROR) << "failed to bind to root realm services: " << status;
-      return;
-    }
     auto svc = fbl::AdoptRef(new fs::RemoteDir(std::move(svc_client_chan)));
     publish_dir_->AddEntry("hub", root_realm_->hub_dir());
     publish_dir_->AddEntry("svc", svc);
diff --git a/src/sys/appmgr/appmgr.h b/src/sys/appmgr/appmgr.h
index ef375f4..2691472 100644
--- a/src/sys/appmgr/appmgr.h
+++ b/src/sys/appmgr/appmgr.h
@@ -28,6 +28,7 @@
   fidl::VectorPtr<std::string> sysmgr_args;
   bool run_virtual_console;
   bool retry_sysmgr_crash;
+  zx::channel trace_server_channel;
 };
 
 class Appmgr {
diff --git a/src/sys/appmgr/integration_tests/outdir/src/main.rs b/src/sys/appmgr/integration_tests/outdir/src/main.rs
index 2ecd312..e23e411 100644
--- a/src/sys/appmgr/integration_tests/outdir/src/main.rs
+++ b/src/sys/appmgr/integration_tests/outdir/src/main.rs
@@ -108,6 +108,23 @@
         pkgfs_client_end.into_channel().into_handle(),
     ));
 
+    let (svc_for_sys_client_end, svc_for_sys_server_end) = create_endpoints::<fio::NodeMarker>()?;
+    fasync::spawn(async move {
+        let fake_svc_for_sys = pseudo_directory! {};
+        fake_svc_for_sys.open(
+            ExecutionScope::from_executor(Box::new(fasync::EHandle::local())),
+            fio::OPEN_RIGHT_READABLE,
+            fio::MODE_TYPE_DIRECTORY,
+            pfsPath::empty(),
+            svc_for_sys_server_end,
+        );
+    });
+    let svc_for_sys_c_str = CString::new("/svc_for_sys").unwrap();
+    spawn_actions.push(fdio::SpawnAction::add_namespace_entry(
+        &svc_for_sys_c_str,
+        svc_for_sys_client_end.into_channel().into_handle(),
+    ));
+
     let mut spawn_options = fdio::SpawnOptions::empty();
     spawn_options.insert(fdio::SpawnOptions::DEFAULT_LOADER);
     spawn_options.insert(fdio::SpawnOptions::CLONE_JOB);
diff --git a/src/sys/appmgr/main.cc b/src/sys/appmgr/main.cc
index d0c863c..988c6e5 100644
--- a/src/sys/appmgr/main.cc
+++ b/src/sys/appmgr/main.cc
@@ -2,21 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <fuchsia/boot/cpp/fidl.h>
-#include <fuchsia/device/cpp/fidl.h>
-#include <fuchsia/device/manager/cpp/fidl.h>
-#include <fuchsia/hardware/power/statecontrol/cpp/fidl.h>
-#include <fuchsia/hardware/pty/cpp/fidl.h>
-#include <fuchsia/kernel/cpp/fidl.h>
-#include <fuchsia/paver/cpp/fidl.h>
-#include <fuchsia/scheduler/cpp/fidl.h>
-#include <fuchsia/virtualconsole/cpp/fidl.h>
+#include <dirent.h>
 #include <lib/async-loop/cpp/loop.h>
 #include <lib/async-loop/default.h>
+#include <lib/fdio/directory.h>
 #include <lib/sys/cpp/service_directory.h>
 #include <zircon/process.h>
 #include <zircon/processargs.h>
 
+#include <src/lib/files/directory.h>
 #include <trace-provider/provider.h>
 
 #include "src/lib/fxl/command_line.h"
@@ -24,27 +18,17 @@
 
 namespace {
 
+// Returns the set of service names that should be proxied to the root realm
+// from appmgr's namespace
 std::vector<std::string> RootRealmServices() {
-  return std::vector<std::string>{
-      fuchsia::boot::Arguments::Name_,
-      fuchsia::boot::FactoryItems::Name_,
-      fuchsia::boot::ReadOnlyLog::Name_,
-      fuchsia::boot::RootJob::Name_,
-      fuchsia::boot::RootJobForInspect::Name_,
-      fuchsia::boot::RootResource::Name_,
-      fuchsia::boot::WriteOnlyLog::Name_,
-      fuchsia::device::NameProvider::Name_,
-      fuchsia::device::manager::Administrator::Name_,
-      fuchsia::hardware::power::statecontrol::Admin::Name_,
-      fuchsia::device::manager::DebugDumper::Name_,
-      fuchsia::hardware::pty::Device::Name_,
-      fuchsia::kernel::Counter::Name_,
-      fuchsia::kernel::DebugBroker::Name_,
-      fuchsia::kernel::Stats::Name_,
-      fuchsia::paver::Paver::Name_,
-      fuchsia::scheduler::ProfileProvider::Name_,
-      fuchsia::virtualconsole::SessionManager::Name_,
-  };
+  std::vector<std::string> res;
+  bool success = files::ReadDirContents("/svc_for_sys", &res);
+  if (!success) {
+    FXL_LOG(WARNING) << "failed to read /svc_for_sys (" << errno
+                     << "), not forwarding services to sys realm";
+    return std::vector<std::string>();
+  }
+  return res;
 }
 
 }  // namespace
@@ -53,7 +37,23 @@
   async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
   auto request = zx_take_startup_handle(PA_DIRECTORY_REQUEST);
 
-  auto environment_services = sys::ServiceDirectory::CreateFromNamespace();
+  zx::channel svc_for_sys_server, svc_for_sys_client;
+  zx_status_t status = zx::channel::create(0, &svc_for_sys_server, &svc_for_sys_client);
+  if (status != ZX_OK) {
+    FXL_LOG(ERROR) << "failed to create channel: " << errno;
+    return status;
+  }
+  status =
+      fdio_open("/svc_for_sys", ZX_FS_RIGHT_READABLE | ZX_FS_FLAG_DIRECTORY | ZX_FS_RIGHT_WRITABLE,
+                svc_for_sys_server.release());
+  if (status != ZX_OK) {
+    FXL_LOG(WARNING) << "failed to open /svc_for_sys (" << errno
+                     << "), not forwarding services to sys realm";
+    return status;
+  }
+
+  auto environment_services =
+      std::make_shared<sys::ServiceDirectory>(std::move(svc_for_sys_client));
 
   // Certain services in appmgr's /svc, which is served by svchost, are added to
   // the root realm so they can be routed into a nested environment (such as the
@@ -62,7 +62,14 @@
   root_realm_services->names = RootRealmServices();
   root_realm_services->host_directory = environment_services->CloneChannel().TakeChannel();
 
-  trace::TraceProviderWithFdio trace_provider(loop.dispatcher());
+  zx::channel trace_client, trace_server;
+  status = zx::channel::create(0, &trace_client, &trace_server);
+  if (status != ZX_OK) {
+    FXL_LOG(ERROR) << "failed to create tracing channel: " << status;
+    return status;
+  }
+
+  trace::TraceProvider trace_provider(std::move(trace_client), loop.dispatcher());
 
   component::AppmgrArgs args{.pa_directory_request = std::move(request),
                              .root_realm_services = std::move(root_realm_services),
@@ -70,7 +77,8 @@
                              .sysmgr_url = "fuchsia-pkg://fuchsia.com/sysmgr#meta/sysmgr.cmx",
                              .sysmgr_args = {},
                              .run_virtual_console = true,
-                             .retry_sysmgr_crash = true};
+                             .retry_sysmgr_crash = true,
+                             .trace_server_channel = std::move(trace_server)};
   component::Appmgr appmgr(loop.dispatcher(), std::move(args));
 
   loop.Run();
diff --git a/src/sys/appmgr/meta/appmgr.cml b/src/sys/appmgr/meta/appmgr.cml
new file mode 100644
index 0000000..2fdf5dd
--- /dev/null
+++ b/src/sys/appmgr/meta/appmgr.cml
@@ -0,0 +1,88 @@
+{
+  "program": { "binary": "bin/appmgr" },
+  "use": [
+    { "runner": "elf" },
+
+    { "directory": "/blob", "rights": ["rw*"] },
+    { "directory": "/boot", "rights": ["rx*"] },
+    { "directory": "/dev", "rights": ["rw*"] },
+    { "directory": "/minfs", "as": "/data", "rights": ["rw*", "admin"] },
+    { "directory": "/pkgfs", "rights": ["rx*"] },
+    { "directory": "/system", "rights": ["rx*"] },
+    { "directory": "/tmp", "rights": ["rw*"] },
+
+    // Services used by appmgr with the svc_for_sys prefix are passed through to
+    // the sys realm
+
+    // From bootsvc
+    { "protocol": "/svc/fuchsia.boot.FactoryItems", "as": "/svc_for_sys/fuchsia.boot.FactoryItems" },
+    { "protocol": "/svc/fuchsia.boot.ReadOnlyLog", "as": "/svc_for_sys/fuchsia.boot.ReadOnlyLog" },
+    { "protocol": "/svc/fuchsia.boot.RootJob", "as": "/svc_for_sys/fuchsia.boot.RootJob" },
+    { "protocol": "/svc/fuchsia.boot.RootJobForInspect", "as": "/svc_for_sys/fuchsia.boot.RootJobForInspect" },
+    { "protocol": "/svc/fuchsia.boot.RootResource", "as": "/svc_for_sys/fuchsia.boot.RootResource" },
+    { "protocol": "/svc/fuchsia.boot.WriteOnlyLog", "as": "/svc_for_sys/fuchsia.boot.WriteOnlyLog" },
+    { "protocol": "/svc/fuchsia.kernel.Stats", "as": "/svc_for_sys/fuchsia.kernel.Stats" },
+
+    // From ptysvc
+    { "protocol": "/svc/fuchsia.hardware.pty.Device", "as": "/svc_for_sys/fuchsia.hardware.pty.Device" },
+
+    // From devcoordinator
+    { "protocol": "/svc/fuchsia.device.NameProvider", "as": "/svc_for_sys/fuchsia.device.NameProvider" },
+    { "protocol": "/svc/fuchsia.device.manager.Administrator", "as": "/svc_for_sys/fuchsia.device.manager.Administrator" },
+    { "protocol": "/svc/fuchsia.device.manager.DebugDumper", "as": "/svc_for_sys/fuchsia.device.manager.DebugDumper" },
+    { "protocol": "/svc/fuchsia.kernel.Counter", "as": "/svc_for_sys/fuchsia.kernel.Counter" },
+    { "protocol": "/svc/fuchsia.kernel.DebugBroker", "as": "/svc_for_sys/fuchsia.kernel.DebugBroker" },
+    { "protocol": "/svc/fuchsia.paver.Paver", "as": "/svc_for_sys/fuchsia.paver.Paver" },
+    { "protocol": "/svc/fuchsia.scheduler.ProfileProvider", "as": "/svc_for_sys/fuchsia.scheduler.ProfileProvider" },
+    { "protocol": "/svc/fuchsia.virtualconsole.SessionManager", "as": "/svc_for_sys/fuchsia.virtualconsole.SessionManager" },
+
+    // From component_manager
+    { "protocol": "/svc/fuchsia.process.Launcher", "as": "/svc_for_sys/fuchsia.process.Launcher" },
+
+    // From sysinfo
+    { "protocol": "/svc/fuchsia.sysinfo.Sysinfo", "as": "/svc_for_sys/fuchsia.sysinfo.Sysinfo" },
+
+    // These services are used by appmgr directly
+    {
+      "protocol": [
+        "/svc/fuchsia.process.Launcher"
+      ]
+    }
+  ],
+  "expose": [
+    { "directory": "/hub", "from": "self", "rights": ["rw*"] },
+
+    // These services exposed by appmgr are services from the v1 component
+    // runtime being made available to the v2 component runtime. These all cause
+    // dependency cycles between the two runtimes. Cycles require trickier
+    // runtime logic to ensure correctness, are a general design smell, and may
+    // be disallowed in the future. Please talk to dgonyeo@google.com before
+    // adding any additional exposed services to this list.
+
+    {
+      "protocol": [
+        "/svc/fuchsia.amber.Control",
+        "/svc/fuchsia.cobalt.LoggerFactory",
+        "/svc/fuchsia.devicesettings.DeviceSettingsManager",
+        "/svc/fuchsia.exception.Handler",
+        "/svc/fuchsia.logger.Log",
+        "/svc/fuchsia.logger.LogSink",
+        "/svc/fuchsia.net.NameLookup",
+        "/svc/fuchsia.net.stack.Log",
+        "/svc/fuchsia.net.stack.Stack",
+        "/svc/fuchsia.netstack.Netstack",
+        "/svc/fuchsia.pkg.PackageResolver",
+        "/svc/fuchsia.pkg.RepositoryManager",
+        "/svc/fuchsia.pkg.rewrite.Engine",
+        "/svc/fuchsia.posix.socket.Provider",
+        "/svc/fuchsia.process.Resolver",
+        "/svc/fuchsia.sys.Environment",
+        "/svc/fuchsia.sys.Launcher",
+        "/svc/fuchsia.tracing.controller.Controller",
+        "/svc/fuchsia.tracing.provider.Registry",
+        "/svc/fuchsia.wlan.service.Wlan",
+      ],
+      "from": "self",
+    }
+  ],
+}
diff --git a/src/sys/appmgr/namespace_builder.cc b/src/sys/appmgr/namespace_builder.cc
index 8d5d5ae..c0e8ba8 100644
--- a/src/sys/appmgr/namespace_builder.cc
+++ b/src/sys/appmgr/namespace_builder.cc
@@ -148,11 +148,9 @@
       PushDirectoryFromPath("/data");
       PushDirectoryFromPath("/dev");
       AddHub(hub_directory_factory);
-      PushDirectoryFromPath("/install");
       PushDirectoryFromPath("/pkgfs");
       PushDirectoryFromPath("/system");
       PushDirectoryFromPath("/tmp");
-      PushDirectoryFromPath("/volume");
     } else if (feature == "shell-commands") {
       PushDirectoryFromPathAs("/pkgfs/packages/shell-commands/0/bin", "/bin");
     } else if (feature == "system-temp") {
diff --git a/src/sys/component_manager/src/elf_runner/mod.rs b/src/sys/component_manager/src/elf_runner/mod.rs
index 1e47df8..e963462 100644
--- a/src/sys/component_manager/src/elf_runner/mod.rs
+++ b/src/sys/component_manager/src/elf_runner/mod.rs
@@ -204,6 +204,24 @@
 
         let child_job = job_default().create_child_job()?;
 
+        child_job
+            .set_policy(zx::JobPolicy::TimerSlack(
+                zx::Duration::from_micros(500),
+                zx::JobDefaultTimerMode::Late,
+            ))
+            .context("error setting job policy to configure timer slack")?;
+
+        // TODO(fxb/39947): The hermetic-decompressor library used in fshost requires the ability
+        // to directly create new processes, and this policy breaks that.
+        if url != "fuchsia-boot:///#meta/fshost.cm" {
+            child_job
+                .set_policy(zx::JobPolicy::Basic(
+                    zx::JobPolicyOption::Absolute,
+                    vec![(zx::JobCondition::NewProcess, zx::JobAction::Deny)],
+                ))
+                .context("error setting job policy to deny new processes")?;
+        }
+
         let child_job_dup = child_job.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
 
         let mut string_iters: Vec<_> =
diff --git a/src/sys/component_manager/src/model/error.rs b/src/sys/component_manager/src/model/error.rs
index d7b5e49..8d36a9d 100644
--- a/src/sys/component_manager/src/model/error.rs
+++ b/src/sys/component_manager/src/model/error.rs
@@ -31,7 +31,7 @@
     Unsupported { feature: String },
     #[error("component declaration invalid")]
     ComponentInvalid,
-    #[error("component manifest invalid")]
+    #[error("component manifest invalid {}: {}", url, err)]
     ManifestInvalid {
         url: String,
         #[source]
@@ -44,7 +44,7 @@
         #[source]
         err: ClonableError,
     },
-    #[error("resolver error")]
+    #[error("resolver error: {}", err)]
     ResolverError {
         #[source]
         err: ResolverError,
@@ -54,12 +54,12 @@
         #[source]
         err: RunnerError,
     },
-    #[error("capability discovery error")]
+    #[error("capability discovery error: {}", err)]
     CapabilityDiscoveryError {
         #[source]
         err: ClonableError,
     },
-    #[error("storage error")]
+    #[error("storage error: {}", err)]
     StorageError {
         #[source]
         err: StorageError,
diff --git a/src/sys/component_manager/src/model/routing.rs b/src/sys/component_manager/src/model/routing.rs
index aa5c688..ccf7403 100644
--- a/src/sys/component_manager/src/model/routing.rs
+++ b/src/sys/component_manager/src/model/routing.rs
@@ -66,10 +66,7 @@
     server_chan: zx::Channel,
 ) -> Result<(), ModelError> {
     match use_decl {
-        UseDecl::Service(_)
-        | UseDecl::Protocol(_)
-        | UseDecl::Directory(_)
-        | UseDecl::Runner(_) => {
+        UseDecl::Service(_) | UseDecl::Protocol(_) | UseDecl::Directory(_) | UseDecl::Runner(_) => {
             let source = find_used_capability_source(use_decl, target_realm).await?;
             open_capability_at_source(
                 model,
@@ -146,13 +143,6 @@
     ) -> Result<(), ModelError> {
         // Start the source component, if necessary
         let source_realm = self.model.bind(&self.source_moniker).await?;
-
-        // TODO(36541): changing the flags for pkgfs is a hack, directory permissions
-        // should be recorded in the manifests
-        let mut flags = flags;
-        if self.path.to_string().contains("pkgfs") {
-            flags = fio::OPEN_RIGHT_READABLE;
-        }
         source_realm.open_outgoing(flags, open_mode, &self.path, server_end).await?;
         Ok(())
     }
@@ -741,9 +731,7 @@
             )))?;
         let (source, target) = match expose {
             ExposeDecl::Service(_) => return Err(ModelError::unsupported("Service capability")),
-            ExposeDecl::Protocol(ls) => {
-                (ExposeSource::Protocol(&ls.source), &ls.target)
-            }
+            ExposeDecl::Protocol(ls) => (ExposeSource::Protocol(&ls.source), &ls.target),
             ExposeDecl::Directory(d) => (ExposeSource::Directory(&d.source), &d.target),
             ExposeDecl::Runner(r) => (ExposeSource::Runner(&r.source), &r.target),
         };
diff --git a/src/sys/root/BUILD.gn b/src/sys/root/BUILD.gn
index bd33fec..da89dc7 100644
--- a/src/sys/root/BUILD.gn
+++ b/src/sys/root/BUILD.gn
@@ -9,10 +9,9 @@
   data = rebase_path("meta/root.cml")
 }
 
-# TODO(BLD-448): This should move into the devcoordinator package that's
-# included in bootfs along with devcoordinator's binary and other
-# resources/libraries. For now it's here since cm_compile doesn't exist in
-# //zircon.
+# TODO(BLD-448): These should move into their appropriate packages that are
+# included in bootfs along with the binaries and other resources/libraries. For
+# now they here since cm_compile doesn't exist in //zircon.
 cm_compile("devcoordinator.cm") {
   data = rebase_path("meta/devcoordinator.cml")
 }
@@ -25,12 +24,16 @@
 cm_compile("sysinfo.cm") {
   data = rebase_path("meta/sysinfo.cml")
 }
+cm_compile("fshost.cm") {
+  data = rebase_path("meta/fshost.cml")
+}
 
 # TODO(BLD-448): Should become a package just containing the root manifests.
 generate_manifest("root_manifests.bootfs") {
   deps = [
     ":console.cm",
     ":devcoordinator.cm",
+    ":fshost.cm",
     ":ptysvc.cm",
     ":root.cm",
     ":sysinfo.cm",
@@ -38,6 +41,7 @@
 
   root_cm_out = get_target_outputs(":root.cm")
   devc_cm_out = get_target_outputs(":devcoordinator.cm")
+  fshost_cm_out = get_target_outputs(":fshost.cm")
   console_cm_out = get_target_outputs(":console.cm")
   ptysvc_cm_out = get_target_outputs(":ptysvc.cm")
   sysinfo_cm_out = get_target_outputs(":sysinfo.cm")
@@ -47,5 +51,6 @@
     "--entry=meta/console.cm=" + rebase_path(console_cm_out[0]),
     "--entry=meta/ptysvc.cm=" + rebase_path(ptysvc_cm_out[0]),
     "--entry=meta/sysinfo.cm=" + rebase_path(sysinfo_cm_out[0]),
+    "--entry=meta/fshost.cm=" + rebase_path(fshost_cm_out[0]),
   ]
 }
diff --git a/src/sys/root/meta/devcoordinator.cml b/src/sys/root/meta/devcoordinator.cml
index 014b775..4c230ed 100644
--- a/src/sys/root/meta/devcoordinator.cml
+++ b/src/sys/root/meta/devcoordinator.cml
@@ -2,13 +2,34 @@
   "program": { "binary": "bin/devcoordinator" },
   "use": [
     { "runner": "elf" },
-    {
-      "directory": "/boot",
-      "rights": ["rx*"]
-    },
-    {
-      "protocol": [
+
+    { "directory": "/bin", "rights": ["rx*"] },
+    { "directory": "/blob", "rights": ["rw*"] },
+    { "directory": "/boot", "rights": ["rx*"] },
+    { "directory": "/hub", "rights": ["rw*"] },
+    { "directory": "/minfs", "as": "/data", "rights": ["rw*"] },
+    { "directory": "/pkgfs", "rights": ["rx*"] },
+    { "directory": "/system", "rights": ["rx*"] },
+
+    // NOTE: /tmp needs admin rights because the bringup bots invoke a shell
+    // script via zircon.system.autorun that mounts a volume under /tmp to
+    // exfiltrate test results.
+    { "directory": "/tmp", "rights": ["rw*", "admin"] },
+
+    // TODO: this directory is unused and can likely be deleted
+    { "directory": "/install", "rights": ["rw*"] },
+
+    // TODO: this volume directory is only used by the paver lib in netsvc under
+    // devcoordinator. The paver lib could create its own memfs instead, so
+    // this should eventually be removed.
+    { "directory": "/volume", "rights": ["rw*", "admin"] },
+
+    { "directory": "/system-delayed", "rights": ["rx*"] },
+
+    { "protocol": [
         "/svc/console",
+
+        // Services from bootsvc
         "/svc/fuchsia.boot.Arguments",
         "/svc/fuchsia.boot.FactoryItems",
         "/svc/fuchsia.boot.Items",
@@ -17,14 +38,70 @@
         "/svc/fuchsia.boot.RootJobForInspect",
         "/svc/fuchsia.boot.RootResource",
         "/svc/fuchsia.boot.WriteOnlyLog",
-        "/svc/fuchsia.hardware.pty.Device",
         "/svc/fuchsia.kernel.Stats",
-        "/svc/fuchsia.process.Launcher",
+
+        // Services from console
+        "/svc/fuchsia.hardware.pty.Device",
+
+        // Services from sysinfo
         "/svc/fuchsia.sysinfo.SysInfo",
+
+        // Builtin service
+        "/svc/fuchsia.process.Launcher",
+
+        // Service from appmgr
+        "/svc/fuchsia.tracing.provider.Registry",
+        "/svc/fuchsia.exception.Handler",
+
+        // These are services from fshost that devcoordinator uses directly
+        "/svc/fuchsia.fshost.Admin",
+        "/svc/fuchsia.fshost.Loader",
+
+        // These are services from fshost that show up in svchost
+        "/svc/fuchsia.fshost.Filesystems",
+        "/svc/fuchsia.fshost.Registry",
+
+        // The following are non-Zircon services that some zircon tests assume they
+        // can reach, and thus are proxied through svchost. This should mirror the
+        // list in //zircon/system/core/svchost/svchost.cc
+        "/svc/fuchsia.amber.Control",
+        "/svc/fuchsia.cobalt.LoggerFactory",
+        "/svc/fuchsia.devicesettings.DeviceSettingsManager",
+        "/svc/fuchsia.logger.Log",
+        "/svc/fuchsia.logger.LogSink",
+        "/svc/fuchsia.process.Resolver",
+        "/svc/fuchsia.net.NameLookup",
+        "/svc/fuchsia.posix.socket.Provider",
+        "/svc/fuchsia.netstack.Netstack",
+        "/svc/fuchsia.net.stack.Stack",
+        "/svc/fuchsia.net.stack.Log",
+        "/svc/fuchsia.sys.Environment",
+        "/svc/fuchsia.sys.Launcher",
+        "/svc/fuchsia.wlan.service.Wlan",
+        "/svc/fuchsia.tracing.controller.Controller",
+        "/svc/fuchsia.pkg.PackageResolver",
+        "/svc/fuchsia.pkg.RepositoryManager",
+        "/svc/fuchsia.pkg.rewrite.Engine",
+
       ]
-    }
+    },
   ],
   "expose": [
-    {"directory": "/dev", "from": "self", "rights": ["rw*"]},
+    // Device tree
+    { "directory": "/dev", "from": "self", "rights": ["rw*"] },
+    // Services proxied by svchost
+    {
+      "protocol": [
+        "/svc/fuchsia.device.NameProvider",
+        "/svc/fuchsia.device.manager.Administrator",
+        "/svc/fuchsia.device.manager.DebugDumper",
+        "/svc/fuchsia.kernel.Counter",
+        "/svc/fuchsia.kernel.DebugBroker",
+        "/svc/fuchsia.paver.Paver",
+        "/svc/fuchsia.scheduler.ProfileProvider",
+        "/svc/fuchsia.virtualconsole.SessionManager",
+      ],
+      "from": "self",
+    }
   ],
 }
diff --git a/src/sys/root/meta/fshost.cml b/src/sys/root/meta/fshost.cml
new file mode 100644
index 0000000..138902c
--- /dev/null
+++ b/src/sys/root/meta/fshost.cml
@@ -0,0 +1,45 @@
+{
+  "program": {
+    "binary": "bin/fshost",
+  },
+  "use": [
+    { "runner": "elf" },
+    { "directory": "/dev", "rights": ["rw*"] },
+    { "directory": "/boot", "rights": ["rx*"] },
+    { "protocol": [
+      "/svc/fuchsia.boot.Arguments",
+      "/svc/fuchsia.boot.Items",
+      "/svc/fuchsia.cobalt.LoggerFactory",
+      "/svc/fuchsia.process.Launcher",
+      "/svc/fuchsia.tracing.provider.Registry",
+    ] },
+  ],
+  "expose": [
+    { "directory": "/delayed/fs/pkgfs", "from": "self", "as": "/pkgfs-delayed", "rights": ["rx*"] },
+    { "directory": "/delayed/fs/system", "from": "self", "as": "/system-delayed", "rights": ["rx*"] },
+    { "directory": "/fs/bin", "from": "self", "as": "/bin", "rights": ["rx*"] },
+    { "directory": "/fs/blob", "from": "self", "as": "/blob", "rights": ["rw*"] },
+    { "directory": "/fs/data", "from": "self", "as": "/minfs", "rights": ["rw*", "admin"] },
+    { "directory": "/fs/install", "from": "self", "as": "/install", "rights": ["rw*"] },
+    { "directory": "/fs/pkgfs", "from": "self", "as": "/pkgfs", "rights": ["rx*"] },
+    { "directory": "/fs/system", "from": "self", "as": "/system", "rights": ["rx*"] },
+    { "directory": "/fs/tmp", "from": "self", "as": "/tmp", "rights": ["rw*", "admin"] },
+
+    // TODO: this volume directory is only used by the paver lib in netsvc under
+    // devcoordinator. The paver lib could create its own memfs instead, so
+    // this should eventually be removed.
+    { "directory": "/fs/volume", "from": "self", "as": "/volume", "rights": ["rw*", "admin"] },
+
+    { "protocol": "/fs-manager-svc/fuchsia.fshost.Filesystems", "from": "self", "as": "/svc/fuchsia.fshost.Filesystems" },
+    { "protocol": "/fs-manager-svc/fuchsia.fshost.Registry", "from": "self", "as": "/svc/fuchsia.fshost.Registry" },
+    { "protocol": "/svc/fuchsia.fshost.Admin", "from": "self" },
+
+    // This service name is breaking the convention whereby the directory entry
+    // name matches the protocol name. This is an implementation of
+    // fuchsia.ldsvc.Loader, and is renamed to make it easier to identify that
+    // this implementation comes from fshost.
+    // TODO(fxb/34633): This service is deprecated and should only be routed to
+    // devcoordinator
+    { "protocol": "/svc/fuchsia.fshost.Loader", "from": "self" , "as": "/svc/fuchsia.fshost.Loader"},
+  ],
+}
diff --git a/src/sys/root/meta/root.cml b/src/sys/root/meta/root.cml
index 11479be..9f393f2 100644
--- a/src/sys/root/meta/root.cml
+++ b/src/sys/root/meta/root.cml
@@ -3,6 +3,14 @@
     {
       "name": "devcoordinator",
       "url": "fuchsia-boot:///#meta/devcoordinator.cm",
+    },
+    {
+      "name": "fshost",
+      "url": "fuchsia-boot:///#meta/fshost.cm",
+    },
+    {
+      "name": "appmgr",
+      "url": "fuchsia-pkg://fuchsia.com/appmgr#meta/appmgr.cm",
       "startup": "eager",
     },
     {
@@ -22,31 +30,121 @@
     {
       "directory": "/boot",
       "from": "realm",
-      "to": [ "#devcoordinator" ],
+      "to": [ "#devcoordinator", "#fshost", "#appmgr" ],
       "rights": ["rx*"],
     },
+    // Services routed from "realm" at the root are either provided by
+    // bootsvc through component_manager's namespace or by component_manager
+    // itself as a builtin service
     {
       "protocol": [
-        // These services and directories routed from "realm" at the root are provided by bootsvc
-        // through component_manager's namespace.
-        "/svc/fuchsia.boot.Arguments",
         "/svc/fuchsia.boot.FactoryItems",
-        "/svc/fuchsia.boot.Items",
-        "/svc/fuchsia.kernel.Stats",
-        // ...except for these, which are provided by component_manager itself as a builtin service.
+        "/svc/fuchsia.boot.ReadOnlyLog",
         "/svc/fuchsia.boot.RootJob",
         "/svc/fuchsia.boot.RootJobForInspect",
-        "/svc/fuchsia.boot.ReadOnlyLog",
         "/svc/fuchsia.boot.WriteOnlyLog",
-        "/svc/fuchsia.process.Launcher",
+        "/svc/fuchsia.kernel.Stats",
       ],
       "from": "realm",
-      "to": [ "#devcoordinator" ],
+      "to": [ "#devcoordinator", "#appmgr" ],
     },
     {
       "protocol": "/svc/fuchsia.boot.RootResource",
       "from": "realm",
-      "to": [ "#devcoordinator", "#console", "#sysinfo"],
+      "to": [ "#appmgr", "#devcoordinator", "#console", "#sysinfo" ],
+    },
+    {
+      "protocol": [
+        "/svc/fuchsia.boot.Arguments",
+        "/svc/fuchsia.boot.Items",
+      ],
+      "from": "realm",
+      "to": [ "#devcoordinator", "#fshost" ],
+    },
+    {
+      "protocol": "/svc/fuchsia.process.Launcher",
+      "from": "realm",
+      "to": [ "#devcoordinator", "#fshost", "#appmgr" ],
+    },
+    // ...and the rest of these are services provided by components for each other
+    {
+      // TODO(fxb/34633): DEPRECATED. Do not add new dependencies on the fshost
+      // loader service!
+      "protocol": "/svc/fuchsia.fshost.Loader",
+      "from": "#fshost",
+      "to": [ "#devcoordinator" ],
+    },
+    {
+      "directory": "/dev",
+      "from": "#devcoordinator",
+      "to": [ "#fshost", "#appmgr", "#sysinfo" ],
+    },
+    {
+      "directory": "/minfs",
+      "from": "#fshost",
+      "to": [ "#appmgr", "#devcoordinator" ],
+    },
+    {
+      "directory": "/pkgfs",
+      "from": "#fshost",
+      "to": [ "#appmgr", "#devcoordinator" ],
+    },
+    {
+      "directory": "/system-delayed",
+      "from": "#fshost",
+      "to": [ "#devcoordinator" ],
+    },
+    {
+      "directory": "/system",
+      "from": "#fshost",
+      "to": [ "#appmgr", "#devcoordinator" ],
+    },
+    {
+      "directory": "/blob",
+      "from": "#fshost",
+      "to": [ "#appmgr", "#devcoordinator" ],
+    },
+    {
+      "directory": "/volume",
+      "from": "#fshost",
+      "to": [ "#devcoordinator" ],
+    },
+    {
+      "directory": "/install",
+      "from": "#fshost",
+      "to": [ "#devcoordinator" ],
+    },
+    {
+      "directory": "/tmp",
+      "from": "#fshost",
+      "to": [ "#appmgr", "#devcoordinator" ],
+    },
+    {
+      "directory": "/bin",
+      "from": "#fshost",
+      "to": [ "#devcoordinator" ],
+    },
+    {
+      "protocol": [
+        "/svc/fuchsia.device.NameProvider",
+        "/svc/fuchsia.device.manager.Administrator",
+        "/svc/fuchsia.device.manager.DebugDumper",
+        "/svc/fuchsia.kernel.Counter",
+        "/svc/fuchsia.kernel.DebugBroker",
+        "/svc/fuchsia.paver.Paver",
+        "/svc/fuchsia.scheduler.ProfileProvider",
+        "/svc/fuchsia.virtualconsole.SessionManager",
+      ],
+      "from": "#devcoordinator",
+      "to": [ "#appmgr" ],
+    },
+    {
+      "protocol": [
+        "/svc/fuchsia.cobalt.LoggerFactory",
+        "/svc/fuchsia.tracing.provider.Registry",
+      ],
+      "from": "#appmgr",
+      "to": [ "#devcoordinator", "#fshost" ],
     },
     // Offer the kernel serial console to the devcoordinator
     {
@@ -55,29 +153,78 @@
       "to": [ "#devcoordinator" ],
       "as": "/svc/console",
     },
-    // Offer the pty service to devcoordinator (temporary hack to expose it to appmgr).
+    // Offer the pty service to appmgr
     {
       "protocol": "/svc/fuchsia.hardware.pty.Device",
       "from": "#ptysvc",
+      "to": [
+        "#appmgr",
+        "#devcoordinator",
+      ],
+    },
+
+    // devcoordinator needs the v1 hub for the serial console
+    {
+      "directory": "/hub",
+      "from": "#appmgr",
       "to": [ "#devcoordinator" ],
     },
      {
       "protocol": "/svc/fuchsia.sysinfo.SysInfo",
       "from": "#sysinfo",
-      "to": [ "#devcoordinator" ],
-    },
-    // Offer platform bus sysinfo interface to sysinfo service
-    {
-      "directory": "/dev",
-      "from": "#devcoordinator",
-      "to": [ "#sysinfo" ],
+      "to": [ "#appmgr", "#devcoordinator" ],
     },
     // Offer the ELF runner to children.
     {
       "runner": "elf",
       "from": "realm",
-      "to": [ "#devcoordinator", "#console", "#ptysvc", "#sysinfo" ],
-    }
+      "to": [
+          "#appmgr",
+          "#console",
+          "#devcoordinator",
+          "#fshost",
+          "#ptysvc",
+          "#sysinfo",
+      ],
+    },
+    // Non-zircon services for svchost
+    {
+      "protocol": [
+        "/svc/fuchsia.amber.Control",
+        "/svc/fuchsia.devicesettings.DeviceSettingsManager",
+        "/svc/fuchsia.exception.Handler",
+        "/svc/fuchsia.logger.Log",
+        "/svc/fuchsia.logger.LogSink",
+        "/svc/fuchsia.net.NameLookup",
+        "/svc/fuchsia.net.stack.Log",
+        "/svc/fuchsia.net.stack.Stack",
+        "/svc/fuchsia.netstack.Netstack",
+        "/svc/fuchsia.pkg.PackageResolver",
+        "/svc/fuchsia.pkg.RepositoryManager",
+        "/svc/fuchsia.pkg.rewrite.Engine",
+        "/svc/fuchsia.posix.socket.Provider",
+        "/svc/fuchsia.process.Resolver",
+        "/svc/fuchsia.sys.Environment",
+        "/svc/fuchsia.sys.Launcher",
+        "/svc/fuchsia.tracing.controller.Controller",
+        "/svc/fuchsia.wlan.service.Wlan",
+      ],
+      "from": "#appmgr",
+      "to": [ "#devcoordinator" ],
+    },
+    {
+      "protocol": [
+        "/svc/fuchsia.fshost.Admin",
+        "/svc/fuchsia.fshost.Filesystems",
+        "/svc/fuchsia.fshost.Registry",
+      ],
+      "from": "#fshost",
+      "to": [ "#devcoordinator" ],
+    },
+  ],
+  "expose": [
+    // This handle is used by component manager to resolve packages from the
+    // base package set.
+    { "directory": "/pkgfs-delayed", "from": "#fshost", "as": "/pkgfs" },
   ]
 }
-
diff --git a/zircon/system/core/devmgr/fshost/main.cc b/zircon/system/core/devmgr/fshost/main.cc
index 5003a7d..4f19f7c 100644
--- a/zircon/system/core/devmgr/fshost/main.cc
+++ b/zircon/system/core/devmgr/fshost/main.cc
@@ -197,6 +197,7 @@
   while ((opt = getopt_long(argc, argv, "", options, nullptr)) != -1) {
     switch (opt) {
       case kDisableBlockWatcher:
+        printf("fshost: received --disable-block-watcher\n");
         disable_block_watcher = true;
         break;
     }
diff --git a/zircon/system/core/devmgr/shared/fdio.cc b/zircon/system/core/devmgr/shared/fdio.cc
index 097f70e..25f582a 100644
--- a/zircon/system/core/devmgr/shared/fdio.cc
+++ b/zircon/system/core/devmgr/shared/fdio.cc
@@ -65,8 +65,6 @@
 };
 
 // clang-format on
-//
-void devmgr_disable_appmgr_services() { FSTAB[1].flags = 0; }
 
 FsProvider::~FsProvider() {}
 
diff --git a/zircon/system/core/devmgr/shared/fdio.h b/zircon/system/core/devmgr/shared/fdio.h
index a21f0bb..2214241 100644
--- a/zircon/system/core/devmgr/shared/fdio.h
+++ b/zircon/system/core/devmgr/shared/fdio.h
@@ -105,8 +105,6 @@
   std::unique_ptr<char[]> raw_bytes_;
 };
 
-void devmgr_disable_appmgr_services();
-
 // The variable to set on the kernel command line to enable ld.so tracing
 // of the processes we launch.
 #define LDSO_TRACE_CMDLINE "ldso.trace"
diff --git a/zircon/system/core/svchost/svchost.cc b/zircon/system/core/svchost/svchost.cc
index 6a39579..9a87525 100644
--- a/zircon/system/core/svchost/svchost.cc
+++ b/zircon/system/core/svchost/svchost.cc
@@ -212,13 +212,13 @@
     fuchsia_boot_FactoryItems_Name,
     fuchsia_boot_Items_Name,
     fuchsia_boot_ReadOnlyLog_Name,
-    fuchsia_boot_RootJob_Name,
     fuchsia_boot_RootJobForInspect_Name,
+    fuchsia_boot_RootJob_Name,
     fuchsia_boot_RootResource_Name,
     fuchsia_boot_WriteOnlyLog_Name,
+    fuchsia_hardware_pty_Device_Name,
     fuchsia_kernel_Stats_Name,
     fuchsia_process_Launcher_Name,
-    fuchsia_hardware_pty_Device_Name,
     fuchsia_sysinfo_SysInfo_Name,
     nullptr,
     // clang-format on
@@ -314,11 +314,9 @@
   async_dispatcher_t* dispatcher = loop.dispatcher();
   svc::Outgoing outgoing(dispatcher);
 
-  zx::channel appmgr_svc = zx::channel(zx_take_startup_handle(PA_HND(PA_USER0, 0)));
   root_job = zx_take_startup_handle(PA_HND(PA_USER0, 1));
   root_resource = zx_take_startup_handle(PA_HND(PA_USER0, 2));
   zx::channel devmgr_proxy_channel = zx::channel(zx_take_startup_handle(PA_HND(PA_USER0, 3)));
-  zx::channel fshost_svc = zx::channel(zx_take_startup_handle(PA_HND(PA_USER0, 4)));
   zx::channel virtcon_proxy_channel = zx::channel(zx_take_startup_handle(PA_HND(PA_USER0, 5)));
   zx::channel miscsvc_svc = zx::channel(zx_take_startup_handle(PA_HND(PA_USER0, 6)));
   zx::channel devcoordinator_svc = zx::channel(zx_take_startup_handle(PA_HND(PA_USER0, 7)));
@@ -375,8 +373,9 @@
     }
   }
 
-  publish_services(outgoing.svc_dir(), deprecated_services, zx::unowned_channel(appmgr_svc));
-  publish_services(outgoing.svc_dir(), fshost_services, zx::unowned_channel(fshost_svc));
+  publish_services(outgoing.svc_dir(), deprecated_services,
+                   zx::unowned_channel(devcoordinator_svc));
+  publish_services(outgoing.svc_dir(), fshost_services, zx::unowned_channel(devcoordinator_svc));
   publish_services(outgoing.svc_dir(), miscsvc_services, zx::unowned_channel(miscsvc_svc));
   publish_services(outgoing.svc_dir(), devcoordinator_services,
                    zx::unowned_channel(devcoordinator_svc));
@@ -390,8 +389,8 @@
   }
 
   thrd_t thread;
-  status = start_crashsvc(zx::job(root_job), require_system ? appmgr_svc.get() : ZX_HANDLE_INVALID,
-                          &thread);
+  status = start_crashsvc(zx::job(root_job),
+                          require_system ? devcoordinator_svc.get() : ZX_HANDLE_INVALID, &thread);
   if (status != ZX_OK) {
     // The system can still function without crashsvc, log the error but
     // keep going.
diff --git a/zircon/system/ulib/devmgr-integration-test/launcher.cc b/zircon/system/ulib/devmgr-integration-test/launcher.cc
index 3e06ca2..72114ff 100644
--- a/zircon/system/ulib/devmgr-integration-test/launcher.cc
+++ b/zircon/system/ulib/devmgr-integration-test/launcher.cc
@@ -105,7 +105,8 @@
 // Components v2/Test Framework concepts as soon as those are ready enough. For now this has to be
 // manually kept in sync with devcoordinator's manifest in //src/sys/root/devcoordinator.cml
 // (although it already seems to be incomplete).
-zx_status_t host_svc_directory(zx::channel bootsvc_server, GetBootItemFunction get_boot_item,
+zx_status_t host_svc_directory(zx::channel bootsvc_server, zx::channel fshost_outgoing_client,
+                               GetBootItemFunction get_boot_item,
                                GetArgumentsFunction get_arguments, zx::unowned_job root_job) {
   async::Loop loop{&kAsyncLoopConfigNoAttachToCurrentThread};
 
@@ -131,9 +132,26 @@
     }
   }
 
+  // Connect to /svc in fshost's outgoing directory
+  zx::channel fshost_svc_client;
+  {
+    zx::channel fshost_svc_server;
+    zx_status_t status = zx::channel::create(0, &fshost_svc_client, &fshost_svc_server);
+    if (status != ZX_OK) {
+      return status;
+    }
+    status = fdio_open_at(fshost_outgoing_client.get(), "svc",
+                          ZX_FS_RIGHT_READABLE | ZX_FS_RIGHT_WRITABLE | ZX_FS_FLAG_DIRECTORY,
+                          fshost_svc_server.release());
+    if (status != ZX_OK) {
+      return status;
+    }
+  }
+
   // Forward required services from the current namespace. Currently this is just
   // fuchsia.process.Launcher.
   ForwardService(root, fuchsia_process_Launcher_Name, zx::unowned_channel(svc_client));
+  ForwardService(root, "fuchsia.fshost.Loader", zx::unowned_channel(fshost_svc_client));
 
   // Host fake instances of some services normally provided by bootsvc and routed to devcoordinator
   // by component_manager. The difference between these fakes and the optional services above is
@@ -171,9 +189,7 @@
 }
 
 __EXPORT
-IsolatedDevmgr::~IsolatedDevmgr() {
-  Terminate();
-}
+IsolatedDevmgr::~IsolatedDevmgr() { Terminate(); }
 
 __EXPORT
 void IsolatedDevmgr::Terminate() {
@@ -195,13 +211,20 @@
     return status;
   }
 
+  zx::channel fshost_outgoing_client, fshost_outgoing_server;
+  status = zx::channel::create(0, &fshost_outgoing_client, &fshost_outgoing_server);
+  if (status != ZX_OK) {
+    return status;
+  }
+
   GetBootItemFunction get_boot_item = std::move(args.get_boot_item);
   GetArgumentsFunction get_arguments = std::move(args.get_arguments);
 
   IsolatedDevmgr devmgr;
   zx::channel devfs;
   zx::channel outgoing_svc_root;
-  status = devmgr_launcher::Launch(std::move(args), std::move(svc_client), &devmgr.job_, &devfs,
+  status = devmgr_launcher::Launch(std::move(args), std::move(svc_client),
+                                   std::move(fshost_outgoing_server), &devmgr.job_, &devfs,
                                    &outgoing_svc_root);
   if (status != ZX_OK) {
     return status;
@@ -209,8 +232,8 @@
 
   // Launch host_svc_directory thread after calling devmgr_launcher::Launch, to
   // avoid a race when accessing devmgr.job_.
-  std::thread(host_svc_directory, std::move(svc_server), std::move(get_boot_item),
-              std::move(get_arguments), zx::unowned_job(devmgr.job_))
+  std::thread(host_svc_directory, std::move(svc_server), std::move(fshost_outgoing_client),
+              std::move(get_boot_item), std::move(get_arguments), zx::unowned_job(devmgr.job_))
       .detach();
 
   int fd;
diff --git a/zircon/system/ulib/devmgr-launcher/include/lib/devmgr-launcher/launch.h b/zircon/system/ulib/devmgr-launcher/include/lib/devmgr-launcher/launch.h
index ab0f9ef..776b7be 100644
--- a/zircon/system/ulib/devmgr-launcher/include/lib/devmgr-launcher/launch.h
+++ b/zircon/system/ulib/devmgr-launcher/include/lib/devmgr-launcher/launch.h
@@ -2,7 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#pragma once
+#ifndef LIB_DEVMGR_LAUNCHER_LAUNCH_H_
+#define LIB_DEVMGR_LAUNCHER_LAUNCH_H_
 
 #include <lib/fit/function.h>
 #include <lib/zx/channel.h>
@@ -62,7 +63,9 @@
 //
 // Returns its containing job and a channel to the root of its devfs.
 // To destroy the devmgr, issue |devmgr_job->kill()|.
-zx_status_t Launch(Args args, zx::channel svc_client, zx::job* devmgr_job, zx::channel* devfs_root,
-                   zx::channel* outgoing_svc_root);
+zx_status_t Launch(Args args, zx::channel svc_client, zx::channel fshost_outgoing_server,
+                   zx::job* devmgr_job, zx::channel* devfs_root, zx::channel* outgoing_svc_root);
 
 }  // namespace devmgr_launcher
+
+#endif  // LIB_DEVMGR_LAUNCHER_LAUNCH_H_
diff --git a/zircon/system/ulib/devmgr-launcher/launcher.cc b/zircon/system/ulib/devmgr-launcher/launcher.cc
index e772124..79c3387 100644
--- a/zircon/system/ulib/devmgr-launcher/launcher.cc
+++ b/zircon/system/ulib/devmgr-launcher/launcher.cc
@@ -25,21 +25,97 @@
 namespace {
 
 constexpr const char* kDevmgrPath = "/boot/bin/devcoordinator";
+constexpr const char* kFshostPath = "/boot/bin/fshost";
 
 }  // namespace
 
 namespace devmgr_launcher {
 
+// Launches an fshost process in the given job. Fshost will have devfs_client
+// installed in its namespace as /dev, and svc_client as /svc
+zx_status_t LaunchFshost(Args args, zx::channel svc_client, zx::channel fshost_outgoing_server,
+                         zx::job devmgr_job, zx::channel devfs_client) {
+  zx::job job_copy;
+  zx_status_t status = devmgr_job.duplicate(ZX_RIGHT_SAME_RIGHTS, &job_copy);
+  if (status != ZX_OK) {
+    return status;
+  }
+
+  const bool clone_stdio = !args.stdio.is_valid();
+
+  fbl::Vector<const char*> argv;
+  argv.push_back(kFshostPath);
+  if (args.disable_block_watcher) {
+    argv.push_back("--disable-block-watcher");
+  }
+  argv.push_back(nullptr);
+
+  fbl::Vector<fdio_spawn_action_t> actions;
+  actions.push_back(fdio_spawn_action_t{
+      .action = FDIO_SPAWN_ACTION_SET_NAME,
+      .name = {.data = "test-fshost"},
+  });
+  actions.push_back(fdio_spawn_action_t{
+      .action = FDIO_SPAWN_ACTION_ADD_HANDLE,
+      .h = {.id = PA_HND(PA_JOB_DEFAULT, 0), .handle = job_copy.release()},
+  });
+  actions.push_back(fdio_spawn_action_t{
+      .action = FDIO_SPAWN_ACTION_ADD_HANDLE,
+      .h = {.id = PA_HND(PA_DIRECTORY_REQUEST, 0), .handle = fshost_outgoing_server.release()},
+  });
+
+  actions.push_back(fdio_spawn_action_t{
+      .action = FDIO_SPAWN_ACTION_ADD_NS_ENTRY,
+      .ns = {.prefix = "/dev", .handle = devfs_client.release()},
+  });
+
+  actions.push_back(fdio_spawn_action_t{
+      .action = FDIO_SPAWN_ACTION_CLONE_DIR,
+      .dir = {.prefix = "/boot"},
+  });
+
+  actions.push_back(fdio_spawn_action_t{
+      .action = FDIO_SPAWN_ACTION_ADD_NS_ENTRY,
+      .ns = {.prefix = "/svc", .handle = svc_client.release()},
+  });
+
+  if (!clone_stdio) {
+    actions.push_back(fdio_spawn_action_t{
+        .action = FDIO_SPAWN_ACTION_TRANSFER_FD,
+        .fd = {.local_fd = args.stdio.release(), .target_fd = FDIO_FLAG_USE_FOR_STDIO},
+    });
+  }
+
+  uint32_t flags = FDIO_SPAWN_DEFAULT_LDSVC;
+  if (clone_stdio) {
+    flags |= FDIO_SPAWN_CLONE_STDIO;
+  }
+
+  zx::process new_process;
+  status = fdio_spawn_etc(devmgr_job.get(), flags, kFshostPath, argv.data(), nullptr /* environ */,
+                          actions.size(), actions.data(), new_process.reset_and_get_address(),
+                          nullptr /* err_msg */);
+  if (status != ZX_OK) {
+    return status;
+  }
+  return ZX_OK;
+}
+
 __EXPORT
-zx_status_t Launch(Args args, zx::channel svc_client, zx::job* devmgr_job, zx::channel* devfs_root,
+zx_status_t Launch(Args args, zx::channel svc_client, zx::channel fshost_outgoing_server,
+                   zx::job* devmgr_job, zx::channel* devfs_root,
                    zx::channel* outgoing_services_root) {
-  // Create containing job (and copy to send to devmgr)
-  zx::job job, job_copy;
+  // Create containing job (and copies for devcoordinator and fshost)
+  zx::job job, devmgr_job_copy, fshost_job_copy;
   zx_status_t status = zx::job::create(*zx::job::default_job(), 0, &job);
   if (status != ZX_OK) {
     return status;
   }
-  status = job.duplicate(ZX_RIGHT_SAME_RIGHTS, &job_copy);
+  status = job.duplicate(ZX_RIGHT_SAME_RIGHTS, &devmgr_job_copy);
+  if (status != ZX_OK) {
+    return status;
+  }
+  status = job.duplicate(ZX_RIGHT_SAME_RIGHTS, &fshost_job_copy);
   if (status != ZX_OK) {
     return status;
   }
@@ -51,6 +127,12 @@
     return status;
   }
 
+  // Create devfs client clone for fshost
+  zx::channel devfs_for_fshost(fdio_service_clone(devfs_client.get()));
+
+  // Create svc client clone for fshost
+  zx::channel svc_client_for_fshost(fdio_service_clone(svc_client.get()));
+
   // Create channel to connect to outgoing services
   zx::channel outgoing_services_client, outgoing_services_server;
   status = zx::channel::create(0, &outgoing_services_client, &outgoing_services_server);
@@ -90,7 +172,7 @@
   });
   actions.push_back(fdio_spawn_action_t{
       .action = FDIO_SPAWN_ACTION_ADD_HANDLE,
-      .h = {.id = PA_HND(PA_JOB_DEFAULT, 0), .handle = job_copy.release()},
+      .h = {.id = PA_HND(PA_JOB_DEFAULT, 0), .handle = devmgr_job_copy.release()},
   });
   actions.push_back(fdio_spawn_action_t{
       .action = FDIO_SPAWN_ACTION_ADD_HANDLE,
@@ -103,9 +185,10 @@
   });
 
   for (auto& ns : args.flat_namespace) {
+    zx_handle_t ns_handle_clone = fdio_service_clone(ns.second.get());
     actions.push_back(fdio_spawn_action_t{
         .action = FDIO_SPAWN_ACTION_ADD_NS_ENTRY,
-        .ns = {.prefix = ns.first, .handle = ns.second.release()},
+        .ns = {.prefix = ns.first, .handle = ns_handle_clone},
     });
   }
 
@@ -120,9 +203,23 @@
   });
 
   if (!clone_stdio) {
+    zx_handle_t stdio_clone;
+    status = fdio_fd_clone(args.stdio.get(), &stdio_clone);
+    if (status != ZX_OK) {
+      return status;
+    }
+
+    fdio_t* stdio_clone_fdio_t;
+    status = fdio_create(stdio_clone, &stdio_clone_fdio_t);
+    if (status != ZX_OK) {
+      return status;
+    }
+
+    int stdio_clone_fd = fdio_bind_to_fd(stdio_clone_fdio_t, -1, 0);
+
     actions.push_back(fdio_spawn_action_t{
         .action = FDIO_SPAWN_ACTION_TRANSFER_FD,
-        .fd = {.local_fd = args.stdio.release(), .target_fd = FDIO_FLAG_USE_FOR_STDIO},
+        .fd = {.local_fd = stdio_clone_fd, .target_fd = FDIO_FLAG_USE_FOR_STDIO},
     });
   }
 
@@ -139,6 +236,13 @@
     return status;
   }
 
+  status = LaunchFshost(std::move(args), std::move(svc_client_for_fshost),
+                        std::move(fshost_outgoing_server), std::move(fshost_job_copy),
+                        std::move(devfs_for_fshost));
+  if (status != ZX_OK) {
+    return status;
+  }
+
   *devmgr_job = std::move(job);
   *devfs_root = std::move(devfs_client);
   *outgoing_services_root = std::move(outgoing_services_client);