From 30b9f32d7a544f50f5e06ef930525d620a028db1 Mon Sep 17 00:00:00 2001 From: "A. Wilcox" Date: Thu, 28 May 2020 10:05:23 -0500 Subject: image: Put target inside ir_dir, add WIP ISO backend ir_dir used to be the target; now the target is inside ir_dir so backends have the choice of making other directories inside ir_dir if needed. --- image/CMakeLists.txt | 13 +++- image/backends/CMakeLists.txt | 1 + image/backends/iso.cc | 170 ++++++++++++++++++++++++++++++++++++++++++ image/backends/tar.cc | 6 +- image/creator.cc | 50 ++++++++++++- 5 files changed, 231 insertions(+), 9 deletions(-) create mode 100644 image/backends/iso.cc (limited to 'image') diff --git a/image/CMakeLists.txt b/image/CMakeLists.txt index ea0a3ed..58b5eef 100644 --- a/image/CMakeLists.txt +++ b/image/CMakeLists.txt @@ -1,12 +1,19 @@ -find_package(Boost REQUIRED COMPONENTS program_options) -include_directories(${Boost_INCLUDE_DIR}) - add_subdirectory(backends) set(IMG_SRCS creator.cc ) add_executable(hscript-image ${IMG_SRCS}) + +if("cxx_std_17" IN_LIST CMAKE_CXX_COMPILE_FEATURES) + find_package(Boost REQUIRED COMPONENTS program_options) + set_property(TARGET hscript-image PROPERTY CXX_STANDARD 17) +else() + find_package(Boost REQUIRED COMPONENTS filesystem program_options) +endif() + +include_directories(${Boost_INCLUDE_DIR}) + target_link_libraries(hscript-image hscript hi-backends ${Boost_LIBRARIES}) install(TARGETS hscript-image DESTINATION bin) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/creator.1 DESTINATION share/man/man1 RENAME hscript-image.1) diff --git a/image/backends/CMakeLists.txt b/image/backends/CMakeLists.txt index 2c79c8e..325fddf 100644 --- a/image/backends/CMakeLists.txt +++ b/image/backends/CMakeLists.txt @@ -1,5 +1,6 @@ set(BACKEND_SRCS basic.cc + iso.cc ) set(BACKEND_LIBS "") diff --git a/image/backends/iso.cc b/image/backends/iso.cc new file mode 100644 index 0000000..b1d4c43 --- /dev/null +++ b/image/backends/iso.cc @@ -0,0 +1,170 @@ +/* + * iso.cc - Implementation of the CD image Horizon Image Creation backend + * image, the image processing utilities for + * Project Horizon + * + * Copyright (c) 2020 Adélie Linux and contributors. All rights reserved. + * This code is licensed under the AGPL 3.0 license, as noted in the + * LICENSE-code file in the root directory of this repository. + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +#include +#include "basic.hh" +#include "hscript/util.hh" +#include "util/filesystem.hh" +#include "util/output.hh" + +namespace Horizon { +namespace Image { + +class CDBackend : public BasicBackend { +public: + enum CDError { + COMMAND_MISSING = 1, + FS_ERROR, + COMMAND_ERROR + }; + + explicit CDBackend(std::string ir, std::string out) + : BasicBackend(ir, out) {}; + + int prepare() override { + error_code ec; + + output_info("CD backend", "probing SquashFS tools version..."); + if(run_command("mksquashfs", {"-version"}) != 0) { + output_error("CD backend", "SquashFS tools are not present"); + return COMMAND_MISSING; + } + + /* REQ: ISO.1 */ + 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()); + /* we can _try_ to proceed anyway... */ + } + } + + /* REQ: ISO.2 */ + fs::create_directory(this->ir_dir, ec); + if(ec && ec.value() != EEXIST) { + output_error("CD backend", "could not create IR directory", + ec.message()); + return FS_ERROR; + } + + /* REQ: ISO.2 */ + fs::create_directory(this->ir_dir + "/cdroot", ec); + if(ec && ec.value() != EEXIST) { + output_error("CD backend", "could not create ISO directory", + ec.message()); + return FS_ERROR; + } + + /* REQ: ISO.2 */ + fs::create_directory(this->ir_dir + "/target", ec); + if(ec && ec.value() != EEXIST) { + output_error("CD backend", "could not create target directory", + ec.message()); + return FS_ERROR; + } + + /* REQ: ISO.4 */ + fs::create_directories(this->ir_dir + "/target/etc/default", ec); + if(ec && ec.value() != EEXIST) { + output_error("CD backend", "could not create target config dir", + ec.message()); + return FS_ERROR; + } + + /* REQ: ISO.4 */ + std::ofstream grub(this->ir_dir + "/target/etc/default/grub"); + grub << "ADELIE_MANUAL_CONFIG=1" << std::endl; + if(grub.fail() || grub.bad()) { + output_error("CD backend", "failed to configure GRUB"); + return FS_ERROR; + } + grub.close(); + + return 0; + } + + int create() override { + 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"); + + /* 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; + } + mtab << "mtab_is_file=no" << std::endl; + if(mtab.fail() || mtab.bad()) { + output_error("CD backend", "failed to write mtab configuration"); + return FS_ERROR; + } + mtab.close(); + + /* REQ: ISO.10 */ + 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"); + + const std::string squashpath = cdpath + "/" + my_arch + ".squashfs"; + + /* REQ: ISO.22 */ + { + std::ofstream exclude(this->ir_dir + "/exclude.list"); + exclude << "dev/*" << std::endl + << "proc/*" << std::endl + << "sys/*" << std::endl; + if(exclude.fail() || exclude.bad()) { + output_error("CD backend", "failed to write exclusion list"); + return FS_ERROR; + } + exclude.flush(); + exclude.close(); + } + + /* REQ: ISO.22 */ + 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; + } + + int finalise() override { + return 0; + } +}; + +__attribute__((constructor(400))) +void register_cd_backend() { + BackendManager::register_backend( + {"iso", "Create a CD image (.iso)", + [](std::string ir_dir, std::string out_path) { + return new CDBackend(ir_dir, out_path); + } + }); +} + +} +} diff --git a/image/backends/tar.cc b/image/backends/tar.cc index 279a11c..f9309a1 100644 --- a/image/backends/tar.cc +++ b/image/backends/tar.cc @@ -81,10 +81,10 @@ public: int fd, r, code = 0; struct stat s; void *buff; + std::string target = this->ir_dir + "/target"; - for(const auto& dent : fs::recursive_directory_iterator(this->ir_dir, - ec)) { - fs::path relpath = dent.path().lexically_relative(this->ir_dir); + for(const auto& dent : fs::recursive_directory_iterator(target, ec)) { + fs::path relpath = dent.path().lexically_relative(target); #define OUTPUT_FAILURE(x) \ output_error("tar backend", "failed to " x " '" + std::string(dent.path()) + "'",\ strerror(errno)); diff --git a/image/creator.cc b/image/creator.cc index 8a6cc73..0934f08 100644 --- a/image/creator.cc +++ b/image/creator.cc @@ -13,13 +13,24 @@ #include #include /* EXIT_* */ #include +#include + #include "backends/basic.hh" +#include "hscript/meta.hh" #include "hscript/script.hh" +#include "util/filesystem.hh" #include "util/output.hh" bool pretty = true; /*! Controls ASCII colour output */ +const std::string arch_xlate(const std::string &arch) { + if(arch == "pmmx") return "i386"; + if(arch == "armv7") return "arm"; + return arch; +} + + #define DESCR_TEXT "Usage: hscript-image [OPTION]... [INSTALLFILE]\n"\ "Write an operating system image configured per INSTALLFILE" /*! Text used at the top of usage output */ @@ -32,7 +43,7 @@ int main(int argc, char *argv[]) { bool needs_help{}, disable_pretty{}, version_only{}; int exit_code = EXIT_SUCCESS; - std::string if_path{"/etc/horizon/installfile"}, ir_dir{"/target"}, + std::string if_path{"/etc/horizon/installfile"}, ir_dir{"/tmp/horizon-image"}, output_path{"image.tar"}, type_code{"tar"}; BasicBackend *backend = nullptr; Horizon::ScriptOptions opts; @@ -49,7 +60,7 @@ int main(int argc, char *argv[]) { target.add_options() ("output,o", value()->default_value("image.tar"), "Desired filename for the output file.") ("type,t", value()->default_value("tar"), "Type of output file to generate. Use 'list' for a list of supported types.") - ("ir-dir,i", value()->default_value("/target"), "Where to store intermediate files.") + ("ir-dir,i", value()->default_value("/tmp/horizon-image"), "Where to store intermediate files.") ; ui.add(general).add(target); @@ -109,6 +120,10 @@ int main(int argc, char *argv[]) { ir_dir = vm["ir-dir"].as(); } + if(fs::path(ir_dir).is_relative()) { + ir_dir = fs::absolute(ir_dir).string(); + } + if(!vm["output"].empty()) { output_path = vm["output"].as(); } @@ -169,18 +184,47 @@ int main(int argc, char *argv[]) { RUN_PHASE_OR_TROUBLE(prepare, "preparation"); - my_script->setTargetDirectory(ir_dir); + /* Attempt to make images work cross-architecture. + * This requires binfmt_misc to be configured properly. + * It also requires qemu-user to be installed to /usr/bin on the host. + * It may not work all the time, so we ignore errors. + * But we try anyway, because it can work and make things easier. + */ + const Horizon::Keys::Key *archkey = my_script->getOneValue("arch"); + std::string qpath; + if(archkey) { + const Horizon::Keys::Arch *arch = + dynamic_cast(archkey); + qpath = "/usr/bin/qemu-" + arch_xlate(arch->value()); + error_code ec; + if(fs::exists(qpath, ec)) { + fs::create_directories(ir_dir + "/target/usr/bin", ec); + if(!ec) fs::copy_file(qpath, ir_dir + "/target/" + qpath, ec); + } + } + + my_script->setTargetDirectory(ir_dir + "/target"); if(!my_script->execute()) { exit_code = EXIT_FAILURE; goto trouble; } + if(!qpath.empty() && fs::exists(ir_dir + "/target" + qpath)) { + error_code ec; + fs::remove(ir_dir + "/target" + qpath, ec); + } + RUN_PHASE_OR_TROUBLE(create, "creation"); RUN_PHASE_OR_TROUBLE(finalise, "finalisation"); } trouble: /* delete the Script and exit */ + /* ensure that our target mounts are unmounted */ + umount((ir_dir + "/target/sys").c_str()); + umount((ir_dir + "/target/proc").c_str()); + umount((ir_dir + "/target/dev").c_str()); + delete my_script; early_trouble: /* no script yet */ delete backend; -- cgit v1.2.3-60-g2f50