blob: 709a56599cb0ef203b863feddc67a246e6cb7e71 [file] [log] [blame]
// Copyright 2020 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 "src/storage/fshost/block-device-manager.h"
#include <fuchsia/device/llcpp/fidl.h>
#include <lib/fdio/cpp/caller.h>
#include <zircon/device/block.h>
#include <zircon/hw/gpt.h>
#include <fs-management/format.h>
namespace devmgr {
namespace {
// Splits the path into a directory and the last component.
std::pair<std::string_view, std::string_view> SplitPath(std::string_view path) {
size_t separator = path.rfind('/');
if (separator != std::string::npos) {
return std::make_pair(path.substr(0, separator), path.substr(separator + 1));
} else {
return std::make_pair(std::string_view(), path);
}
}
// Matches anything that appears to have the given content and keeps track of the first device it
// finds.
class ContentMatcher : public BlockDeviceManager::Matcher {
public:
ContentMatcher(disk_format_t format) : format_(format) {}
disk_format_t Match(const BlockDeviceInterface& device) override {
if (device.content_format() == format_) {
return format_;
} else {
return DISK_FORMAT_UNKNOWN;
}
}
zx_status_t Add(BlockDeviceInterface& device) override {
zx_status_t status = device.Add();
if (status != ZX_OK) {
return status;
}
if (path_.empty()) {
path_ = device.topological_path();
}
return ZX_OK;
}
const std::string& path() const { return path_; }
private:
const disk_format_t format_;
std::string path_;
};
// Matches devices that handle groups of partitions.
class PartitionMapMatcher : public ContentMatcher {
public:
// |suffix| is a device that is expected to appear when the driver is bound. For example, FVM,
// will add a "/fvm" device before adding children whilst GPT won't add anything. If
// |ramdisk_required| is set, this matcher will only match against a ram-disk.
PartitionMapMatcher(disk_format_t format, std::string_view suffix, bool ramdisk_required)
: ContentMatcher(format), suffix_(suffix), ramdisk_required_(ramdisk_required) {}
bool ramdisk_required() const { return ramdisk_required_; }
disk_format_t Match(const BlockDeviceInterface& device) override {
constexpr std::string_view kRamdiskPrefix = "/dev/misc/ramctl/";
if (ramdisk_required_ &&
device.topological_path().compare(0, kRamdiskPrefix.length(), kRamdiskPrefix) != 0) {
return DISK_FORMAT_UNKNOWN;
}
return ContentMatcher::Match(device);
}
// Returns true if |device| is a child of the device matched by this matcher.
bool IsChild(const BlockDeviceInterface& device) const {
if (path().empty()) {
return false;
}
// Child partitions should have topological paths of the form:
// .../<suffix>/<partition-name>/block
auto [dir1, base1] = SplitPath(device.topological_path());
if (base1 != "block") {
return false;
}
auto [dir2, base2] = SplitPath(dir1);
// base should be something like <partition-name>-p-1, but we ignore that.
return path() + suffix_ == dir2;
}
private:
const std::string suffix_;
const bool ramdisk_required_;
};
// Matches a partition with a given name and expected type GUID.
class SimpleMatcher : public BlockDeviceManager::Matcher {
public:
SimpleMatcher(PartitionMapMatcher& map, std::string partition_name,
const fuchsia_hardware_block_partition_GUID& type_guid, disk_format_t format)
: map_(map), partition_name_(partition_name), type_guid_(type_guid), format_(format) {}
disk_format_t Match(const BlockDeviceInterface& device) override {
if (map_.IsChild(device) && device.partition_name() == partition_name_ &&
!memcmp(&device.GetTypeGuid(), &type_guid_, sizeof(type_guid_))) {
return format_;
} else {
return DISK_FORMAT_UNKNOWN;
}
}
private:
const PartitionMapMatcher& map_;
const std::string partition_name_;
const fuchsia_hardware_block_partition_GUID type_guid_;
const disk_format_t format_;
};
// Matches a data partition, which is a Minfs partition backed by zxcrypt.
class MinfsMatcher : public BlockDeviceManager::Matcher {
public:
using PartitionNames = std::set<std::string, std::less<>>;
enum class Variant {
// A regular minfs partition backed by zxcrypt.
kNormal,
// A minfs partition not backed by zxcrypt.
kNoZxcrypt,
// Only attach and unseal the zxcrypt partition; doesn't mount minfs.
kZxcryptOnly
};
static constexpr std::string_view kZxcryptSuffix = "/zxcrypt/unsealed/block";
MinfsMatcher(const PartitionMapMatcher& map, PartitionNames partition_names,
const fuchsia_hardware_block_partition_GUID& type_guid, Variant variant)
: map_(map),
partition_names_(std::move(partition_names)),
type_guid_(type_guid),
variant_(variant) {}
static Variant GetVariantFromOptions(const BlockDeviceManager::Options& options) {
if (options.is_set(BlockDeviceManager::Options::kNoZxcrypt)) {
return Variant::kNoZxcrypt;
} else {
return Variant::kNormal;
}
}
disk_format_t Match(const BlockDeviceInterface& device) override {
if (expected_inner_path_.empty()) {
if (map_.IsChild(device) &&
partition_names_.find(device.partition_name()) != partition_names_.end() &&
!memcmp(&device.GetTypeGuid(), &type_guid_, sizeof(type_guid_))) {
switch (variant_) {
case Variant::kNormal:
return map_.ramdisk_required() ? DISK_FORMAT_MINFS : DISK_FORMAT_ZXCRYPT;
case Variant::kNoZxcrypt:
return DISK_FORMAT_MINFS;
case Variant::kZxcryptOnly:
return DISK_FORMAT_ZXCRYPT;
}
}
} else if (variant_ == Variant::kNormal && device.topological_path() == expected_inner_path_ &&
!memcmp(&device.GetTypeGuid(), &type_guid_, sizeof(type_guid_))) {
return DISK_FORMAT_MINFS;
}
return DISK_FORMAT_UNKNOWN;
}
zx_status_t Add(BlockDeviceInterface& device) override {
// If the volume doesn't appear to be zxcrypt, assume that it's because it was never formatted
// as such, or the keys have been shredded, so skip straight to reformatting. Strictly
// speaking, it's not necessary, because attempting to unseal should trigger the same behaviour,
// but the log messages in that case are scary.
if (device.GetFormat() == DISK_FORMAT_ZXCRYPT) {
if (device.content_format() != DISK_FORMAT_ZXCRYPT) {
printf("fshost: Formatting as zxcrypt partition\n");
zx_status_t status = device.FormatZxcrypt();
if (status != ZX_OK) {
return status;
}
// Set the reformat_ flag so that when the Minfs device appears we can skip straight to
// reformatting it (and skip any fsck). Again, this isn't strictly required because
// mounting should fail and we'll reformat, but we can skip that when we know we need to
// reformat.
reformat_ = true;
}
} else if (reformat_) {
// We formatted zxcrypt, so skip straight to formatting minfs.
zx_status_t status = device.FormatFilesystem();
if (status != ZX_OK) {
return status;
}
reformat_ = false;
}
zx_status_t status = device.Add();
if (status != ZX_OK) {
return status;
}
if (device.GetFormat() == DISK_FORMAT_ZXCRYPT) {
expected_inner_path_ = device.topological_path();
expected_inner_path_.append(kZxcryptSuffix);
}
return ZX_OK;
}
private:
const PartitionMapMatcher& map_;
const PartitionNames partition_names_;
const fuchsia_hardware_block_partition_GUID type_guid_;
const Variant variant_;
std::string expected_inner_path_;
// If we reformat the zxcrypt device, this flag is set so that we know we should reformat the
// minfs device when it appears.
bool reformat_ = false;
};
// Matches the factory partition.
class FactoryfsMatcher : public BlockDeviceManager::Matcher {
public:
static constexpr std::string_view kVerityMutableSuffix = "/verity/mutable/block";
static constexpr std::string_view kVerityVerifiedSuffix = "/verity/verified/block";
FactoryfsMatcher(const PartitionMapMatcher& map) : map_(map) {}
disk_format_t Match(const BlockDeviceInterface& device) override {
static constexpr fuchsia_hardware_block_partition_GUID factory_type_guid =
GPT_FACTORY_TYPE_GUID;
if (base_path_.empty()) {
if (map_.IsChild(device) &&
!memcmp(&device.GetTypeGuid(), &factory_type_guid, sizeof(factory_type_guid)) &&
device.partition_name() == "factory") {
return DISK_FORMAT_BLOCK_VERITY;
}
} else if (!memcmp(&device.GetTypeGuid(), &factory_type_guid, sizeof(factory_type_guid)) &&
(device.topological_path() == std::string(base_path_).append(kVerityMutableSuffix) ||
device.topological_path() ==
std::string(base_path_).append(kVerityVerifiedSuffix))) {
return DISK_FORMAT_FACTORYFS;
}
return DISK_FORMAT_UNKNOWN;
}
zx_status_t Add(BlockDeviceInterface& device) override {
zx_status_t status = device.Add();
if (status != ZX_OK) {
return status;
}
base_path_ = device.topological_path();
return ZX_OK;
}
private:
const PartitionMapMatcher& map_;
std::string base_path_;
};
// Matches devices that report flags with BLOCK_FLAG_BOOTPART set.
class BootpartMatcher : public BlockDeviceManager::Matcher {
public:
disk_format_t Match(const BlockDeviceInterface& device) override {
fuchsia_hardware_block_BlockInfo info;
zx_status_t status = device.GetInfo(&info);
if (status != ZX_OK) {
return DISK_FORMAT_UNKNOWN;
}
return info.flags & BLOCK_FLAG_BOOTPART ? DISK_FORMAT_BOOTPART : DISK_FORMAT_UNKNOWN;
}
};
MinfsMatcher::PartitionNames GetMinfsPartitionNames() { return {"minfs", GUID_DATA_NAME, "data"}; }
} // namespace
BlockDeviceManager::Options BlockDeviceManager::ReadOptions(std::istream& stream) {
std::set<std::string, std::less<>> options;
for (std::string line; std::getline(stream, line);) {
if (line == Options::kDefault) {
auto default_options = DefaultOptions();
options.insert(default_options.options.begin(), default_options.options.end());
}
if (line.size() > 0) {
if (line[0] == '-') {
options.erase(line.substr(1));
} else if (line[0] == '#') {
// Treat as comment; ignore.
} else {
options.emplace(std::move(line));
}
}
}
return {.options = std::move(options)};
}
BlockDeviceManager::Options BlockDeviceManager::DefaultOptions() {
return {.options = {Options::kBlobfs, Options::kBootpart, Options::kDurable, Options::kFactory,
Options::kFvm, Options::kGpt, Options::kMinfs}};
}
BlockDeviceManager::BlockDeviceManager(const Options& options) {
static constexpr fuchsia_hardware_block_partition_GUID minfs_type_guid = GUID_DATA_VALUE;
if (options.is_set(Options::kBootpart)) {
matchers_.push_back(std::make_unique<BootpartMatcher>());
}
auto gpt = std::make_unique<PartitionMapMatcher>(DISK_FORMAT_GPT, "", /*ramdisk_required=*/false);
auto fvm = std::make_unique<PartitionMapMatcher>(DISK_FORMAT_FVM, "/fvm",
options.is_set(Options::kFvmRamdisk));
bool gpt_required = options.is_set(Options::kGpt);
bool fvm_required = options.is_set(Options::kFvm);
if (!options.is_set(Options::kNetboot)) {
// GPT partitions:
if (options.is_set(Options::kDurable)) {
static constexpr fuchsia_hardware_block_partition_GUID durable_type_guid =
GPT_DURABLE_TYPE_GUID;
matchers_.push_back(std::make_unique<MinfsMatcher>(
*gpt, MinfsMatcher::PartitionNames{GPT_DURABLE_NAME}, durable_type_guid,
MinfsMatcher::GetVariantFromOptions(options)));
gpt_required = true;
}
if (options.is_set(Options::kFactory)) {
matchers_.push_back(std::make_unique<FactoryfsMatcher>(*gpt));
gpt_required = true;
}
// FVM partitions:
if (options.is_set(Options::kBlobfs)) {
static constexpr fuchsia_hardware_block_partition_GUID blobfs_type_guid = GUID_BLOB_VALUE;
matchers_.push_back(
std::make_unique<SimpleMatcher>(*fvm, "blobfs", blobfs_type_guid, DISK_FORMAT_BLOBFS));
fvm_required = true;
}
if (options.is_set(Options::kMinfs)) {
matchers_.push_back(
std::make_unique<MinfsMatcher>(*fvm, GetMinfsPartitionNames(), minfs_type_guid,
MinfsMatcher::GetVariantFromOptions(options)));
fvm_required = true;
}
}
// The partition map matchers go last because they match on content.
if (fvm_required) {
std::unique_ptr<PartitionMapMatcher> non_ramdisk_fvm;
if (options.is_set(Options::kFvmRamdisk)) {
// Add another matcher for the non-ramdisk version of FVM.
non_ramdisk_fvm = std::make_unique<PartitionMapMatcher>(DISK_FORMAT_FVM, "/fvm",
/*ramdisk_required=*/false);
if (options.is_set(Options::kAttachZxcryptToNonRamdisk)) {
matchers_.push_back(
std::make_unique<MinfsMatcher>(*non_ramdisk_fvm, GetMinfsPartitionNames(),
minfs_type_guid, MinfsMatcher::Variant::kZxcryptOnly));
}
}
matchers_.push_back(std::move(fvm));
if (non_ramdisk_fvm) {
matchers_.push_back(std::move(non_ramdisk_fvm));
}
}
if (gpt_required) {
matchers_.push_back(std::move(gpt));
}
if (options.is_set(Options::kMbr)) {
matchers_.push_back(
std::make_unique<PartitionMapMatcher>(DISK_FORMAT_MBR, "", /*ramdisk_required=*/false));
}
}
zx_status_t BlockDeviceManager::AddDevice(BlockDeviceInterface& device) {
if (device.topological_path().empty()) {
return ZX_ERR_NOT_SUPPORTED;
}
for (auto& matcher : matchers_) {
disk_format_t format = matcher->Match(device);
if (format != DISK_FORMAT_UNKNOWN) {
device.SetFormat(format);
return matcher->Add(device);
}
}
return ZX_ERR_NOT_SUPPORTED;
}
} // namespace devmgr