blob: aa5d067fad3e2cb20e3ee465a4364da5cf383254 [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 <ctype.h>
#include <inttypes.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <blkctl/blkctl.h>
#include <blkctl/command.h>
#include <fbl/string.h>
#include <fbl/unique_ptr.h>
#include <fbl/vector.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include "fvm.h"
#include "generic.h"
#include "ramdisk.h"
#include "zxcrypt.h"
namespace blkctl {
namespace {
#define ADD_CMD_TYPE(T) \
{ T::kType, T::kCommands, T::kNumCommands }
struct CmdType {
const char* type;
const Cmd* cmds;
size_t num;
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// *** LOOK HERE FIRST! ***
//
// This associates types with subclasses. To add new block device types, implement a subclass of
// Command that includes:
// - A constructor that takes an r-value reference to a Command
// - An array (and length) of CommandImpls that describe individual commands and their arguments.
// Then simply #include the appropriate header and add a DEVICE_TYPE to the list below.
constexpr CmdType kTypes[] = {
ADD_CMD_TYPE(ramdisk),
ADD_CMD_TYPE(fvm),
ADD_CMD_TYPE(zxcrypt),
// The generic commands should be last, so that various routines use them if no type matches
ADD_CMD_TYPE(generic),
};
////////////////////////////////////////////////////////////////////////////////////////////////////
constexpr size_t kNumTypes = (sizeof(kTypes) / sizeof(kTypes[0]));
// This option skips confirmation
const char* kForce = "--force";
// The column widths when print usage and/or help messages.
constexpr size_t kLineLen = 80;
} // namespace
// Public methods
void BlkCtl::Usage() const {
printf("Usage: %s [%s] [<type>] <command> [args...]\n", binname_.c_str(), kForce);
printf("\nOptions:\n");
printf(" %8s %64s\n", kForce, "Skips confirmation prompts; be careful!");
printf("\nTypes and Commands:\n");
printf(" In the commands below, <device> may refer to a path in the device tree, or an\n");
printf(" ID under %s.\n\n", Command::kDevClassBlock);
for (size_t i = 0; i < kNumTypes; ++i) {
for (size_t j = 0; j < kTypes[i].num; ++j) {
const Cmd* cmd = &kTypes[i].cmds[j];
printf("> %s ", binname_.c_str());
if (kTypes[i].type) {
printf("%s ", kTypes[i].type);
}
printf("%s %s\n", cmd->name ? cmd->name : "", cmd->args ? cmd->args : "");
char buf[kLineLen - 4];
const char* p = cmd->help;
char* q;
while (true) {
ssize_t n = snprintf(buf, sizeof(buf), "%s", p);
ZX_DEBUG_ASSERT(n >= 0);
if (static_cast<size_t>(n) < sizeof(buf)) {
break;
}
if (!(q = strrchr(buf, ' '))) {
p += sizeof(buf);
} else {
*q = '\0';
p += q - buf + 1;
}
printf(" %s\n", buf);
}
printf(" %s\n\n", buf);
}
}
}
zx_status_t BlkCtl::Execute(int argc, char** argv) {
zx_status_t rc;
BlkCtl tmp;
if ((rc = tmp.Parse(argc, argv)) != ZX_OK) {
return rc;
}
Command* cmd = tmp.cmd();
return cmd->Run();
}
zx_status_t BlkCtl::Parse(int argc, char** argv, const char* canned) {
zx_status_t rc;
if (argc == 0 || !argv) {
fprintf(stderr, "bad arguments: argc=%d, argv=%p\n", argc, argv);
return ZX_ERR_INVALID_ARGS;
}
// Reset the internal state of the command line
force_ = false;
argn_ = 0;
canned_ = canned;
// Consume binname
fbl::AllocChecker ac;
binname_.Set(argv[0], &ac);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
argv = &argv[1];
--argc;
// Consume force_ option, if present
for (int i = 0; i < argc; ++i) {
if (force_) {
argv[i - 1] = argv[i];
} else if (strcmp(argv[i], kForce) == 0) {
force_ = true;
}
}
if (force_) {
--argc;
}
args_.reset();
// Consume type, if present. Ignore the "generic" type.
size_t type;
for (type = 0; argc != 0 && type < kNumTypes - 1; ++type) {
if (strcmp(argv[0], kTypes[type].type) == 0) {
argv = &argv[1];
--argc;
break;
}
}
// Consume command
if (argc == 0) {
fprintf(stderr, "missing command\n");
return ZX_ERR_INVALID_ARGS;
}
const char* name = argv[0];
argv = &argv[1];
--argc;
// Consume arguments
for (int i = 0; i < argc; ++i) {
fbl::String arg(argv[i], &ac);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
args_.push_back(arg, &ac);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
}
// Build a specific command
rc = ZX_ERR_INVALID_ARGS;
for (size_t i = 0; i < kTypes[type].num; ++i) {
const Cmd* cmd = &kTypes[type].cmds[i];
if (strcmp(name, cmd->name) == 0) {
rc = cmd->Instantiate(this, &cmd_);
break;
}
}
if (rc != ZX_OK) {
fprintf(stderr, "unrecognized command: %s\n", name);
return rc;
}
return ZX_OK;
}
zx_status_t BlkCtl::GetNumArg(const char* argname, uint64_t* out, bool optional) {
zx_status_t rc;
const char* arg;
if ((rc = GetStrArg(argname, &arg, optional)) != ZX_OK) {
return rc;
}
char* endptr = nullptr;
int64_t tmp = strtoll(arg, &endptr, 0);
if (*endptr != '\0') {
fprintf(stderr, "non-numeric value for '%s': %s\n", argname, arg);
return ZX_ERR_INVALID_ARGS;
}
if (tmp < 0) {
fprintf(stderr, "negative value for '%s': %" PRId64 "\n", argname, tmp);
return ZX_ERR_INVALID_ARGS;
}
*out = static_cast<uint64_t>(tmp);
return ZX_OK;
}
zx_status_t BlkCtl::GetStrArg(const char* argname, const char** out, bool optional) {
if (argn_ < args_.size()) {
*out = args_[argn_].c_str();
++argn_;
return ZX_OK;
}
if (optional) {
return ZX_ERR_NOT_FOUND;
}
fprintf(stderr, "missing required argument: %s\n", argname);
return ZX_ERR_INVALID_ARGS;
}
zx_status_t BlkCtl::UngetArgs(size_t n) {
argn_ = argn_ < n ? 0 : argn_ - n;
return ZX_OK;
}
zx_status_t BlkCtl::ArgsDone() const {
if (argn_ < args_.size()) {
fprintf(stderr, "too many arguments\n");
return ZX_ERR_INVALID_ARGS;
}
return ZX_OK;
}
zx_status_t BlkCtl::Confirm() const {
if (force_) {
return ZX_OK;
}
printf("About to commit changes to disk. Are you sure? [y/N] ");
fflush(stdout);
int c = fgetc(stdin);
printf("\n");
switch (c) {
case 'y':
case 'Y':
return ZX_OK;
default:
return ZX_ERR_CANCELED;
}
}
zx_status_t BlkCtl::Prompt(const char* prompt, char* s, size_t n) {
if (!canned_) {
printf("Enter %s: ", prompt);
fflush(stdout);
}
for (size_t i = 0; i < n;) {
int c = canned_ ? *canned_++ : fgetc(stdin);
if (c == 0x7f && i != 0) {
printf("\x1b[D\x1b[K");
fflush(stdout);
--i;
} else if (c == '\0' || c == '\n' || c == '\r') {
s[i] = '\0';
printf("\n");
break;
} else if (!iscntrl(c)) {
printf("%c", c);
fflush(stdout);
s[i] = static_cast<char>(c);
++i;
}
}
return ZX_OK;
}
} // namespace blkctl