blob: b61f905b63669f837743b39e8ae7c327f3d8b540 [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 <errno.h>
#include <fcntl.h>
#include <fuchsia/hardware/block/llcpp/fidl.h>
#include <fuchsia/io/llcpp/fidl.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/limits.h>
#include <lib/fdio/namespace.h>
#include <lib/fdio/vfs.h>
#include <lib/zx/channel.h>
#include <string.h>
#include <unistd.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>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_call.h>
#include <fbl/unique_fd.h>
#include <fbl/vector.h>
#include <fs-management/mount.h>
#include <fs/vfs.h>
#include <pretty/hexdump.h>
#include "admin.h"
namespace fblock = ::llcpp::fuchsia::hardware::block;
namespace fio = ::llcpp::fuchsia::io;
namespace fs_management {
namespace {
zx_status_t MakeDirAndRemoteMount(const char* path, zx::channel root) {
// 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;
}
}
zx_status_t status;
zx::channel parent, parent_server;
if ((status = zx::channel::create(0, &parent, &parent_server)) != ZX_OK) {
return status;
}
if ((status = fdio_open(parent_path, O_RDONLY | O_DIRECTORY | O_ADMIN,
parent_server.release())) != ZX_OK) {
return status;
}
fio::DirectoryAdmin::SyncClient parent_client(std::move(parent));
auto resp =
parent_client.MountAndCreate(std::move(root), fidl::unowned_str(name, strlen(name)), 0);
if (!resp.ok()) {
return resp.status();
}
return resp.value().s;
}
zx_status_t StartFilesystem(fbl::unique_fd device_fd, disk_format_t df,
const mount_options_t* options, LaunchCallback cb,
OutgoingDirectory outgoing_directory, zx::channel* out_data_root) {
// get the device handle from the device_fd
zx_status_t status;
zx::channel device;
status = fdio_get_service_handle(device_fd.release(), device.reset_and_get_address());
if (status != ZX_OK) {
return status;
}
// convert mount options to init options
init_options_t init_options = {
.readonly = options->readonly,
.verbose_mount = options->verbose_mount,
.collect_metrics = options->collect_metrics,
.wait_until_ready = options->wait_until_ready,
.write_compression_algorithm = options->write_compression_algorithm,
// TODO(jfsulliv): This is currently only used in tests. Plumb through mount options if
// needed.
.write_compression_level = -1,
.cache_eviction_policy = options->cache_eviction_policy,
.fsck_after_every_transaction = options->fsck_after_every_transaction,
.sandbox_decompression = options->sandbox_decompression,
.callback = cb,
};
// launch the filesystem process
zx::unowned_channel export_root(outgoing_directory.client);
status =
FsInit(std::move(device), df, init_options, std::move(outgoing_directory)).status_value();
if (status != ZX_OK) {
return status;
}
// register the export root with the fshost registry
if (options->register_fs && ((status = fs_register(export_root->get())) != ZX_OK)) {
return status;
}
// Extract the handle to the root of the filesystem from the export root. The POSIX flag will
// cause the writable and executable rights to be inherited (if present).
uint32_t flags = fio::OPEN_RIGHT_READABLE | fio::OPEN_FLAG_POSIX;
if (options->admin)
flags |= fio::OPEN_RIGHT_ADMIN;
auto handle_or = GetFsRootHandle(zx::unowned_channel(export_root), flags);
if (handle_or.is_error()) {
return handle_or.status_value();
}
*out_data_root = std::move(handle_or).value();
return ZX_OK;
}
} // namespace
} // namespace fs_management
const mount_options_t default_mount_options = {
.readonly = false,
.verbose_mount = false,
.collect_metrics = false,
.wait_until_ready = true,
.create_mountpoint = false,
.write_compression_algorithm = nullptr,
.cache_eviction_policy = nullptr,
.register_fs = true,
.fsck_after_every_transaction = false,
.admin = true,
.outgoing_directory = {ZX_HANDLE_INVALID, ZX_HANDLE_INVALID},
.bind_to_namespace = false,
.sandbox_decompression = false,
};
const mkfs_options_t default_mkfs_options = {
.fvm_data_slices = 1,
.verbose = false,
.sectors_per_cluster = 0,
};
const fsck_options_t default_fsck_options = {
.verbose = false,
.never_modify = false,
.always_modify = false,
.force = 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;
}
fdio_cpp::UnownedFdioCaller caller(fd);
auto resp = fblock::Block::Call::GetInfo(caller.channel());
if (!resp.ok() || resp.value().status != ZX_OK) {
fprintf(stderr, "detect_disk_format: Could not acquire block device info\n");
return DISK_FORMAT_UNKNOWN;
}
if (!resp.value().info->block_size) {
fprintf(stderr, "detect_disk_format: Expected a block size of > 0\n");
return DISK_FORMAT_UNKNOWN;
}
// We need to read at least two blocks, because the GPT magic is located inside the second block
// of the disk.
size_t header_size =
(HEADER_SIZE > (2 * resp->info->block_size)) ? HEADER_SIZE : (2 * resp->info->block_size);
// check if the partition is big enough to hold the header in the first place
if (header_size > resp.value().info->block_size * resp.value().info->block_count) {
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(header_size, static_cast<size_t>(resp.value().info->block_size));
ZX_DEBUG_ASSERT_MSG(buffer_size > 0, "Expected buffer_size to be greater than 0\n");
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, block_verity_magic, sizeof(block_verity_magic))) {
return DISK_FORMAT_BLOCK_VERITY;
}
if (!memcmp(data + resp->info->block_size, 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 (!memcmp(data, factoryfs_magic, sizeof(factoryfs_magic))) {
return DISK_FORMAT_FACTORYFS;
}
if (!memcmp(data, vbmeta_magic, sizeof(vbmeta_magic))) {
return DISK_FORMAT_VBMETA;
}
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 one block in, so it can coexist with MBR.
hexdump_very_ex(data + resp->info->block_size, 16, resp->info->block_size, hexdump_stdio_printf,
stderr);
}
return DISK_FORMAT_UNKNOWN;
}
__EXPORT
disk_format_t detect_disk_format(int fd) {
return detect_disk_format_impl(fd, DiskFormatLogVerbosity::Silent);
}
__EXPORT
disk_format_t detect_disk_format_log_unknown(int fd) {
return detect_disk_format_impl(fd, DiskFormatLogVerbosity::Verbose);
}
__EXPORT
zx_status_t fmount(int dev_fd, int mount_fd, disk_format_t df, const mount_options_t* options,
LaunchCallback cb) {
if (options->bind_to_namespace) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t status;
zx::channel data_root;
fbl::unique_fd device_fd(dev_fd);
fs_management::OutgoingDirectory handles{zx::unowned_channel(options->outgoing_directory.client),
zx::channel(options->outgoing_directory.server)};
zx::channel client;
if (!*handles.client) {
zx_status_t status = zx::channel::create(0, &client, &handles.server);
if (status != ZX_OK)
return status;
handles.client = zx::unowned_channel(client);
}
if ((status = fs_management::StartFilesystem(std::move(device_fd), df, options, cb,
std::move(handles), &data_root)) != ZX_OK) {
return status;
}
fdio_cpp::FdioCaller caller{fbl::unique_fd(mount_fd)};
auto resp = fio::DirectoryAdmin::Call::Mount(caller.channel(), std::move(data_root));
caller.release().release();
if (!resp.ok()) {
return resp.status();
}
return resp.value().s;
}
__EXPORT
zx_status_t mount_root_handle(zx_handle_t root_handle, const char* mount_path) {
zx_status_t status;
zx::channel mount_point, mount_point_server;
if ((status = zx::channel::create(0, &mount_point, &mount_point_server)) != ZX_OK) {
return status;
}
if ((status = fdio_open(mount_path, O_RDONLY | O_DIRECTORY | O_ADMIN,
mount_point_server.release())) != ZX_OK) {
return status;
}
fio::DirectoryAdmin::SyncClient mount_client(std::move(mount_point));
auto resp = mount_client.Mount(zx::channel(root_handle));
if (!resp.ok()) {
return resp.status();
}
return resp.value().s;
}
__EXPORT
zx_status_t mount(int dev_fd, const char* mount_path, disk_format_t df,
const mount_options_t* options, LaunchCallback cb) {
zx_status_t status;
zx::channel data_root;
fbl::unique_fd device_fd(dev_fd);
fs_management::OutgoingDirectory handles{zx::unowned_channel(options->outgoing_directory.client),
zx::channel(options->outgoing_directory.server)};
zx::channel client;
if (!*handles.client) {
zx_status_t status = zx::channel::create(0, &client, &handles.server);
if (status != ZX_OK)
return status;
handles.client = zx::unowned_channel(client);
}
if ((status = fs_management::StartFilesystem(std::move(device_fd), df, options, cb,
std::move(handles), &data_root)) != ZX_OK) {
return status;
}
// If no mount point is provided, just return success; the caller can get whatever they want from
// the export root.
if (mount_path == nullptr)
return ZX_OK;
if (options->bind_to_namespace) {
fdio_ns_t* ns;
if ((status = fdio_ns_get_installed(&ns)) != ZX_OK) {
return status;
}
return fdio_ns_bind(ns, mount_path, data_root.release());
} else {
// mount the channel in the requested location
if (options->create_mountpoint) {
return fs_management::MakeDirAndRemoteMount(mount_path, std::move(data_root));
}
return mount_root_handle(data_root.release(), mount_path);
}
}
__EXPORT
zx_status_t fumount(int mount_fd) {
fdio_cpp::FdioCaller caller{fbl::unique_fd(mount_fd)};
auto resp = fio::DirectoryAdmin::Call::UnmountNode(caller.channel());
caller.release().release();
if (!resp.ok()) {
return resp.status();
}
if (resp.value().s != ZX_OK) {
return resp.value().s;
}
// Note: we are unsafely converting from a client end of the
// |fuchsia.io/Directory| protocol into a client end of the
// |fuchsia.io/DirectoryAdmin| protocol.
// This method will only work if |mount_fd| is backed by a connection
// that actually speaks the |DirectoryAdmin| protocol.
fidl::ClientEnd<fio::DirectoryAdmin> directory_admin_client(
std::move(resp.value().remote.channel()));
return fs::Vfs::UnmountHandle(std::move(directory_admin_client), zx::time::infinite());
}
__EXPORT
zx_status_t umount(const char* mount_path) {
fprintf(stderr, "Unmounting %s\n", mount_path);
fbl::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;
}