[blkctl] Add FVM commands
This CL adds the FVM function objects, allowing blkctl to create, query,
and destroy FVM volumes, and allocate, free, query, extend, and shrink
FVM partitions.
Change-Id: I6f053524ae5f77a91914d5c352dc29c2ba4a1de4
diff --git a/system/ulib/blkctl/blkctl.cpp b/system/ulib/blkctl/blkctl.cpp
index 41b5267..c62c8c2 100644
--- a/system/ulib/blkctl/blkctl.cpp
+++ b/system/ulib/blkctl/blkctl.cpp
@@ -16,6 +16,7 @@
#include <zircon/errors.h>
#include <zircon/types.h>
+#include "fvm.h"
#include "generic.h"
#include "ramdisk.h"
@@ -40,6 +41,7 @@
// 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),
// The generic commands should be last, so that various routines use them if no type matches
ADD_CMD_TYPE(generic),
};
diff --git a/system/ulib/blkctl/fvm.cpp b/system/ulib/blkctl/fvm.cpp
new file mode 100644
index 0000000..ef58ebd
--- /dev/null
+++ b/system/ulib/blkctl/fvm.cpp
@@ -0,0 +1,345 @@
+// 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 <fcntl.h>
+#include <inttypes.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#include <blkctl/blkctl.h>
+#include <blkctl/command.h>
+#include <fs-management/fvm.h>
+#include <fs-management/ramdisk.h>
+#include <fvm/fvm.h>
+#include <lib/zx/time.h>
+#include <zircon/device/block.h>
+#include <zircon/device/device.h>
+#include <zircon/errors.h>
+#include <zircon/status.h>
+#include <zircon/types.h>
+
+#include "fvm.h"
+#include "generic.h"
+
+namespace blkctl {
+namespace fvm {
+namespace {
+
+bool SupportsFvmQuery(int fd) {
+ if (fd < 0) {
+ return false;
+ }
+ fvm_info_t info;
+ memset(&info, 0, sizeof(info));
+ return ioctl_block_fvm_query(fd, &info) != ZX_ERR_NOT_SUPPORTED;
+}
+
+bool SupportsFvmVSliceQuery(int fd) {
+ if (fd < 0) {
+ return false;
+ }
+ query_request_t request;
+ query_response_t response;
+ memset(&request, 0, sizeof(request));
+ memset(&response, 0, sizeof(response));
+ return ioctl_block_fvm_vslice_query(fd, &request, &response) != ZX_ERR_NOT_SUPPORTED;
+}
+
+zx_status_t CheckFvm(int fd) {
+ if (!SupportsFvmQuery(fd) && !SupportsFvmVSliceQuery(fd)) {
+ fprintf(stderr, "device does not appear to be an FVM volume or partition\n");
+ return ZX_ERR_INVALID_ARGS;
+ }
+ return ZX_OK;
+}
+
+zx_status_t CheckFvmVolume(int fd) {
+ if (!SupportsFvmQuery(fd)) {
+ fprintf(stderr, "device does not appear to be an FVM volume\n");
+ return ZX_ERR_INVALID_ARGS;
+ }
+ return ZX_OK;
+}
+
+zx_status_t CheckFvmPartition(int fd) {
+ if (!SupportsFvmVSliceQuery(fd)) {
+ fprintf(stderr, "device does not appear to be an FVM partition\n");
+ return ZX_ERR_INVALID_ARGS;
+ }
+ return ZX_OK;
+}
+
+} // namespace
+
+zx_status_t Init::Run() {
+ zx_status_t rc;
+ ssize_t res;
+ BlkCtl* cmdline = this->cmdline();
+
+ const char* dev;
+ int fd;
+ size_t slice_size;
+ if ((rc = cmdline->GetStrArg("device", &dev)) != ZX_OK ||
+ (rc = cmdline->GetNumArg("slice_size", &slice_size)) != ZX_OK ||
+ (rc = cmdline->ArgsDone()) != ZX_OK || (rc = OpenReadable(dev, &fd)) != ZX_OK) {
+ return rc;
+ }
+ char path[PATH_MAX];
+ if ((res = ioctl_device_get_topo_path(fd, path, sizeof(path))) < 0) {
+ rc = static_cast<zx_status_t>(res);
+ fprintf(stderr, "failed to get topological path: %s\n", zx_status_get_string(rc));
+ return rc;
+ }
+ if ((rc = cmdline->Confirm()) != ZX_OK || (rc = ReopenWritable(&fd)) != ZX_OK) {
+ return rc;
+ }
+ if ((rc = fvm_init(fd, slice_size)) != ZX_OK) {
+ fprintf(stderr, "fvm_init failed: %s\n", zx_status_get_string(rc));
+ return rc;
+ }
+ if ((res = ioctl_device_bind(fd, kDriver, strlen(kDriver))) < 0) {
+ rc = static_cast<zx_status_t>(res);
+ fprintf(stderr, "could not bind fvm driver: %s\n", zx_status_get_string(rc));
+ return rc;
+ }
+ char name[PATH_MAX];
+ snprintf(name, sizeof(name), "%s/fvm", path);
+ if (wait_for_device(name, zx::sec(3).get()) != 0) {
+ fprintf(stderr, "timed out waiting for fvm driver to bind\n");
+ return ZX_ERR_TIMED_OUT;
+ }
+ printf("%s created\n", name);
+ return ZX_OK;
+}
+
+zx_status_t Dump::Run() {
+ zx_status_t rc;
+ ssize_t res;
+ BlkCtl* cmdline = this->cmdline();
+
+ const char* dev;
+ int fd;
+ fvm_info_t info;
+ if ((rc = cmdline->GetStrArg("device", &dev)) != ZX_OK || (rc = cmdline->ArgsDone()) != ZX_OK ||
+ (rc = OpenReadable(dev, &fd)) != ZX_OK || (rc = CheckFvm(fd)) != ZX_OK) {
+ return rc;
+ }
+ if ((res = ioctl_block_fvm_query(fd, &info)) < 0) {
+ rc = static_cast<zx_status_t>(res);
+ fprintf(stderr, "ioctl_block_fvm_query failed: %s\n", zx_status_get_string(rc));
+ return rc;
+ }
+ generic::Dump baseCmd(cmdline);
+ if ((rc = cmdline->UngetArgs(1)) != ZX_OK || (rc = baseCmd.Run()) != ZX_OK) {
+ return rc;
+ }
+ printf("%16s: %zu\n", "FVM slice size", info.slice_size);
+ printf("%16s: %zu\n", "FVM slice count", info.vslice_count);
+ return ZX_OK;
+}
+
+zx_status_t Add::Run() {
+ zx_status_t rc;
+ ssize_t res;
+ BlkCtl* cmdline = this->cmdline();
+
+ const char* dev;
+ int fd;
+ alloc_req_t request;
+ memset(&request, 0, sizeof(request));
+ const char* name;
+ if ((rc = cmdline->GetStrArg("device", &dev)) != ZX_OK ||
+ (rc = cmdline->GetStrArg("name", &name)) != ZX_OK ||
+ (rc = cmdline->GetNumArg("slices", &request.slice_count)) != ZX_OK) {
+ return rc;
+ }
+ const char* guid;
+
+ rc = cmdline->GetStrArg("guid", &guid, true /* optional */);
+
+ if (rc == ZX_ERR_NOT_FOUND) {
+ GenerateGuid(request.type, sizeof(request.type));
+ } else if (rc != ZX_OK || (rc = ParseGuid(guid, request.type, sizeof(request.type))) != ZX_OK) {
+ return rc;
+ }
+ if ((rc = cmdline->ArgsDone()) != ZX_OK ||
+ (rc = OpenReadable(dev, &fd)) != ZX_OK || (rc = CheckFvmVolume(fd)) != ZX_OK ||
+ (rc = cmdline->Confirm()) != ZX_OK || (rc = ReopenWritable(&fd)) != ZX_OK) {
+ return rc;
+ }
+
+ GenerateGuid(request.guid, sizeof(request.guid));
+ if ((res = ioctl_block_fvm_alloc_partition(fd, &request)) < 0) {
+ rc = static_cast<zx_status_t>(res);
+ fprintf(stderr, "ioctl_block_fvm_alloc_partition failed: %s\n", zx_status_get_string(rc));
+ return rc;
+ }
+ printf("added partition '%s' as ", name);
+ PrintGuid(request.guid, sizeof(request.guid));
+ printf("\n");
+ return ZX_OK;
+}
+
+zx_status_t Query::Run() {
+ zx_status_t rc;
+ ssize_t res;
+ BlkCtl* cmdline = this->cmdline();
+
+ const char* dev;
+ int fd;
+ if ((rc = cmdline->GetStrArg("device", &dev)) != ZX_OK || (rc = cmdline->ArgsDone()) != ZX_OK ||
+ (rc = OpenReadable(dev, &fd)) != ZX_OK || (rc = CheckFvmPartition(fd)) != ZX_OK) {
+ return rc;
+ }
+ fvm_info_t info;
+ if ((res = ioctl_block_fvm_query(fd, &info)) < 0) {
+ rc = static_cast<zx_status_t>(res);
+ fprintf(stderr, "ioctl_block_fvm_query failed: %s\n", zx_status_get_string(rc));
+ return rc;
+ }
+ query_request_t request;
+ query_response_t response;
+ request.count = 1;
+ printf("Allocated ranges:\n");
+ size_t end = 0;
+ for (size_t off = 0; off < info.vslice_count; off = end) {
+ request.vslice_start[0] = off;
+ if ((res = ioctl_block_fvm_vslice_query(fd, &request, &response)) < 0) {
+ rc = static_cast<zx_status_t>(res);
+ fprintf(stderr, "ioctl_block_fvm_vslice_query failed: %s\n", zx_status_get_string(rc));
+ return rc;
+ }
+ ZX_DEBUG_ASSERT(response.count == 1);
+ ZX_DEBUG_ASSERT(response.vslice_range[0].count != 0);
+ end = off + response.vslice_range[0].count;
+ if (response.vslice_range[0].allocated) {
+ printf(" <0x%016" PRIx64 ", 0x%016" PRIx64 ">: slices %zu through %zu\n",
+ static_cast<uint64_t>(off * info.slice_size),
+ static_cast<uint64_t>((end * info.slice_size) - 1), off, end);
+ }
+ }
+ return ZX_OK;
+}
+
+zx_status_t Extend::Run() {
+ zx_status_t rc;
+ ssize_t res;
+ BlkCtl* cmdline = this->cmdline();
+
+ const char* dev;
+ int fd;
+ extend_request_t request;
+ if ((rc = cmdline->GetStrArg("device", &dev)) != ZX_OK ||
+ (rc = cmdline->GetNumArg("start", &request.offset)) != ZX_OK ||
+ (rc = cmdline->GetNumArg("length", &request.length)) != ZX_OK ||
+ (rc = cmdline->ArgsDone()) != ZX_OK || (rc = OpenReadable(dev, &fd)) != ZX_OK ||
+ (rc = CheckFvmPartition(fd)) != ZX_OK) {
+ return rc;
+ }
+ if ((rc = cmdline->Confirm()) != ZX_OK || (rc = ReopenWritable(&fd)) != ZX_OK) {
+ return rc;
+ }
+ if ((res = ioctl_block_fvm_extend(fd, &request)) < 0) {
+ fprintf(stderr, "ioctl_block_fvm_extend failed: %s\n", zx_status_get_string(rc));
+ rc = static_cast<zx_status_t>(res);
+ return rc;
+ }
+ printf("partition extended\n");
+ return ZX_OK;
+}
+
+zx_status_t Shrink::Run() {
+ zx_status_t rc;
+ ssize_t res;
+ BlkCtl* cmdline = this->cmdline();
+
+ const char* dev;
+ int fd;
+ extend_request_t request;
+ if ((rc = cmdline->GetStrArg("device", &dev)) != ZX_OK ||
+ (rc = cmdline->GetNumArg("start", &request.offset)) != ZX_OK ||
+ (rc = cmdline->GetNumArg("length", &request.length)) != ZX_OK ||
+ (rc = cmdline->ArgsDone()) != ZX_OK || (rc = OpenReadable(dev, &fd)) != ZX_OK ||
+ (rc = CheckFvmPartition(fd)) != ZX_OK) {
+ return rc;
+ }
+ if ((rc = cmdline->Confirm()) != ZX_OK || (rc = ReopenWritable(&fd)) != ZX_OK) {
+ return rc;
+ }
+ if ((res = ioctl_block_fvm_shrink(fd, &request)) < 0) {
+ rc = static_cast<zx_status_t>(res);
+ fprintf(stderr, "ioctl_block_fvm_shrink failed: %s\n", zx_status_get_string(rc));
+ return rc;
+ }
+ printf("partition shrunk\n");
+ return ZX_OK;
+}
+
+zx_status_t Remove::Run() {
+ zx_status_t rc;
+ ssize_t res;
+ BlkCtl* cmdline = this->cmdline();
+
+ const char* dev;
+ int fd;
+ if ((rc = cmdline->GetStrArg("device", &dev)) != ZX_OK || (rc = cmdline->ArgsDone()) != ZX_OK ||
+ (rc = OpenReadable(dev, &fd)) != ZX_OK || (rc = CheckFvmPartition(fd)) != ZX_OK) {
+ return rc;
+ }
+ if ((rc = cmdline->Confirm()) != ZX_OK || (rc = ReopenWritable(&fd)) != ZX_OK) {
+ return rc;
+ }
+ if ((res = ioctl_block_fvm_destroy_partition(fd)) < 0) {
+ rc = static_cast<zx_status_t>(res);
+ fprintf(stderr, "ioctl_block_fvm_destroy_partition failed: %s\n", zx_status_get_string(rc));
+ return rc;
+ }
+ printf("partition removed\n");
+ return ZX_OK;
+}
+
+zx_status_t Destroy::Run() {
+ zx_status_t rc;
+ ssize_t res;
+ BlkCtl* cmdline = this->cmdline();
+
+ const char* dev;
+ int fd;
+ if ((rc = cmdline->GetStrArg("device", &dev)) != ZX_OK || (rc = cmdline->ArgsDone()) != ZX_OK ||
+ (rc = OpenReadable(dev, &fd)) != ZX_OK) {
+ return rc;
+ }
+ char path[PATH_MAX];
+ constexpr const char* suffix = "/fvm";
+ ZX_DEBUG_ASSERT(strlen(suffix) < sizeof(path));
+ size_t path_max = sizeof(path) - (strlen(suffix) + 1);
+ if ((res = ioctl_device_get_topo_path(fd, path, path_max)) < 0) {
+ rc = static_cast<zx_status_t>(res);
+ fprintf(stderr, "failed to get topological path: %s\n", zx_status_get_string(rc));
+ return rc;
+ }
+ strcat(path, suffix); // Safe to due to size limit above
+ fbl::unique_fd fvm_fd(open(path, O_RDONLY));
+ if ((rc = CheckFvmVolume(fvm_fd.get())) != ZX_OK) {
+ return rc;
+ }
+ fvm_fd.reset();
+ if ((rc = cmdline->Confirm()) != ZX_OK || (rc = ReopenWritable(&fd)) != ZX_OK) {
+ return rc;
+ }
+ if ((rc = fvm_destroy(devname())) != ZX_OK) {
+ fprintf(stderr, "fvm_destroy failed\n");
+ return rc;
+ }
+ if ((res = ioctl_block_rr_part(fd)) < 0) {
+ rc = static_cast<zx_status_t>(res);
+ fprintf(stderr, "failed to unbind FVM driver: %s\n", zx_status_get_string(rc));
+ return rc;
+ }
+ printf("FVM volume metadata destroyed\n");
+ return ZX_OK;
+}
+
+} // namespace fvm
+} // namespace blkctl
diff --git a/system/ulib/blkctl/fvm.h b/system/ulib/blkctl/fvm.h
new file mode 100644
index 0000000..2fc4aa6
--- /dev/null
+++ b/system/ulib/blkctl/fvm.h
@@ -0,0 +1,38 @@
+// 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.
+
+#pragma once
+
+#include <blkctl/command.h>
+
+namespace blkctl {
+namespace fvm {
+
+DEFINE_COMMAND(Init);
+DEFINE_COMMAND(Dump);
+DEFINE_COMMAND(Add);
+DEFINE_COMMAND(Query);
+DEFINE_COMMAND(Extend);
+DEFINE_COMMAND(Shrink);
+DEFINE_COMMAND(Remove);
+DEFINE_COMMAND(Destroy);
+
+constexpr const char* kType = "fvm";
+
+constexpr Cmd kCommands[] = {
+ {"init", "<device> <slice_size>", "Format a block device to be an empty FVM volume.", Instantiate<Init>},
+ {"dump", "<device>", "Dump block device and FVM volume information.", Instantiate<Dump>},
+ {"add", "<device> <name> <slices> [type-guid]", "Allocates a new partition in the FVM volume.", Instantiate<Add>},
+ {"query", "<device>", "List ranges of allocated slices.", Instantiate<Query>},
+ {"extend", "<device> <start> <length>", "Allocates slices for the partition.", Instantiate<Extend>},
+ {"shrink", "<device> <start> <length>", "Free slices from the partition.", Instantiate<Shrink>},
+ {"remove", "<device>", "Removes partition from the FVM volume.", Instantiate<Remove>},
+ {"destroy", "<device>", "Overwrites and unbinds an FVM volume.", Instantiate<Destroy>},
+};
+constexpr size_t kNumCommands = sizeof(kCommands) / sizeof(kCommands[0]);
+
+constexpr const char *kDriver = "/boot/driver/fvm.so";
+
+} // namespace fvm
+} // namespace blkctl
diff --git a/system/ulib/blkctl/rules.mk b/system/ulib/blkctl/rules.mk
index 2a61007..ab9d488 100644
--- a/system/ulib/blkctl/rules.mk
+++ b/system/ulib/blkctl/rules.mk
@@ -15,6 +15,7 @@
$(LOCAL_DIR)/command.cpp \
$(LOCAL_DIR)/generic.cpp \
$(LOCAL_DIR)/ramdisk.cpp \
+ $(LOCAL_DIR)/fvm.cpp \
MODULE_LIBS := \
system/ulib/c \
diff --git a/system/utest/blkctl/fvm.cpp b/system/utest/blkctl/fvm.cpp
new file mode 100644
index 0000000..7efc820
--- /dev/null
+++ b/system/utest/blkctl/fvm.cpp
@@ -0,0 +1,262 @@
+// 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 <limits.h>
+#include <stddef.h>
+
+#include <fbl/unique_ptr.h>
+#include <unittest/unittest.h>
+#include <zircon/types.h>
+
+#include "utils.h"
+
+namespace blkctl {
+namespace testing {
+namespace {
+
+bool TestBadCommand(void) {
+ BEGIN_TEST;
+
+ // Missing command
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm"));
+
+ // Gibberish
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm booplesnoot"));
+
+ END_TEST;
+}
+
+bool TestInitDestroy(void) {
+ BEGIN_TEST;
+ ScopedRamdisk ramdisk;
+ ScopedRamdisk nonfvm;
+ ASSERT_TRUE(ramdisk.Init());
+ ASSERT_TRUE(nonfvm.Init());
+
+ // Missing/bad device
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm init"));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm init booplesnoot"));
+
+ // Missing/bad slice size
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm init %s", ramdisk.path()));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm init %s foo", ramdisk.path()));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm init %s -1", ramdisk.path()));
+
+ // Too many args
+ EXPECT_TRUE(
+ ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm init %s %zu foo", ramdisk.path(), kSliceSize));
+
+ // Valid
+ EXPECT_TRUE(ParseAndRun(ZX_OK, "blkctl fvm init --force %s %zu", ramdisk.path(), kSliceSize));
+
+ // Missing/bad device
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm destroy"));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm destroy booplesnoot"));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm destroy %s", nonfvm.path()));
+
+ // Too many args
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm destroy %s foo", ramdisk.path()));
+
+ // Valid
+ EXPECT_TRUE(ParseAndRun(ZX_OK, "blkctl fvm destroy --force %s", ramdisk.path()));
+
+ END_TEST;
+}
+
+bool TestDump(void) {
+ BEGIN_TEST;
+ ScopedFvmPartition partition;
+ ASSERT_TRUE(partition.Init());
+ const ScopedFvmVolume& volume = partition.volume();
+ const ScopedRamdisk& ramdisk = volume.ramdisk();
+
+ // Missing/bad device
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm dump"));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm dump booplesnoot"));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm add %s", ramdisk.path()));
+
+ // Too many args
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm dump %s foo", volume.path()));
+
+ // Valid for volume
+ EXPECT_TRUE(ParseAndRun(ZX_OK, "blkctl fvm dump %s", volume.path()));
+
+ // Valid for partition
+ EXPECT_TRUE(ParseAndRun(ZX_OK, "blkctl fvm dump %s", partition.path()));
+
+ END_TEST;
+}
+
+bool TestAdd(void) {
+ BEGIN_TEST;
+ ScopedFvmVolume volume;
+ ASSERT_TRUE(volume.Init());
+ const ScopedRamdisk& ramdisk = volume.ramdisk();
+
+ // Missing device
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm add"));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm add booplesnoot"));
+
+ // Missing/bad name and/or slices
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm add %s", volume.path()));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm add %s foo", volume.path()));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm add %s foo bar", volume.path()));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm add %s foo -1", volume.path()));
+
+ // GUID is optional
+ EXPECT_TRUE(ParseAndRun(ZX_OK, "blkctl fvm add --force %s foo %zu", volume.path(),
+ volume.slices() / 2));
+
+ // Bad GUID
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS,
+ "blkctl fvm add %s bar %zu 00000000-0000-0000-0000000000000000",
+ volume.path(), volume.slices()));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS,
+ "blkctl fvm add %s bar %zu thisisno-thex-adec-imal-anditmustbe!",
+ volume.path(), volume.slices()));
+
+ // Too many args
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS,
+ "blkctl fvm add %s bar %zu deadbeef-dead-beef-dead-beefdeadbeef bar",
+ volume.path(), volume.slices()));
+
+ // Bad device
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm add %s bar %zu", ramdisk.path(),
+ volume.slices() / 2));
+
+ // Valid
+ EXPECT_TRUE(
+ ParseAndRun(ZX_OK, "blkctl fvm add --force %s bar %zu deadbeef-dead-beef-dead-beefdeadbeef",
+ volume.path(), volume.slices() / 2));
+
+ END_TEST;
+}
+
+bool TestQuery(void) {
+ BEGIN_TEST;
+ ScopedFvmPartition partition;
+ ASSERT_TRUE(partition.Init());
+ const ScopedFvmVolume& volume = partition.volume();
+
+ // Missing/bad device
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm query"));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm query booplesnoot"));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm query %s", volume.path()));
+
+ // Too many args
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm query %s foo", partition.path()));
+
+ // Valid
+ EXPECT_TRUE(ParseAndRun(ZX_OK, "blkctl fvm query %s", partition.path()));
+
+ END_TEST;
+}
+
+bool TestExtend(void) {
+ BEGIN_TEST;
+ ScopedFvmPartition partition;
+ ASSERT_TRUE(partition.Init());
+ const ScopedFvmVolume& volume = partition.volume();
+
+ // Missing device
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm extend"));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm extend booplesnoot"));
+
+ // Missing/bad start and/or length
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm extend %s", partition.path()));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm extend %s %zu", partition.path(),
+ partition.slices()));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm extend %s foo 1", partition.path()));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm extend %s -1 1", partition.path()));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm extend %s %zu foo", partition.path(),
+ partition.slices()));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm extend %s %zu -1", partition.path(),
+ partition.slices()));
+
+ // Too many args
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm extend %s %zu 1 foo", partition.path(),
+ partition.slices()));
+
+ // Bad device
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm extend %s %zu 1", volume.path(),
+ partition.slices()));
+
+ // Valid
+ EXPECT_TRUE(ParseAndRun(ZX_OK, "blkctl fvm extend --force %s %zu 1", partition.path(),
+ partition.slices()));
+
+ END_TEST;
+}
+
+bool TestShrink(void) {
+ BEGIN_TEST;
+ ScopedFvmPartition partition;
+ ASSERT_TRUE(partition.Init());
+ const ScopedFvmVolume& volume = partition.volume();
+
+ // Missing device
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm shrink"));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm shrink booplesnoot"));
+
+ // Missing/bad start and/or length
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm shrink %s", partition.path()));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm shrink %s %zu", partition.path(),
+ partition.slices() - 1));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm shrink %s foo 1", partition.path()));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm shrink %s -1 1", partition.path()));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm shrink %s %zu foo", partition.path(),
+ partition.slices() - 1));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm shrink %s %zu -1", partition.path(),
+ partition.slices() - 1));
+
+ // Too many args
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm shrink %s %zu 1 foo", partition.path(),
+ partition.slices() - 1));
+
+ // Bad device
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm shrink %s %zu 1", volume.path(),
+ partition.slices() - 1));
+
+ // Valid
+ EXPECT_TRUE(ParseAndRun(ZX_OK, "blkctl fvm shrink --force %s %zu 1", partition.path(),
+ partition.slices() - 1));
+
+ END_TEST;
+}
+
+bool TestRemove(void) {
+ BEGIN_TEST;
+ ScopedFvmPartition partition;
+ ASSERT_TRUE(partition.Init());
+ const ScopedFvmVolume& volume = partition.volume();
+
+ // Missing device
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm remove"));
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm remove booplesnoot"));
+
+ // Too many args
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm remove %s foo", partition.path()));
+
+ // Bad device
+ EXPECT_TRUE(ParseAndRun(ZX_ERR_INVALID_ARGS, "blkctl fvm remove %s", volume.path()));
+
+ // Valid
+ EXPECT_TRUE(ParseAndRun(ZX_OK, "blkctl fvm remove --force %s", partition.path()));
+
+ END_TEST;
+}
+
+BEGIN_TEST_CASE(FvmCommandTest)
+RUN_TEST(TestInitDestroy)
+RUN_TEST(TestDump)
+RUN_TEST(TestAdd)
+RUN_TEST(TestQuery)
+RUN_TEST(TestExtend)
+RUN_TEST(TestShrink)
+RUN_TEST(TestRemove)
+END_TEST_CASE(FvmCommandTest)
+
+} // namespace
+} // namespace testing
+} // namespace blkctl
diff --git a/system/utest/blkctl/rules.mk b/system/utest/blkctl/rules.mk
index fcaed07..bd148f2 100644
--- a/system/utest/blkctl/rules.mk
+++ b/system/utest/blkctl/rules.mk
@@ -19,6 +19,7 @@
MODULE_SRCS += \
$(LOCAL_DIR)/command.cpp \
$(LOCAL_DIR)/ramdisk.cpp \
+ $(LOCAL_DIR)/fvm.cpp \
MODULE_LIBS := \
system/ulib/blkctl \