blob: 4f8f2aacbe65ea72bf66f2e4715f8a8c1c2a64e3 [file] [log] [blame] [edit]
// Copyright 2020 The ChromiumOS Authors
// 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 <optional>
#include <base/logging.h>
#include <base/strings/pattern.h>
namespace brillo {
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/pvs", "--reportformat", "json", device_path.value()},
&output)) {
return false;
}
std::optional<base::Value> pv_value =
lvm_->UnwrapReportContents(output, "pv");
if (!pv_value || !pv_value->is_dict()) {
LOG(ERROR) << "Failed to get report contents";
return false;
}
const auto& pv_dictionary = pv_value->GetDict();
const std::string* pv_name = pv_dictionary.FindString("pv_name");
if (!pv_name) {
LOG(ERROR) << "Missing value \"pv_name\".";
return false;
} else if (*pv_name != device_path.value()) {
LOG(ERROR) << "Mismatched value: expected: " << device_path
<< " actual: " << *pv_name;
return false;
}
if (volume_group_name) {
const std::string* vg_name = pv_dictionary.FindString("vg_name");
if (!vg_name) {
LOG(ERROR) << "Failed to fetch volume group name";
return false;
}
*volume_group_name = *vg_name;
}
return true;
}
std::optional<PhysicalVolume> LogicalVolumeManager::GetPhysicalVolume(
const base::FilePath& device_path) {
return ValidatePhysicalVolume(device_path, nullptr)
? std::make_optional(PhysicalVolume(device_path, lvm_))
: std::nullopt;
}
std::optional<VolumeGroup> LogicalVolumeManager::GetVolumeGroup(
const PhysicalVolume& pv) {
std::string vg_name;
return ValidatePhysicalVolume(pv.GetPath(), &vg_name)
? std::make_optional(VolumeGroup(vg_name, lvm_))
: std::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/lvs", "-S", pool_lv_check, "--reportformat",
"json", vg_name + "/" + lv_name},
&output)) {
return false;
}
std::optional<base::Value> lv_value =
lvm_->UnwrapReportContents(output, "lv");
if (!lv_value || !lv_value->is_dict()) {
LOG(ERROR) << "Failed to get report contents";
return false;
}
const auto& lv_dictionary = lv_value->GetDict();
const std::string* output_lv_name = lv_dictionary.FindString("lv_name");
if (!output_lv_name) {
LOG(ERROR) << "Missing value \"lv_name\".";
return false;
} else if (*output_lv_name != lv_name) {
LOG(ERROR) << "Mismatched value: expected: " << lv_name
<< " actual: " << *output_lv_name;
return false;
}
return true;
}
std::optional<Thinpool> LogicalVolumeManager::GetThinpool(
const VolumeGroup& vg, const std::string& thinpool_name) {
return ValidateLogicalVolume(vg, thinpool_name, true /* is_thinpool */)
? std::make_optional(Thinpool(thinpool_name, vg.GetName(), lvm_))
: std::nullopt;
}
std::optional<LogicalVolume> LogicalVolumeManager::GetLogicalVolume(
const VolumeGroup& vg, const std::string& lv_name) {
return ValidateLogicalVolume(vg, lv_name, false /* is_thinpool */)
? std::make_optional(LogicalVolume(lv_name, vg.GetName(), lvm_))
: std::nullopt;
}
std::vector<LogicalVolume> LogicalVolumeManager::ListLogicalVolumes(
const VolumeGroup& vg, const std::string& pattern) {
std::string output;
std::string vg_name = vg.GetName();
std::vector<LogicalVolume> lv_vector;
if (!lvm_->RunProcess({"/sbin/lvs", "-S", "pool_lv!=\"\"", "--reportformat",
"json", vg_name},
&output)) {
return lv_vector;
}
std::optional<base::Value> lv_list = lvm_->UnwrapReportContents(output, "lv");
if (!lv_list || !lv_list->is_list()) {
LOG(ERROR) << "Failed to get report contents";
return lv_vector;
}
for (const auto& lv_value : lv_list->GetList()) {
if (!lv_value.is_dict()) {
LOG(ERROR) << "Failed to get dictionary value for physical volume";
continue;
}
const auto& lv_dictionary = lv_value.GetDict();
const std::string* output_lv_name = lv_dictionary.FindString("lv_name");
if (!output_lv_name) {
LOG(ERROR) << "Failed to get logical volume name";
continue;
}
if (!pattern.empty() && !base::MatchPattern(*output_lv_name, pattern)) {
LOG(INFO) << "Logical volume=" << *output_lv_name
<< " does not match pattern=" << pattern;
continue;
}
lv_vector.push_back(LogicalVolume(*output_lv_name, vg_name, lvm_));
}
return lv_vector;
}
std::optional<PhysicalVolume> LogicalVolumeManager::CreatePhysicalVolume(
const base::FilePath& device_path) {
return lvm_->RunCommand({"pvcreate", "-ff", "--yes", device_path.value()})
? std::make_optional(PhysicalVolume(device_path, lvm_))
: std::nullopt;
}
std::optional<VolumeGroup> LogicalVolumeManager::CreateVolumeGroup(
const PhysicalVolume& pv, const std::string& vg_name) {
return lvm_->RunCommand(
{"vgcreate", "-p", "1", vg_name, pv.GetPath().value()})
? std::make_optional(VolumeGroup(vg_name, lvm_))
: std::nullopt;
}
std::optional<Thinpool> LogicalVolumeManager::CreateThinpool(
const VolumeGroup& vg, const base::Value& config) {
if (!config.is_dict()) {
LOG(ERROR) << "Invalid configuration";
return std::nullopt;
}
const auto& config_dict = config.GetDict();
std::vector<std::string> cmd = {"lvcreate"};
const std::string* size = config_dict.FindString("size");
const std::string* metadata_size = config_dict.FindString("metadata_size");
const std::string* name = config_dict.FindString("name");
if (!size || !name || !metadata_size) {
LOG(ERROR) << "Invalid configuration";
return std::nullopt;
}
cmd.insert(cmd.end(),
{"--zero", "n", "--size", *size + "M", "--poolmetadatasize",
*metadata_size + "M", "--thinpool", *name, vg.GetName()});
return lvm_->RunCommand(cmd)
? std::make_optional(Thinpool(*name, vg.GetName(), lvm_))
: std::nullopt;
}
std::optional<LogicalVolume> LogicalVolumeManager::CreateLogicalVolume(
const VolumeGroup& vg,
const Thinpool& thinpool,
const base::Value::Dict& config) {
std::vector<std::string> cmd = {"lvcreate", "--thin"};
const std::string* size = config.FindString("size");
const std::string* name = config.FindString("name");
if (!size || !name) {
LOG(ERROR) << "Invalid configuration";
return std::nullopt;
}
cmd.insert(cmd.end(), {"-V", *size + "M", "-n", *name, thinpool.GetName()});
return lvm_->RunCommand(cmd)
? std::make_optional(LogicalVolume(*name, vg.GetName(), lvm_))
: std::nullopt;
}
bool LogicalVolumeManager::RemoveLogicalVolume(const VolumeGroup& vg,
const std::string& lv_name) {
std::optional<LogicalVolume> lv = GetLogicalVolume(vg, lv_name);
if (!lv || !lv->IsValid()) {
LOG(WARNING) << "Logical volume " << lv_name << " does not exist.";
return true;
}
bool ret = lv->Remove();
if (!ret) {
LOG(ERROR) << "Failed to remove logical volume.";
}
return ret;
}
} // namespace brillo