blob: d565e240592c1019ed72eb2d777f0011011d81d5 [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 "zircon/errors.h"
#define _XOPEN_SOURCE
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <algorithm>
#include <memory>
#include <utility>
#include <fbl/algorithm.h>
#include "minfs.h"
#include "src/storage/minfs/format.h"
#include "src/storage/minfs/fsck.h"
#include "src/storage/minfs/host.h"
#include "src/storage/minfs/minfs.h"
#include "src/storage/minfs/transaction_limits.h"
char kDot[2] = ".";
char kDotDot[3] = "..";
namespace {
template <class T>
uint32_t ToU32(T in) {
if (in > std::numeric_limits<uint32_t>::max()) {
fprintf(stderr, "out of range %" PRIuMAX "\n", static_cast<uintmax_t>(in));
exit(-1);
}
return static_cast<uint32_t>(in);
}
} // namespace
// Returns the string version of |mode|.
static const char* GetModeString(uint32_t mode) {
switch (mode & S_IFMT) {
case S_IFREG:
return "-";
case S_IFCHR:
return "c";
case S_IFBLK:
return "b";
case S_IFDIR:
return "d";
default:
return "?";
}
}
// Adds PATH_PREFIX to path if it isn't there
void GetHostPath(const char* path, char* out) {
out[0] = 0;
int remaining = PATH_MAX;
if (strncmp(path, PATH_PREFIX, PREFIX_SIZE)) {
strncat(out, PATH_PREFIX, remaining);
remaining -= strlen(PATH_PREFIX);
}
strncat(out, path, remaining);
}
// Checks if the given |path| is a directory, and return the answer in |*result|.
zx_status_t IsDirectory(const char* path, bool* result) {
struct stat s;
bool host = host_path(path);
if (host) {
if (stat(path, &s) < 0) {
return ZX_ERR_IO;
}
} else {
if (!emu_is_mounted()) {
fprintf(stderr, "Cannot check directory of unmounted minfs partition\n");
return ZX_ERR_INTERNAL;
}
if (emu_stat(path, &s) < 0) {
return ZX_ERR_IO;
}
}
if (S_ISDIR(s.st_mode)) {
*result = true;
} else {
*result = false;
}
return ZX_OK;
}
// Copies a file to minfs from the host, or vice versa.
zx_status_t CopyFile(const char* src_path, const char* dst_path) {
FileWrapper src;
FileWrapper dst;
if (FileWrapper::Open(src_path, O_RDONLY, 0, &src) < 0) {
fprintf(stderr, "error: cannot open file '%s'\n", src_path);
return ZX_ERR_IO;
}
if (FileWrapper::Open(dst_path, O_WRONLY | O_CREAT | O_TRUNC, 0644, &dst) < 0) {
fprintf(stderr, "error: cannot open file '%s'\n", dst_path);
return ZX_ERR_IO;
}
char buffer[256 * 1024];
ssize_t r;
for (;;) {
if ((r = src.Read(buffer, sizeof(buffer))) < 0) {
fprintf(stderr, "error: reading from '%s'\n", src_path);
break;
} else if (r == 0) {
break;
}
void* ptr = buffer;
ssize_t len = r;
while (len > 0) {
if ((r = dst.Write(ptr, len)) < 0) {
fprintf(stderr, "error: writing to '%s'\n", dst_path);
goto done;
}
ptr = (void*)((uintptr_t)ptr + r);
len -= r;
}
}
done:
return r == 0 ? ZX_OK : ZX_ERR_IO;
}
// Attempts to make the directory at |path|.
// Returns a positive result if creation was successful or the specified directory already exists.
zx_status_t MakeDirectory(const char* path) {
if (DirWrapper::Make(path, 0777) && errno != EEXIST) {
fprintf(stderr, "minfs: could not create directory %s: %s\n", path, strerror(errno));
return ZX_ERR_IO;
}
return ZX_OK;
}
zx_status_t MinfsCreator::Usage() {
zx_status_t status = FsCreator::Usage();
// Additional information about manifest format.
fprintf(stderr, "\nThe format of the manifest must be as follows:\n");
fprintf(stderr, "\t'dst/path=src/path'\n");
fprintf(stderr, "with one dst/src pair on each line.\n");
fprintf(stderr,
"\nPrefix all minfs paths with '::' "
"(unless they are included in a manifest).\n");
return status;
}
bool MinfsCreator::IsCommandValid(Command command) {
switch (command) {
case Command::kMkfs:
case Command::kFsck:
case Command::kUsedDataSize:
case Command::kUsedInodes:
case Command::kUsedSize:
case Command::kLs:
case Command::kCp:
case Command::kManifest:
case Command::kMkdir:
case Command::kAdd:
return true;
default:
return false;
}
}
bool MinfsCreator::IsOptionValid(Option option) {
switch (option) {
case Option::kDepfile:
case Option::kReadonly:
case Option::kOffset:
case Option::kLength:
case Option::kHelp:
return true;
default:
return false;
}
}
bool MinfsCreator::IsArgumentValid(Argument argument) {
switch (argument) {
case Argument::kManifest:
return true;
default:
return false;
}
}
zx_status_t MinfsCreator::ProcessManifestLine(FILE* manifest, const char* dir_path) {
char src[PATH_MAX];
src[0] = '\0';
char dst[PATH_MAX];
dst[0] = '\0';
zx_status_t status;
if ((status = ParseManifestLine(manifest, dir_path, src, dst)) != ZX_OK) {
return status;
}
if (!strlen(src) || !strlen(dst)) {
fprintf(stderr, "Manifest line must specify source and destination files\n");
return ZX_ERR_INVALID_ARGS;
}
bool dir;
if ((status = IsDirectory(src, &dir)) != ZX_OK) {
return status;
}
if (dir) {
fprintf(stderr, "Manifest cannot specify directory as source\n");
return ZX_ERR_INVALID_ARGS;
}
// Make dst into minfs path.
char emu_dst[PATH_MAX];
GetHostPath(dst, emu_dst);
// Process parent directories.
if ((status = ProcessParentDirectories(emu_dst)) != ZX_OK) {
return status;
}
// Copy src to dst.
return ProcessFile(src, emu_dst);
}
zx_status_t MinfsCreator::ProcessCustom(int argc, char** argv, uint8_t* processed) {
uint8_t required_args = 0;
switch (GetCommand()) {
case Command::kLs:
__FALLTHROUGH;
case Command::kMkdir: {
required_args = 1;
if (argc != required_args) {
return ZX_ERR_INVALID_ARGS;
}
// A non-host path is required here since the ls and mkdir commands must be run on minfs.
if (host_path(argv[0])) {
fprintf(stderr, "Must specify path with prefix '::'");
return ZX_ERR_INVALID_ARGS;
}
// ls and mkdir can only be run on one path at a time.
if (!dir_list_.is_empty()) {
fprintf(stderr, "Too many paths specified\n");
return ZX_ERR_INVALID_ARGS;
}
if (GetCommand() == Command::kLs) {
dir_list_.push_back(argv[0]);
return ZX_OK;
}
zx_status_t status;
if ((status = ProcessParentDirectories(argv[0])) != ZX_OK) {
return status;
}
if ((status = ProcessDirectory(argv[0])) != ZX_OK) {
return status;
}
break;
}
case Command::kCp: {
required_args = 2;
if (argc != required_args) {
return ZX_ERR_INVALID_ARGS;
}
// If our source file is located within minfs, we need to mount the minfs image so that we
// can process any child files/directories.
zx_status_t status;
if (!host_path(argv[0]) && (status = MountMinfs()) != ZX_OK) {
return status;
}
char src[PATH_MAX];
char dst[PATH_MAX];
strncpy(src, argv[0], PATH_MAX);
strncpy(dst, argv[1], PATH_MAX);
if ((status = ProcessParentDirectories(dst)) != ZX_OK) {
return status;
}
if ((status = ProcessEntityAndChildren(src, dst)) != ZX_OK) {
return status;
}
break;
}
case Command::kManifest: {
required_args = 1;
if (argc != required_args) {
return ZX_ERR_INVALID_ARGS;
}
zx_status_t status;
if ((status = ProcessManifest(argv[0])) != ZX_OK) {
return status;
}
break;
}
default:
fprintf(stderr, "Arguments not supported\n");
return ZX_ERR_INVALID_ARGS;
}
*processed = required_args;
return ZX_OK;
}
zx_status_t MinfsCreator::CalculateRequiredSize(off_t* out) {
uint32_t dir_count = static_cast<uint32_t>(dir_list_.size() + 1); // dir_list_ + root
// This is a rough estimate of how many directory data blocks will be needed.
// This is not super robust and will not hold up if directories start requiring indirect blocks,
// but for our current purposes it should be sufficient.
uint32_t dir_blocks = ToU32(dir_count + (dir_bytes_ / minfs::kMinfsBlockSize));
minfs::Superblock info;
info.flags = 0;
info.block_size = minfs::kMinfsBlockSize;
info.inode_count = minfs::kMinfsDefaultInodeCount;
info.block_count = ToU32(data_blocks_ + dir_blocks);
// Calculate number of blocks we will need for all minfs structures.
uint32_t inode_bitmap_blocks =
(info.inode_count + minfs::kMinfsBlockBits - 1) / minfs::kMinfsBlockBits;
uint32_t block_bitmap_blocks =
(info.block_count + minfs::kMinfsBlockBits - 1) / minfs::kMinfsBlockBits;
uint32_t inode_table_blocks =
(info.inode_count + minfs::kMinfsInodesPerBlock - 1) / minfs::kMinfsInodesPerBlock;
info.ibm_block = 8;
info.abm_block = info.ibm_block + fbl::round_up(inode_bitmap_blocks, 8u);
info.ino_block = info.abm_block + fbl::round_up(block_bitmap_blocks, 8u);
info.integrity_start_block = info.ino_block + inode_table_blocks;
minfs::TransactionLimits limits(info);
info.dat_block = info.integrity_start_block + limits.GetRecommendedIntegrityBlocks();
*out = (info.dat_block + info.block_count) * info.BlockSize();
return ZX_OK;
}
zx_status_t MinfsCreator::Mkfs() {
// Create the bcache.
auto bc_or = GenerateBcache();
if (bc_or.is_error()) {
return bc_or.error_value();
}
// Consume the bcache to mkfs.
if (auto mkfs_or = minfs::Mkfs(bc_or.value().get()); mkfs_or.is_error()) {
return mkfs_or.error_value();
}
// Add any directories/files that have been pre-processed.
if (zx_status_t status = Add(); status != ZX_OK) {
fprintf(stderr,
"Warning: Adding files on create failed - required size may have been "
"miscalculated\n");
return status;
}
return ZX_OK;
}
zx_status_t MinfsCreator::Fsck() {
auto bc_or = GenerateBcache();
if (bc_or.is_error()) {
return bc_or.error_value();
}
bc_or = minfs::Fsck(std::move(bc_or.value()), minfs::FsckOptions{.repair = true});
return bc_or.status_value();
}
zx_status_t MinfsCreator::UsedDataSize() {
auto bc_or = GenerateBcache();
if (bc_or.is_error()) {
return bc_or.error_value();
}
auto size_or = minfs::UsedDataSize(bc_or.value());
if (size_or.is_error()) {
return size_or.error_value();
}
printf("%" PRIu64 "\n", size_or.value());
return ZX_OK;
}
zx_status_t MinfsCreator::UsedInodes() {
auto bc_or = GenerateBcache();
if (bc_or.is_error()) {
return bc_or.error_value();
}
auto used_inodes_or = minfs::UsedInodes(bc_or.value());
if (used_inodes_or.is_error()) {
return used_inodes_or.error_value();
}
printf("%" PRIu64 "\n", used_inodes_or.value());
return ZX_OK;
}
zx_status_t MinfsCreator::UsedSize() {
auto bc_or = GenerateBcache();
if (bc_or.is_error()) {
return bc_or.error_value();
}
auto size_or = minfs::UsedSize(bc_or.value());
if (size_or.is_error()) {
return size_or.error_value();
}
printf("%" PRIu64 "\n", size_or.value());
return ZX_OK;
}
zx_status_t MinfsCreator::Add() {
zx_status_t status;
// Mount the minfs.
if ((status = MountMinfs()) != ZX_OK) {
return status;
}
// Make all directories.
for (size_t n = 0; n < dir_list_.size(); n++) {
if ((status = MakeDirectory(dir_list_[n].c_str())) != ZX_OK) {
return status;
}
}
// Copy all files.
for (size_t n = 0; n < file_list_.size(); n++) {
if ((status = AppendDepfile(file_list_[n].first.c_str())) != ZX_OK) {
return status;
}
if ((status = CopyFile(file_list_[n].first.c_str(), file_list_[n].second.c_str())) != ZX_OK) {
return status;
}
}
return ZX_OK;
}
zx_status_t MinfsCreator::Ls() {
if (!file_list_.is_empty() || dir_list_.size() != 1) {
fprintf(stderr, "ls requires one argument\n");
return Usage();
}
zx_status_t status;
if ((status = MountMinfs()) != ZX_OK) {
return status;
}
const char* path = dir_list_[0].c_str();
if (strncmp(path, PATH_PREFIX, PREFIX_SIZE)) {
fprintf(stderr, "error: ls can only operate minfs paths (must start with %s)\n", PATH_PREFIX);
return ZX_ERR_INVALID_ARGS;
}
DIR* d = emu_opendir(path);
if (!d) {
return ZX_ERR_IO;
}
struct dirent* dir_entry;
char tmp[2048];
struct stat stats;
while ((dir_entry = emu_readdir(d)) != nullptr) {
if (strcmp(dir_entry->d_name, ".") && strcmp(dir_entry->d_name, "..")) {
memset(&stats, 0, sizeof(struct stat));
if ((strlen(dir_entry->d_name) + strlen(path) + 2) <= sizeof(tmp)) {
snprintf(tmp, sizeof(tmp), "%s/%s", path, dir_entry->d_name);
emu_stat(tmp, &stats);
}
fprintf(stdout, "%s %8jd %s\n", GetModeString(stats.st_mode),
static_cast<intmax_t>(stats.st_size), dir_entry->d_name);
}
}
emu_closedir(d);
return ZX_OK;
}
zx_status_t MinfsCreator::ProcessEntityAndChildren(char* src, char* dst) {
bool dir;
zx_status_t status;
if ((status = IsDirectory(src, &dir)) != ZX_OK) {
return status;
}
if (dir) {
ProcessDirectory(dst);
} else {
return ProcessFile(src, dst);
}
DirWrapper current_dir;
if (DirWrapper::Open(src, &current_dir)) {
return ZX_ERR_IO;
}
size_t src_len = strlen(src);
size_t dst_len = strlen(dst);
struct dirent* dir_entry;
while ((dir_entry = current_dir.ReadDir()) != nullptr) {
if (!strcmp(dir_entry->d_name, ".") || !strcmp(dir_entry->d_name, "..")) {
continue;
}
size_t name_len = strlen(dir_entry->d_name);
if (src_len + name_len + 1 > PATH_MAX - 1 || dst_len + name_len + 1 > PATH_MAX - 1) {
fprintf(stderr, "Path size exceeds PATH_MAX\n");
return ZX_ERR_INVALID_ARGS;
}
strncat(src, "/", 1);
strncat(src, dir_entry->d_name, PATH_MAX - src_len - 2);
strncat(dst, "/", 1);
strncat(dst, dir_entry->d_name, PATH_MAX - dst_len - 2);
zx_status_t status = ProcessEntityAndChildren(src, dst);
if (status != ZX_OK) {
return status;
}
src[src_len] = '\0';
dst[dst_len] = '\0';
}
return 0;
}
zx_status_t MinfsCreator::ProcessParentDirectories(char* path) {
// Create parent directories if they don't exist.
char* slash = strchr(path, '/');
while (slash != nullptr) {
*slash = '\0';
char emu_path[PATH_MAX];
GetHostPath(path, emu_path);
zx_status_t status;
if ((status = ProcessDirectory(emu_path)) != ZX_OK) {
return status;
}
*slash = '/';
slash = strchr(slash + 1, '/');
}
return ZX_OK;
}
zx_status_t MinfsCreator::ProcessDirectory(char* path) {
if (!strncmp(path, PATH_PREFIX, strlen(path))) {
// If |path| is the minfs root directory ("::"), return without processing.
return ZX_OK;
}
// Check to see if |path| has already been processed.
bool found = false;
for (size_t i = 0; i < dir_list_.size(); i++) {
if (!strcmp(path, dir_list_[i].c_str())) {
found = true;
break;
}
}
if (!found) {
// Only process the directory if it has not already been processed.
if (GetCommand() == Command::kMkfs && !host_path(path)) {
// Only calculate required space if we are running mkfs, and this is a minfs path.
ProcessDirectoryEntry(path);
ProcessDirectoryEntry(kDot);
ProcessDirectoryEntry(kDotDot);
}
dir_list_.push_back(path);
}
return ZX_OK;
}
zx_status_t MinfsCreator::ProcessFile(char* src, char* dst) {
struct stat stats;
int result = host_path(src) ? stat(src, &stats) : emu_stat(src, &stats);
if (result < 0) {
fprintf(stderr, "Failed to stat file %s\n", src);
return ZX_ERR_IO;
}
// Check if the file already exists in file_list_.
for (size_t n = 0; n < file_list_.size(); n++) {
if (!strcmp(dst, file_list_[n].second.c_str())) {
if (!strcmp(src, file_list_[n].first.c_str())) {
return ZX_OK;
}
// If the file does exist but the source does not match, return an error.
fprintf(stderr, "Error: Source %s does not match existing source %s for destination %s\n",
src, file_list_[n].first.c_str(), dst);
return ZX_ERR_INVALID_ARGS;
}
}
if (GetCommand() == Command::kMkfs && !host_path(dst)) {
// Only calculate required size if we are copying to minfs, and we are mkfsing.
zx_status_t status;
if ((status = ProcessBlocks(stats.st_size)) != ZX_OK) {
return status;
}
ProcessDirectoryEntry(dst);
}
file_list_.push_back(std::make_pair(src, dst));
return ZX_OK;
}
void MinfsCreator::ProcessDirectoryEntry(char* path) {
if (path == nullptr) {
return;
}
char* last_slash = strrchr(path, '/');
last_slash = last_slash == nullptr ? path : last_slash + 1;
size_t component_length = strlen(last_slash);
if (component_length > std::numeric_limits<uint8_t>::max()) {
fprintf(stderr, "component too long");
exit(-1);
}
dir_bytes_ += minfs::DirentSize(static_cast<uint8_t>(component_length));
}
zx_status_t MinfsCreator::ProcessBlocks(off_t file_size) {
uint64_t total_blocks = 0;
uint32_t remaining = ToU32((file_size + minfs::kMinfsBlockSize - 1) / minfs::kMinfsBlockSize);
// Add direct blocks to the total.
uint32_t direct_blocks = std::min(remaining, minfs::kMinfsDirect);
total_blocks += direct_blocks;
remaining -= direct_blocks;
if (remaining) {
// If more blocks remain, calculate how many indirect blocks we need.
uint32_t indirect_blocks =
std::min((remaining + minfs::kMinfsDirectPerIndirect + 1) / minfs::kMinfsDirectPerIndirect,
minfs::kMinfsIndirect);
direct_blocks = std::min(remaining, indirect_blocks * minfs::kMinfsDirectPerIndirect);
// Add blocks to the total, and update remaining.
total_blocks += indirect_blocks + direct_blocks;
remaining -= direct_blocks;
if (remaining) {
// If blocks remain past the indirect block range, calculate required doubly indirect
// and indirect blocks.
uint32_t dindirect_blocks = std::min(
(remaining + minfs::kMinfsDirectPerDindirect + 1) / minfs::kMinfsDirectPerDindirect,
minfs::kMinfsDoublyIndirect);
indirect_blocks = std::min(
(remaining + minfs::kMinfsDirectPerIndirect + 1) / minfs::kMinfsDirectPerIndirect,
minfs::kMinfsDirectPerIndirect * minfs::kMinfsDoublyIndirect);
direct_blocks =
std::min(remaining, minfs::kMinfsDoublyIndirect * minfs::kMinfsDirectPerDindirect);
// Add blocks to the total, and update remaining.
total_blocks += dindirect_blocks + indirect_blocks + direct_blocks;
remaining -= direct_blocks;
if (remaining) {
// If we still have more blocks remaining at this point, the file is larger than
// the current minfs max file size.
fprintf(stderr, "Error: File too large for minfs @ %" PRIu64 " bytes\n", file_size);
return ZX_ERR_INVALID_ARGS;
}
}
}
// Add calculated blocks to the total so far.
data_blocks_ += total_blocks;
return ZX_OK;
}
zx::status<std::unique_ptr<minfs::Bcache>> MinfsCreator::GenerateBcache() {
uint32_t block_count = static_cast<uint32_t>(GetLength() / minfs::kMinfsBlockSize);
// Duplicate the fd so that we can re-open the minfs partition if we need to.
int dupfd = dup(fd_.get());
auto bc_or = minfs::Bcache::Create(std::move(fd_), block_count);
if (bc_or.is_error()) {
fprintf(stderr, "error: cannot create block cache\n");
return zx::error(ZX_ERR_IO);
}
if (auto status = bc_or->SetOffset(GetOffset()); status.is_error()) {
return zx::error(ZX_ERR_BAD_STATE);
}
fd_.reset(dupfd);
return bc_or;
}
zx_status_t MinfsCreator::MountMinfs() {
if (emu_is_mounted()) {
// If we have already mounted minfs, do nothing.
return ZX_OK;
}
auto bc_or = GenerateBcache();
if (bc_or.is_error()) {
return bc_or.error_value();
}
return emu_mount_bcache(std::move(bc_or.value()));
}
int main(int argc, char** argv) {
MinfsCreator minfs;
if (minfs.ProcessAndRun(argc, argv) != ZX_OK) {
return -1;
}
return 0;
}