blob: bbb609084443ed7b4c0b9a19b70ae763f2885847 [file] [log] [blame]
// Copyright 2018 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 "bootfs-service.h"
#include <fcntl.h>
#include <fidl/fuchsia.io/cpp/wire.h>
#include <lib/stdcompat/string_view.h>
#include <lib/zbitl/error_stdio.h>
#include <lib/zbitl/items/bootfs.h>
#include <lib/zbitl/vmo.h>
#include <lib/zx/event.h>
#include <lib/zx/time.h>
#include <sys/stat.h>
#include <zircon/compiler.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <string_view>
#include <utility>
#include <fbl/algorithm.h>
#include <launchpad/launchpad.h>
#include "src/lib/storage/vfs/cpp/vfs_types.h"
#include "util.h"
namespace fio = fuchsia_io;
namespace bootsvc {
namespace {
using BootfsView = zbitl::BootfsView<zbitl::MapUnownedVmo>;
// 'Packages' in bootfs can contain executable files but we need to account for the package name
// path component, which can be anything. For example, 'pkg/my_package/bin' should be executable but
// 'pkg/my_package/foo' should not.
static constexpr std::string_view kBootfsPackagePrefix = "pkg/";
static constexpr std::string_view kExecutablePackageDirectories[] = {
"bin/",
"lib/",
};
static bool PathInExecutablePackageDirectory(std::string_view path) {
// All packages in bootfs are located under a single directory.
if (!cpp20::starts_with(path, kBootfsPackagePrefix)) {
return false;
}
// Advance past the path separator separating the package name and path inside the package.
path.remove_prefix(kBootfsPackagePrefix.size());
std::string_view pkg_name = path.substr(0, path.find('/'));
path.remove_prefix(std::min(pkg_name.size() + 1, path.size()));
// Finally, check if the path inside the package is one of the allowlisted paths.
for (std::string_view prefix : kExecutablePackageDirectories) {
if (cpp20::starts_with(path, prefix)) {
return true;
}
}
return false;
}
// Other top-level directories in bootfs that are allowed to contain executable files (i.e. files
// for which bootfs should allow opening with OPEN_RIGHT_EXECUTABLE).
static constexpr std::string_view kExecutableDirectories[] = {
"bin/",
"driver/",
"lib/",
"test/",
};
static bool PathInExecutableDirectory(std::string_view path) {
if (PathInExecutablePackageDirectory(path)) {
return true;
}
for (std::string_view prefix : kExecutableDirectories) {
if (cpp20::starts_with(path, prefix)) {
return true;
}
}
return false;
}
} // namespace
BootfsService::BootfsService(zx::resource vmex_rsrc) : vmex_rsrc_(std::move(vmex_rsrc)) {}
zx_status_t BootfsService::Create(async_dispatcher_t* dispatcher, zx::resource vmex_rsrc,
fbl::RefPtr<BootfsService>* out) {
auto svc = fbl::AdoptRef(new BootfsService(std::move(vmex_rsrc)));
zx_status_t status = memfs::Vfs::Create(dispatcher, "<root>", &svc->vfs_, &svc->root_);
if (status != ZX_OK) {
return status;
}
*out = std::move(svc);
return ZX_OK;
}
zx_status_t BootfsService::AddBootfs(zx::vmo bootfs_vmo) {
// The bootfs VnodeVmo nodes are all created from the same backing VMO with differing offsets, and
// memfs creates clones as needed. Executable files use a duplicate handle to this same VMO which
// has ZX_RIGHT_EXECUTE added. This is done once here rather than in PublishUnownedVmo to avoid
// lots of repetitive unnecessary syscalls for every executable file.
zx::vmo bootfs_exec_vmo;
zx_status_t status = DuplicateAsExecutable(bootfs_vmo, &bootfs_exec_vmo);
if (status != ZX_OK) {
return status;
}
// Load all of the entries in the bootfs into the FS.
zbitl::MapUnownedVmo mapvmo{bootfs_vmo.borrow()};
BootfsView bootfs;
if (auto result = BootfsView::Create(std::move(mapvmo)); result.is_error()) {
zbitl::PrintBootfsError(result.error_value());
return ZX_ERR_INTERNAL;
} else {
bootfs = std::move(result.value());
}
// A helper to have `status` encode the first error we come across: while it
// is ZX_OK, it will be overridden with the next status; once it is an error
// state, subsequent statuses will be ignored.
auto update_status = [&status](zx_status_t next) {
if (status == ZX_OK) {
status = next;
}
};
for (const auto& file : bootfs) {
const zx::vmo& vmo = PathInExecutableDirectory(file.name) ? bootfs_exec_vmo : bootfs_vmo;
update_status(PublishUnownedVmo(file.name, vmo, file.offset, file.size));
}
if (auto result = bootfs.take_error(); result.is_error()) {
zbitl::PrintBootfsError(result.error_value());
update_status(ZX_ERR_INTERNAL);
}
// Add these VMOs to our list of owned VMOs even on failure, since we may have
// added a file.
owned_vmos_.push_back(std::move(bootfs_vmo));
owned_vmos_.push_back(std::move(bootfs_exec_vmo));
return status;
}
zx_status_t BootfsService::CreateRootConnection(zx::channel* out) {
return CreateVnodeConnection(vfs_.get(), root_, fs::Rights::ReadExec(), out);
}
zx_status_t BootfsService::Open(const char* path, bool executable, zx::vmo* vmo, size_t* size) {
if (path != nullptr && (path[0] == '/' || path[0] == 0)) {
return ZX_ERR_INVALID_ARGS;
}
auto open_options =
executable ? fs::VnodeConnectionOptions::ReadExec() : fs::VnodeConnectionOptions::ReadOnly();
open_options.set_no_remote();
// fdio cannot be used since it is synchronous, and the filesystem we're opening from is
// in-process and single threaded, but using the ulib/fs APIs directly instead of going through
// the fuchsia.io APIs risks behavior differences or skipped checks.
auto open_result = vfs_->Open(root_, path, open_options, fs::Rights::ReadOnly(), 0);
if (open_result.is_error()) {
return open_result.error();
}
ZX_ASSERT(open_result.is_ok());
fbl::RefPtr<fs::Vnode> node = std::move(open_result.ok().vnode);
// memfs doesn't currently do anything different for kPrivateClone, but it may in the future,
// and this matches the flags used by fdio_get_vmo_clone/exec.
fio::wire::VmoFlags vmo_flags = fio::wire::VmoFlags::kRead | fio::wire::VmoFlags::kPrivateClone;
if (executable) {
vmo_flags |= fio::wire::VmoFlags::kExecute;
}
return node->GetVmo(vmo_flags, vmo, size);
}
BootfsService::~BootfsService() {
// Correctly shutting down a memfs (avoiding both use-after-frees and leaks) requires async
// operations, so we use an Event to wait until the shutdown callback is finished. This is a bit
// silly and likely won't be exercised outside of tests since bootsvc usually does not terminate
// normally, but it makes ASAN and LSAN happy.
zx::event event;
zx::event::create(0, &event);
auto callback = [parts(std::move(owned_vmos_)), &event](zx_status_t status) mutable {
// Bootfs uses multiple Vnodes which may share a reference to a single VMO.
// Since the lifetime of the VMOs are coupled with the BootfsService, all
// connections to these Vnodes must be terminated (with Shutdown) before
// we can safely close the VMOs
parts.reset();
event.signal(0, ZX_USER_SIGNAL_0);
};
vfs_->Shutdown(std::move(callback));
event.wait_one(ZX_USER_SIGNAL_0, zx::deadline_after(zx::min(1)), nullptr);
}
zx_status_t BootfsService::DuplicateAsExecutable(const zx::vmo& vmo, zx::vmo* out_vmo) {
zx::vmo out;
zx_status_t status = vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &out);
if (status != ZX_OK) {
return status;
}
status = out.replace_as_executable(vmex_rsrc_, &out);
if (status != ZX_OK) {
return status;
}
*out_vmo = std::move(out);
return ZX_OK;
}
zx_status_t BootfsService::PublishVmo(std::string_view path, zx::vmo vmo, zx_off_t off,
size_t len) {
zx_status_t status = PublishUnownedVmo(path, vmo, off, len);
if (status != ZX_OK) {
return status;
}
owned_vmos_.push_back(std::move(vmo));
return ZX_OK;
}
zx_status_t BootfsService::PublishUnownedVmo(std::string_view path, const zx::vmo& vmo,
zx_off_t off, size_t len) {
ZX_ASSERT(root_ != nullptr);
if (path.empty() || path.front() == '/' || path.front() == '\0') {
return ZX_ERR_INVALID_ARGS;
}
fbl::RefPtr<memfs::VnodeDir> vnb(root_);
while (!path.empty()) {
std::string_view dir = path.substr(0, path.find('/'));
if (dir.size() == path.size()) {
dir = {};
} else {
path.remove_prefix(dir.size() + 1);
}
if (dir.empty()) {
if (path.empty() || path.front() == '/' || path.front() == '\0') {
return ZX_ERR_INVALID_ARGS;
}
return vfs_->CreateFromVmo(vnb.get(), path, vmo.get(), off, len);
}
fbl::RefPtr<fs::Vnode> out;
zx_status_t status = vnb->Lookup(dir, &out);
if (status == ZX_ERR_NOT_FOUND) {
status = vnb->Create(dir, S_IFDIR, &out);
}
if (status != ZX_OK) {
return status;
}
vnb = fbl::RefPtr<memfs::VnodeDir>::Downcast(std::move(out));
}
// Should not be reached.
return ZX_ERR_INTERNAL;
}
void BootfsService::PublishStartupVmos(uint8_t type, const char* debug_type_name) {
constexpr char kVmoSubdir[] = "kernel/";
constexpr size_t kVmoSubdirLen = sizeof(kVmoSubdir) - 1;
for (uint16_t i = 0; true; ++i) {
zx::vmo owned_vmo(zx_take_startup_handle(PA_HND(type, i)));
if (!owned_vmo.is_valid()) {
break;
}
// We use an unowned VMO here so we can have some finer control over
// whether the handle is closed. This is safe since |owned_vmo| will
// never be closed before |vmo|.
zx::unowned_vmo vmo(owned_vmo);
// The first vDSO is the default vDSO. Since we've taken the startup
// handle, launchpad won't find it on its own. So point launchpad at
// it instead of closing it.
const bool is_default_vdso = (type == PA_VMO_VDSO && i == 0);
if (is_default_vdso) {
launchpad_set_vdso_vmo(owned_vmo.release());
}
// The vDSO VMOs have names like "vdso/default", so those
// become VMO files at "/boot/kernel/vdso/default".
char name[kVmoSubdirLen + ZX_MAX_NAME_LEN] = {};
memcpy(name, kVmoSubdir, kVmoSubdirLen);
size_t size;
zx_status_t status =
vmo->get_property(ZX_PROP_NAME, name + kVmoSubdirLen, sizeof(name) - kVmoSubdirLen);
if (status != ZX_OK) {
printf("bootsvc: vmo.get_property on %s %u: %s\n", debug_type_name, i,
zx_status_get_string(status));
continue;
}
if (strlen(name) == kVmoSubdirLen) {
// Nameless VMOs do not get published.
continue;
}
status = vmo->get_size(&size);
if (status != ZX_OK) {
printf("bootsvc: vmo.get_size on %s %u: %s\n", debug_type_name, i,
zx_status_get_string(status));
continue;
}
if (size == 0) {
// Empty VMOs do not get published.
continue;
}
// If the VMO has a precise content size set, use that as the file size.
uint64_t content_size;
status = vmo->get_property(ZX_PROP_VMO_CONTENT_SIZE, &content_size, sizeof(content_size));
if (status != ZX_OK) {
printf("bootsvc: vmo.get_property on %s %u: %s\n", debug_type_name, i,
zx_status_get_string(status));
continue;
}
if (content_size != 0) {
size = content_size;
}
if (!strcmp(name + kVmoSubdirLen, "crashlog")) {
// The crashlog has a special home.
strcpy(name, kLastPanicFilePath);
}
if (owned_vmo.is_valid()) {
status = PublishVmo(name, std::move(owned_vmo), 0, size);
} else {
status = PublishUnownedVmo(name, *vmo, 0, size);
}
if (status != ZX_OK) {
printf("bootsvc: failed to add %s %u to filesystem as %s: %s\n", debug_type_name, i, name,
zx_status_get_string(status));
continue;
}
}
}
} // namespace bootsvc