blob: ecc7d26cf5fddb41e628daa926319969c4af03de [file] [log] [blame]
// Copyright 2018 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 <assert.h>
#include <getopt.h>
#include <inttypes.h>
#include <libgen.h>
#include <limits.h>
#include <sys/stat.h>
#include <fbl/algorithm.h>
#include <fbl/auto_call.h>
#include "fs-host/common.h"
#define MIN_ARGS 3
// Options struct.
struct {
const char* name;
Option option;
const char* argument;
const char* default_value;
const char* help;
} OPTS[] = {
{"depfile", Option::kDepfile, "", nullptr, "Produce a depfile"},
{"readonly", Option::kReadonly, "", nullptr, "Mount filesystem read-only"},
{"offset", Option::kOffset, "[bytes]", "0", "Byte offset at which minfs partition starts"},
{"length", Option::kLength, "[bytes]", "Remaining Length",
"Length in bytes of minfs partition"},
{"compress", Option::kCompress, "", nullptr, "Compress files before adding them to blobfs"},
{"sizes", Option::kSizes, "[file]", nullptr, "Record sizes of written entries to file"},
{"help", Option::kHelp, "", nullptr, "Display this message"},
};
// Commands struct.
// clang-format off
struct {
const char* name;
Command command;
uint32_t flags;
ArgType arg_type;
const char* help;
} CMDS[] = {
{"create", Command::kMkfs, O_RDWR | O_CREAT, ArgType::kOptional,
"Initialize filesystem."},
{"mkfs", Command::kMkfs, O_RDWR | O_CREAT, ArgType::kOptional,
"Initialize filesystem."},
{"check", Command::kFsck, O_RDONLY, ArgType::kNone,
"Check filesystem integrity."},
{"fsck", Command::kFsck, O_RDONLY, ArgType::kNone,
"Check filesystem integrity."},
{"used-data-size", Command::kUsedDataSize, O_RDONLY, ArgType::kNone,
"Prints total bytes consumed by data."},
{"used-inodes", Command::kUsedInodes, O_RDONLY, ArgType::kNone,
"Prints number of allocated inodes."},
{"used-size", Command::kUsedSize, O_RDONLY, ArgType::kNone,
"Prints total bytes used by data and reserved for fs internal data structures."},
{"add", Command::kAdd, O_RDWR, ArgType::kMany,
"Add files to an fs image (additional arguments required)."},
{"cp", Command::kCp, O_RDWR, ArgType::kTwo,
"Copy to/from fs."},
{"mkdir", Command::kMkdir, O_RDWR, ArgType::kOne,
"Create directory."},
{"ls", Command::kLs, O_RDONLY, ArgType::kOne,
"List contents of directory."},
{"manifest", Command::kManifest, O_RDWR, ArgType::kOne,
"Add files to fs as specified in manifest (deprecated)."},
};
// clang-format on
// Arguments struct.
struct {
const char* name;
Argument argument;
} ARGS[] = {
{"--manifest", Argument::kManifest},
{"--blob", Argument::kBlob},
};
zx_status_t FsCreator::ProcessAndRun(int argc, char** argv) {
zx_status_t status;
if ((status = ProcessArgs(argc, argv)) != ZX_OK) {
return status;
}
return RunCommand();
}
zx_status_t FsCreator::Usage() {
fprintf(stderr, "usage: %s [ <option>* ] <file-or-device>[@<size>] <command> [ <arg>* ]\n\n",
GetToolName());
// Display all valid pre-command options.
bool first = true;
for (unsigned n = 0; n < fbl::count_of(OPTS); n++) {
if (IsOptionValid(OPTS[n].option)) {
fprintf(stderr, "%-8s -%c|--%-8s ", first ? "options:" : "", OPTS[n].name[0], OPTS[n].name);
fprintf(stderr, "%-8s", OPTS[n].argument);
fprintf(stderr, "\t%s\n", OPTS[n].help);
if (OPTS[n].default_value != nullptr) {
fprintf(stderr, "%33s(Default = %s)\n", "", OPTS[n].default_value);
}
first = false;
}
}
fprintf(stderr, "\n");
// Display all valid commands.
first = true;
for (unsigned n = 0; n < fbl::count_of(CMDS); n++) {
if (IsCommandValid(CMDS[n].command)) {
fprintf(stderr, "%9s %-10s %s\n", first ? "commands:" : "", CMDS[n].name, CMDS[n].help);
first = false;
}
}
fprintf(stderr, "\n");
// Display all valid '--' arguments.
fprintf(stderr, "arguments (valid for create, one or more required for add):\n");
for (unsigned n = 0; n < fbl::count_of(ARGS); n++) {
if (IsArgumentValid(ARGS[n].argument)) {
fprintf(stderr, "\t%-10s <path>\n", ARGS[n].name);
}
}
return ZX_ERR_INVALID_ARGS;
}
zx_status_t FsCreator::ProcessManifest(char* manifest_path) {
fbl::unique_fd manifest_fd(open(manifest_path, O_RDONLY, 0644));
if (!manifest_fd) {
fprintf(stderr, "error: cannot open '%s'\n", manifest_path);
return ZX_ERR_IO;
}
char dir_path[PATH_MAX];
strncpy(dir_path, dirname(manifest_path), PATH_MAX);
FILE* manifest = fdopen(manifest_fd.release(), "r");
while (true) {
// Keep processing lines in the manifest until we have reached EOF.
zx_status_t status = ProcessManifestLine(manifest, dir_path);
if (status == ZX_ERR_OUT_OF_RANGE) {
fclose(manifest);
return ZX_OK;
} else if (status != ZX_OK) {
fclose(manifest);
return status;
}
}
}
zx_status_t FsCreator::ParseManifestLine(FILE* manifest, const char* dir_path, char* src,
char* dst) {
size_t size = 0;
char* line = nullptr;
// Always free the line on exiting this method.
auto cleanup = fbl::MakeAutoCall([&line]() {
if (line)
free(line);
});
// Retrieve the next line from the manifest.
ssize_t r = getline(&line, &size, manifest);
if (r < 0) {
return ZX_ERR_OUT_OF_RANGE;
}
// Exit early if line is commented out
if (line[0] == '#') {
return ZX_OK;
}
char* equals = strchr(line, '=');
char* src_start = line;
if (equals != nullptr) {
// If we found an '=', there is a destination in this line.
// (Note that destinations are allowed but not required for blobfs.)
if (strchr(equals + 1, '=') != nullptr) {
fprintf(stderr, "Too many '=' in input\n");
return ZX_ERR_INVALID_ARGS;
}
src_start = equals + 1;
equals[0] = '\0';
strncat(dst, line, PATH_MAX - strlen(dst));
}
// If the source is not an absolute path, append the manifest's local directory.
if (src_start[0] != '/') {
strncpy(src, dir_path, PATH_MAX);
strncat(src, "/", PATH_MAX - strlen(src));
}
strncat(src, src_start, PATH_MAX - strlen(src));
// Set the source path to terminate if it currently ends in a new line.
char* new_line = strchr(src, '\n');
if (new_line != nullptr) {
*new_line = '\0';
}
return ZX_OK;
}
zx_status_t FsCreator::ProcessArgs(int argc, char** argv) {
if (argc < MIN_ARGS) {
fprintf(stderr, "Not enough args\n");
return Usage();
}
bool depfile_needed = false;
// Read options.
while (true) {
// Set up options struct for pre-device option processing.
unsigned index = 0;
struct option opts[fbl::count_of(OPTS) + 1];
for (unsigned n = 0; n < fbl::count_of(OPTS); n++) {
if (IsOptionValid(OPTS[n].option)) {
opts[index].name = OPTS[n].name;
opts[index].has_arg = strlen(OPTS[n].argument) ? required_argument : no_argument;
opts[index].flag = nullptr;
opts[index].val = OPTS[n].name[0];
index++;
}
}
opts[index] = {nullptr, 0, nullptr, 0};
int opt_index;
int c = getopt_long(argc, argv, "+dro:l:cs:h", opts, &opt_index);
if (c < 0) {
break;
}
switch (c) {
case 'd':
depfile_needed = true;
break;
case 'r':
read_only_ = true;
break;
case 'o':
offset_ = atoll(optarg);
if (offset_ < 0) {
fprintf(stderr, "error: offset < 0\n");
return ZX_ERR_INVALID_ARGS;
}
break;
case 'l':
length_ = atoll(optarg);
if (length_ < 0) {
fprintf(stderr, "error: length < 0\n");
return ZX_ERR_INVALID_ARGS;
}
break;
case 'c':
compress_ = true;
break;
case 's': {
const char* const sizes_file = optarg;
if (!size_recorder_.OpenSizeFile(sizes_file)) {
fprintf(stderr, "error: cannot open '%s'\n", sizes_file);
return ZX_ERR_IO;
}
break;
}
case 'h':
default:
return Usage();
}
}
argc -= optind;
argv += optind;
if (argc < 2) {
fprintf(stderr, "Not enough arguments\n");
return Usage();
}
// Read device name.
char* device = argv[0];
argc--;
argv++;
// Read command name.
char* command = argv[0];
argc--;
argv++;
uint32_t open_flags = 0;
ArgType arg_type = ArgType::kNone;
// Validate command.
for (unsigned i = 0; i < sizeof(CMDS) / sizeof(CMDS[0]); i++) {
if (!strcmp(command, CMDS[i].name)) {
if (!IsCommandValid(CMDS[i].command)) {
fprintf(stderr, "Invalid command %s\n", command);
return Usage();
}
command_ = CMDS[i].command;
open_flags = read_only_ ? O_RDONLY : CMDS[i].flags;
arg_type = CMDS[i].arg_type;
}
}
if (command_ == Command::kNone) {
fprintf(stderr, "Unknown command: %s\n", argv[0]);
return Usage();
}
// Parse the size argument (if any) from the device string.
size_t requested_size = 0;
if (ParseSize(device, &requested_size) != ZX_OK) {
return Usage();
}
// Open the target device. Do this before we continue processing arguments, in case we are
// copying directories from a minfs image and need to pre-process them.
fd_.reset(open(device, open_flags, 0644));
if (!fd_) {
fprintf(stderr, "error: cannot open '%s'\n", device);
return ZX_ERR_IO;
}
struct stat stats;
if (fstat(fd_.get(), &stats) < 0) {
fprintf(stderr, "Failed to stat device %s\n", device);
return ZX_ERR_IO;
}
// Unless we are creating an image, the length_ has already been decided.
if (command_ != Command::kMkfs) {
if (length_) {
if (offset_ + length_ > stats.st_size) {
fprintf(stderr, "Must specify offset + length <= file size\n");
return ZX_ERR_INVALID_ARGS;
}
} else {
length_ = stats.st_size - offset_;
}
}
// Verify that we've received a valid number of arguments for the given command.
bool valid = true;
switch (arg_type) {
case ArgType::kNone:
valid = argc == 0;
break;
case ArgType::kOne:
valid = argc == 1;
break;
case ArgType::kTwo:
valid = argc == 2;
break;
case ArgType::kMany:
valid = argc;
break;
case ArgType::kOptional:
break;
default:
return ZX_ERR_INTERNAL;
}
if (!valid) {
fprintf(stderr, "Invalid arguments\n");
return Usage();
}
// Process remaining arguments.
while (argc > 0) {
// Default to 2 arguments processed for manifest. If ProcessCustom is called, processed
// will be populated with the actual number of arguments used.
uint8_t processed = 2;
if (!strcmp(argv[0], "--manifest")) {
if (argc < 2) {
return ZX_ERR_INVALID_ARGS;
}
zx_status_t status;
if ((status = ProcessManifest(argv[1])) != ZX_OK) {
return status;
}
} else if (ProcessCustom(argc, argv, &processed) != ZX_OK) {
fprintf(stderr, "ProcessCustom failed\n");
return Usage();
}
argc -= processed;
argv += processed;
}
// Resize the file if we need to.
zx_status_t status;
if ((status = ResizeFile(requested_size, stats)) != ZX_OK) {
return status;
}
if (depfile_needed) {
size_t len = strlen(device);
assert(len + 2 < PATH_MAX);
char buf[PATH_MAX] = {0};
memcpy(&buf[0], device, strlen(device));
buf[len++] = '.';
buf[len++] = 'd';
depfile_.reset(open(buf, O_CREAT | O_TRUNC | O_WRONLY, 0644));
if (!depfile_) {
fprintf(stderr, "error: cannot open '%s'\n", buf);
return ZX_ERR_IO;
}
// update the buf to be suitable to pass to AppendDepfile.
buf[len - 2] = ':';
buf[len - 1] = 0;
status = AppendDepfile(&buf[0]);
}
return status;
}
zx_status_t FsCreator::AppendDepfile(const char* str) {
if (!depfile_) {
return ZX_OK;
}
size_t len = strlen(str);
assert(len < PATH_MAX);
char buf[PATH_MAX] = {0};
memcpy(&buf[0], str, len);
buf[len++] = ' ';
std::lock_guard<std::mutex> lock(depfile_lock_);
// this code makes assumptions about the size of atomic writes on target
// platforms which currently hold true, but are not part of e.g. POSIX.
ssize_t result = write(depfile_.get(), buf, len);
if (result < 0 || static_cast<size_t>(result) != len) {
fprintf(stderr, "error: depfile append error\n");
return ZX_ERR_IO;
}
return ZX_OK;
}
zx_status_t FsCreator::RunCommand() {
if (!fd_) {
fprintf(stderr, "Failed to open fd before running command\n");
return ZX_ERR_INTERNAL;
}
switch (command_) {
case Command::kMkfs:
return Mkfs();
case Command::kFsck:
return Fsck();
case Command::kUsedDataSize:
return UsedDataSize();
case Command::kUsedInodes:
return UsedInodes();
case Command::kUsedSize:
return UsedSize();
case Command::kAdd:
case Command::kCp:
case Command::kManifest:
case Command::kMkdir:
return Add();
case Command::kLs:
return Ls();
default:
fprintf(stderr, "Error: Command not defined\n");
return ZX_ERR_INTERNAL;
}
}
zx_status_t FsCreator::ParseSize(char* device, size_t* out) {
char* sizestr = nullptr;
if ((sizestr = strchr(device, '@')) != nullptr) {
if (command_ != Command::kMkfs) {
fprintf(stderr, "Cannot specify size for this command\n");
return ZX_ERR_INVALID_ARGS;
}
// Create a file with an explicitly requested size
*sizestr++ = 0;
char* end;
size_t size = strtoull(sizestr, &end, 10);
if (end == sizestr) {
fprintf(stderr, "%s: bad size: %s\n", GetToolName(), sizestr);
return ZX_ERR_INVALID_ARGS;
}
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] || size == 0) {
fprintf(stderr, "%s: bad size: %s\n", GetToolName(), sizestr);
return ZX_ERR_INVALID_ARGS;
}
if (length_ && static_cast<size_t>(offset_ + length_) > size) {
fprintf(stderr, "Must specify size > offset + length\n");
return ZX_ERR_INVALID_ARGS;
}
*out = size;
}
return ZX_OK;
}
zx_status_t FsCreator::ResizeFile(off_t requested_size, struct stat stats) {
// Calculate the total required size for the fs image, given all files that have been processed
// up to this point.
off_t required_size;
zx_status_t status = CalculateRequiredSize(&required_size);
if (status != ZX_OK) {
return status;
}
bool is_block = S_ISBLK(stats.st_mode);
if (requested_size) {
if (requested_size < required_size) {
// If the size requested by @ is smaller than the size required, return an error.
fprintf(stderr, "Must specify size larger than required size %" PRIu64 "\n", required_size);
return ZX_ERR_INVALID_ARGS;
} else if (is_block) {
// Do not allow re-sizing for block devices.
fprintf(stderr, "%s: @size argument is not supported for block device targets\n",
GetToolName());
return ZX_ERR_INVALID_ARGS;
}
}
if (command_ == Command::kMkfs && !is_block &&
(stats.st_size != required_size || requested_size)) {
// Only truncate the file size under the following conditions:
// 1. We are creating the fs store for the first time.
// 2. We are not operating on a block device.
// 3a. The current file size is different than the size required for the specified files, OR
// 3b. The user has requested a particular size using the @ argument.
off_t truncate_size = requested_size ? requested_size : required_size;
if (length_ && (offset_ + length_) > truncate_size) {
// If an offset+length were specified and they are smaller than the minimum required,
// return an error.
fprintf(stderr, "Length %" PRIu64 " too small for required size %" PRIu64 "\n", length_,
truncate_size);
return ZX_ERR_INVALID_ARGS;
}
if (ftruncate(fd_.get(), truncate_size)) {
fprintf(stderr, "error: cannot truncate device\n");
return ZX_ERR_IO;
}
if (!length_) {
length_ = truncate_size - offset_;
}
} else if (!length_) {
// If not otherwise specified, update length to be equal to the size of the image.
length_ = stats.st_size - offset_;
}
return ZX_OK;
}