summaryrefslogblamecommitdiff
path: root/hscript/script.cc
blob: e253dbe789e8d8b9ae36ad3311c632a1848ec21d (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
  
                                                 









                                                                           
                    
                   
                             

                   
              


                             
              
                  
 
                    
                  


                     
 

                         
                           
 
 
                                                                                  
 

                              
                                                        






























                                               


  

                   

                                




                                                      

  
                              
                                                          
                                     
                                        
                                       
                                                        
                                   
                                
                                           

                                   

                                   

                                    
 
                                           
                                                         
                                                
                                                    
                                            
                                                  
 
                           
                                                     

                                                         
 


                                                                  
                                   
                                                   

                                                         

                                                         





                                                        

                                                 

                                                    

                                                 
 



                                       

                                                                     
                                                                   

                                                                               
                                                   
       
                                                                           
                                                                           
                                   
                                                                      
                                             
                                                                              

                                                       



                                                                            
                                          
                                                                        

                                                   







                                                                         

                                                                     

                                                                       

                                                                       
                                             
                                                                              

                                                   



                                                                             
                                           








                                                                         



                                                                        



                                                                         



                                                                         











                                                                              



                                                                     



                                                                            



                                                                     



                         
 








                                                                       

                                                                      









                                                                     

                                                                       









                                                                      

                                                                           







                                                                               
             





                                 

                                                                     









                                                                               

                                                                       














                                                                        











                                                                   











                                                                     











                                                                       

                                                                       














                                                                         

                                                             

                    


                                                           
                                 






                                                                              
                                                                 
                                                

                                                                         
                                                   






                                                                               
 

                                                                     

                                                                                
                                                







                                                         
 

                                                                       

                                                                      
                                                  

                                                                           

                         


                                       
 
                                                                  
                                                 



                                                                         
     


                      

  
 
                  
                                 

 



                    

                                                       
                             
               
                                                          



                                    

 
 

                                                       
                               
              
                                                          
                               



                                                   


                                                            
                                 
 
                                  
 


                                    
                                   
                                   
                                 








                                                        
                        
                                                           






                                                   
                                                             
                                                    
                                                                              
                                   
                                                          
                     

         


                                                                       
                                                      



                                                                
                                                                  
             
                     
         




                                                                           



                                                                          
                                                               

                                                        
         




                                                                 

                                                      
                 



                                                             
                                                                
                 

     
                                               

                                                          
                                                                      
                                          

             















                                                        


                    


                                                              




                          
                                

                          


                     

 
 


























                                                                        
                                           

































































































                                                                               
                               
                     
                                                                          

                                                                         
                                                


                            
 
                                      
                                                                  
 
                                                 












                                                                          
                                                           










                                                                                

     






                                          
                                              
                                             


                                         

     





                                                                   


                                                                           

                                                                               





                                                                   















                                                                          

                                
                                           
                                                       

                         

     
                                         




                                             







                                                                         












                                                                             


                                                
                                                                         


















                                                                           
















                                                                          







                                                   


                                                                             









                                                                         














                                                                              












                                                                           

     



























                                                                               
                      





                                                                               
                            








                                                                              







                                                              
                                              







                                                                            
                                                     














                                                                               









                                                               























                                                                            














                                                                       






                                                                 

     















                                                                            





                                                                          


         

                          





                                                                        

                                                              
                           

 
 

                              
                  
 





                                                                         


                                                                        

     
                                    



                                  
                                                
                                                                        



                                                             
                                
                                                               

                                                        
                                                  
                              


                                                             







                                                 







                                                   














                                                                                   
                     
























                                             
                 







                                          

                                   


                                                                            

                                                          







                                                   


                                                            

                            
                                                            




                                                  
 

                                             
                                            


                         






                                                                      



                                               









                                                                            
                         











                                                                           


                                                                   
                                                                                   


                                                       
                    

                                                                              

                

                                                                           

                                            


                                                                           

         
 




















                                                                              
                                       







                                                                      
                                                                       



                                                             
                                                        


                                                                             
                                       


                                        

         
 



                                                 















                                                                                 

                                            





                                                                                 

                                            






                                                                               



                                             
 
                                              









                                                                    
                                        



















                                                                              

                             
                                                             
                                       
 

                                                

                     





                                                                      







                                               

                                     

 
 
/*
 * script.cc - Implementation of 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
 */

#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 "disk.hh"
#include "meta.hh"
#include "network.hh"
#include "user.hh"

#include "util/output.hh"

#define SCRIPT_LINE_MAX 512


typedef Horizon::Keys::Key *(*key_parse_fn)(const std::string &, int, int*, int*);

using namespace Horizon::Keys;

const std::map<std::string, key_parse_fn> valid_keys = {
    {"network", &Network::parseFromData},
    {"hostname", &Hostname::parseFromData},
    {"pkginstall", &PkgInstall::parseFromData},
    {"rootpw", &RootPassphrase::parseFromData},

    {"language", &Language::parseFromData},
    {"keymap", &Keymap::parseFromData},
    {"firmware", &Firmware::parseFromData},
    {"timezone", &Timezone::parseFromData},
    {"repository", &Repository::parseFromData},
    {"signingkey", &SigningKey::parseFromData},

    {"netaddress", &NetAddress::parseFromData},
    {"nameserver", &Nameserver::parseFromData},
    {"netssid", &NetSSID::parseFromData},

    {"username", &Username::parseFromData},
    {"useralias", &UserAlias::parseFromData},
    {"userpw", &UserPassphrase::parseFromData},
    {"usericon", &UserIcon::parseFromData},
    {"usergroups", &UserGroups::parseFromData},

    {"diskid", &DiskId::parseFromData},
    {"disklabel", &DiskLabel::parseFromData},
    {"partition", &Partition::parseFromData},
    {"lvm_pv", &LVMPhysical::parseFromData},
    {"lvm_vg", &LVMGroup::parseFromData},
    {"lvm_lv", &LVMVolume::parseFromData},
    {"encrypt", &Encrypt::parseFromData},
    {"fs", &Filesystem::parseFromData},
    {"mount", &Mount::parseFromData}
};


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);
        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);
        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;
        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);
        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);
        return true;
