| // Copyright 2016 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "fs-manager.h" |
| |
| #include <fcntl.h> |
| #include <fuchsia/fshost/llcpp/fidl.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async/cpp/wait.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/io.h> |
| #include <lib/inspect/service/cpp/service.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/zircon-internal/debug.h> |
| #include <lib/zircon-internal/thread_annotations.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <threads.h> |
| #include <zircon/device/vfs.h> |
| #include <zircon/processargs.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| |
| #include <iterator> |
| #include <memory> |
| #include <string_view> |
| #include <utility> |
| |
| #include <fbl/algorithm.h> |
| #include <fbl/alloc_checker.h> |
| #include <fbl/auto_lock.h> |
| #include <fbl/ref_ptr.h> |
| #include <fs/remote_dir.h> |
| #include <fs/vfs.h> |
| #include <fs/vfs_types.h> |
| |
| #include "admin-server.h" |
| #include "block-watcher.h" |
| #include "fshost-boot-args.h" |
| #include "lib/async/cpp/task.h" |
| #include "lifecycle.h" |
| #include "metrics.h" |
| |
| #define ZXDEBUG 0 |
| |
| namespace devmgr { |
| |
| FsManager::FsManager(std::shared_ptr<FshostBootArgs> boot_args, |
| std::unique_ptr<FsHostMetrics> metrics) |
| : global_loop_(new async::Loop(&kAsyncLoopConfigNoAttachToCurrentThread)), |
| outgoing_vfs_(fs::ManagedVfs(global_loop_->dispatcher())), |
| registry_(global_loop_.get()), |
| metrics_(std::move(metrics)), |
| boot_args_(boot_args) { |
| ZX_ASSERT(global_root_ == nullptr); |
| } |
| |
| // In the event that we haven't been explicitly signalled, tear ourself down. |
| FsManager::~FsManager() { |
| if (!shutdown_called_) { |
| Shutdown([](zx_status_t status) { |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "filesystem shutdown failed: " << zx_status_get_string(status); |
| return; |
| } |
| FX_LOGS(INFO) << "filesystem shutdown complete"; |
| }); |
| } |
| sync_completion_wait(&shutdown_, ZX_TIME_INFINITE); |
| } |
| |
| zx_status_t FsManager::SetupLifecycleServer( |
| fidl::ServerEnd<::llcpp::fuchsia::process::lifecycle::Lifecycle> lifecycle_request) { |
| return devmgr::LifecycleServer::Create(global_loop_->dispatcher(), this, |
| std::move(lifecycle_request)); |
| } |
| |
| // Sets up the outgoing directory, and runs it on the PA_DIRECTORY_REQUEST |
| // handle if it exists. See fshost.cml for a list of what's in the directory. |
| zx_status_t FsManager::SetupOutgoingDirectory( |
| fidl::ServerEnd<::llcpp::fuchsia::io::Directory> dir_request, |
| std::shared_ptr<loader::LoaderServiceBase> loader, BlockWatcher& watcher) { |
| auto outgoing_dir = fbl::MakeRefCounted<fs::PseudoDir>(); |
| |
| // TODO(unknown): fshost exposes two separate service directories, one here and one in |
| // the registry vfs that's mounted under fs-manager-svc further down in this |
| // function. These should be combined by either pulling the registry services |
| // into this VFS or by pushing the services in this directory into the |
| // registry. |
| |
| // Add loader and admin services to the vfs |
| svc_dir_ = fbl::MakeRefCounted<fs::PseudoDir>(); |
| |
| if (loader) { |
| // 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. |
| svc_dir_->AddEntry( |
| "fuchsia.fshost.Loader", fbl::MakeRefCounted<fs::Service>([loader](zx::channel chan) { |
| auto status = loader->Bind(std::move(chan)); |
| if (status.is_error()) { |
| FX_LOGS(ERROR) << "failed to attach loader service: " << status.status_string(); |
| } |
| return status.status_value(); |
| })); |
| } |
| svc_dir_->AddEntry(llcpp::fuchsia::fshost::Admin::Name, |
| AdminServer::Create(this, global_loop_->dispatcher())); |
| |
| svc_dir_->AddEntry(llcpp::fuchsia::fshost::BlockWatcher::Name, |
| BlockWatcherServer::Create(global_loop_->dispatcher(), watcher)); |
| |
| outgoing_dir->AddEntry("svc", svc_dir_); |
| |
| // Add /fs to the outgoing vfs |
| zx::channel filesystems_client, filesystems_server; |
| zx_status_t status = zx::channel::create(0, &filesystems_client, &filesystems_server); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "failed to create channel"; |
| return status; |
| } |
| status = this->ServeRoot(std::move(filesystems_server)); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Cannot serve root filesystem"; |
| return status; |
| } |
| outgoing_dir->AddEntry("fs", fbl::MakeRefCounted<fs::RemoteDir>(std::move(filesystems_client))); |
| |
| // Add /fs-manager-svc to the vfs |
| zx::channel services_client, services_server; |
| status = zx::channel::create(0, &services_client, &services_server); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "failed to create channel"; |
| return status; |
| } |
| status = this->ServeFshostRoot(std::move(services_server)); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Cannot serve export directory"; |
| return status; |
| } |
| outgoing_dir->AddEntry("fs-manager-svc", |
| fbl::MakeRefCounted<fs::RemoteDir>(std::move(services_client))); |
| |
| // TODO(fxbug.dev/39588): delete this |
| // Add the delayed directory |
| zx::channel filesystems_client_2, filesystems_server_2; |
| status = zx::channel::create(0, &filesystems_client_2, &filesystems_server_2); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "failed to create channel"; |
| return status; |
| } |
| status = this->ServeRoot(std::move(filesystems_server_2)); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "cannot serve root filesystem"; |
| return status; |
| } |
| outgoing_dir->AddEntry("delayed", delayed_outdir_.Initialize(std::move(filesystems_client_2))); |
| |
| // Add the diagnostics directory |
| diagnostics_dir_ = inspect_.Initialize(global_loop_->dispatcher()); |
| outgoing_dir->AddEntry("diagnostics", diagnostics_dir_); |
| |
| // Run the outgoing directory |
| outgoing_vfs_.ServeDirectory(outgoing_dir, std::move(dir_request)); |
| return ZX_OK; |
| } |
| |
| zx_status_t FsManager::Initialize( |
| fidl::ServerEnd<::llcpp::fuchsia::io::Directory> dir_request, |
| fidl::ServerEnd<::llcpp::fuchsia::process::lifecycle::Lifecycle> lifecycle_request, |
| fidl::ClientEnd<::llcpp::fuchsia::device::manager::Administrator> driver_admin, |
| std::shared_ptr<loader::LoaderServiceBase> loader, BlockWatcher& watcher) { |
| zx_status_t status = memfs::Vfs::Create("<root>", &root_vfs_, &global_root_); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| fbl::RefPtr<fs::Vnode> vn; |
| if ((status = global_root_->Create("boot", S_IFDIR, &vn)) != ZX_OK) { |
| return status; |
| } |
| if ((status = global_root_->Create("tmp", S_IFDIR, &vn)) != ZX_OK) { |
| return status; |
| } |
| for (const auto& point : kAllMountPoints) { |
| auto open_result = root_vfs_->Open(global_root_, std::string_view(MountPointPath(point)), |
| fs::VnodeConnectionOptions::ReadWrite().set_create(), |
| fs::Rights::ReadWrite(), S_IFDIR); |
| if (open_result.is_error()) { |
| return open_result.error(); |
| } |
| |
| ZX_ASSERT(open_result.is_ok()); |
| mount_nodes_[point].root_directory = std::move(open_result.ok().vnode); |
| } |
| |
| auto open_result = |
| root_vfs_->Open(global_root_, std::string_view("/data"), |
| fs::VnodeConnectionOptions::ReadOnly(), fs::Rights::ReadOnly(), S_IFDIR); |
| if (open_result.is_ok()) { |
| inspect_.ServeStats("data", open_result.ok().vnode); |
| } else { |
| FX_LOGS(ERROR) << "failed to serve /data stats"; |
| } |
| |
| global_loop_->StartThread("root-dispatcher"); |
| root_vfs_->SetDispatcher(global_loop_->dispatcher()); |
| if (dir_request.is_valid()) { |
| status = SetupOutgoingDirectory(std::move(dir_request), std::move(loader), watcher); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| if (lifecycle_request.is_valid()) { |
| status = SetupLifecycleServer(std::move(lifecycle_request)); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| if (driver_admin.is_valid()) { |
| driver_admin_ = fidl::Client<llcpp::fuchsia::device::manager::Administrator>( |
| std::move(driver_admin), global_loop_->dispatcher()); |
| } |
| return ZX_OK; |
| } |
| |
| void FsManager::FlushMetrics() { mutable_metrics()->Flush(); } |
| |
| zx_status_t FsManager::InstallFs(MountPoint point, zx::channel root_directory) { |
| if (mount_nodes_.find(point) == mount_nodes_.end()) { |
| // The map should have been fully initialized. |
| return ZX_ERR_BAD_STATE; |
| } |
| if (zx_status_t status = root_vfs_->InstallRemote(mount_nodes_[point].root_directory, |
| fs::MountChannel(std::move(root_directory))); |
| status != ZX_OK) { |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t FsManager::SetFsExportRoot(MountPoint point, zx::channel export_root_directory) { |
| if (mount_nodes_.find(point) == mount_nodes_.end()) { |
| // The map should have been fully initialized. |
| return ZX_ERR_BAD_STATE; |
| } |
| mount_nodes_[point].root_export_dir = std::move(export_root_directory); |
| return ZX_OK; |
| } |
| |
| zx_status_t FsManager::ServeRoot(fidl::ServerEnd<::llcpp::fuchsia::io::Directory> server) { |
| fs::Rights rights; |
| rights.read = true; |
| rights.write = true; |
| rights.admin = true; |
| rights.execute = true; |
| return root_vfs_->ServeDirectory(global_root_, std::move(server), rights); |
| } |
| |
| void FsManager::RemoveSystemDrivers(fit::callback<void(zx_status_t)> callback) { |
| // If we don't have a connection to Driver Manager, just return ZX_OK. |
| if (driver_admin_.get() == nullptr) { |
| callback(ZX_OK); |
| return; |
| } |
| |
| auto callback_ptr = std::make_shared<fit::callback<void(zx_status_t)>>(std::move(callback)); |
| auto res = driver_admin_->UnregisterSystemStorageForShutdown( |
| [callback_ptr](llcpp::fuchsia::device::manager::Administrator:: |
| UnregisterSystemStorageForShutdownResponse* res) { |
| if (res->status != ZX_OK) { |
| FX_LOGS(ERROR) << "RemoveSystemDevices returned error: " |
| << zx_status_get_string(res->status); |
| } |
| if (*callback_ptr) { |
| (*callback_ptr)(res->status); |
| } |
| }); |
| if (res.status() != ZX_OK) { |
| if (*callback_ptr) { |
| (*callback_ptr)(res.status()); |
| } |
| } |
| } |
| |
| void FsManager::Shutdown(fit::function<void(zx_status_t)> callback) { |
| std::lock_guard guard(lock_); |
| if (shutdown_called_) { |
| FX_LOGS(ERROR) << "shutdown called more than once"; |
| callback(ZX_ERR_INTERNAL); |
| return; |
| } |
| shutdown_called_ = true; |
| |
| async::PostTask(global_loop_->dispatcher(), [this, callback = std::move(callback)]() mutable { |
| FX_LOGS(INFO) << "filesystem shutdown initiated"; |
| RemoveSystemDrivers([this, callback = std::move(callback)](zx_status_t status) mutable { |
| FX_LOGS(INFO) << "RemoveSystemDrivers returned: " << zx_status_get_string(status); |
| |
| status = root_vfs_->UninstallAll(zx::time::infinite()); |
| callback(status); |
| sync_completion_signal(&shutdown_); |
| // after this signal, FsManager can be destroyed. |
| }); |
| }); |
| } |
| |
| bool FsManager::IsShutdown() { return sync_completion_signaled(&shutdown_); } |
| |
| void FsManager::WaitForShutdown() { sync_completion_wait(&shutdown_, ZX_TIME_INFINITE); } |
| |
| const char* FsManager::MountPointPath(FsManager::MountPoint point) { |
| switch (point) { |
| case MountPoint::kUnknown: |
| return ""; |
| case MountPoint::kBin: |
| return "/bin"; |
| case MountPoint::kData: |
| return "/data"; |
| case MountPoint::kVolume: |
| return "/volume"; |
| case MountPoint::kSystem: |
| return "/system"; |
| case MountPoint::kInstall: |
| return "/install"; |
| case MountPoint::kBlob: |
| return "/blob"; |
| case MountPoint::kPkgfs: |
| return "/pkgfs"; |
| case MountPoint::kFactory: |
| return "/factory"; |
| case MountPoint::kDurable: |
| return "/durable"; |
| } |
| } |
| |
| zx_status_t FsManager::ForwardFsDiagnosticsDirectory(MountPoint point, |
| const char* diagnostics_dir_name) { |
| // The diagnostics directory may not be initialized in tests. |
| if (diagnostics_dir_ == nullptr) { |
| return ZX_ERR_INTERNAL; |
| } |
| if (point == MountPoint::kUnknown) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (!mount_nodes_[point].root_export_dir) { |
| FX_LOGS(ERROR) << "Can't forward diagnostics dir for " << MountPointPath(point) |
| << ", export root directory was not set"; |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| auto inspect_node = fbl::MakeRefCounted<fs::Service>([this, point](zx::channel request) { |
| std::string name = std::string("diagnostics/") + fuchsia::inspect::Tree::Name_; |
| return fdio_service_connect_at(mount_nodes_[point].root_export_dir.get(), name.c_str(), |
| request.release()); |
| }); |
| auto fs_diagnostics_dir = fbl::MakeRefCounted<fs::PseudoDir>(); |
| zx_status_t status = fs_diagnostics_dir->AddEntry(fuchsia::inspect::Tree::Name_, inspect_node); |
| if (status != ZX_OK) { |
| return status; |
| } |
| return diagnostics_dir_->AddEntry(diagnostics_dir_name, fs_diagnostics_dir); |
| } |
| |
| zx_status_t FsManager::ForwardFsService(MountPoint point, const char* service_name) { |
| // The outgoing service directory may not be initialized in tests. |
| if (svc_dir_ == nullptr) { |
| return ZX_ERR_INTERNAL; |
| } |
| if (point == MountPoint::kUnknown) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (!mount_nodes_[point].root_export_dir) { |
| FX_LOGS(ERROR) << "Can't forward service for " << MountPointPath(point) |
| << ", export root directory was not set"; |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| auto service_node = |
| fbl::MakeRefCounted<fs::Service>([this, point, service_name](zx::channel request) { |
| std::string name = std::string("svc/") + service_name; |
| return fdio_service_connect_at(mount_nodes_[point].root_export_dir.get(), name.c_str(), |
| request.release()); |
| }); |
| return svc_dir_->AddEntry(service_name, std::move(service_node)); |
| } |
| |
| } // namespace devmgr |