/* * tar.cc - Implementation of the tarball 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 #include #include #include #include #include #include #include #include "basic.hh" #include "util/filesystem.hh" #include "util/output.hh" namespace Horizon { namespace Image { class TarBackend : public BasicBackend { public: enum CompressionType { None, GZip, BZip2, XZ }; private: CompressionType comp; struct archive *a; public: TarBackend(const std::string &ir, const std::string &out, const std::map &opts, CompressionType _c = None) : BasicBackend{ir, out, opts}, comp{_c} {}; int prepare() override { int res; a = archive_write_new(); archive_write_set_format_pax_restricted(a); switch(comp) { case None: break; case GZip: archive_write_add_filter_gzip(a); break; case BZip2: archive_write_add_filter_bzip2(a); break; case XZ: archive_write_add_filter_xz(a); break; } res = archive_write_open_filename(a, this->out_path.c_str()); if(res < ARCHIVE_OK) { if(res < ARCHIVE_WARN) { output_error("tar backend", archive_error_string(a)); return res; } else { output_warning("tar backend", archive_error_string(a)); } } return 0; } int create() override { struct archive_entry *entry = archive_entry_new(); error_code ec; int fd, r, code = 0; struct stat s; void *buff; std::string target = this->ir_dir + "/target"; umount((ir_dir + "/target/sys").c_str()); umount((ir_dir + "/target/proc").c_str()); umount((ir_dir + "/target/dev").c_str()); 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)); r = lstat(dent.path().c_str(), &s); if(r == -1) { OUTPUT_FAILURE("stat") code = -1; goto ret; } archive_entry_copy_stat(entry, &s); if(dent.is_symlink()) { archive_entry_set_filetype(entry, AE_IFLNK); fs::path resolved = fs::read_symlink(dent.path(), ec); const fs::path::value_type *c_rpath = resolved.u8string().c_str(); archive_entry_update_symlink_utf8(entry, c_rpath); } archive_entry_update_pathname_utf8(entry, relpath.u8string().c_str()); if(archive_write_header(this->a, entry) != ARCHIVE_OK) { output_error("tar backend", archive_error_string(a)); code = -1; goto ret; } if(dent.is_regular_file() && dent.file_size() > 0) { fd = open(dent.path().c_str(), O_RDONLY); if(fd == -1) { OUTPUT_FAILURE("open") code = -1; goto ret; } buff = mmap(NULL, dent.file_size(), PROT_READ, MAP_SHARED, fd, 0); if(buff == MAP_FAILED) { OUTPUT_FAILURE("map") close(fd); code = -1; goto ret; } archive_write_data(this->a, buff, dent.file_size()); close(fd); } archive_write_finish_entry(this->a); archive_entry_clear(entry); } ret: archive_entry_free(entry); return code; } int finalise() override { archive_write_close(a); archive_write_free(a); return 0; } }; __attribute__((constructor(400))) void register_tar_backend() { BackendManager::register_backend( {"tar", "Create a tarball (.tar)", [](const std::string &ir_dir, const std::string &out_path, const std::map &opts) { return new TarBackend(ir_dir, out_path, opts); } }); BackendManager::register_backend( {"tgz", "Create a tarball with GZ compression (.tar.gz)", [](const std::string &ir_dir, const std::string &out_path, const std::map &opts) { return new TarBackend(ir_dir, out_path, opts, TarBackend::GZip); } }); BackendManager::register_backend( {"tbz", "Create a tarball with BZip2 compression (.tar.bz2)", [](const std::string &ir_dir, const std::string &out_path, const std::map &opts) { return new TarBackend(ir_dir, out_path, opts, TarBackend::BZip2); } }); BackendManager::register_backend( {"txz", "Create a tarball with XZ compression (.tar.xz)", [](const std::string &ir_dir, const std::string &out_path, const std::map &opts) { return new TarBackend(ir_dir, out_path, opts, TarBackend::XZ); } }); } } }