From 0e5d52b23ae0b86e938905a332ad5b7439011dcc Mon Sep 17 00:00:00 2001 From: "A. Wilcox" Date: Mon, 4 Nov 2019 15:51:15 -0600 Subject: hscript: Refactor script.cc and disk.cc for maintainability --- hscript/disk_lvm.cc | 327 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 hscript/disk_lvm.cc (limited to 'hscript/disk_lvm.cc') diff --git a/hscript/disk_lvm.cc b/hscript/disk_lvm.cc new file mode 100644 index 0000000..98ff5c1 --- /dev/null +++ b/hscript/disk_lvm.cc @@ -0,0 +1,327 @@ +/* + * disk_lvm.cc - Implementation of the Key classes for LVM manipulation + * libhscript, the HorizonScript library for + * Project Horizon + * + * Copyright (c) 2019 Adélie Linux and contributors. All rights reserved. + * This code is licensed under the AGPL 3.0 license, as noted in the + * LICENSE-code file in the root directory of this repository. + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +#include +#include /* strcmp */ +#include +#ifdef HAS_INSTALL_ENV +# include /* blkid_get_tag_value */ +# include "util/filesystem.hh" +#endif /* HAS_INSTALL_ENV */ +#include "disk.hh" +#include "util.hh" +#include "util/output.hh" + +using namespace Horizon::Keys; + +bool parse_size_string(const std::string &, uint64_t *, SizeType *); + +Key *LVMPhysical::parseFromData(const std::string &data, int lineno, + int *errors, int *) { + if(data.size() < 6 || data.substr(0, 5) != "/dev/") { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "lvm_pv: expected an absolute path to a block device"); + return nullptr; + } + + return new LVMPhysical(lineno, data); +} + +bool LVMPhysical::execute(ScriptOptions opts) const { + output_info("installfile:" + std::to_string(line), + "lvm_pv: creating physical volume on " + _value); + + if(opts.test(Simulate)) { + std::cout << "pvcreate --force " << _value << std::endl; + return true; + } + +#ifdef HAS_INSTALL_ENV + const char *fstype = blkid_get_tag_value(nullptr, "TYPE", _value.c_str()); + if(fstype != nullptr && strcmp(fstype, "LVM2_member") == 0) { + /* already a pv; skip */ + return true; + } + + if(run_command("pvcreate", {"--force", _value}) != 0) { + output_error("installfile:" + std::to_string(line), + "lvm_pv: failed to create physical volume on " + _value); + return false; + } +#endif /* HAS_INSTALL_ENV */ + return true; +} + + +/*! Determine if a string is a valid LVM VG/LV name. + * @param name The name of which to test validity. + * @returns true if the string is a valid name, false otherwise. + * @note LVM LVs have additional restrictions; see is_valid_lvm_lv_name. + */ +bool is_valid_lvm_name(const std::string &name) { + if(name[0] == '.' && (name.length() == 1 || name[1] == '.')) { + /* . and .. are invalid */ + return false; + } + if(name[0] == '-') { + /* VG nor LV may start with - */ + return false; + } + + const std::string valid_syms("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+_.-"); + return (name.find_first_not_of(valid_syms) == std::string::npos); +} + +/*! Determine if a string is a valid LVM LV name. + * @param name The name of which to test validity. + * @returns true if the string is a valid LV name, false otherwise. + */ +bool is_valid_lvm_lv_name(const std::string &name) { + if(!is_valid_lvm_name(name)) { + /* Fail fast if we fail the general test. */ + return false; + } + + if(name == "snapshot" || name == "pvmove") { + /* Invalid full names. */ + return false; + } + + if(name.find("_cdata") != std::string::npos || + name.find("_cmeta") != std::string::npos || + name.find("_corig") != std::string::npos || + name.find("_mlog") != std::string::npos || + name.find("_mimage") != std::string::npos || + name.find("_pmspare") != std::string::npos || + name.find("_rimage") != std::string::npos || + name.find("_rmeta") != std::string::npos || + name.find("_tdata") != std::string::npos || + name.find("_tmeta") != std::string::npos || + name.find("_vorigin") != std::string::npos) { + /* Cannot occur anywhere in the name. */ + return false; + } + + return true; +} + + +Key *LVMGroup::parseFromData(const std::string &data, int lineno, int *errors, + int *) { + std::string::size_type space = data.find_first_of(' '); + if(space == std::string::npos || data.size() == space + 1) { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "lvm_vg: expected exactly two elements", + "syntax is lvm_vg [pv_block] [name-of-vg]"); + return nullptr; + } + + const std::string pv(data.substr(0, space)); + const std::string name(data.substr(space + 1)); + + if(pv.length() < 6 || pv.substr(0, 5) != "/dev/") { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "lvm_vg: expected absolute path to block device"); + return nullptr; + } + + if(!is_valid_lvm_name(name)) { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "lvm_vg: invalid volume group name"); + return nullptr; + } + + return new LVMGroup(lineno, pv, name); +} + +bool LVMGroup::validate(ScriptOptions) const { + /* validation occurs during parsing */ + return true; +} + +bool LVMGroup::test_pv(ScriptOptions) const { +#ifdef HAS_INSTALL_ENV + const char *fstype = blkid_get_tag_value(nullptr, "TYPE", + this->pv().c_str()); + if(fstype == nullptr) { + /* inconclusive */ + return true; + } + + return (strcmp(fstype, "LVM2_member") == 0); +#else /* !HAS_INSTALL_ENV */ + return true; +#endif /* HAS_INSTALL_ENV */ +} + +/*! Determine if a named Volume Group currently exists on a LVM PV. + * @param vg The name of the Volume Group. + * @param pv The path to the LVM physical volume. + * @param line The installfile line number. + * @param msgs Whether or not to print messages. + * @returns true if +vg+ appears on +pv+; false otherwise. + */ +bool does_vg_exist_on_pv(const std::string &vg, const std::string &pv, + long line, bool msgs) { + bool success = false; + const std::string pv_command("pvs --noheadings -o vg_name " + pv + + " 2>/dev/null"); + + FILE *pvs = popen(pv_command.c_str(), "r"); + if(pvs == nullptr) { + if(msgs) output_error("installfile:" + std::to_string(line), + "lvm_vg: can't determine if vg is duplicate"); + return false; + } + + char *buf = nullptr; + size_t buf_size = 0; + ssize_t read_bytes = getline(&buf, &buf_size, pvs); + + pclose(pvs); + + /* size must match *exactly* or else we could have a short compare succeed + * i.e. on-disk VG "Group", our VG "GroupNouveau" + * also, use vg.size() to avoid comparing the terminating \n */ + if(static_cast(read_bytes) != vg.size() + 3 || + strncmp(buf + 2, vg.c_str(), vg.size())) { + if(msgs) output_error("installfile:" + std::to_string(line), + "lvm_vg: volume group already exists and is " + "not using the specified physical volume"); + } else { + /* the VG already exists and uses the specified PV - we're good */ + success = true; + } + + free(buf); + return success; +} + +bool LVMGroup::execute(ScriptOptions opts) const { + output_info("installfile:" + std::to_string(line), + "lvm_vg: creating volume group " + _vgname + " on " + _pv); + + if(opts.test(Simulate)) { + std::cout << "vgcreate " << _vgname << " " << _pv << std::endl; + return true; + } + +#ifdef HAS_INSTALL_ENV + /* REQ: Runner.Execute.lvm_vg.Duplicate */ + if(fs::exists("/dev/" + _vgname)) { + return does_vg_exist_on_pv(_vgname, _pv, line, true); + } + + if(run_command("vgcreate", {_vgname, _pv}) != 0) { + if(does_vg_exist_on_pv(_vgname, _pv, line, true)) { + return true; + } + + output_error("installfile:" + std::to_string(line), + "lvm_vg: failed to create volume group " + _vgname); + return false; + } +#endif /* HAS_INSTALL_ENV */ + return true; +} + + +Key *LVMVolume::parseFromData(const std::string &data, int lineno, int *errors, + int *) { + std::string vg, name, size_str; + std::string::size_type name_start, size_start; + SizeType size_type; + uint64_t size; + + long spaces = std::count(data.cbegin(), data.cend(), ' '); + if(spaces != 2) { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "lvm_lv: expected 3 elements, got: " + + std::to_string(spaces), + "syntax is: lvm_lv [vg] [name] [size]"); + return nullptr; + } + + name_start = data.find_first_of(' '); + vg = data.substr(0, name_start); + size_start = data.find_first_of(' ', name_start + 1); + name = data.substr(name_start + 1, size_start - name_start - 1); + size_str = data.substr(size_start + 1); + + if(!is_valid_lvm_name(vg)) { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "lvm_lv: invalid volume group name"); + return nullptr; + } + + if(!is_valid_lvm_lv_name(name)) { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "lvm_lv: invalid volume name"); + return nullptr; + } + + if(!parse_size_string(size_str, &size, &size_type)) { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "lvm_lv: invalid size", size_str); + return nullptr; + } + + return new LVMVolume(lineno, vg, name, size_type, size); +} + +bool LVMVolume::validate(ScriptOptions) const { + return true; +} + +bool LVMVolume::execute(ScriptOptions opts) const { + output_info("installfile:" + std::to_string(line), + "lvm_lv: creating volume " + _lvname + " on " + _vg); + std::string param, size; + + switch(_size_type) { + case Fill: + param = "-l"; + size = "100%FREE"; + break; + case Bytes: + param = "-L"; + size = std::to_string(_size) + "B"; + break; + case Percent: + param = "-l"; + size = std::to_string(_size) + "%VG"; + break; + } + + if(opts.test(Simulate)) { + std::cout << "lvcreate " << param << " " << size << " -n " + << _lvname << " " << _vg << std::endl; + return true; + } + +#ifdef HAS_INSTALL_ENV + if(run_command("lvcreate", {param, size, "-n", _lvname, _vg}) != 0) { + output_error("installfile:" + std::to_string(line), + "lvm_lv: failed to create logical volume " + _lvname); + return false; + } +#endif /* HAS_INSTALL_ENV */ + return true; +} -- cgit v1.2.3-60-g2f50