From 419e2951caca02ba77a89b6a75c9317c6ff02300 Mon Sep 17 00:00:00 2001 From: "A. Wilcox" Date: Wed, 3 Jun 2020 00:06:49 -0500 Subject: image: Finish implementing ISO backend --- image/backends/initrd.sh.cpp | 82 +++++++++++ image/backends/iso.cc | 322 +++++++++++++++++++++++++++++++++++++++---- image/creator.cc | 4 + 3 files changed, 384 insertions(+), 24 deletions(-) create mode 100644 image/backends/initrd.sh.cpp (limited to 'image') diff --git a/image/backends/initrd.sh.cpp b/image/backends/initrd.sh.cpp new file mode 100644 index 0000000..6608481 --- /dev/null +++ b/image/backends/initrd.sh.cpp @@ -0,0 +1,82 @@ +const char *initrd = "#!/bin/sh -e\n\ + # \n\ + # This file is part of the Horizon image creation system.\n\ + # SPDX-License-Identifier: AGPL-3.0-only\n\ + # \n\ + # Portions of this file are derived from adelie-build-cd.\n\ + # \n\ + \n\ + log() {\n\ + # $1 - Type of log message (info, warning, error)\n\ + # $2 - The message.\n\ + printf \"%s\tlog\tinitrd: %s: %s\n\" `date -u +%Y-%m-%dT%H:%M:%S.000` \"$1\" \"$2\"\n\ + }\n\ + \n\ + log info 'Creating initrd structure...'\n\ + \n\ + ARCH=$1\n\ + LDARCH=$1\n\ + case $ARCH in\n\ + ppc) LDARCH=powerpc;;\n\ + ppc64) LDARCH=powerpc64;;\n\ + pmmx) LDARCH=i386;;\n\ + esac\n\ + TARGET_DIR=$2\n\ + CDROOT_DIR=$3\n\ + CDINIT_DIR=$4\n\ + INITRD_DIR=`mktemp -d -t 'hinitrd-XXXXXX'`\n\ + if [ $? -ne 0 ]; then\n\ + log error 'cannot create temporary directory'\n\ + exit 1\n\ + fi\n\ + \n\ + # mount points\n\ + mkdir ${INITRD_DIR}/dev\n\ + mkdir ${INITRD_DIR}/media\n\ + for _rootdir in newroot lowerroot upperroot; do\n\ + mkdir ${INITRD_DIR}/$_rootdir\n\ + chmod 755 ${INITRD_DIR}/$_rootdir\n\ + done\n\ + mkdir ${INITRD_DIR}/proc\n\ + mkdir ${INITRD_DIR}/sys\n\ + \n\ + # manual /dev nodes for initial udev startup\n\ + mknod -m 600 ${INITRD_DIR}/dev/console c 5 1\n\ + mknod -m 666 ${INITRD_DIR}/dev/null c 1 3\n\ + mknod -m 666 ${INITRD_DIR}/dev/ptmx c 5 2\n\ + mknod -m 666 ${INITRD_DIR}/dev/random c 1 8\n\ + mknod -m 666 ${INITRD_DIR}/dev/tty c 5 0\n\ + mknod -m 620 ${INITRD_DIR}/dev/tty1 c 4 1\n\ + mknod -m 666 ${INITRD_DIR}/dev/urandom c 1 9\n\ + mknod -m 666 ${INITRD_DIR}/dev/zero c 1 5\n\ + \n\ + # base\n\ + mkdir ${INITRD_DIR}/lib\n\ + cp ${TARGET_DIR}/lib/ld-musl-$LDARCH.so.1 ${INITRD_DIR}/lib/\n\ + cp ${TARGET_DIR}/lib/libblkid.so.1 ${INITRD_DIR}/lib/\n\ + cp ${TARGET_DIR}/lib/libuuid.so.1 ${INITRD_DIR}/lib/\n\ + \n\ + # udev\n\ + mkdir -p ${INITRD_DIR}/etc/udev\n\ + mkdir ${INITRD_DIR}/run\n\ + mkdir ${INITRD_DIR}/sbin\n\ + cp ${TARGET_DIR}/bin/udevadm ${INITRD_DIR}/sbin/\n\ + cp ${TARGET_DIR}/sbin/udevd ${INITRD_DIR}/sbin/\n\ + cp ${TARGET_DIR}/lib/libkmod.so.2 ${INITRD_DIR}/lib/\n\ + cp ${TARGET_DIR}/lib/libcrypto.so.1.1 ${INITRD_DIR}/lib/\n\ + cp ${TARGET_DIR}/lib/liblzma.so.5 ${INITRD_DIR}/lib/\n\ + cp ${TARGET_DIR}/lib/libudev.so.1 ${INITRD_DIR}/lib/\n\ + cp ${TARGET_DIR}/lib/libz.so.1 ${INITRD_DIR}/lib/\n\ + \n\ + if [ -n ${CDINIT_DIR} ]; then\n\ + cp ${CDINIT_DIR}/cdinit-$ARCH ${INITRD_DIR}/init\n\ + else\n\ + log error 'No cdinit binary found, and compilation is not yet supported'\n\ + exit 2\n\ + fi\n\ + \n\ + log info 'Compressing initrd...'\n\ + \n\ + (cd ${INITRD_DIR}; find . | cpio -H newc -o) > ${CDROOT_DIR}/initrd-$ARCH\n\ + gzip -9 ${CDROOT_DIR}/initrd-$ARCH\n\ + mv ${CDROOT_DIR}/initrd-$ARCH.gz ${CDROOT_DIR}/initrd-$ARCH"; diff --git a/image/backends/iso.cc b/image/backends/iso.cc index c3f316e..a7e2dd7 100644 --- a/image/backends/iso.cc +++ b/image/backends/iso.cc @@ -10,12 +10,151 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -#include +#include /* getenv */ +#include /* strlen, strtok */ +#include /* ifstream, ofstream */ +#include + #include "basic.hh" #include "hscript/util.hh" #include "util/filesystem.hh" #include "util/output.hh" +using namespace boost::algorithm; + +const std::vector data_dirs() { + std::vector dirs; + + char *home = std::getenv("XDG_DATA_HOME"); + if(home != nullptr && std::strlen(home) > 0) { + dirs.push_back(home); + } else { + home = std::getenv("HOME"); + if(home != nullptr && std::strlen(home) > 0) { + dirs.push_back(std::string(home) + "/.local/share"); + } else { + home = std::getenv("APPDATA"); + if(home != nullptr) { + dirs.push_back(home); + } else { + /* Give up. */ + } + } + } + + const char *sys_c = std::getenv("XDG_DATA_DIRS"); + if(sys_c == nullptr || std::strlen(sys_c) == 0) { + sys_c = "/usr/local/share:/usr/share"; + } + + const std::string sys{sys_c}; + std::vector temp; + boost::split(temp, sys, is_any_of(":")); + std::move(temp.begin(), temp.end(), std::back_inserter(dirs)); + + return dirs; +} + +const fs::path find_data_file(std::string name) { + error_code ec; + for(const auto &p : data_dirs()) { + fs::path src = fs::path(p).append("horizon").append("iso").append(name); + + if(fs::exists(src, ec)) { + return src; + } + } + return fs::path(); +} + +bool copy_volume_icon_to(fs::path ir_dir) { + error_code ec; + + const fs::path dest = ir_dir.append("cdroot").append("VolumeIcon.icns"); + const fs::path src = find_data_file("VolumeIcon.icns"); + + /* No volume icon. */ + if(!src.has_filename()) return false; + + fs::copy(src, dest, ec); + if(ec && ec.value() != EEXIST) { + output_error("CD backend", "could not copy volume icon", ec.message()); + return false; + } + return true; +} + +bool write_etc_mtab_to(fs::path target) { + std::ofstream mtab(target.append("/etc/conf.d/mtab")); + if(!mtab) { + output_error("CD backend", "failed to open mtab configuration"); + return false; + } + mtab << "mtab_is_file=no" << std::endl; + if(mtab.fail() || mtab.bad()) { + output_error("CD backend", "failed to write mtab configuration"); + return false; + } + mtab.flush(); + mtab.close(); + return true; +} + +bool write_fstab_to(fs::path target) { + std::ofstream fstab{target.append("/etc/fstab")}; + if(!fstab) { + output_error("CD backend", "failed to open fstab"); + return false; + } + + fstab << "# Welcome to Adélie Linux." << std::endl + << "# This fstab(5) is for the live media only. " + << "Do not edit or use for your installation." << std::endl + << std::endl + << "tmpfs /tmp tmpfs defaults 0 1" + << std::endl + << "proc /proc proc defaults 0 1" + << std::endl; + + if(fstab.fail() || fstab.bad()) { + output_error("CD backend", "failed to write fstab"); + return false; + } + fstab.flush(); + fstab.close(); + return true; +} + +bool write_etc_issue_to(fs::path target) { + error_code ec; + const fs::path dest{target.append("/etc/issue")}; + + const fs::path src{find_data_file("issue")}; + if(src.has_filename()) { + fs::copy(src, dest, ec); + return !ec; + } + + /* We don't have a file, so write out our default. */ + std::ofstream issue(dest); + if(!issue) { + output_error("CD backend", "failed to open issue file"); + return false; + } + + issue << "Welcome to Adélie Linux!" << std::endl + << "You may log in as 'root' to install, or 'live' to play around." + << std::endl << std::endl << "Have fun." << std::endl; + + if(issue.fail() || issue.bad()) { + output_error("CD backend", "failed to write issue file"); + return false; + } + issue.flush(); + issue.close(); + return true; +} + namespace Horizon { namespace Image { @@ -41,15 +180,17 @@ public: } /* REQ: ISO.1 */ - if(fs::exists(this->ir_dir, ec)) { + /*if(fs::exists(this->ir_dir, ec)) { output_info("CD backend", "removing old IR tree", this->ir_dir); fs::remove_all(this->ir_dir, ec); if(ec) { output_warning("CD backend", "could not remove IR tree", - ec.message()); + ec.message());*/ /* we can _try_ to proceed anyway... */ - } - } + //} + //} + + output_info("CD backend", "creating directory tree"); /* REQ: ISO.2 */ fs::create_directory(this->ir_dir, ec); @@ -84,6 +225,7 @@ public: } /* REQ: ISO.4 */ + output_info("CD backend", "configuring boot loader"); std::ofstream grub(this->ir_dir + "/target/etc/default/grub"); grub << "ADELIE_MANUAL_CONFIG=1" << std::endl; if(grub.fail() || grub.bad()) { @@ -96,39 +238,70 @@ public: } int create() override { + error_code ec; std::string my_arch; std::ifstream archstream(this->ir_dir + "/target/etc/apk/arch"); const std::string target = this->ir_dir + "/target"; const std::string cdpath = this->ir_dir + "/cdroot"; archstream >> my_arch; - /* REQ: ISO.7 */ - fs::create_directory(target + "/target"); - fs::create_directories(target + "/media/live"); + fs::current_path(this->ir_dir); - /* REQ: ISO.9 */ - std::ofstream mtab(target + "/etc/conf.d/mtab"); - if(!mtab) { - output_error("CD backend", "failed to open mtab configuration"); - return FS_ERROR; + /* REQ: ISO.7 */ + output_info("CD backend", "creating live environment directories"); + fs::create_directory(target + "/target", ec); + if(ec && ec.value() != EEXIST) { + output_error("CD backend", "could not create directory", + ec.message()); } - mtab << "mtab_is_file=no" << std::endl; - if(mtab.fail() || mtab.bad()) { - output_error("CD backend", "failed to write mtab configuration"); - return FS_ERROR; + fs::create_directories(target + "/media/live", ec); + if(ec && ec.value() != EEXIST) { + output_error("CD backend", "could not create directory", + ec.message()); } - mtab.close(); + + /* REQ: ISO.9 */ + output_info("CD backend", "configuring mtab"); + write_etc_mtab_to(target); /* REQ: ISO.10 */ + output_info("CD backend", "enabling required services"); const std::string targetsi = target + "/etc/runlevels/sysinit/"; - fs::create_symlink("/etc/init.d/udev", targetsi + "udev"); - fs::create_symlink("/etc/init.d/udev-trigger", - targetsi + "udev-trigger"); - fs::create_symlink("/etc/init.d/lvmetad", targetsi + "lvmetad"); + for(const std::string &svc : {"udev", "udev-trigger", "lvmetad"}) { + fs::create_symlink("/etc/init.d/" + svc, targetsi + svc, ec); + if(ec && ec.value() != EEXIST) { + output_error("CD backend", "could not enable service " + svc, + ec.message()); + return FS_ERROR; + } + } - const std::string squashpath = cdpath + "/" + my_arch + ".squashfs"; + /* REQ: ISO.12 */ + output_info("CD backend", "creating live environment /etc/fstab"); + if(!write_fstab_to(target)) return FS_ERROR; + + /* REQ: ISO.13 */ + output_info("CD backend", "setting root shell"); + run_command("sed", {"-i", "s#/root:/bin/sh$#/root:/bin/zsh#", + target + "/etc/passwd"}); + + /* REQ: ISO.15 */ + output_info("CD backend", "configuring login services"); + run_command("sed", {"-i", "s/pam_unix.so$/pam_unix.so nullok_secure/", + target + "/etc/pam.d/base-auth"}); + + /* REQ: ISO.19 */ + output_info("CD backend", "creating live /etc/issue"); + if(opts.find("issue-path") != opts.end() && + fs::exists(opts.at("issue-path"))) { + fs::path dest = fs::path(cdpath).append("etc/issue"); + fs::copy(opts.at("issue-path"), dest, ec); + if(ec) output_error("CD backend", "could not copy /etc/issue", + ec.message()); + } else if(!write_etc_issue_to(target)) return FS_ERROR; /* REQ: ISO.22 */ + output_info("CD backend", "generating file list"); { std::ofstream exclude(this->ir_dir + "/exclude.list"); exclude << "dev/*" << std::endl @@ -143,16 +316,117 @@ public: } /* REQ: ISO.22 */ + output_info("CD backend", "creating SquashFS"); + const std::string squashpath = cdpath + "/" + my_arch + ".squashfs"; if(run_command("mksquashfs", {target, squashpath, "-noappend", "-wildcards", "-ef", this->ir_dir + "/exclude.list"}) != 0) { output_error("CD backend", "failed to create SquashFS"); return COMMAND_ERROR; } - return 0; + + /* REQ: ISO.21 */ + if(opts.find("icon-path") != opts.end() && + fs::exists(opts.at("icon-path"))) { + fs::path dest = fs::path(cdpath).append("VolumeIcon.icns"); + fs::copy(opts.at("icon-path"), dest, ec); + if(ec) output_error("CD backend", "could not copy volume icon", + ec.message()); + } else if(!copy_volume_icon_to(this->ir_dir)) { + output_warning("CD backend", "No volume icon could be found."); + } + + /* REQ: ISO.23 */ + output_info("CD backend", "creating initrd"); + std::string cdinit_path{""}; + for(const auto &path : data_dirs()) { + fs::path candidate{fs::path{path}.append("horizon").append("iso") + .append("cdinits")}; + if(fs::exists(candidate, ec)) { + cdinit_path = candidate; + } + } +#include "initrd.sh.cpp" + if(run_command("/bin/sh", {"-ec", initrd, "mkinitrd", my_arch, target, + cdpath, cdinit_path}) != 0) { + output_error("CD backend", "failed to create initrd"); + return COMMAND_ERROR; + } + + /* REQ: ISO.24 */ + std::string postscript; + if(opts.find("post-script") != opts.end() && + fs::exists(opts.at("post-script"))) { + postscript = opts.at("post-script"); + } else { + for(const auto &path : data_dirs()) { + fs::path candidate{fs::path{path}.append("horizon") + .append("iso").append("post-" + my_arch + ".sh")}; + if(fs::exists(candidate, ec)) { + postscript = candidate; + break; + } + } + } + if(postscript.length() > 0) { + output_info("CD backend", "running architecture-specific script", + postscript); + if(run_command("/bin/sh", {"-e", postscript}) != 0) { + output_error("CD backend", "architecture-specific script failed"); + return COMMAND_ERROR; + } + } + + /* REQ: ISO.25 */ + output_info("CD backend", "installing kernel"); + for(const auto &candidate : fs::directory_iterator(target + "/boot")) { + auto name = candidate.path().filename().string(); + if(name.length() > 6 && name.substr(0, 6) == "vmlinu") { + fs::copy(candidate.path(), cdpath + "/kernel-" + my_arch, ec); + if(ec) { + output_error("CD backend", "failed to copy kernel", + ec.message()); + return FS_ERROR; + } + fs::remove(candidate.path(), ec); + break; + } + } + + /* REQ: ISO.26 */ + output_info("CD backend", "creating ISO"); + std::vector iso_args = {"-as", "mkisofs", "-o", out_path, + "-joliet", "-rational-rock", "-V", + "Adelie "+my_arch}; + std::vector arch_args; + std::string raw_arch; + { + const fs::path param_file = find_data_file("iso-params-" + my_arch); + if(param_file.has_filename()) { + std::ifstream params{param_file}; + if(!params) { + output_warning("CD backend", "couldn't read ISO params"); + } else { + params >> raw_arch; + } + } + } + if(raw_arch.length() > 0) { + boost::split(arch_args, raw_arch, is_any_of(" ")); + std::move(arch_args.begin(), arch_args.end(), + std::back_inserter(iso_args)); + } + if(opts.find("iso-params") != opts.end()) { + boost::split(arch_args, opts.at("iso-params"), is_any_of(" ")); + std::move(arch_args.begin(), arch_args.end(), + std::back_inserter(iso_args)); + } + iso_args.push_back(cdpath); + return run_command("xorriso", iso_args); } int finalise() override { + output_info("CD backend", "Live image created successfully", out_path); return 0; } }; diff --git a/image/creator.cc b/image/creator.cc index 046027a..1a064f1 100644 --- a/image/creator.cc +++ b/image/creator.cc @@ -137,6 +137,10 @@ int main(int argc, char *argv[]) { output_path = vm["output"].as(); } + if(fs::path(output_path).is_relative()) { + output_path = fs::absolute(output_path).string(); + } + if(!vm["backconfig"].empty()) { for(const auto &confpart : vm["backconfig"].as>()) { -- cgit v1.2.3-60-g2f50