diff --git a/meson.build b/meson.build index e3ea673..ea4c8da 100644 --- a/meson.build +++ b/meson.build @@ -54,6 +54,7 @@ lib_src = [ 'src/util/curl.cpp', 'src/util/fs.cpp', 'src/util/shell.cpp', + 'src/util/signal.cpp', 'src/util/strings.cpp', 'src/util/subprocess.cpp', ] diff --git a/src/cli/add_remove.cpp b/src/cli/add_remove.cpp index 6da0bb1..31a151c 100644 --- a/src/cli/add_remove.cpp +++ b/src/cli/add_remove.cpp @@ -322,11 +322,14 @@ int image_rm([[maybe_unused]] const image_rm_args& args, spdlog::debug("image_rm: treating {} as a label", U); auto label = uenv::parse_uenv_label(U); if (!label) { + spdlog::error("the label {} is not valid: {}", U, + label.error().message()); term::error("the label {} is not valid: {}", U, label.error().message()); return 1; } if (!label->partially_qualified()) { + spdlog::error("no uenv matches {}", U); term::error( "the label {} does not provide at least name/version:tag", U); return 1; @@ -335,12 +338,13 @@ int image_rm([[maybe_unused]] const image_rm_args& args, if (auto r = store->query(*label)) { if (r->empty()) { + spdlog::error("no uenv matches {}", U); term::error("no uenv matches {}", U); return 1; } else if (r->size() > 1) { - term::error("the pattern {} matches more than one uenv:", U); - print_record_set(*r, true); - term::msg("use a more specific pattern"); + term::error("the pattern {} matches more than one " + "uenv:\n{}use a more specifc version", + U, format_record_set(*r)); return 1; } else { // check whether there are more than one tag attached to sha diff --git a/src/cli/pull.cpp b/src/cli/pull.cpp index 5817aec..f535b1d 100644 --- a/src/cli/pull.cpp +++ b/src/cli/pull.cpp @@ -1,5 +1,6 @@ // vim: ts=4 sts=4 sw=4 et +#include #include #include @@ -17,6 +18,7 @@ #include #include #include +#include #include "help.h" #include "pull.h" @@ -133,37 +135,47 @@ int image_pull([[maybe_unused]] const image_pull_args& args, spdlog::debug("label in repo: {}", label_in_repo); if (args.force || !sha_in_repo) { - bool pull_sqfs = !args.only_meta && (args.force || !sqfs_exists); - bool pull_meta = args.force || !meta_exists; - - spdlog::debug("pull meta: {}", pull_meta); - spdlog::debug("pull sqfs: {}", pull_sqfs); - - auto rego_url = site::registry_url(); - spdlog::debug("registry url: {}", rego_url); - - auto digests = oras::discover(rego_url, args.nspace, record); - if (!digests || digests->empty()) { - fmt::print("unable to pull image - rerun with -vvv flag and send " - "error report to service desk.\n"); - return 1; - } - spdlog::debug("manifests: {}", fmt::join(*digests, ", ")); - - const auto digest = *(digests->begin()); - - if (auto okay = oras::pull_digest(rego_url, args.nspace, record, digest, - paths.store); - !okay) { - fmt::print("unable to pull image - rerun with -vvv flag and send " - "error report to service desk.\n"); - return 1; - } - - auto tag_result = - oras::pull_tag(rego_url, args.nspace, record, paths.store); - if (!tag_result) { - return 1; + try { + bool pull_sqfs = !args.only_meta && (args.force || !sqfs_exists); + bool pull_meta = args.force || !meta_exists; + + spdlog::debug("pull meta: {}", pull_meta); + spdlog::debug("pull sqfs: {}", pull_sqfs); + + auto rego_url = site::registry_url(); + spdlog::debug("registry url: {}", rego_url); + + auto digests = oras::discover(rego_url, args.nspace, record); + if (!digests || digests->empty()) { + fmt::print( + "unable to pull image - rerun with -vvv flag and send " + "error report to service desk.\n"); + return 1; + } + spdlog::debug("manifests: {}", fmt::join(*digests, ", ")); + + const auto digest = *(digests->begin()); + + if (auto okay = oras::pull_digest(rego_url, args.nspace, record, + digest, paths.store); + !okay) { + fmt::print( + "unable to pull image - rerun with -vvv flag and send " + "error report to service desk.\n"); + return 1; + } + + auto tag_result = + oras::pull_tag(rego_url, args.nspace, record, paths.store); + if (!tag_result) { + return 1; + } + } catch (util::signal_exception& e) { + spdlog::debug("removing record {}", record); + store->remove(record.sha); + spdlog::debug("deleting path {}", paths.store); + std::filesystem::remove_all(paths.store); + raise(e.signal); } } diff --git a/src/uenv/oras.cpp b/src/uenv/oras.cpp index 20f2f66..f0d5d03 100644 --- a/src/uenv/oras.cpp +++ b/src/uenv/oras.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include namespace uenv { @@ -127,6 +127,8 @@ util::expected pull_tag(const std::string& registry, return util::unexpected{-1}; } + util::set_signal_catcher(); + const fs::path& sqfs = destination / "store.squashfs"; std::size_t downloaded_mb{0u}; @@ -142,6 +144,11 @@ util::expected pull_tag(const std::string& registry, }); while (!proc->finished()) { std::this_thread::sleep_for(500ms); + // handle a signacl, usually SIGTERM or SIGINT + if (util::signal_raised()) { + spdlog::warn("signal raised - interrupting download"); + throw util::signal_exception(util::last_signal_raised()); + } if (fs::is_regular_file(sqfs)) { auto downloaded_bytes = fs::file_size(sqfs); downloaded_mb = downloaded_bytes / (1024 * 1024); diff --git a/src/uenv/print.cpp b/src/uenv/print.cpp index d94ef0f..c4aae0d 100644 --- a/src/uenv/print.cpp +++ b/src/uenv/print.cpp @@ -8,18 +8,23 @@ namespace uenv { -void print_record_set(const record_set& records, bool no_header) { +std::string format_record_set(const record_set& records, bool no_header) { if (records.empty()) { if (!no_header) { - fmt::println("no matching uenv"); + return "no matching uenv\n"; } - return; + return ""; } + auto size_string = [](std::size_t size) { + auto size_mb = size / (1024 * 1024); + return fmt::format(std::locale("en_US.UTF-8"), "{:<{}L}", size_mb, 6); + }; // print the records std::size_t w_name = std::string_view("uenv").size(); std::size_t w_sys = std::string_view("system").size(); std::size_t w_arch = std::string_view("arch").size(); + std::size_t w_size = std::string_view("size(MB)").size(); std::size_t w_date = std::string_view("yyyy/mm/dd").size(); std::size_t w_id = 16; @@ -28,56 +33,35 @@ void print_record_set(const record_set& records, bool no_header) { r.name.size() + r.version.size() + r.tag.size() + 2); w_sys = std::max(w_sys, r.system.size()); w_arch = std::max(w_arch, r.uarch.size()); + w_size = std::max(w_size, size_string(r.size_byte).size()); } w_name += 2; w_sys += 2; w_arch += 2; + w_size += 2; w_date += 2; w_id += 2; + std::string result; if (!no_header) { - auto header = fmt::format("{:<{}}{:<{}}{:<{}}{:<{}}{:<{}}\n", "uenv", - w_name, "arch", w_arch, "system", w_sys, "id", - w_id, "date", w_date); - fmt::print("{}", color::yellow(header)); + auto header = + fmt::format("{:<{}}{:<{}}{:<{}}{:<{}}{:<{}}{:<{}}\n", "uenv", + w_name, "arch", w_arch, "system", w_sys, "id", w_id, + "size(MB)", w_size, "date", w_date); + result += fmt::format("{}", color::yellow(header)); } - for (auto& r : records) { - auto name = fmt::format("{}/{}:{}", r.name, r.version, r.tag); - fmt::print("{:<{}}{:<{}}{:<{}}{:<{}}{:s}\n", name, w_name, r.uarch, - w_arch, r.system, w_sys, r.id.string(), w_id, r.date); - } -} - -std::string format_record_set(const record_set& records) { - if (records.empty()) { - return ""; - } - - // print the records - std::size_t w_name = std::string_view("uenv").size(); - std::size_t w_sys = std::string_view("system").size(); - std::size_t w_arch = std::string_view("arch").size(); - std::size_t w_id = 16; - - for (auto& r : records) { - w_name = std::max(w_name, - r.name.size() + r.version.size() + r.tag.size() + 2); - w_sys = std::max(w_sys, r.system.size()); - w_arch = std::max(w_arch, r.uarch.size()); - } - w_name += 2; - w_sys += 2; - w_arch += 2; - w_id += 2; - - std::string result{}; for (auto& r : records) { auto name = fmt::format("{}/{}:{}", r.name, r.version, r.tag); result += - fmt::format("{:<{}}{:<{}}{:<{}}{:<{}}{:s}\n", name, w_name, r.uarch, - w_arch, r.system, w_sys, r.id.string(), w_id, r.date); + fmt::format("{:<{}}{:<{}}{:<{}}{:<{}}{:{}}{:s}\n", name, w_name, + r.uarch, w_arch, r.system, w_sys, r.id.string(), w_id, + size_string(r.size_byte), w_size, r.date); } return result; } +void print_record_set(const record_set& records, bool no_header) { + fmt::print("{}", format_record_set(records, no_header)); +} + } // namespace uenv diff --git a/src/uenv/print.h b/src/uenv/print.h index 2ddfc82..8654b1b 100644 --- a/src/uenv/print.h +++ b/src/uenv/print.h @@ -4,5 +4,5 @@ namespace uenv { void print_record_set(const record_set& result, bool no_header); -std::string format_record_set(const record_set& records); +std::string format_record_set(const record_set& records, bool no_header = true); } // namespace uenv diff --git a/src/util/sigint.h b/src/util/sigint.h deleted file mode 100644 index dc21362..0000000 --- a/src/util/sigint.h +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace util { - -template -concept nothrow_move_constructible = - std::is_nothrow_move_constructible::value; - -// RAII interrupt signal handler -template class sigint_wrap { - F on_interrupt; - inline static std::atomic triggered{false}; - struct sigaction old_handler; - - public: - template ::value>> - - explicit sigint_wrap(F2&& f) noexcept : on_interrupt(std::forward(f)) { - // std::signal(SIGINT, &sigint_wrap::handler); - struct sigaction h; - h.sa_handler = &sigint_wrap::handler; - sigemptyset(&h.sa_mask); - h.sa_flags = 0; - - // set the new handler, keeping a copy of the old handler - sigaction(SIGINT, &h, &old_handler); - } - - static void handler(int signal) { - if (signal == SIGINT) { - triggered = true; - } - } - - void release() noexcept { - triggered = false; - } - - ~sigint_wrap() noexcept(noexcept(on_interrupt())) { - // reset the old handler before proceeding - sigaction(SIGINT, &old_handler, nullptr); - - // make the callback if the handler was triggered - if (triggered) { - on_interrupt(); - } - - // triggered is a static variable, so it needs to be reset for the next - // time that a guard is used. - triggered = false; - } -}; - -template auto sigint_handle(F&& f) { - return sigint_wrap>(std::forward(f)); -} - -} // namespace util diff --git a/src/util/signal.cpp b/src/util/signal.cpp new file mode 100644 index 0000000..c6e9acb --- /dev/null +++ b/src/util/signal.cpp @@ -0,0 +1,37 @@ +#include +#include + +#include + +namespace util { + +std::atomic signal_received{false}; +std::atomic last_signal{0}; + +void signal_handler(int signal) { + if (signal == SIGINT || signal == SIGTERM) { + signal_received.store(true); + last_signal.store(signal); + } +} + +void set_signal_catcher() { + signal_received.store(false); + struct sigaction h; + + h.sa_handler = signal_handler; + sigemptyset(&h.sa_mask); + h.sa_flags = SA_RESETHAND; + sigaction(SIGINT, &h, nullptr); + sigaction(SIGTERM, &h, nullptr); +} + +bool signal_raised() { + return signal_received.exchange(false); +} + +int last_signal_raised() { + return last_signal.load(); +} + +} // namespace util diff --git a/src/util/signal.h b/src/util/signal.h new file mode 100644 index 0000000..3eadc75 --- /dev/null +++ b/src/util/signal.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include + +namespace util { + +class signal_exception : public std::exception { + public: + const int signal; + const std::string msg; + signal_exception(int signal) + : signal(signal), msg(fmt::format("signal {} raised", signal)) { + } + const char* what() const throw() { + return msg.c_str(); + } +}; + +void set_signal_catcher(); +bool signal_raised(); +int last_signal_raised(); + +} // namespace util diff --git a/test/unit/signal.cpp b/test/unit/signal.cpp index 0a6b0b7..6c046f6 100644 --- a/test/unit/signal.cpp +++ b/test/unit/signal.cpp @@ -3,37 +3,22 @@ #include #include -#include - -TEST_CASE("ctrl-c", "[signal]") { - int counter{0}; - - auto on_sig = [&counter]() { ++counter; }; - - // destruction with no signal: on_sig is not called - { auto h = util::sigint_handle(on_sig); } - REQUIRE(counter == 0); - - // raise signal inside a scope: call once - { - auto h = util::sigint_handle(on_sig); - raise(SIGINT); - } - REQUIRE(counter == 1); - - // handlers that are not interrupted should not - // test here to ensure that the global state that detects - // whether a signal has been triggered was cleared previously - { auto h = util::sigint_handle(on_sig); } - - REQUIRE(counter == 1); - - // raise another 4 times: it should cleanly handle the signal each time - for (int i = 0; i < 4; ++i) { - { - auto h = util::sigint_handle(on_sig); - raise(SIGINT); - } - } - REQUIRE(counter == 5); +#include + +TEST_CASE("SIGINT", "[signal]") { + util::set_signal_catcher(); + raise(SIGINT); + REQUIRE(util::signal_raised()); + REQUIRE(!util::signal_raised()); + + util::set_signal_catcher(); + REQUIRE(!util::signal_raised()); + raise(SIGTERM); + REQUIRE(util::signal_raised()); + REQUIRE(!util::signal_raised()); + + util::set_signal_catcher(); + raise(SIGINT); + REQUIRE(util::signal_raised()); + REQUIRE(!util::signal_raised()); }