| // 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 |