diff options
author | A. Wilcox <AWilcox@Wilcox-Tech.com> | 2019-11-04 15:51:15 -0600 |
---|---|---|
committer | A. Wilcox <AWilcox@Wilcox-Tech.com> | 2019-11-04 15:51:15 -0600 |
commit | 0e5d52b23ae0b86e938905a332ad5b7439011dcc (patch) | |
tree | f97ab264328bec2ccdb7a9e5b54f3357d1ef3988 /hscript/script_v.cc | |
parent | 5a44e287dd8a6142c7a2ad7ddbc0570554ab149b (diff) | |
download | horizon-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/script_v.cc')
-rw-r--r-- | hscript/script_v.cc | 565 |
1 files changed, 565 insertions, 0 deletions
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); +} + +} |