blob: 3c7471ca6d65b1ff45a1b5512b3881304a713cae [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 <dirent.h>
#include <fcntl.h>
#include <fuchsia/hardware/block/c/fidl.h>
#include <fuchsia/security/resource/llcpp/fidl.h>
#include <getopt.h>
#include <lib/fdio/directory.h>
#include <lib/zx/channel.h>
#include <lib/zx/resource.h>
#include <libgen.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <optional>
#include <utility>
#include <blobfs/compression-algorithm.h>
#include <blobfs/fsck.h>
#include <blobfs/mkfs.h>
#include <blobfs/mount.h>
#include <block-client/cpp/remote-block-device.h>
#include <fbl/auto_call.h>
#include <fbl/string.h>
#include <fbl/unique_fd.h>
#include <fbl/vector.h>
#include <fs/trace.h>
#include <fs/vfs.h>
namespace {
using block_client::BlockDevice;
using block_client::RemoteBlockDevice;
zx::resource AttemptToGetVmexResource() {
zx::channel local, remote;
zx_status_t status = zx::channel::create(0, &local, &remote);
if (status != ZX_OK) {
return zx::resource();
}
status = fdio_service_connect("/svc_blobfs/fuchsia.security.resource.Vmex", remote.release());
if (status != ZX_OK) {
FS_TRACE_WARN("blobfs: Failed to connect to fuchsia.security.resource.Vmex: %d\n", status);
return zx::resource();
}
auto client = llcpp::fuchsia::security::resource::Vmex::SyncClient{std::move(local)};
auto result = client.Get();
if (!result.ok()) {
FS_TRACE_WARN("blobfs: fuchsia.security.resource.Vmex.Get() failed: %d\n", result.status());
return zx::resource();
}
return std::move(result.Unwrap()->vmex);
}
zx_status_t Mount(std::unique_ptr<BlockDevice> device, blobfs::MountOptions* options) {
zx::channel outgoing_server = zx::channel(zx_take_startup_handle(PA_DIRECTORY_REQUEST));
// TODO(fxb/34531): this currently supports both the old (data root only) and the new (outgoing
// directory) behaviors. once all clients are moved over to using the new behavior, delete the old
// one.
zx::channel root_server = zx::channel(zx_take_startup_handle(FS_HANDLE_ROOT_ID));
if (outgoing_server.is_valid() && root_server.is_valid()) {
FS_TRACE_ERROR(
"blobfs: both PA_DIRECTORY_REQUEST and FS_HANDLE_ROOT_ID provided - need one or the "
"other.\n");
return ZX_ERR_BAD_STATE;
}
zx::channel export_root;
blobfs::ServeLayout layout;
if (outgoing_server.is_valid()) {
export_root = std::move(outgoing_server);
layout = blobfs::ServeLayout::kExportDirectory;
} else if (root_server.is_valid()) {
export_root = std::move(root_server);
layout = blobfs::ServeLayout::kDataRootOnly;
} else {
// neither provided? or we can't access them for some reason.
FS_TRACE_ERROR("blobfs: could not get startup handle to serve on\n");
return ZX_ERR_BAD_STATE;
}
// Try and get a ZX_RSRC_KIND_VMEX resource if the fuchsia.security.resource.Vmex service is
// available, which will only be the case if this is launched by fshost. This is non-fatal because
// blobfs can still otherwise work but will not support executable blobs.
zx::resource vmex = AttemptToGetVmexResource();
if (!vmex.is_valid()) {
FS_TRACE_WARN("blobfs: VMEX resource unavailable, executable blobs are unsupported\n");
}
return blobfs::Mount(std::move(device), options, std::move(export_root), layout, std::move(vmex));
}
zx_status_t Mkfs(std::unique_ptr<BlockDevice> device, blobfs::MountOptions* options) {
return blobfs::FormatFilesystem(device.get());
}
zx_status_t Fsck(std::unique_ptr<BlockDevice> device, blobfs::MountOptions* options) {
return blobfs::Fsck(std::move(device), options);
}
typedef zx_status_t (*CommandFunction)(std::unique_ptr<BlockDevice> device,
blobfs::MountOptions* options);
const struct {
const char* name;
CommandFunction func;
const char* help;
} kCmds[] = {
{"create", Mkfs, "initialize filesystem"}, {"mkfs", Mkfs, "initialize filesystem"},
{"check", Fsck, "check filesystem integrity"}, {"fsck", Fsck, "check filesystem integrity"},
{"mount", Mount, "mount filesystem"},
};
std::optional<blobfs::CompressionAlgorithm> ParseAlgorithm(const char* str) {
if (!strcmp(str, "UNCOMPRESSED")) {
return blobfs::CompressionAlgorithm::UNCOMPRESSED;
} else if (!strcmp(str, "ZSTD")) {
return blobfs::CompressionAlgorithm::ZSTD;
} else if (!strcmp(str, "ZSTD_SEEKABLE")) {
return blobfs::CompressionAlgorithm::ZSTD_SEEKABLE;
} else if (!strcmp(str, "ZSTD_CHUNKED")) {
return blobfs::CompressionAlgorithm::CHUNKED;
}
return std::nullopt;
}
int usage() {
fprintf(
stderr,
"usage: blobfs [ <options>* ] <command> [ <arg>* ]\n"
"\n"
"options: -v|--verbose Additional debug logging\n"
" -r|--readonly Mount filesystem read-only\n"
" -m|--metrics Collect filesystem metrics\n"
" -j|--journal Utilize the blobfs journal\n"
" For fsck, the journal is replayed before verification\n"
" -p|--pager Enable user pager\n"
" -c|--compression [alg] compression algorithm to apply to newly stored blobs.\n"
" Does not affect any blobs already stored on-disk.\n"
" 'alg' can be one of ZSTD, ZSTD_SEEKABLE, ZSTD_CHUNKED,\n"
" or UNCOMPRESSED.\n"
" -h|--help Display this message\n"
"\n"
"On Fuchsia, blobfs takes the block device argument by handle.\n"
"This can make 'blobfs' commands hard to invoke from command line.\n"
"Try using the [mkfs,fsck,mount,umount] commands instead\n"
"\n");
for (unsigned n = 0; n < (sizeof(kCmds) / sizeof(kCmds[0])); n++) {
fprintf(stderr, "%9s %-10s %s\n", n ? "" : "commands:", kCmds[n].name, kCmds[n].help);
}
fprintf(stderr, "\n");
return ZX_ERR_INVALID_ARGS;
}
zx_status_t ProcessArgs(int argc, char** argv, CommandFunction* func,
blobfs::MountOptions* options) {
while (1) {
static struct option opts[] = {
{"verbose", no_argument, nullptr, 'v'}, {"readonly", no_argument, nullptr, 'r'},
{"metrics", no_argument, nullptr, 'm'}, {"journal", no_argument, nullptr, 'j'},
{"pager", no_argument, nullptr, 'p'}, {"write-uncompressed", no_argument, nullptr, 'u'},
{"compression", required_argument, nullptr, 'c'}, {"help", no_argument, nullptr, 'h'},
{nullptr, 0, nullptr, 0},
};
int opt_index;
int c = getopt_long(argc, argv, "vrmjpuc:h", opts, &opt_index);
if (c < 0) {
break;
}
switch (c) {
case 'r':
options->writability = blobfs::Writability::ReadOnlyFilesystem;
break;
case 'm':
options->metrics = true;
break;
case 'j':
options->journal = true;
break;
case 'p':
options->pager = true;
break;
case 'u':
// TODO(jfsulliv): Remove legacy parameter.
options->write_compression_algorithm = blobfs::CompressionAlgorithm::UNCOMPRESSED;
break;
case 'c': {
std::optional<blobfs::CompressionAlgorithm> algorithm = ParseAlgorithm(optarg);
if (!algorithm) {
fprintf(stderr, "Invalid compression algorithm: %s\n", optarg);
return usage();
}
options->write_compression_algorithm = *algorithm;
break;
}
case 'v':
options->verbose = true;
break;
case 'h':
default:
return usage();
}
}
argc -= optind;
argv += optind;
if (argc < 1) {
return usage();
}
const char* command = argv[0];
// Validate command
for (unsigned i = 0; i < sizeof(kCmds) / sizeof(kCmds[0]); i++) {
if (!strcmp(command, kCmds[i].name)) {
*func = kCmds[i].func;
}
}
if (*func == nullptr) {
fprintf(stderr, "Unknown command: %s\n", command);
return usage();
}
return ZX_OK;
}
} // namespace
int main(int argc, char** argv) {
CommandFunction func = nullptr;
blobfs::MountOptions options;
zx_status_t status = ProcessArgs(argc, argv, &func, &options);
if (status != ZX_OK) {
return -1;
}
zx::channel block_connection = zx::channel(zx_take_startup_handle(FS_HANDLE_BLOCK_DEVICE_ID));
if (!block_connection.is_valid()) {
FS_TRACE_ERROR("blobfs: Could not access startup handle to block device\n");
return -1;
}
fbl::unique_fd svc_fd(open("/svc", O_RDONLY));
if (!svc_fd.is_valid()) {
FS_TRACE_ERROR("blobfs: Failed to open svc from incoming namespace\n");
return -1;
}
std::unique_ptr<RemoteBlockDevice> device;
status = RemoteBlockDevice::Create(std::move(block_connection), &device);
if (status != ZX_OK) {
FS_TRACE_ERROR("blobfs: Could not initialize block device\n");
return -1;
}
status = func(std::move(device), &options);
if (status != ZX_OK) {
return -1;
}
return 0;
}