blob: b8b6f57e798fc4a158897f71fd1a83253feb7ee8 [file] [log] [blame]
// Copyright 2017 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 <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fbl/algorithm.h>
#include <fbl/unique_fd.h>
#include <fs-management/mount.h>
#include <gpt/gpt.h>
#include <lib/fdio/util.h>
#include <lib/fdio/watcher.h>
#include <lib/zx/channel.h>
#include <lib/zx/process.h>
#include <lib/zx/time.h>
#include <loader-service/loader-service.h>
#include <zircon/device/block.h>
#include <zircon/device/device.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <utility>
#include "fshost.h"
namespace devmgr {
namespace {
class BlockWatcher {
public:
BlockWatcher(fbl::unique_ptr<FsManager> fshost, zx::unowned_job job, bool netboot)
: fshost_(std::move(fshost)), job_(job), netboot_(netboot) {}
void FuchsiaStart() const {
fshost_->FuchsiaStart();
}
bool IsSystemMounted() const {
return fshost_->IsSystemMounted();
}
zx_status_t InstallFs(const char* path, zx::channel h) {
return fshost_->InstallFs(path, std::move(h));
}
const zx::unowned_job& Job() const {
return job_;
}
bool Netbooting() const {
return netboot_;
}
// Optionally checks the filesystem stored on the device at |device_path|,
// if "zircon.system.filesystem-check" is set.
zx_status_t CheckFilesystem(const char* device_path, disk_format_t df) const;
// Attempts to mount a block device backed by |fd| to "/data".
// Fails if already mounted.
zx_status_t MountData(fbl::unique_fd fd, mount_options_t* options);
// Attempts to mount a block device backed by |fd| to "/install".
// Fails if already mounted.
zx_status_t MountInstall(fbl::unique_fd fd, mount_options_t* options);
// Attempts to mount a block device backed by |fd| to "/blob".
// Fails if already mounted.
zx_status_t MountBlob(fbl::unique_fd fd, mount_options_t* options);
private:
fbl::unique_ptr<FsManager> fshost_;
zx::unowned_job job_;
bool netboot_ = false;
bool data_mounted_ = false;
bool install_mounted_ = false;
bool blob_mounted_ = false;
};
// TODO(smklein): When launching filesystems can pass a cookie representing a unique
// BlockWatcher instance, this global should be removed.
zx::unowned_job g_job;
zx_status_t fshost_launch_load(void* ctx, launchpad_t* lp, const char* file) {
return launchpad_load_from_file(lp, file);
}
void pkgfs_finish(BlockWatcher* watcher, zx::process proc, zx::channel pkgfs_root) {
auto deadline = zx::deadline_after(zx::sec(5));
zx_signals_t observed;
zx_status_t status = proc.wait_one(ZX_USER_SIGNAL_0 | ZX_PROCESS_TERMINATED,
deadline, &observed);
if (status != ZX_OK) {
printf("fshost: pkgfs did not signal completion: %d (%s)\n",
status, zx_status_get_string(status));
return;
}
if (!(observed & ZX_USER_SIGNAL_0)) {
printf("fshost: pkgfs terminated prematurely\n");
return;
}
// re-export /pkgfs/system as /system
zx::channel systemChan, systemReq;
if (zx::channel::create(0, &systemChan, &systemReq) != ZX_OK) {
return;
}
if (fdio_open_at(pkgfs_root.get(), "system", FS_DIR_FLAGS, systemReq.release()) != ZX_OK) {
return;
}
// re-export /pkgfs/packages/shell-commands/0/bin as /bin
zx::channel binChan, binReq;
if (zx::channel::create(0, &binChan, &binReq) != ZX_OK) {
return;
}
if (fdio_open_at(pkgfs_root.get(), "packages/shell-commands/0/bin", FS_DIR_FLAGS, binReq.release()) != ZX_OK) {
// non-fatal.
printf("fshost: failed to install /bin (could not open shell-commands)\n");
}
if (watcher->InstallFs("/pkgfs", std::move(pkgfs_root)) != ZX_OK) {
printf("fshost: failed to install /pkgfs\n");
return;
}
if (watcher->InstallFs("/system", std::move(systemChan)) != ZX_OK) {
printf("fshost: failed to install /system\n");
return;
}
// as above, failure of /bin export is non-fatal.
if (watcher->InstallFs("/bin", std::move(binChan)) != ZX_OK) {
printf("fshost: failed to install /bin\n");
}
// start the appmgr
watcher->FuchsiaStart();
}
// TODO(mcgrathr): Remove this fallback path when the old args
// are no longer used.
void old_launch_blob_init(BlockWatcher* watcher) {
const char* blob_init = getenv("zircon.system.blob-init");
if (blob_init == nullptr) {
return;
}
if (watcher->IsSystemMounted()) {
printf("fshost: zircon.system.blob-init ignored since system already mounted\n");
return;
}
zx::process proc;
uint32_t type = PA_HND(PA_USER0, 0);
zx::channel handle, pkgfs_root;
if (zx::channel::create(0, &handle, &pkgfs_root) != ZX_OK) {
return;
}
//TODO: make blob-init a /fs/blob relative path
const char *argv[2];
char binary[strlen(blob_init) + 4];
sprintf(binary, "/fs%s", blob_init);
argv[0] = binary;
const char* blob_init_arg = getenv("zircon.system.blob-init-arg");
int argc = 1;
if (blob_init_arg != nullptr) {
argc++;
argv[1] = blob_init_arg;
}
const zx_handle_t raw_handle = handle.release();
zx_status_t status = devmgr_launch(
*watcher->Job(), "pkgfs", &fshost_launch_load, nullptr, argc, &argv[0], nullptr, -1,
&raw_handle, &type, 1, &proc, FS_DATA | FS_BLOB | FS_SVC);
if (status != ZX_OK) {
printf("fshost: '%s' failed to launch: %d\n", blob_init, status);
return;
}
pkgfs_finish(watcher, std::move(proc), std::move(pkgfs_root));
}
// Launching pkgfs uses its own loader service and command lookup to run out of
// the blobfs without any real filesystem. Files are found by
// getenv("zircon.system.pkgfs.file.PATH") returning a blob content ID.
// That is, a manifest of name->blob is embedded in /boot/config/devmgr.
zx_status_t pkgfs_ldsvc_load_blob(void* ctx, const char* prefix,
const char* name, zx_handle_t* vmo) {
const int fs_blob_fd = static_cast<int>(reinterpret_cast<intptr_t>(ctx));
char key[256];
if (snprintf(key, sizeof(key), "zircon.system.pkgfs.file.%s%s",
prefix, name) >= (int)sizeof(key)) {
return ZX_ERR_BAD_PATH;
}
const char *blob = getenv(key);
if (blob == nullptr) {
return ZX_ERR_NOT_FOUND;
}
int fd = openat(fs_blob_fd, blob, O_RDONLY);
if (fd < 0) {
return ZX_ERR_NOT_FOUND;
}
zx_status_t status = fdio_get_vmo_clone(fd, vmo);
close(fd);
if (status == ZX_OK) {
zx_object_set_property(*vmo, ZX_PROP_NAME, key, strlen(key));
}
return status;
}
zx_status_t pkgfs_ldsvc_load_object(void* ctx, const char* name, zx_handle_t* vmo) {
return pkgfs_ldsvc_load_blob(ctx, "lib/", name, vmo);
}
zx_status_t pkgfs_ldsvc_load_abspath(void* ctx, const char* name, zx_handle_t* vmo) {
return pkgfs_ldsvc_load_blob(ctx, "", name + 1, vmo);
}
zx_status_t pkgfs_ldsvc_publish_data_sink(void* ctx, const char* name, zx_handle_t vmo) {
zx_handle_close(vmo);
return ZX_ERR_NOT_SUPPORTED;
}
void pkgfs_ldsvc_finalizer(void* ctx) {
close(static_cast<int>(reinterpret_cast<intptr_t>(ctx)));
}
const loader_service_ops_t pkgfs_ldsvc_ops = {
.load_object = pkgfs_ldsvc_load_object,
.load_abspath = pkgfs_ldsvc_load_abspath,
.publish_data_sink = pkgfs_ldsvc_publish_data_sink,
.finalizer = pkgfs_ldsvc_finalizer,
};
// Create a local loader service with a fixed mapping of names to blobs.
// Always consumes fs_blob_fd.
zx_status_t pkgfs_ldsvc_start(int fs_blob_fd, zx_handle_t* ldsvc) {
loader_service_t* service;
zx_status_t status = loader_service_create(nullptr, &pkgfs_ldsvc_ops,
(void*)(intptr_t)fs_blob_fd,
&service);
if (status != ZX_OK) {
printf("fshost: cannot create pkgfs loader service: %d (%s)\n",
status, zx_status_get_string(status));
close(fs_blob_fd);
return status;
}
status = loader_service_connect(service, ldsvc);
loader_service_release(service);
if (status != ZX_OK) {
printf("fshost: cannot connect pkgfs loader service: %d (%s)\n",
status, zx_status_get_string(status));
}
return status;
}
// This is the callback to load the file via launchpad. First look up the
// file itself. Then get the loader service started so it can service
// launchpad's request for the PT_INTERP file. Then load it up.
zx_status_t pkgfs_launch_load(void* ctx, launchpad_t* lp, const char* file) {
while (file[0] == '/') {
++file;
}
zx_handle_t vmo;
zx_status_t status = pkgfs_ldsvc_load_blob(ctx, "", file, &vmo);
const int fs_blob_fd = static_cast<int>(reinterpret_cast<intptr_t>(ctx));
if (status == ZX_OK) {
// The service takes ownership of fs_blob_fd.
zx_handle_t ldsvc;
status = pkgfs_ldsvc_start(fs_blob_fd, &ldsvc);
if (status == ZX_OK) {
launchpad_use_loader_service(lp, ldsvc);
launchpad_load_from_vmo(lp, vmo);
} else {
zx_handle_close(vmo);
}
} else {
close(fs_blob_fd);
}
return status;
}
bool pkgfs_launch(BlockWatcher* watcher) {
const char* cmd = getenv("zircon.system.pkgfs.cmd");
if (cmd == nullptr) {
return false;
}
fbl::unique_fd fs_blob_fd(open("/fs/blob", O_RDONLY | O_DIRECTORY));
if (!fs_blob_fd) {
printf("fshost: open(/fs/blob): %m\n");
return false;
}
zx::channel h0, h1;
zx_status_t status = zx::channel::create(0, &h0, &h1);
if (status != ZX_OK) {
printf("fshost: cannot create pkgfs root channel: %d (%s)\n",
status, zx_status_get_string(status));
return false;
}
const zx_handle_t raw_h1 = h1.release();
zx::process proc;
status = devmgr_launch_cmdline(
"fshost", *watcher->Job(), "pkgfs",
&pkgfs_launch_load, (void*)(intptr_t)fs_blob_fd.release(), cmd,
&raw_h1, (const uint32_t[]){ PA_HND(PA_USER0, 0) }, 1,
&proc, FS_DATA | FS_BLOB | FS_SVC);
if (status != ZX_OK) {
printf("fshost: failed to launch %s: %d (%s)\n",
cmd, status, zx_status_get_string(status));
return false;
}
pkgfs_finish(watcher, std::move(proc), std::move(h0));
return true;
}
void launch_blob_init(BlockWatcher* watcher) {
if (!pkgfs_launch(watcher)) {
// TODO(mcgrathr): Remove when the old args are no longer used.
old_launch_blob_init(watcher);
}
}
zx_status_t launch_blobfs(int argc, const char** argv, zx_handle_t* hnd,
uint32_t* ids, size_t len) {
return devmgr_launch(*g_job, "blobfs:/blob",
&fshost_launch_load, nullptr, argc, argv, nullptr, -1,
hnd, ids, len, nullptr, FS_FOR_FSPROC);
}
zx_status_t launch_minfs(int argc, const char** argv, zx_handle_t* hnd,
uint32_t* ids, size_t len) {
return devmgr_launch(*g_job, "minfs:/data",
&fshost_launch_load, nullptr, argc, argv, nullptr, -1,
hnd, ids, len, nullptr, FS_FOR_FSPROC);
}
zx_status_t launch_fat(int argc, const char** argv, zx_handle_t* hnd,
uint32_t* ids, size_t len) {
return devmgr_launch(*g_job, "fatfs:/volume",
&fshost_launch_load, nullptr, argc, argv, nullptr, -1,
hnd, ids, len, nullptr, FS_FOR_FSPROC);
}
zx_status_t BlockWatcher::MountData(fbl::unique_fd fd, mount_options_t* options) {
if (data_mounted_) {
return ZX_ERR_ALREADY_BOUND;
}
options->wait_until_ready = true;
zx_status_t status = mount(fd.release(), "/fs" PATH_DATA, DISK_FORMAT_MINFS,
options, launch_minfs);
if (status != ZX_OK) {
printf("devmgr: failed to mount %s: %s.\n", PATH_DATA, zx_status_get_string(status));
} else {
data_mounted_ = true;
}
return status;
}
zx_status_t BlockWatcher::MountInstall(fbl::unique_fd fd, mount_options_t* options) {
if (install_mounted_) {
return ZX_ERR_ALREADY_BOUND;
}
options->readonly = true;
zx_status_t status = mount(fd.release(), "/fs" PATH_INSTALL, DISK_FORMAT_MINFS,
options, launch_minfs);
if (status != ZX_OK) {
printf("devmgr: failed to mount %s: %s.\n", PATH_INSTALL, zx_status_get_string(status));
} else {
install_mounted_ = true;
}
return status;
}
zx_status_t BlockWatcher::MountBlob(fbl::unique_fd fd, mount_options_t* options) {
if (blob_mounted_) {
return ZX_ERR_ALREADY_BOUND;
}
zx_status_t status = mount(fd.release(), "/fs" PATH_BLOB, DISK_FORMAT_BLOBFS,
options, launch_blobfs);
if (status != ZX_OK) {
printf("devmgr: failed to mount %s: %s.\n", PATH_BLOB, zx_status_get_string(status));
} else {
blob_mounted_ = true;
}
return status;
}
zx_status_t BlockWatcher::CheckFilesystem(const char* device_path, disk_format_t df) const {
if (!getenv_bool("zircon.system.filesystem-check", false)) {
return ZX_OK;
}
printf("fshost: fsck of %s started\n", disk_format_string_[df]);
const fsck_options_t* options = &default_fsck_options;
auto launch_fsck = [](int argc, const char** argv, zx_handle_t* hnd, uint32_t* ids,
size_t len) {
zx::process proc;
zx_status_t status = devmgr_launch(*g_job, "fsck", &fshost_launch_load, nullptr,
argc, argv, nullptr, -1, hnd, ids, len,
&proc, FS_FOR_FSPROC);
if (status != ZX_OK) {
fprintf(stderr, "fshost: Couldn't launch fsck\n");
return status;
}
status = proc.wait_one(ZX_PROCESS_TERMINATED, zx::time::infinite(), nullptr);
if (status != ZX_OK) {
fprintf(stderr, "fshost: Error waiting for fsck to terminate\n");
return status;
}
zx_info_process_t info;
status = proc.get_info(ZX_INFO_PROCESS, &info, sizeof(info), nullptr, nullptr);
if (status != ZX_OK) {
fprintf(stderr, "fshost: Failed to get process info\n");
return status;
}
if (info.return_code != 0) {
fprintf(stderr, "fshost: Fsck return code: %" PRId64 "\n", info.return_code);
return ZX_ERR_BAD_STATE;
}
return ZX_OK;
};
zx_status_t status = fsck(device_path, df, options, launch_fsck);
if (status != ZX_OK) {
fprintf(stderr, "---------------------------------------------------------\n");
fprintf(stderr, "| \n");
fprintf(stderr, "| WARNING: fshost fsck failure! \n");
fprintf(stderr, "| Corrupt device: %s \n", device_path);
fprintf(stderr, "| Please report this device to the local storage team, \n");
fprintf(stderr, "| Preferably BEFORE reformatting your device. \n");
fprintf(stderr, "| \n");
fprintf(stderr, "---------------------------------------------------------\n");
} else {
printf("fshost: fsck of %s completed OK\n", disk_format_string_[df]);
}
return status;
}
/*
* Attempt to mount the device pointed to be the file descriptor at a known
* location.
* Returns ZX_ERR_ALREADY_BOUND if the device could be mounted, but something
* is already mounted at that location. Returns ZX_ERR_INVALID_ARGS if the
* GUID of the device does not match a known valid one. Returns ZX_OK if an
* attempt to mount is made, without checking mount success.
*/
zx_status_t mount_minfs(BlockWatcher* watcher, fbl::unique_fd fd, mount_options_t* options) {
uint8_t type_guid[GPT_GUID_LEN];
// initialize our data for this run
ssize_t read_sz = ioctl_block_get_type_guid(fd.get(), type_guid, sizeof(type_guid));
if (read_sz != GPT_GUID_LEN) {
printf("fshost: cannot read GUID from minfs-formatted device\n");
return ZX_ERR_INVALID_ARGS;
}
if (gpt_is_sys_guid(type_guid, read_sz)) {
if (watcher->IsSystemMounted()) {
return ZX_ERR_ALREADY_BOUND;
}
if (getenv("zircon.system.blob-init") != nullptr) {
printf("fshost: minfs system partition ignored due to zircon.system.blob-init\n");
return ZX_ERR_ALREADY_BOUND;
}
const char* volume = getenv("zircon.system.volume");
if (volume != nullptr && !strcmp(volume, "any")) {
// Fall-through; we'll take anything.
} else if (volume != nullptr && !strcmp(volume, "local")) {
// Fall-through only if we can guarantee the partition
// is not removable.
block_info_t info;
if ((ioctl_block_get_info(fd.get(), &info) < 0) ||
(info.flags & BLOCK_FLAG_REMOVABLE)) {
return ZX_ERR_BAD_STATE;
}
} else {
return ZX_ERR_BAD_STATE;
}
// TODO(ZX-1008): replace getenv with cmdline_bool("zircon.system.writable", false);
options->readonly = getenv("zircon.system.writable") == nullptr;
options->wait_until_ready = true;
zx_status_t st = mount(fd.release(), "/fs" PATH_SYSTEM, DISK_FORMAT_MINFS,
options, launch_minfs);
if (st != ZX_OK) {
printf("devmgr: failed to mount %s: %s.\n", PATH_SYSTEM, zx_status_get_string(st));
} else {
watcher->FuchsiaStart();
}
return st;
} else if (gpt_is_data_guid(type_guid, read_sz)) {
return watcher->MountData(std::move(fd), options);
} else if (gpt_is_install_guid(type_guid, read_sz)) {
return watcher->MountInstall(std::move(fd), options);
}
printf("fshost: Unrecognized partition GUID for minfs; not mounting\n");
return ZX_ERR_INVALID_ARGS;
}
#define FVM_DRIVER_LIB "/boot/driver/fvm.so"
#define GPT_DRIVER_LIB "/boot/driver/gpt.so"
#define MBR_DRIVER_LIB "/boot/driver/mbr.so"
#define BOOTPART_DRIVER_LIB "/boot/driver/bootpart.so"
#define ZXCRYPT_DRIVER_LIB "/boot/driver/zxcrypt.so"
#define STRLEN(s) sizeof(s) / sizeof((s)[0])
zx_status_t block_device_added(int dirfd, int event, const char* name, void* cookie) {
auto watcher = static_cast<BlockWatcher*>(cookie);
if (event != WATCH_EVENT_ADD_FILE) {
return ZX_OK;
}
char device_path[PATH_MAX];
sprintf(device_path, "%s/%s", PATH_DEV_BLOCK, name);
fbl::unique_fd fd(openat(dirfd, name, O_RDWR));
if (!fd) {
return ZX_OK;
}
block_info_t info;
if (ioctl_block_get_info(fd.get(), &info) >= 0 && info.flags & BLOCK_FLAG_BOOTPART) {
ioctl_device_bind(fd.get(), BOOTPART_DRIVER_LIB, STRLEN(BOOTPART_DRIVER_LIB));
return ZX_OK;
}
disk_format_t df = detect_disk_format(fd.get());
switch (df) {
case DISK_FORMAT_GPT: {
printf("devmgr: %s: GPT?\n", device_path);
// probe for partition table
ioctl_device_bind(fd.get(), GPT_DRIVER_LIB, STRLEN(GPT_DRIVER_LIB));
return ZX_OK;
}
case DISK_FORMAT_FVM: {
printf("devmgr: /dev/class/block/%s: FVM?\n", name);
// probe for partition table
ioctl_device_bind(fd.get(), FVM_DRIVER_LIB, STRLEN(FVM_DRIVER_LIB));
return ZX_OK;
}
case DISK_FORMAT_MBR: {
printf("devmgr: %s: MBR?\n", device_path);
// probe for partition table
ioctl_device_bind(fd.get(), MBR_DRIVER_LIB, STRLEN(MBR_DRIVER_LIB));
return ZX_OK;
}
case DISK_FORMAT_ZXCRYPT: {
if (!watcher->Netbooting()) {
printf("devmgr: %s: zxcrypt?\n", device_path);
// TODO(security): ZX-1130. We need to bind with channel in order to pass a key here.
// Where does the key come from? We need to determine if this is unattended.
ioctl_device_bind(fd.get(), ZXCRYPT_DRIVER_LIB, STRLEN(ZXCRYPT_DRIVER_LIB));
}
return ZX_OK;
}
default:
break;
}
uint8_t guid[GPT_GUID_LEN] = GUID_EMPTY_VALUE;
ioctl_block_get_type_guid(fd.get(), guid, sizeof(guid));
// If we're in netbooting mode, then only bind drivers for partition
// containers and the install partition, not regular filesystems.
if (watcher->Netbooting()) {
const uint8_t expected_guid[GPT_GUID_LEN] = GUID_INSTALL_VALUE;
if (memcmp(guid, expected_guid, sizeof(guid)) == 0) {
printf("devmgr: mounting install partition\n");
mount_options_t options = default_mount_options;
mount_minfs(watcher, std::move(fd), &options);
return ZX_OK;
}
return ZX_OK;
}
switch (df) {
case DISK_FORMAT_BLOBFS: {
const uint8_t expected_guid[GPT_GUID_LEN] = GUID_BLOB_VALUE;
if (memcmp(guid, expected_guid, sizeof(guid))) {
return ZX_OK;
}
if (watcher->CheckFilesystem(device_path, DISK_FORMAT_BLOBFS) != ZX_OK) {
return ZX_OK;
}
mount_options_t options = default_mount_options;
options.enable_journal = true;
zx_status_t status = watcher->MountBlob(std::move(fd), &options);
if (status != ZX_OK) {
printf("devmgr: Failed to mount blobfs partition %s at %s: %s.\n",
device_path, PATH_BLOB, zx_status_get_string(status));
} else {
launch_blob_init(watcher);
}
return ZX_OK;
}
case DISK_FORMAT_MINFS: {
printf("devmgr: mounting minfs\n");
if (watcher->CheckFilesystem(device_path, DISK_FORMAT_MINFS) != ZX_OK) {
return ZX_OK;
}
mount_options_t options = default_mount_options;
mount_minfs(watcher, std::move(fd), &options);
return ZX_OK;
}
case DISK_FORMAT_FAT: {
// Use the GUID to avoid auto-mounting the EFI partition
uint8_t guid[GPT_GUID_LEN];
ssize_t r = ioctl_block_get_type_guid(fd.get(), guid, sizeof(guid));
bool efi = gpt_is_efi_guid(guid, r);
if (efi) {
printf("devmgr: not automounting efi\n");
return ZX_OK;
}
mount_options_t options = default_mount_options;
options.create_mountpoint = true;
static int fat_counter = 0;
char mountpath[FDIO_MAX_FILENAME + 64];
snprintf(mountpath, sizeof(mountpath), "%s/fat-%d", "/fs" PATH_VOLUME, fat_counter++);
options.wait_until_ready = false;
printf("devmgr: mounting fatfs\n");
mount(fd.release(), mountpath, df, &options, launch_fat);
return ZX_OK;
}
default:
return ZX_OK;
}
}
} // namespace
void block_device_watcher(fbl::unique_ptr<FsManager> fshost, zx::unowned_job job, bool netboot) {
g_job = job;
BlockWatcher watcher(std::move(fshost), std::move(job), netboot);
fbl::unique_fd dirfd(open("/dev/class/block", O_DIRECTORY | O_RDONLY));
if (dirfd) {
fdio_watch_directory(dirfd.get(), block_device_added, ZX_TIME_INFINITE, &watcher);
}
}
} // namespace devmgr