summaryrefslogtreecommitdiff
path: root/hscript
diff options
context:
space:
mode:
authorA. Wilcox <AWilcox@Wilcox-Tech.com>2019-11-04 15:51:15 -0600
committerA. Wilcox <AWilcox@Wilcox-Tech.com>2019-11-04 15:51:15 -0600
commit0e5d52b23ae0b86e938905a332ad5b7439011dcc (patch)
treef97ab264328bec2ccdb7a9e5b54f3357d1ef3988 /hscript
parent5a44e287dd8a6142c7a2ad7ddbc0570554ab149b (diff)
downloadhorizon-0e5d52b23ae0b86e938905a332ad5b7439011dcc.tar.gz
horizon-0e5d52b23ae0b86e938905a332ad5b7439011dcc.tar.bz2
horizon-0e5d52b23ae0b86e938905a332ad5b7439011dcc.tar.xz
horizon-0e5d52b23ae0b86e938905a332ad5b7439011dcc.zip
hscript: Refactor script.cc and disk.cc for maintainability
Diffstat (limited to 'hscript')
-rw-r--r--hscript/CMakeLists.txt3
-rw-r--r--hscript/disk.cc305
-rw-r--r--hscript/disk_lvm.cc327
-rw-r--r--hscript/script.cc1338
-rw-r--r--hscript/script_e.cc400
-rw-r--r--hscript/script_i.hh289
-rw-r--r--hscript/script_v.cc565
7 files changed, 1661 insertions, 1566 deletions
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<unsigned long>(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<std::string> 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 <algorithm>
+#include <cstring> /* strcmp */
+#include <string>
+#ifdef HAS_INSTALL_ENV
+# include <blkid/blkid.h> /* 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<unsigned long>(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 <algorithm>
-#include <assert.h>
#include "util/filesystem.hh"
#include <fstream>
#include <iostream>
#include <map>
-#ifdef HAS_INSTALL_ENV
-# include <parted/parted.h>
-#endif /* HAS_INSTALL_ENV */
#include <set>
#include <sstream>
#include "script.hh"
+#include "script_i.hh"
#include "disk.hh"
#include "meta.hh"
#include "network.hh"
@@ -74,363 +71,95 @@ const std::map<std::string, key_parse_fn> valid_keys = {
namespace Horizon {
-/*! Describes a user account. */
-struct UserDetail {
- std::unique_ptr<Username> name;
- std::unique_ptr<UserAlias> alias;
- std::unique_ptr<UserPassphrase> passphrase;
- std::unique_ptr<UserIcon> icon;
- std::vector< std::unique_ptr<UserGroups> > groups;
-};
-
-struct Script::ScriptPrivate {
- /*! Determines whether or not to enable networking. */
- std::unique_ptr<Network> network;
- /*! The target system's hostname. */
- std::unique_ptr<Hostname> hostname;
- /*! The packages to install to the target system. */
- std::set<std::string> packages;
- /*! The root shadow line. */
- std::unique_ptr<RootPassphrase> rootpw;
- /*! The system language. */
- std::unique_ptr<Language> lang;
- /*! The system keymap. */
- std::unique_ptr<Keymap> keymap;
- /*! The system timezone. */
- std::unique_ptr<Timezone> tzone;
-
- /*! Network addressing configuration */
- std::vector< std::unique_ptr<NetAddress> > addresses;
- /*! Network nameserver resolver addresses */
- std::vector< std::unique_ptr<Nameserver> > nses;
- /*! Wireless networking configuration */
- std::vector< std::unique_ptr<NetSSID> > ssids;
-
- /*! APK repositories */
- std::vector< std::unique_ptr<Repository> > repos;
- /*! APK repository keys */
- std::vector< std::unique_ptr<SigningKey> > repo_keys;
-
- /*! User account information */
- std::map< std::string, std::unique_ptr<UserDetail> > accounts;
-
- /*! Disk identification keys */
- std::vector< std::unique_ptr<DiskId> > diskids;
- /*! Disklabel configuration keys */
- std::vector< std::unique_ptr<DiskLabel> > disklabels;
- /*! Partition creation keys */
- std::vector< std::unique_ptr<Partition> > partitions;
- /*! LVM physical volume keys */
- std::vector< std::unique_ptr<LVMPhysical> > lvm_pvs;
- /*! LVM volume group keys */
- std::vector< std::unique_ptr<LVMGroup> > lvm_vgs;
- /*! LVM logical volume keys */
- std::vector< std::unique_ptr<LVMVolume> > lvm_lvs;
- /*! LUKS creation keys */
- std::vector< std::unique_ptr<Encrypt> > luks;
- /*! Filesystem creation keys */
- std::vector< std::unique_ptr<Filesystem> > fses;
- /*! Target system's mountpoints. */
- std::vector< std::unique_ptr<Mount> > mounts;
-
-#ifdef NON_LIBRE_FIRMWARE
- std::unique_ptr<Firmware> 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<NetAddress> addr(dynamic_cast<NetAddress *>(obj));
- this->addresses.push_back(std::move(addr));
- return true;
- } else if(key_name == "nameserver") {
- std::unique_ptr<Nameserver> ns(dynamic_cast<Nameserver *>(obj));
- this->nses.push_back(std::move(ns));
- return true;
- } else if(key_name == "netssid") {
- std::unique_ptr<NetSSID> ssid(dynamic_cast<NetSSID *>(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<Repository> repo(dynamic_cast<Repository *>(obj));
- this->repos.push_back(std::move(repo));
- return true;
- } else if(key_name == "signingkey") {
- std::unique_ptr<SigningKey> key(dynamic_cast<SigningKey *>(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> diskid(dynamic_cast<DiskId *>(obj));
- this->diskids.push_back(std::move(diskid));
- return true;
- } else if(key_name == "disklabel") {
- std::unique_ptr<DiskLabel> l(dynamic_cast<DiskLabel *>(obj));
- this->disklabels.push_back(std::move(l));
- return true;
- } else if(key_name == "partition") {
- std::unique_ptr<Partition> p(dynamic_cast<Partition *>(obj));
- this->partitions.push_back(std::move(p));
- return true;
- } else if(key_name == "lvm_pv") {
- std::unique_ptr<LVMPhysical> pv(dynamic_cast<LVMPhysical *>(obj));
- this->lvm_pvs.push_back(std::move(pv));
- return true;
- } else if(key_name == "lvm_vg") {
- std::unique_ptr<LVMGroup> vg(dynamic_cast<LVMGroup *>(obj));
- this->lvm_vgs.push_back(std::move(vg));
- return true;
- } else if(key_name == "lvm_lv") {
- std::unique_ptr<LVMVolume> lv(dynamic_cast<LVMVolume *>(obj));
- this->lvm_lvs.push_back(std::move(lv));
- return true;
- } else if(key_name == "encrypt") {
- std::unique_ptr<Encrypt> e(dynamic_cast<Encrypt *>(obj));
- this->luks.push_back(std::move(e));
- return true;
- } else if(key_name == "fs") {
- std::unique_ptr<Filesystem> fs(dynamic_cast<Filesystem *>(obj));
- this->fses.push_back(std::move(fs));
- return true;
- } else if(key_name == "mount") {
- std::unique_ptr<Mount> mount(dynamic_cast<Mount *>(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<Network> net(dynamic_cast<Network *>(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<NetAddress> addr(dynamic_cast<NetAddress *>(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<Hostname> name(dynamic_cast<Hostname *>(obj));
- this->hostname = std::move(name);
+ } else if(key_name == "nameserver") {
+ std::unique_ptr<Nameserver> ns(dynamic_cast<Nameserver *>(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<PkgInstall *>(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<NetSSID> ssid(dynamic_cast<NetSSID *>(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<RootPassphrase> r(dynamic_cast<RootPassphrase *>(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<Repository> repo(dynamic_cast<Repository *>(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<Firmware> f(dynamic_cast<Firmware *>(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<SigningKey> key(dynamic_cast<SigningKey *>(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> diskid(dynamic_cast<DiskId *>(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<Language> l(dynamic_cast<Language *>(obj));
- this->lang = std::move(l);
+ } else if(key_name == "disklabel") {
+ std::unique_ptr<DiskLabel> l(dynamic_cast<DiskLabel *>(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<Keymap> k(dynamic_cast<Keymap *>(obj));
- this->keymap = std::move(k);
+ } else if(key_name == "partition") {
+ std::unique_ptr<Partition> p(dynamic_cast<Partition *>(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<Timezone> t(dynamic_cast<Timezone *>(obj));
- this->tzone = std::move(t);
+ } else if(key_name == "lvm_pv") {
+ std::unique_ptr<LVMPhysical> pv(dynamic_cast<LVMPhysical *>(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<Username> name(dynamic_cast<Username *>(obj));
- if(accounts.find(name->value()) != accounts.end()) {
- DUPLICATE_ERROR((*accounts.find(name->value())).second->name,
- "username", "assigned")
- return false;
- }
- std::unique_ptr<UserDetail> 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<LVMGroup> vg(dynamic_cast<LVMGroup *>(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<UserAlias> alias(dynamic_cast<UserAlias *>(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<LVMVolume> lv(dynamic_cast<LVMVolume *>(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<UserPassphrase> pw(dynamic_cast<UserPassphrase *>(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<Encrypt> e(dynamic_cast<Encrypt *>(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<UserIcon> icon(dynamic_cast<UserIcon *>(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<Filesystem> fs(dynamic_cast<Filesystem *>(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<UserGroups> grp(dynamic_cast<UserGroups *>(obj));
- GET_USER_DETAIL(grp, "usergroups")
- detail->groups.push_back(std::move(grp));
+ } else if(key_name == "mount") {
+ std::unique_ptr<Mount> mount(dynamic_cast<Mount *>(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<std::string> seen_groups;
- for(auto &group : detail->groups) {
- /* REQ: Runner.Validate.usergroups */
- if(!group->validate(opts)) {
- failures++;
- }
-
- /* REQ: Runner.Validate.usergroups.Unique */
- const std::set<std::string> 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<std::unique_ptr<Keys::Repository>> &repos) {
- Keys::Repository *sys_key = dynamic_cast<Keys::Repository *>(
- 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<Keys::Repository> sys_repo(sys_key);
- repos.push_back(std::move(sys_repo));
- Keys::Repository *user_key = dynamic_cast<Keys::Repository *>(
- 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<Keys::Repository> 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<Keys::Repository *>(
- 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<Keys::Repository> 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<std::unique_ptr<Keys::SigningKey>> &keys) {
- Keys::SigningKey *key = dynamic_cast<Keys::SigningKey *>(
- 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<Keys::SigningKey> repo_key(key);
- keys.push_back(std::move(repo_key));
- return true;
-}
-
-
-bool Script::validate() const {
- int failures = 0;
- std::set<std::string> seen_diskids, seen_labels, seen_parts, seen_pvs,
- seen_vg_names, seen_vg_pvs, seen_lvs, seen_fses, seen_mounts,
- seen_luks;
- std::map<const std::string, int> 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<Keys::Timezone *>(
- Horizon::Keys::Timezone::parseFromData("UTC", 0,
- &failures, nullptr)
- );
- if(!utc) {
- output_error("internal", "failed to create default timezone");
- return false;
- }
- std::unique_ptr<Keys::Timezone> 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<Keys::Partition> const &e1,
- std::unique_ptr<Keys::Partition> 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<Keys::Mount> const &e1,
- std::unique_ptr<Keys::Mount> 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 <algorithm>
+#include <fstream>
+#ifdef HAS_INSTALL_ENV
+# include <parted/parted.h>
+#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<Partition> const &e1,
+ std::unique_ptr<Partition> 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<Mount> const &e1,
+ std::unique_ptr<Mount> 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 <assert.h>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#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<Username> name;
+ std::unique_ptr<UserAlias> alias;
+ std::unique_ptr<UserPassphrase> passphrase;
+ std::unique_ptr<UserIcon> icon;
+ std::vector< std::unique_ptr<UserGroups> > groups;
+};
+
+struct Script::ScriptPrivate {
+ /*! Determines whether or not to enable networking. */
+ std::unique_ptr<Network> network;
+ /*! The target system's hostname. */
+ std::unique_ptr<Hostname> hostname;
+ /*! The packages to install to the target system. */
+ std::set<std::string> packages;
+ /*! The root shadow line. */
+ std::unique_ptr<RootPassphrase> rootpw;
+ /*! The system language. */
+ std::unique_ptr<Language> lang;
+ /*! The system keymap. */
+ std::unique_ptr<Keymap> keymap;
+ /*! The system timezone. */
+ std::unique_ptr<Timezone> tzone;
+
+ /*! Network addressing configuration */
+ std::vector< std::unique_ptr<NetAddress> > addresses;
+ /*! Network nameserver resolver addresses */
+ std::vector< std::unique_ptr<Nameserver> > nses;
+ /*! Wireless networking configuration */
+ std::vector< std::unique_ptr<NetSSID> > ssids;
+
+ /*! APK repositories */
+ std::vector< std::unique_ptr<Repository> > repos;
+ /*! APK repository keys */
+ std::vector< std::unique_ptr<SigningKey> > repo_keys;
+
+ /*! User account information */
+ std::map< std::string, std::unique_ptr<UserDetail> > accounts;
+
+ /*! Disk identification keys */
+ std::vector< std::unique_ptr<DiskId> > diskids;
+ /*! Disklabel configuration keys */
+ std::vector< std::unique_ptr<DiskLabel> > disklabels;
+ /*! Partition creation keys */
+ std::vector< std::unique_ptr<Partition> > partitions;
+ /*! LVM physical volume keys */
+ std::vector< std::unique_ptr<LVMPhysical> > lvm_pvs;
+ /*! LVM volume group keys */
+ std::vector< std::unique_ptr<LVMGroup> > lvm_vgs;
+ /*! LVM logical volume keys */
+ std::vector< std::unique_ptr<LVMVolume> > lvm_lvs;
+ /*! LUKS creation keys */
+ std::vector< std::unique_ptr<Encrypt> > luks;
+ /*! Filesystem creation keys */
+ std::vector< std::unique_ptr<Filesystem> > fses;
+ /*! Target system's mountpoints. */
+ std::vector< std::unique_ptr<Mount> > mounts;
+
+#ifdef NON_LIBRE_FIRMWARE
+ std::unique_ptr<Firmware> 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<Network> net(dynamic_cast<Network *>(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<Hostname> name(dynamic_cast<Hostname *>(obj));
+ hostname = std::move(name);
+ return true;
+ }
+
+ bool store_pkginstall(Key* obj, int line, int *, int *warnings,
+ ScriptOptions opts) {
+ PkgInstall *install = dynamic_cast<PkgInstall *>(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<RootPassphrase> r(dynamic_cast<RootPassphrase *>(obj));
+ rootpw = std::move(r);
+ return true;
+ }
+
+ bool store_firmware(Key *obj, int line, int *errors, int *, ScriptOptions) {
+ std::unique_ptr<Firmware> f(dynamic_cast<Firmware *>(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<Language> l(dynamic_cast<Language *>(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<Keymap> k(dynamic_cast<Keymap *>(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<Timezone> t(dynamic_cast<Timezone *>(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<Username> name(dynamic_cast<Username *>(obj));
+ if(accounts.find(name->value()) != accounts.end()) {
+ DUPLICATE_ERROR((*accounts.find(name->value())).second->name,
+ "username", "assigned")
+ return false;
+ }
+ std::unique_ptr<UserDetail> 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<UserAlias> alias(dynamic_cast<UserAlias *>(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<UserPassphrase> pw(dynamic_cast<UserPassphrase *>(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<UserIcon> icon(dynamic_cast<UserIcon *>(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<UserGroups> grp(dynamic_cast<UserGroups *>(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 <algorithm>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#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<std::string> seen_groups;
+ for(auto &group : detail->groups) {
+ /* REQ: Runner.Validate.usergroups */
+ if(!group->validate(opts)) failures++;
+
+ /* REQ: Runner.Validate.usergroups.Unique */
+ const std::set<std::string> 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<std::unique_ptr<Repository>> &repos,
+ bool firmware = false) {
+ Repository *sys_key = dynamic_cast<Repository *>(
+ 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<Repository> sys_repo(sys_key);
+ repos.push_back(std::move(sys_repo));
+ Repository *user_key = dynamic_cast<Repository *>(
+ 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<Repository> 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 *>(
+ 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<Repository> 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<std::unique_ptr<SigningKey>> &keys) {
+ SigningKey *key = dynamic_cast<SigningKey *>(
+ 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<SigningKey> repo_key(key);
+ keys.push_back(std::move(repo_key));
+ return true;
+}
+
+
+bool Horizon::Script::validate() const {
+ int failures = 0;
+ std::set<std::string> seen_diskids, seen_labels, seen_parts, seen_pvs,
+ seen_vg_names, seen_vg_pvs, seen_lvs, seen_fses, seen_mounts,
+ seen_luks;
+ std::map<const std::string, int> 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 *>
+ (Timezone::parseFromData("UTC", 0, &failures, nullptr));
+ if(!utc) {
+ output_error("internal", "failed to create default timezone");
+ return false;
+ }
+ std::unique_ptr<Timezone> 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);
+}
+
+}