blob: 3addacbffa11edbac24dc6807ccefa44a4bdf51e [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// There are several methods to use lvm2 constructs from C or C++ code:
// - liblvm2app (deprecated) is a natural interface to creating and using
// lvm2 construct objects and performing operations on them. However, the
// deprecation of this library renders its use a non-starter.
// - executing the command line utilities directly.
// - liblvm2cmd provides an interface to run lvm2 commands without the
// indirection of another process execution. While this is faster, the output
// collection mechanism for liblvm2cmd relies on overriding the logging
// function.
// - lvmdbusd is a daemon (written in Python) with a D-Bus interface which
// exec()s the relevant commands. However, this library is additionally
// intended to be used in situations where D-Bus may or may not be running.
//
// To strike a balance between speed and usability, the following class uses
// liblvm2cmd for commands without output (eg. pvcreate, vgcreate ...) and
// uses a process invocation for the rest.
#include "brillo/blkdev_utils/lvm.h"
#include <base/json/json_reader.h>
namespace brillo {
namespace {
// LVM reports are structured as:
// {
// "report": [
// {
// "lv": [
// {"lv_name":"foo", "vg_name":"bar", ...},
// {...}
// ]
// }
// ]
// }
//
// Common function to fetch the underlying dictionary (assume for now
// that the reports will be reporting just a single type (lv/vg/pv) for now).
base::Optional<base::Value> UnwrapReportContents(const std::string& output,
const std::string& key) {
auto report = base::JSONReader::Read(output);
base::DictionaryValue* dictionary_report;
if (!report || !report->is_dict() ||
!report->GetAsDictionary(&dictionary_report)) {
LOG(ERROR) << "Failed to get report as dictionary";
return base::nullopt;
}
base::ListValue* report_list;
if (!dictionary_report->GetList("report", &report_list)) {
LOG(ERROR) << "Failed to find 'report' list";
return base::nullopt;
}
if (report_list->GetSize() != 1) {
LOG(ERROR) << "Unexpected size: " << report_list->GetSize();
return base::nullopt;
}
base::DictionaryValue* report_dictionary;
if (!report_list->GetDictionary(0, &report_dictionary)) {
LOG(ERROR) << "Failed to find 'report' dictionary";
return base::nullopt;
}
base::ListValue* key_list;
if (!report_dictionary->GetList(key, &key_list)) {
LOG(ERROR) << "Failed to find " << key << " list";
return base::nullopt;
}
// If the list has just a single dictionary element, return it directly.
if (key_list && key_list->GetSize() == 1) {
base::DictionaryValue* key_dictionary;
if (!key_list->GetDictionary(0, &key_dictionary)) {
LOG(ERROR) << "Failed to get " << key << " dictionary";
return base::nullopt;
}
return key_dictionary->Clone();
}
return key_list->Clone();
}
} // namespace
LogicalVolumeManager::LogicalVolumeManager()
: LogicalVolumeManager(std::make_shared<LvmCommandRunner>()) {}
LogicalVolumeManager::LogicalVolumeManager(
std::shared_ptr<LvmCommandRunner> lvm)
: lvm_(lvm) {}
bool LogicalVolumeManager::ValidatePhysicalVolume(
const base::FilePath& device_path, std::string* volume_group_name) {
std::string output;
if (!lvm_->RunProcess({"/sbin/pvdisplay", "-C", "--reportformat", "json",
device_path.value()},
&output)) {
LOG(ERROR) << "Failed to get output from pvdisplay";
return false;
}
base::Optional<base::Value> report_contents =
UnwrapReportContents(output, "pv");
base::DictionaryValue* pv_dictionary;
if (!report_contents || !report_contents->GetAsDictionary(&pv_dictionary)) {
LOG(ERROR) << "Failed to get report contents";
return false;
}
std::string pv_name;
if (!pv_dictionary->GetString("pv_name", &pv_name) &&
pv_name != device_path.value()) {
LOG(ERROR) << "Mismatched value: expected: " << device_path
<< " actual: " << pv_name;
return false;
}
if (volume_group_name) {
if (!pv_dictionary->GetString("vg_name", volume_group_name)) {
LOG(ERROR) << "Failed to fetch volume group name";
return false;
}
}
return true;
}
base::Optional<PhysicalVolume> LogicalVolumeManager::GetPhysicalVolume(
const base::FilePath& device_path) {
return ValidatePhysicalVolume(device_path, nullptr)
? base::make_optional(PhysicalVolume(device_path, lvm_))
: base::nullopt;
}
base::Optional<VolumeGroup> LogicalVolumeManager::GetVolumeGroup(
const PhysicalVolume& pv) {
std::string vg_name;
return ValidatePhysicalVolume(pv.GetPath(), &vg_name)
? base::make_optional(VolumeGroup(vg_name, lvm_))
: base::nullopt;
}
bool LogicalVolumeManager::ValidateLogicalVolume(const VolumeGroup& vg,
const std::string& lv_name,
bool is_thinpool) {
std::string output;
const std::string vg_name = vg.GetName();
std::string pool_lv_check = is_thinpool ? "pool_lv=\"\"" : "pool_lv!=\"\"";
if (!lvm_->RunProcess({"/sbin/lvdisplay", "-S", pool_lv_check, "-C",
"--reportformat", "json", vg_name + "/" + lv_name},
&output)) {
LOG(ERROR) << "Failed to get output from lvdisplay";
return false;
}
base::Optional<base::Value> report_contents =
UnwrapReportContents(output, "lv");
base::DictionaryValue* lv_dictionary;
if (!report_contents || !report_contents->GetAsDictionary(&lv_dictionary)) {
LOG(ERROR) << "Failed to get report contents";
return false;
}
std::string output_lv_name;
if (!lv_dictionary->GetString("lv_name", &output_lv_name) &&
output_lv_name != lv_name) {
LOG(ERROR) << "Mismatched value: expected: " << lv_name
<< " actual: " << output_lv_name;
return false;
}
return true;
}
base::Optional<Thinpool> LogicalVolumeManager::GetThinpool(
const VolumeGroup& vg, const std::string& thinpool_name) {
return ValidateLogicalVolume(vg, thinpool_name, true /* is_thinpool */)
? base::make_optional(Thinpool(thinpool_name, vg.GetName(), lvm_))
: base::nullopt;
}
base::Optional<LogicalVolume> LogicalVolumeManager::GetLogicalVolume(
const VolumeGroup& vg, const std::string& lv_name) {
return ValidateLogicalVolume(vg, lv_name, false /* is_thinpool */)
? base::make_optional(LogicalVolume(lv_name, vg.GetName(), lvm_))
: base::nullopt;
}
std::vector<LogicalVolume> LogicalVolumeManager::ListLogicalVolumes(
const VolumeGroup& vg) {
std::string output;
std::string vg_name = vg.GetName();
std::vector<LogicalVolume> lv_vector;
if (!lvm_->RunProcess({"/sbin/lvdisplay", "-S", "pool_lv!=\"\"", "-C",
"--reportformat", "json", vg_name},
&output)) {
LOG(ERROR) << "Failed to get output from lvdisplay";
return lv_vector;
}
base::Optional<base::Value> report_contents =
UnwrapReportContents(output, "lv");
base::ListValue* lv_list;
if (!report_contents || !report_contents->GetAsList(&lv_list)) {
LOG(ERROR) << "Failed to get report contents";
return lv_vector;
}
for (size_t i = 0; i < lv_list->GetSize(); i++) {
base::DictionaryValue* lv_dictionary;
if (!lv_list->GetDictionary(i, &lv_dictionary)) {
LOG(ERROR) << "Failed to get dictionary value for physical volume";
continue;
}
std::string output_lv_name;
if (!lv_dictionary->GetString("lv_name", &output_lv_name)) {
LOG(ERROR) << "Failed to get logical volume name";
continue;
}
lv_vector.push_back(LogicalVolume(output_lv_name, vg_name, lvm_));
}
return lv_vector;
}
base::Optional<PhysicalVolume> LogicalVolumeManager::CreatePhysicalVolume(
const base::FilePath& device_path) {
return lvm_->RunCommand({"pvcreate", "-ff", "--yes", device_path.value()})
? base::make_optional(PhysicalVolume(device_path, lvm_))
: base::nullopt;
}
base::Optional<VolumeGroup> LogicalVolumeManager::CreateVolumeGroup(
const PhysicalVolume& pv, const std::string& vg_name) {
return lvm_->RunCommand(
{"vgcreate", "-p", "1", vg_name, pv.GetPath().value()})
? base::make_optional(VolumeGroup(vg_name, lvm_))
: base::nullopt;
}
base::Optional<Thinpool> LogicalVolumeManager::CreateThinpool(
const VolumeGroup& vg, const base::DictionaryValue& config) {
std::vector<std::string> cmd = {"lvcreate"};
std::string size, metadata_size, name;
if (!config.GetString("size", &size) || !config.GetString("name", &name) ||
!config.GetString("metadata_size", &metadata_size)) {
LOG(ERROR) << "Invalid configuration";
return base::nullopt;
}
cmd.insert(cmd.end(),
{"--size", size + "M", "--poolmetadatasize", metadata_size + "M",
"--thinpool", name, vg.GetName()});
return lvm_->RunCommand(cmd)
? base::make_optional(Thinpool(name, vg.GetName(), lvm_))
: base::nullopt;
}
base::Optional<LogicalVolume> LogicalVolumeManager::CreateLogicalVolume(
const VolumeGroup& vg,
const Thinpool& thinpool,
const base::DictionaryValue& config) {
std::vector<std::string> cmd = {"lvcreate", "--thin"};
std::string size, name;
if (!config.GetString("size", &size) || !config.GetString("name", &name)) {
LOG(ERROR) << "Invalid configuration";
return base::nullopt;
}
cmd.insert(cmd.end(), {"-V", size + "M", "-n", name, thinpool.GetName()});
return lvm_->RunCommand(cmd)
? base::make_optional(LogicalVolume(name, vg.GetName(), lvm_))
: base::nullopt;
}
} // namespace brillo