| // 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 <getopt.h> |
| #include <libgen.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include <blobfs/blobfs.h> |
| #include <blobfs/fsck.h> |
| #include <fbl/auto_call.h> |
| #include <fbl/string.h> |
| #include <fbl/unique_fd.h> |
| #include <fbl/unique_ptr.h> |
| #include <fbl/vector.h> |
| #include <fs/vfs.h> |
| #include <fuchsia/hardware/block/c/fidl.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/fzl/fdio.h> |
| #include <lib/zx/channel.h> |
| #include <trace-provider/provider.h> |
| #include <zircon/process.h> |
| #include <zircon/processargs.h> |
| |
| #include <utility> |
| |
| namespace { |
| |
| int Mount(fbl::unique_fd fd, blobfs::MountOptions* options) { |
| if (options->writability == blobfs::Writability::Writable) { |
| fzl::UnownedFdioCaller caller(fd.get()); |
| |
| fuchsia_hardware_block_BlockInfo block_info; |
| zx_status_t status; |
| zx_status_t io_status = fuchsia_hardware_block_BlockGetInfo(caller.borrow_channel(), |
| &status, &block_info); |
| if (io_status != ZX_OK) { |
| status = io_status; |
| } |
| if (status != ZX_OK) { |
| FS_TRACE_ERROR("blobfs: Unable to query block device, fd: %d status: 0x%x\n", |
| fd.get(), status); |
| return -1; |
| } |
| if (block_info.flags & BLOCK_FLAG_READONLY) { |
| FS_TRACE_WARN("blobfs: Mounting as read-only. WARNING: Journal will not be applied\n"); |
| options->writability = blobfs::Writability::ReadOnlyDisk; |
| } |
| } |
| |
| zx::channel root = zx::channel(zx_take_startup_handle(PA_HND(PA_USER0, 0))); |
| if (!root.is_valid()) { |
| FS_TRACE_ERROR("blobfs: Could not access startup handle to mount point\n"); |
| return -1; |
| } |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToThread); |
| trace::TraceProvider provider(loop.dispatcher()); |
| auto loop_quit = [&loop]() { |
| loop.Quit(); |
| FS_TRACE_WARN("blobfs: Unmounted\n"); |
| }; |
| if (blobfs::Mount(loop.dispatcher(), std::move(fd), *options, std::move(root), |
| std::move(loop_quit)) != ZX_OK) { |
| return -1; |
| } |
| loop.Run(); |
| return ZX_OK; |
| } |
| |
| int Mkfs(fbl::unique_fd fd, blobfs::MountOptions* options) { |
| uint64_t block_count; |
| if (blobfs::GetBlockCount(fd.get(), &block_count)) { |
| fprintf(stderr, "blobfs: cannot find end of underlying device\n"); |
| return -1; |
| } |
| |
| return blobfs::Mkfs(fd.get(), block_count); |
| } |
| |
| int Fsck(fbl::unique_fd fd, blobfs::MountOptions* options) { |
| fbl::unique_ptr<blobfs::Blobfs> blobfs; |
| if (blobfs::Initialize(std::move(fd), *options, &blobfs) != ZX_OK) { |
| return -1; |
| } |
| |
| return blobfs::Fsck(std::move(blobfs), options->journal); |
| } |
| |
| typedef int (*CommandFunction)(fbl::unique_fd fd, 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"}, |
| }; |
| |
| int usage() { |
| fprintf(stderr, |
| "usage: blobfs [ <options>* ] <command> [ <arg>* ]\n" |
| "\n" |
| "options: -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" |
| " -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 -1; |
| } |
| |
| // Process options/commands and return open fd to device |
| int ProcessArgs(int argc, char** argv, CommandFunction* func, blobfs::MountOptions* options) { |
| while (1) { |
| static struct option opts[] = { |
| {"readonly", no_argument, nullptr, 'r'}, |
| {"metrics", no_argument, nullptr, 'm'}, |
| {"journal", no_argument, nullptr, 'j'}, |
| {"help", no_argument, nullptr, 'h'}, |
| {nullptr, 0, nullptr, 0}, |
| }; |
| int opt_index; |
| int c = getopt_long(argc, argv, "rmjh", 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 '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(); |
| } |
| |
| // Block device passed by handle |
| return FS_FD_BLOCKDEVICE; |
| } |
| } // namespace |
| |
| int main(int argc, char** argv) { |
| CommandFunction func = nullptr; |
| blobfs::MountOptions options; |
| fbl::unique_fd fd(ProcessArgs(argc, argv, &func, &options)); |
| |
| if (!fd) { |
| return -1; |
| } |
| |
| return func(std::move(fd), &options); |
| } |