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/CMakeLists.txt | 3 + hscript/disk.cc | 305 +---------- hscript/disk_lvm.cc | 327 ++++++++++++ hscript/script.cc | 1338 +++--------------------------------------------- hscript/script_e.cc | 400 +++++++++++++++ hscript/script_i.hh | 289 +++++++++++ hscript/script_v.cc | 565 ++++++++++++++++++++ 7 files changed, 1661 insertions(+), 1566 deletions(-) create mode 100644 hscript/disk_lvm.cc create mode 100644 hscript/script_e.cc create mode 100644 hscript/script_i.hh create mode 100644 hscript/script_v.cc diff --git a/hscript/CMakeLists.txt b/hscript/CMakeLists.txt index e7f54e9..1f8c358 100644 --- a/hscript/CMakeLists.txt +++ b/hscript/CMakeLists.txt @@ -2,7 +2,10 @@ pkg_check_modules(CURL libcurl) set(HSCRIPT_SOURCE script.cc + script_v.cc + script_e.cc disk.cc + disk_lvm.cc key.cc meta.cc network.cc diff --git a/hscript/disk.cc b/hscript/disk.cc index 0bf3633..cef1a1e 100644 --- a/hscript/disk.cc +++ b/hscript/disk.cc @@ -298,7 +298,8 @@ bool Encrypt::execute(ScriptOptions) const { * @param type (out) The type of size determined. * @returns true if the string was parseable, false otherwise. */ -bool parse_size_string(const std::string &in_size, uint64_t *out_size, SizeType *type) { +bool parse_size_string(const std::string &in_size, uint64_t *out_size, + SizeType *type) { std::string size(in_size), numbers, suffix; std::string::size_type suffix_pos; uint64_t multiplicand = 0; @@ -582,308 +583,6 @@ bool Partition::execute(ScriptOptions opts) const { } -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; -} - - const static std::set valid_fses = { "ext2", "ext3", "ext4", "jfs", "vfat", "xfs" }; 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; +} diff --git a/hscript/script.cc b/hscript/script.cc index 4ed52b7..fcb7035 100644 --- a/hscript/script.cc +++ b/hscript/script.cc @@ -11,18 +11,15 @@ */ #include -#include #include "util/filesystem.hh" #include #include #include -#ifdef HAS_INSTALL_ENV -# include -#endif /* HAS_INSTALL_ENV */ #include #include #include "script.hh" +#include "script_i.hh" #include "disk.hh" #include "meta.hh" #include "network.hh" @@ -74,363 +71,95 @@ const std::map valid_keys = { namespace Horizon { -/*! Describes a user account. */ -struct UserDetail { - std::unique_ptr name; - std::unique_ptr alias; - std::unique_ptr passphrase; - std::unique_ptr icon; - std::vector< std::unique_ptr > groups; -}; - -struct Script::ScriptPrivate { - /*! Determines whether or not to enable networking. */ - std::unique_ptr network; - /*! The target system's hostname. */ - std::unique_ptr hostname; - /*! The packages to install to the target system. */ - std::set packages; - /*! The root shadow line. */ - std::unique_ptr rootpw; - /*! The system language. */ - std::unique_ptr lang; - /*! The system keymap. */ - std::unique_ptr keymap; - /*! The system timezone. */ - std::unique_ptr tzone; - - /*! Network addressing configuration */ - std::vector< std::unique_ptr > addresses; - /*! Network nameserver resolver addresses */ - std::vector< std::unique_ptr > nses; - /*! Wireless networking configuration */ - std::vector< std::unique_ptr > ssids; - - /*! APK repositories */ - std::vector< std::unique_ptr > repos; - /*! APK repository keys */ - std::vector< std::unique_ptr > repo_keys; - - /*! User account information */ - std::map< std::string, std::unique_ptr > accounts; - - /*! Disk identification keys */ - std::vector< std::unique_ptr > diskids; - /*! Disklabel configuration keys */ - std::vector< std::unique_ptr > disklabels; - /*! Partition creation keys */ - std::vector< std::unique_ptr > partitions; - /*! LVM physical volume keys */ - std::vector< std::unique_ptr > lvm_pvs; - /*! LVM volume group keys */ - std::vector< std::unique_ptr > lvm_vgs; - /*! LVM logical volume keys */ - std::vector< std::unique_ptr > lvm_lvs; - /*! LUKS creation keys */ - std::vector< std::unique_ptr > luks; - /*! Filesystem creation keys */ - std::vector< std::unique_ptr > fses; - /*! Target system's mountpoints. */ - std::vector< std::unique_ptr > mounts; - -#ifdef NON_LIBRE_FIRMWARE - std::unique_ptr firmware; -#endif - - /*! Store +key_obj+ representing the key +key_name+. - * @param key_name The name of the key that is being stored. - * @param obj The Key object associated with the key. - * @param errors Output parameter: if given, incremented on error. - * @param warnings Output parameter: if given, incremented on warning. - * @param opts Script parsing options. - */ - bool store_key(const std::string &key_name, Keys::Key *obj, int lineno, - int *errors, int *warnings, const ScriptOptions &opts) { - if(key_name == "network") { - return store_network(obj, lineno, errors, warnings, opts); - } else if(key_name == "netaddress") { - std::unique_ptr addr(dynamic_cast(obj)); - this->addresses.push_back(std::move(addr)); - return true; - } else if(key_name == "nameserver") { - std::unique_ptr ns(dynamic_cast(obj)); - this->nses.push_back(std::move(ns)); - return true; - } else if(key_name == "netssid") { - std::unique_ptr ssid(dynamic_cast(obj)); - this->ssids.push_back(std::move(ssid)); - return true; - } else if(key_name == "hostname") { - return store_hostname(obj, lineno, errors, warnings, opts); - } else if(key_name == "pkginstall") { - return store_pkginstall(obj, lineno, errors, warnings, opts); - } else if(key_name == "rootpw") { - return store_rootpw(obj, lineno, errors, warnings, opts); - } else if(key_name == "language") { - return store_lang(obj, lineno, errors, warnings, opts); - } else if(key_name == "keymap") { - return store_keymap(obj, lineno, errors, warnings, opts); - } else if(key_name == "firmware") { - return store_firmware(obj, lineno, errors, warnings, opts); - } else if(key_name == "timezone") { - return store_timezone(obj, lineno, errors, warnings, opts); - } else if(key_name == "repository") { - std::unique_ptr repo(dynamic_cast(obj)); - this->repos.push_back(std::move(repo)); - return true; - } else if(key_name == "signingkey") { - std::unique_ptr key(dynamic_cast(obj)); - this->repo_keys.push_back(std::move(key)); - return true; - } else if(key_name == "username") { - return store_username(obj, lineno, errors, warnings, opts); - } else if(key_name == "useralias") { - return store_useralias(obj, lineno, errors, warnings, opts); - } else if(key_name == "userpw") { - return store_userpw(obj, lineno, errors, warnings, opts); - } else if(key_name == "usericon") { - return store_usericon(obj, lineno, errors, warnings, opts); - } else if(key_name == "usergroups") { - return store_usergroups(obj, lineno, errors, warnings, opts); - } else if(key_name == "diskid") { - std::unique_ptr diskid(dynamic_cast(obj)); - this->diskids.push_back(std::move(diskid)); - return true; - } else if(key_name == "disklabel") { - std::unique_ptr l(dynamic_cast(obj)); - this->disklabels.push_back(std::move(l)); - return true; - } else if(key_name == "partition") { - std::unique_ptr p(dynamic_cast(obj)); - this->partitions.push_back(std::move(p)); - return true; - } else if(key_name == "lvm_pv") { - std::unique_ptr pv(dynamic_cast(obj)); - this->lvm_pvs.push_back(std::move(pv)); - return true; - } else if(key_name == "lvm_vg") { - std::unique_ptr vg(dynamic_cast(obj)); - this->lvm_vgs.push_back(std::move(vg)); - return true; - } else if(key_name == "lvm_lv") { - std::unique_ptr lv(dynamic_cast(obj)); - this->lvm_lvs.push_back(std::move(lv)); - return true; - } else if(key_name == "encrypt") { - std::unique_ptr e(dynamic_cast(obj)); - this->luks.push_back(std::move(e)); - return true; - } else if(key_name == "fs") { - std::unique_ptr fs(dynamic_cast(obj)); - this->fses.push_back(std::move(fs)); - return true; - } else if(key_name == "mount") { - std::unique_ptr mount(dynamic_cast(obj)); - this->mounts.push_back(std::move(mount)); - return true; - } else { - return false; - } - } - -#define DUPLICATE_ERROR(OBJ, KEY, OLD_VAL) \ - std::string err_str("previous value was ");\ - err_str += OLD_VAL;\ - err_str += " at installfile:" + std::to_string(OBJ->lineno());\ - if(errors) *errors += 1;\ - output_error("installfile:" + std::to_string(lineno),\ - "duplicate value for key '" + std::string(KEY) + "'",\ - err_str); - - bool store_network(Keys::Key* obj, int lineno, int *errors, int *, - ScriptOptions) { - if(this->network) { - DUPLICATE_ERROR(this->network, "network", - this->network->test() ? "true" : "false") - return false; - } - std::unique_ptr net(dynamic_cast(obj)); - this->network = std::move(net); +bool Script::ScriptPrivate::store_key(const std::string &key_name, Key *obj, + int lineno, int *errors, int *warnings, + const ScriptOptions &opts) { + if(key_name == "network") { + return store_network(obj, lineno, errors, warnings, opts); + } else if(key_name == "netaddress") { + std::unique_ptr addr(dynamic_cast(obj)); + this->addresses.push_back(std::move(addr)); return true; - } - - bool store_hostname(Keys::Key* obj, int lineno, int *errors, int *, - ScriptOptions) { - if(this->hostname) { - DUPLICATE_ERROR(this->hostname, "hostname", - this->hostname->value()) - return false; - } - std::unique_ptr name(dynamic_cast(obj)); - this->hostname = std::move(name); + } else if(key_name == "nameserver") { + std::unique_ptr ns(dynamic_cast(obj)); + this->nses.push_back(std::move(ns)); return true; - } - - bool store_pkginstall(Keys::Key* obj, int lineno, int *, int *warnings, - ScriptOptions opts) { - PkgInstall *install = dynamic_cast(obj); - for(auto &pkg : install->packages()) { - if(opts.test(StrictMode) && packages.find(pkg) != packages.end()) { - if(warnings) *warnings += 1; - output_warning("installfile:" + std::to_string(lineno), - "pkginstall: package '" + pkg + - "' has already been specified"); - continue; - } - packages.insert(pkg); - } - delete install; + } else if(key_name == "netssid") { + std::unique_ptr ssid(dynamic_cast(obj)); + this->ssids.push_back(std::move(ssid)); return true; - } - - bool store_rootpw(Keys::Key* obj, int lineno, int *errors, int *, - ScriptOptions) { - if(this->rootpw) { - DUPLICATE_ERROR(this->rootpw, std::string("rootpw"), - "an encrypted passphrase") - return false; - } - std::unique_ptr r(dynamic_cast(obj)); - this->rootpw = std::move(r); + } else if(key_name == "hostname") { + return store_hostname(obj, lineno, errors, warnings, opts); + } else if(key_name == "pkginstall") { + return store_pkginstall(obj, lineno, errors, warnings, opts); + } else if(key_name == "rootpw") { + return store_rootpw(obj, lineno, errors, warnings, opts); + } else if(key_name == "language") { + return store_lang(obj, lineno, errors, warnings, opts); + } else if(key_name == "keymap") { + return store_keymap(obj, lineno, errors, warnings, opts); + } else if(key_name == "firmware") { + return store_firmware(obj, lineno, errors, warnings, opts); + } else if(key_name == "timezone") { + return store_timezone(obj, lineno, errors, warnings, opts); + } else if(key_name == "repository") { + std::unique_ptr repo(dynamic_cast(obj)); + this->repos.push_back(std::move(repo)); return true; - } - - bool store_firmware(Keys::Key *obj, int lineno, int *errors, int *, - ScriptOptions) { - std::unique_ptr f(dynamic_cast(obj)); -#ifdef NON_LIBRE_FIRMWARE - if(this->firmware) { - DUPLICATE_ERROR(this->firmware, std::string("firmware"), - (this->firmware->test()) ? "true" : "false") - return false; - } - this->firmware = std::move(f); + } else if(key_name == "signingkey") { + std::unique_ptr key(dynamic_cast(obj)); + this->repo_keys.push_back(std::move(key)); return true; -#else - assert(!f->test()); + } else if(key_name == "username") { + return store_username(obj, lineno, errors, warnings, opts); + } else if(key_name == "useralias") { + return store_useralias(obj, lineno, errors, warnings, opts); + } else if(key_name == "userpw") { + return store_userpw(obj, lineno, errors, warnings, opts); + } else if(key_name == "usericon") { + return store_usericon(obj, lineno, errors, warnings, opts); + } else if(key_name == "usergroups") { + return store_usergroups(obj, lineno, errors, warnings, opts); + } else if(key_name == "diskid") { + std::unique_ptr diskid(dynamic_cast(obj)); + this->diskids.push_back(std::move(diskid)); return true; -#endif - } - - bool store_lang(Keys::Key *obj, int lineno, int *errors, int *, - ScriptOptions) { - if(this->lang) { - DUPLICATE_ERROR(this->lang, std::string("language"), - this->lang->value()) - return false; - } - std::unique_ptr l(dynamic_cast(obj)); - this->lang = std::move(l); + } else if(key_name == "disklabel") { + std::unique_ptr l(dynamic_cast(obj)); + this->disklabels.push_back(std::move(l)); return true; - } - - bool store_keymap(Keys::Key *obj, int lineno, int *errors, int *, - ScriptOptions) { - if(this->keymap) { - DUPLICATE_ERROR(this->keymap, std::string("keymap"), - this->keymap->value()) - return false; - } - std::unique_ptr k(dynamic_cast(obj)); - this->keymap = std::move(k); + } else if(key_name == "partition") { + std::unique_ptr p(dynamic_cast(obj)); + this->partitions.push_back(std::move(p)); return true; - } - - bool store_timezone(Keys::Key *obj, int lineno, int *errors, int *, - ScriptOptions) { - if(this->tzone) { - DUPLICATE_ERROR(this->tzone, std::string("timezone"), - this->tzone->value()) - return false; - } - std::unique_ptr t(dynamic_cast(obj)); - this->tzone = std::move(t); + } else if(key_name == "lvm_pv") { + std::unique_ptr pv(dynamic_cast(obj)); + this->lvm_pvs.push_back(std::move(pv)); return true; - } - - bool store_username(Keys::Key *obj, int lineno, int *errors, int *, - ScriptOptions) { - if(accounts.size() >= 255) { - if(errors) *errors += 1; - output_error("installfile:" + std::to_string(lineno), - "username: too many users", - "you may only specify 255 users"); - return false; - } - std::unique_ptr name(dynamic_cast(obj)); - if(accounts.find(name->value()) != accounts.end()) { - DUPLICATE_ERROR((*accounts.find(name->value())).second->name, - "username", "assigned") - return false; - } - std::unique_ptr detail(new UserDetail); - detail->name = std::move(name); - accounts.insert(std::make_pair(detail->name->value(), - std::move(detail))); + } else if(key_name == "lvm_vg") { + std::unique_ptr vg(dynamic_cast(obj)); + this->lvm_vgs.push_back(std::move(vg)); return true; - } - -#define GET_USER_DETAIL(OBJ, KEY) \ - if(accounts.find(OBJ->username()) == accounts.end()) {\ - if(errors) *errors += 1;\ - output_error("installfile:" + std::to_string(lineno),\ - std::string(KEY) + ": account name " + OBJ->username() +\ - " is unknown");\ - return false;\ - }\ - UserDetail *detail = (*accounts.find(OBJ->username())).second.get(); - - bool store_useralias(Keys::Key* obj, int lineno, int *errors, - int *, ScriptOptions) { - std::unique_ptr alias(dynamic_cast(obj)); - GET_USER_DETAIL(alias, "useralias") - /* REQ: Runner.Validate.useralias.Unique */ - if(detail->alias) { - DUPLICATE_ERROR(detail->alias, "useralias", detail->alias->alias()) - return false; - } - detail->alias = std::move(alias); + } else if(key_name == "lvm_lv") { + std::unique_ptr lv(dynamic_cast(obj)); + this->lvm_lvs.push_back(std::move(lv)); return true; - } - - bool store_userpw(Keys::Key *obj, int lineno, int *errors, int *, - ScriptOptions) { - std::unique_ptr pw(dynamic_cast(obj)); - GET_USER_DETAIL(pw, "userpw") - /* REQ: Runner.Validate.userpw.Unique */ - if(detail->passphrase) { - DUPLICATE_ERROR(detail->passphrase, "userpw", - "an encrypted passphrase") - return false; - } - detail->passphrase = std::move(pw); + } else if(key_name == "encrypt") { + std::unique_ptr e(dynamic_cast(obj)); + this->luks.push_back(std::move(e)); return true; - } - - bool store_usericon(Keys::Key *obj, int lineno, int *errors, int *, - ScriptOptions) { - std::unique_ptr icon(dynamic_cast(obj)); - GET_USER_DETAIL(icon, "usericon") - /* REQ: Runner.Validate.usericon.Unique */ - if(detail->icon) { - DUPLICATE_ERROR(detail->icon, "usericon", detail->icon->icon()) - return false; - } - detail->icon = std::move(icon); + } else if(key_name == "fs") { + std::unique_ptr fs(dynamic_cast(obj)); + this->fses.push_back(std::move(fs)); return true; - } - - bool store_usergroups(Keys::Key* obj, int lineno, int *errors, - int *, ScriptOptions) { - std::unique_ptr grp(dynamic_cast(obj)); - GET_USER_DETAIL(grp, "usergroups") - detail->groups.push_back(std::move(grp)); + } else if(key_name == "mount") { + std::unique_ptr mount(dynamic_cast(obj)); + this->mounts.push_back(std::move(mount)); return true; + } else { + return false; } -#undef GET_USER_DETAIL - -#undef DUPLICATE_ERROR -}; +} Script::Script() { @@ -585,921 +314,4 @@ const Script *Script::load(std::istream &sstream, #undef PARSER_ERROR } - -/*! Perform all necessary validations on a single user account. - * @param name The username of the account. - * @param detail The UserDetail record of the account. - * @param opts The ScriptOptions in use. - * @returns A count of errors encountered, or 0 if the account is valid. - */ -int validate_one_account(const std::string &name, UserDetail *detail, - ScriptOptions opts) { - int failures = 0; - - /* REQ: Runner.Validate.username */ - if(!detail->name->validate(opts)) { - failures++; - } - - /* REQ: Runner.Validate.useralias */ - if(detail->alias && !detail->alias->validate(opts)) { - failures++; - } - - /* REQ: Runner.Validate.userpw */ - if(detail->passphrase && !detail->passphrase->validate(opts)) { - failures++; - } - - /* REQ: Runner.Validate.userpw.None */ - if(!detail->passphrase) { - long line = detail->name->lineno(); - output_warning("installfile:" + std::to_string(line), - "username: " + name + " has no set passphrase", - "This account will not be able to log in."); - } - - /* REQ: Runner.Validate.usericon */ - if(detail->icon && !detail->icon->validate(opts)) { - failures++; - } - - if(detail->groups.size() > 0) { - std::set seen_groups; - for(auto &group : detail->groups) { - /* REQ: Runner.Validate.usergroups */ - if(!group->validate(opts)) { - failures++; - } - - /* REQ: Runner.Validate.usergroups.Unique */ - const std::set these = group->groups(); - if(!std::all_of(these.begin(), these.end(), - [&seen_groups](std::string elem) { - return seen_groups.find(elem) == seen_groups.end(); - }) - ) { - output_error("installfile:" + std::to_string(group->lineno()), - "usergroups: duplicate group name specified"); - failures++; - } - seen_groups.insert(these.begin(), these.end()); - } - - /* REQ: Runner.Validate.usergroups.Count */ - if(seen_groups.size() > 16) { - output_error("installfile:0", - "usergroups: " + name + " is a member of " + - "more than 16 groups"); - failures++; - } - } - - return failures; -} - - -/*! Add the default repositories to the repo list. - * @param repos The list of repositories - * The list +repos+ will be modified with the default repositories for - * Adélie Linux. Both system/ and user/ will be added. - */ -bool add_default_repos(std::vector> &repos) { - Keys::Repository *sys_key = dynamic_cast( - Horizon::Keys::Repository::parseFromData( - "https://distfiles.adelielinux.org/adelie/stable/system", 0, - nullptr, nullptr - ) - ); - if(!sys_key) { - output_error("internal", "failed to create default system repository"); - return false; - } - std::unique_ptr sys_repo(sys_key); - repos.push_back(std::move(sys_repo)); - Keys::Repository *user_key = dynamic_cast( - Horizon::Keys::Repository::parseFromData( - "https://distfiles.adelielinux.org/adelie/stable/user", 0, - nullptr, nullptr - ) - ); - if(!user_key) { - output_error("internal", "failed to create default user repository"); - return false; - } - std::unique_ptr user_repo(user_key); - repos.push_back(std::move(user_repo)); - -#ifdef NON_LIBRE_FIRMWARE - /* REQ: Runner.Execute.firmware.Repository */ - if(this->internal->firmware && this->internal->firmware->test()) { - Keys::Repository *fw_key = dynamic_cast( - Horizon::Keys::Repository::parseFromData( - "https://distfiles.apkfission.net/adelie-stable/nonfree", - 0, nullptr, nullptr - ) - ); - if(!fw_key) { - output_error("internal", - "failed to create firmware repository"); - return false; - } - std::unique_ptr fw_repo(fw_key); - repos.push_back(std::move(fw_repo)); - } -#endif - return true; -} - - -/*! Add the default repository keys to the signing key list. - * @param keys The list of repository keys. - * The list +keys+ will be modified with the default repository signing keys - * for Adélie Linux. - */ -bool add_default_repo_keys(std::vector> &keys) { - Keys::SigningKey *key = dynamic_cast( - Horizon::Keys::SigningKey::parseFromData( - "/etc/apk/keys/packages@adelielinux.org.pub", 0, nullptr, nullptr) - ); - if(!key) { - output_error("internal", "failed to create default repository signing key"); - return false; - } - std::unique_ptr repo_key(key); - keys.push_back(std::move(repo_key)); - return true; -} - - -bool Script::validate() const { - int failures = 0; - std::set seen_diskids, seen_labels, seen_parts, seen_pvs, - seen_vg_names, seen_vg_pvs, seen_lvs, seen_fses, seen_mounts, - seen_luks; - std::map seen_iface; -#ifdef HAS_INSTALL_ENV - error_code ec; -#endif /* HAS_INSTALL_ENV */ - - /* REQ: Runner.Validate.network */ - if(!this->internal->network->validate(this->opts)) failures++; - - /* REQ: Runner.Validate.network.netaddress */ - if(this->internal->network->test() && - this->internal->addresses.size() == 0) { - failures++; - output_error("installfile:0", - "networking requested but no 'netaddress' defined", - "You need to specify at least one address to enable " - "networking."); - } - for(auto &address : this->internal->addresses) { - if(!address->validate(this->opts)) { - failures++; - } - - /* REQ: Runner.Validate.network.netaddress.Count */ - if(seen_iface.find(address->iface()) == seen_iface.end()) { - seen_iface.insert(std::make_pair(address->iface(), 1)); - } else { - seen_iface[address->iface()] += 1; - if(seen_iface[address->iface()] > 255) { - failures++; - output_error("installfile:" + std::to_string(address->lineno()), - "netaddress: interface '" + address->iface() + - "' has too many addresses assigned"); - } - } - } - - /* REQ: Runner.Validate.nameserver */ - for(auto &ns : this->internal->nses) { - if(!ns->validate(this->opts)) { - failures++; - } - } - - /* REQ: Runner.Validate.network.netssid */ - for(auto &ssid : this->internal->ssids) { - if(!ssid->validate(this->opts)) { - failures++; - } - } - - /* REQ: Runner.Validate.hostname */ - if(!this->internal->hostname->validate(this->opts)) failures++; - - /* REQ: Runner.Validate.rootpw */ - if(!this->internal->rootpw->validate(this->opts)) failures++; - - /* REQ: Runner.Validate.language */ - if(internal->lang && !internal->lang->validate(this->opts)) failures++; - - /* REQ: Runner.Validate.keymap */ - if(internal->keymap && !internal->keymap->validate(this->opts)) failures++; - -#ifdef NON_LIBRE_FIRMWARE - /* REQ: Runner.Validate.firmware */ - if(!this->internal->firmware->validate(this->opts)) failures++; -#endif - - /* REQ: Runner.Execute.timezone */ - if(!internal->tzone) { - Keys::Timezone *utc = dynamic_cast( - Horizon::Keys::Timezone::parseFromData("UTC", 0, - &failures, nullptr) - ); - if(!utc) { - output_error("internal", "failed to create default timezone"); - return false; - } - std::unique_ptr zone(utc); - this->internal->tzone = std::move(zone); - } - - /* REQ: Runner.Validate.timezone */ - if(!this->internal->tzone->validate(this->opts)) failures++; - - /* REQ: Script.repository */ - if(this->internal->repos.size() == 0) { - if(!add_default_repos(this->internal->repos)) { - return false; - } - } - - /* REQ: Runner.Validate.repository */ - for(auto &repo : this->internal->repos) { - if(!repo->validate(this->opts)) { - failures++; - } - } - if(this->internal->repos.size() > 10) { - failures++; - output_error("installfile:" + - std::to_string(this->internal->repos[11]->lineno()), - "repository: too many repositories specified", - "You may only specify up to 10 repositories."); - } - - /* REQ: Script.signingkey */ - if(this->internal->repo_keys.size() == 0) { - if(!add_default_repo_keys(this->internal->repo_keys)) { - return false; - } - } - - /* REQ: Runner.Validate.signingkey */ - for(auto &key : this->internal->repo_keys) { - if(!key->validate(this->opts)) { - failures++; - } - } - if(this->internal->repo_keys.size() > 10) { - failures++; - output_error("installfile:" + - std::to_string(this->internal->repo_keys[11]->lineno()), - "signingkey: too many keys specified", - "You may only specify up to 10 repository keys."); - } - - for(auto &acct : this->internal->accounts) { - UserDetail *detail = acct.second.get(); - failures += validate_one_account(acct.first, detail, this->opts); - } - - /* REQ: Runner.Validate.diskid */ - for(auto &diskid : this->internal->diskids) { - if(!diskid->validate(this->opts)) { - failures++; - continue; - } - - /* REQ: Runner.Validate.diskid.Unique */ - if(seen_diskids.find(diskid->device()) != seen_diskids.end()) { - failures++; - output_error("installfile:" + std::to_string(diskid->lineno()), - "diskid: device " + diskid->device() + - " has already been identified"); - } - seen_diskids.insert(diskid->device()); - } - - /* REQ: Runner.Validate.disklabel */ - for(auto &label : this->internal->disklabels) { - if(!label->validate(this->opts)) { - failures++; - continue; - } - - /* REQ: Runner.Validate.disklabel.Unique */ - if(seen_labels.find(label->device()) != seen_labels.end()) { - failures++; - output_error("installfile:" + std::to_string(label->lineno()), - "disklabel: device " + label->device() + - " already has a label queued"); - } - seen_labels.insert(label->device()); - } - - /* REQ: Runner.Validate.partition */ - for(auto &part : this->internal->partitions) { - if(!part->validate(this->opts)) { - failures++; - continue; - } - - /* REQ: Runner.Validate.partition.Unique */ - const std::string &dev = part->device(); - const std::string maybe_p(::isdigit(dev[dev.size() - 1]) ? "p" : ""); - std::string name = dev + maybe_p + std::to_string(part->partno()); - if(seen_parts.find(name) != seen_parts.end()) { - failures++; - output_error("installfile:" + std::to_string(part->lineno()), - "partition: partition #" + - std::to_string(part->partno()) + - " already exists on device " + part->device()); - } - seen_parts.insert(name); - } - - /* REQ: Runner.Validate.lvm_pv */ - for(auto &pv : this->internal->lvm_pvs) { - if(!pv->validate(this->opts)) { - failures++; - continue; - } - - /* We don't actually have a requirement, but... */ - if(seen_pvs.find(pv->value()) != seen_pvs.end()) { - failures++; - output_error("installfile:" + std::to_string(pv->lineno()), - "lvm_pv: a physical volume already exists on device " - + pv->value()); - } - seen_pvs.insert(pv->value()); - - /* REQ: Runner.Validate.lvm_pv.Block */ - if(opts.test(InstallEnvironment)) { -#ifdef HAS_INSTALL_ENV - if(!fs::exists(pv->value(), ec) && - seen_parts.find(pv->value()) == seen_parts.end()) { - failures++; - output_error("installfile:" + std::to_string(pv->lineno()), - "lvm_pv: device " + pv->value() + - " does not exist"); - } -#endif /* HAS_INSTALL_ENV */ - } - } - - /* REQ: Runner.Validate.lvm_vg */ - for(auto &vg : this->internal->lvm_vgs) { - if(!vg->validate(this->opts)) { - failures++; - continue; - } - - if(seen_vg_names.find(vg->name()) != seen_vg_names.end()) { - failures++; - output_error("installfile:" + std::to_string(vg->lineno()), - "lvm_vg: duplicate volume group name specified", - vg->name() + " already given"); - } - seen_vg_names.insert(vg->name()); - - if(seen_vg_pvs.find(vg->pv()) != seen_vg_pvs.end()) { - failures++; - output_error("installfile:" + std::to_string(vg->lineno()), - "lvm_vg: a volume group already exists on " + - vg->pv()); - } - seen_vg_pvs.insert(vg->pv()); - - /* REQ: Runner.Validate.lvm_vg.PhysicalVolume */ - /* If we already know a PV is being created there, we know it's fine */ - if(seen_pvs.find(vg->pv()) == seen_pvs.end()) { - /* Okay, let's see if a PV already exists there... */ - if(opts.test(InstallEnvironment)) { -#ifdef HAS_INSTALL_ENV - if(!vg->test_pv(opts)) { - failures++; - output_error("installfile:" + std::to_string(vg->lineno()), - "lvm_vg: a physical volume does not exist on " - + vg->pv()); - } -#endif /* HAS_INSTALL_ENV */ - } else { - /* We can't tell if we aren't running on the target. */ - output_warning("installfile:" + std::to_string(vg->lineno()), - "lvm_vg: please ensure an LVM physical volume " - "already exists at " + vg->pv()); - } - } - } - - /* REQ: Runner.Validate.lvm_lv */ - for(auto &lv : this->internal->lvm_lvs) { - const std::string lvpath(lv->vg() + "/" + lv->name()); - if(!lv->validate(this->opts)) { - failures++; - continue; - } - - /* REQ: Runner.Validate.lvm_lv.Name */ - if(seen_lvs.find(lvpath) != seen_lvs.end()) { - failures++; - output_error("installfile:" + std::to_string(lv->lineno()), - "lvm_lv: a volume with the name " + lv->name() + - " already exists on the volume group " + lv->vg()); - } - seen_lvs.insert(lvpath); - - /* REQ: Runner.Validate.lvm_lv.VolumeGroup */ - if(seen_vg_names.find(lv->vg()) == seen_vg_names.end()) { - /* Let's make sure it still exists, if we are running in the IE */ - if(opts.test(InstallEnvironment)) { -#ifdef HAS_INSTALL_ENV - if(!fs::exists("/dev/" + lv->vg())) { - failures++; - output_error("installfile:" + std::to_string(lv->lineno()), - "lvm_lv: volume group " + lv->vg() + - " does not exist"); - } -#endif /* HAS_INSTALL_ENV */ - } - } - } - -#define CHECK_EXIST_PART_LV(device, key, line) \ - if(!fs::exists(device, ec) &&\ - seen_parts.find(device) == seen_parts.end() &&\ - seen_lvs.find(device.substr(5)) == seen_lvs.end()) {\ - failures++;\ - output_error("installfile:" + std::to_string(line),\ - std::string(key) + ": device " + device +\ - " does not exist");\ - } - - /* REQ: Runner.Validate.encrypt */ - for(auto &crypt : this->internal->luks) { - if(!crypt->validate(this->opts)) { - failures++; - continue; - } - - /* REQ: Runner.Validate.encrypt.Unique */ - if(seen_luks.find(crypt->device()) != seen_luks.end()) { - failures++; - output_error("installfile:" + std::to_string(crypt->lineno()), - "encrypt: encryption is already scheduled for " + - crypt->device()); - } - seen_luks.insert(crypt->device()); - - /* REQ: Runner.Validate.encrypt.Block */ - if(opts.test(InstallEnvironment)) { -#ifdef HAS_INSTALL_ENV - CHECK_EXIST_PART_LV(crypt->device(), "encrypt", crypt->lineno()) -#endif /* HAS_INSTALL_ENV */ - } - } - - /* REQ: Runner.Validate.fs */ - for(auto &fs : this->internal->fses) { - if(!fs->validate(this->opts)) { - failures++; - continue; - } - - /* REQ: Runner.Validate.fs.Unique */ - if(seen_fses.find(fs->device()) != seen_fses.end()) { - failures++; - output_error("installfile:" + std::to_string(fs->lineno()), - "fs: a filesystem is already scheduled to be " - "created on " + fs->device()); - } - seen_fses.insert(fs->device()); - - /* REQ: Runner.Validate.fs.Block */ - if(opts.test(InstallEnvironment)) { -#ifdef HAS_INSTALL_ENV - CHECK_EXIST_PART_LV(fs->device(), "fs", fs->lineno()) -#endif /* HAS_INSTALL_ENV */ - } - } - - /* REQ: Runner.Validate.mount */ - for(auto &mount : this->internal->mounts) { - if(!mount->validate(this->opts)) { - failures++; - continue; - } - - /* REQ: Runner.Validate.mount.Unique */ - if(seen_mounts.find(mount->mountpoint()) != seen_mounts.end()) { - failures++; - output_error("installfile:" + std::to_string(mount->lineno()), - "mount: mountpoint " + mount->mountpoint() + - " has already been specified; " + mount->device() + - " is a duplicate"); - } - seen_mounts.insert(mount->mountpoint()); - - /* REQ: Runner.Validate.mount.Block */ - if(opts.test(InstallEnvironment)) { -#ifdef HAS_INSTALL_ENV - CHECK_EXIST_PART_LV(mount->device(), "mount", mount->lineno()) -#endif /* HAS_INSTALL_ENV */ - } - } - -#undef CHECK_EXIST_PART_LV - - /* REQ: Runner.Validate.mount.Root */ - if(seen_mounts.find("/") == seen_mounts.end()) { - failures++; - output_error("installfile:0", "mount: no root mount specified"); - } - - output_log("validator", "0", "installfile", - std::to_string(failures) + " failure(s).", ""); - return (failures == 0); -} - - -bool Script::execute() const { - bool success; - error_code ec; - - /* assume create_directory will give us the error if removal fails */ - if(fs::exists("/tmp/horizon", ec)) { - fs::remove_all("/tmp/horizon", ec); - } - - if(!fs::create_directory("/tmp/horizon", ec)) { - output_error("internal", "could not create temporary directory", - ec.message()); - return false; - } - - /* REQ: Runner.Execute.Verify */ - output_step_start("validate"); - success = this->validate(); - output_step_end("validate"); - if(!success) { - /* REQ: Runner.Execute.Verify.Failure */ - output_error("validator", "The HorizonScript failed validation", - "Check the output from the validator."); - return false; - } - -#define EXECUTE_FAILURE(phase) \ - output_error(phase, "The HorizonScript failed to execute",\ - "Check the log file for more details.") - - /**************** DISK SETUP ****************/ - output_step_start("disk"); -#ifdef HAS_INSTALL_ENV - if(opts.test(InstallEnvironment)) ped_device_probe_all(); -#endif /* HAS_INSTALL_ENV */ - /* REQ: Runner.Execute.diskid */ - for(auto &diskid : this->internal->diskids) { - if(!diskid->execute(opts)) { - EXECUTE_FAILURE("disk"); - return false; - } - } - - /* REQ: Runner.Execute.disklabel */ - for(auto &label : this->internal->disklabels) { - if(!label->execute(opts)) { - EXECUTE_FAILURE("disk"); - return false; - } - } - - /* REQ: Runner.Execute.partition */ - /* Ensure partitions are created in on-disk order. */ - std::sort(this->internal->partitions.begin(), this->internal->partitions.end(), - [](std::unique_ptr const &e1, - std::unique_ptr const &e2) { - return (e1->device() + "p" + std::to_string(e1->partno())) < - (e2->device() + "p" + std::to_string(e2->partno())); - }); - for(auto &part : this->internal->partitions) { - if(!part->execute(opts)) { - EXECUTE_FAILURE("disk"); - return false; - } - } - - /* encrypt PVs */ - - /* REQ: Runner.Execute.lvm_pv */ - for(auto &pv : this->internal->lvm_pvs) { - if(!pv->execute(opts)) { - EXECUTE_FAILURE("disk"); - return false; - } - } - - /* REQ: Runner.Execute.lvm_vg */ - for(auto &vg : this->internal->lvm_vgs) { - if(!vg->execute(opts)) { - EXECUTE_FAILURE("disk"); - return false; - } - } - - /* REQ: Runner.Execute.lvm_lv */ - for(auto &lv : this->internal->lvm_lvs) { - if(!lv->execute(opts)) { - EXECUTE_FAILURE("disk"); - return false; - } - } - - /* encrypt */ - - /* REQ: Runner.Execute.fs */ - for(auto &fs : this->internal->fses) { - if(!fs->execute(opts)) { - EXECUTE_FAILURE("disk"); - return false; - } - } - - /* REQ: Runner.Execute.mount */ - /* Sort by mountpoint. - * This ensures that any subdirectory mounts come after their parent. */ - std::sort(this->internal->mounts.begin(), this->internal->mounts.end(), - [](std::unique_ptr const &e1, - std::unique_ptr const &e2) { - return e1->mountpoint() < e2->mountpoint(); - }); - for(auto &mount : this->internal->mounts) { - if(!mount->execute(opts)) { - EXECUTE_FAILURE("disk"); - return false; - } - } -#ifdef HAS_INSTALL_ENV - if(opts.test(InstallEnvironment)) ped_device_free_all(); -#endif /* HAS_INSTALL_ENV */ - output_step_end("disk"); - - /**************** PRE PACKAGE METADATA ****************/ - output_step_start("pre-metadata"); - - /* REQ: Runner.Execute.hostname */ - if(!this->internal->hostname->execute(opts)) { - EXECUTE_FAILURE("pre-metadata"); - return false; - } - - /* REQ: Runner.Execute.repository */ - if(opts.test(Simulate)) { - std::cout << "mkdir -p /target/etc/apk" << std::endl; - } -#ifdef HAS_INSTALL_ENV - else { - if(!fs::exists("/target/etc/apk", ec)) { - fs::create_directory("/target/etc/apk", ec); - if(ec) { - output_error("internal", "failed to initialise APK"); - EXECUTE_FAILURE("pre-metadata"); - return false; - } - } - } -#endif /* HAS_INSTALL_ENV */ - for(auto &repo : this->internal->repos) { - if(!repo->execute(opts)) { - EXECUTE_FAILURE("pre-metadata"); - return false; - } - } - -#ifdef NON_LIBRE_FIRMWARE - /* REQ: Runner.Execute.firmware */ - if(this->internal->firmware && this->internal->firmware->test()) { - this->internal->packages.insert("linux-firmware"); - } -#endif - output_step_end("pre-metadata"); - - /**************** NETWORK ****************/ - output_step_start("net"); - - if(!this->internal->ssids.empty()) { - std::ofstream wpao("/tmp/horizon/wpa_supplicant.conf", - std::ios_base::trunc); - if(wpao) { - wpao << "# Enable the control interface for wpa_cli and wpa_gui" - << std::endl - << "ctrl_interface=/var/run/wpa_supplicant" << std::endl - << "ctrl_interface_group=wheel" << std::endl - << "update_config=1" << std::endl; - wpao.close(); - } else { - output_error("internal", - "cannot write wireless networking configuration"); - } - - for(auto &ssid : this->internal->ssids) { - if(!ssid->execute(opts)) { - EXECUTE_FAILURE("net"); - /* "Soft" error. Not fatal. */ - } - } - - if(opts.test(Simulate)) { - std::ifstream wpai("/tmp/horizon/wpa_supplicant.conf"); - if(wpai) { - std::cout << "cat >/target/etc/wpa_supplicant/wpa_supplicant.conf " - << "<<- WPA_EOF" << std::endl - << wpai.rdbuf() << std::endl - << "WPA_EOF" << std::endl; - } else { - output_error("internal", - "cannot read wireless networking configuration"); - } - } else { - if(!fs::exists("/target/etc/wpa_supplicant", ec)) { - fs::create_directory("/target/etc/wpa_supplicant", ec); - } - fs::copy_file("/tmp/horizon/wpa_supplicant.conf", - "/target/etc/wpa_supplicant/wpa_supplicant.conf", - fs_overwrite, ec); - if(ec) { - output_error("internal", "cannot save wireless networking " - "configuration to target", ec.message()); - } - } - } - - if(!this->internal->addresses.empty()) { - fs::path netifrc_dir("/tmp/horizon/netifrc"); - if(!fs::exists(netifrc_dir) && - !fs::create_directory(netifrc_dir, ec)) { - output_error("internal", "cannot create temporary directory", - ec.message()); - } - - for(auto &addr : this->internal->addresses) { - if(!addr->execute(opts)) { - EXECUTE_FAILURE("net"); - /* "Soft" error. Not fatal. */ - } - } - - std::ostringstream conf; - for(auto &&var_dent : fs::directory_iterator(netifrc_dir)) { - const std::string variable(var_dent.path().filename().string()); - std::ifstream contents(var_dent.path().string()); - if(!contents) { - output_error("internal", "cannot read network configuration"); - EXECUTE_FAILURE("net"); - continue; - } - conf << variable << "=\""; - if(contents.rdbuf()->in_avail()) conf << contents.rdbuf(); - conf << "\"" << std::endl; - } - - if(opts.test(Simulate)) { - std::cout << "mkdir -p /target/etc/conf.d" << std::endl; - std::cout << "cat >>/target/etc/conf.d/net <<- NETCONF_EOF" - << std::endl << conf.str() << std::endl - << "NETCONF_EOF" << std::endl; - } else { - if(!fs::exists("/target/etc/conf.d")) { - fs::create_directory("/target/etc/conf.d", ec); - if(ec) { - output_error("internal", "could not create /etc/conf.d " - "directory", ec.message()); - } - } - std::ofstream conf_file("/target/etc/conf.d/net", - std::ios_base::app); - if(!conf_file) { - output_error("internal", "cannot save network configuration " - "to target"); - EXECUTE_FAILURE("net"); - } else { - conf_file << conf.str(); - } - } - } - - if(!this->internal->network->execute(opts)) { - EXECUTE_FAILURE("net"); - return false; - } - - if(this->internal->network->test()) { - bool do_wpa = !this->internal->ssids.empty(); - - if(opts.test(Simulate)) { - if(do_wpa) { - std::cout << "cp /target/etc/wpa_supplicant/wpa_supplicant.conf " - << "/etc/wpa_supplicant/wpa_supplicant.conf" - << std::endl; - } - std::cout << "cp /target/etc/conf.d/net /etc/conf.d/net" - << std::endl; - } else { - if(do_wpa) { - fs::copy_file("/target/etc/wpa_supplicant/wpa_supplicant.conf", - "/etc/wpa_supplicant/wpa_supplicant.conf", - fs_overwrite, ec); - if(ec) { - output_error("internal", "cannot use wireless configuration " - "during installation", ec.message()); - EXECUTE_FAILURE("net"); - } - } - fs::copy_file("/target/etc/conf.d/net", "/etc/conf.d/net", - fs_overwrite, ec); - if(ec) { - output_error("internal", "cannot use networking configuration " - "during installation", ec.message()); - EXECUTE_FAILURE("net"); - return false; - } - } - } - output_step_end("net"); - - /**************** PKGDB ****************/ - output_step_start("pkgdb"); - - /* REQ: Runner.Execute.signingkey */ - for(auto &key : this->internal->repo_keys) { - if(!key->execute(opts)) { - EXECUTE_FAILURE("pkgdb"); - return false; - } - } - - /* REQ: Runner.Execute.pkginstall.APKDB */ - output_info("internal", "initialising APK"); - if(opts.test(Simulate)) { - std::cout << "apk --root /target --initdb add" << std::endl; - } else { - if(system("apk --root /target --initdb add") != 0) { - EXECUTE_FAILURE("pkgdb"); - return false; - } - } - - /* REQ: Runner.Execute.pkginstall */ - output_info("internal", "installing packages to target"); - std::ostringstream pkg_list; - for(auto &pkg : this->internal->packages) { - pkg_list << pkg << " "; - } - if(opts.test(Simulate)) { - std::cout << "apk --root /target update" << std::endl; - std::cout << "apk --root /target add " << pkg_list.str() << std::endl; - } else { - if(system("apk --root /target update") != 0) { - EXECUTE_FAILURE("pkgdb"); - return false; - } - std::string apk_invoc = "apk --root /target add " + pkg_list.str(); - if(system(apk_invoc.c_str()) != 0) { - EXECUTE_FAILURE("pkgdb"); - return false; - } - } - - output_step_end("pkgdb"); - - /**************** POST PACKAGE METADATA ****************/ - output_step_start("post-metadata"); - - if(!this->internal->rootpw->execute(opts)) { - EXECUTE_FAILURE("post-metadata"); - return false; - } - - if(this->internal->lang && !this->internal->lang->execute(opts)) { - EXECUTE_FAILURE("post-metadata"); - return false; - } - - /* keymap */ - /* UserAccounts */ - - if(!this->internal->tzone->execute(opts)) { - EXECUTE_FAILURE("post-metadata"); - return false; - } - - output_step_end("post-metadata"); - return true; -} - } diff --git a/hscript/script_e.cc b/hscript/script_e.cc new file mode 100644 index 0000000..a35b818 --- /dev/null +++ b/hscript/script_e.cc @@ -0,0 +1,400 @@ +/* + * script_e.cc - Implementation of Script::execute + * 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 +#ifdef HAS_INSTALL_ENV +# include +#endif /* HAS_INSTALL_ENV */ + +#include "script.hh" +#include "script_i.hh" + +#include "util/filesystem.hh" + +namespace Horizon { + +bool Script::execute() const { + bool success; + error_code ec; + + /* assume create_directory will give us the error if removal fails */ + if(fs::exists("/tmp/horizon", ec)) { + fs::remove_all("/tmp/horizon", ec); + } + + if(!fs::create_directory("/tmp/horizon", ec)) { + output_error("internal", "could not create temporary directory", + ec.message()); + return false; + } + + /* REQ: Runner.Execute.Verify */ + output_step_start("validate"); + success = this->validate(); + output_step_end("validate"); + if(!success) { + /* REQ: Runner.Execute.Verify.Failure */ + output_error("validator", "The HorizonScript failed validation", + "Check the output from the validator."); + return false; + } + +#define EXECUTE_FAILURE(phase) \ + output_error(phase, "The HorizonScript failed to execute",\ + "Check the log file for more details.") + + /**************** DISK SETUP ****************/ + output_step_start("disk"); +#ifdef HAS_INSTALL_ENV + if(opts.test(InstallEnvironment)) ped_device_probe_all(); +#endif /* HAS_INSTALL_ENV */ + /* REQ: Runner.Execute.diskid */ + for(auto &diskid : internal->diskids) { + if(!diskid->execute(opts)) { + EXECUTE_FAILURE("diskid"); + return false; + } + } + + /* REQ: Runner.Execute.disklabel */ + for(auto &label : internal->disklabels) { + if(!label->execute(opts)) { + EXECUTE_FAILURE("disklabel"); + return false; + } + } + + /* REQ: Runner.Execute.partition */ + /* Ensure partitions are created in on-disk order. */ + std::sort(internal->partitions.begin(), internal->partitions.end(), + [](std::unique_ptr const &e1, + std::unique_ptr const &e2) { + return (e1->device() + "p" + std::to_string(e1->partno())) < + (e2->device() + "p" + std::to_string(e2->partno())); + }); + for(auto &part : internal->partitions) { + if(!part->execute(opts)) { + EXECUTE_FAILURE("partition"); + return false; + } + } + + /* encrypt PVs */ + + /* REQ: Runner.Execute.lvm_pv */ + for(auto &pv : internal->lvm_pvs) { + if(!pv->execute(opts)) { + EXECUTE_FAILURE("lvm_pv"); + return false; + } + } + + /* REQ: Runner.Execute.lvm_vg */ + for(auto &vg : internal->lvm_vgs) { + if(!vg->execute(opts)) { + EXECUTE_FAILURE("lvm_vg"); + return false; + } + } + + /* REQ: Runner.Execute.lvm_lv */ + for(auto &lv : internal->lvm_lvs) { + if(!lv->execute(opts)) { + EXECUTE_FAILURE("lvm_lv"); + return false; + } + } + + /* encrypt */ + + /* REQ: Runner.Execute.fs */ + for(auto &fs : internal->fses) { + if(!fs->execute(opts)) { + EXECUTE_FAILURE("fs"); + return false; + } + } + + /* REQ: Runner.Execute.mount */ + /* Sort by mountpoint. + * This ensures that any subdirectory mounts come after their parent. */ + std::sort(internal->mounts.begin(), internal->mounts.end(), + [](std::unique_ptr const &e1, + std::unique_ptr const &e2) { + return e1->mountpoint() < e2->mountpoint(); + }); + for(auto &mount : internal->mounts) { + if(!mount->execute(opts)) { + EXECUTE_FAILURE("mount"); + return false; + } + } +#ifdef HAS_INSTALL_ENV + if(opts.test(InstallEnvironment)) ped_device_free_all(); +#endif /* HAS_INSTALL_ENV */ + output_step_end("disk"); + + /**************** PRE PACKAGE METADATA ****************/ + output_step_start("pre-metadata"); + + /* REQ: Runner.Execute.hostname */ + if(!internal->hostname->execute(opts)) { + EXECUTE_FAILURE("hostname"); + return false; + } + + /* REQ: Runner.Execute.repository */ + if(opts.test(Simulate)) { + std::cout << "mkdir -p /target/etc/apk" << std::endl; + } +#ifdef HAS_INSTALL_ENV + else { + if(!fs::exists("/target/etc/apk", ec)) { + fs::create_directory("/target/etc/apk", ec); + if(ec) { + output_error("internal", "failed to initialise APK"); + EXECUTE_FAILURE("pre-metadata"); + return false; + } + } + } +#endif /* HAS_INSTALL_ENV */ + for(auto &repo : internal->repos) { + if(!repo->execute(opts)) { + EXECUTE_FAILURE("repository"); + return false; + } + } + +#ifdef NON_LIBRE_FIRMWARE + /* REQ: Runner.Execute.firmware */ + if(internal->firmware && internal->firmware->test()) { + internal->packages.insert("linux-firmware"); + } +#endif + output_step_end("pre-metadata"); + + /**************** NETWORK ****************/ + output_step_start("net"); + + if(!this->internal->ssids.empty()) { + std::ofstream wpao("/tmp/horizon/wpa_supplicant.conf", + std::ios_base::trunc); + if(wpao) { + wpao << "# Enable the control interface for wpa_cli and wpa_gui" + << std::endl + << "ctrl_interface=/var/run/wpa_supplicant" << std::endl + << "ctrl_interface_group=wheel" << std::endl + << "update_config=1" << std::endl; + wpao.close(); + } else { + output_error("internal", + "cannot write wireless networking configuration"); + } + + for(auto &ssid : internal->ssids) { + if(!ssid->execute(opts)) { + EXECUTE_FAILURE("ssid"); + /* "Soft" error. Not fatal. */ + } + } + + if(opts.test(Simulate)) { + std::ifstream wpai("/tmp/horizon/wpa_supplicant.conf"); + if(wpai) { + std::cout << "cat >/target/etc/wpa_supplicant/wpa_supplicant.conf " + << "<<- WPA_EOF" << std::endl + << wpai.rdbuf() << std::endl + << "WPA_EOF" << std::endl; + } else { + output_error("internal", + "cannot read wireless networking configuration"); + } + } else { + if(!fs::exists("/target/etc/wpa_supplicant", ec)) { + fs::create_directory("/target/etc/wpa_supplicant", ec); + } + fs::copy_file("/tmp/horizon/wpa_supplicant.conf", + "/target/etc/wpa_supplicant/wpa_supplicant.conf", + fs_overwrite, ec); + if(ec) { + output_error("internal", "cannot save wireless networking " + "configuration to target", ec.message()); + } + } + } + + if(!internal->addresses.empty()) { + fs::path netifrc_dir("/tmp/horizon/netifrc"); + if(!fs::exists(netifrc_dir) && + !fs::create_directory(netifrc_dir, ec)) { + output_error("internal", "cannot create temporary directory", + ec.message()); + } + + for(auto &addr : internal->addresses) { + if(!addr->execute(opts)) { + EXECUTE_FAILURE("netaddress"); + /* "Soft" error. Not fatal. */ + } + } + + std::ostringstream conf; + for(auto &&var_dent : fs::directory_iterator(netifrc_dir)) { + const std::string variable(var_dent.path().filename().string()); + std::ifstream contents(var_dent.path().string()); + if(!contents) { + output_error("internal", "cannot read network configuration"); + EXECUTE_FAILURE("net-internal"); + continue; + } + conf << variable << "=\""; + if(contents.rdbuf()->in_avail()) conf << contents.rdbuf(); + conf << "\"" << std::endl; + } + + if(opts.test(Simulate)) { + std::cout << "mkdir -p /target/etc/conf.d" << std::endl; + std::cout << "cat >>/target/etc/conf.d/net <<- NETCONF_EOF" + << std::endl << conf.str() << std::endl + << "NETCONF_EOF" << std::endl; + } else { + if(!fs::exists("/target/etc/conf.d")) { + fs::create_directory("/target/etc/conf.d", ec); + if(ec) { + output_error("internal", "could not create /etc/conf.d " + "directory", ec.message()); + } + } + std::ofstream conf_file("/target/etc/conf.d/net", + std::ios_base::app); + if(!conf_file) { + output_error("internal", "cannot save network configuration " + "to target"); + EXECUTE_FAILURE("net-internal"); + } else { + conf_file << conf.str(); + } + } + } + + if(!internal->network->execute(opts)) { + EXECUTE_FAILURE("network"); + return false; + } + + if(internal->network->test()) { + bool do_wpa = !internal->ssids.empty(); + + if(opts.test(Simulate)) { + if(do_wpa) { + std::cout << "cp /target/etc/wpa_supplicant/wpa_supplicant.conf " + << "/etc/wpa_supplicant/wpa_supplicant.conf" + << std::endl; + } + std::cout << "cp /target/etc/conf.d/net /etc/conf.d/net" + << std::endl; + } else { + if(do_wpa) { + fs::copy_file("/target/etc/wpa_supplicant/wpa_supplicant.conf", + "/etc/wpa_supplicant/wpa_supplicant.conf", + fs_overwrite, ec); + if(ec) { + output_error("internal", "cannot use wireless configuration " + "during installation", ec.message()); + EXECUTE_FAILURE("network"); + } + } + fs::copy_file("/target/etc/conf.d/net", "/etc/conf.d/net", + fs_overwrite, ec); + if(ec) { + output_error("internal", "cannot use networking configuration " + "during installation", ec.message()); + EXECUTE_FAILURE("network"); + return false; + } + } + } + output_step_end("net"); + + /**************** PKGDB ****************/ + output_step_start("pkgdb"); + + /* REQ: Runner.Execute.signingkey */ + for(auto &key : internal->repo_keys) { + if(!key->execute(opts)) { + EXECUTE_FAILURE("signingkey"); + return false; + } + } + + /* REQ: Runner.Execute.pkginstall.APKDB */ + output_info("internal", "initialising APK"); + if(opts.test(Simulate)) { + std::cout << "apk --root /target --initdb add" << std::endl; + } else { + if(system("apk --root /target --initdb add") != 0) { + EXECUTE_FAILURE("pkginstall"); + return false; + } + } + + /* REQ: Runner.Execute.pkginstall */ + output_info("internal", "installing packages to target"); + std::ostringstream pkg_list; + for(auto &pkg : this->internal->packages) { + pkg_list << pkg << " "; + } + if(opts.test(Simulate)) { + std::cout << "apk --root /target update" << std::endl; + std::cout << "apk --root /target add " << pkg_list.str() << std::endl; + } else { + if(system("apk --root /target update") != 0) { + EXECUTE_FAILURE("pkginstall"); + return false; + } + std::string apk_invoc = "apk --root /target add " + pkg_list.str(); + if(system(apk_invoc.c_str()) != 0) { + EXECUTE_FAILURE("pkginstall"); + return false; + } + } + + output_step_end("pkgdb"); + + /**************** POST PACKAGE METADATA ****************/ + output_step_start("post-metadata"); + + if(!internal->rootpw->execute(opts)) { + EXECUTE_FAILURE("rootpw"); + return false; + } + + if(internal->lang && !internal->lang->execute(opts)) { + EXECUTE_FAILURE("language"); + return false; + } + + /* keymap */ + /* UserAccounts */ + + if(!internal->tzone->execute(opts)) { + EXECUTE_FAILURE("timezone"); + return false; + } + + output_step_end("post-metadata"); + return true; +} + +} diff --git a/hscript/script_i.hh b/hscript/script_i.hh new file mode 100644 index 0000000..cb7da13 --- /dev/null +++ b/hscript/script_i.hh @@ -0,0 +1,289 @@ +/* + * script_i.hh - Implementation of internal doodads for the Script class + * 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 + */ + +#ifndef HSCRIPT_SCRIPT_INTERNAL_HH_ +#define HSCRIPT_SCRIPT_INTERNAL_HH_ + +#include +#include +#include +#include +#include + +#include "disk.hh" +#include "meta.hh" +#include "network.hh" +#include "user.hh" + +using namespace Horizon::Keys; + +namespace Horizon { + +/*! Describes a user account. */ +struct UserDetail { + std::unique_ptr name; + std::unique_ptr alias; + std::unique_ptr passphrase; + std::unique_ptr icon; + std::vector< std::unique_ptr > groups; +}; + +struct Script::ScriptPrivate { + /*! Determines whether or not to enable networking. */ + std::unique_ptr network; + /*! The target system's hostname. */ + std::unique_ptr hostname; + /*! The packages to install to the target system. */ + std::set packages; + /*! The root shadow line. */ + std::unique_ptr rootpw; + /*! The system language. */ + std::unique_ptr lang; + /*! The system keymap. */ + std::unique_ptr keymap; + /*! The system timezone. */ + std::unique_ptr tzone; + + /*! Network addressing configuration */ + std::vector< std::unique_ptr > addresses; + /*! Network nameserver resolver addresses */ + std::vector< std::unique_ptr > nses; + /*! Wireless networking configuration */ + std::vector< std::unique_ptr > ssids; + + /*! APK repositories */ + std::vector< std::unique_ptr > repos; + /*! APK repository keys */ + std::vector< std::unique_ptr > repo_keys; + + /*! User account information */ + std::map< std::string, std::unique_ptr > accounts; + + /*! Disk identification keys */ + std::vector< std::unique_ptr > diskids; + /*! Disklabel configuration keys */ + std::vector< std::unique_ptr > disklabels; + /*! Partition creation keys */ + std::vector< std::unique_ptr > partitions; + /*! LVM physical volume keys */ + std::vector< std::unique_ptr > lvm_pvs; + /*! LVM volume group keys */ + std::vector< std::unique_ptr > lvm_vgs; + /*! LVM logical volume keys */ + std::vector< std::unique_ptr > lvm_lvs; + /*! LUKS creation keys */ + std::vector< std::unique_ptr > luks; + /*! Filesystem creation keys */ + std::vector< std::unique_ptr > fses; + /*! Target system's mountpoints. */ + std::vector< std::unique_ptr > mounts; + +#ifdef NON_LIBRE_FIRMWARE + std::unique_ptr firmware; +#endif + + /*! Store +key_obj+ representing the key +key_name+. + * @param key_name The name of the key that is being stored. + * @param obj The Key object associated with the key. + * @param errors Output parameter: if given, incremented on error. + * @param warnings Output parameter: if given, incremented on warning. + * @param opts Script parsing options. + */ + bool store_key(const std::string &key_name, Key *obj, int lineno, + int *errors, int *warnings, const ScriptOptions &opts); + +#define DUPLICATE_ERROR(OBJ, KEY, OLD_VAL) \ + std::string err_str("previous value was ");\ + err_str += OLD_VAL;\ + err_str += " at installfile:" + std::to_string(OBJ->lineno());\ + if(errors) *errors += 1;\ + output_error("installfile:" + std::to_string(line),\ + "duplicate value for key '" + std::string(KEY) + "'",\ + err_str); + + bool store_network(Key* obj, int line, int *errors, int *, ScriptOptions) { + if(network) { + DUPLICATE_ERROR(network, "network", + network->test() ? "true" : "false") + return false; + } + std::unique_ptr net(dynamic_cast(obj)); + network = std::move(net); + return true; + } + + bool store_hostname(Key* obj, int line, int *errors, int *, ScriptOptions) { + if(hostname) { + DUPLICATE_ERROR(hostname, "hostname", hostname->value()) + return false; + } + std::unique_ptr name(dynamic_cast(obj)); + hostname = std::move(name); + return true; + } + + bool store_pkginstall(Key* obj, int line, int *, int *warnings, + ScriptOptions opts) { + PkgInstall *install = dynamic_cast(obj); + for(auto &pkg : install->packages()) { + if(opts.test(StrictMode) && packages.find(pkg) != packages.end()) { + if(warnings) *warnings += 1; + output_warning("installfile:" + std::to_string(line), + "pkginstall: package '" + pkg + + "' has already been specified"); + continue; + } + packages.insert(pkg); + } + delete install; + return true; + } + + bool store_rootpw(Key* obj, int line, int *errors, int *, ScriptOptions) { + if(rootpw) { + DUPLICATE_ERROR(rootpw, "rootpw", "an encrypted passphrase") + return false; + } + std::unique_ptr r(dynamic_cast(obj)); + rootpw = std::move(r); + return true; + } + + bool store_firmware(Key *obj, int line, int *errors, int *, ScriptOptions) { + std::unique_ptr f(dynamic_cast(obj)); +#ifdef NON_LIBRE_FIRMWARE + if(firmware) { + DUPLICATE_ERROR(firmware, "firmware", + (firmware->test()) ? "true" : "false") + return false; + } + firmware = std::move(f); + return true; +#else + assert(!f->test()); + return true; +#endif + } + + bool store_lang(Key *obj, int line, int *errors, int *, ScriptOptions) { + if(lang) { + DUPLICATE_ERROR(lang, "language", lang->value()) + return false; + } + std::unique_ptr l(dynamic_cast(obj)); + lang = std::move(l); + return true; + } + + bool store_keymap(Key *obj, int line, int *errors, int *, ScriptOptions) { + if(keymap) { + DUPLICATE_ERROR(keymap, "keymap", keymap->value()) + return false; + } + std::unique_ptr k(dynamic_cast(obj)); + keymap = std::move(k); + return true; + } + + bool store_timezone(Key *obj, int line, int *errors, int *, ScriptOptions) { + if(tzone) { + DUPLICATE_ERROR(tzone, "timezone", tzone->value()) + return false; + } + std::unique_ptr t(dynamic_cast(obj)); + tzone = std::move(t); + return true; + } + + bool store_username(Key *obj, int line, int *errors, int *, ScriptOptions) { + if(accounts.size() >= 255) { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(line), + "username: too many users", + "you may only specify 255 users"); + return false; + } + std::unique_ptr name(dynamic_cast(obj)); + if(accounts.find(name->value()) != accounts.end()) { + DUPLICATE_ERROR((*accounts.find(name->value())).second->name, + "username", "assigned") + return false; + } + std::unique_ptr detail(new UserDetail); + detail->name = std::move(name); + accounts.insert(std::make_pair(detail->name->value(), + std::move(detail))); + return true; + } + +#define GET_USER_DETAIL(OBJ, KEY) \ + if(accounts.find(OBJ->username()) == accounts.end()) {\ + if(errors) *errors += 1;\ + output_error("installfile:" + std::to_string(line),\ + std::string(KEY) + ": account name " + OBJ->username() +\ + " is unknown");\ + return false;\ + }\ + UserDetail *detail = (*accounts.find(OBJ->username())).second.get(); + + bool store_useralias(Key* obj, int line, int *errors, int *, ScriptOptions) { + std::unique_ptr alias(dynamic_cast(obj)); + GET_USER_DETAIL(alias, "useralias") + /* REQ: Runner.Validate.useralias.Unique */ + if(detail->alias) { + DUPLICATE_ERROR(detail->alias, "useralias", detail->alias->alias()) + return false; + } + detail->alias = std::move(alias); + return true; + } + + bool store_userpw(Key *obj, int line, int *errors, int *, ScriptOptions) { + std::unique_ptr pw(dynamic_cast(obj)); + GET_USER_DETAIL(pw, "userpw") + /* REQ: Runner.Validate.userpw.Unique */ + if(detail->passphrase) { + DUPLICATE_ERROR(detail->passphrase, "userpw", + "an encrypted passphrase") + return false; + } + detail->passphrase = std::move(pw); + return true; + } + + bool store_usericon(Key *obj, int line, int *errors, int *, ScriptOptions) { + std::unique_ptr icon(dynamic_cast(obj)); + GET_USER_DETAIL(icon, "usericon") + /* REQ: Runner.Validate.usericon.Unique */ + if(detail->icon) { + DUPLICATE_ERROR(detail->icon, "usericon", detail->icon->icon()) + return false; + } + detail->icon = std::move(icon); + return true; + } + + bool store_usergroups(Key* obj, int line, int *errors, int *, + ScriptOptions) { + std::unique_ptr grp(dynamic_cast(obj)); + GET_USER_DETAIL(grp, "usergroups") + detail->groups.push_back(std::move(grp)); + return true; + } +#undef GET_USER_DETAIL + +#undef DUPLICATE_ERROR +}; + +} + +#endif /* !HSCRIPT_SCRIPT_INTERNAL_HH_ */ diff --git a/hscript/script_v.cc b/hscript/script_v.cc new file mode 100644 index 0000000..a5b85dc --- /dev/null +++ b/hscript/script_v.cc @@ -0,0 +1,565 @@ +/* + * script_v.cc - Implementation of global HorizonScript validation routines + * 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 +#include +#include +#include +#include + +#include "script.hh" +#include "script_i.hh" +#include "disk.hh" +#include "meta.hh" +#include "network.hh" +#include "user.hh" + +#include "util/filesystem.hh" +#include "util/output.hh" + +using namespace Horizon::Keys; +using Horizon::ScriptOptions; + +using std::to_string; + +namespace Horizon { + +/*! Perform all necessary validations on a single user account. + * @param name The username of the account. + * @param detail The UserDetail record of the account. + * @param opts The ScriptOptions in use. + * @returns A count of errors encountered, or 0 if the account is valid. + */ +int validate_one_account(const std::string &name, UserDetail *detail, + ScriptOptions opts) { + int failures = 0; + + /* REQ: Runner.Validate.username */ + if(!detail->name->validate(opts)) failures++; + + /* REQ: Runner.Validate.useralias */ + if(detail->alias && !detail->alias->validate(opts)) failures++; + + /* REQ: Runner.Validate.userpw */ + if(detail->passphrase && !detail->passphrase->validate(opts)) failures++; + + /* REQ: Runner.Validate.userpw.None */ + if(!detail->passphrase) { + long line = detail->name->lineno(); + output_warning("installfile:" + to_string(line), + "username: " + name + " has no set passphrase", + "This account will not be able to log in."); + } + + /* REQ: Runner.Validate.usericon */ + if(detail->icon && !detail->icon->validate(opts)) failures++; + + if(detail->groups.size() > 0) { + std::set seen_groups; + for(auto &group : detail->groups) { + /* REQ: Runner.Validate.usergroups */ + if(!group->validate(opts)) failures++; + + /* REQ: Runner.Validate.usergroups.Unique */ + const std::set these = group->groups(); + if(!std::all_of(these.begin(), these.end(), + [&seen_groups](std::string elem) { + return seen_groups.find(elem) == seen_groups.end(); + }) + ) { + output_error("installfile:" + to_string(group->lineno()), + "usergroups: duplicate group name specified"); + failures++; + } + seen_groups.insert(these.begin(), these.end()); + } + + /* REQ: Runner.Validate.usergroups.Count */ + if(seen_groups.size() > 16) { + output_error("installfile:0", "usergroups: " + name + + " is a member of more than 16 groups"); + failures++; + } + } + + return failures; +} + + +/*! Add the default repositories to the repo list. + * @param repos The list of repositories. + * @param firmware Whether to include firmware repository. + * The list +repos+ will be modified with the default repositories for + * Adélie Linux. Both system/ and user/ will be added. + */ +bool add_default_repos(std::vector> &repos, + bool firmware = false) { + Repository *sys_key = dynamic_cast( + Repository::parseFromData( + "https://distfiles.adelielinux.org/adelie/stable/system", 0, + nullptr, nullptr + ) + ); + if(!sys_key) { + output_error("internal", "failed to create default system repository"); + return false; + } + std::unique_ptr sys_repo(sys_key); + repos.push_back(std::move(sys_repo)); + Repository *user_key = dynamic_cast( + Repository::parseFromData( + "https://distfiles.adelielinux.org/adelie/stable/user", 0, + nullptr, nullptr + ) + ); + if(!user_key) { + output_error("internal", "failed to create default user repository"); + return false; + } + std::unique_ptr user_repo(user_key); + repos.push_back(std::move(user_repo)); + +#ifdef NON_LIBRE_FIRMWARE + /* REQ: Runner.Execute.firmware.Repository */ + if(firmware) { + Repository *fw_key = dynamic_cast( + Repository::parseFromData( + "https://distfiles.apkfission.net/adelie-stable/nonfree", + 0, nullptr, nullptr + ) + ); + if(!fw_key) { + output_error("internal", + "failed to create firmware repository"); + return false; + } + std::unique_ptr fw_repo(fw_key); + repos.push_back(std::move(fw_repo)); + } +#endif + return true; +} + + +/*! Add the default repository keys to the signing key list. + * @param keys The list of repository keys. + * The list +keys+ will be modified with the default repository signing keys + * for Adélie Linux. + */ +bool add_default_repo_keys(std::vector> &keys) { + SigningKey *key = dynamic_cast( + SigningKey::parseFromData( + "/etc/apk/keys/packages@adelielinux.org.pub", 0, nullptr, nullptr) + ); + if(!key) { + output_error("internal", "failed to create default repository signing key"); + return false; + } + std::unique_ptr repo_key(key); + keys.push_back(std::move(repo_key)); + return true; +} + + +bool Horizon::Script::validate() const { + int failures = 0; + std::set seen_diskids, seen_labels, seen_parts, seen_pvs, + seen_vg_names, seen_vg_pvs, seen_lvs, seen_fses, seen_mounts, + seen_luks; + std::map seen_iface; +#ifdef HAS_INSTALL_ENV + error_code ec; +#endif /* HAS_INSTALL_ENV */ + + /* REQ: Runner.Validate.network */ + if(!internal->network->validate(opts)) failures++; + + /* REQ: Runner.Validate.network.netaddress */ + if(internal->network->test() && internal->addresses.size() == 0) { + failures++; + output_error("installfile:0", "networking requires 'netaddress'", + "You need to specify at least one address to enable " + "networking."); + } + for(auto &address : internal->addresses) { + if(!address->validate(opts)) failures++; + + /* REQ: Runner.Validate.network.netaddress.Count */ + if(seen_iface.find(address->iface()) == seen_iface.end()) { + seen_iface.insert(std::make_pair(address->iface(), 1)); + } else { + seen_iface[address->iface()] += 1; + if(seen_iface[address->iface()] > 255) { + failures++; + output_error("installfile:" + std::to_string(address->lineno()), + "netaddress: interface '" + address->iface() + + "' has too many addresses assigned"); + } + } + } + + /* REQ: Runner.Validate.nameserver */ + for(auto &ns : internal->nses) { + if(!ns->validate(opts)) failures++; + } + + /* REQ: Runner.Validate.network.netssid */ + for(auto &ssid : internal->ssids) { + if(!ssid->validate(opts)) failures++; + } + + /* REQ: Runner.Validate.hostname */ + if(!internal->hostname->validate(opts)) failures++; + + /* REQ: Runner.Validate.rootpw */ + if(!internal->rootpw->validate(opts)) failures++; + + /* REQ: Runner.Validate.language */ + if(internal->lang && !internal->lang->validate(opts)) failures++; + + /* REQ: Runner.Validate.keymap */ + if(internal->keymap && !internal->keymap->validate(opts)) failures++; + +#ifdef NON_LIBRE_FIRMWARE + /* REQ: Runner.Validate.firmware */ + if(internal->firmware && !internal->firmware->validate(opts)) failures++; +#endif + + /* REQ: Runner.Execute.timezone */ + if(!internal->tzone) { + Timezone *utc = dynamic_cast + (Timezone::parseFromData("UTC", 0, &failures, nullptr)); + if(!utc) { + output_error("internal", "failed to create default timezone"); + return false; + } + std::unique_ptr zone(utc); + internal->tzone = std::move(zone); + } + + /* REQ: Runner.Validate.timezone */ + if(!internal->tzone->validate(opts)) failures++; + + /* REQ: Script.repository */ + if(internal->repos.size() == 0) { + if(!add_default_repos(internal->repos +#ifdef NON_LIBRE_FIRMWARE + , internal->firmware && internal->firmware->test() +#endif + )) { + return false; + } + } + + /* REQ: Runner.Validate.repository */ + for(auto &repo : internal->repos) { + if(!repo->validate(opts)) failures++; + } + if(internal->repos.size() > 10) { + failures++; + output_error("installfile:" + to_string(internal->repos[11]->lineno()), + "repository: too many repositories specified", + "You may only specify up to 10 repositories."); + } + + /* REQ: Script.signingkey */ + if(internal->repo_keys.size() == 0) { + if(!add_default_repo_keys(internal->repo_keys)) { + return false; + } + } + + /* REQ: Runner.Validate.signingkey */ + for(auto &key : internal->repo_keys) { + if(!key->validate(opts)) failures++; + } + if(internal->repo_keys.size() > 10) { + failures++; + output_error("installfile:" + + to_string(internal->repo_keys[11]->lineno()), + "signingkey: too many keys specified", + "You may only specify up to 10 repository keys."); + } + + for(auto &acct : internal->accounts) { + UserDetail *detail = acct.second.get(); + failures += validate_one_account(acct.first, detail, opts); + } + + /* REQ: Runner.Validate.diskid */ + for(auto &diskid : internal->diskids) { + if(!diskid->validate(opts)) { + failures++; + continue; + } + + /* REQ: Runner.Validate.diskid.Unique */ + if(seen_diskids.find(diskid->device()) != seen_diskids.end()) { + failures++; + output_error("installfile:" + to_string(diskid->lineno()), + "diskid: device " + diskid->device() + + " has already been identified"); + } + seen_diskids.insert(diskid->device()); + } + + /* REQ: Runner.Validate.disklabel */ + for(auto &label : internal->disklabels) { + if(!label->validate(opts)) { + failures++; + continue; + } + + /* REQ: Runner.Validate.disklabel.Unique */ + if(seen_labels.find(label->device()) != seen_labels.end()) { + failures++; + output_error("installfile:" + to_string(label->lineno()), + "disklabel: device " + label->device() + + " already has a label queued"); + } else { + seen_labels.insert(label->device()); + } + } + + /* REQ: Runner.Validate.partition */ + for(auto &part : internal->partitions) { + if(!part->validate(opts)) { + failures++; + continue; + } + + /* REQ: Runner.Validate.partition.Unique */ + const std::string &dev = part->device(); + const std::string maybe_p(::isdigit(dev[dev.size() - 1]) ? "p" : ""); + std::string name = dev + maybe_p + to_string(part->partno()); + if(seen_parts.find(name) != seen_parts.end()) { + failures++; + output_error("installfile:" + to_string(part->lineno()), + "partition: partition #" + to_string(part->partno()) + + " already exists on device " + part->device()); + } else { + seen_parts.insert(name); + } + } + + /* REQ: Runner.Validate.lvm_pv */ + for(auto &pv : internal->lvm_pvs) { + if(!pv->validate(opts)) { + failures++; + continue; + } + + /* We don't actually have a requirement, but... */ + if(seen_pvs.find(pv->value()) != seen_pvs.end()) { + failures++; + output_error("installfile:" + to_string(pv->lineno()), + "lvm_pv: a physical volume already exists on device " + + pv->value()); + } else { + seen_pvs.insert(pv->value()); + } + + /* REQ: Runner.Validate.lvm_pv.Block */ + if(opts.test(InstallEnvironment)) { +#ifdef HAS_INSTALL_ENV + if(!fs::exists(pv->value(), ec) && + seen_parts.find(pv->value()) == seen_parts.end()) { + failures++; + output_error("installfile:" + to_string(pv->lineno()), + "lvm_pv: device " + pv->value() + + " does not exist"); + } +#endif /* HAS_INSTALL_ENV */ + } + } + + /* REQ: Runner.Validate.lvm_vg */ + for(auto &vg : internal->lvm_vgs) { + if(!vg->validate(opts)) { + failures++; + continue; + } + + if(seen_vg_names.find(vg->name()) != seen_vg_names.end()) { + failures++; + output_error("installfile:" + to_string(vg->lineno()), + "lvm_vg: duplicate volume group name specified", + vg->name() + " already given"); + } else { + seen_vg_names.insert(vg->name()); + } + + if(seen_vg_pvs.find(vg->pv()) != seen_vg_pvs.end()) { + failures++; + output_error("installfile:" + to_string(vg->lineno()), + "lvm_vg: a volume group already exists on " + + vg->pv()); + } else { + seen_vg_pvs.insert(vg->pv()); + } + + /* REQ: Runner.Validate.lvm_vg.PhysicalVolume */ + /* If we already know a PV is being created there, we know it's fine */ + if(seen_pvs.find(vg->pv()) == seen_pvs.end()) { + /* Okay, let's see if a PV already exists there... */ + if(opts.test(InstallEnvironment)) { +#ifdef HAS_INSTALL_ENV + if(!vg->test_pv(opts)) { + failures++; + output_error("installfile:" + to_string(vg->lineno()), + "lvm_vg: a physical volume does not exist on " + + vg->pv()); + } +#endif /* HAS_INSTALL_ENV */ + } else { + /* We can't tell if we aren't running on the target. */ + output_warning("installfile:" + to_string(vg->lineno()), + "lvm_vg: please ensure an LVM physical volume " + "already exists at " + vg->pv()); + } + } + } + + /* REQ: Runner.Validate.lvm_lv */ + for(auto &lv : internal->lvm_lvs) { + const std::string lvpath(lv->vg() + "/" + lv->name()); + if(!lv->validate(opts)) { + failures++; + continue; + } + + /* REQ: Runner.Validate.lvm_lv.Name */ + if(seen_lvs.find(lvpath) != seen_lvs.end()) { + failures++; + output_error("installfile:" + to_string(lv->lineno()), + "lvm_lv: a volume with the name " + lv->name() + + " already exists on the volume group " + lv->vg()); + } else { + seen_lvs.insert(lvpath); + } + + /* REQ: Runner.Validate.lvm_lv.VolumeGroup */ + if(seen_vg_names.find(lv->vg()) == seen_vg_names.end()) { + /* Let's make sure it still exists, if we are running in the IE */ + if(opts.test(InstallEnvironment)) { +#ifdef HAS_INSTALL_ENV + if(!fs::exists("/dev/" + lv->vg())) { + failures++; + output_error("installfile:" + to_string(lv->lineno()), + "lvm_lv: volume group " + lv->vg() + + " does not exist"); + } +#endif /* HAS_INSTALL_ENV */ + } + } + } + +#define CHECK_EXIST_PART_LV(device, key, line) \ + if(!fs::exists(device, ec) &&\ + seen_parts.find(device) == seen_parts.end() &&\ + seen_lvs.find(device.substr(5)) == seen_lvs.end()) {\ + failures++;\ + output_error("installfile:" + to_string(line),\ + std::string(key) + ": device " + device +\ + " does not exist");\ + } + + /* REQ: Runner.Validate.encrypt */ + for(auto &crypt : internal->luks) { + if(!crypt->validate(opts)) { + failures++; + continue; + } + + /* REQ: Runner.Validate.encrypt.Unique */ + if(seen_luks.find(crypt->device()) != seen_luks.end()) { + failures++; + output_error("installfile:" + to_string(crypt->lineno()), + "encrypt: encryption is already scheduled for " + + crypt->device()); + } else { + seen_luks.insert(crypt->device()); + } + + /* REQ: Runner.Validate.encrypt.Block */ + if(opts.test(InstallEnvironment)) { +#ifdef HAS_INSTALL_ENV + CHECK_EXIST_PART_LV(crypt->device(), "encrypt", crypt->lineno()) +#endif /* HAS_INSTALL_ENV */ + } + } + + /* REQ: Runner.Validate.fs */ + for(auto &fs : internal->fses) { + if(!fs->validate(opts)) { + failures++; + continue; + } + + /* REQ: Runner.Validate.fs.Unique */ + if(seen_fses.find(fs->device()) != seen_fses.end()) { + failures++; + output_error("installfile:" + std::to_string(fs->lineno()), + "fs: a filesystem is already scheduled to be " + "created on " + fs->device()); + } + seen_fses.insert(fs->device()); + + /* REQ: Runner.Validate.fs.Block */ + if(opts.test(InstallEnvironment)) { +#ifdef HAS_INSTALL_ENV + CHECK_EXIST_PART_LV(fs->device(), "fs", fs->lineno()) +#endif /* HAS_INSTALL_ENV */ + } + } + + /* REQ: Runner.Validate.mount */ + for(auto &mount : internal->mounts) { + if(!mount->validate(opts)) { + failures++; + continue; + } + + /* REQ: Runner.Validate.mount.Unique */ + if(seen_mounts.find(mount->mountpoint()) != seen_mounts.end()) { + failures++; + output_error("installfile:" + to_string(mount->lineno()), + "mount: mountpoint " + mount->mountpoint() + + " has already been specified; " + mount->device() + + " is a duplicate"); + } else { + seen_mounts.insert(mount->mountpoint()); + } + + /* REQ: Runner.Validate.mount.Block */ + if(opts.test(InstallEnvironment)) { +#ifdef HAS_INSTALL_ENV + CHECK_EXIST_PART_LV(mount->device(), "mount", mount->lineno()) +#endif /* HAS_INSTALL_ENV */ + } + } + +#undef CHECK_EXIST_PART_LV + + /* REQ: Runner.Validate.mount.Root */ + if(seen_mounts.find("/") == seen_mounts.end()) { + failures++; + output_error("installfile:0", "mount: no root mount specified"); + } + + output_log("validator", "0", "installfile", + to_string(failures) + " failure(s).", ""); + return (failures == 0); +} + +} -- cgit v1.2.3-70-g09d2