blob: 3f2f9d1484beac49054546bfa3c932b6bc43f389 [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 <libgen.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fbl/ref_ptr.h>
#include <fbl/string.h>
#include <fbl/unique_fd.h>
#include <fbl/vector.h>
#include <fs/vfs.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <blobstore/fsck.h>
#ifdef __Fuchsia__
#include <async/loop.h>
#include <blobstore/blobstore.h>
#else
#include <blobstore/host.h>
#endif
namespace {
#ifdef __Fuchsia__
#define MIN_ARGS 2
#else
#define MIN_ARGS 3
#endif
typedef struct {
bool readonly = false;
uint64_t data_blocks = 0;
fbl::Vector<fbl::String> blob_list;
} blob_options_t;
#ifdef __Fuchsia__
int do_blobstore_mount(fbl::unique_fd fd, const blob_options_t& options) {
bool readonly = options.readonly;
if (!readonly) {
block_info_t block_info;
zx_status_t status = static_cast<zx_status_t>(ioctl_block_get_info(fd.get(), &block_info));
if (status < ZX_OK) {
FS_TRACE_ERROR("blobstore: Unable to query block device, fd: %d status: 0x%x\n",
fd.get(), status);
return -1;
}
readonly = block_info.flags & BLOCK_FLAG_READONLY;
}
fbl::RefPtr<blobstore::VnodeBlob> vn;
if (blobstore::blobstore_mount(&vn, fbl::move(fd)) < 0) {
return -1;
}
zx_handle_t h = zx_get_startup_handle(PA_HND(PA_USER0, 0));
if (h == ZX_HANDLE_INVALID) {
FS_TRACE_ERROR("blobstore: Could not access startup handle to mount point\n");
return h;
}
async::Loop loop;
fs::Vfs vfs(loop.async());
vfs.SetReadonly(readonly);
zx_status_t status;
if ((status = vfs.ServeDirectory(fbl::move(vn), zx::channel(h))) != ZX_OK) {
return status;
}
loop.Run();
return 0;
}
#else
int do_blobstore_add_blob(blobstore::Blobstore* bs, const char* blob_name) {
fbl::unique_fd data_fd(open(blob_name, O_RDONLY, 0644));
if (!data_fd) {
fprintf(stderr, "error: cannot open '%s'\n", blob_name);
return -1;
}
int r;
if ((r = blobstore::blobstore_add_blob(bs, data_fd.get())) != 0) {
if (r != ZX_ERR_ALREADY_EXISTS) {
fprintf(stderr, "blobstore: Failed to add blob '%s': %d\n", blob_name, r);
return -1;
}
}
return 0;
}
int do_blobstore_add_blobs(fbl::unique_fd fd, const blob_options_t& options) {
if (options.blob_list.is_empty()) {
fprintf(stderr, "Adding a blob requires an additional file argument\n");
return -1;
}
fbl::RefPtr<blobstore::Blobstore> bs;
if (blobstore_create(&bs, fbl::move(fd)) < 0) {
return -1;
}
for (unsigned i = 0; i < options.blob_list.size(); i++) {
if (do_blobstore_add_blob(bs.get(), options.blob_list[i].c_str()) < 0) {
return -1;
}
}
return 0;
}
#endif
int do_blobstore_mkfs(fbl::unique_fd fd, const blob_options_t& options) {
uint64_t block_count;
if (blobstore::blobstore_get_blockcount(fd.get(), &block_count)) {
fprintf(stderr, "blobstore: cannot find end of underlying device\n");
return -1;
}
int r = blobstore::blobstore_mkfs(fd.get(), block_count);
#ifndef __Fuchsia__
if (r >= 0 && !options.blob_list.is_empty()) {
if (do_blobstore_add_blobs(fbl::move(fd), options) < 0) {
return -1;
}
}
#endif
return r;
}
int do_blobstore_check(fbl::unique_fd fd, const blob_options_t& options) {
fbl::RefPtr<blobstore::Blobstore> vn;
if (blobstore::blobstore_create(&vn, fbl::move(fd)) < 0) {
return -1;
}
return blobstore::blobstore_check(vn);
}
#ifndef __Fuchsia__
off_t calculate_total_size(off_t data_blocks) {
blobstore::blobstore_info_t info;
info.block_count = data_blocks;
info.inode_count = 32768;
return (data_blocks + blobstore::DataStartBlock(info)) * blobstore::kBlobstoreBlockSize;
}
off_t calculate_blob_blocks(off_t data_size) {
blobstore::blobstore_inode_t node;
node.blob_size = data_size;
return MerkleTreeBlocks(node) + BlobDataBlocks(node);
}
zx_status_t process_blob(char* blob_name, blob_options_t* options) {
struct stat s;
if (stat(blob_name, &s) < 0) {
fprintf(stderr, "Failed to stat blob %s\n", blob_name);
return ZX_ERR_IO;
}
options->data_blocks += calculate_blob_blocks(s.st_size);
options->blob_list.push_back(blob_name);
return ZX_OK;
}
zx_status_t process_manifest_line(FILE* manifest, const char* dir_path, blob_options_t* options) {
size_t size = 0;
char* line = nullptr;
int r = getline(&line, &size, manifest);
if (r < 0) {
return ZX_ERR_OUT_OF_RANGE;
}
fbl::unique_free_ptr<char> ptr(line);
// Exit early if line is commented out
if (line[0] == '#') {
return ZX_OK;
}
char src[PATH_MAX];
strncpy(src, dir_path, PATH_MAX);
strncat(src, "/", PATH_MAX - strlen(src));
char* eq_ptr = strchr(line, '=');
if (eq_ptr == nullptr) {
strncat(src, line, PATH_MAX - strlen(src));
} else {
if (strchr(eq_ptr + 1, '=') != nullptr) {
fprintf(stderr, "Too many '=' in input\n");
return ZX_ERR_INVALID_ARGS;
}
strncat(src, eq_ptr + 1, PATH_MAX - strlen(src));
}
char* nl_ptr = strchr(src, '\n');
if (nl_ptr != nullptr) {
*nl_ptr = '\0';
}
return process_blob(src, options);
}
int process_manifest(char* manifest_path, blob_options_t* options) {
fbl::unique_fd manifest_fd(open(manifest_path, O_RDONLY, 0644));
if (!manifest_fd) {
fprintf(stderr, "error: cannot open '%s'\n", manifest_path);
return -1;
}
char dir_path[PATH_MAX];
strncpy(dir_path, dirname(manifest_path), PATH_MAX);
FILE* manifest = fdopen(manifest_fd.release(), "r");
while (true) {
zx_status_t status = process_manifest_line(manifest, dir_path, options);
if (status == ZX_ERR_OUT_OF_RANGE) {
fclose(manifest);
return 0;
} else if (status != ZX_OK) {
fclose(manifest);
return -1;
}
}
}
#endif
typedef int (*CommandFunction)(fbl::unique_fd fd, const blob_options_t& options);
struct {
const char* name;
CommandFunction func;
bool edit_file;
bool accept_args;
const char* help;
} CMDS[] = {
{"create", do_blobstore_mkfs, true, true, "initialize filesystem"},
{"mkfs", do_blobstore_mkfs, true, true, "initialize filesystem"},
{"check", do_blobstore_check, false, false, "check filesystem integrity"},
{"fsck", do_blobstore_check, false, false, "check filesystem integrity"},
#ifdef __Fuchsia__
{"mount", do_blobstore_mount, false, false, "mount filesystem"},
#else
{"add", do_blobstore_add_blobs, false, true, "add blobs to a blobstore image"},
#endif
};
int usage() {
fprintf(stderr,
#ifdef __Fuchsia__
"usage: blobstore [ <options>* ] <command> [ <arg>* ]\n"
"\n"
"options: --readonly Mount filesystem read-only\n"
"\n"
"On Fuchsia, blobstore takes the block device argument by handle.\n"
"This can make 'blobstore' commands hard to invoke from command line.\n"
"Try using the [mkfs,fsck,mount,umount] commands instead\n"
#else
"usage: blobstore <file-or-device>[@<size>] <command> [ <arg>* ]\n"
#endif
"\n");
for (unsigned n = 0; n < (sizeof(CMDS) / sizeof(CMDS[0])); n++) {
fprintf(stderr, "%9s %-10s %s\n", n ? "" : "commands:",
CMDS[n].name, CMDS[n].help);
}
fprintf(stderr, "\n");
#ifndef __Fuchsia__
fprintf(stderr, "arguments (valid for create, one or more required for add):\n"
"\t--blob <path-to-file>\n"
"\t--manifest <path-to-manifest>\n");
#endif
return -1;
}
// Process options/commands and return open fd to device
int process_args(int argc, char** argv, CommandFunction* func, blob_options_t* options) {
if (argc < MIN_ARGS) {
fprintf(stderr, "Not enough args\n");
return usage();
}
argc--;
argv++;
// Read options
while (argc > 1) {
if (!strcmp(argv[0], "--readonly")) {
options->readonly = true;
} else {
break;
}
argc--;
argv++;
}
#ifndef __Fuchsia__
char* device = argv[0];
argc--;
argv++;
#endif
char* command = argv[0];
__UNUSED bool edit_file = false;
__UNUSED bool accept_args = false;
// Validate command
for (unsigned i = 0; i < sizeof(CMDS) / sizeof(CMDS[0]); i++) {
if (!strcmp(command, CMDS[i].name)) {
*func = CMDS[i].func;
edit_file = CMDS[i].edit_file;
accept_args = CMDS[i].accept_args;
} else if (!strcmp(command, "manifest") && !strcmp(CMDS[i].name, "add")) {
// Temporary hack to allow manifest command to use add function
*func = CMDS[i].func;
edit_file = CMDS[i].edit_file;
accept_args = CMDS[i].accept_args;
}
}
if (*func == nullptr) {
fprintf(stderr, "Unknown command: %s\n", argv[0]);
return usage();
}
argc--;
argv++;
#ifdef __Fuchsia__
// Block device passed by handle
return FS_FD_BLOCKDEVICE;
#else
// Process arguments
while (argc > 0) {
if (!accept_args) {
fprintf(stderr, "Arguments not allowed\n");
return usage();
} else if (argc == 1 && !edit_file && options->blob_list.is_empty()) {
// Temporary hack to allow previous syntax for add/manifest
if (!strcmp(command, "add")) {
if (process_blob(argv[0], options) < 0) {
return -1;
}
} else if (!strcmp(command, "manifest")) {
if (process_manifest(argv[0], options) < 0) {
return -1;
}
} else {
return usage();
}
} else if (argc < 2) {
fprintf(stderr, "Invalid arguments\n");
return usage();
} else if (!strcmp(argv[0], "--blob")) {
if (process_blob(argv[1], options) < 0) {
return -1;
}
} else if (!strcmp(argv[0], "--manifest")) {
if (process_manifest(argv[1], options) < 0) {
return -1;
}
} else {
fprintf(stderr, "Argument not found: %s\n", argv[0]);
return usage();
}
argc -= 2;
argv += 2;
}
// Determine blobstore size (with no de-duping for identical files)
off_t total_size = calculate_total_size(options->data_blocks);
char* sizestr = nullptr;
if ((sizestr = strchr(device, '@')) != nullptr) {
if (!edit_file) {
fprintf(stderr, "Cannot specify size for this command\n");
return -1;
}
// Create a file with an explicitly requested size
*sizestr++ = 0;
char* end;
size_t size = strtoull(sizestr, &end, 10);
if (end == sizestr) {
fprintf(stderr, "blobstore: bad size: %s\n", sizestr);
return usage();
}
switch (end[0]) {
case 'M':
case 'm':
size *= (1024 * 1024);
end++;
break;
case 'G':
case 'g':
size *= (1024 * 1024 * 1024);
end++;
break;
}
if (end[0]) {
fprintf(stderr, "blobstore: bad size: %s\n", sizestr);
return usage();
}
if (size < total_size) {
fprintf(stderr, "Specified size is not large enough\n");
return -1;
}
total_size = size;
}
int open_flags = O_RDWR;
if (edit_file) {
open_flags |= O_CREAT;
}
fbl::unique_fd fd(open(device, open_flags, 0644));
if (!fd) {
fprintf(stderr, "error: cannot open '%s'\n", device);
return -1;
}
struct stat s;
if (fstat(fd.get(), &s) < 0) {
fprintf(stderr, "Failed to stat blob %s\n", device);
return -1;
}
// Update size
if (edit_file && (sizestr != nullptr || s.st_size < total_size)) {
if (ftruncate(fd.get(), total_size)) {
fprintf(stderr, "error: cannot truncate device '%s'\n", device);
return -1;
}
}
return fd.release();
#endif
}
} // namespace
int main(int argc, char** argv) {
CommandFunction func = nullptr;
blob_options_t options;
fbl::unique_fd fd(process_args(argc, argv, &func, &options));
if (!fd) {
return -1;
}
return func(fbl::move(fd), options);
}