diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..6856a09 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,24 @@ +Checks: '-android-cloexec-fopen, + -cppcoreguidelines-*, + -fuchsia-default-arguments-calls, + -fuchsia-default-arguments-declarations, + -fuchsia-overloaded-operator, + -google-explicit-constructor, + -google-readability-function-size, + -google-runtime-int, + -google-runtime-references, + -hicpp-*, + -llvm-header-guard, + -llvm-include-order, + -llvmlibc-*, + -misc-*, + -modernize-*, + -readability-*, + -performance-*' +FormatStyle: 'file' + +CheckOptions: + - key: hicpp-special-member-functions.AllowSoleDefaultDtor + value: 1 + +HeaderFilterRegex: '.*hpp$' diff --git a/CMakeLists.txt b/CMakeLists.txt index d042bc5..048adf3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,6 +80,12 @@ add_executable(${PROJECT_NAME} src/main.cpp ) +add_executable(test-exec-interactive + src/config.cpp + src/utils.cpp + src/main_test.cpp + ) + # Link this 'library' to use the warnings specified in CompilerWarnings.cmake add_library(project_warnings INTERFACE) set_project_warnings(project_warnings) @@ -93,6 +99,7 @@ enable_sanitizers(project_options) include_directories(${CMAKE_SOURCE_DIR}/src) target_link_libraries(${PROJECT_NAME} PRIVATE project_warnings project_options fmt::fmt ftxui::screen ftxui::dom ftxui::component nlohmann_json::nlohmann_json cpr::cpr) +target_link_libraries(test-exec-interactive PRIVATE project_warnings project_options fmt::fmt) option(ENABLE_UNITY "Enable Unity builds of projects" OFF) if(ENABLE_UNITY) diff --git a/meson.build b/meson.build index f17b7ee..a91e639 100644 --- a/meson.build +++ b/meson.build @@ -103,6 +103,13 @@ executable( include_directories: [include_directories('src')], install: true) +executable( + 'test-exec-interactive', + files('src/config.cpp', 'src/utils.cpp', 'src/main_test.cpp'), + dependencies: [fmt], + include_directories: [include_directories('src')], + install: false) + summary( { 'Build type': get_option('buildtype'), diff --git a/src/main_test.cpp b/src/main_test.cpp new file mode 100644 index 0000000..574c9bf --- /dev/null +++ b/src/main_test.cpp @@ -0,0 +1,8 @@ +#include "definitions.hpp" +#include "utils.hpp" + +int main() { + output("\n\n------ TEST BASH LAUNCH BEGIN ------\n\n"); + utils::exec("bash", true); + output("\n\n------ TEST BASH LAUNCH END ------\n\n"); +} diff --git a/src/tui.cpp b/src/tui.cpp index 8ea83ca..afcef58 100644 --- a/src/tui.cpp +++ b/src/tui.cpp @@ -38,12 +38,10 @@ ftxui::Element centered_widget(ftxui::Component& container, const std::string_vi }); } -ftxui::Component controls_widget(const std::array&& titles, const std::array, 2>&& callbacks) { +ftxui::Component controls_widget(const std::array&& titles, const std::array, 2>&& callbacks, ftxui::ButtonOption* button_option) { /* clang-format off */ - auto button_option = ButtonOption(); - button_option.border = false; - auto button_ok = Button(titles[0].data(), callbacks[0], &button_option); - auto button_quit = Button(titles[1].data(), callbacks[1], &button_option); + auto button_ok = Button(titles[0].data(), callbacks[0], button_option); + auto button_quit = Button(titles[1].data(), callbacks[1], button_option); /* clang-format on */ auto container = Container::Horizontal({ @@ -80,6 +78,63 @@ ftxui::Element multiline_text(const std::vector& lines) { return vbox(std::move(multiline)) | frame; } +// BIOS and UEFI +void auto_partition() noexcept { + auto* config_instance = Config::instance(); + auto& config_data = config_instance->data(); + + // Find existing partitions (if any) to remove + auto parts = utils::exec(fmt::format("parted -s {} print | {}", config_data["DEVICE"], "awk \'/^ / {print $1}\'")); + const auto& del_parts = utils::make_multiline(parts); + for (const auto& del_part : del_parts) { +#ifdef NDEVENV + utils::exec(fmt::format("parted -s {} rm {}", config_data["DEVICE"], del_part)); +#else + output("{}\n", del_part); +#endif + } + +#ifdef NDEVENV + // Identify the partition table + const auto& part_table = utils::exec(fmt::format("parted -s {} print | grep -i \'partition table\' | {}", config_data["DEVICE"], "awk \'{print $3}\'")); + + // Create partition table if one does not already exist + if ((config_data["SYSTEM"] == "BIOS") && (part_table != "msdos")) + utils::exec(fmt::format("parted -s {} mklabel msdos", config_data["DEVICE"])); + if ((config_data["SYSTEM"] == "UEFI") && (part_table != "gpt")) + utils::exec(fmt::format("parted -s {} mklabel gpt", config_data["DEVICE"])); + + // Create partitions (same basic partitioning scheme for BIOS and UEFI) + if (config_data["SYSTEM"] == "BIOS") + utils::exec(fmt::format("parted -s {} mkpart primary ext3 1MiB 513MiB", config_data["DEVICE"])); + else + utils::exec(fmt::format("parted -s {} mkpart ESP fat32 1MiB 513MiB", config_data["DEVICE"])); + + utils::exec(fmt::format("parted -s {} set 1 boot on", config_data["DEVICE"])); + utils::exec(fmt::format("parted -s {} mkpart primary ext3 513MiB 100%", config_data["DEVICE"])); +#endif + + // Show created partitions + auto disklist = utils::exec(fmt::format("lsblk {} -o NAME,TYPE,FSTYPE,SIZE", config_data["DEVICE"])); + + auto& screen = tui::screen_service::instance()->data(); + /* clang-format off */ + auto button_option = ButtonOption(); + button_option.border = false; + auto button_back = Button("Back", screen.ExitLoopClosure(), &button_option); + /* clang-format on */ + + auto container = Container::Horizontal({ + button_back, + }); + + auto renderer = Renderer(container, [&] { + return tui::centered_widget(container, "New CLI Installer", multiline_text(utils::make_multiline(disklist)) | size(HEIGHT, GREATER_THAN, 5)); + }); + + screen.Loop(renderer); +} + // Simple code to show devices / partitions. void show_devices() noexcept { auto& screen = tui::screen_service::instance()->data(); @@ -122,7 +177,70 @@ void select_device() noexcept { const auto& lines = utils::make_multiline(src, " "); config_data["DEVICE"] = lines[0]; }; - auto controls_container = controls_widget({"OK", "Cancel"}, {ok_callback, screen.ExitLoopClosure()}); + + ButtonOption button_option{.border = false}; + auto controls_container = controls_widget({"OK", "Cancel"}, {ok_callback, screen.ExitLoopClosure()}, &button_option); + + auto controls = Renderer(controls_container, [&] { + return controls_container->Render() | hcenter | size(HEIGHT, LESS_THAN, 3) | size(WIDTH, GREATER_THAN, 25); + }); + + auto global = Container::Vertical({ + content, + Renderer([] { return separator(); }), + controls, + }); + + auto renderer = Renderer(global, [&] { + return tui::centered_interative_multi("New CLI Installer", global); + }); + + screen.Loop(renderer); +} + +void create_partitions() noexcept { + static constexpr std::string_view optwipe = "Securely Wipe Device (optional)"; + static constexpr std::string_view optauto = "Automatic Partitioning"; + + auto* config_instance = Config::instance(); + auto& config_data = config_instance->data(); + + std::vector menu_entries = { + optwipe.data(), + optauto.data(), + "cfdisk", + "cgdisk", + "fdisk", + "gdisk", + "parted", + }; + + auto& screen = tui::screen_service::instance()->data(); + std::int32_t selected{}; + auto menu = Menu(&menu_entries, &selected); + auto content = Renderer(menu, [&] { + return menu->Render() | center | size(HEIGHT, GREATER_THAN, 10) | size(WIDTH, GREATER_THAN, 40); + }); + + auto ok_callback = [&] { + const auto& answer = menu_entries[static_cast(selected)]; + if (answer != optwipe && answer != optauto) { + utils::exec(fmt::format("{} {}", answer, config_data["DEVICE"]), true); + return; + } + + if (answer == optwipe) { + utils::secure_wipe(); + return; + } + if (answer == optauto) { + auto_partition(); + return; + } + }; + + ButtonOption button_option{.border = false}; + auto controls_container = controls_widget({"OK", "Cancel"}, {ok_callback, screen.ExitLoopClosure()}, &button_option); auto controls = Renderer(controls_container, [&] { return controls_container->Render() | hcenter | size(HEIGHT, LESS_THAN, 3) | size(WIDTH, GREATER_THAN, 25); @@ -144,7 +262,8 @@ void select_device() noexcept { void init() noexcept { auto& screen = tui::screen_service::instance()->data(); auto ok_callback = [=] { info("ok\n"); }; - auto container = controls_widget({"OK", "Quit"}, {ok_callback, screen.ExitLoopClosure()}); + ButtonOption button_option{.border = false}; + auto container = controls_widget({"OK", "Quit"}, {ok_callback, screen.ExitLoopClosure()}, &button_option); auto renderer = Renderer(container, [&] { return tui::centered_widget(container, "New CLI Installer", text("TODO!!") | size(HEIGHT, GREATER_THAN, 5)); diff --git a/src/utils.cpp b/src/utils.cpp index 60557e5..d737549 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -2,6 +2,7 @@ #include "config.hpp" #include "definitions.hpp" +#include // for transform #include // for array #include // for filesystem, seconds #include // for int32_t @@ -9,6 +10,7 @@ #include // for exit, WIFEXITED, WIFSIGNALED #include // for exists, is_directory #include // for basic_istream, cin +#include // for regex_search, match_results<>::_Base_type #include // for operator==, string, basic_string, allocator #include // for mount #include // for waitpid @@ -16,10 +18,12 @@ #include // for execvp, fork #include // for unordered_map +#ifdef NDEVENV #include #include #include #include +#endif namespace fs = std::filesystem; @@ -51,22 +55,35 @@ void clear_screen() noexcept { output("{}", CLEAR_SCREEN_ANSI); } -std::string exec(const std::string_view& command, bool capture_output) noexcept { - if (!capture_output) { - std::int32_t status{}; - auto pid = fork(); - if (pid == 0) { - /* clang-format off */ - char* args[2] = { const_cast(command.data()), nullptr }; - /* clang-format on */ - execvp(args[0], args); - } else { - do { - waitpid(pid, &status, 0); - } while ((!WIFEXITED(status)) && (!WIFSIGNALED(status))); - } +void exec(const std::vector& vec) noexcept { + std::int32_t status{}; + auto pid = fork(); + if (pid == 0) { + std::vector args; + std::transform(vec.cbegin(), vec.cend(), std::back_inserter(args), + [=](const std::string& arg) -> char* { return const_cast(arg.data()); }); + args.push_back(nullptr); + + char** command = args.data(); + execvp(command[0], command); + } else { + do { + waitpid(pid, &status, 0); + } while ((!WIFEXITED(status)) && (!WIFSIGNALED(status))); + } +} + +// https://github.com/sheredom/subprocess.h +// https://gist.github.com/konstantint/d49ab683b978b3d74172 +// https://github.com/arun11299/cpp-subprocess/blob/master/subprocess.hpp#L1218 +// https://stackoverflow.com/questions/11342868/c-interface-for-interactive-bash +// https://github.com/hniksic/rust-subprocess +std::string exec(const std::string_view& command, const bool& interactive) noexcept { + if (interactive) { + system(command.data()); return {}; } + auto* pipe = popen(command.data(), "r"); if (!pipe) { return "popen failed!"; @@ -125,6 +142,17 @@ auto make_multiline(std::string& str, const std::string_view&& delim) noexcept - return lines; } +// install a pkg in the live session if not installed +void inst_needed(const std::string_view& pkg) { + const auto& pkg_info = utils::exec(fmt::format("pacman -Q {}", pkg)); + const std::regex pkg_regex("/error/"); + if (!std::regex_search(pkg_info, pkg_regex)) { + std::this_thread::sleep_for(std::chrono::seconds(2)); + utils::clear_screen(); + utils::exec(fmt::format("pacman -Sy --noconfirm {}", pkg)); + } +} + // Unmount partitions. void umount_partitions() noexcept { auto* config_instance = Config::instance(); @@ -144,6 +172,20 @@ void umount_partitions() noexcept { } } +// Securely destroy all data on a given device. +void secure_wipe() noexcept { + auto* config_instance = Config::instance(); + auto& config_data = config_instance->data(); + +#ifdef NDEVENV + utils::inst_needed("wipe"); + utils::exec(fmt::format("wipe -Ifre {}", config_data["DEVICE"])); +#else + utils::inst_needed("bash"); + output("{}\n", config_data["DEVICE"]); +#endif +} + void id_system() noexcept { auto* config_instance = Config::instance(); auto& config_data = config_instance->data(); @@ -223,7 +265,7 @@ void show_iwctl() noexcept { info("6 - type `exit`\n"); while (utils::prompt_char("Press a key to continue...", CYAN)) { - utils::exec("iwctl", false); + utils::exec("iwctl", true); break; } } diff --git a/src/utils.hpp b/src/utils.hpp index 1dd5263..60b2d8a 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -13,8 +13,10 @@ void print_banner() noexcept; bool prompt_char(const char* prompt, const char* color = RESET, char* read = nullptr) noexcept; void clear_screen() noexcept; [[nodiscard]] auto make_multiline(std::string& str, const std::string_view&& delim = "\n") noexcept -> std::vector; +void secure_wipe() noexcept; -auto exec(const std::string_view& command, bool capture_output = true) noexcept -> std::string; +void exec(const std::vector& vec) noexcept; +auto exec(const std::string_view& command, const bool& interactive = false) noexcept -> std::string; [[nodiscard]] bool check_root() noexcept; void id_system() noexcept; [[nodiscard]] bool handle_connection() noexcept;