blob: 6aaa32ba378b12b113a6fb163eb57e65e110a5fc [file] [log] [blame]
// 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