blob: efd15ead00e2b81633f01b9be52c92a9773da72d [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-management/mount.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_call.h>
#include <fbl/unique_fd.h>
#include <fbl/unique_ptr.h>
#include <fbl/vector.h>
#include <fs/client.h>
#include <fuchsia/hardware/block/c/fidl.h>
#include <fuchsia/io/c/fidl.h>
#include <lib/fdio/limits.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/vfs.h>
#include <lib/fzl/fdio.h>
#include <lib/zx/channel.h>
#include <pretty/hexdump.h>
#include <zircon/compiler.h>
#include <zircon/device/block.h>
#include <zircon/device/vfs.h>
#include <zircon/processargs.h>
#include <zircon/syscalls.h>
#include <utility>
namespace {
using fbl::unique_fd;
zx_status_t MountFs(int fd, zx_handle_t root) {
zx_status_t status;
fzl::FdioCaller caller{fbl::unique_fd(fd)};
zx_status_t io_status = fuchsia_io_DirectoryAdminMount(caller.borrow_channel(),
root, &status);
caller.release().release();
if (io_status != ZX_OK) {
return io_status;
}
return status;
}
void UnmountHandle(zx_handle_t root, bool wait_until_ready) {
// We've entered a failure case where the filesystem process (which may or may not be alive)
// had a *chance* to be spawned, but cannot be attached to a vnode (for whatever reason).
// Rather than abandoning the filesystem process (maybe causing dirty bits to be set), give it a
// chance to shutdown properly.
//
// The unmount process is a little atypical, since we're just sending a signal over a handle,
// rather than detaching the mounted filesystem from the "parent" filesystem.
vfs_unmount_handle(root, wait_until_ready ? ZX_TIME_INFINITE : 0);
}
// Performs the actual work of mounting a volume.
class Mounter {
public:
// The mount point is either a path (to be created) or an existing fd.
explicit Mounter(int fd) : path_(nullptr), fd_(fd) {}
explicit Mounter(const char* path) : path_(path), fd_(-1) {}
~Mounter() {}
// Mounts the given device.
zx_status_t Mount(zx::channel device, disk_format_t format, const mount_options_t& options,
LaunchCallback cb);
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(Mounter);
private:
zx_status_t PrepareHandles(zx::channel device);
zx_status_t MakeDirAndMount(const mount_options_t& options);
zx_status_t LaunchAndMount(LaunchCallback cb, const mount_options_t& options, const char** argv,
int argc);
zx_status_t MountNativeFs(const char* binary, zx::channel device,
const mount_options_t& options, LaunchCallback cb);
zx_status_t MountFat(zx::channel device, const mount_options_t& options, LaunchCallback cb);
zx_handle_t root_ = ZX_HANDLE_INVALID;
const char* path_;
int fd_;
uint32_t flags_ = 0; // Currently not used.
size_t num_handles_ = 0;
zx_handle_t handles_[2];
uint32_t ids_[2];
};
// Initializes 'handles_' and 'ids_' with the root handle and block device handle.
zx_status_t Mounter::PrepareHandles(zx::channel block_device) {
zx::channel root_server, root_client;
zx_status_t status = zx::channel::create(0, &root_server, &root_client);
if (status != ZX_OK) {
return status;
}
handles_[0] = root_server.release();
ids_[0] = FS_HANDLE_ROOT_ID;
handles_[1] = block_device.release();
ids_[1] = FS_HANDLE_BLOCK_DEVICE_ID;
num_handles_ = 2;
root_ = root_client.release();
return ZX_OK;
}
zx_status_t Mounter::MakeDirAndMount(const mount_options_t& options) {
auto cleanup =
fbl::MakeAutoCall([this, options]() { UnmountHandle(root_, options.wait_until_ready); });
// Open the parent path as O_ADMIN, and sent the mkdir+mount command
// to that directory.
char parent_path[PATH_MAX];
const char* name;
strcpy(parent_path, path_);
char* last_slash = strrchr(parent_path, '/');
if (last_slash == NULL) {
strcpy(parent_path, ".");
name = path_;
} else {
*last_slash = '\0';
name = last_slash + 1;
if (*name == '\0') {
return ZX_ERR_INVALID_ARGS;
}
}
unique_fd parent(open(parent_path, O_RDONLY | O_DIRECTORY | O_ADMIN));
if (!parent) {
return ZX_ERR_IO;
}
cleanup.cancel();
zx_status_t status;
fzl::FdioCaller caller(std::move(parent));
zx_status_t io_status = fuchsia_io_DirectoryAdminMountAndCreate(
caller.borrow_channel(), root_, name, strlen(name), flags_, &status);
if (io_status != ZX_OK) {
return io_status;
}
return status;
}
// Calls the 'launch callback' and mounts the remote handle to the target vnode, if successful.
zx_status_t Mounter::LaunchAndMount(LaunchCallback cb, const mount_options_t& options,
const char** argv, int argc) {
auto cleanup =
fbl::MakeAutoCall([this, options]() { UnmountHandle(root_, options.wait_until_ready); });
zx_status_t status = cb(argc, argv, handles_, ids_, num_handles_);
if (status != ZX_OK) {
return status;
}
if (options.wait_until_ready) {
// Wait until the filesystem is ready to take incoming requests
zx_signals_t observed;
status = zx_object_wait_one(root_, ZX_USER_SIGNAL_0 | ZX_CHANNEL_PEER_CLOSED,
ZX_TIME_INFINITE, &observed);
if ((status != ZX_OK) || (observed & ZX_CHANNEL_PEER_CLOSED)) {
status = (status != ZX_OK) ? status : ZX_ERR_BAD_STATE;
return status;
}
}
cleanup.cancel();
// Install remote handle.
if (options.create_mountpoint) {
return MakeDirAndMount(options);
}
return MountFs(fd_, root_);
}
zx_status_t Mounter::MountNativeFs(const char* binary, zx::channel device,
const mount_options_t& options, LaunchCallback cb) {
zx_status_t status = PrepareHandles(std::move(device));
if (status != ZX_OK) {
return status;
}
if (options.verbose_mount) {
printf("fs_mount: Launching %s\n", binary);
}
// 1. binary
// 2. (optional) readonly
// 3. (optional) verbose
// 4. (optional) metrics
// 5. command
fbl::Vector<const char*> argv;
argv.push_back(binary);
if (options.readonly) {
argv.push_back("--readonly");
}
if (options.verbose_mount) {
argv.push_back("--verbose");
}
if (options.collect_metrics) {
argv.push_back("--metrics");
}
if (options.enable_journal) {
argv.push_back("--journal");
}
argv.push_back("mount");
argv.push_back(nullptr);
return LaunchAndMount(cb, options, argv.get(), static_cast<int>(argv.size() - 1));
}
zx_status_t Mounter::MountFat(zx::channel device, const mount_options_t& options,
LaunchCallback cb) {
zx_status_t status = PrepareHandles(std::move(device));
if (status != ZX_OK) {
return status;
}
if (options.verbose_mount) {
printf("fs_mount: FAT not presently supported\n");
}
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t Mounter::Mount(zx::channel device, disk_format_t format, const mount_options_t& options,
LaunchCallback cb) {
switch (format) {
case DISK_FORMAT_MINFS:
return MountNativeFs("/boot/bin/minfs", std::move(device), options, cb);
case DISK_FORMAT_BLOBFS:
return MountNativeFs("/boot/bin/blobfs", std::move(device), options, cb);
case DISK_FORMAT_FAT:
return MountFat(std::move(device), options, cb);
default:
return ZX_ERR_NOT_SUPPORTED;
}
}
} // namespace
const mount_options_t default_mount_options = {
.readonly = false,
.verbose_mount = false,
.collect_metrics = false,
.wait_until_ready = true,
.create_mountpoint = false,
.enable_journal = false,
};
const mkfs_options_t default_mkfs_options = {
.fvm_data_slices = 1,
.verbose = false,
};
const fsck_options_t default_fsck_options = {
.verbose = false,
.never_modify = false,
.always_modify = false,
.force = false,
.apply_journal = false,
};
enum DiskFormatLogVerbosity {
Silent,
Verbose,
};
disk_format_t detect_disk_format_impl(int fd, DiskFormatLogVerbosity verbosity) {
if (lseek(fd, 0, SEEK_SET) != 0) {
fprintf(stderr, "detect_disk_format: Cannot seek to start of device.\n");
return DISK_FORMAT_UNKNOWN;
}
fuchsia_hardware_block_BlockInfo info;
fzl::UnownedFdioCaller caller(fd);
zx_status_t status;
zx_status_t io_status = fuchsia_hardware_block_BlockGetInfo(caller.borrow_channel(), &status,
&info);
if (io_status != ZX_OK || status != ZX_OK) {
fprintf(stderr, "detect_disk_format: Could not acquire block device info\n");
return DISK_FORMAT_UNKNOWN;
}
// We expect to read HEADER_SIZE bytes, but we may need to read
// extra to read a multiple of the underlying block size.
const size_t buffer_size = fbl::round_up(static_cast<size_t>(HEADER_SIZE),
static_cast<size_t>(info.block_size));
uint8_t data[buffer_size];
if (read(fd, data, buffer_size) != static_cast<ssize_t>(buffer_size)) {
fprintf(stderr, "detect_disk_format: Error reading block device.\n");
return DISK_FORMAT_UNKNOWN;
}
if (!memcmp(data, fvm_magic, sizeof(fvm_magic))) {
return DISK_FORMAT_FVM;
}
if (!memcmp(data, zxcrypt_magic, sizeof(zxcrypt_magic))) {
return DISK_FORMAT_ZXCRYPT;
}
if (!memcmp(data + 0x200, gpt_magic, sizeof(gpt_magic))) {
return DISK_FORMAT_GPT;
}
if (!memcmp(data, minfs_magic, sizeof(minfs_magic))) {
return DISK_FORMAT_MINFS;
}
if (!memcmp(data, blobfs_magic, sizeof(blobfs_magic))) {
return DISK_FORMAT_BLOBFS;
}
if ((data[510] == 0x55 && data[511] == 0xAA)) {
if ((data[38] == 0x29 || data[66] == 0x29)) {
// 0x55AA are always placed at offset 510 and 511 for FAT filesystems.
// 0x29 is the Boot Signature, but it is placed at either offset 38 or
// 66 (depending on FAT type).
return DISK_FORMAT_FAT;
}
return DISK_FORMAT_MBR;
}
if (verbosity == DiskFormatLogVerbosity::Verbose) {
// Log a hexdump of the bytes we looked at and didn't find any magic in.
fprintf(stderr, "detect_disk_format: did not recognize format. Looked at:\n");
// fvm, zxcrypt, minfs, and blobfs have their magic bytes at the start
// of the block.
hexdump_very_ex(data, 16, 0, hexdump_stdio_printf, stderr);
// MBR is two bytes at offset 0x1fe, but print 16 just for consistency
hexdump_very_ex(data + 0x1f0, 16, 0x1f0, hexdump_stdio_printf, stderr);
// GPT magic is stored 512 bytes in, so it can coexist with MBR.
hexdump_very_ex(data + 0x200, 16, 0x200, hexdump_stdio_printf, stderr);
}
return DISK_FORMAT_UNKNOWN;
}
disk_format_t detect_disk_format(int fd) {
return detect_disk_format_impl(fd, DiskFormatLogVerbosity::Silent);
}
disk_format_t detect_disk_format_log_unknown(int fd) {
return detect_disk_format_impl(fd, DiskFormatLogVerbosity::Verbose);
}
zx_status_t fmount(int device_fd, int mount_fd, disk_format_t df, const mount_options_t* options,
LaunchCallback cb) {
Mounter mounter(mount_fd);
zx::channel block_device;
zx_status_t status = fdio_get_service_handle(device_fd, block_device.reset_and_get_address());
if (status != ZX_OK) {
return status;
}
return mounter.Mount(std::move(block_device), df, *options, cb);
}
zx_status_t mount(int device_fd, const char* mount_path, disk_format_t df,
const mount_options_t* options, LaunchCallback cb) {
if (!options->create_mountpoint) {
// Open mountpoint; use it directly.
unique_fd mount_point(open(mount_path, O_RDONLY | O_DIRECTORY | O_ADMIN));
if (!mount_point) {
return ZX_ERR_BAD_STATE;
}
return fmount(device_fd, mount_point.get(), df, options, cb);
}
Mounter mounter(mount_path);
zx::channel block_device;
zx_status_t status = fdio_get_service_handle(device_fd, block_device.reset_and_get_address());
if (status != ZX_OK) {
return status;
}
return mounter.Mount(std::move(block_device), df, *options, cb);
}
zx_status_t fumount(int mount_fd) {
zx_handle_t h;
zx_status_t status;
fzl::FdioCaller caller{fbl::unique_fd(mount_fd)};
zx_status_t io_status = fuchsia_io_DirectoryAdminUnmountNode(caller.borrow_channel(),
&status, &h);
caller.release().release();
if (io_status != ZX_OK) {
return io_status;
}
zx::channel c(h);
if (status != ZX_OK) {
return status;
}
return vfs_unmount_handle(c.release(), ZX_TIME_INFINITE);
}
zx_status_t umount(const char* mount_path) {
unique_fd fd(open(mount_path, O_DIRECTORY | O_NOREMOTE | O_ADMIN));
if (!fd) {
fprintf(stderr, "Could not open directory: %s\n", strerror(errno));
return ZX_ERR_BAD_STATE;
}
zx_status_t status = fumount(fd.get());
return status;
}