| // 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 <fidl/fuchsia.feedback/cpp/wire.h> |
| #include <fidl/fuchsia.fshost/cpp/wire.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/service/llcpp/service.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <threads.h> |
| #include <zircon/errors.h> |
| #include <zircon/processargs.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| |
| #include <iterator> |
| #include <memory> |
| #include <string> |
| #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 <fbl/string.h> |
| |
| #include "admin-server.h" |
| #include "block-watcher.h" |
| #include "fidl/fuchsia.ldsvc/cpp/wire.h" |
| #include "fshost-boot-args.h" |
| #include "lib/async/cpp/task.h" |
| #include "lifecycle.h" |
| #include "src/lib/storage/vfs/cpp/remote_dir.h" |
| #include "src/lib/storage/vfs/cpp/vfs.h" |
| #include "src/lib/storage/vfs/cpp/vfs_types.h" |
| |
| namespace fshost { |
| |
| namespace { |
| |
| fbl::String ReportReasonString(fs_management::DiskFormat format, FsManager::ReportReason reason) { |
| switch (reason) { |
| case FsManager::ReportReason::kFsckFailure: |
| return fbl::String::Concat( |
| {"fuchsia-", fs_management::DiskFormatString(format), "-corruption"}); |
| } |
| } |
| |
| zx::status<uint64_t> GetFsId(fidl::UnownedClientEnd<fuchsia_io::Directory> root) { |
| auto result = fidl::WireCall(root)->QueryFilesystem(); |
| if (!result.ok()) |
| return zx::error(result.status()); |
| if (result.value().s != ZX_OK) |
| return zx::error(result.value().s); |
| return zx::ok(result.value().info->fs_id); |
| } |
| |
| } // namespace |
| |
| FsManager::MountedFilesystem::~MountedFilesystem() { |
| if (auto status = fs_management::Shutdown(export_root_); status.is_error()) |
| FX_LOGS(WARNING) << "Unmount error: " << status.status_string(); |
| } |
| |
| FsManager::FsManager(std::shared_ptr<FshostBootArgs> boot_args) |
| : global_loop_(new async::Loop(&kAsyncLoopConfigNoAttachToCurrentThread)), |
| vfs_(fs::ManagedVfs(global_loop_->dispatcher())), |
| boot_args_(std::move(boot_args)) {} |
| |
| // 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); |
| global_loop_->Shutdown(); |
| } |
| |
| zx_status_t FsManager::Initialize( |
| fidl::ServerEnd<fuchsia_io::Directory> dir_request, |
| fidl::ServerEnd<fuchsia_process_lifecycle::Lifecycle> lifecycle_request, |
| const fshost_config::Config& config, BlockWatcher& watcher) { |
| global_loop_->StartThread("root-dispatcher"); |
| |
| auto outgoing_dir = fbl::MakeRefCounted<fs::PseudoDir>(); |
| |
| // Add services to the vfs |
| svc_dir_ = fbl::MakeRefCounted<fs::PseudoDir>(); |
| svc_dir_->AddEntry(fidl::DiscoverableProtocolName<fuchsia_fshost::Admin>, |
| AdminServer::Create(this, global_loop_->dispatcher())); |
| svc_dir_->AddEntry(fidl::DiscoverableProtocolName<fuchsia_fshost::BlockWatcher>, |
| BlockWatcherServer::Create(global_loop_->dispatcher(), watcher)); |
| outgoing_dir->AddEntry("svc", svc_dir_); |
| |
| fs_dir_ = fbl::MakeRefCounted<fs::PseudoDir>(); |
| |
| // Construct the list of mount points we will be serving. Durable and Factory are somewhat special |
| // cases - they rarely exist as partitions on the device, but they are always exported as |
| // directory capabilities. If we aren't configured to find these partitions, don't queue requests |
| // for them, and instead point them at an empty, read-only folder in the fs dir, so the directory |
| // capability can be successfully routed. |
| std::vector<MountPoint> mount_points; |
| if (config.data_filesystem_format() == "f2fs") { |
| mount_points.push_back(MountPoint::kData); |
| } |
| if (config.durable()) { |
| mount_points.push_back(MountPoint::kDurable); |
| } else { |
| fs_dir_->AddEntry(MountPointPath(MountPoint::kDurable), fbl::MakeRefCounted<fs::PseudoDir>()); |
| } |
| if (config.factory()) { |
| mount_points.push_back(MountPoint::kFactory); |
| } else { |
| fs_dir_->AddEntry(MountPointPath(MountPoint::kFactory), fbl::MakeRefCounted<fs::PseudoDir>()); |
| } |
| |
| for (const auto& point : mount_points) { |
| zx::status endpoints_or = fidl::CreateEndpoints<fuchsia_io::Directory>(); |
| if (endpoints_or.is_error()) { |
| return endpoints_or.status_value(); |
| } |
| |
| // FsRootHandle issues an Open call on the export root. These open calls are asynchronous - |
| // they are queued into the channel pair and serviced when the filesystem is started. |
| // Similarly, calls on the pair created by FsRootHandle, of which root_or is the client end, |
| // are also queued. |
| zx::status root_or = fs_management::FsRootHandle(endpoints_or->client); |
| if (root_or.is_error()) { |
| return root_or.status_value(); |
| } |
| |
| zx_status_t status = fs_dir_->AddEntry(MountPointPath(point), |
| fbl::MakeRefCounted<fs::RemoteDir>(std::move(*root_or))); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "failed to add " << MountPointPath(point) << " to /fs directory"; |
| } |
| |
| auto [it, inserted] = |
| mount_nodes_.try_emplace(point, FsManager::MountNode{ |
| .export_root = std::move(endpoints_or->client), |
| .server_end = std::move(endpoints_or->server), |
| }); |
| if (!inserted) { |
| FX_LOGS(ERROR) << "Channel pair for mount point " << MountPointPath(point) |
| << " already exists"; |
| } |
| } |
| outgoing_dir->AddEntry("fs", fs_dir_); |
| |
| diagnostics_dir_ = inspect_manager_.Initialize(global_loop_->dispatcher()); |
| outgoing_dir->AddEntry("diagnostics", diagnostics_dir_); |
| |
| fbl::RefPtr<memfs::VnodeDir> tmp_vnode; |
| zx_status_t status = memfs::Memfs::Create(global_loop_->dispatcher(), "<tmp>", &tmp_, &tmp_vnode); |
| if (status != ZX_OK) { |
| return status; |
| } |
| outgoing_dir->AddEntry("tmp", tmp_vnode); |
| |
| mnt_dir_ = fbl::MakeRefCounted<fs::PseudoDir>(); |
| outgoing_dir->AddEntry("mnt", mnt_dir_); |
| |
| if (dir_request.is_valid()) { |
| // Run the outgoing directory. |
| vfs_.ServeDirectory(outgoing_dir, std::move(dir_request)); |
| } |
| if (lifecycle_request.is_valid()) { |
| zx_status_t status = |
| LifecycleServer::Create(global_loop_->dispatcher(), this, std::move(lifecycle_request)); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| return ZX_OK; |
| } |
| |
| zx::status<fidl::ClientEnd<fuchsia_io::Directory>> FsManager::GetFsDir() { |
| zx::status endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>(); |
| if (endpoints.is_error()) { |
| return endpoints.take_error(); |
| } |
| zx_status_t status = vfs_.ServeDirectory(fs_dir_, std::move(endpoints->server)); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| return zx::ok(std::move(endpoints->client)); |
| } |
| |
| std::optional<FsManager::MountPointEndpoints> FsManager::TakeMountPointServerEnd(MountPoint point) { |
| // Hold the shutdown lock for the entire duration of the install to avoid racing with shutdown on |
| // adding/removing the remote mount. |
| std::lock_guard guard(shutdown_lock_); |
| if (shutdown_called_) { |
| FX_LOGS(INFO) << "Not installing " << MountPointPath(point) << " after shutdown"; |
| return std::nullopt; |
| } |
| |
| auto node = mount_nodes_.find(point); |
| if (node == mount_nodes_.end()) { |
| // The map should have been fully initialized. |
| return std::nullopt; |
| } |
| if (!node->second.server_end.has_value()) { |
| // The server end for this mount point was already taken, or the map was not fully initialized. |
| return std::nullopt; |
| } |
| fidl::ServerEnd<fuchsia_io::Directory> server_end = |
| std::exchange(node->second.server_end, std::nullopt).value(); |
| |
| return FsManager::MountPointEndpoints{ |
| .export_root = node->second.export_root, |
| .server_end = std::move(server_end), |
| }; |
| } |
| |
| void FsManager::RegisterDevicePath(MountPoint point, std::string_view device_path) { |
| // Retrieving the device path and setting it for a particular filesystem is best-effort, so any |
| // failures are logged but otherwise ignored. |
| if (device_path.empty()) |
| return; |
| |
| std::lock_guard guard(shutdown_lock_); |
| if (shutdown_called_) { |
| FX_LOGS(INFO) << "Not registering device path for " << MountPointPath(point) |
| << " after shutdown"; |
| return; |
| } |
| |
| zx::status root_or = GetRoot(point); |
| if (root_or.is_error()) { |
| FX_PLOGS(WARNING, root_or.status_value()) << "Failed to get root handle for mount point"; |
| return; |
| } |
| if (auto result = fidl::WireCall(*root_or)->QueryFilesystem(); !result.ok()) { |
| FX_PLOGS(WARNING, result.status()) << "QueryFilesystem call failed (fidl error)"; |
| return; |
| } else if (result->s != ZX_OK) { |
| FX_PLOGS(WARNING, result->s) << "QueryFilesystem call failed"; |
| return; |
| } else { |
| std::lock_guard guard(device_paths_lock_); |
| if (!device_paths_.try_emplace(result->info->fs_id, device_path).second) { |
| FX_LOGS(WARNING) << "Device path entry for fs id " << result->info->fs_id |
| << " already exists; not inserting " << device_path; |
| } |
| } |
| } |
| |
| void FsManager::Shutdown(fit::function<void(zx_status_t)> callback) { |
| std::lock_guard guard(shutdown_lock_); |
| if (shutdown_called_) { |
| FX_LOGS(ERROR) << "shutdown called more than once"; |
| callback(ZX_ERR_INTERNAL); |
| return; |
| } |
| shutdown_called_ = true; |
| |
| FX_LOGS(INFO) << "filesystem shutdown initiated"; |
| // Shutting down fshost involves sending asynchronous shutdown signals to several different |
| // systems in order with continuation passing. |
| // 0. Before fshost is told to shut down, almost everything that is running out of the |
| // filesystems is shut down by component manager. Also before this, blobfs is told to shut |
| // down by component manager. Blobfs, as part of it's shutdown, notifies driver manager that |
| // drivers running out of /system should be shut down. |
| // 1. Shut down any filesystems which were started, synchronously calling shutdown on each one in |
| // no particular order. |
| // 2. Shut down the memfs which hosts /tmp. |
| // 3. Shut down the vfs. This hosts the fshost outgoing directory. |
| // 4. Call the shutdown callback provided when the shutdown function was called. |
| // 5. Signal the shutdown completion that shutdown is complete. After this point, the FsManager |
| // class can be destroyed, and fshost can exit. |
| // If at any point we hit an error, we log loudly, but continue with the shutdown procedure. At |
| // the end, we send the callback whatever the first error value we encountered was. |
| std::vector<std::pair<MountPoint, fidl::ClientEnd<fuchsia_io::Directory>>> |
| filesystems_to_shut_down; |
| for (auto& [point, node] : mount_nodes_) { |
| if (!node.server_end.has_value()) { |
| filesystems_to_shut_down.emplace_back(point, std::move(node.export_root)); |
| } |
| } |
| |
| // fs_management::Shutdown is synchronous, so we spawn a thread to shut down |
| // the mounted filesystems. |
| std::thread shutdown_thread([this, callback = std::move(callback), |
| filesystems_to_shutdown = |
| std::move(filesystems_to_shut_down)]() mutable { |
| // Ensure that we are ready for shutdown. |
| sync_completion_wait(&ready_for_shutdown_, ZX_TIME_INFINITE); |
| auto merge_status = [first_status = ZX_OK](zx_status_t status) mutable { |
| if (first_status == ZX_OK) |
| first_status = status; |
| return first_status; |
| }; |
| |
| for (auto& [point, fs] : filesystems_to_shutdown) { |
| FX_LOGS(INFO) << "Shutting down " << MountPointPath(point); |
| if (auto result = fs_management::Shutdown({fs.borrow()}); result.is_error()) { |
| FX_LOGS(WARNING) << "Failed to shut down " << MountPointPath(point) << ": " |
| << result.status_string(); |
| merge_status(result.error_value()); |
| } |
| } |
| |
| // Continue on the async loop... |
| zx_status_t status = async::PostTask( |
| global_loop_->dispatcher(), |
| [this, callback = std::move(callback), merge_status = std::move(merge_status)]() mutable { |
| tmp_->Shutdown([this, callback = std::move(callback), |
| merge_status = std::move(merge_status)](zx_status_t status) mutable { |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "tmp shutdown failed: " << zx_status_get_string(status); |
| merge_status(status); |
| } |
| vfs_.Shutdown([this, callback = std::move(callback), |
| merge_status = std::move(merge_status)](zx_status_t status) mutable { |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "vfs shutdown failed: " << zx_status_get_string(status); |
| merge_status(status); |
| } |
| callback(merge_status(ZX_OK)); |
| sync_completion_signal(&shutdown_); |
| // after this signal, FsManager can be destroyed. |
| }); |
| }); |
| }); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Unable to finish shut down: " << zx_status_get_string(status); |
| // We can't call the callback here because we moved it, but we don't expect posting the task |
| // to fail, so let's not worry about it. |
| } |
| }); |
| |
| shutdown_thread.detach(); |
| } |
| |
| bool FsManager::IsShutdown() { return sync_completion_signaled(&shutdown_); } |
| |
| void FsManager::WaitForShutdown() { sync_completion_wait(&shutdown_, ZX_TIME_INFINITE); } |
| |
| void FsManager::ReadyForShutdown() { sync_completion_signal(&ready_for_shutdown_); } |
| |
| const char* FsManager::MountPointPath(FsManager::MountPoint point) { |
| switch (point) { |
| case MountPoint::kData: |
| return "data"; |
| case MountPoint::kFactory: |
| return "factory"; |
| case MountPoint::kDurable: |
| return "durable"; |
| } |
| } |
| |
| zx_status_t FsManager::ForwardFsDiagnosticsDirectory(MountPoint point, |
| std::string_view diagnostics_dir_name) { |
| // The diagnostics directory may not be initialized in tests. |
| if (diagnostics_dir_ == nullptr) { |
| return ZX_ERR_INTERNAL; |
| } |
| if (!mount_nodes_[point].export_root) { |
| 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].export_root.channel().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 (!mount_nodes_[point].export_root) { |
| 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].export_root.channel().get(), |
| name.c_str(), request.release()); |
| }); |
| return svc_dir_->AddEntry(service_name, std::move(service_node)); |
| } |
| |
| void FsManager::FileReport(fs_management::DiskFormat format, ReportReason reason) const { |
| fbl::String report_reason = ReportReasonString(format, reason); |
| FX_LOGS(INFO) << "Filing crash report, reason: " << report_reason.c_str(); |
| if (!file_crash_report_) { |
| FX_LOGS(INFO) << "Report filing disabled, ignoring crash report."; |
| return; |
| } |
| // This thread accesses no state in the SyntheticCrashReporter, so is thread-safe even if the |
| // reporter is destroyed. |
| std::thread t([format, report_reason = std::move(report_reason)]() { |
| auto client_end = service::Connect<fuchsia_feedback::CrashReporter>(); |
| if (client_end.is_error()) { |
| FX_LOGS(WARNING) << "Unable to connect to crash reporting service: " |
| << client_end.status_string(); |
| return; |
| } |
| auto client = fidl::BindSyncClient(std::move(*client_end)); |
| |
| fidl::Arena allocator; |
| auto report = fuchsia_feedback::wire::CrashReport::Builder(allocator) |
| .program_name(fs_management::DiskFormatString(format)) |
| .crash_signature(report_reason) |
| .is_fatal(false) |
| .Build(); |
| |
| auto res = client->File(report); |
| if (!res.ok()) { |
| FX_LOGS(WARNING) << "Unable to send crash report (fidl error): " << res.status_string(); |
| } else if (res->is_error()) { |
| FX_LOGS(WARNING) << "Failed to file crash report: " |
| << zx_status_get_string(res->error_value()); |
| } else { |
| FX_LOGS(INFO) << "Crash report successfully filed"; |
| } |
| }); |
| t.detach(); |
| } |
| |
| zx_status_t FsManager::AttachMount(std::string_view device_path, |
| fidl::ClientEnd<fuchsia_io::Directory> export_root, |
| std::string_view name) { |
| auto root_or = fs_management::FsRootHandle(export_root); |
| if (root_or.is_error()) { |
| FX_PLOGS(WARNING, root_or.status_value()) << "Failed to get root"; |
| zx::status result = fs_management::Shutdown(export_root); |
| if (result.is_error()) { |
| FX_PLOGS(WARNING, result.status_value()) << "Failed to shutdown after failure to get root"; |
| } |
| return root_or.error_value(); |
| } |
| |
| uint64_t fs_id = GetFsId(*root_or).value_or(0); |
| mnt_dir_->AddEntry(name, fbl::MakeRefCounted<fs::RemoteDir>(std::move(*root_or))); |
| |
| std::lock_guard guard(device_paths_lock_); |
| mounted_filesystems_.emplace( |
| std::make_unique<MountedFilesystem>(name, std::move(export_root), fs_id)); |
| if (!device_path.empty()) |
| device_paths_.emplace(fs_id, device_path); |
| return ZX_OK; |
| } |
| |
| zx_status_t FsManager::DetachMount(std::string_view name) { |
| std::lock_guard guard(device_paths_lock_); |
| if (auto iter = mounted_filesystems_.find(name); iter == mounted_filesystems_.end()) { |
| return ZX_ERR_NOT_FOUND; |
| } else { |
| device_paths_.erase((*iter)->fs_id()); |
| mounted_filesystems_.erase(iter); |
| } |
| return mnt_dir_->RemoveEntry(name); |
| } |
| |
| zx::status<std::string> FsManager::GetDevicePath(uint64_t fs_id) { |
| std::lock_guard guard(device_paths_lock_); |
| auto iter = device_paths_.find(fs_id); |
| if (iter == device_paths_.end()) |
| return zx::error(ZX_ERR_NOT_FOUND); |
| else |
| return zx::ok(iter->second); |
| } |
| |
| zx::status<fidl::ClientEnd<fuchsia_io::Directory>> FsManager::GetRoot(MountPoint point) const { |
| auto node = mount_nodes_.find(point); |
| if (node == mount_nodes_.cend()) { |
| if (point == MountPoint::kData) { |
| // The data mount has been mounted via a component in which case we can get to the service |
| // root through our local namespace. |
| return service::Connect<fuchsia_io::Directory>("/data_root"); |
| } |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| return fs_management::FsRootHandle(node->second.export_root.borrow()); |
| } |
| |
| } // namespace fshost |