blob: 2bf306e52b29d260983e74e542d677932cd99805 [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.
#include "brillo/blkdev_utils/lvm_device.h"
// lvm2 has multiple options for managing LVM objects:
// - liblvm2app: deprecated.
// - liblvm2cmd: simple interface to directly parse cli commands into functions.
// - lvmdbusd: persistent daemon that can be reached via D-Bus.
//
// Since the logical/physical volume and volume group creation can occur during
// early boot when dbus is not available, the preferred solution is to use
// lvm2cmd.
#include <lvm2cmd.h>
#include <base/posix/eintr_wrapper.h>
#include <brillo/process/process.h>
namespace brillo {
namespace {
void LogLvmError(int rc, const std::string& cmd) {
switch (rc) {
case LVM2_COMMAND_SUCCEEDED:
break;
case LVM2_NO_SUCH_COMMAND:
LOG(ERROR) << "Failed to run lvm2 command: no such command " << cmd;
break;
case LVM2_INVALID_PARAMETERS:
LOG(ERROR) << "Failed to run lvm2 command: invalid parameters " << cmd;
break;
case LVM2_PROCESSING_FAILED:
LOG(ERROR) << "Failed to run lvm2 command: processing failed " << cmd;
break;
default:
LOG(ERROR) << "Failed to run lvm2 command: invalid return code " << cmd;
break;
}
}
} // namespace
PhysicalVolume::PhysicalVolume(const base::FilePath& device_path,
std::shared_ptr<LvmCommandRunner> lvm)
: device_path_(device_path), lvm_(lvm) {}
bool PhysicalVolume::Check() {
if (device_path_.empty() || !lvm_)
return false;
return lvm_->RunCommand({"pvck", device_path_.value()});
}
bool PhysicalVolume::Repair() {
if (device_path_.empty() || !lvm_)
return false;
return lvm_->RunCommand({"pvck", "--yes", device_path_.value()});
}
bool PhysicalVolume::Remove() {
if (device_path_.empty() || !lvm_)
return false;
bool ret = lvm_->RunCommand({"pvremove", device_path_.value()});
device_path_ = base::FilePath();
return ret;
}
VolumeGroup::VolumeGroup(const std::string& volume_group_name,
std::shared_ptr<LvmCommandRunner> lvm)
: volume_group_name_(volume_group_name), lvm_(lvm) {}
bool VolumeGroup::Check() {
if (volume_group_name_.empty() || !lvm_)
return false;
return lvm_->RunCommand({"vgck", GetPath().value()});
}
bool VolumeGroup::Repair() {
if (volume_group_name_.empty() || !lvm_)
return false;
return lvm_->RunCommand({"vgck", "--yes", GetPath().value()});
}
base::FilePath VolumeGroup::GetPath() const {
if (volume_group_name_.empty() || !lvm_)
return base::FilePath();
return base::FilePath("/dev").Append(volume_group_name_);
}
bool VolumeGroup::Activate() {
if (volume_group_name_.empty() || !lvm_)
return false;
return lvm_->RunCommand({"vgchange", "-ay", volume_group_name_});
}
bool VolumeGroup::Deactivate() {
if (volume_group_name_.empty() || !lvm_)
return false;
return lvm_->RunCommand({"vgchange", "-an", volume_group_name_});
}
bool VolumeGroup::Remove() {
if (volume_group_name_.empty() || !lvm_)
return false;
bool ret = lvm_->RunCommand({"vgremove", volume_group_name_});
volume_group_name_ = "";
return ret;
}
LogicalVolume::LogicalVolume(const std::string& logical_volume_name,
const std::string& volume_group_name,
std::shared_ptr<LvmCommandRunner> lvm)
: logical_volume_name_(logical_volume_name),
volume_group_name_(volume_group_name),
lvm_(lvm) {}
base::FilePath LogicalVolume::GetPath() {
if (logical_volume_name_.empty() || !lvm_)
return base::FilePath();
return base::FilePath("/dev")
.Append(volume_group_name_)
.Append(logical_volume_name_);
}
bool LogicalVolume::Activate() {
if (logical_volume_name_.empty() || !lvm_)
return false;
return lvm_->RunCommand({"lvchange", "-ay", GetName()});
}
bool LogicalVolume::Deactivate() {
if (logical_volume_name_.empty() || !lvm_)
return false;
return lvm_->RunCommand({"lvchange", "-an", GetName()});
}
bool LogicalVolume::Remove() {
if (volume_group_name_.empty() || !lvm_)
return false;
bool ret = lvm_->RunCommand({"lvremove", GetName()});
logical_volume_name_ = "";
volume_group_name_ = "";
return ret;
}
Thinpool::Thinpool(const std::string& thinpool_name,
const std::string& volume_group_name,
std::shared_ptr<LvmCommandRunner> lvm)
: thinpool_name_(thinpool_name),
volume_group_name_(volume_group_name),
lvm_(lvm) {}
bool Thinpool::Check() {
if (thinpool_name_.empty() || !lvm_)
return false;
return lvm_->RunProcess({"thin_check", GetName()});
}
bool Thinpool::Repair() {
if (thinpool_name_.empty() || !lvm_)
return false;
return lvm_->RunProcess({"lvconvert", "--repair", GetName()});
}
bool Thinpool::Activate() {
if (thinpool_name_.empty() || !lvm_)
return false;
return lvm_->RunCommand({"lvchange", "-ay", GetName()});
}
bool Thinpool::Deactivate() {
if (thinpool_name_.empty() || !lvm_)
return false;
return lvm_->RunCommand({"lvchange", "-an", GetName()});
}
bool Thinpool::Remove() {
if (thinpool_name_.empty() || !lvm_)
return false;
bool ret = lvm_->RunCommand({"lvremove", GetName()});
volume_group_name_ = "";
thinpool_name_ = "";
return ret;
}
// For unittests, don't initialize the lvm2 handle.
LvmCommandRunner::LvmCommandRunner() : lvm_handle_(lvm2_init()) {}
LvmCommandRunner::~LvmCommandRunner() {
lvm2_exit(lvm_handle_);
}
bool LvmCommandRunner::RunCommand(const std::vector<std::string>& cmd) {
// lvm2_run() does not exec/fork a separate process, instead it parses the
// command line and calls the relevant functions within liblvm2cmd directly.
std::string lvm_cmd = base::JoinString(cmd, " ");
int rc = lvm2_run(lvm_handle_, lvm_cmd.c_str());
LogLvmError(rc, lvm_cmd);
return rc == LVM2_COMMAND_SUCCEEDED;
}
bool LvmCommandRunner::RunProcess(const std::vector<std::string>& cmd,
std::string* output) {
brillo::ProcessImpl lvm_process;
for (auto arg : cmd)
lvm_process.AddArg(arg);
lvm_process.SetCloseUnusedFileDescriptors(true);
if (output) {
lvm_process.RedirectUsingMemory(STDOUT_FILENO);
}
if (lvm_process.Run() != 0) {
PLOG(ERROR) << "Failed to run command";
return false;
}
if (output) {
*output = lvm_process.GetOutputString(STDOUT_FILENO);
}
return true;
}
} // namespace brillo