#else
        assert(!f->test());
        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);
        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);
        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);
        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)));
        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);
        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);
        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);
        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));
        return true;
    }
#undef GET_USER_DETAIL

#undef DUPLICATE_ERROR
};


Script::Script() {
    internal = new ScriptPrivate;
}

Script::~Script() {
    delete internal;
}

const Script *Script::load(const std::string &path,
                           const ScriptOptions &opts) {
    std::ifstream file(path);
    if(!file) {
        output_error(path, "Cannot open installfile", "");
        return nullptr;
    }

    return Script::load(file, opts);
}


const Script *Script::load(std::istream &sstream,
                           const ScriptOptions &opts) {
#define PARSER_ERROR(err_str) \
    errors++;\
    output_error("installfile:" + std::to_string(lineno),\
                 err_str, "");\
    if(!opts.test(ScriptOptionFlags::KeepGoing)) {\
        break;\
    }

#define PARSER_WARNING(warn_str) \
    warnings++;\
    output_warning("installfile:" + std::to_string(lineno),\
                   warn_str, "");

    using namespace Horizon::Keys;

    Script *the_script = new Script;

    int lineno = 0;
    char nextline[SCRIPT_LINE_MAX];
    const std::string delim(" \t");
    int errors = 0, warnings = 0;

    while(sstream.getline(nextline, sizeof(nextline))) {
        lineno++;
        if(nextline[0] == '#') {
            /* This is a comment line; ignore it. */
            continue;
        }

        const std::string line(nextline);
        std::string key;
        std::string::size_type start, key_end, value_begin;
        start = line.find_first_not_of(delim);
        if(start == std::string::npos) {
            /* This is a blank line; ignore it. */
            continue;
        }

        key_end = line.find_first_of(delim, start);
        value_begin = line.find_first_not_of(delim, key_end);
        key = line.substr(start, (key_end - start));
        if(key_end == std::string::npos || value_begin == std::string::npos) {
            /* Key without value */
            PARSER_ERROR("key '" + key + "' has no value")
            continue;
        }

        /* Normalise key to lower-case */
        std::transform(key.begin(), key.end(), key.begin(), ::tolower);

        if(valid_keys.find(key) == valid_keys.end()) {
            /* Invalid key */
            if(opts.test(StrictMode)) {
                PARSER_ERROR("key '" + key + "' is not defined")
            } else {
                PARSER_WARNING("key '" + key + "' is not defined")
            }
            continue;
        }

        Key *key_obj = valid_keys.at(key)(line.substr(value_begin), lineno,
                                          &errors, &warnings);
        if(!key_obj) {
            PARSER_ERROR("value for key '" + key + "' was invalid")
            continue;
        }

        if(!the_script->internal->store_key(key, key_obj, lineno, &errors,
                                            &warnings, opts)) {
            PARSER_ERROR("stopping due to prior errors")
            continue;
        }
    }

    if(sstream.fail() && !sstream.eof()) {
        output_error("installfile:" + std::to_string(lineno + 1),
                     "line exceeds maximum length",
                     "Maximum line length is " +
                     std::to_string(SCRIPT_LINE_MAX));
        errors++;
    }

    if(sstream.bad() && !sstream.eof()) {
        output_error("installfile:" + std::to_string(lineno),
                     "I/O error while reading installfile", "");
        errors++;
    }

    /* Ensure all required keys are present. */
#define MISSING_ERROR(key) \
    output_error("installfile:" + std::to_string(lineno),\
                 "expected value for key '" + std::string(key) + "'",\
                 "this key is required");\
    errors++;

    if(errors == 0) {
        if(!the_script->internal->network) {
            MISSING_ERROR("network")
        }
        if(!the_script->internal->hostname) {
            MISSING_ERROR("hostname")
        }
        if(the_script->internal->packages.size() == 0) {
            MISSING_ERROR("pkginstall")
        }
        if(!the_script->internal->rootpw) {
            MISSING_ERROR("rootpw")
        }
        if(the_script->internal->mounts.size() == 0) {
            MISSING_ERROR("mount")
        }
    }
#undef MISSING_ERROR

    output_log("parser", "0", "installfile",
               std::to_string(errors) + " error(s), " +
               std::to_string(warnings) + " warning(s).", "");

    if(errors > 0) {
        delete the_script;
        return nullptr;
    } else {
        the_script->opts = opts;
        return the_script;
    }

#undef PARSER_WARNING
#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;
}


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: 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");
    if(!this->internal->hostname->execute(opts)) {
        EXECUTE_FAILURE("pre-metadata");
        return false;
    }

    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 {
            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 << "cat >>/target/etc/conf.d/net <<- NETCONF_EOF"
                      << std::endl << conf.str() << std::endl
                      << "NETCONF_EOF" << std::endl;
        } else {
            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.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;
}

}