From 2a8875b4109e0ce9a990b51259af6dc5b17259c8 Mon Sep 17 00:00:00 2001 From: Charliechen114514 <725610365@qq.com> Date: Tue, 26 May 2026 15:08:46 +0800 Subject: [PATCH 1/8] fix: purely disabled any rtti and exceptions --- CMakeLists.txt | 3 +++ cmake/compile/CompilerFlag.cmake | 4 ++++ src/applets/ar.cpp | 6 +++--- src/applets/hostid.cpp | 2 +- src/applets/install.cpp | 12 +++++------- src/applets/stat.cpp | 4 ++-- src/applets/sum.cpp | 4 ++-- 7 files changed, 20 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a4e8010..46cd6a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,9 @@ target_include_directories(cfbox PUBLIC include) target_include_directories(cfbox PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/include) target_link_libraries(cfbox PRIVATE cfbox_compiler_flags) +# ── Install target ───────────────────────────────────────────── +install(TARGETS cfbox DESTINATION bin) + # ── GTest via CPM (FetchContent) ────────────────────────────── if(NOT CMAKE_CROSSCOMPILING) CPMAddPackage( diff --git a/cmake/compile/CompilerFlag.cmake b/cmake/compile/CompilerFlag.cmake index ca0a625..5ef05fe 100644 --- a/cmake/compile/CompilerFlag.cmake +++ b/cmake/compile/CompilerFlag.cmake @@ -27,6 +27,10 @@ target_compile_options(cfbox_compiler_flags INTERFACE -Wnull-dereference -Wdouble-promotion -Wformat=2 + -Wformat-signedness + -Wstrict-aliasing + -fno-exceptions + -fno-rtti ) # ── Debug-specific flags ────────────────────────────────────── diff --git a/src/applets/ar.cpp b/src/applets/ar.cpp index 3382440..578ada9 100644 --- a/src/applets/ar.cpp +++ b/src/applets/ar.cpp @@ -54,9 +54,9 @@ auto ar_main(int argc, char* argv[]) -> int { std::memset(hdr, ' ', 60); std::memcpy(hdr, fname.c_str(), std::min(fname.size(), static_cast(16))); std::snprintf(hdr + 16, 12, "%-10lu", static_cast(::time(nullptr))); - std::snprintf(hdr + 28, 12, "%-6u", 0); // uid - std::snprintf(hdr + 34, 12, "%-6u", 0); // gid - std::snprintf(hdr + 40, 12, "%-8o", 0100644); + std::snprintf(hdr + 28, 12, "%-6u", 0u); // uid + std::snprintf(hdr + 34, 12, "%-6u", 0u); // gid + std::snprintf(hdr + 40, 12, "%-8o", 0100644u); std::snprintf(hdr + 48, 12, "%-10zu", data->size()); hdr[58] = '`'; hdr[59] = '\n'; output.append(hdr, 60); diff --git a/src/applets/hostid.cpp b/src/applets/hostid.cpp index 560a594..8e106dc 100644 --- a/src/applets/hostid.cpp +++ b/src/applets/hostid.cpp @@ -22,6 +22,6 @@ auto hostid_main(int argc, char* argv[]) -> int { if (parsed.has_long("version")) { cfbox::help::print_version(HELP); return 0; } long id = gethostid(); - std::printf("%08lx\n", id); + std::printf("%08ld\n", id); return 0; } diff --git a/src/applets/install.cpp b/src/applets/install.cpp index 7f66b74..a4ab7ca 100644 --- a/src/applets/install.cpp +++ b/src/applets/install.cpp @@ -36,22 +36,20 @@ auto install_main(int argc, char* argv[]) -> int { auto target_dir = parsed.get_any('t', "target-directory"); const auto& pos = parsed.positional(); - auto parse_mode = [](const std::string& s) -> std::filesystem::perms { - unsigned long m = std::stoul(s, nullptr, 8); - return static_cast(m); - }; - std::filesystem::perms mode = std::filesystem::perms::owner_read | std::filesystem::perms::owner_write | std::filesystem::perms::group_read | std::filesystem::perms::others_read; if (mode_str) { - try { mode = parse_mode(std::string{*mode_str}); } - catch (...) { + char* end = nullptr; + errno = 0; + unsigned long m = std::strtoul(mode_str->data(), &end, 8); + if (errno != 0 || end == mode_str->data() || *end != '\0') { std::fprintf(stderr, "cfbox install: invalid mode '%.*s'\n", static_cast(mode_str->size()), mode_str->data()); return 1; } + mode = static_cast(m); } if (mkdir_mode) { diff --git a/src/applets/stat.cpp b/src/applets/stat.cpp index c79b977..9cb9024 100644 --- a/src/applets/stat.cpp +++ b/src/applets/stat.cpp @@ -155,8 +155,8 @@ auto stat_main(int argc, char* argv[]) -> int { file_type_string(st.st_mode)); auto* pw = getpwuid(st.st_uid); auto* gr = getgrgid(st.st_gid); - std::printf("Access: (%04o/%s) Uid: (%5d/%-8s) Gid: (%5d/%-8s)\n", - st.st_mode & 07777, + std::printf("Access: (%04o/%s) Uid: (%5u/%-8s) Gid: (%5u/%-8s)\n", + st.st_mode & 07777u, format_perms(st.st_mode).c_str(), st.st_uid, pw ? pw->pw_name : "", st.st_gid, gr ? gr->gr_name : ""); diff --git a/src/applets/sum.cpp b/src/applets/sum.cpp index 865301e..658db1f 100644 --- a/src/applets/sum.cpp +++ b/src/applets/sum.cpp @@ -40,10 +40,10 @@ auto sum_main(int argc, char* argv[]) -> int { if (sysv) { auto result = cfbox::checksum::sysv_sum(*data_result); - std::printf("%d %d", result.checksum, result.blocks); + std::printf("%u %u", result.checksum, result.blocks); } else { auto result = cfbox::checksum::bsd_sum(*data_result); - std::printf("%05d %5d", result.checksum, result.blocks); + std::printf("%05u %5u", result.checksum, result.blocks); } if (p != "-") std::printf(" %.*s", static_cast(p.size()), p.data()); std::putchar('\n'); From 56486a1c5b21b273f1871cfcc82cf3818eca999d Mon Sep 17 00:00:00 2001 From: Charliechen114514 <725610365@qq.com> Date: Tue, 26 May 2026 15:19:40 +0800 Subject: [PATCH 2/8] file ptr leak away and concurrency saftely --- src/applets/dmesg.cpp | 9 ++++----- src/applets/init/init.hpp | 7 ++++--- src/applets/init/init_shutdown.cpp | 1 - 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/applets/dmesg.cpp b/src/applets/dmesg.cpp index 919888d..24feebd 100644 --- a/src/applets/dmesg.cpp +++ b/src/applets/dmesg.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace { @@ -21,23 +22,21 @@ constexpr cfbox::help::HelpEntry HELP = { auto read_kmsg() -> std::vector { std::vector lines; - // Try /var/log/dmesg first (works without CAP_SYSLOG) - FILE* f = std::fopen("/var/log/dmesg", "r"); - if (!f) f = std::fopen("/var/log/kern.log", "r"); + cfbox::io::unique_file f(std::fopen("/var/log/dmesg", "r")); + if (!f) f.reset(std::fopen("/var/log/kern.log", "r")); if (!f) { std::fprintf(stderr, "cfbox dmesg: cannot open kernel log\n"); return lines; } char buf[4096]; - while (std::fgets(buf, sizeof(buf), f)) { + while (std::fgets(buf, sizeof(buf), f.get())) { auto len = std::strlen(buf); while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) { buf[--len] = '\0'; } lines.emplace_back(buf); } - std::fclose(f); return lines; } diff --git a/src/applets/init/init.hpp b/src/applets/init/init.hpp index 942d6b1..a35589e 100644 --- a/src/applets/init/init.hpp +++ b/src/applets/init/init.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -54,9 +55,9 @@ struct InitState { RunLevel runlevel = RunLevel::SysInit; std::vector entries; std::vector children; - bool shutting_down = false; - bool sigchld_received = false; - bool sigint_received = false; + volatile sig_atomic_t shutting_down = 0; + volatile sig_atomic_t sigchld_received = 0; + volatile sig_atomic_t sigint_received = 0; }; // --- Module interfaces --- diff --git a/src/applets/init/init_shutdown.cpp b/src/applets/init/init_shutdown.cpp index c0d9d25..ff77f91 100644 --- a/src/applets/init/init_shutdown.cpp +++ b/src/applets/init/init_shutdown.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include From 4ddd51cc9779c661671361ccb3417f614464be23 Mon Sep 17 00:00:00 2001 From: Charliechen114514 <725610365@qq.com> Date: Tue, 26 May 2026 16:13:32 +0800 Subject: [PATCH 3/8] migrate to using the SAME Macro logger --- include/cfbox/error.hpp | 8 ++++++++ include/cfbox/io.hpp | 2 +- include/cfbox/term.hpp | 8 +++++++- src/applets/ar.cpp | 13 +++++++------ src/applets/awk/awk_executor.cpp | 3 ++- src/applets/awk/awk_main.cpp | 3 ++- src/applets/awk/awk_parser.cpp | 4 ++-- src/applets/basename.cpp | 3 ++- src/applets/cat.cpp | 3 ++- src/applets/chgrp.cpp | 5 +++-- src/applets/chmod.cpp | 15 ++++++++------- src/applets/chown.cpp | 11 ++++++----- src/applets/cksum.cpp | 3 ++- src/applets/cmp.cpp | 7 ++++--- src/applets/comm.cpp | 7 ++++--- src/applets/cp.cpp | 25 ++++++++++--------------- src/applets/cpio.cpp | 9 +++++---- src/applets/cut.cpp | 11 ++++++----- src/applets/date.cpp | 4 ++-- src/applets/df.cpp | 6 +++--- src/applets/diff.cpp | 7 ++++--- src/applets/dirname.cpp | 3 ++- src/applets/dmesg.cpp | 3 ++- src/applets/env.cpp | 4 ++-- src/applets/expand.cpp | 5 +++-- src/applets/expr.cpp | 7 ++++--- src/applets/factor.cpp | 3 ++- src/applets/find.cpp | 4 ++-- src/applets/fold.cpp | 5 +++-- src/applets/free.cpp | 3 ++- src/applets/fuser.cpp | 7 ++++--- src/applets/grep.cpp | 11 ++++++----- src/applets/gunzip.cpp | 7 ++++--- src/applets/gzip.cpp | 7 ++++--- src/applets/head.cpp | 3 ++- src/applets/hexdump.cpp | 3 ++- src/applets/hostname.cpp | 3 ++- src/applets/init/init_spawn.cpp | 7 +++---- src/applets/install.cpp | 17 ++++++++--------- src/applets/iostat.cpp | 3 ++- src/applets/kill.cpp | 7 ++++--- src/applets/link.cpp | 5 +++-- src/applets/ln.cpp | 5 +++-- src/applets/logname.cpp | 3 ++- src/applets/ls.cpp | 12 ++++++------ src/applets/md5sum.cpp | 3 ++- src/applets/mkdir.cpp | 12 +++++------- src/applets/mkfifo.cpp | 6 +++--- src/applets/mknod.cpp | 12 ++++++------ src/applets/mktemp.cpp | 9 ++++----- src/applets/more.cpp | 3 ++- src/applets/mountpoint.cpp | 5 +++-- src/applets/mv.cpp | 23 +++++++++-------------- src/applets/nice.cpp | 5 +++-- src/applets/nl.cpp | 3 ++- src/applets/nohup.cpp | 8 ++++---- src/applets/od.cpp | 3 ++- src/applets/paste.cpp | 5 +++-- src/applets/patch.cpp | 11 ++++++----- src/applets/pgrep.cpp | 5 +++-- src/applets/pidof.cpp | 5 +++-- src/applets/pmap.cpp | 5 +++-- src/applets/ps.cpp | 3 ++- src/applets/pstree.cpp | 3 ++- src/applets/pwd.cpp | 3 ++- src/applets/pwdx.cpp | 6 +++--- src/applets/readlink.cpp | 5 +++-- src/applets/realpath.cpp | 5 +++-- src/applets/renice.cpp | 7 ++++--- src/applets/rev.cpp | 3 ++- src/applets/rm.cpp | 18 +++++++----------- src/applets/rmdir.cpp | 6 +++--- src/applets/sed.cpp | 9 +++++---- src/applets/seq.cpp | 5 +++-- src/applets/sh/sh_builtins.cpp | 7 ++++--- src/applets/sh/sh_executor.cpp | 17 +++++++++-------- src/applets/sh/sh_main.cpp | 3 ++- src/applets/sh/sh_parser.cpp | 23 ++++++++++++----------- src/applets/shuf.cpp | 3 ++- src/applets/sleep.cpp | 6 +++--- src/applets/sort.cpp | 7 ++++--- src/applets/split.cpp | 7 ++++--- src/applets/stat.cpp | 9 ++++----- src/applets/sum.cpp | 3 ++- src/applets/sysctl.cpp | 11 ++++++----- src/applets/tac.cpp | 3 ++- src/applets/tail.cpp | 3 ++- src/applets/tar.cpp | 11 ++++++----- src/applets/tee.cpp | 3 ++- src/applets/test.cpp | 27 +++++++++++++-------------- src/applets/timeout.cpp | 13 ++++++------- src/applets/touch.cpp | 9 ++++----- src/applets/tr.cpp | 5 +++-- src/applets/truncate.cpp | 10 +++++----- src/applets/tsort.cpp | 3 ++- src/applets/uname.cpp | 3 ++- src/applets/uniq.cpp | 5 +++-- src/applets/unlink.cpp | 9 ++++----- src/applets/unzip.cpp | 9 +++++---- src/applets/uptime.cpp | 3 ++- src/applets/usleep.cpp | 6 +++--- src/applets/watch.cpp | 3 ++- src/applets/wc.cpp | 7 ++++--- src/applets/which.cpp | 6 +++--- src/applets/xargs.cpp | 7 ++++--- 105 files changed, 398 insertions(+), 333 deletions(-) diff --git a/include/cfbox/error.hpp b/include/cfbox/error.hpp index 3d1d55c..18e7b8d 100644 --- a/include/cfbox/error.hpp +++ b/include/cfbox/error.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include #include @@ -19,6 +20,13 @@ template using Result = std::expected; } // namespace cfbox::base +#ifndef CFBOX_ERR +# define CFBOX_ERR(cmd, fmt, ...) \ + std::fprintf(stderr, "cfbox " cmd ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__) +# define CFBOX_ERR_V(cmd, fmt, ...) \ + std::fprintf(stderr, "cfbox %s: " fmt "\n", cmd __VA_OPT__(,) __VA_ARGS__) +#endif + #ifndef CFBOX_TRY # define CFBOX_TRY(var, expr) \ auto var = (expr); \ diff --git a/include/cfbox/io.hpp b/include/cfbox/io.hpp index 331df90..8fff3c6 100644 --- a/include/cfbox/io.hpp +++ b/include/cfbox/io.hpp @@ -19,7 +19,7 @@ struct FileCloser { using unique_file = std::unique_ptr; [[nodiscard]] inline auto open_file(std::string_view path, const char* mode) -> base::Result { - auto* f = std::fopen(std::string{path}.c_str(), mode); + auto* f = std::fopen(path.data(), mode); if (!f) { return std::unexpected(base::Error{errno, "cannot open file: " + std::string{path}}); } diff --git a/include/cfbox/term.hpp b/include/cfbox/term.hpp index 4a846be..8be0e30 100644 --- a/include/cfbox/term.hpp +++ b/include/cfbox/term.hpp @@ -68,7 +68,13 @@ namespace detail { // Utility: wrap text with a color and reset inline auto colored(std::string_view text, std::string_view color_code) -> std::string { if (!color_enabled()) return std::string{text}; - return std::string{color_code} + std::string{text} + std::string{reset()}; + auto r = reset(); + std::string result; + result.reserve(color_code.size() + text.size() + r.size()); + result.append(color_code); + result.append(text); + result.append(r); + return result; } } // namespace cfbox::term diff --git a/src/applets/ar.cpp b/src/applets/ar.cpp index 578ada9..2fcf79b 100644 --- a/src/applets/ar.cpp +++ b/src/applets/ar.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -37,7 +38,7 @@ auto ar_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox ar: missing archive name\n"); + CFBOX_ERR("ar", "missing archive name"); return 1; } @@ -65,7 +66,7 @@ auto ar_main(int argc, char* argv[]) -> int { } auto wresult = cfbox::io::write_all(archive, output); if (!wresult) { - std::fprintf(stderr, "cfbox ar: %s\n", wresult.error().msg.c_str()); + CFBOX_ERR("ar", "%s", wresult.error().msg.c_str()); return 1; } return 0; @@ -74,12 +75,12 @@ auto ar_main(int argc, char* argv[]) -> int { if (list || extract) { auto input = cfbox::io::read_all(archive); if (!input) { - std::fprintf(stderr, "cfbox ar: %s\n", input.error().msg.c_str()); + CFBOX_ERR("ar", "%s", input.error().msg.c_str()); return 1; } const auto& data = *input; if (data.size() < 8 || data.substr(0, 8) != "!\n") { - std::fprintf(stderr, "cfbox ar: not a valid archive\n"); + CFBOX_ERR("ar", "not a valid archive"); return 1; } std::size_t offset = 8; @@ -95,7 +96,7 @@ auto ar_main(int argc, char* argv[]) -> int { } else if (extract) { auto content = data.substr(offset + 60, fsize); if (!cfbox::io::write_all(name, content)) { - std::fprintf(stderr, "cfbox ar: write failed: %s\n", name.c_str()); + CFBOX_ERR("ar", "write failed: %s", name.c_str()); return 1; } } @@ -105,6 +106,6 @@ auto ar_main(int argc, char* argv[]) -> int { return 0; } - std::fprintf(stderr, "cfbox ar: must specify -r, -t, or -x\n"); + CFBOX_ERR("ar", "must specify -r, -t, or -x"); return 1; } diff --git a/src/applets/awk/awk_executor.cpp b/src/applets/awk/awk_executor.cpp index 2bc2588..2696818 100644 --- a/src/applets/awk/awk_executor.cpp +++ b/src/applets/awk/awk_executor.cpp @@ -5,6 +5,7 @@ #include #include +#include namespace cfbox::awk { @@ -450,7 +451,7 @@ class Executor { auto process_file(const std::string& path, NodePtr prog) -> void { FILE* f = std::fopen(path.c_str(), "r"); - if (!f) { std::fprintf(stderr, "cfbox awk: cannot open '%s'\n", path.c_str()); return; } + if (!f) { CFBOX_ERR("awk", "cannot open '%s'", path.c_str()); return; }; st_.filename = path; char line[65536]; while (std::fgets(line, sizeof(line), f)) { diff --git a/src/applets/awk/awk_main.cpp b/src/applets/awk/awk_main.cpp index 89a5c3d..8229fc6 100644 --- a/src/applets/awk/awk_main.cpp +++ b/src/applets/awk/awk_main.cpp @@ -5,6 +5,7 @@ #include "awk.hpp" #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -43,7 +44,7 @@ auto awk_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox awk: missing program\n"); + CFBOX_ERR("awk", "missing program"); return 1; } diff --git a/src/applets/awk/awk_parser.cpp b/src/applets/awk/awk_parser.cpp index 26d3136..9566f45 100644 --- a/src/applets/awk/awk_parser.cpp +++ b/src/applets/awk/awk_parser.cpp @@ -1,5 +1,6 @@ #include "awk.hpp" #include +#include namespace cfbox::awk { @@ -30,8 +31,7 @@ class Parser { auto expect(TokType t) -> Token { auto tok = advance(); if (tok.type != t) { - std::fprintf(stderr, "cfbox awk: expected token type %d, got '%s'\n", - static_cast(t), tok.text.c_str()); + CFBOX_ERR("awk", "expected token type %d, got '%s'", static_cast(t), tok.text.c_str()); } return tok; } diff --git a/src/applets/basename.cpp b/src/applets/basename.cpp index bc8f920..b0e9d92 100644 --- a/src/applets/basename.cpp +++ b/src/applets/basename.cpp @@ -4,6 +4,7 @@ #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -56,7 +57,7 @@ auto basename_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox basename: missing operand\n"); + CFBOX_ERR("basename", "missing operand"); return 1; } diff --git a/src/applets/cat.cpp b/src/applets/cat.cpp index be7a0b8..46b48b7 100644 --- a/src/applets/cat.cpp +++ b/src/applets/cat.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace { @@ -68,7 +69,7 @@ auto cat_file(std::string_view path, bool n_flag, bool b_flag, bool A_flag) -> i auto result = cfbox::io::open_file(path, "rb"); if (!result) { - std::fprintf(stderr, "cfbox cat: %s\n", result.error().msg.c_str()); + CFBOX_ERR("cat", "%s", result.error().msg.c_str()); return 1; } diff --git a/src/applets/chgrp.cpp b/src/applets/chgrp.cpp index e77cc37..5a7d4df 100644 --- a/src/applets/chgrp.cpp +++ b/src/applets/chgrp.cpp @@ -8,6 +8,7 @@ #include #include +#include namespace { @@ -37,7 +38,7 @@ auto chgrp_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.size() < 2) { - std::fprintf(stderr, "cfbox chgrp: missing operand\n"); + CFBOX_ERR("chgrp", "missing operand"); return 2; } @@ -52,7 +53,7 @@ auto chgrp_main(int argc, char* argv[]) -> int { auto chgrp_one = [&](const std::string& path) -> int { if (::chown(path.c_str(), static_cast(-1), gid) != 0) { - std::fprintf(stderr, "cfbox chgrp: %s: %s\n", path.c_str(), std::strerror(errno)); + CFBOX_ERR("chgrp", "%s: %s", path.c_str(), std::strerror(errno)); return 1; } if (verbose) std::printf("group of '%s' changed\n", path.c_str()); diff --git a/src/applets/chmod.cpp b/src/applets/chmod.cpp index 70c9b50..94855a3 100644 --- a/src/applets/chmod.cpp +++ b/src/applets/chmod.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace { @@ -80,7 +81,7 @@ auto apply_symbolic(std::filesystem::perms current, std::string_view spec) -> st auto chmod_one(const std::string& path, std::filesystem::perms mode, bool verbose) -> int { auto result = cfbox::fs::permissions(path, mode); if (!result) { - std::fprintf(stderr, "cfbox chmod: %s: %s\n", path.c_str(), result.error().msg.c_str()); + CFBOX_ERR("chmod", "%s: %s", path.c_str(), result.error().msg.c_str()); return 1; } if (verbose) std::printf("mode of '%s' changed\n", path.c_str()); @@ -109,24 +110,24 @@ auto chmod_main(int argc, char* argv[]) -> int { if (parsed.has_long("reference")) { auto rfile = parsed.get_long("reference"); if (!rfile) { - std::fprintf(stderr, "cfbox chmod: --reference requires an argument\n"); + CFBOX_ERR("chmod", "--reference requires an argument"); return 2; } std::string rfile_str(*rfile); auto st = cfbox::fs::status(rfile_str); if (!st) { - std::fprintf(stderr, "cfbox chmod: %s: %s\n", rfile_str.c_str(), st.error().msg.c_str()); + CFBOX_ERR("chmod", "%s: %s", rfile_str.c_str(), st.error().msg.c_str()); return 1; } target_mode = st->permissions(); files_start = 0; if (pos.empty()) { - std::fprintf(stderr, "cfbox chmod: missing operand\n"); + CFBOX_ERR("chmod", "missing operand"); return 2; } } else { if (pos.size() < 2) { - std::fprintf(stderr, "cfbox chmod: missing operand\n"); + CFBOX_ERR("chmod", "missing operand"); return 2; } auto octal = parse_octal(pos[0]); @@ -138,13 +139,13 @@ auto chmod_main(int argc, char* argv[]) -> int { std::string path(pos[i]); auto st = cfbox::fs::status(path); if (!st) { - std::fprintf(stderr, "cfbox chmod: %s: %s\n", path.c_str(), st.error().msg.c_str()); + CFBOX_ERR("chmod", "%s: %s", path.c_str(), st.error().msg.c_str()); rc = 1; continue; } auto new_mode = apply_symbolic(st->permissions(), pos[0]); if (!new_mode) { - std::fprintf(stderr, "cfbox chmod: invalid mode: %s\n", std::string(pos[0]).c_str()); + CFBOX_ERR("chmod", "invalid mode: %s", std::string(pos[0]).c_str()); return 2; } if (chmod_one(path, *new_mode, verbose) != 0) rc = 1; diff --git a/src/applets/chown.cpp b/src/applets/chown.cpp index 2dfcbe8..72cd0fc 100644 --- a/src/applets/chown.cpp +++ b/src/applets/chown.cpp @@ -11,6 +11,7 @@ #include #include +#include namespace { @@ -66,7 +67,7 @@ auto parse_owner_spec(std::string_view spec) -> OwnerSpec { auto chown_one(const std::string& path, uid_t uid, gid_t gid, bool verbose) -> int { if (::chown(path.c_str(), uid, gid) != 0) { - std::fprintf(stderr, "cfbox chown: %s: %s\n", path.c_str(), std::strerror(errno)); + CFBOX_ERR("chown", "%s: %s", path.c_str(), std::strerror(errno)); return 1; } if (verbose) std::printf("ownership of '%s' changed\n", path.c_str()); @@ -95,13 +96,13 @@ auto chown_main(int argc, char* argv[]) -> int { if (parsed.has_long("reference")) { auto rfile = parsed.get_long("reference"); if (!rfile) { - std::fprintf(stderr, "cfbox chown: --reference requires an argument\n"); + CFBOX_ERR("chown", "--reference requires an argument"); return 2; } struct stat st; std::string rfile_str(*rfile); if (stat(rfile_str.c_str(), &st) != 0) { - std::fprintf(stderr, "cfbox chown: %s: %s\n", rfile_str.c_str(), std::strerror(errno)); + CFBOX_ERR("chown", "%s: %s", rfile_str.c_str(), std::strerror(errno)); return 1; } owner.uid = st.st_uid; @@ -110,12 +111,12 @@ auto chown_main(int argc, char* argv[]) -> int { owner.set_gid = true; files_start = 0; if (pos.empty()) { - std::fprintf(stderr, "cfbox chown: missing operand\n"); + CFBOX_ERR("chown", "missing operand"); return 2; } } else { if (pos.size() < 2) { - std::fprintf(stderr, "cfbox chown: missing operand\n"); + CFBOX_ERR("chown", "missing operand"); return 2; } owner = parse_owner_spec(pos[0]); diff --git a/src/applets/cksum.cpp b/src/applets/cksum.cpp index 7e40009..4bc5a38 100644 --- a/src/applets/cksum.cpp +++ b/src/applets/cksum.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -30,7 +31,7 @@ auto cksum_main(int argc, char* argv[]) -> int { for (auto p : paths) { auto data_result = (p == "-") ? cfbox::io::read_all_stdin() : cfbox::io::read_all(p); if (!data_result) { - std::fprintf(stderr, "cfbox cksum: %s\n", data_result.error().msg.c_str()); + CFBOX_ERR("cksum", "%s", data_result.error().msg.c_str()); rc = 1; continue; } diff --git a/src/applets/cmp.cpp b/src/applets/cmp.cpp index 417e38a..99e7f1d 100644 --- a/src/applets/cmp.cpp +++ b/src/applets/cmp.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -25,14 +26,14 @@ auto cmp_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.size() < 2) { - std::fprintf(stderr, "cfbox cmp: missing operand\n"); + CFBOX_ERR("cmp", "missing operand"); return 2; } auto a_result = cfbox::io::read_all(std::string{pos[0]}); auto b_result = cfbox::io::read_all(std::string{pos[1]}); - if (!a_result) { std::fprintf(stderr, "cfbox cmp: %s\n", a_result.error().msg.c_str()); return 2; } - if (!b_result) { std::fprintf(stderr, "cfbox cmp: %s\n", b_result.error().msg.c_str()); return 2; } + if (!a_result) { CFBOX_ERR("cmp", "%s", a_result.error().msg.c_str()); return 2; }; + if (!b_result) { CFBOX_ERR("cmp", "%s", b_result.error().msg.c_str()); return 2; }; const auto& a = *a_result; const auto& b = *b_result; diff --git a/src/applets/comm.cpp b/src/applets/comm.cpp index 4dfefc7..c283d6a 100644 --- a/src/applets/comm.cpp +++ b/src/applets/comm.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -35,18 +36,18 @@ auto comm_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.size() < 2) { - std::fprintf(stderr, "cfbox comm: missing operand\n"); + CFBOX_ERR("comm", "missing operand"); return 1; } auto lines1_result = cfbox::io::read_lines(std::string{pos[0]}); auto lines2_result = cfbox::io::read_lines(std::string{pos[1]}); if (!lines1_result) { - std::fprintf(stderr, "cfbox comm: %s\n", lines1_result.error().msg.c_str()); + CFBOX_ERR("comm", "%s", lines1_result.error().msg.c_str()); return 1; } if (!lines2_result) { - std::fprintf(stderr, "cfbox comm: %s\n", lines2_result.error().msg.c_str()); + CFBOX_ERR("comm", "%s", lines2_result.error().msg.c_str()); return 1; } diff --git a/src/applets/cp.cpp b/src/applets/cp.cpp index 6d72f7d..aaeafda 100644 --- a/src/applets/cp.cpp +++ b/src/applets/cp.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace { @@ -23,7 +24,7 @@ auto copy_preserve(const std::string& src, const std::string& dst) -> int { // Copy the file first auto copy_result = cfbox::fs::copy_file(src, dst); if (!copy_result) { - std::fprintf(stderr, "cfbox cp: %s\n", copy_result.error().msg.c_str()); + CFBOX_ERR("cp", "%s", copy_result.error().msg.c_str()); return 1; } @@ -55,7 +56,7 @@ auto cp_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.size() < 2) { - std::fprintf(stderr, "cfbox cp: missing file operand\n"); + CFBOX_ERR("cp", "missing file operand"); return 1; } @@ -69,8 +70,7 @@ auto cp_main(int argc, char* argv[]) -> int { if (cfbox::fs::is_directory(src)) { if (!recursive) { - std::fprintf(stderr, "cfbox cp: -r not specified; omitting directory '%s'\n", - src.c_str()); + CFBOX_ERR("cp", "-r not specified; omitting directory '%s'", src.c_str()); return 1; } // Determine destination path @@ -82,8 +82,7 @@ auto cp_main(int argc, char* argv[]) -> int { } auto result = cfbox::fs::copy_recursive(src, dest); if (!result) { - std::fprintf(stderr, "cfbox cp: cannot copy '%s' to '%s': %s\n", - src.c_str(), dest.c_str(), result.error().msg.c_str()); + CFBOX_ERR("cp", "cannot copy '%s' to '%s': %s", src.c_str(), dest.c_str(), result.error().msg.c_str()); rc = 1; } } else { @@ -97,8 +96,7 @@ auto cp_main(int argc, char* argv[]) -> int { } else { auto result = cfbox::fs::copy_file(src, dest); if (!result) { - std::fprintf(stderr, "cfbox cp: cannot copy '%s' to '%s': %s\n", - src.c_str(), dest.c_str(), result.error().msg.c_str()); + CFBOX_ERR("cp", "cannot copy '%s' to '%s': %s", src.c_str(), dest.c_str(), result.error().msg.c_str()); rc = 1; } } @@ -106,7 +104,7 @@ auto cp_main(int argc, char* argv[]) -> int { } else { // Multiple sources — destination must be a directory if (!cfbox::fs::is_directory(dst)) { - std::fprintf(stderr, "cfbox cp: target '%s' is not a directory\n", dst.c_str()); + CFBOX_ERR("cp", "target '%s' is not a directory", dst.c_str()); return 1; } @@ -117,15 +115,13 @@ auto cp_main(int argc, char* argv[]) -> int { if (cfbox::fs::is_directory(src)) { if (!recursive) { - std::fprintf(stderr, "cfbox cp: -r not specified; omitting directory '%s'\n", - src.c_str()); + CFBOX_ERR("cp", "-r not specified; omitting directory '%s'", src.c_str()); rc = 1; continue; } auto result = cfbox::fs::copy_recursive(src, dest); if (!result) { - std::fprintf(stderr, "cfbox cp: cannot copy '%s' to '%s': %s\n", - src.c_str(), dest.c_str(), result.error().msg.c_str()); + CFBOX_ERR("cp", "cannot copy '%s' to '%s': %s", src.c_str(), dest.c_str(), result.error().msg.c_str()); rc = 1; } } else { @@ -134,8 +130,7 @@ auto cp_main(int argc, char* argv[]) -> int { } else { auto result = cfbox::fs::copy_file(src, dest); if (!result) { - std::fprintf(stderr, "cfbox cp: cannot copy '%s' to '%s': %s\n", - src.c_str(), dest.c_str(), result.error().msg.c_str()); + CFBOX_ERR("cp", "cannot copy '%s' to '%s': %s", src.c_str(), dest.c_str(), result.error().msg.c_str()); rc = 1; } } diff --git a/src/applets/cpio.cpp b/src/applets/cpio.cpp index 63c1895..74c8310 100644 --- a/src/applets/cpio.cpp +++ b/src/applets/cpio.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -46,7 +47,7 @@ auto cpio_main(int argc, char* argv[]) -> int { if (copy_out) { auto input = cfbox::io::read_all_stdin(); - if (!input) { std::fprintf(stderr, "cfbox cpio: read error\n"); return 1; } + if (!input) { CFBOX_ERR("cpio", "read error"); return 1; }; // Read filenames from stdin std::vector names; std::string name; @@ -85,7 +86,7 @@ auto cpio_main(int argc, char* argv[]) -> int { if (copy_in || list_mode) { auto input = cfbox::io::read_all_stdin(); - if (!input) { std::fprintf(stderr, "cfbox cpio: read error\n"); return 1; } + if (!input) { CFBOX_ERR("cpio", "read error"); return 1; }; const auto& data = *input; std::size_t offset = 0; while (offset + 110 < data.size()) { @@ -103,7 +104,7 @@ auto cpio_main(int argc, char* argv[]) -> int { } else { auto content = data.substr(offset + hdr_size, filesize); auto wresult = cfbox::io::write_all(name, content); - if (!wresult) std::fprintf(stderr, "cfbox cpio: %s\n", wresult.error().msg.c_str()); + if (!wresult) CFBOX_ERR("cpio", "%s", wresult.error().msg.c_str()); } auto data_padded = (filesize + 3) & ~3u; offset += hdr_size + data_padded; @@ -111,6 +112,6 @@ auto cpio_main(int argc, char* argv[]) -> int { return 0; } - std::fprintf(stderr, "cfbox cpio: must specify -o, -i, or -t\n"); + CFBOX_ERR("cpio", "must specify -o, -i, or -t"); return 1; } diff --git a/src/applets/cut.cpp b/src/applets/cut.cpp index ee45336..29400db 100644 --- a/src/applets/cut.cpp +++ b/src/applets/cut.cpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -64,7 +65,7 @@ auto cut_main(int argc, char* argv[]) -> int { char delim = '\t'; if (auto d = parsed.get_any('d', "delimiter")) { if (d->size() != 1) { - std::fprintf(stderr, "cfbox cut: delimiter must be a single character\n"); + CFBOX_ERR("cut", "delimiter must be a single character"); return 1; } delim = (*d)[0]; @@ -75,7 +76,7 @@ auto cut_main(int argc, char* argv[]) -> int { bool char_mode = parsed.has_any('c', "characters"); if (!field_mode && !char_mode) { - std::fprintf(stderr, "cfbox cut: you must specify a list of fields or characters\n"); + CFBOX_ERR("cut", "you must specify a list of fields or characters"); return 1; } @@ -83,14 +84,14 @@ auto cut_main(int argc, char* argv[]) -> int { if (field_mode) { auto list = parsed.get_any('f', "fields"); if (!list) { - std::fprintf(stderr, "cfbox cut: missing list for -f\n"); + CFBOX_ERR("cut", "missing list for -f"); return 1; } indices = parse_range_list(std::string{*list}); } else { auto list = parsed.get_any('c', "characters"); if (!list) { - std::fprintf(stderr, "cfbox cut: missing list for -c\n"); + CFBOX_ERR("cut", "missing list for -c"); return 1; } indices = parse_range_list(std::string{*list}); @@ -130,7 +131,7 @@ auto cut_main(int argc, char* argv[]) -> int { return true; }); if (!result) { - std::fprintf(stderr, "cfbox cut: %s\n", result.error().msg.c_str()); + CFBOX_ERR("cut", "%s", result.error().msg.c_str()); rc = 1; } } diff --git a/src/applets/date.cpp b/src/applets/date.cpp index 327a32a..6d76210 100644 --- a/src/applets/date.cpp +++ b/src/applets/date.cpp @@ -5,6 +5,7 @@ #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -60,8 +61,7 @@ auto date_main(int argc, char* argv[]) -> int { if (result) { now = mktime(&tm_val); } else { - std::fprintf(stderr, "cfbox date: invalid date '%.*s'\n", - static_cast(d->size()), d->data()); + CFBOX_ERR("date", "invalid date '%.*s'", static_cast(d->size()), d->data()); return 1; } } diff --git a/src/applets/df.cpp b/src/applets/df.cpp index 7d947b0..8069880 100644 --- a/src/applets/df.cpp +++ b/src/applets/df.cpp @@ -7,6 +7,7 @@ #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -56,8 +57,7 @@ auto df_main(int argc, char* argv[]) -> int { for (auto p : pos) { struct statvfs vfs; if (statvfs(std::string{p}.c_str(), &vfs) != 0) { - std::fprintf(stderr, "cfbox df: cannot stat '%.*s': %s\n", - static_cast(p.size()), p.data(), std::strerror(errno)); + CFBOX_ERR("df", "cannot stat '%.*s': %s", static_cast(p.size()), p.data(), std::strerror(errno)); continue; } auto bsize = static_cast(vfs.f_bsize); @@ -73,7 +73,7 @@ auto df_main(int argc, char* argv[]) -> int { auto* mtab = setmntent("/proc/mounts", "r"); if (!mtab) mtab = setmntent("/etc/mtab", "r"); if (!mtab) { - std::fprintf(stderr, "cfbox df: cannot read mount table\n"); + CFBOX_ERR("df", "cannot read mount table"); return 1; } struct mntent* mnt; diff --git a/src/applets/diff.cpp b/src/applets/diff.cpp index 55210da..dacbd52 100644 --- a/src/applets/diff.cpp +++ b/src/applets/diff.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -243,14 +244,14 @@ auto diff_main(int argc, char* argv[]) -> int { bool unified = parsed.has('u'); const auto& pos = parsed.positional(); if (pos.size() < 2) { - std::fprintf(stderr, "cfbox diff: missing operand\n"); + CFBOX_ERR("diff", "missing operand"); return 2; } auto a_result = cfbox::io::read_lines(std::string{pos[0]}); auto b_result = cfbox::io::read_lines(std::string{pos[1]}); - if (!a_result) { std::fprintf(stderr, "cfbox diff: %s\n", a_result.error().msg.c_str()); return 2; } - if (!b_result) { std::fprintf(stderr, "cfbox diff: %s\n", b_result.error().msg.c_str()); return 2; } + if (!a_result) { CFBOX_ERR("diff", "%s", a_result.error().msg.c_str()); return 2; }; + if (!b_result) { CFBOX_ERR("diff", "%s", b_result.error().msg.c_str()); return 2; }; if (*a_result == *b_result) return 0; diff --git a/src/applets/dirname.cpp b/src/applets/dirname.cpp index 69c46f7..097bd2e 100644 --- a/src/applets/dirname.cpp +++ b/src/applets/dirname.cpp @@ -4,6 +4,7 @@ #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -48,7 +49,7 @@ auto dirname_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox dirname: missing operand\n"); + CFBOX_ERR("dirname", "missing operand"); return 1; } diff --git a/src/applets/dmesg.cpp b/src/applets/dmesg.cpp index 24feebd..d66fb62 100644 --- a/src/applets/dmesg.cpp +++ b/src/applets/dmesg.cpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace { @@ -25,7 +26,7 @@ auto read_kmsg() -> std::vector { cfbox::io::unique_file f(std::fopen("/var/log/dmesg", "r")); if (!f) f.reset(std::fopen("/var/log/kern.log", "r")); if (!f) { - std::fprintf(stderr, "cfbox dmesg: cannot open kernel log\n"); + CFBOX_ERR("dmesg", "cannot open kernel log"); return lines; } diff --git a/src/applets/env.cpp b/src/applets/env.cpp index 49826ef..30f96bd 100644 --- a/src/applets/env.cpp +++ b/src/applets/env.cpp @@ -6,6 +6,7 @@ #include #include +#include extern char** environ; @@ -81,7 +82,6 @@ auto env_main(int argc, char* argv[]) -> int { cmd_args.push_back(nullptr); execvp(cmd.c_str(), cmd_args.data()); - std::fprintf(stderr, "cfbox env: failed to execute '%s': %s\n", - cmd.c_str(), std::strerror(errno)); + CFBOX_ERR("env", "failed to execute '%s': %s", cmd.c_str(), std::strerror(errno)); return 127; } diff --git a/src/applets/expand.cpp b/src/applets/expand.cpp index 1550dad..a53c233 100644 --- a/src/applets/expand.cpp +++ b/src/applets/expand.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -28,7 +29,7 @@ auto expand_main(int argc, char* argv[]) -> int { if (auto t = parsed.get_any('t', "tabs")) { tab_stop = std::stoi(std::string{*t}); if (tab_stop <= 0) { - std::fprintf(stderr, "cfbox expand: invalid tab stop: %d\n", tab_stop); + CFBOX_ERR("expand", "invalid tab stop: %d", tab_stop); return 1; } } @@ -56,7 +57,7 @@ auto expand_main(int argc, char* argv[]) -> int { return true; }); if (!result) { - std::fprintf(stderr, "cfbox expand: %s\n", result.error().msg.c_str()); + CFBOX_ERR("expand", "%s", result.error().msg.c_str()); rc = 1; } } diff --git a/src/applets/expr.cpp b/src/applets/expr.cpp index 431f6d9..3d16e61 100644 --- a/src/applets/expr.cpp +++ b/src/applets/expr.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -166,14 +167,14 @@ static auto eval(std::vector::iterator& it, } else if (op == "/") { ++it; auto right = eval_add(it, end); if (right.to_int() == 0) { - std::fprintf(stderr, "cfbox expr: division by zero\n"); + CFBOX_ERR("expr", "division by zero"); return Value::integer(0); } left = Value::integer(left.to_int() / right.to_int()); } else if (op == "%") { ++it; auto right = eval_add(it, end); if (right.to_int() == 0) { - std::fprintf(stderr, "cfbox expr: division by zero\n"); + CFBOX_ERR("expr", "division by zero"); return Value::integer(0); } left = Value::integer(left.to_int() % right.to_int()); @@ -202,7 +203,7 @@ auto expr_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox expr: missing operand\n"); + CFBOX_ERR("expr", "missing operand"); return 2; } diff --git a/src/applets/factor.cpp b/src/applets/factor.cpp index 85ca7ca..20afaf4 100644 --- a/src/applets/factor.cpp +++ b/src/applets/factor.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -39,7 +40,7 @@ auto factor_main(int argc, char* argv[]) -> int { if (pos.empty()) { auto input = cfbox::io::read_all_stdin(); if (!input) { - std::fprintf(stderr, "cfbox factor: %s\n", input.error().msg.c_str()); + CFBOX_ERR("factor", "%s", input.error().msg.c_str()); return 1; } char* str = input->data(); diff --git a/src/applets/find.cpp b/src/applets/find.cpp index cf6be2c..e8d0bb0 100644 --- a/src/applets/find.cpp +++ b/src/applets/find.cpp @@ -15,6 +15,7 @@ #include #include +#include namespace { @@ -192,8 +193,7 @@ auto do_find(const std::filesystem::path& root, const std::vector& pr auto it = std::filesystem::recursive_directory_iterator(root, std::filesystem::directory_options::follow_directory_symlink, ec); if (ec) { - std::fprintf(stderr, "cfbox find: '%s': %s\n", - root.string().c_str(), ec.message().c_str()); + CFBOX_ERR("find", "'%s': %s", root.string().c_str(), ec.message().c_str()); return 1; } diff --git a/src/applets/fold.cpp b/src/applets/fold.cpp index 3511958..bcb6c5e 100644 --- a/src/applets/fold.cpp +++ b/src/applets/fold.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -30,7 +31,7 @@ auto fold_main(int argc, char* argv[]) -> int { if (auto w = parsed.get_any('w', "width")) { width = std::stoi(std::string{*w}); if (width <= 0) { - std::fprintf(stderr, "cfbox fold: invalid width: %d\n", width); + CFBOX_ERR("fold", "invalid width: %d", width); return 1; } } @@ -76,7 +77,7 @@ auto fold_main(int argc, char* argv[]) -> int { return true; }); if (!result) { - std::fprintf(stderr, "cfbox fold: %s\n", result.error().msg.c_str()); + CFBOX_ERR("fold", "%s", result.error().msg.c_str()); rc = 1; } } diff --git a/src/applets/free.cpp b/src/applets/free.cpp index 429d271..1882ec6 100644 --- a/src/applets/free.cpp +++ b/src/applets/free.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace { @@ -75,7 +76,7 @@ auto free_main(int argc, char* argv[]) -> int { auto result = cfbox::proc::read_meminfo(); if (!result) { - std::fprintf(stderr, "cfbox free: %s\n", result.error().msg.c_str()); + CFBOX_ERR("free", "%s", result.error().msg.c_str()); return 1; } const auto& mi = *result; diff --git a/src/applets/fuser.cpp b/src/applets/fuser.cpp index bbdd7bc..95c5209 100644 --- a/src/applets/fuser.cpp +++ b/src/applets/fuser.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace { @@ -36,7 +37,7 @@ auto fuser_main(int argc, char* argv[]) -> int { bool verbose = parsed.has('v') || parsed.has_long("verbose"); const auto& targets = parsed.positional(); if (targets.empty()) { - std::fprintf(stderr, "cfbox fuser: no file specified\n"); + CFBOX_ERR("fuser", "no file specified"); return 1; } @@ -46,7 +47,7 @@ auto fuser_main(int argc, char* argv[]) -> int { auto target_str = std::string(target); struct stat target_stat {}; if (stat(target_str.c_str(), &target_stat) != 0) { - std::fprintf(stderr, "cfbox fuser: cannot stat %s\n", target_str.c_str()); + CFBOX_ERR("fuser", "cannot stat %s", target_str.c_str()); continue; } @@ -88,7 +89,7 @@ auto fuser_main(int argc, char* argv[]) -> int { if (do_kill) { for (auto p : found_pids) { if (::kill(p, SIGKILL) != 0) { - std::fprintf(stderr, "cfbox fuser: cannot kill %d\n", p); + CFBOX_ERR("fuser", "cannot kill %d", p); } } } else if (!verbose) { diff --git a/src/applets/grep.cpp b/src/applets/grep.cpp index 003c6c0..637c17a 100644 --- a/src/applets/grep.cpp +++ b/src/applets/grep.cpp @@ -13,6 +13,7 @@ #include #include #include +#include namespace { @@ -50,7 +51,7 @@ auto grep_file(const std::string& pattern, const GrepOptions& opts, cfbox::util::scoped_regex re; if (re.compile(pattern.c_str(), cflags) != 0) { - std::fprintf(stderr, "cfbox grep: invalid regex: %s\n", pattern.c_str()); + CFBOX_ERR("grep", "invalid regex: %s", pattern.c_str()); return 2; } @@ -69,12 +70,12 @@ auto grep_file(const std::string& pattern, const GrepOptions& opts, if (opts.quiet) return false; if (opts.files_with_matches) { - std::printf("%s\n", std::string{path}.c_str()); + std::printf("%s\n", path.data()); return false; } if (!opts.count_only) { if (print_filename) { - std::printf("%s:", std::string{path}.c_str()); + std::printf("%s:", path.data()); } if (opts.line_numbers) { std::printf("%zu:", line_num); @@ -87,7 +88,7 @@ auto grep_file(const std::string& pattern, const GrepOptions& opts, auto result = cfbox::io::for_each_line(path, process_line); if (!result) { - std::fprintf(stderr, "cfbox grep: %s\n", result.error().msg.c_str()); + CFBOX_ERR("grep", "%s", result.error().msg.c_str()); return 2; } @@ -144,7 +145,7 @@ auto grep_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox grep: missing pattern\n"); + CFBOX_ERR("grep", "missing pattern"); return 2; } diff --git a/src/applets/gunzip.cpp b/src/applets/gunzip.cpp index f837b78..35c8385 100644 --- a/src/applets/gunzip.cpp +++ b/src/applets/gunzip.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -27,7 +28,7 @@ auto gunzip_main(int argc, char* argv[]) -> int { if (pos.empty()) { auto input = cfbox::io::read_all_stdin(); - if (!input) { std::fprintf(stderr, "cfbox gunzip: read error\n"); return 1; } + if (!input) { CFBOX_ERR("gunzip", "read error"); return 1; }; auto result = cfbox::compress::gzip_decompress(*input); std::fwrite(result.data(), 1, result.size(), stdout); return 0; @@ -38,7 +39,7 @@ auto gunzip_main(int argc, char* argv[]) -> int { std::string path{p}; auto input = cfbox::io::read_all(path); if (!input) { - std::fprintf(stderr, "cfbox gunzip: %s\n", input.error().msg.c_str()); + CFBOX_ERR("gunzip", "%s", input.error().msg.c_str()); rc = 1; continue; } @@ -47,7 +48,7 @@ auto gunzip_main(int argc, char* argv[]) -> int { ? path.substr(0, path.size() - 3) : path + ".out"; auto wresult = cfbox::io::write_all(outpath, result); if (!wresult) { - std::fprintf(stderr, "cfbox gunzip: %s\n", wresult.error().msg.c_str()); + CFBOX_ERR("gunzip", "%s", wresult.error().msg.c_str()); rc = 1; } } diff --git a/src/applets/gzip.cpp b/src/applets/gzip.cpp index 77d9a68..e526cc7 100644 --- a/src/applets/gzip.cpp +++ b/src/applets/gzip.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -30,7 +31,7 @@ auto gzip_main(int argc, char* argv[]) -> int { if (pos.empty()) { auto input = cfbox::io::read_all_stdin(); - if (!input) { std::fprintf(stderr, "cfbox gzip: read error\n"); return 1; } + if (!input) { CFBOX_ERR("gzip", "read error"); return 1; }; auto result = decompress ? cfbox::compress::gzip_decompress(*input) : cfbox::compress::gzip_compress(*input); std::fwrite(result.data(), 1, result.size(), stdout); @@ -42,7 +43,7 @@ auto gzip_main(int argc, char* argv[]) -> int { std::string path{p}; auto input = cfbox::io::read_all(path); if (!input) { - std::fprintf(stderr, "cfbox gzip: %s\n", input.error().msg.c_str()); + CFBOX_ERR("gzip", "%s", input.error().msg.c_str()); rc = 1; continue; } @@ -53,7 +54,7 @@ auto gzip_main(int argc, char* argv[]) -> int { : (path + ".gz"); auto wresult = cfbox::io::write_all(outpath, result); if (!wresult) { - std::fprintf(stderr, "cfbox gzip: %s\n", wresult.error().msg.c_str()); + CFBOX_ERR("gzip", "%s", wresult.error().msg.c_str()); rc = 1; } } diff --git a/src/applets/head.cpp b/src/applets/head.cpp index 2a00c2e..0fc17b5 100644 --- a/src/applets/head.cpp +++ b/src/applets/head.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace { @@ -40,7 +41,7 @@ auto head_file(std::string_view path, long n_lines, long n_bytes, auto result = use_stdin ? cfbox::io::read_all_stdin() : cfbox::io::read_all(path); if (!result) { - std::fprintf(stderr, "cfbox head: %s\n", result.error().msg.c_str()); + CFBOX_ERR("head", "%s", result.error().msg.c_str()); return 1; } diff --git a/src/applets/hexdump.cpp b/src/applets/hexdump.cpp index b3d2946..29f203d 100644 --- a/src/applets/hexdump.cpp +++ b/src/applets/hexdump.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace { @@ -116,7 +117,7 @@ auto hexdump_main(int argc, char* argv[]) -> int { std::FILE* f = std::fopen(filename.c_str(), "rb"); if (!f) { - std::fprintf(stderr, "cfbox hexdump: cannot open %s\n", filename.c_str()); + CFBOX_ERR("hexdump", "cannot open %s", filename.c_str()); return 1; } auto rc = do_dump(f); diff --git a/src/applets/hostname.cpp b/src/applets/hostname.cpp index 2a1ea48..13f49b6 100644 --- a/src/applets/hostname.cpp +++ b/src/applets/hostname.cpp @@ -5,6 +5,7 @@ #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -27,7 +28,7 @@ auto hostname_main(int argc, char* argv[]) -> int { char buf[256]; if (gethostname(buf, sizeof(buf)) != 0) { - std::fprintf(stderr, "cfbox hostname: cannot get hostname\n"); + CFBOX_ERR("hostname", "cannot get hostname"); return 1; } diff --git a/src/applets/init/init_spawn.cpp b/src/applets/init/init_spawn.cpp index 30e9b1a..674b9c5 100644 --- a/src/applets/init/init_spawn.cpp +++ b/src/applets/init/init_spawn.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace cfbox::init { @@ -13,8 +14,7 @@ auto spawn_process(InitState& state, const InittabEntry& entry, bool respawn) -> pid_t pid = fork(); if (pid < 0) { - std::fprintf(stderr, "cfbox init: fork failed for '%s': %s\n", - entry.process.c_str(), std::strerror(errno)); + CFBOX_ERR("init", "fork failed for '%s': %s", entry.process.c_str(), std::strerror(errno)); return -1; } @@ -33,8 +33,7 @@ auto spawn_process(InitState& state, const InittabEntry& entry, bool respawn) -> execv(shell, argv); // If execv fails, try /bin/cfbox sh - std::fprintf(stderr, "cfbox init: exec failed for '%s': %s\n", - entry.process.c_str(), std::strerror(errno)); + CFBOX_ERR("init", "exec failed for '%s': %s", entry.process.c_str(), std::strerror(errno)); _exit(127); } diff --git a/src/applets/install.cpp b/src/applets/install.cpp index a4ab7ca..157b4ab 100644 --- a/src/applets/install.cpp +++ b/src/applets/install.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -45,8 +46,7 @@ auto install_main(int argc, char* argv[]) -> int { errno = 0; unsigned long m = std::strtoul(mode_str->data(), &end, 8); if (errno != 0 || end == mode_str->data() || *end != '\0') { - std::fprintf(stderr, "cfbox install: invalid mode '%.*s'\n", - static_cast(mode_str->size()), mode_str->data()); + CFBOX_ERR("install", "invalid mode '%.*s'", static_cast(mode_str->size()), mode_str->data()); return 1; } mode = static_cast(m); @@ -54,15 +54,14 @@ auto install_main(int argc, char* argv[]) -> int { if (mkdir_mode) { if (pos.empty()) { - std::fprintf(stderr, "cfbox install: missing operand\n"); + CFBOX_ERR("install", "missing operand"); return 1; } for (auto p : pos) { std::error_code ec; std::filesystem::create_directories(std::filesystem::path{p}, ec); if (ec) { - std::fprintf(stderr, "cfbox install: cannot create directory '%.*s': %s\n", - static_cast(p.size()), p.data(), ec.message().c_str()); + CFBOX_ERR("install", "cannot create directory '%.*s': %s", static_cast(p.size()), p.data(), ec.message().c_str()); return 1; } std::filesystem::permissions(std::filesystem::path{p}, mode, ec); @@ -71,7 +70,7 @@ auto install_main(int argc, char* argv[]) -> int { } if (pos.empty()) { - std::fprintf(stderr, "cfbox install: missing operand\n"); + CFBOX_ERR("install", "missing operand"); return 1; } @@ -82,7 +81,7 @@ auto install_main(int argc, char* argv[]) -> int { sources = pos; } else { if (pos.size() < 2) { - std::fprintf(stderr, "cfbox install: missing destination file operand\n"); + CFBOX_ERR("install", "missing destination file operand"); return 1; } dest = std::string{pos.back()}; @@ -106,13 +105,13 @@ auto install_main(int argc, char* argv[]) -> int { auto result = cfbox::io::read_all(src_path); if (!result) { - std::fprintf(stderr, "cfbox install: %s\n", result.error().msg.c_str()); + CFBOX_ERR("install", "%s", result.error().msg.c_str()); rc = 1; continue; } auto wresult = cfbox::io::write_all(dst_path, *result); if (!wresult) { - std::fprintf(stderr, "cfbox install: %s\n", wresult.error().msg.c_str()); + CFBOX_ERR("install", "%s", wresult.error().msg.c_str()); rc = 1; continue; } diff --git a/src/applets/iostat.cpp b/src/applets/iostat.cpp index 70efa93..6853d13 100644 --- a/src/applets/iostat.cpp +++ b/src/applets/iostat.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace { @@ -83,7 +84,7 @@ auto iostat_main(int argc, char* argv[]) -> int { auto first = cfbox::proc::read_diskstats(); if (!first) { - std::fprintf(stderr, "cfbox iostat: %s\n", first.error().msg.c_str()); + CFBOX_ERR("iostat", "%s", first.error().msg.c_str()); return 1; } diff --git a/src/applets/kill.cpp b/src/applets/kill.cpp index 7ac67d7..aaa9aed 100644 --- a/src/applets/kill.cpp +++ b/src/applets/kill.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace { @@ -85,7 +86,7 @@ auto kill_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox kill: no PID specified\n"); + CFBOX_ERR("kill", "no PID specified"); return 1; } @@ -105,7 +106,7 @@ auto kill_main(int argc, char* argv[]) -> int { } if (sig <= 0) { - std::fprintf(stderr, "cfbox kill: invalid signal\n"); + CFBOX_ERR("kill", "invalid signal"); return 1; } @@ -115,7 +116,7 @@ auto kill_main(int argc, char* argv[]) -> int { // Handle special case: 0 means all processes in process group pid_t pid = static_cast(std::strtol(arg.data(), nullptr, 10)); if (kill(pid, sig) != 0) { - std::fprintf(stderr, "cfbox kill: (%d) - %s\n", pid, std::strerror(errno)); + CFBOX_ERR("kill", "(%d) - %s", pid, std::strerror(errno)); ++errors; } } diff --git a/src/applets/link.cpp b/src/applets/link.cpp index 452a557..7716577 100644 --- a/src/applets/link.cpp +++ b/src/applets/link.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -23,13 +24,13 @@ auto link_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.size() < 2) { - std::fprintf(stderr, "cfbox link: missing operand\n"); + CFBOX_ERR("link", "missing operand"); return 1; } auto result = cfbox::fs::create_hard_link(pos[0], pos[1]); if (!result) { - std::fprintf(stderr, "cfbox link: %s\n", result.error().msg.c_str()); + CFBOX_ERR("link", "%s", result.error().msg.c_str()); return 1; } return 0; diff --git a/src/applets/ln.cpp b/src/applets/ln.cpp index e36a108..c42004f 100644 --- a/src/applets/ln.cpp +++ b/src/applets/ln.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -34,7 +35,7 @@ auto ln_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.size() < 2) { - std::fprintf(stderr, "cfbox ln: missing operand\n"); + CFBOX_ERR("ln", "missing operand"); return 1; } @@ -59,7 +60,7 @@ auto ln_main(int argc, char* argv[]) -> int { result = cfbox::fs::create_hard_link(target, link_name); } if (!result) { - std::fprintf(stderr, "cfbox ln: %s\n", result.error().msg.c_str()); + CFBOX_ERR("ln", "%s", result.error().msg.c_str()); return 1; } return 0; diff --git a/src/applets/logname.cpp b/src/applets/logname.cpp index 3fb5397..da840a3 100644 --- a/src/applets/logname.cpp +++ b/src/applets/logname.cpp @@ -3,6 +3,7 @@ #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -26,7 +27,7 @@ auto logname_main(int argc, char* argv[]) -> int { if (!name) name = std::getenv("USER"); if (!name || name[0] == '\0') { - std::fprintf(stderr, "cfbox logname: no login name\n"); + CFBOX_ERR("logname", "no login name"); return 1; } diff --git a/src/applets/ls.cpp b/src/applets/ls.cpp index c6df1b2..da8e0e0 100644 --- a/src/applets/ls.cpp +++ b/src/applets/ls.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace { @@ -76,8 +77,7 @@ struct LsOptions { auto list_directory(const std::string& path, const LsOptions& opts) -> int { auto entries_result = cfbox::fs::directory_entries(path); if (!entries_result) { - std::fprintf(stderr, "cfbox ls: cannot access '%s': %s\n", - path.c_str(), entries_result.error().msg.c_str()); + CFBOX_ERR("ls", "cannot access '%s': %s", path.c_str(), entries_result.error().msg.c_str()); return 1; } @@ -158,8 +158,7 @@ auto list_directory(const std::string& path, const LsOptions& opts) -> int { auto list_path(const std::string& path, const LsOptions& opts, bool show_header) -> int { if (!cfbox::fs::exists(path)) { - std::fprintf(stderr, "cfbox ls: cannot access '%s': No such file or directory\n", - path.c_str()); + CFBOX_ERR("ls", "cannot access '%s': No such file or directory", path.c_str()); return 1; } @@ -168,7 +167,7 @@ auto list_path(const std::string& path, const LsOptions& opts, bool show_header) if (opts.long_format) { auto status_result = cfbox::fs::symlink_status(path); if (!status_result) { - std::fprintf(stderr, "cfbox ls: %s\n", status_result.error().msg.c_str()); + CFBOX_ERR("ls", "%s", status_result.error().msg.c_str()); return 1; } auto& st = status_result.value(); @@ -200,7 +199,8 @@ auto list_path(const std::string& path, const LsOptions& opts, bool show_header) time_str.c_str(), name.c_str()); } else { - std::printf("%s\n", std::filesystem::path{path}.filename().string().c_str()); + auto fname = std::filesystem::path{path}.filename().string(); + std::printf("%s\n", fname.c_str()); } return 0; } diff --git a/src/applets/md5sum.cpp b/src/applets/md5sum.cpp index 8101fb8..f53bb6f 100644 --- a/src/applets/md5sum.cpp +++ b/src/applets/md5sum.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -30,7 +31,7 @@ auto md5sum_main(int argc, char* argv[]) -> int { for (auto p : paths) { auto data_result = (p == "-") ? cfbox::io::read_all_stdin() : cfbox::io::read_all(p); if (!data_result) { - std::fprintf(stderr, "cfbox md5sum: %s\n", data_result.error().msg.c_str()); + CFBOX_ERR("md5sum", "%s", data_result.error().msg.c_str()); rc = 1; continue; } diff --git a/src/applets/mkdir.cpp b/src/applets/mkdir.cpp index 21438c1..01dc1e8 100644 --- a/src/applets/mkdir.cpp +++ b/src/applets/mkdir.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace { @@ -44,7 +45,7 @@ auto mkdir_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox mkdir: missing operand\n"); + CFBOX_ERR("mkdir", "missing operand"); return 1; } @@ -53,21 +54,18 @@ auto mkdir_main(int argc, char* argv[]) -> int { if (recursive) { auto result = cfbox::fs::mkdir_recursive(dir, mode_val); if (!result) { - std::fprintf(stderr, "cfbox mkdir: cannot create directory '%s': %s\n", - std::string{dir}.c_str(), result.error().msg.c_str()); + CFBOX_ERR("mkdir", "cannot create directory '%s': %s", dir.data(), result.error().msg.c_str()); rc = 1; } } else { if (cfbox::fs::exists(dir)) { - std::fprintf(stderr, "cfbox mkdir: cannot create directory '%s': File exists\n", - std::string{dir}.c_str()); + CFBOX_ERR("mkdir", "cannot create directory '%s': File exists", dir.data()); rc = 1; continue; } auto result = cfbox::fs::mkdir_single(dir, mode_val); if (!result) { - std::fprintf(stderr, "cfbox mkdir: cannot create directory '%s': %s\n", - std::string{dir}.c_str(), result.error().msg.c_str()); + CFBOX_ERR("mkdir", "cannot create directory '%s': %s", dir.data(), result.error().msg.c_str()); rc = 1; } } diff --git a/src/applets/mkfifo.cpp b/src/applets/mkfifo.cpp index 119e0c5..ec72f50 100644 --- a/src/applets/mkfifo.cpp +++ b/src/applets/mkfifo.cpp @@ -5,6 +5,7 @@ #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -33,7 +34,7 @@ auto mkfifo_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox mkfifo: missing operand\n"); + CFBOX_ERR("mkfifo", "missing operand"); return 1; } @@ -41,8 +42,7 @@ auto mkfifo_main(int argc, char* argv[]) -> int { for (auto p : pos) { std::string path{p}; if (mkfifo(path.c_str(), mode) != 0) { - std::fprintf(stderr, "cfbox mkfifo: cannot create fifo '%s': %s\n", - path.c_str(), std::strerror(errno)); + CFBOX_ERR("mkfifo", "cannot create fifo '%s': %s", path.c_str(), std::strerror(errno)); rc = 1; } } diff --git a/src/applets/mknod.cpp b/src/applets/mknod.cpp index 9479d8c..3c6bfd4 100644 --- a/src/applets/mknod.cpp +++ b/src/applets/mknod.cpp @@ -7,6 +7,7 @@ #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -27,7 +28,7 @@ auto mknod_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.size() < 2) { - std::fprintf(stderr, "cfbox mknod: missing operand\n"); + CFBOX_ERR("mknod", "missing operand"); return 1; } @@ -40,7 +41,7 @@ auto mknod_main(int argc, char* argv[]) -> int { switch (type) { case 'b': if (pos.size() < 4) { - std::fprintf(stderr, "cfbox mknod: missing major/minor for block device\n"); + CFBOX_ERR("mknod", "missing major/minor for block device"); return 1; } mode = S_IFBLK | 0660; @@ -49,7 +50,7 @@ auto mknod_main(int argc, char* argv[]) -> int { break; case 'c': case 'u': if (pos.size() < 4) { - std::fprintf(stderr, "cfbox mknod: missing major/minor for char device\n"); + CFBOX_ERR("mknod", "missing major/minor for char device"); return 1; } mode = S_IFCHR | 0660; @@ -60,13 +61,12 @@ auto mknod_main(int argc, char* argv[]) -> int { mode = S_IFIFO | 0666; break; default: - std::fprintf(stderr, "cfbox mknod: invalid type '%c'\n", type); + CFBOX_ERR("mknod", "invalid type '%c'", type); return 1; } if (mknod(name.c_str(), mode, dev) != 0) { - std::fprintf(stderr, "cfbox mknod: cannot create node '%s': %s\n", - name.c_str(), std::strerror(errno)); + CFBOX_ERR("mknod", "cannot create node '%s': %s", name.c_str(), std::strerror(errno)); return 1; } return 0; diff --git a/src/applets/mktemp.cpp b/src/applets/mktemp.cpp index 90a8d6a..92cc49d 100644 --- a/src/applets/mktemp.cpp +++ b/src/applets/mktemp.cpp @@ -6,6 +6,7 @@ #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -54,7 +55,7 @@ auto mktemp_main(int argc, char* argv[]) -> int { // Replace trailing X's with random chars auto xpos = tmpl.rfind('X'); if (xpos == std::string::npos || xpos < tmpl.size() - 6) { - std::fprintf(stderr, "cfbox mktemp: too few X's in template '%s'\n", tmpl.c_str()); + CFBOX_ERR("mktemp", "too few X's in template '%s'", tmpl.c_str()); return 1; } std::puts(tmpl.c_str()); @@ -64,16 +65,14 @@ auto mktemp_main(int argc, char* argv[]) -> int { if (make_dir) { char* result = mkdtemp(tmpl.data()); if (!result) { - std::fprintf(stderr, "cfbox mktemp: failed to create directory: %s\n", - std::strerror(errno)); + CFBOX_ERR("mktemp", "failed to create directory: %s", std::strerror(errno)); return 1; } std::puts(result); } else { int fd = mkstemp(tmpl.data()); if (fd < 0) { - std::fprintf(stderr, "cfbox mktemp: failed to create file: %s\n", - std::strerror(errno)); + CFBOX_ERR("mktemp", "failed to create file: %s", std::strerror(errno)); return 1; } ::close(fd); diff --git a/src/applets/more.cpp b/src/applets/more.cpp index 8e56fec..45e2123 100644 --- a/src/applets/more.cpp +++ b/src/applets/more.cpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace { @@ -47,7 +48,7 @@ auto more_main(int argc, char* argv[]) -> int { if (!filename.empty() && filename != "-") { f = std::fopen(filename.c_str(), "r"); if (!f) { - std::fprintf(stderr, "cfbox more: cannot open %s\n", filename.c_str()); + CFBOX_ERR("more", "cannot open %s", filename.c_str()); return 1; } } diff --git a/src/applets/mountpoint.cpp b/src/applets/mountpoint.cpp index 61ddbd9..ac98cce 100644 --- a/src/applets/mountpoint.cpp +++ b/src/applets/mountpoint.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace { @@ -36,14 +37,14 @@ auto mountpoint_main(int argc, char* argv[]) -> int { bool dev = parsed.has('d'); const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox mountpoint: missing argument\n"); + CFBOX_ERR("mountpoint", "missing argument"); return 2; } std::string target(pos[0]); struct stat st; if (stat(target.c_str(), &st) != 0) { - if (!quiet) std::fprintf(stderr, "cfbox mountpoint: %s: %s\n", target.c_str(), std::strerror(errno)); + if (!quiet) CFBOX_ERR("mountpoint", "%s: %s", target.c_str(), std::strerror(errno)); return 1; } diff --git a/src/applets/mv.cpp b/src/applets/mv.cpp index 606a11d..28ea4ed 100644 --- a/src/applets/mv.cpp +++ b/src/applets/mv.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace { @@ -19,8 +20,7 @@ constexpr cfbox::help::HelpEntry HELP = { auto do_move(const std::string& src, const std::string& dst, bool force) -> int { if (!cfbox::fs::exists(src)) { - std::fprintf(stderr, "cfbox mv: cannot stat '%s': No such file or directory\n", - src.c_str()); + CFBOX_ERR("mv", "cannot stat '%s': No such file or directory", src.c_str()); return 1; } @@ -31,8 +31,7 @@ auto do_move(const std::string& src, const std::string& dst, bool force) -> int } if (cfbox::fs::exists(dest) && !force) { - std::fprintf(stderr, "cfbox mv: overwrite '%s'? (skipped, use -f to force)\n", - dest.c_str()); + CFBOX_ERR("mv", "overwrite '%s'? (skipped, use -f to force)", dest.c_str()); return 1; } @@ -46,27 +45,23 @@ auto do_move(const std::string& src, const std::string& dst, bool force) -> int if (cfbox::fs::is_directory(src)) { auto copy_result = cfbox::fs::copy_recursive(src, dest); if (!copy_result) { - std::fprintf(stderr, "cfbox mv: cannot move '%s' to '%s': %s\n", - src.c_str(), dest.c_str(), copy_result.error().msg.c_str()); + CFBOX_ERR("mv", "cannot move '%s' to '%s': %s", src.c_str(), dest.c_str(), copy_result.error().msg.c_str()); return 1; } auto rm_result = cfbox::fs::remove_all(src); if (!rm_result) { - std::fprintf(stderr, "cfbox mv: cannot remove '%s': %s\n", - src.c_str(), rm_result.error().msg.c_str()); + CFBOX_ERR("mv", "cannot remove '%s': %s", src.c_str(), rm_result.error().msg.c_str()); return 1; } } else { auto copy_result = cfbox::fs::copy_file(src, dest); if (!copy_result) { - std::fprintf(stderr, "cfbox mv: cannot move '%s' to '%s': %s\n", - src.c_str(), dest.c_str(), copy_result.error().msg.c_str()); + CFBOX_ERR("mv", "cannot move '%s' to '%s': %s", src.c_str(), dest.c_str(), copy_result.error().msg.c_str()); return 1; } auto rm_result = cfbox::fs::remove_single(src); if (!rm_result) { - std::fprintf(stderr, "cfbox mv: cannot remove '%s': %s\n", - src.c_str(), rm_result.error().msg.c_str()); + CFBOX_ERR("mv", "cannot remove '%s': %s", src.c_str(), rm_result.error().msg.c_str()); return 1; } } @@ -88,7 +83,7 @@ auto mv_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.size() < 2) { - std::fprintf(stderr, "cfbox mv: missing file operand\n"); + CFBOX_ERR("mv", "missing file operand"); return 1; } @@ -101,7 +96,7 @@ auto mv_main(int argc, char* argv[]) -> int { // Multiple sources — destination must be a directory if (!cfbox::fs::is_directory(dst)) { - std::fprintf(stderr, "cfbox mv: target '%s' is not a directory\n", dst.c_str()); + CFBOX_ERR("mv", "target '%s' is not a directory", dst.c_str()); return 1; } diff --git a/src/applets/nice.cpp b/src/applets/nice.cpp index 5660014..bbd4c52 100644 --- a/src/applets/nice.cpp +++ b/src/applets/nice.cpp @@ -7,6 +7,7 @@ #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -34,7 +35,7 @@ auto nice_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox nice: missing command\n"); + CFBOX_ERR("nice", "missing command"); return 1; } @@ -49,6 +50,6 @@ auto nice_main(int argc, char* argv[]) -> int { cmd_args.push_back(nullptr); execvp(cmd_args[0], cmd_args.data()); - std::fprintf(stderr, "cfbox nice: %s: %s\n", cmd_args[0], std::strerror(errno)); + CFBOX_ERR("nice", "%s: %s", cmd_args[0], std::strerror(errno)); return 127; } diff --git a/src/applets/nl.cpp b/src/applets/nl.cpp index 9d80bc4..7cf6010 100644 --- a/src/applets/nl.cpp +++ b/src/applets/nl.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -75,7 +76,7 @@ auto nl_main(int argc, char* argv[]) -> int { return true; }); if (!result) { - std::fprintf(stderr, "cfbox nl: %s\n", result.error().msg.c_str()); + CFBOX_ERR("nl", "%s", result.error().msg.c_str()); rc = 1; } } diff --git a/src/applets/nohup.cpp b/src/applets/nohup.cpp index 164a83d..41c12eb 100644 --- a/src/applets/nohup.cpp +++ b/src/applets/nohup.cpp @@ -7,6 +7,7 @@ #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -27,7 +28,7 @@ auto nohup_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox nohup: missing command\n"); + CFBOX_ERR("nohup", "missing command"); return 1; } @@ -44,8 +45,7 @@ auto nohup_main(int argc, char* argv[]) -> int { auto* f = freopen(outfile.c_str(), "a", stdout); if (!f) { - std::fprintf(stderr, "cfbox nohup: cannot open %s: %s\n", - outfile.c_str(), std::strerror(errno)); + CFBOX_ERR("nohup", "cannot open %s: %s", outfile.c_str(), std::strerror(errno)); return 1; } dup2(fileno(stdout), STDERR_FILENO); @@ -57,6 +57,6 @@ auto nohup_main(int argc, char* argv[]) -> int { cmd_args.push_back(nullptr); execvp(cmd_args[0], cmd_args.data()); - std::fprintf(stderr, "cfbox nohup: %s: %s\n", cmd_args[0], std::strerror(errno)); + CFBOX_ERR("nohup", "%s: %s", cmd_args[0], std::strerror(errno)); return 127; } diff --git a/src/applets/od.cpp b/src/applets/od.cpp index 66bbde2..875d499 100644 --- a/src/applets/od.cpp +++ b/src/applets/od.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -58,7 +59,7 @@ auto od_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); auto data_result = pos.empty() ? cfbox::io::read_all_stdin() : cfbox::io::read_all(pos[0]); if (!data_result) { - std::fprintf(stderr, "cfbox od: %s\n", data_result.error().msg.c_str()); + CFBOX_ERR("od", "%s", data_result.error().msg.c_str()); return 1; } diff --git a/src/applets/paste.cpp b/src/applets/paste.cpp index 95fe609..4a74207 100644 --- a/src/applets/paste.cpp +++ b/src/applets/paste.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -53,7 +54,7 @@ auto paste_main(int argc, char* argv[]) -> int { return true; }); if (!result) { - std::fprintf(stderr, "cfbox paste: %s\n", result.error().msg.c_str()); + CFBOX_ERR("paste", "%s", result.error().msg.c_str()); return 1; } std::putchar('\n'); @@ -71,7 +72,7 @@ auto paste_main(int argc, char* argv[]) -> int { return true; }); if (!result) { - std::fprintf(stderr, "cfbox paste: %s\n", result.error().msg.c_str()); + CFBOX_ERR("paste", "%s", result.error().msg.c_str()); return 1; } if (lines.size() > max_lines) max_lines = lines.size(); diff --git a/src/applets/patch.cpp b/src/applets/patch.cpp index 5092d21..7c32839 100644 --- a/src/applets/patch.cpp +++ b/src/applets/patch.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -51,20 +52,20 @@ auto patch_main(int argc, char* argv[]) -> int { auto patch_result = cfbox::io::read_all_stdin(); if (!patch_result) { - std::fprintf(stderr, "cfbox patch: %s\n", patch_result.error().msg.c_str()); + CFBOX_ERR("patch", "%s", patch_result.error().msg.c_str()); return 1; } const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox patch: missing file operand\n"); + CFBOX_ERR("patch", "missing file operand"); return 1; } std::string filepath{pos[0]}; auto file_result = cfbox::io::read_lines(filepath); if (!file_result) { - std::fprintf(stderr, "cfbox patch: %s\n", file_result.error().msg.c_str()); + CFBOX_ERR("patch", "%s", file_result.error().msg.c_str()); return 1; } @@ -132,7 +133,7 @@ auto patch_main(int argc, char* argv[]) -> int { int remove_count = 0; for (auto& l : h.lines) if (!l.empty() && l[0] != '+') ++remove_count; if (start + remove_count > static_cast(lines.size())) { - std::fprintf(stderr, "cfbox patch: hunk at line %d doesn't match\n", h.old_start); + CFBOX_ERR("patch", "hunk at line %d doesn't match", h.old_start); return 1; } @@ -152,7 +153,7 @@ auto patch_main(int argc, char* argv[]) -> int { for (const auto& l : lines) output += l + "\n"; auto wresult = cfbox::io::write_all(filepath, output); if (!wresult) { - std::fprintf(stderr, "cfbox patch: %s\n", wresult.error().msg.c_str()); + CFBOX_ERR("patch", "%s", wresult.error().msg.c_str()); return 1; } return 0; diff --git a/src/applets/pgrep.cpp b/src/applets/pgrep.cpp index 76d6cf0..31eef06 100644 --- a/src/applets/pgrep.cpp +++ b/src/applets/pgrep.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -78,14 +79,14 @@ auto pgrep_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox %s: no pattern specified\n", is_pkill ? "pkill" : "pgrep"); + CFBOX_ERR_V(is_pkill ? "pkill" : "pgrep", "no pattern specified"); return 1; } const auto& pattern = pos[0]; auto result = cfbox::proc::read_all_processes(); if (!result) { - std::fprintf(stderr, "cfbox %s: %s\n", is_pkill ? "pkill" : "pgrep", result.error().msg.c_str()); + CFBOX_ERR_V(is_pkill ? "pkill" : "pgrep", "%s", result.error().msg.c_str()); return 1; } diff --git a/src/applets/pidof.cpp b/src/applets/pidof.cpp index feb47b2..d33e97d 100644 --- a/src/applets/pidof.cpp +++ b/src/applets/pidof.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace { @@ -30,13 +31,13 @@ auto pidof_main(int argc, char* argv[]) -> int { bool single = parsed.has('s') || parsed.has_long("single"); const auto& names = parsed.positional(); if (names.empty()) { - std::fprintf(stderr, "cfbox pidof: no program name specified\n"); + CFBOX_ERR("pidof", "no program name specified"); return 1; } auto result = cfbox::proc::read_all_processes(); if (!result) { - std::fprintf(stderr, "cfbox pidof: %s\n", result.error().msg.c_str()); + CFBOX_ERR("pidof", "%s", result.error().msg.c_str()); return 1; } diff --git a/src/applets/pmap.cpp b/src/applets/pmap.cpp index 43b0bd0..44d19d6 100644 --- a/src/applets/pmap.cpp +++ b/src/applets/pmap.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace { @@ -94,14 +95,14 @@ auto pmap_main(int argc, char* argv[]) -> int { const auto& args = parsed.positional(); if (args.empty()) { - std::fprintf(stderr, "cfbox pmap: no PID specified\n"); + CFBOX_ERR("pmap", "no PID specified"); return 1; } pid_t pid = static_cast(std::stoi(std::string(args[0]))); auto entries = parse_maps(pid); if (entries.empty()) { - std::fprintf(stderr, "cfbox pmap: cannot read maps for PID %d\n", pid); + CFBOX_ERR("pmap", "cannot read maps for PID %d", pid); return 1; } diff --git a/src/applets/ps.cpp b/src/applets/ps.cpp index e52b6d2..15c79d2 100644 --- a/src/applets/ps.cpp +++ b/src/applets/ps.cpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace { @@ -96,7 +97,7 @@ auto ps_main(int argc, char* argv[]) -> int { auto result = cfbox::proc::read_all_processes(); if (!result) { - std::fprintf(stderr, "cfbox ps: %s\n", result.error().msg.c_str()); + CFBOX_ERR("ps", "%s", result.error().msg.c_str()); return 1; } diff --git a/src/applets/pstree.cpp b/src/applets/pstree.cpp index 86ac031..5dc36fe 100644 --- a/src/applets/pstree.cpp +++ b/src/applets/pstree.cpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace { @@ -70,7 +71,7 @@ auto pstree_main(int argc, char* argv[]) -> int { auto result = cfbox::proc::read_all_processes(); if (!result) { - std::fprintf(stderr, "cfbox pstree: %s\n", result.error().msg.c_str()); + CFBOX_ERR("pstree", "%s", result.error().msg.c_str()); return 1; } diff --git a/src/applets/pwd.cpp b/src/applets/pwd.cpp index d1be9c0..ac6c030 100644 --- a/src/applets/pwd.cpp +++ b/src/applets/pwd.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -27,7 +28,7 @@ auto pwd_main(int argc, char* argv[]) -> int { auto result = cfbox::fs::current_path(); if (!result) { - std::fprintf(stderr, "cfbox pwd: %s\n", result.error().msg.c_str()); + CFBOX_ERR("pwd", "%s", result.error().msg.c_str()); return 1; } std::puts(result->c_str()); diff --git a/src/applets/pwdx.cpp b/src/applets/pwdx.cpp index 1bdca0a..ef50110 100644 --- a/src/applets/pwdx.cpp +++ b/src/applets/pwdx.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace { @@ -26,7 +27,7 @@ auto pwdx_main(int argc, char* argv[]) -> int { const auto& args = parsed.positional(); if (args.empty()) { - std::fprintf(stderr, "cfbox pwdx: no PID specified\n"); + CFBOX_ERR("pwdx", "no PID specified"); return 1; } @@ -36,8 +37,7 @@ auto pwdx_main(int argc, char* argv[]) -> int { std::error_code ec; auto target = std::filesystem::read_symlink(link, ec); if (ec) { - std::fprintf(stderr, "cfbox pwdx: %.*s: %s\n", - static_cast(arg.size()), arg.data(), ec.message().c_str()); + CFBOX_ERR("pwdx", "%.*s: %s", static_cast(arg.size()), arg.data(), ec.message().c_str()); rc = 1; } else { std::printf("%.*s: %s\n", diff --git a/src/applets/readlink.cpp b/src/applets/readlink.cpp index 6014182..cb0ffad 100644 --- a/src/applets/readlink.cpp +++ b/src/applets/readlink.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -31,7 +32,7 @@ auto readlink_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox readlink: missing operand\n"); + CFBOX_ERR("readlink", "missing operand"); return 1; } @@ -45,7 +46,7 @@ auto readlink_main(int argc, char* argv[]) -> int { result = cfbox::fs::read_symlink(path); } if (!result) { - std::fprintf(stderr, "cfbox readlink: %s\n", result.error().msg.c_str()); + CFBOX_ERR("readlink", "%s", result.error().msg.c_str()); rc = 1; continue; } diff --git a/src/applets/realpath.cpp b/src/applets/realpath.cpp index 4f763f5..be3d707 100644 --- a/src/applets/realpath.cpp +++ b/src/applets/realpath.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -24,7 +25,7 @@ auto realpath_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox realpath: missing operand\n"); + CFBOX_ERR("realpath", "missing operand"); return 1; } @@ -32,7 +33,7 @@ auto realpath_main(int argc, char* argv[]) -> int { for (auto p : pos) { auto result = cfbox::fs::canonical(std::string{p}); if (!result) { - std::fprintf(stderr, "cfbox realpath: %s\n", result.error().msg.c_str()); + CFBOX_ERR("realpath", "%s", result.error().msg.c_str()); rc = 1; continue; } diff --git a/src/applets/renice.cpp b/src/applets/renice.cpp index d9005a4..6757a5f 100644 --- a/src/applets/renice.cpp +++ b/src/applets/renice.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace { @@ -39,7 +40,7 @@ auto renice_main(int argc, char* argv[]) -> int { const auto& args = parsed.positional(); if (args.empty()) { - std::fprintf(stderr, "cfbox renice: no ID specified\n"); + CFBOX_ERR("renice", "no ID specified"); return 1; } @@ -54,14 +55,14 @@ auto renice_main(int argc, char* argv[]) -> int { errno = 0; auto current = getpriority(which, id); if (errno != 0) { - std::fprintf(stderr, "cfbox renice: %d: %s\n", static_cast(id), std::strerror(errno)); + CFBOX_ERR("renice", "%d: %s", static_cast(id), std::strerror(errno)); rc = 1; continue; } auto new_pri = current + increment; if (setpriority(which, id, new_pri) != 0) { - std::fprintf(stderr, "cfbox renice: %d: %s\n", static_cast(id), std::strerror(errno)); + CFBOX_ERR("renice", "%d: %s", static_cast(id), std::strerror(errno)); rc = 1; } else { std::printf("%d: old priority %d, new priority %d\n", static_cast(id), current, new_pri); diff --git a/src/applets/rev.cpp b/src/applets/rev.cpp index bd277e3..77d021e 100644 --- a/src/applets/rev.cpp +++ b/src/applets/rev.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace { @@ -48,7 +49,7 @@ auto rev_main(int argc, char* argv[]) -> int { } else { std::ifstream f(fn); if (!f) { - std::fprintf(stderr, "cfbox rev: cannot open %s\n", fn.c_str()); + CFBOX_ERR("rev", "cannot open %s", fn.c_str()); rc = 1; continue; } diff --git a/src/applets/rm.cpp b/src/applets/rm.cpp index 3978af4..2b50abd 100644 --- a/src/applets/rm.cpp +++ b/src/applets/rm.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace { @@ -47,7 +48,7 @@ auto rm_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { if (!force) { - std::fprintf(stderr, "cfbox rm: missing operand\n"); + CFBOX_ERR("rm", "missing operand"); return 1; } return 0; @@ -57,16 +58,14 @@ auto rm_main(int argc, char* argv[]) -> int { for (const auto& target : pos) { // Safety check: prevent removing / if (is_root_path(target)) { - std::fprintf(stderr, "cfbox rm: refusing to remove '%s'\n", - std::string{target}.c_str()); + CFBOX_ERR("rm", "refusing to remove '%s'", std::string{target}.c_str()); rc = 1; continue; } if (!cfbox::fs::exists(target)) { if (!force) { - std::fprintf(stderr, "cfbox rm: cannot remove '%s': No such file or directory\n", - std::string{target}.c_str()); + CFBOX_ERR("rm", "cannot remove '%s': No such file or directory", std::string{target}.c_str()); rc = 1; } continue; @@ -74,22 +73,19 @@ auto rm_main(int argc, char* argv[]) -> int { if (cfbox::fs::is_directory(target)) { if (!recursive) { - std::fprintf(stderr, "cfbox rm: cannot remove '%s': Is a directory\n", - std::string{target}.c_str()); + CFBOX_ERR("rm", "cannot remove '%s': Is a directory", std::string{target}.c_str()); rc = 1; continue; } auto result = cfbox::fs::remove_all(target); if (!result) { - std::fprintf(stderr, "cfbox rm: cannot remove '%s': %s\n", - std::string{target}.c_str(), result.error().msg.c_str()); + CFBOX_ERR("rm", "cannot remove '%s': %s", std::string{target}.c_str(), result.error().msg.c_str()); rc = 1; } } else { auto result = cfbox::fs::remove_single(target); if (!result) { - std::fprintf(stderr, "cfbox rm: cannot remove '%s': %s\n", - std::string{target}.c_str(), result.error().msg.c_str()); + CFBOX_ERR("rm", "cannot remove '%s': %s", std::string{target}.c_str(), result.error().msg.c_str()); rc = 1; } } diff --git a/src/applets/rmdir.cpp b/src/applets/rmdir.cpp index f8e4717..8bb61c3 100644 --- a/src/applets/rmdir.cpp +++ b/src/applets/rmdir.cpp @@ -4,6 +4,7 @@ #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -24,7 +25,7 @@ auto rmdir_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox rmdir: missing operand\n"); + CFBOX_ERR("rmdir", "missing operand"); return 1; } @@ -33,8 +34,7 @@ auto rmdir_main(int argc, char* argv[]) -> int { std::error_code ec; std::filesystem::remove(std::filesystem::path{p}, ec); if (ec) { - std::fprintf(stderr, "cfbox rmdir: failed to remove '%.*s': %s\n", - static_cast(p.size()), p.data(), ec.message().c_str()); + CFBOX_ERR("rmdir", "failed to remove '%.*s': %s", static_cast(p.size()), p.data(), ec.message().c_str()); rc = 1; } } diff --git a/src/applets/sed.cpp b/src/applets/sed.cpp index 4010209..ad65e21 100644 --- a/src/applets/sed.cpp +++ b/src/applets/sed.cpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace { @@ -327,7 +328,7 @@ auto sed_main(int argc, char* argv[]) -> int { // First positional arg is the script const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox sed: missing script\n"); + CFBOX_ERR("sed", "missing script"); return 1; } script = std::string{pos[0]}; @@ -338,7 +339,7 @@ auto sed_main(int argc, char* argv[]) -> int { auto commands = parse_script(script); if (commands.empty()) { - std::fprintf(stderr, "cfbox sed: empty command\n"); + CFBOX_ERR("sed", "empty command"); return 1; } @@ -346,7 +347,7 @@ auto sed_main(int argc, char* argv[]) -> int { // Read from stdin auto result = cfbox::io::read_all_stdin(); if (!result) { - std::fprintf(stderr, "cfbox sed: %s\n", result.error().msg.c_str()); + CFBOX_ERR("sed", "%s", result.error().msg.c_str()); return 1; } auto lines = cfbox::io::split_lines(result.value()); @@ -355,7 +356,7 @@ auto sed_main(int argc, char* argv[]) -> int { for (const auto& f : files) { auto result = (f == "-") ? cfbox::io::read_all_stdin() : cfbox::io::read_all(f); if (!result) { - std::fprintf(stderr, "cfbox sed: %s\n", result.error().msg.c_str()); + CFBOX_ERR("sed", "%s", result.error().msg.c_str()); return 1; } auto lines = cfbox::io::split_lines(result.value()); diff --git a/src/applets/seq.cpp b/src/applets/seq.cpp index 36d475f..5ed803c 100644 --- a/src/applets/seq.cpp +++ b/src/applets/seq.cpp @@ -5,6 +5,7 @@ #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -35,7 +36,7 @@ auto seq_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox seq: missing operand\n"); + CFBOX_ERR("seq", "missing operand"); return 1; } @@ -52,7 +53,7 @@ auto seq_main(int argc, char* argv[]) -> int { } if (incr == 0) { - std::fprintf(stderr, "cfbox seq: zero increment\n"); + CFBOX_ERR("seq", "zero increment"); return 1; } diff --git a/src/applets/sh/sh_builtins.cpp b/src/applets/sh/sh_builtins.cpp index 28f20c2..c18b1da 100644 --- a/src/applets/sh/sh_builtins.cpp +++ b/src/applets/sh/sh_builtins.cpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace cfbox::sh { @@ -43,7 +44,7 @@ static int builtin_cd(std::vector& args, ShellState& state) { std::string old = std::filesystem::current_path().string(); if (::chdir(dir.c_str()) != 0) { - std::fprintf(stderr, "cfbox sh: cd: %s: %s\n", dir.c_str(), std::strerror(errno)); + CFBOX_ERR("sh", "cd: %s: %s", dir.c_str(), std::strerror(errno)); return 1; } state.set_var("OLDPWD", old); @@ -189,13 +190,13 @@ static int builtin_eval(std::vector& args, ShellState& state) { static int builtin_source(std::vector& args, ShellState& state) { if (args.size() <= 1) { - std::fprintf(stderr, "cfbox sh: source: missing argument\n"); + CFBOX_ERR("sh", "source: missing argument"); return 2; } auto* fp = std::fopen(args[1].c_str(), "r"); if (!fp) { - std::fprintf(stderr, "cfbox sh: %s: %s\n", args[1].c_str(), std::strerror(errno)); + CFBOX_ERR("sh", "%s: %s", args[1].c_str(), std::strerror(errno)); return 1; } diff --git a/src/applets/sh/sh_executor.cpp b/src/applets/sh/sh_executor.cpp index 74d614d..4a4da38 100644 --- a/src/applets/sh/sh_executor.cpp +++ b/src/applets/sh/sh_executor.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace cfbox::sh { @@ -27,7 +28,7 @@ static auto apply_redirections(const std::vector& redirs) -> std::vector< case Redir::Read: { int fd = ::open(r.target.c_str(), O_RDONLY); if (fd < 0) { - std::fprintf(stderr, "cfbox sh: %s: %s\n", r.target.c_str(), std::strerror(errno)); + CFBOX_ERR("sh", "%s: %s", r.target.c_str(), std::strerror(errno)); } else { ::dup2(fd, target_fd); ::close(fd); @@ -37,7 +38,7 @@ static auto apply_redirections(const std::vector& redirs) -> std::vector< case Redir::Write: { int fd = ::open(r.target.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) { - std::fprintf(stderr, "cfbox sh: %s: %s\n", r.target.c_str(), std::strerror(errno)); + CFBOX_ERR("sh", "%s: %s", r.target.c_str(), std::strerror(errno)); } else { ::dup2(fd, target_fd); ::close(fd); @@ -47,7 +48,7 @@ static auto apply_redirections(const std::vector& redirs) -> std::vector< case Redir::Append: { int fd = ::open(r.target.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0644); if (fd < 0) { - std::fprintf(stderr, "cfbox sh: %s: %s\n", r.target.c_str(), std::strerror(errno)); + CFBOX_ERR("sh", "%s: %s", r.target.c_str(), std::strerror(errno)); } else { ::dup2(fd, target_fd); ::close(fd); @@ -103,7 +104,7 @@ static auto execute_simple(SimpleCommand& cmd, ShellState& state) -> int { // External command: fork and exec pid_t pid = ::fork(); if (pid < 0) { - std::fprintf(stderr, "cfbox sh: fork failed: %s\n", std::strerror(errno)); + CFBOX_ERR("sh", "fork failed: %s", std::strerror(errno)); return 126; } @@ -119,7 +120,7 @@ static auto execute_simple(SimpleCommand& cmd, ShellState& state) -> int { argv.push_back(nullptr); ::execvp(argv[0], argv.data()); - std::fprintf(stderr, "cfbox sh: %s: command not found\n", argv[0]); + CFBOX_ERR("sh", "%s: command not found", argv[0]); ::_Exit(127); } @@ -143,7 +144,7 @@ static auto execute_pipeline(Pipeline& node, ShellState& state) -> int { std::vector pipes(static_cast(n - 1)); for (int i = 0; i < n - 1; ++i) { if (::pipe(pipes[static_cast(i)]) < 0) { - std::fprintf(stderr, "cfbox sh: pipe failed\n"); + CFBOX_ERR("sh", "pipe failed"); return 1; } } @@ -153,7 +154,7 @@ static auto execute_pipeline(Pipeline& node, ShellState& state) -> int { for (int i = 0; i < n; ++i) { pids[static_cast(i)] = ::fork(); if (pids[static_cast(i)] < 0) { - std::fprintf(stderr, "cfbox sh: fork failed\n"); + CFBOX_ERR("sh", "fork failed"); return 1; } @@ -192,7 +193,7 @@ static auto execute_pipeline(Pipeline& node, ShellState& state) -> int { for (auto& arg : expanded) argv.push_back(arg.data()); argv.push_back(nullptr); ::execvp(argv[0], argv.data()); - std::fprintf(stderr, "cfbox sh: %s: command not found\n", argv[0]); + CFBOX_ERR("sh", "%s: command not found", argv[0]); ::_Exit(127); } else { int rc = execute_command(cmd, state); diff --git a/src/applets/sh/sh_main.cpp b/src/applets/sh/sh_main.cpp index 1cd7491..b0e99b6 100644 --- a/src/applets/sh/sh_main.cpp +++ b/src/applets/sh/sh_main.cpp @@ -6,6 +6,7 @@ #include #include +#include namespace { @@ -30,7 +31,7 @@ auto run_string(const std::string& script, cfbox::sh::ShellState& state) -> int auto run_file(const char* path, cfbox::sh::ShellState& state) -> int { auto* fp = std::fopen(path, "r"); if (!fp) { - std::fprintf(stderr, "cfbox sh: %s: %s\n", path, std::strerror(errno)); + CFBOX_ERR("sh", "%s: %s", path, std::strerror(errno)); return 127; } std::string script; diff --git a/src/applets/sh/sh_parser.cpp b/src/applets/sh/sh_parser.cpp index b4f9473..565ee6a 100644 --- a/src/applets/sh/sh_parser.cpp +++ b/src/applets/sh/sh_parser.cpp @@ -2,6 +2,7 @@ #include #include +#include namespace cfbox::sh { @@ -247,7 +248,7 @@ auto Parser::parse_redirect() -> std::optional { // Get target if (current_.type != TokType::Word) { - std::fprintf(stderr, "cfbox sh: syntax error: missing redirect target\n"); + CFBOX_ERR("sh", "syntax error: missing redirect target"); return std::nullopt; } @@ -275,7 +276,7 @@ auto Parser::parse_if() -> std::unique_ptr { // Parse condition (until 'then') auto cond = parse_compound_list(); if (!expect_keyword("then")) { - std::fprintf(stderr, "cfbox sh: syntax error: expected 'then'\n"); + CFBOX_ERR("sh", "syntax error: expected 'then'"); return result; } @@ -289,7 +290,7 @@ auto Parser::parse_if() -> std::unique_ptr { advance(); auto elif_cond = parse_compound_list(); if (!expect_keyword("then")) { - std::fprintf(stderr, "cfbox sh: syntax error: expected 'then'\n"); + CFBOX_ERR("sh", "syntax error: expected 'then'"); break; } auto elif_body = parse_compound_list(); @@ -304,7 +305,7 @@ auto Parser::parse_if() -> std::unique_ptr { } if (!expect_keyword("fi")) { - std::fprintf(stderr, "cfbox sh: syntax error: expected 'fi'\n"); + CFBOX_ERR("sh", "syntax error: expected 'fi'"); } return result; @@ -317,13 +318,13 @@ auto Parser::parse_while() -> std::unique_ptr { result->condition = parse_compound_list(); if (!expect_keyword("do")) { - std::fprintf(stderr, "cfbox sh: syntax error: expected 'do'\n"); + CFBOX_ERR("sh", "syntax error: expected 'do'"); return result; } result->body = parse_compound_list(); if (!expect_keyword("done")) { - std::fprintf(stderr, "cfbox sh: syntax error: expected 'done'\n"); + CFBOX_ERR("sh", "syntax error: expected 'done'"); } return result; @@ -334,7 +335,7 @@ auto Parser::parse_for() -> std::unique_ptr { advance(); // consume 'for' if (current_.type != TokType::Word) { - std::fprintf(stderr, "cfbox sh: syntax error: expected variable name after 'for'\n"); + CFBOX_ERR("sh", "syntax error: expected variable name after 'for'"); return result; } result->var_name = std::move(current_.value); @@ -354,13 +355,13 @@ auto Parser::parse_for() -> std::unique_ptr { while (current_.type == TokType::Semi || current_.type == TokType::Newline) advance(); if (!expect_keyword("do")) { - std::fprintf(stderr, "cfbox sh: syntax error: expected 'do'\n"); + CFBOX_ERR("sh", "syntax error: expected 'do'"); return result; } result->body = parse_compound_list(); if (!expect_keyword("done")) { - std::fprintf(stderr, "cfbox sh: syntax error: expected 'done'\n"); + CFBOX_ERR("sh", "syntax error: expected 'done'"); } return result; @@ -371,7 +372,7 @@ auto Parser::parse_subshell() -> std::unique_ptr { auto result = std::make_unique(); result->body = parse_compound_list(); if (!expect(TokType::RParen)) { - std::fprintf(stderr, "cfbox sh: syntax error: expected ')'\n"); + CFBOX_ERR("sh", "syntax error: expected ');'"); } return result; } @@ -383,7 +384,7 @@ auto Parser::parse_brace_group() -> std::unique_ptr { if (current_.type == TokType::Newline) advance(); result->body = parse_compound_list(); if (current_.type != TokType::RBrace) { - std::fprintf(stderr, "cfbox sh: syntax error: expected '}'\n"); + CFBOX_ERR("sh", "syntax error: expected '}'"); } else { advance(); } diff --git a/src/applets/shuf.cpp b/src/applets/shuf.cpp index a2c3186..918fede 100644 --- a/src/applets/shuf.cpp +++ b/src/applets/shuf.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -50,7 +51,7 @@ auto shuf_main(int argc, char* argv[]) -> int { return true; }); if (!result) { - std::fprintf(stderr, "cfbox shuf: %s\n", result.error().msg.c_str()); + CFBOX_ERR("shuf", "%s", result.error().msg.c_str()); return 1; } } diff --git a/src/applets/sleep.cpp b/src/applets/sleep.cpp index c00f113..e714109 100644 --- a/src/applets/sleep.cpp +++ b/src/applets/sleep.cpp @@ -6,6 +6,7 @@ #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -26,7 +27,7 @@ auto sleep_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox sleep: missing operand\n"); + CFBOX_ERR("sleep", "missing operand"); return 1; } @@ -36,8 +37,7 @@ auto sleep_main(int argc, char* argv[]) -> int { char* end = nullptr; double val = std::strtod(s.c_str(), &end); if (end == s.c_str() || val < 0) { - std::fprintf(stderr, "cfbox sleep: invalid time interval '%.*s'\n", - static_cast(arg.size()), arg.data()); + CFBOX_ERR("sleep", "invalid time interval '%.*s'", static_cast(arg.size()), arg.data()); return 1; } total += val; diff --git a/src/applets/sort.cpp b/src/applets/sort.cpp index 5c959ee..9f7070d 100644 --- a/src/applets/sort.cpp +++ b/src/applets/sort.cpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace { @@ -116,14 +117,14 @@ auto sort_main(int argc, char* argv[]) -> int { if (pos.empty()) { auto result = cfbox::io::read_all_stdin(); if (!result) { - std::fprintf(stderr, "cfbox sort: %s\n", result.error().msg.c_str()); + CFBOX_ERR("sort", "%s", result.error().msg.c_str()); return 1; } all_lines = cfbox::io::split_lines(result.value()); } else if (pos.size() == 1) { auto result = (pos[0] == "-") ? cfbox::io::read_all_stdin() : cfbox::io::read_all(pos[0]); if (!result) { - std::fprintf(stderr, "cfbox sort: %s\n", result.error().msg.c_str()); + CFBOX_ERR("sort", "%s", result.error().msg.c_str()); return 1; } all_lines = cfbox::io::split_lines(result.value()); @@ -132,7 +133,7 @@ auto sort_main(int argc, char* argv[]) -> int { for (const auto& p : pos) { auto result = (p == "-") ? cfbox::io::read_all_stdin() : cfbox::io::read_all(p); if (!result) { - std::fprintf(stderr, "cfbox sort: %s\n", result.error().msg.c_str()); + CFBOX_ERR("sort", "%s", result.error().msg.c_str()); rc = 1; } else { auto file_lines = cfbox::io::split_lines(result.value()); diff --git a/src/applets/split.cpp b/src/applets/split.cpp index 254c667..6156d93 100644 --- a/src/applets/split.cpp +++ b/src/applets/split.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -58,7 +59,7 @@ auto split_main(int argc, char* argv[]) -> int { auto data_result = (input_path == "-") ? cfbox::io::read_all_stdin() : cfbox::io::read_all(input_path); if (!data_result) { - std::fprintf(stderr, "cfbox split: %s\n", data_result.error().msg.c_str()); + CFBOX_ERR("split", "%s", data_result.error().msg.c_str()); return 1; } @@ -73,7 +74,7 @@ auto split_main(int argc, char* argv[]) -> int { if (offset + len > data.size()) len = data.size() - offset; auto wresult = cfbox::io::write_all(fname, std::string_view{data.data() + offset, len}); if (!wresult) { - std::fprintf(stderr, "cfbox split: %s\n", wresult.error().msg.c_str()); + CFBOX_ERR("split", "%s", wresult.error().msg.c_str()); return 1; } ++file_num; @@ -94,7 +95,7 @@ auto split_main(int argc, char* argv[]) -> int { auto wresult = cfbox::io::write_all(fname, std::string_view{data.data() + line_start, len}); if (!wresult) { - std::fprintf(stderr, "cfbox split: %s\n", wresult.error().msg.c_str()); + CFBOX_ERR("split", "%s", wresult.error().msg.c_str()); return 1; } } diff --git a/src/applets/stat.cpp b/src/applets/stat.cpp index 9cb9024..0f42dd7 100644 --- a/src/applets/stat.cpp +++ b/src/applets/stat.cpp @@ -9,6 +9,7 @@ #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -72,7 +73,7 @@ auto stat_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox stat: missing operand\n"); + CFBOX_ERR("stat", "missing operand"); return 1; } @@ -83,8 +84,7 @@ auto stat_main(int argc, char* argv[]) -> int { if (fs_stat) { struct statvfs vfs; if (statvfs(path.c_str(), &vfs) != 0) { - std::fprintf(stderr, "cfbox stat: cannot stat '%s': %s\n", - path.c_str(), std::strerror(errno)); + CFBOX_ERR("stat", "cannot stat '%s': %s", path.c_str(), std::strerror(errno)); rc = 1; continue; } @@ -102,8 +102,7 @@ auto stat_main(int argc, char* argv[]) -> int { struct stat st; if (lstat(path.c_str(), &st) != 0) { - std::fprintf(stderr, "cfbox stat: cannot stat '%s': %s\n", - path.c_str(), std::strerror(errno)); + CFBOX_ERR("stat", "cannot stat '%s': %s", path.c_str(), std::strerror(errno)); rc = 1; continue; } diff --git a/src/applets/sum.cpp b/src/applets/sum.cpp index 658db1f..ed8f501 100644 --- a/src/applets/sum.cpp +++ b/src/applets/sum.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -33,7 +34,7 @@ auto sum_main(int argc, char* argv[]) -> int { for (auto p : paths) { auto data_result = (p == "-") ? cfbox::io::read_all_stdin() : cfbox::io::read_all(p); if (!data_result) { - std::fprintf(stderr, "cfbox sum: %s\n", data_result.error().msg.c_str()); + CFBOX_ERR("sum", "%s", data_result.error().msg.c_str()); rc = 1; continue; } diff --git a/src/applets/sysctl.cpp b/src/applets/sysctl.cpp index 67ec129..a492ce5 100644 --- a/src/applets/sysctl.cpp +++ b/src/applets/sysctl.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace { @@ -83,7 +84,7 @@ auto show_all(bool no_name) -> void { auto load_file(const std::string& filepath, bool no_name) -> int { std::ifstream f(filepath); if (!f) { - std::fprintf(stderr, "cfbox sysctl: cannot open %s\n", filepath.c_str()); + CFBOX_ERR("sysctl", "cannot open %s", filepath.c_str()); return 1; } @@ -102,7 +103,7 @@ auto load_file(const std::string& filepath, bool no_name) -> int { auto path = key_to_path(key); if (!write_sysctl_value(path, val)) { - std::fprintf(stderr, "cfbox sysctl: cannot set %s\n", key.c_str()); + CFBOX_ERR("sysctl", "cannot set %s", key.c_str()); ++errors; } else if (!no_name) { std::printf("%s = %s\n", key.c_str(), val.c_str()); @@ -149,7 +150,7 @@ auto sysctl_main(int argc, char* argv[]) -> int { if (do_write) { auto eq = s.find('='); if (eq == std::string::npos) { - std::fprintf(stderr, "cfbox sysctl: invalid setting: %s\n", s.c_str()); + CFBOX_ERR("sysctl", "invalid setting: %s", s.c_str()); ++errors; continue; } @@ -157,14 +158,14 @@ auto sysctl_main(int argc, char* argv[]) -> int { auto val = s.substr(eq + 1); auto path = key_to_path(key); if (!write_sysctl_value(path, val)) { - std::fprintf(stderr, "cfbox sysctl: cannot set %s\n", key.c_str()); + CFBOX_ERR("sysctl", "cannot set %s", key.c_str()); ++errors; } else if (!no_name) { std::printf("%s = %s\n", key.c_str(), val.c_str()); } } else { if (!show_key(s, no_name)) { - std::fprintf(stderr, "cfbox sysctl: cannot stat %s\n", s.c_str()); + CFBOX_ERR("sysctl", "cannot stat %s", s.c_str()); ++errors; } } diff --git a/src/applets/tac.cpp b/src/applets/tac.cpp index 90156ff..209db20 100644 --- a/src/applets/tac.cpp +++ b/src/applets/tac.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -35,7 +36,7 @@ auto tac_main(int argc, char* argv[]) -> int { return true; }); if (!result) { - std::fprintf(stderr, "cfbox tac: %s\n", result.error().msg.c_str()); + CFBOX_ERR("tac", "%s", result.error().msg.c_str()); rc = 1; continue; } diff --git a/src/applets/tail.cpp b/src/applets/tail.cpp index 5aa2e93..4282529 100644 --- a/src/applets/tail.cpp +++ b/src/applets/tail.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace { @@ -48,7 +49,7 @@ auto tail_file(std::string_view path, long n, bool use_bytes, auto result = use_stdin ? cfbox::io::read_all_stdin() : cfbox::io::read_all(path); if (!result) { - std::fprintf(stderr, "cfbox tail: %s\n", result.error().msg.c_str()); + CFBOX_ERR("tail", "%s", result.error().msg.c_str()); return 1; } diff --git a/src/applets/tar.cpp b/src/applets/tar.cpp index 2570450..36b0682 100644 --- a/src/applets/tar.cpp +++ b/src/applets/tar.cpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -131,7 +132,7 @@ auto tar_main(int argc, char* argv[]) -> int { } auto data = cfbox::io::read_all(fullpath.string()); if (!data) { - std::fprintf(stderr, "cfbox tar: %s: %s\n", relpath.c_str(), data.error().msg.c_str()); + CFBOX_ERR("tar", "%s: %s", relpath.c_str(), data.error().msg.c_str()); continue; } auto hdr = create_header(relpath, data->size(), '0'); @@ -147,7 +148,7 @@ auto tar_main(int argc, char* argv[]) -> int { } else { auto wresult = cfbox::io::write_all(archive, archive_data); if (!wresult) { - std::fprintf(stderr, "cfbox tar: %s\n", wresult.error().msg.c_str()); + CFBOX_ERR("tar", "%s", wresult.error().msg.c_str()); return 1; } } @@ -159,7 +160,7 @@ auto tar_main(int argc, char* argv[]) -> int { auto input = (archive == "-") ? cfbox::io::read_all_stdin() : cfbox::io::read_all(archive); if (!input) { - std::fprintf(stderr, "cfbox tar: %s\n", input.error().msg.c_str()); + CFBOX_ERR("tar", "%s", input.error().msg.c_str()); return 1; } const auto& data = *input; @@ -182,7 +183,7 @@ auto tar_main(int argc, char* argv[]) -> int { auto content = data.substr(offset + 512, fsize); auto wresult = cfbox::io::write_all(name, content); if (!wresult) { - std::fprintf(stderr, "cfbox tar: %s\n", wresult.error().msg.c_str()); + CFBOX_ERR("tar", "%s", wresult.error().msg.c_str()); } } } @@ -193,6 +194,6 @@ auto tar_main(int argc, char* argv[]) -> int { return 0; } - std::fprintf(stderr, "cfbox tar: must specify -c, -x, or -t\n"); + CFBOX_ERR("tar", "must specify -c, -x, or -t"); return 1; } diff --git a/src/applets/tee.cpp b/src/applets/tee.cpp index 82e3d02..a36994c 100644 --- a/src/applets/tee.cpp +++ b/src/applets/tee.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -33,7 +34,7 @@ auto tee_main(int argc, char* argv[]) -> int { for (auto p : pos) { auto* f = std::fopen(std::string{p}.c_str(), append ? "ab" : "wb"); if (!f) { - std::fprintf(stderr, "cfbox tee: %s: %s\n", std::string{p}.c_str(), std::strerror(errno)); + CFBOX_ERR("tee", "%s: %s", std::string{p}.c_str(), std::strerror(errno)); } else { files.emplace_back(f); } diff --git a/src/applets/test.cpp b/src/applets/test.cpp index c03dff4..8de41ff 100644 --- a/src/applets/test.cpp +++ b/src/applets/test.cpp @@ -9,6 +9,7 @@ #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -29,20 +30,20 @@ using Args = std::vector; auto eval_expr(const Args& args) -> int; auto to_int(std::string_view s) -> long { - return std::strtol(std::string{s}.c_str(), nullptr, 10); + return std::strtol(s.data(), nullptr, 10); } auto file_test(char op, std::string_view path) -> bool { struct stat st {}; switch (op) { - case 'e': return stat(std::string{path}.c_str(), &st) == 0; - case 'f': return stat(std::string{path}.c_str(), &st) == 0 && S_ISREG(st.st_mode); - case 'd': return stat(std::string{path}.c_str(), &st) == 0 && S_ISDIR(st.st_mode); - case 'r': return access(std::string{path}.c_str(), R_OK) == 0; - case 'w': return access(std::string{path}.c_str(), W_OK) == 0; - case 'x': return access(std::string{path}.c_str(), X_OK) == 0; - case 's': return stat(std::string{path}.c_str(), &st) == 0 && st.st_size > 0; - case 'L': return lstat(std::string{path}.c_str(), &st) == 0 && S_ISLNK(st.st_mode); + case 'e': return stat(path.data(), &st) == 0; + case 'f': return stat(path.data(), &st) == 0 && S_ISREG(st.st_mode); + case 'd': return stat(path.data(), &st) == 0 && S_ISDIR(st.st_mode); + case 'r': return access(path.data(), R_OK) == 0; + case 'w': return access(path.data(), W_OK) == 0; + case 'x': return access(path.data(), X_OK) == 0; + case 's': return stat(path.data(), &st) == 0 && st.st_size > 0; + case 'L': return lstat(path.data(), &st) == 0 && S_ISLNK(st.st_mode); default: return false; } } @@ -111,8 +112,7 @@ auto eval_expr(const Args& args) -> int { return file_test(op, args[1]) ? 0 : 1; } } - std::fprintf(stderr, "cfbox test: unknown operator '%.*s'\n", - static_cast(args[0].size()), args[0].data()); + CFBOX_ERR("test", "unknown operator '%.*s'", static_cast(args[0].size()), args[0].data()); return 2; } @@ -130,8 +130,7 @@ auto eval_expr(const Args& args) -> int { if (args[1] == "-gt") return to_int(args[0]) > to_int(args[2]) ? 0 : 1; if (args[1] == "-ge") return to_int(args[0]) >= to_int(args[2]) ? 0 : 1; - std::fprintf(stderr, "cfbox test: unknown operator '%.*s'\n", - static_cast(args[1].size()), args[1].data()); + CFBOX_ERR("test", "unknown operator '%.*s'", static_cast(args[1].size()), args[1].data()); return 2; } @@ -169,7 +168,7 @@ auto test_main(int argc, char* argv[]) -> int { auto base = (slash != std::string_view::npos) ? prog.substr(slash + 1) : prog; if (base == "[" || base == "[") { if (expr_args.empty() || expr_args.back() != "]") { - std::fprintf(stderr, "cfbox [: missing ']'\n"); + CFBOX_ERR("[", "missing ']'"); return 2; } expr_args.pop_back(); diff --git a/src/applets/timeout.cpp b/src/applets/timeout.cpp index 84112ed..c42f6b8 100644 --- a/src/applets/timeout.cpp +++ b/src/applets/timeout.cpp @@ -8,6 +8,7 @@ #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -32,15 +33,14 @@ auto timeout_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.size() < 2) { - std::fprintf(stderr, "cfbox timeout: missing operand\n"); + CFBOX_ERR("timeout", "missing operand"); return 1; } char* end = nullptr; double duration = std::strtod(std::string{pos[0]}.c_str(), &end); if (end == std::string{pos[0]}.c_str() || duration <= 0) { - std::fprintf(stderr, "cfbox timeout: invalid duration '%.*s'\n", - static_cast(pos[0].size()), pos[0].data()); + CFBOX_ERR("timeout", "invalid duration '%.*s'", static_cast(pos[0].size()), pos[0].data()); return 1; } @@ -52,14 +52,14 @@ auto timeout_main(int argc, char* argv[]) -> int { else if (name == "INT" || name == "2") sig = SIGINT; else if (name == "HUP" || name == "1") sig = SIGHUP; else { - std::fprintf(stderr, "cfbox timeout: unknown signal '%s'\n", name.c_str()); + CFBOX_ERR("timeout", "unknown signal '%s'", name.c_str()); return 1; } } pid_t pid = fork(); if (pid < 0) { - std::fprintf(stderr, "cfbox timeout: fork failed: %s\n", std::strerror(errno)); + CFBOX_ERR("timeout", "fork failed: %s", std::strerror(errno)); return 1; } @@ -75,8 +75,7 @@ auto timeout_main(int argc, char* argv[]) -> int { for (auto& s : arg_storage) cmd_args.push_back(s.data()); cmd_args.push_back(nullptr); execvp(cmd_args[0], cmd_args.data()); - std::fprintf(stderr, "cfbox timeout: failed to execute '%s': %s\n", - cmd_args[0], std::strerror(errno)); + CFBOX_ERR("timeout", "failed to execute '%s': %s", cmd_args[0], std::strerror(errno)); _exit(125); } diff --git a/src/applets/touch.cpp b/src/applets/touch.cpp index 8df3e13..529269f 100644 --- a/src/applets/touch.cpp +++ b/src/applets/touch.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -32,7 +33,7 @@ auto touch_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox touch: missing operand\n"); + CFBOX_ERR("touch", "missing operand"); return 1; } @@ -43,8 +44,7 @@ auto touch_main(int argc, char* argv[]) -> int { if (no_create) continue; int fd = ::open(path.c_str(), O_CREAT | O_WRONLY, 0666); if (fd < 0) { - std::fprintf(stderr, "cfbox touch: cannot touch '%s': %s\n", - path.c_str(), std::strerror(errno)); + CFBOX_ERR("touch", "cannot touch '%s': %s", path.c_str(), std::strerror(errno)); rc = 1; continue; } @@ -52,8 +52,7 @@ auto touch_main(int argc, char* argv[]) -> int { } // Update timestamps if (::utime(path.c_str(), nullptr) != 0) { - std::fprintf(stderr, "cfbox touch: cannot touch '%s': %s\n", - path.c_str(), std::strerror(errno)); + CFBOX_ERR("touch", "cannot touch '%s': %s", path.c_str(), std::strerror(errno)); rc = 1; } } diff --git a/src/applets/tr.cpp b/src/applets/tr.cpp index 80acb8b..30b82e1 100644 --- a/src/applets/tr.cpp +++ b/src/applets/tr.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -51,7 +52,7 @@ auto tr_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox tr: missing operand\n"); + CFBOX_ERR("tr", "missing operand"); return 1; } @@ -93,7 +94,7 @@ auto tr_main(int argc, char* argv[]) -> int { auto input = cfbox::io::read_all_stdin(); if (!input) { - std::fprintf(stderr, "cfbox tr: %s\n", input.error().msg.c_str()); + CFBOX_ERR("tr", "%s", input.error().msg.c_str()); return 1; } diff --git a/src/applets/truncate.cpp b/src/applets/truncate.cpp index 8bd438e..aac7603 100644 --- a/src/applets/truncate.cpp +++ b/src/applets/truncate.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -42,20 +43,19 @@ auto truncate_main(int argc, char* argv[]) -> int { auto size_str = parsed.get_any('s', "size"); if (!size_str) { - std::fprintf(stderr, "cfbox truncate: missing operand: -s SIZE\n"); + CFBOX_ERR("truncate", "missing operand: -s SIZE"); return 1; } long size = parse_size(std::string{*size_str}); if (size < 0) { - std::fprintf(stderr, "cfbox truncate: invalid size: '%.*s'\n", - static_cast(size_str->size()), size_str->data()); + CFBOX_ERR("truncate", "invalid size: '%.*s'", static_cast(size_str->size()), size_str->data()); return 1; } const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox truncate: missing operand\n"); + CFBOX_ERR("truncate", "missing operand"); return 1; } @@ -63,7 +63,7 @@ auto truncate_main(int argc, char* argv[]) -> int { for (auto p : pos) { auto result = cfbox::fs::resize_file(std::string{p}, static_cast(size)); if (!result) { - std::fprintf(stderr, "cfbox truncate: %s\n", result.error().msg.c_str()); + CFBOX_ERR("truncate", "%s", result.error().msg.c_str()); rc = 1; } } diff --git a/src/applets/tsort.cpp b/src/applets/tsort.cpp index 28aae77..25b6532 100644 --- a/src/applets/tsort.cpp +++ b/src/applets/tsort.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -46,7 +47,7 @@ auto tsort_main(int argc, char* argv[]) -> int { return true; }); if (!result) { - std::fprintf(stderr, "cfbox tsort: %s\n", result.error().msg.c_str()); + CFBOX_ERR("tsort", "%s", result.error().msg.c_str()); return 1; } diff --git a/src/applets/uname.cpp b/src/applets/uname.cpp index 533be09..8b6647c 100644 --- a/src/applets/uname.cpp +++ b/src/applets/uname.cpp @@ -5,6 +5,7 @@ #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -37,7 +38,7 @@ auto uname_main(int argc, char* argv[]) -> int { struct utsname info {}; if (::uname(&info) != 0) { - std::fprintf(stderr, "cfbox uname: failed to get system information\n"); + CFBOX_ERR("uname", "failed to get system information"); return 1; } diff --git a/src/applets/uniq.cpp b/src/applets/uniq.cpp index a18975a..abcf1a3 100644 --- a/src/applets/uniq.cpp +++ b/src/applets/uniq.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace { @@ -78,14 +79,14 @@ auto uniq_main(int argc, char* argv[]) -> int { if (pos.empty()) { auto result = cfbox::io::read_all_stdin(); if (!result) { - std::fprintf(stderr, "cfbox uniq: %s\n", result.error().msg.c_str()); + CFBOX_ERR("uniq", "%s", result.error().msg.c_str()); return 1; } lines = cfbox::io::split_lines(result.value()); } else { auto result = (pos[0] == "-") ? cfbox::io::read_all_stdin() : cfbox::io::read_all(pos[0]); if (!result) { - std::fprintf(stderr, "cfbox uniq: %s\n", result.error().msg.c_str()); + CFBOX_ERR("uniq", "%s", result.error().msg.c_str()); return 1; } lines = cfbox::io::split_lines(result.value()); diff --git a/src/applets/unlink.cpp b/src/applets/unlink.cpp index b098f3b..5c604da 100644 --- a/src/applets/unlink.cpp +++ b/src/applets/unlink.cpp @@ -5,6 +5,7 @@ #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -25,18 +26,16 @@ auto unlink_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox unlink: missing operand\n"); + CFBOX_ERR("unlink", "missing operand"); return 1; } if (pos.size() > 1) { - std::fprintf(stderr, "cfbox unlink: extra operand '%.*s'\n", - static_cast(pos[1].size()), pos[1].data()); + CFBOX_ERR("unlink", "extra operand '%.*s'", static_cast(pos[1].size()), pos[1].data()); return 1; } if (::unlink(std::string{pos[0]}.c_str()) != 0) { - std::fprintf(stderr, "cfbox unlink: cannot unlink '%.*s': %s\n", - static_cast(pos[0].size()), pos[0].data(), std::strerror(errno)); + CFBOX_ERR("unlink", "cannot unlink '%.*s': %s", static_cast(pos[0].size()), pos[0].data(), std::strerror(errno)); return 1; } return 0; diff --git a/src/applets/unzip.cpp b/src/applets/unzip.cpp index 5e64a62..65c6509 100644 --- a/src/applets/unzip.cpp +++ b/src/applets/unzip.cpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -52,13 +53,13 @@ auto unzip_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox unzip: missing archive\n"); + CFBOX_ERR("unzip", "missing archive"); return 1; } auto input = cfbox::io::read_all(std::string{pos[0]}); if (!input) { - std::fprintf(stderr, "cfbox unzip: %s\n", input.error().msg.c_str()); + CFBOX_ERR("unzip", "%s", input.error().msg.c_str()); return 1; } const auto& data = *input; @@ -73,7 +74,7 @@ auto unzip_main(int argc, char* argv[]) -> int { } } if (eocd >= data.size()) { - std::fprintf(stderr, "cfbox unzip: not a valid zip file\n"); + CFBOX_ERR("unzip", "not a valid zip file"); return 1; } @@ -137,7 +138,7 @@ auto unzip_main(int argc, char* argv[]) -> int { } auto wresult = cfbox::io::write_all(outpath, content); if (!wresult) { - std::fprintf(stderr, "cfbox unzip: %s\n", wresult.error().msg.c_str()); + CFBOX_ERR("unzip", "%s", wresult.error().msg.c_str()); } } return 0; diff --git a/src/applets/uptime.cpp b/src/applets/uptime.cpp index f3d28e1..5151c13 100644 --- a/src/applets/uptime.cpp +++ b/src/applets/uptime.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace { @@ -50,7 +51,7 @@ auto uptime_main(int argc, char* argv[]) -> int { // Uptime auto up_result = cfbox::proc::read_uptime(); if (!up_result) { - std::fprintf(stderr, "cfbox uptime: %s\n", up_result.error().msg.c_str()); + CFBOX_ERR("uptime", "%s", up_result.error().msg.c_str()); return 1; } double uptime_secs = up_result->first; diff --git a/src/applets/usleep.cpp b/src/applets/usleep.cpp index 02d623c..91330b5 100644 --- a/src/applets/usleep.cpp +++ b/src/applets/usleep.cpp @@ -6,6 +6,7 @@ #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -26,15 +27,14 @@ auto usleep_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox usleep: missing operand\n"); + CFBOX_ERR("usleep", "missing operand"); return 1; } char* end = nullptr; long usecs = std::strtol(std::string{pos[0]}.c_str(), &end, 10); if (end == std::string{pos[0]}.c_str() || usecs < 0) { - std::fprintf(stderr, "cfbox usleep: invalid time interval '%.*s'\n", - static_cast(pos[0].size()), pos[0].data()); + CFBOX_ERR("usleep", "invalid time interval '%.*s'", static_cast(pos[0].size()), pos[0].data()); return 1; } diff --git a/src/applets/watch.cpp b/src/applets/watch.cpp index 21acfc1..b4d7401 100644 --- a/src/applets/watch.cpp +++ b/src/applets/watch.cpp @@ -13,6 +13,7 @@ #include #include #include +#include namespace { @@ -87,7 +88,7 @@ auto watch_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox watch: no command specified\n"); + CFBOX_ERR("watch", "no command specified"); return 1; } diff --git a/src/applets/wc.cpp b/src/applets/wc.cpp index 90b7f53..6d349e9 100644 --- a/src/applets/wc.cpp +++ b/src/applets/wc.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace { @@ -87,7 +88,7 @@ auto wc_main(int argc, char* argv[]) -> int { if (pos.empty()) { auto result = wc_file("-"); if (!result) { - std::fprintf(stderr, "cfbox wc: %s\n", result.error().msg.c_str()); + CFBOX_ERR("wc", "%s", result.error().msg.c_str()); return 1; } print_counts(*result, show_lines, show_words, show_bytes, all); @@ -98,7 +99,7 @@ auto wc_main(int argc, char* argv[]) -> int { if (pos.size() == 1) { auto result = wc_file(pos[0]); if (!result) { - std::fprintf(stderr, "cfbox wc: %s\n", result.error().msg.c_str()); + CFBOX_ERR("wc", "%s", result.error().msg.c_str()); return 1; } print_counts(*result, show_lines, show_words, show_bytes, all); @@ -112,7 +113,7 @@ auto wc_main(int argc, char* argv[]) -> int { for (const auto& p : pos) { auto result = wc_file(p); if (!result) { - std::fprintf(stderr, "cfbox wc: %s\n", result.error().msg.c_str()); + CFBOX_ERR("wc", "%s", result.error().msg.c_str()); rc = 1; continue; } diff --git a/src/applets/which.cpp b/src/applets/which.cpp index 1845e75..1daa302 100644 --- a/src/applets/which.cpp +++ b/src/applets/which.cpp @@ -6,6 +6,7 @@ #include #include +#include namespace { @@ -56,15 +57,14 @@ auto which_main(int argc, char* argv[]) -> int { bool all = parsed.has('a'); const auto& pos = parsed.positional(); if (pos.empty()) { - std::fprintf(stderr, "cfbox which: missing argument\n"); + CFBOX_ERR("which", "missing argument"); return 2; } int rc = 0; for (const auto& cmd : pos) { if (find_in_path(cmd, all) != 0) { - std::fprintf(stderr, "cfbox which: no %.*s in PATH\n", - static_cast(cmd.size()), cmd.data()); + CFBOX_ERR("which", "no %.*s in PATH", static_cast(cmd.size()), cmd.data()); rc = 1; } } diff --git a/src/applets/xargs.cpp b/src/applets/xargs.cpp index eaeaa82..e75318f 100644 --- a/src/applets/xargs.cpp +++ b/src/applets/xargs.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace { constexpr cfbox::help::HelpEntry HELP = { @@ -66,7 +67,7 @@ auto xargs_main(int argc, char* argv[]) -> int { // Read items from stdin auto input = cfbox::io::read_all_stdin(); if (!input) { - std::fprintf(stderr, "cfbox xargs: %s\n", input.error().msg.c_str()); + CFBOX_ERR("xargs", "%s", input.error().msg.c_str()); return 1; } @@ -136,12 +137,12 @@ auto xargs_main(int argc, char* argv[]) -> int { pid_t pid = fork(); if (pid < 0) { - std::fprintf(stderr, "cfbox xargs: fork: %s\n", std::strerror(errno)); + CFBOX_ERR("xargs", "fork: %s", std::strerror(errno)); return 1; } if (pid == 0) { execvp(command.c_str(), exec_args.data()); - std::fprintf(stderr, "cfbox xargs: %s: %s\n", command.c_str(), std::strerror(errno)); + CFBOX_ERR("xargs", "%s: %s", command.c_str(), std::strerror(errno)); _exit(127); } int status; From ffa8f47706e30f6be735bbdd9af9068f966b5796 Mon Sep 17 00:00:00 2001 From: Charliechen114514 <725610365@qq.com> Date: Tue, 26 May 2026 18:28:14 +0800 Subject: [PATCH 4/8] feat: add ci cache and optimize most applets --- .github/workflows/ci.yml | 38 +- CMakeLists.txt | 2 +- README.en.md | 47 +-- README.md | 47 +-- changelogs/v0.2.0.md | 209 ++++++++++ document/todo/README.md | 16 +- .../phases/phase-1.5-code-quality-review.md | 5 +- include/cfbox/fs_util.hpp | 29 ++ include/cfbox/io.hpp | 45 ++- include/cfbox/proc.hpp | 379 +++++++++++------- src/applets/chgrp.cpp | 21 +- src/applets/chmod.cpp | 12 +- src/applets/chown.cpp | 28 +- src/applets/cut.cpp | 15 +- src/applets/fuser.cpp | 2 +- src/applets/hexdump.cpp | 1 - src/applets/more.cpp | 2 +- src/applets/pmap.cpp | 87 ++-- src/applets/rev.cpp | 24 +- src/applets/sysctl.cpp | 46 ++- tests/unit/test_cat.cpp | 66 +++ tests/unit/test_echo.cpp | 45 +++ tests/unit/test_head.cpp | 62 +++ tests/unit/test_tail.cpp | 63 +++ tests/unit/test_wc.cpp | 83 ++++ 25 files changed, 1036 insertions(+), 338 deletions(-) create mode 100644 changelogs/v0.2.0.md create mode 100644 tests/unit/test_cat.cpp create mode 100644 tests/unit/test_echo.cpp create mode 100644 tests/unit/test_head.cpp create mode 100644 tests/unit/test_tail.cpp create mode 100644 tests/unit/test_wc.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9a97933..ffb2c0c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,10 +16,19 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y cmake g++-13 + sudo apt-get install -y cmake g++-13 ccache + + - name: Restore ccache + uses: actions/cache@v4 + with: + path: ~/.ccache + key: ccache-native-${{ runner.os }}-${{ github.sha }} + restore-keys: ccache-native-${{ runner.os }}- - name: Configure (Debug) - run: cmake -B build -DCMAKE_CXX_COMPILER=g++-13 + run: cmake -B build -DCMAKE_CXX_COMPILER=g++-13 -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + env: + CCACHE_MAXSIZE: 256M - name: Build run: cmake --build build -j $(nproc) @@ -46,14 +55,24 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y cmake g++-13 + sudo apt-get install -y cmake g++-13 ccache + + - name: Restore ccache + uses: actions/cache@v4 + with: + path: ~/.ccache + key: ccache-release-${{ runner.os }}-${{ github.sha }} + restore-keys: ccache-release-${{ runner.os }}- - name: Configure (Release, -Os) run: > cmake -B build-release -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-13 + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCFBOX_OPTIMIZE_FOR_SIZE=ON + env: + CCACHE_MAXSIZE: 256M - name: Build run: cmake --build build-release -j $(nproc) @@ -92,22 +111,32 @@ jobs: if: matrix.target == 'aarch64' run: | sudo apt-get update - sudo apt-get install -y cmake ${{ matrix.compiler_pkg }} + sudo apt-get install -y cmake ${{ matrix.compiler_pkg }} ccache - name: Install cross-compiler (armhf) if: matrix.target == 'armhf' run: | + sudo apt-get update + sudo apt-get install -y ccache curl -L -o /tmp/arm-toolchain.tar.xz \ https://developer.arm.com/-/media/Files/downloads/gnu/15.2.rel1/binrel/arm-gnu-toolchain-15.2.rel1-x86_64-arm-none-linux-gnueabihf.tar.xz sudo tar -xJf /tmp/arm-toolchain.tar.xz -C /opt sudo ln -s /opt/arm-gnu-toolchain-15.2.rel1-x86_64-arm-none-linux-gnueabihf /opt/arm-gnu-toolchain echo "/opt/arm-gnu-toolchain-15.2.rel1-x86_64-arm-none-linux-gnueabihf/bin" >> "$GITHUB_PATH" + - name: Restore ccache + uses: actions/cache@v4 + with: + path: ~/.ccache + key: ccache-cross-${{ matrix.target }}-${{ github.sha }} + restore-keys: ccache-cross-${{ matrix.target }}- + - name: Cross-compile (dynamic, -Os) run: | cmake -B build-${{ matrix.target }} \ -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain/${{ matrix.toolchain }} \ -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DCFBOX_OPTIMIZE_FOR_SIZE=ON cmake --build build-${{ matrix.target }} -j $(nproc) @@ -123,6 +152,7 @@ jobs: cmake -B build-${{ matrix.target }}-static \ -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain/${{ matrix.toolchain }} \ -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DCFBOX_OPTIMIZE_FOR_SIZE=ON \ -DCFBOX_STATIC_LINK=ON cmake --build build-${{ matrix.target }}-static -j $(nproc) diff --git a/CMakeLists.txt b/CMakeLists.txt index 46cd6a0..f867c99 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.26 FATAL_ERROR) project(CFBox - VERSION 0.0.1 + VERSION 0.2.0 DESCRIPTION "A Modern C++ Toy Busybox" HOMEPAGE_URL "https://github.com/Awesome-Embedded-Learning-Studio/CFBox" LANGUAGES CXX diff --git a/README.en.md b/README.en.md index d7f5232..82bc0fc 100644 --- a/README.en.md +++ b/README.en.md @@ -8,12 +8,12 @@ A minimalist BusyBox alternative written in modern C++23. [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![C++23](https://img.shields.io/badge/C++23-00599C?logo=cplusplus)](https://en.cppreference.com/w/cpp/23) [![CMake](https://img.shields.io/badge/CMake-3.26+-064F8C?logo=cmake)](https://cmake.org/) -[![Tests](https://img.shields.io/badge/Tests-331_passing-brightgreen)](tests/) -[![Applets](https://img.shields.io/badge/Applets-109-brightgreen)](src/applets/) +[![Tests](https://img.shields.io/badge/Tests-379_passing-brightgreen)](tests/) +[![Applets](https://img.shields.io/badge/Applets-115-brightgreen)](src/applets/) ## Overview -CFBox is a single-executable Unix utility collection distributed via symbolic links. 109 applets implemented and tested, with a CI pipeline covering native builds, cross-compilation, and QEMU user/system-mode testing. Features configurable CMake builds (per-applet toggles), GNU-style long options, and colored help output. +CFBox is a single-executable Unix utility collection distributed via symbolic links. 115 applets implemented and tested, with a CI pipeline covering native builds, cross-compilation, and QEMU user/system-mode testing. Features configurable CMake builds (per-applet toggles), GNU-style long options, and colored help output. **Design philosophy:** Simplicity first — Modern C++ (`std::expected`) — Embedded-friendly (cross-compilation, static linking) @@ -21,7 +21,7 @@ CFBox is a single-executable Unix utility collection distributed via symbolic li | Project | Language | Size | Applets | Size/Applet | |---------|----------|------|---------|-------------| -| **CFBox (size-opt)** | **C++23** | **446 KB** | **109** | **~4.1 KB** | +| **CFBox (size-opt)** | **C++23** | **406 KB** | **115** | **~3.5 KB** | | Toybox | C | ~500 KB | 238 | ~2.1 KB | | BusyBox (full) | C | ~1.7 MB | 274 | ~9 KB | | uutils/coreutils | Rust | ~11 MB | ~100 | ~110 KB | @@ -50,7 +50,7 @@ cmake -B build cmake --build build # Test -ctest --test-dir build --output-on-failure # 331 GTest unit tests +ctest --test-dir build --output-on-failure # 379 GTest unit tests bash tests/integration/run_all.sh # 54 integration test scripts # Run via subcommand @@ -61,15 +61,15 @@ bash tests/integration/run_all.sh # 54 integration test scripts echo "Hello, World!" # now calls cfbox via symlink ``` -## Supported Commands (109) +## Supported Commands (115) ### Text Processing (31) `echo`, `printf`, `cat`, `head`, `tail`, `wc`, `sort`, `uniq`, `grep`, `sed`, `fold`, `expand`, `cut`, `paste`, `nl`, `comm`, `tr`, `tac`, `rev`, `shuf`, `factor`, `od`, `split`, `seq`, `tsort`, `expr`, `awk`, `diff`, `patch`, `cmp`, `ed` -### File Operations (19) +### File Operations (22) -`mkdir`, `rm`, `cp`, `mv`, `ls`, `find`, `ln`, `touch`, `stat`, `install`, `mktemp`, `truncate`, `du`, `df`, `readlink`, `realpath`, `rmdir`, `link`, `unlink` +`mkdir`, `rm`, `cp`, `mv`, `ls`, `find`, `ln`, `touch`, `stat`, `install`, `mktemp`, `truncate`, `du`, `df`, `readlink`, `realpath`, `rmdir`, `link`, `unlink`, `chmod`, `chown`, `chgrp` ### Archive & Compression (6) @@ -87,9 +87,9 @@ echo "Hello, World!" # now calls cfbox via symlink `ps`, `top`, `kill`, `pgrep`/`pkill`, `pidof`, `pstree`, `pmap`, `fuser`, `pwdx`, `sysctl`, `iostat`, `watch`, `nice`, `renice`, `timeout` -### Other (16) +### Other (19) -`true`, `false`, `yes`, `sleep`, `usleep`, `sync`, `nohup`, `cksum`, `md5sum`, `sum`, `hexdump`, `more`, `tee`, `init` (PID 1 initramfs init system), `mkfifo`, `mknod` +`true`, `false`, `yes`, `sleep`, `usleep`, `sync`, `nohup`, `cksum`, `md5sum`, `sum`, `hexdump`, `more`, `tee`, `init` (PID 1 initramfs init system), `mkfifo`, `mknod`, `clear`, `which`, `mountpoint` > All applets support `--help` / `--version` @@ -137,33 +137,26 @@ cfbox/ │ └── ... # help.hpp, fs_util.hpp, escape.hpp, checksum.hpp ├── src/ │ ├── main.cpp # Dispatch entry -│ └── applets/ # 109 command implementations +│ └── applets/ # 115 command implementations ├── tests/ -│ ├── unit/ # GTest unit tests (331 cases) +│ ├── unit/ # GTest unit tests (379 cases) │ └── integration/ # Shell integration tests (54 scripts) └── scripts/ # Build, test, install scripts ``` ## Next Steps -Current release: v0.1.0. Upcoming work, in priority order: +Current release: v0.2.0 (Phase 1 Wave 1 + Phase 1.5 code quality review complete). Now entering Phase 2: core command deepening. -### Phase 0: Production Pre-gates (In Progress) +### Phase 2: Core Command Deepening (In Progress) -Before adding new applets, the following quality foundations must be completed: +Deepening existing commands from ~30% to ~70% feature completeness, in batches by operational frequency: -| Phase | Scope | Status | -|-------|-------|--------| -| **0A** Baseline Inventory | 109-applet catalog, maturity labels, profile assignments, doc drift fixes | Pending | -| **0B** Perf Baseline | Core benchmarks (cat/grep/sed/sort/find/tar/gzip/cp/tail), RSS regression thresholds | Pending | -| **0C** Size Budget | Per-profile size caps, new rescue/container profiles, per-applet delta tracking | Pending | -| **0D** IO Policy | Streaming audit (head/tail/sed/tr/md5sum etc.), large file/pipe/broken pipe tests | Pending | -| **0E** Safety Hardening | Unified numeric parsers, parser fuzz smoke, privileged command isolation tests | Pending | -| **0F** CI & Release | Tiered CI (sanitizer/benchmark/differential/cross/QEMU), reproducible builds, error format spec | Pending | - -### Phase 1: Core System (After Phase 0) - -New system-level applets: `chmod`, `chown`, `chgrp`, `mount`, `umount`, `chroot`, `dd`, `stty`, plus deepening existing core commands. +| Batch | Commands | Key Additions | +|-------|----------|---------------| +| Batch 1 | `tail`, `cp`, `test`, `ls` | tail -f, cp -a, full POSIX test, ls -R/--color | +| Batch 2 | `grep`, `tar`, `sed`, `sort` | grep -A/-B/-C, tar -z/-v, sed -i, sort -k | +| Batch 3 | `find`, `sh`, `ps`, `df`, `du` | find boolean expressions, sh case/heredoc/functions | > See [document/todo/README.md](document/todo/README.md) for the full roadmap. diff --git a/README.md b/README.md index b619096..691fb4e 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,12 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![C++23](https://img.shields.io/badge/C++23-00599C?logo=cplusplus)](https://en.cppreference.com/w/cpp/23) [![CMake](https://img.shields.io/badge/CMake-3.26+-064F8C?logo=cmake)](https://cmake.org/) -[![Tests](https://img.shields.io/badge/Tests-331_passing-brightgreen)](tests/) -[![Applets](https://img.shields.io/badge/Applets-109-brightgreen)](src/applets/) +[![Tests](https://img.shields.io/badge/Tests-379_passing-brightgreen)](tests/) +[![Applets](https://img.shields.io/badge/Applets-115-brightgreen)](src/applets/) ## 概述 -CFBox 是一个单一可执行文件的 Unix 工具集,通过符号链接分发。109 个 applet 已实现并通过测试,CI 流水线覆盖原生构建、交叉编译、QEMU 用户/系统模式测试。支持 CMake 配置化构建(per-applet 开关)、GNU 风格长选项、彩色帮助输出。 +CFBox 是一个单一可执行文件的 Unix 工具集,通过符号链接分发。115 个 applet 已实现并通过测试,CI 流水线覆盖原生构建、交叉编译、QEMU 用户/系统模式测试。支持 CMake 配置化构建(per-applet 开关)、GNU 风格长选项、彩色帮助输出。 **设计理念:** 简洁优先 — 现代C++(`std::expected`) — 嵌入式友好(交叉编译、静态链接) @@ -21,7 +21,7 @@ CFBox 是一个单一可执行文件的 Unix 工具集,通过符号链接分 | 项目 | 语言 | 体积 | Applets | 体积/Applet | |------|------|------|---------|-------------| -| **CFBox (size-opt)** | **C++23** | **446 KB** | **109** | **~4.1 KB** | +| **CFBox (size-opt)** | **C++23** | **406 KB** | **115** | **~3.5 KB** | | Toybox | C | ~500 KB | 238 | ~2.1 KB | | BusyBox (full) | C | ~1.7 MB | 274 | ~9 KB | | uutils/coreutils | Rust | ~11 MB | ~100 | ~110 KB | @@ -50,7 +50,7 @@ cmake -B build cmake --build build # 测试 -ctest --test-dir build --output-on-failure # 331 个 GTest 单元测试 +ctest --test-dir build --output-on-failure # 379 个 GTest 单元测试 bash tests/integration/run_all.sh # 54 套集成测试脚本 # 通过子命令运行 @@ -61,15 +61,15 @@ bash tests/integration/run_all.sh # 54 套集成测试脚本 echo "Hello, World!" # 通过符号链接调用 cfbox ``` -## 支持的命令(109 个) +## 支持的命令(115 个) ### 文本处理(31 个) `echo`, `printf`, `cat`, `head`, `tail`, `wc`, `sort`, `uniq`, `grep`, `sed`, `fold`, `expand`, `cut`, `paste`, `nl`, `comm`, `tr`, `tac`, `rev`, `shuf`, `factor`, `od`, `split`, `seq`, `tsort`, `expr`, `awk`, `diff`, `patch`, `cmp`, `ed` -### 文件操作(19 个) +### 文件操作(22 个) -`mkdir`, `rm`, `cp`, `mv`, `ls`, `find`, `ln`, `touch`, `stat`, `install`, `mktemp`, `truncate`, `du`, `df`, `readlink`, `realpath`, `rmdir`, `link`, `unlink` +`mkdir`, `rm`, `cp`, `mv`, `ls`, `find`, `ln`, `touch`, `stat`, `install`, `mktemp`, `truncate`, `du`, `df`, `readlink`, `realpath`, `rmdir`, `link`, `unlink`, `chmod`, `chown`, `chgrp` ### 归档与压缩(6 个) @@ -87,9 +87,9 @@ echo "Hello, World!" # 通过符号链接调用 cfbox `ps`, `top`, `kill`, `pgrep`/`pkill`, `pidof`, `pstree`, `pmap`, `fuser`, `pwdx`, `sysctl`, `iostat`, `watch`, `nice`, `renice`, `timeout` -### 其他(16 个) +### 其他(19 个) -`true`, `false`, `yes`, `sleep`, `usleep`, `sync`, `nohup`, `cksum`, `md5sum`, `sum`, `hexdump`, `more`, `tee`, `init`(PID 1 initramfs init 系统), `mkfifo`, `mknod` +`true`, `false`, `yes`, `sleep`, `usleep`, `sync`, `nohup`, `cksum`, `md5sum`, `sum`, `hexdump`, `more`, `tee`, `init`(PID 1 initramfs init 系统), `mkfifo`, `mknod`, `clear`, `which`, `mountpoint` > 所有 applet 均支持 `--help` / `--version` @@ -137,33 +137,26 @@ cfbox/ │ └── ... # help.hpp, fs_util.hpp, escape.hpp, checksum.hpp ├── src/ │ ├── main.cpp # 分发入口 -│ └── applets/ # 109 个命令实现 +│ └── applets/ # 115 个命令实现 ├── tests/ -│ ├── unit/ # GTest 单元测试(331 个用例) +│ ├── unit/ # GTest 单元测试(379 个用例) │ └── integration/ # Shell 集成测试(54 个脚本) └── scripts/ # 构建、测试、安装脚本 ``` ## 下一步计划 -当前版本 v0.1.0,下一阶段工作按优先级排列: +当前版本 v0.2.0(Phase 1 Wave 1 + Phase 1.5 代码质量审查已完成),进入 Phase 2 核心命令深化: -### Phase 0:生产化前置门禁(进行中) +### Phase 2:核心命令深化(进行中) -在新增 applet 之前,必须完成以下质量地基: +将现有命令功能深度从 ~30% 提升到 ~70%,按运维频率分批推进: -| 阶段 | 内容 | 状态 | -|------|------|------| -| **0A** 基线盘点 | 109 个 applet 清单、成熟度标注、profile 归属、文档漂移修复 | 待开始 | -| **0B** 性能基线 | 核心命令 benchmark(cat/grep/sed/sort/find/tar/gzip/cp/tail)、RSS 回归阈值 | 待开始 | -| **0C** 体积预算 | 按 profile 设定体积上限、新增 rescue/container profile、每 applet 增量追踪 | 待开始 | -| **0D** IO 策略 | 流式处理审计(head/tail/sed/tr/md5sum 等整改为流式)、大文件/管道/broken pipe 测试 | 待开始 | -| **0E** 安全加固 | 数值解析统一 helper、parser fuzz smoke、特权命令隔离测试 | 待开始 | -| **0F** CI 与发布 | 分层 CI(sanitizer/benchmark/differential/cross/QEMU)、可复现构建、错误格式规范 | 待开始 | - -### Phase 1:核心系统(Phase 0 完成后) - -新增 `chmod`、`chown`、`chgrp`、`mount`、`umount`、`chroot`、`dd`、`stty` 等系统级 applet,深化核心命令功能。 +| 批次 | 命令 | 关键补充 | +|------|------|---------| +| 第一批 | `tail`、`cp`、`test`、`ls` | tail -f、cp -a、全面 POSIX test、ls -R/--color | +| 第二批 | `grep`、`tar`、`sed`、`sort` | grep -A/-B/-C、tar -z/-v、sed -i、sort -k | +| 第三批 | `find`、`sh`、`ps`、`df`、`du` | find 布尔表达式、sh case/heredoc/函数 | > 详细路线图见 [document/todo/README.md](document/todo/README.md)。 diff --git a/changelogs/v0.2.0.md b/changelogs/v0.2.0.md new file mode 100644 index 0000000..e1bb9f6 --- /dev/null +++ b/changelogs/v0.2.0.md @@ -0,0 +1,209 @@ +# CFBox v0.2.0 — 优化与更新 + +> 代码质量全面优化:二进制体积减少 14%,消除 iostream 运行时依赖,修复潜在 abort 风险。 + +## 亮点 + +- **体积缩减 14%**:size-opt Release 从 473 KB 降至 **406 KB** +- **彻底消除 iostream 依赖**:全量移除 `` / `` / ``,零 fstream 符号残留 +- **`std::stoi` 全部替换为 `std::strtol`**:避免 `-fno-exceptions` 下 `stoi` 解析失败时直接 `abort()` +- **353 测试全绿**,零编译 warning,ASan 零泄漏 + +## 体积对比 + +| 指标 | v0.1.0 | v0.2.0 | 变化 | +|------|:------:|:------:|:----:| +| size-opt Release | 473 KB | **406 KB** | **-67 KB (-14%)** | +| fstream 符号 | 有 | **0** | 彻底消除 | +| `std::stoi` 调用 | 多处 | **0** | 全部替换 | +| GTest 通过 | 353 | 353 | 不变 | +| 编译 warning | 0 | 0 | 不变 | +| Applet 数量 | 115 | 115 | 不变 | + +## 新增 Applet(6 个) + +`chgrp`, `chmod`, `chown`, `clear`, `mountpoint`, `which` + +## 变更详情 + +### Phase 1 — 零风险编译选项与基础设施 + +- 全局启用 `-fno-exceptions -fno-rtti` +- 新增 `-Wformat-signedness` `-Wstrict-aliasing` 编译警告 +- 修复 `install.cpp` 唯一 `try/catch` → `strtoul` + errno +- 修复多处 `%d` / `%u` 格式不匹配(unsigned 值配 `%d`) + +### Phase 2 — 明确 Bug 修复 + +- `dmesg.cpp`:FILE* 泄漏 → `io::unique_file` RAII +- `init.hpp`:信号标志 `bool` → `volatile sig_atomic_t` + +### Phase 3 — std::string / string_view 清理 + +- 消除多处循环内不必要的 `std::string{string_view}` 构造 +- 涉及:`term.hpp`, `grep.cpp`, `io.hpp`, `test.cpp`, `mkdir.cpp`, `ls.cpp` + +### Phase 4 — 统一错误报告宏 + +- 定义 `CFBOX_ERR(cmd, fmt, ...)` / `CFBOX_ERR_V(cmd, fmt, ...)` 宏 +- 102 个 applet 全量迁移 `fprintf(stderr, "cfbox xxx: ...")` → 宏 + +### Phase 5 — fs_util 扩展 + +- `fs_util.hpp` 新增 `chown()` / `lchown()` / `for_each_entry()` +- 重构 `chown.cpp`, `chgrp.cpp`, `chmod.cpp` 使用新 API,消除重复递归遍历代码 + +### Phase 6 — iostream 清理 + +- `hexdump.cpp`, `more.cpp`:移除未使用的 `` +- `rev.cpp`:`istream` → `FILE*` + `fgets` +- `pmap.cpp`:`ifstream` / `istringstream` → `FILE*` + `fgets` + `sscanf` +- `sysctl.cpp`:`ifstream` / `ofstream` → `FILE*` + `fgets` / `fprintf` +- `proc.hpp`:8 处 `ifstream` / `istringstream` / `ostringstream` 全部转为 `` +- `io.hpp`:`read_all()` 修复 `/proc` 虚拟文件 `ftell` 返回 0 的问题 + +### Phase 7 — stoi 安全化 + I/O 审计 + +- `cut.cpp`:5 处 `std::stoi` → `std::strtol` +- `fuser.cpp`:`std::stoi` → `std::strtol` +- D3 I/O 审计:现有 `read_all()` 调用者均需全量数据,无需流式化 + +## 新增基础设施 + +| 组件 | 说明 | +|------|------| +| `CFBOX_ERR` / `CFBOX_ERR_V` | 统一错误输出宏(Phase 4) | +| `fs::chown()` / `fs::lchown()` | chown 系统调用封装(Phase 5) | +| `fs::for_each_entry()` | 递归/非递归目录遍历模板(Phase 5) | + +## 本轮额外完成 + +- **Phase 9:测试覆盖补全** — 为 `cat`, `echo`, `head`, `tail`, `wc` 补 GTest 单元测试(+26 测试,379 全绿) +- **CI ccache 加速** — GitHub Actions 全部构建作业配置 ccache + 缓存 + +--- + +**完整变更记录**:https://github.com/Awesome-Embedded-Learning-Studio/cfbox/compare/v0.1.0...v0.2.0 + +--- + +
+English Version + +# CFBox v0.2.0 — Optimization & Updates + +> Comprehensive code quality optimization: 14% binary size reduction, iostream runtime dependency eliminated, potential abort risks fixed. + +## Highlights + +- **14% size reduction**: size-opt Release shrinks from 473 KB to **406 KB** +- **iostream dependency fully removed**: all `` / `` / `` eliminated, zero fstream symbols remaining +- **All `std::stoi` replaced with `std::strtol`**: prevents `abort()` on parse failure under `-fno-exceptions` +- **353 tests passing**, zero compiler warnings, ASan zero leaks + +## Size Comparison + +| Metric | v0.1.0 | v0.2.0 | Change | +|--------|:------:|:------:|:------:| +| size-opt Release | 473 KB | **406 KB** | **-67 KB (-14%)** | +| fstream symbols | Yes | **0** | Eliminated | +| `std::stoi` calls | Multiple | **0** | All replaced | +| GTest passing | 353 | 353 | Unchanged | +| Compiler warnings | 0 | 0 | Unchanged | +| Applet count | 115 | 115 | Unchanged | + +## Additionally Completed in This Release + +- **Phase 9: Test coverage** — GTest tests for `cat`, `echo`, `head`, `tail`, `wc` (+26 tests, 379 all passing) +- **CI ccache** — ccache + cache configured for all GitHub Actions build jobs + +## New Applets (6) + +`chgrp`, `chmod`, `chown`, `clear`, `mountpoint`, `which` +## Changes + +### Phase 1 — Compiler Flags & Infrastructure + +- Globally enabled `-fno-exceptions -fno-rtti` +- Added `-Wformat-signedness` `-Wstrict-aliasing` warning flags +- Fixed sole `try/catch` in `install.cpp` → `strtoul` + errno +- Fixed multiple `%d` / `%u` format mismatches + +### Phase 2 — Bug Fixes + +- `dmesg.cpp`: FILE* leak → `io::unique_file` RAII +- `init.hpp`: signal flags `bool` → `volatile sig_atomic_t` + +### Phase 3 — std::string / string_view Cleanup + +- Eliminated unnecessary `std::string{string_view}` constructions in hot paths +- Files: `term.hpp`, `grep.cpp`, `io.hpp`, `test.cpp`, `mkdir.cpp`, `ls.cpp` + +### Phase 4 — Unified Error Reporting Macros + +- Defined `CFBOX_ERR(cmd, fmt, ...)` / `CFBOX_ERR_V(cmd, fmt, ...)` macros +- Migrated all 102 applets from `fprintf(stderr, "cfbox xxx: ...")` to macros + +### Phase 5 — fs_util Extensions + +- Added `chown()` / `lchown()` / `for_each_entry()` to `fs_util.hpp` +- Refactored `chown.cpp`, `chgrp.cpp`, `chmod.cpp` to use new APIs + +### Phase 6 — iostream Cleanup + +- Removed unused `` from `hexdump.cpp`, `more.cpp` +- Converted `rev.cpp`, `pmap.cpp`, `sysctl.cpp` from `ifstream`/`ofstream` to `FILE*` + `` +- Converted all 8 `ifstream`/`istringstream`/`ostringstream` usages in `proc.hpp` to `` +- Fixed `io::read_all()` for `/proc` virtual files (ftell returns 0) + +### Phase 7 — stoi Safety & I/O Audit + +- Replaced `std::stoi` with `std::strtol` in `cut.cpp` (5 sites) and `fuser.cpp` +- I/O audit: all `read_all()` callers require full data; no safe streaming conversions found + +## New Infrastructure + +| Component | Description | +|-----------|-------------| +| `CFBOX_ERR` / `CFBOX_ERR_V` | Unified error output macros (Phase 4) | +| `fs::chown()` / `fs::lchown()` | chown syscall wrappers (Phase 5) | +| `fs::for_each_entry()` | Recursive/non-recursive directory traversal template (Phase 5) | + +--- + +**Full Changelog**: https://github.com/Awesome-Embedded-Learning-Studio/cfbox/compare/v0.1.0...v0.2.0 + +--- + +## 下一步:Phase 2 — 核心命令深化(v0.3.0 规划) + +将现有命令功能深度从 ~30% 提升到 ~70%,按运维频率分批推进。 + +### 第一批(v0.3.0) + +| 命令 | 关键补充 | 优先级 | +|------|---------|--------| +| `tail` | `-f` follow 模式(inotify/polling) | P0 | +| `cp` | `-a` archive 模式(保留权限/链接/时间戳/递归) | P0 | +| `test` | 全面 POSIX:`-a`/`-o`/字符串比较/文件类型/`-nt`/`-ot` | P0 | +| `ls` | `-R` 递归 + `--color` 彩色输出 | P1 | + +### 第二批(v0.4.0) + +| 命令 | 关键补充 | +|------|---------| +| `grep` | `-A`/`-B`/`-C` 上下文行、`--include`/`--exclude` | +| `tar` | `-z` gzip、`-v` verbose、`-C` 目录切换 | +| `sed` | `-i` 原地编辑、多命令 `-e` | +| `sort` | `-k` 按列排序、`-n` 数值排序、`-u` 去重 | + +### 第三批(v0.5.0) + +| 命令 | 关键补充 | +|------|---------| +| `find` | 布尔表达式(`-and`/`-or`/`!`)、`-type f`/`-name` | +| `sh` | `case`/`heredoc`/函数/`return` | +| `ps` | `-aux`/`-ef`、线程显示 | +| `df`/`du` | `-h` 人类可读、`-T` 文件系统类型 | + +
diff --git a/document/todo/README.md b/document/todo/README.md index e046ae1..23a2551 100644 --- a/document/todo/README.md +++ b/document/todo/README.md @@ -6,14 +6,14 @@ | 项目 | 状态 | |------|------| -| 版本 | v0.1.0 | -| Applet | 109 个 | -| 体积 | 约 446 KB(size-opt, LTO + strip) | -| 测试 | 331 个 GTest 单元测试 + 56 个集成脚本 | -| 已完成阶段 | 历史 Phase 0-4:构建、POSIX-like shell、coreutils、归档压缩、文本处理、procps、init | -| **当前阶段** | **Phase 1:P0 系统命令补齐**(从 `clear` 开始) | -| 主要缺口 | 权限/挂载/`dd`、网络、登录管理、系统日志、核心命令功能深度、生产发布工程 | -| 执行策略 | Phase 0-lite 并行推进,不阻塞新功能 | +| 版本 | v0.2.0 | +| Applet | 115 个 | +| 体积 | 406 KB(size-opt, LTO + strip, `-fno-exceptions -fno-rtti`) | +| 测试 | 379 个 GTest 单元测试 + 56 个集成脚本 | +| 已完成阶段 | Phase 1 Wave 1(6 新 applet)+ Phase 1.5 代码质量审查(全部通过) | +| **当前阶段** | **Phase 2:核心命令深化**(tail -f、cp -a、test POSIX、ls -R/--color) | +| 主要缺口 | 核心命令功能深度、网络、登录管理、系统日志、生产发布工程 | +| 执行策略 | 按运维频率分批深化,每批发版 | ## 文档索引 diff --git a/document/todo/phases/phase-1.5-code-quality-review.md b/document/todo/phases/phase-1.5-code-quality-review.md index 774b170..f47a0c6 100644 --- a/document/todo/phases/phase-1.5-code-quality-review.md +++ b/document/todo/phases/phase-1.5-code-quality-review.md @@ -1,4 +1,7 @@ -# Phase 1.5: 代码质量审查 +# Phase 1.5: 代码质量审查 — **已完成** + +> **完成日期**:2026-05-26 +> **结果**:全部退出条件满足。体积从 473 KB 降至 406 KB(-14%),379 GTest 全绿,零 warning,零 fstream 残留。详见 `changelogs/v0.2.0.md`。 ## 概述 diff --git a/include/cfbox/fs_util.hpp b/include/cfbox/fs_util.hpp index 8ba7c82..68cef56 100644 --- a/include/cfbox/fs_util.hpp +++ b/include/cfbox/fs_util.hpp @@ -1,10 +1,13 @@ #pragma once +#include #include +#include #include #include #include #include +#include #include @@ -239,4 +242,30 @@ inline auto space(std::string_view path) -> base::Result base::Result { + if (::chown(std::string{path}.c_str(), uid, gid) != 0) { + return std::unexpected(base::Error{errno, std::strerror(errno)}); + } + return {}; +} + +inline auto lchown(std::string_view path, uid_t uid, gid_t gid) -> base::Result { + if (::lchown(std::string{path}.c_str(), uid, gid) != 0) { + return std::unexpected(base::Error{errno, std::strerror(errno)}); + } + return {}; +} + +template +void for_each_entry(std::string_view path, bool recursive, Func&& fn) { + if (recursive && is_directory(path)) { + std::error_code ec; + for (const auto& entry : std::filesystem::recursive_directory_iterator(std::filesystem::path{path}, ec)) { + if (ec) continue; + fn(entry.path().string()); + } + } + fn(std::string{path}); +} + } // namespace cfbox::fs diff --git a/include/cfbox/io.hpp b/include/cfbox/io.hpp index 8fff3c6..d03cc00 100644 --- a/include/cfbox/io.hpp +++ b/include/cfbox/io.hpp @@ -13,12 +13,14 @@ namespace cfbox::io { struct FileCloser { void operator()(std::FILE* f) const noexcept { - if (f) std::fclose(f); + if (f) + std::fclose(f); } }; using unique_file = std::unique_ptr; -[[nodiscard]] inline auto open_file(std::string_view path, const char* mode) -> base::Result { +[[nodiscard]] inline auto open_file(std::string_view path, const char* mode) + -> base::Result { auto* f = std::fopen(path.data(), mode); if (!f) { return std::unexpected(base::Error{errno, "cannot open file: " + std::string{path}}); @@ -31,11 +33,21 @@ using unique_file = std::unique_ptr; std::fseek(f->get(), 0, SEEK_END); long size = std::ftell(f->get()); - std::fseek(f->get(), 0, SEEK_SET); + if (size > 0) { + std::fseek(f->get(), 0, SEEK_SET); + std::string content(static_cast(size), '\0'); + auto nread = std::fread(content.data(), 1, content.size(), f->get()); + content.resize(nread); + return content; + } - std::string content(static_cast(size), '\0'); - auto nread = std::fread(content.data(), 1, content.size(), f->get()); - content.resize(nread); + // Virtual files (e.g. /proc) may report 0 size; fall back to incremental read + std::fseek(f->get(), 0, SEEK_SET); + std::string content; + char buf[4096]; + while (auto n = std::fread(buf, 1, sizeof(buf), f->get())) { + content.append(buf, n); + } return content; } @@ -50,10 +62,10 @@ using unique_file = std::unique_ptr; [[nodiscard]] inline auto split_lines(std::string_view content) -> std::vector { std::vector lines; - if (content.empty()) return lines; + if (content.empty()) + return lines; - auto nl = static_cast( - std::count(content.begin(), content.end(), '\n')); + auto nl = static_cast(std::count(content.begin(), content.end(), '\n')); lines.reserve(nl + 1); std::size_t start = 0; @@ -69,10 +81,12 @@ using unique_file = std::unique_ptr; return lines; } -[[nodiscard]] inline auto read_lines(std::string_view path) -> base::Result> { +[[nodiscard]] inline auto read_lines(std::string_view path) + -> base::Result> { CFBOX_TRY(content, read_all(path)); auto lines = split_lines(*content); - if (content->empty()) lines.emplace_back(); + if (content->empty()) + lines.emplace_back(); return lines; } @@ -85,15 +99,15 @@ inline auto write_all(std::string_view path, std::string_view data) -> base::Res return {}; } -template -auto for_each_line(std::FILE* f, Fn&& fn) -> base::Result { +template auto for_each_line(std::FILE* f, Fn&& fn) -> base::Result { std::string line; line.reserve(256); int ch; while ((ch = std::fgetc(f)) != EOF) { if (ch == '\n') { if constexpr (std::is_invocable_r_v) { - if (!fn(line)) return {}; + if (!fn(line)) + return {}; } else { fn(line); } @@ -115,8 +129,7 @@ auto for_each_line(std::FILE* f, Fn&& fn) -> base::Result { return {}; } -template -auto for_each_line(std::string_view path, Fn&& fn) -> base::Result { +template auto for_each_line(std::string_view path, Fn&& fn) -> base::Result { if (path == "-") { return for_each_line(stdin, std::forward(fn)); } diff --git a/include/cfbox/proc.hpp b/include/cfbox/proc.hpp index c45dbc7..02e1c61 100644 --- a/include/cfbox/proc.hpp +++ b/include/cfbox/proc.hpp @@ -1,17 +1,17 @@ #pragma once #include +#include #include +#include #include #include -#include -#include -#include -#include #include #include +#include #include +#include namespace cfbox::proc { @@ -27,8 +27,8 @@ inline auto page_size() noexcept -> long { } inline auto total_memory_kb() noexcept -> std::uint64_t { - static std::uint64_t mem = static_cast(sysconf(_SC_PHYS_PAGES)) - * static_cast(sysconf(_SC_PAGE_SIZE)) / 1024; + static std::uint64_t mem = static_cast(sysconf(_SC_PHYS_PAGES)) * + static_cast(sysconf(_SC_PAGE_SIZE)) / 1024; return mem; } @@ -46,31 +46,44 @@ struct MemInfo { }; inline auto read_meminfo() -> base::Result { - std::ifstream f("/proc/meminfo"); - if (!f) return std::unexpected(base::Error{1, "cannot open /proc/meminfo"}); + auto* f = std::fopen("/proc/meminfo", "r"); + if (!f) + return std::unexpected(base::Error{1, "cannot open /proc/meminfo"}); MemInfo mi{}; - std::string line; - while (std::getline(f, line)) { - auto colon = line.find(':'); - if (colon == std::string::npos) continue; - auto key = line.substr(0, colon); - // Parse number after colon - auto p = line.c_str() + colon + 1; - while (*p == ' ') ++p; + char line[256]; + while (std::fgets(line, sizeof(line), f)) { + auto colon = std::strchr(line, ':'); + if (!colon) + continue; + *colon = '\0'; + auto* p = colon + 1; + while (*p == ' ') + ++p; std::uint64_t val = 0; std::sscanf(p, "%llu", reinterpret_cast(&val)); - if (key == "MemTotal") mi.total = val; - else if (key == "MemFree") mi.free = val; - else if (key == "MemAvailable") mi.available = val; - else if (key == "Buffers") mi.buffers = val; - else if (key == "Cached") mi.cached = val; - else if (key == "SwapTotal") mi.swap_total = val; - else if (key == "SwapFree") mi.swap_free = val; - else if (key == "Shmem") mi.shmem = val; - else if (key == "SReclaimable") mi.s_reclaimable = val; + auto key = line; + if (std::strcmp(key, "MemTotal") == 0) + mi.total = val; + else if (std::strcmp(key, "MemFree") == 0) + mi.free = val; + else if (std::strcmp(key, "MemAvailable") == 0) + mi.available = val; + else if (std::strcmp(key, "Buffers") == 0) + mi.buffers = val; + else if (std::strcmp(key, "Cached") == 0) + mi.cached = val; + else if (std::strcmp(key, "SwapTotal") == 0) + mi.swap_total = val; + else if (std::strcmp(key, "SwapFree") == 0) + mi.swap_free = val; + else if (std::strcmp(key, "Shmem") == 0) + mi.shmem = val; + else if (std::strcmp(key, "SReclaimable") == 0) + mi.s_reclaimable = val; } + std::fclose(f); // Adjust cached to include SReclaimable (like procps does) mi.cached += mi.s_reclaimable; @@ -85,24 +98,32 @@ struct CpuStats { auto total() const noexcept -> std::uint64_t { return user + nice + system + idle + iowait + irq + softirq + steal; } - auto idle_time() const noexcept -> std::uint64_t { - return idle + iowait; - } + auto idle_time() const noexcept -> std::uint64_t { return idle + iowait; } }; inline auto read_cpu_stats() -> base::Result { - std::ifstream f("/proc/stat"); - if (!f) return std::unexpected(base::Error{1, "cannot open /proc/stat"}); + auto* f = std::fopen("/proc/stat", "r"); + if (!f) + return std::unexpected(base::Error{1, "cannot open /proc/stat"}); - std::string line; - if (!std::getline(f, line) || line.substr(0, 4) != "cpu ") + char line[512]; + if (!std::fgets(line, sizeof(line), f) || std::strncmp(line, "cpu ", 4) != 0) { + std::fclose(f); return std::unexpected(base::Error{1, "unexpected /proc/stat format"}); + } + std::fclose(f); CpuStats cs{}; // "cpu user nice system idle iowait irq softirq steal [guest guest_nice]" - std::istringstream iss(line.substr(5)); - iss >> cs.user >> cs.nice >> cs.system >> cs.idle - >> cs.iowait >> cs.irq >> cs.softirq >> cs.steal; + std::sscanf(line + 5, "%llu %llu %llu %llu %llu %llu %llu %llu", + reinterpret_cast(&cs.user), + reinterpret_cast(&cs.nice), + reinterpret_cast(&cs.system), + reinterpret_cast(&cs.idle), + reinterpret_cast(&cs.iowait), + reinterpret_cast(&cs.irq), + reinterpret_cast(&cs.softirq), + reinterpret_cast(&cs.steal)); return cs; } @@ -114,10 +135,10 @@ struct ProcessInfo { pid_t ppid = 0; int priority = 0; int nice_val = 0; - std::uint64_t vsize = 0; // bytes - std::uint64_t rss = 0; // pages - std::uint64_t utime = 0; // clock ticks - std::uint64_t stime = 0; // clock ticks + std::uint64_t vsize = 0; // bytes + std::uint64_t rss = 0; // pages + std::uint64_t utime = 0; // clock ticks + std::uint64_t stime = 0; // clock ticks std::uint64_t start_time = 0; // clock ticks since boot uid_t uid = static_cast(-1); gid_t gid = static_cast(-1); @@ -127,42 +148,51 @@ struct ProcessInfo { namespace detail { -inline auto read_file_str(std::string_view path) -> std::string { - auto p = std::string(path); - std::ifstream f{p}; - if (!f) return {}; - std::ostringstream ss; - ss << f.rdbuf(); - return ss.str(); -} - inline auto parse_uid_gid(std::string_view path, uid_t& uid, gid_t& gid) -> void { auto p = std::string(path); - std::ifstream f{p}; - if (!f) return; - std::string line; - while (std::getline(f, line)) { - if (line.starts_with("Uid:")) { - std::sscanf(line.c_str() + 4, " %u", &uid); - } else if (line.starts_with("Gid:")) { - std::sscanf(line.c_str() + 4, " %u", &gid); + auto* f = std::fopen(p.c_str(), "r"); + if (!f) + return; + char line[256]; + while (std::fgets(line, sizeof(line), f)) { + if (std::strncmp(line, "Uid:", 4) == 0) { + std::sscanf(line + 4, " %u", &uid); + } else if (std::strncmp(line, "Gid:", 4) == 0) { + std::sscanf(line + 4, " %u", &gid); } } + std::fclose(f); } inline auto parse_cmdline(std::string_view path) -> std::vector { auto p = std::string(path); - std::ifstream f{p, std::ios::binary}; - if (!f) return {}; - std::string data((std::istreambuf_iterator(f)), - std::istreambuf_iterator()); - if (data.empty()) return {}; + auto* f = std::fopen(p.c_str(), "rb"); + if (!f) + return {}; + + // Read entire file + std::fseek(f, 0, SEEK_END); + auto sz = std::ftell(f); + std::fseek(f, 0, SEEK_SET); + if (sz <= 0) { + std::fclose(f); + return {}; + } + + std::string data(static_cast(sz), '\0'); + auto nread = std::fread(data.data(), 1, data.size(), f); + std::fclose(f); + data.resize(nread); + + if (data.empty()) + return {}; std::vector args; std::string::size_type start = 0; for (std::string::size_type i = 0; i <= data.size(); ++i) { if (i == data.size() || data[i] == '\0') { - if (i > start) args.emplace_back(data.substr(start, i - start)); + if (i > start) + args.emplace_back(data.substr(start, i - start)); start = i + 1; } } @@ -173,11 +203,14 @@ inline auto tty_from_dev(std::uint64_t tty_nr) -> std::string { int major = static_cast(tty_nr >> 8); int minor = static_cast(tty_nr & 0xFF); if (major == 4) { - if (minor < 64) return "tty" + std::to_string(minor); + if (minor < 64) + return "tty" + std::to_string(minor); return "pts/" + std::to_string(minor - 64 > 255 ? minor : minor - 64); } - if (major == 136) return "pts/" + std::to_string(minor); - if (major == 5 && minor == 0) return "tty"; + if (major == 136) + return "pts/" + std::to_string(minor); + if (major == 5 && minor == 0) + return "tty"; return "?"; } @@ -189,14 +222,16 @@ inline auto read_process(pid_t pid) -> base::Result { // Parse /proc/[pid]/stat auto stat_path = "/proc/" + std::to_string(pid) + "/stat"; - auto content = detail::read_file_str(stat_path); - if (content.empty()) + auto content_result = cfbox::io::read_all(stat_path); + if (!content_result) return std::unexpected(base::Error{1, "cannot read " + stat_path}); + auto& content = *content_result; // comm is in parentheses and may contain spaces — find last ')' auto open_paren = content.find('('); auto close_paren = content.rfind(')'); - if (open_paren == std::string::npos || close_paren == std::string::npos || close_paren <= open_paren) + if (open_paren == std::string::npos || close_paren == std::string::npos || + close_paren <= open_paren) return std::unexpected(base::Error{1, "unexpected /proc/[pid]/stat format"}); pi.comm = content.substr(open_paren + 1, close_paren - open_paren - 1); @@ -208,84 +243,113 @@ inline auto read_process(pid_t pid) -> base::Result { // field 3: state (char) pi.state = *p; ++p; // skip state char - if (*p == ' ') ++p; + if (*p == ' ') + ++p; // field 4: ppid pi.ppid = static_cast(std::strtoull(p, nullptr, 10)); - while (*p && *p != ' ') ++p; - if (*p == ' ') ++p; + while (*p && *p != ' ') + ++p; + if (*p == ' ') + ++p; // field 5: pgrp (skip) std::strtoull(p, nullptr, 10); - while (*p && *p != ' ') ++p; - if (*p == ' ') ++p; + while (*p && *p != ' ') + ++p; + if (*p == ' ') + ++p; // field 6: session (skip) std::strtoull(p, nullptr, 10); - while (*p && *p != ' ') ++p; - if (*p == ' ') ++p; + while (*p && *p != ' ') + ++p; + if (*p == ' ') + ++p; // field 7: tty_nr std::uint64_t tty_nr = std::strtoull(p, nullptr, 10); pi.tty = detail::tty_from_dev(tty_nr); - while (*p && *p != ' ') ++p; - if (*p == ' ') ++p; + while (*p && *p != ' ') + ++p; + if (*p == ' ') + ++p; // field 8: tpgid (skip) std::strtoull(p, nullptr, 10); - while (*p && *p != ' ') ++p; - if (*p == ' ') ++p; + while (*p && *p != ' ') + ++p; + if (*p == ' ') + ++p; // fields 9-13: flags, minflt, cminflt, majflt, cmajflt (skip 5 fields) for (int i = 0; i < 5; ++i) { std::strtoull(p, nullptr, 10); - while (*p && *p != ' ') ++p; - if (*p == ' ') ++p; + while (*p && *p != ' ') + ++p; + if (*p == ' ') + ++p; } // field 14: utime pi.utime = std::strtoull(p, nullptr, 10); - while (*p && *p != ' ') ++p; - if (*p == ' ') ++p; + while (*p && *p != ' ') + ++p; + if (*p == ' ') + ++p; // field 15: stime pi.stime = std::strtoull(p, nullptr, 10); - while (*p && *p != ' ') ++p; - if (*p == ' ') ++p; + while (*p && *p != ' ') + ++p; + if (*p == ' ') + ++p; // field 16-17: cutime, cstime (skip) for (int i = 0; i < 2; ++i) { std::strtoull(p, nullptr, 10); - while (*p && *p != ' ') ++p; - if (*p == ' ') ++p; + while (*p && *p != ' ') + ++p; + if (*p == ' ') + ++p; } // field 18: priority pi.priority = static_cast(std::strtol(p, nullptr, 10)); - while (*p && *p != ' ') ++p; - if (*p == ' ') ++p; + while (*p && *p != ' ') + ++p; + if (*p == ' ') + ++p; // field 19: nice pi.nice_val = static_cast(std::strtol(p, nullptr, 10)); - while (*p && *p != ' ') ++p; - if (*p == ' ') ++p; + while (*p && *p != ' ') + ++p; + if (*p == ' ') + ++p; // fields 20-21: num_threads, itrealvalue (skip) for (int i = 0; i < 2; ++i) { std::strtoull(p, nullptr, 10); - while (*p && *p != ' ') ++p; - if (*p == ' ') ++p; + while (*p && *p != ' ') + ++p; + if (*p == ' ') + ++p; } // field 22: starttime pi.start_time = std::strtoull(p, nullptr, 10); - while (*p && *p != ' ') ++p; - if (*p == ' ') ++p; + while (*p && *p != ' ') + ++p; + if (*p == ' ') + ++p; // field 23: vsize (bytes) pi.vsize = std::strtoull(p, nullptr, 10); - while (*p && *p != ' ') ++p; - if (*p == ' ') ++p; + while (*p && *p != ' ') + ++p; + if (*p == ' ') + ++p; // field 24: rss (pages) pi.rss = std::strtoull(p, nullptr, 10); @@ -305,13 +369,16 @@ inline auto read_all_processes() -> base::Result> { std::vector procs; std::error_code ec; for (const auto& entry : std::filesystem::directory_iterator("/proc", ec)) { - if (!entry.is_directory()) continue; + if (!entry.is_directory()) + continue; auto name = entry.path().filename().string(); // Check if directory name is numeric (PID) - if (name.empty() || name[0] < '0' || name[0] > '9') continue; - pid_t pid = static_cast(std::stoi(name)); + if (name.empty() || name[0] < '0' || name[0] > '9') + continue; + pid_t pid = static_cast(std::strtol(name.c_str(), nullptr, 10)); auto result = read_process(pid); - if (result) procs.push_back(std::move(*result)); + if (result) + procs.push_back(std::move(*result)); } return procs; } @@ -324,34 +391,36 @@ struct LoadAvg { }; inline auto read_loadavg() -> base::Result { - auto content = detail::read_file_str("/proc/loadavg"); - if (content.empty()) + auto content_result = cfbox::io::read_all("/proc/loadavg"); + if (!content_result) return std::unexpected(base::Error{1, "cannot read /proc/loadavg"}); LoadAvg la{}; - std::sscanf(content.c_str(), "%lf %lf %lf %d/%d %d", - &la.avg1, &la.avg5, &la.avg15, &la.running, &la.total, reinterpret_cast(&la.last_pid)); + std::sscanf(content_result->c_str(), "%lf %lf %lf %d/%d %d", &la.avg1, &la.avg5, &la.avg15, + &la.running, &la.total, reinterpret_cast(&la.last_pid)); return la; } // --- /proc/uptime --- inline auto read_uptime() -> base::Result> { - auto content = detail::read_file_str("/proc/uptime"); - if (content.empty()) + auto content_result = cfbox::io::read_all("/proc/uptime"); + if (!content_result) return std::unexpected(base::Error{1, "cannot read /proc/uptime"}); double up = 0, idle = 0; - std::sscanf(content.c_str(), "%lf %lf", &up, &idle); + std::sscanf(content_result->c_str(), "%lf %lf", &up, &idle); return std::make_pair(up, idle); } // --- /proc/version --- inline auto read_version() -> base::Result { - auto content = detail::read_file_str("/proc/version"); - if (content.empty()) + auto content_result = cfbox::io::read_all("/proc/version"); + if (!content_result) return std::unexpected(base::Error{1, "cannot read /proc/version"}); + auto& content = *content_result; // Trim trailing newline - while (!content.empty() && content.back() == '\n') content.pop_back(); + while (!content.empty() && content.back() == '\n') + content.pop_back(); return content; } @@ -364,19 +433,26 @@ struct MountEntry { }; inline auto read_mounts() -> base::Result> { - std::ifstream f("/proc/mounts"); - if (!f) return std::unexpected(base::Error{1, "cannot open /proc/mounts"}); + auto* f = std::fopen("/proc/mounts", "r"); + if (!f) + return std::unexpected(base::Error{1, "cannot open /proc/mounts"}); std::vector mounts; - std::string line; - while (std::getline(f, line)) { - if (line.empty()) continue; - // device mountpoint fstype options freq passno + char line[1024]; + while (std::fgets(line, sizeof(line), f)) { + if (line[0] == '\n') + continue; MountEntry me; - std::istringstream iss(line); - iss >> me.device >> me.mountpoint >> me.fstype >> me.options; - if (!me.device.empty()) mounts.push_back(std::move(me)); + char dev[256], mp[256], fs[256], opts[256]; + if (std::sscanf(line, "%255s %255s %255s %255s", dev, mp, fs, opts) == 4) { + me.device = dev; + me.mountpoint = mp; + me.fstype = fs; + me.options = opts; + mounts.push_back(std::move(me)); + } } + std::fclose(f); return mounts; } @@ -389,44 +465,61 @@ struct DiskStat { }; inline auto read_diskstats() -> base::Result> { - std::ifstream f("/proc/diskstats"); - if (!f) return std::unexpected(base::Error{1, "cannot open /proc/diskstats"}); + auto* f = std::fopen("/proc/diskstats", "r"); + if (!f) + return std::unexpected(base::Error{1, "cannot open /proc/diskstats"}); std::vector stats; - std::string line; - while (std::getline(f, line)) { - if (line.empty()) continue; + char line[512]; + while (std::fgets(line, sizeof(line), f)) { + if (line[0] == '\n') + continue; DiskStat ds; - std::istringstream iss(line); - std::string maj_str, min_str; - iss >> maj_str >> min_str >> ds.device - >> ds.reads >> ds.reads_merged >> ds.sectors_read >> ds.ms_reading - >> ds.writes >> ds.writes_merged >> ds.sectors_written >> ds.ms_writing - >> ds.ios_in_progress >> ds.ms_ios >> ds.weighted_ms_ios; - - if (!ds.device.empty()) stats.push_back(std::move(ds)); + char dev[64]; + auto n = + std::sscanf(line, "%*d %*d %63s %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu", + dev, reinterpret_cast(&ds.reads), + reinterpret_cast(&ds.reads_merged), + reinterpret_cast(&ds.sectors_read), + reinterpret_cast(&ds.ms_reading), + reinterpret_cast(&ds.writes), + reinterpret_cast(&ds.writes_merged), + reinterpret_cast(&ds.sectors_written), + reinterpret_cast(&ds.ms_writing), + reinterpret_cast(&ds.ios_in_progress), + reinterpret_cast(&ds.ms_ios), + reinterpret_cast(&ds.weighted_ms_ios)); + if (n >= 1) { + ds.device = dev; + stats.push_back(std::move(ds)); + } } + std::fclose(f); return stats; } // --- /proc/partitions --- inline auto read_partitions() -> base::Result> { - std::ifstream f("/proc/partitions"); - if (!f) return std::unexpected(base::Error{1, "cannot open /proc/partitions"}); + auto* f = std::fopen("/proc/partitions", "r"); + if (!f) + return std::unexpected(base::Error{1, "cannot open /proc/partitions"}); std::vector parts; - std::string line; + char line[256]; // Skip header line - std::getline(f, line); - while (std::getline(f, line)) { - if (line.empty()) continue; - std::istringstream iss(line); + std::fgets(line, sizeof(line), f); + while (std::fgets(line, sizeof(line), f)) { + if (line[0] == '\n') + continue; int major, minor; std::uint64_t blocks; - std::string name; - iss >> major >> minor >> blocks >> name; - if (!name.empty()) parts.push_back(std::move(name)); + char name[64]; + if (std::sscanf(line, "%d %d %llu %63s", &major, &minor, + reinterpret_cast(&blocks), name) >= 4) { + parts.emplace_back(name); + } } + std::fclose(f); return parts; } diff --git a/src/applets/chgrp.cpp b/src/applets/chgrp.cpp index 5a7d4df..3c9dde6 100644 --- a/src/applets/chgrp.cpp +++ b/src/applets/chgrp.cpp @@ -1,12 +1,10 @@ -#include #include #include -#include #include #include -#include #include +#include #include #include @@ -52,8 +50,9 @@ auto chgrp_main(int argc, char* argv[]) -> int { } auto chgrp_one = [&](const std::string& path) -> int { - if (::chown(path.c_str(), static_cast(-1), gid) != 0) { - CFBOX_ERR("chgrp", "%s: %s", path.c_str(), std::strerror(errno)); + auto result = cfbox::fs::chown(path, static_cast(-1), gid); + if (!result) { + CFBOX_ERR("chgrp", "%s: %s", path.c_str(), result.error().msg.c_str()); return 1; } if (verbose) std::printf("group of '%s' changed\n", path.c_str()); @@ -62,15 +61,9 @@ auto chgrp_main(int argc, char* argv[]) -> int { int rc = 0; for (size_t i = 1; i < pos.size(); i++) { - std::string path(pos[i]); - if (recursive && std::filesystem::is_directory(path)) { - std::error_code ec; - for (const auto& entry : std::filesystem::recursive_directory_iterator(path, ec)) { - if (ec) continue; - if (chgrp_one(entry.path().string()) != 0) rc = 1; - } - } - if (chgrp_one(path) != 0) rc = 1; + cfbox::fs::for_each_entry(pos[i], recursive, [&](const std::string& p) { + if (chgrp_one(p) != 0) rc = 1; + }); } return rc; } diff --git a/src/applets/chmod.cpp b/src/applets/chmod.cpp index 94855a3..8d67d8c 100644 --- a/src/applets/chmod.cpp +++ b/src/applets/chmod.cpp @@ -157,15 +157,9 @@ auto chmod_main(int argc, char* argv[]) -> int { int rc = 0; for (size_t i = files_start; i < pos.size(); i++) { - std::string path(pos[i]); - if (recursive && cfbox::fs::is_directory(path)) { - std::error_code ec; - for (const auto& entry : std::filesystem::recursive_directory_iterator(path, ec)) { - if (ec) continue; - if (chmod_one(entry.path().string(), target_mode, verbose) != 0) rc = 1; - } - } - if (chmod_one(path, target_mode, verbose) != 0) rc = 1; + cfbox::fs::for_each_entry(pos[i], recursive, [&](const std::string& p) { + if (chmod_one(p, target_mode, verbose) != 0) rc = 1; + }); } return rc; } diff --git a/src/applets/chown.cpp b/src/applets/chown.cpp index 72cd0fc..76e817a 100644 --- a/src/applets/chown.cpp +++ b/src/applets/chown.cpp @@ -1,15 +1,14 @@ #include #include #include -#include #include #include #include #include #include -#include #include +#include #include #include @@ -66,8 +65,9 @@ auto parse_owner_spec(std::string_view spec) -> OwnerSpec { } auto chown_one(const std::string& path, uid_t uid, gid_t gid, bool verbose) -> int { - if (::chown(path.c_str(), uid, gid) != 0) { - CFBOX_ERR("chown", "%s: %s", path.c_str(), std::strerror(errno)); + auto result = cfbox::fs::chown(path, uid, gid); + if (!result) { + CFBOX_ERR("chown", "%s: %s", path.c_str(), result.error().msg.c_str()); return 1; } if (verbose) std::printf("ownership of '%s' changed\n", path.c_str()); @@ -125,21 +125,11 @@ auto chown_main(int argc, char* argv[]) -> int { int rc = 0; for (size_t i = files_start; i < pos.size(); i++) { - std::string path(pos[i]); - auto apply = [&](const std::string& p) { - uid_t uid = owner.set_uid ? owner.uid : static_cast(-1); - gid_t gid = owner.set_gid ? owner.gid : static_cast(-1); - return chown_one(p, uid, gid, verbose); - }; - - if (recursive && std::filesystem::is_directory(path)) { - std::error_code ec; - for (const auto& entry : std::filesystem::recursive_directory_iterator(path, ec)) { - if (ec) continue; - if (apply(entry.path().string()) != 0) rc = 1; - } - } - if (apply(path) != 0) rc = 1; + uid_t uid = owner.set_uid ? owner.uid : static_cast(-1); + gid_t gid = owner.set_gid ? owner.gid : static_cast(-1); + cfbox::fs::for_each_entry(pos[i], recursive, [&](const std::string& p) { + if (chown_one(p, uid, gid, verbose) != 0) rc = 1; + }); } return rc; } diff --git a/src/applets/cut.cpp b/src/applets/cut.cpp index 29400db..d868945 100644 --- a/src/applets/cut.cpp +++ b/src/applets/cut.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -24,6 +25,10 @@ constexpr cfbox::help::HelpEntry HELP = { }; } // namespace +static auto str_to_int(const std::string& s) -> int { + return static_cast(std::strtol(s.c_str(), nullptr, 10)); +} + static auto parse_range_list(const std::string& list) -> std::set { std::set fields; std::string token; @@ -31,16 +36,16 @@ static auto parse_range_list(const std::string& list) -> std::set { if (i == list.size() || list[i] == ',') { auto dash = token.find('-'); if (dash == std::string::npos) { - fields.insert(std::stoi(token)); + fields.insert(str_to_int(token)); } else if (dash == 0) { - int end = std::stoi(token.substr(1)); + int end = str_to_int(token.substr(1)); for (int j = 1; j <= end; ++j) fields.insert(j); } else if (dash == token.size() - 1) { - int start = std::stoi(token.substr(0, dash)); + int start = str_to_int(token.substr(0, dash)); for (int j = start; j <= 1024; ++j) fields.insert(j); } else { - int start = std::stoi(token.substr(0, dash)); - int end = std::stoi(token.substr(dash + 1)); + int start = str_to_int(token.substr(0, dash)); + int end = str_to_int(token.substr(dash + 1)); for (int j = start; j <= end; ++j) fields.insert(j); } token.clear(); diff --git a/src/applets/fuser.cpp b/src/applets/fuser.cpp index 95c5209..1caae8f 100644 --- a/src/applets/fuser.cpp +++ b/src/applets/fuser.cpp @@ -61,7 +61,7 @@ auto fuser_main(int argc, char* argv[]) -> int { auto name = proc_entry.path().filename().string(); if (name.empty() || name[0] < '0' || name[0] > '9') continue; - pid_t pid = static_cast(std::stoi(name)); + pid_t pid = static_cast(std::strtol(name.c_str(), nullptr, 10)); auto fd_dir = proc_entry.path() / "fd"; for (const auto& fd_entry : std::filesystem::directory_iterator(fd_dir, ec)) { diff --git a/src/applets/hexdump.cpp b/src/applets/hexdump.cpp index 29f203d..d1dcf2c 100644 --- a/src/applets/hexdump.cpp +++ b/src/applets/hexdump.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include #include diff --git a/src/applets/more.cpp b/src/applets/more.cpp index 45e2123..b5a5458 100644 --- a/src/applets/more.cpp +++ b/src/applets/more.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include diff --git a/src/applets/pmap.cpp b/src/applets/pmap.cpp index 44d19d6..2585a62 100644 --- a/src/applets/pmap.cpp +++ b/src/applets/pmap.cpp @@ -1,9 +1,9 @@ #include #include -#include -#include +#include #include #include +#include #include #include @@ -34,45 +34,62 @@ struct MapEntry { auto parse_maps(pid_t pid) -> std::vector { auto path = "/proc/" + std::to_string(pid) + "/maps"; - std::ifstream f(path); + auto* f = std::fopen(path.c_str(), "r"); if (!f) return {}; std::vector entries; - std::string line; - while (std::getline(f, line)) { - if (line.empty()) continue; - MapEntry e; - - auto dash = line.find('-'); - if (dash == std::string::npos) continue; - e.address = std::strtoull(line.substr(0, dash).c_str(), nullptr, 16); - - // Find the space after perms to get end_address - auto rest = line.substr(dash + 1); - std::istringstream iss(rest); - std::string perms_str; - iss >> perms_str; - e.end_address = e.address + 1; // Will be properly calculated from next line - e.perms = perms_str; - iss >> std::hex >> e.offset; - - std::string dev_str; - iss >> dev_str; - e.dev = dev_str; - - iss >> std::dec >> e.inode; - - // Remaining is pathname (may be empty) - std::string pn; - while (iss.peek() == ' ') iss.get(); - if (std::getline(iss, pn)) { - // Trim leading space - auto start = pn.find_first_not_of(' '); - if (start != std::string::npos) e.pathname = pn.substr(start); + char line[1024]; + while (std::fgets(line, sizeof(line), f)) { + auto len = std::strlen(line); + while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) { + line[--len] = '\0'; } + if (len == 0) continue; + + MapEntry e; + char perms[8] = {}; + char dev[16] = {}; + unsigned long long addr = 0; + unsigned long long off = 0; + unsigned long long ino = 0; + + auto n = std::sscanf(line, "%llx-%*x %7s %llx %15s %llu", + &addr, perms, &off, dev, &ino); + if (n < 1) continue; + + e.address = addr; + e.perms = perms; + e.offset = off; + e.dev = dev; + e.inode = ino; + + // Extract pathname: find the field after inode + const char* p = line; + // Skip address field + while (*p && *p != ' ') ++p; + // Skip to perms + while (*p == ' ') ++p; + // Skip perms + while (*p && *p != ' ') ++p; + // Skip to offset + while (*p == ' ') ++p; + // Skip offset + while (*p && *p != ' ') ++p; + // Skip to dev + while (*p == ' ') ++p; + // Skip dev + while (*p && *p != ' ') ++p; + // Skip to inode + while (*p == ' ') ++p; + // Skip inode + while (*p && *p != ' ') ++p; + // Skip spaces to pathname + while (*p == ' ') ++p; + if (*p) e.pathname = p; entries.push_back(std::move(e)); } + std::fclose(f); // Calculate end_address from next entry for (size_t i = 0; i + 1 < entries.size(); ++i) { @@ -99,7 +116,7 @@ auto pmap_main(int argc, char* argv[]) -> int { return 1; } - pid_t pid = static_cast(std::stoi(std::string(args[0]))); + pid_t pid = static_cast(std::strtol(args[0].data(), nullptr, 10)); auto entries = parse_maps(pid); if (entries.empty()) { CFBOX_ERR("pmap", "cannot read maps for PID %d", pid); diff --git a/src/applets/rev.cpp b/src/applets/rev.cpp index 77d021e..03fc998 100644 --- a/src/applets/rev.cpp +++ b/src/applets/rev.cpp @@ -1,7 +1,6 @@ #include #include -#include -#include +#include #include #include @@ -20,11 +19,15 @@ constexpr cfbox::help::HelpEntry HELP = { .extra = "", }; -auto process_stream(std::istream& in) -> void { - std::string line; - while (std::getline(in, line)) { - std::reverse(line.begin(), line.end()); - std::printf("%s\n", line.c_str()); +auto process_stream(std::FILE* f) -> void { + char buf[4096]; + while (std::fgets(buf, sizeof(buf), f)) { + auto len = std::strlen(buf); + while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) { + buf[--len] = '\0'; + } + std::reverse(buf, buf + len); + std::printf("%s\n", buf); } } @@ -37,7 +40,7 @@ auto rev_main(int argc, char* argv[]) -> int { const auto& pos = parsed.positional(); if (pos.empty()) { - process_stream(std::cin); + process_stream(stdin); return 0; } @@ -45,15 +48,16 @@ auto rev_main(int argc, char* argv[]) -> int { for (const auto& filename : pos) { auto fn = std::string(filename); if (fn == "-") { - process_stream(std::cin); + process_stream(stdin); } else { - std::ifstream f(fn); + auto* f = std::fopen(fn.c_str(), "r"); if (!f) { CFBOX_ERR("rev", "cannot open %s", fn.c_str()); rc = 1; continue; } process_stream(f); + std::fclose(f); } } return rc; diff --git a/src/applets/sysctl.cpp b/src/applets/sysctl.cpp index a492ce5..8c4e4f0 100644 --- a/src/applets/sysctl.cpp +++ b/src/applets/sysctl.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include @@ -46,18 +46,26 @@ auto path_to_key(std::string_view path) -> std::string { } auto read_sysctl_value(const std::string& path) -> std::string { - std::ifstream f(path); + auto* f = std::fopen(path.c_str(), "r"); if (!f) return {}; - std::string val; - std::getline(f, val); - return val; + char buf[4096]; + if (!std::fgets(buf, sizeof(buf), f)) { + std::fclose(f); + return {}; + } + std::fclose(f); + auto len = std::strlen(buf); + while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) { + buf[--len] = '\0'; + } + return buf; } auto write_sysctl_value(const std::string& path, std::string_view value) -> bool { - std::ofstream f(path, std::ios::trunc); + auto* f = std::fopen(path.c_str(), "w"); if (!f) return false; - f << value << "\n"; - return static_cast(f); + std::fprintf(f, "%.*s\n", static_cast(value.size()), value.data()); + return std::fclose(f) == 0; } auto show_key(std::string_view key, bool no_name) -> bool { @@ -82,21 +90,26 @@ auto show_all(bool no_name) -> void { } auto load_file(const std::string& filepath, bool no_name) -> int { - std::ifstream f(filepath); + auto* f = std::fopen(filepath.c_str(), "r"); if (!f) { CFBOX_ERR("sysctl", "cannot open %s", filepath.c_str()); return 1; } int errors = 0; - std::string line; - while (std::getline(f, line)) { + char line[4096]; + while (std::fgets(line, sizeof(line), f)) { + auto len = std::strlen(line); + while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) { + line[--len] = '\0'; + } // Skip comments and empty lines - if (line.empty() || line[0] == '#' || line[0] == ';') continue; - auto eq = line.find('='); - if (eq == std::string::npos) continue; - auto key = line.substr(0, eq); - auto val = line.substr(eq + 1); + if (len == 0 || line[0] == '#' || line[0] == ';') continue; + auto* eq = std::strchr(line, '='); + if (!eq) continue; + *eq = '\0'; + std::string key(line); + std::string val(eq + 1); // Trim whitespace while (!key.empty() && (key.back() == ' ' || key.back() == '\t')) key.pop_back(); while (!val.empty() && (val.front() == ' ' || val.front() == '\t')) val.erase(val.begin()); @@ -109,6 +122,7 @@ auto load_file(const std::string& filepath, bool no_name) -> int { std::printf("%s = %s\n", key.c_str(), val.c_str()); } } + std::fclose(f); return errors > 0 ? 1 : 0; } diff --git a/tests/unit/test_cat.cpp b/tests/unit/test_cat.cpp new file mode 100644 index 0000000..da973b9 --- /dev/null +++ b/tests/unit/test_cat.cpp @@ -0,0 +1,66 @@ +#include +#include +#include "test_capture.hpp" +#include + +#if CFBOX_ENABLE_CAT + +using namespace cfbox::test; + +TEST(CatTest, SingleFile) { + TempDir tmp; + tmp.write_file("hello.txt", "hello world\n"); + auto f = (tmp.path / "hello.txt").string(); + char a0[] = "cat", a1[256]; + std::snprintf(a1, sizeof(a1), "%s", f.c_str()); + char* argv[] = {a0, a1}; + auto out = capture_stdout([&]{ return cat_main(2, argv); }); + EXPECT_EQ(out, "hello world\n"); +} + +TEST(CatTest, MultipleFiles) { + TempDir tmp; + tmp.write_file("a.txt", "aaa\n"); + tmp.write_file("b.txt", "bbb\n"); + auto fa = (tmp.path / "a.txt").string(); + auto fb = (tmp.path / "b.txt").string(); + char a0[] = "cat", a1[256], a2[256]; + std::snprintf(a1, sizeof(a1), "%s", fa.c_str()); + std::snprintf(a2, sizeof(a2), "%s", fb.c_str()); + char* argv[] = {a0, a1, a2}; + auto out = capture_stdout([&]{ return cat_main(3, argv); }); + EXPECT_EQ(out, "aaa\nbbb\n"); +} + +TEST(CatTest, NumberAllLines) { + TempDir tmp; + tmp.write_file("num.txt", "one\n\ntwo\n"); + auto f = (tmp.path / "num.txt").string(); + char a0[] = "cat", a1[] = "-n", a2[256]; + std::snprintf(a2, sizeof(a2), "%s", f.c_str()); + char* argv[] = {a0, a1, a2}; + auto out = capture_stdout([&]{ return cat_main(3, argv); }); + EXPECT_NE(out.find("1\tone"), std::string::npos); + EXPECT_NE(out.find("2\t"), std::string::npos); + EXPECT_NE(out.find("3\ttwo"), std::string::npos); +} + +TEST(CatTest, NumberNonEmptyLines) { + TempDir tmp; + tmp.write_file("num.txt", "one\n\ntwo\n"); + auto f = (tmp.path / "num.txt").string(); + char a0[] = "cat", a1[] = "-b", a2[256]; + std::snprintf(a2, sizeof(a2), "%s", f.c_str()); + char* argv[] = {a0, a1, a2}; + auto out = capture_stdout([&]{ return cat_main(3, argv); }); + EXPECT_NE(out.find("1\tone"), std::string::npos); + EXPECT_NE(out.find("2\ttwo"), std::string::npos); +} + +TEST(CatTest, NonexistentFile) { + char a0[] = "cat", a1[] = "/no/such/file.txt"; + char* argv[] = {a0, a1}; + EXPECT_NE(cat_main(2, argv), 0); +} + +#endif // CFBOX_ENABLE_CAT diff --git a/tests/unit/test_echo.cpp b/tests/unit/test_echo.cpp new file mode 100644 index 0000000..8570565 --- /dev/null +++ b/tests/unit/test_echo.cpp @@ -0,0 +1,45 @@ +#include +#include +#include "test_capture.hpp" +#include + +#if CFBOX_ENABLE_ECHO + +using namespace cfbox::test; + +TEST(EchoTest, BasicOutput) { + char a0[] = "echo", a1[] = "hello", a2[] = "world"; + char* argv[] = {a0, a1, a2}; + auto out = capture_stdout([&]{ return echo_main(3, argv); }); + EXPECT_EQ(out, "hello world\n"); +} + +TEST(EchoTest, NoTrailingNewline) { + char a0[] = "echo", a1[] = "-n", a2[] = "no newline"; + char* argv[] = {a0, a1, a2}; + auto out = capture_stdout([&]{ return echo_main(3, argv); }); + EXPECT_EQ(out, "no newline"); +} + +TEST(EchoTest, NoArgs) { + char a0[] = "echo"; + char* argv[] = {a0}; + auto out = capture_stdout([&]{ return echo_main(1, argv); }); + EXPECT_EQ(out, "\n"); +} + +TEST(EchoTest, EscapeSequences) { + char a0[] = "echo", a1[] = "-e", a2[] = "a\\tb"; + char* argv[] = {a0, a1, a2}; + auto out = capture_stdout([&]{ return echo_main(3, argv); }); + EXPECT_EQ(out, "a\tb\n"); +} + +TEST(EchoTest, EscapeNewline) { + char a0[] = "echo", a1[] = "-e", a2[] = "line1\\nline2"; + char* argv[] = {a0, a1, a2}; + auto out = capture_stdout([&]{ return echo_main(3, argv); }); + EXPECT_EQ(out, "line1\nline2\n"); +} + +#endif // CFBOX_ENABLE_ECHO diff --git a/tests/unit/test_head.cpp b/tests/unit/test_head.cpp new file mode 100644 index 0000000..cdf8917 --- /dev/null +++ b/tests/unit/test_head.cpp @@ -0,0 +1,62 @@ +#include +#include +#include "test_capture.hpp" +#include + +#if CFBOX_ENABLE_HEAD + +using namespace cfbox::test; + +TEST(HeadTest, DefaultTenLines) { + TempDir tmp; + tmp.write_file("data.txt", "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n"); + auto f = (tmp.path / "data.txt").string(); + char a0[] = "head", a1[256]; + std::snprintf(a1, sizeof(a1), "%s", f.c_str()); + char* argv[] = {a0, a1}; + auto out = capture_stdout([&]{ return head_main(2, argv); }); + // Should have 10 lines, not 12 + auto count = std::count(out.begin(), out.end(), '\n'); + EXPECT_EQ(count, 10); +} + +TEST(HeadTest, NLines) { + TempDir tmp; + tmp.write_file("data.txt", "a\nb\nc\nd\ne\n"); + auto f = (tmp.path / "data.txt").string(); + char a0[] = "head", a1[] = "-n", a2[] = "3", a3[256]; + std::snprintf(a3, sizeof(a3), "%s", f.c_str()); + char* argv[] = {a0, a1, a2, a3}; + auto out = capture_stdout([&]{ return head_main(4, argv); }); + EXPECT_EQ(out, "a\nb\nc\n"); +} + +TEST(HeadTest, CBytes) { + TempDir tmp; + tmp.write_file("data.txt", "abcdef"); + auto f = (tmp.path / "data.txt").string(); + char a0[] = "head", a1[] = "-c", a2[] = "3", a3[256]; + std::snprintf(a3, sizeof(a3), "%s", f.c_str()); + char* argv[] = {a0, a1, a2, a3}; + auto out = capture_stdout([&]{ return head_main(4, argv); }); + EXPECT_EQ(out, "abc"); +} + +TEST(HeadTest, FileShorterThanN) { + TempDir tmp; + tmp.write_file("short.txt", "only\n"); + auto f = (tmp.path / "short.txt").string(); + char a0[] = "head", a1[] = "-n", a2[] = "10", a3[256]; + std::snprintf(a3, sizeof(a3), "%s", f.c_str()); + char* argv[] = {a0, a1, a2, a3}; + auto out = capture_stdout([&]{ return head_main(4, argv); }); + EXPECT_EQ(out, "only\n"); +} + +TEST(HeadTest, NonexistentFile) { + char a0[] = "head", a1[] = "/no/such/file.txt"; + char* argv[] = {a0, a1}; + EXPECT_NE(head_main(2, argv), 0); +} + +#endif // CFBOX_ENABLE_HEAD diff --git a/tests/unit/test_tail.cpp b/tests/unit/test_tail.cpp new file mode 100644 index 0000000..645d669 --- /dev/null +++ b/tests/unit/test_tail.cpp @@ -0,0 +1,63 @@ +#include +#include +#include "test_capture.hpp" +#include + +#if CFBOX_ENABLE_TAIL + +using namespace cfbox::test; + +TEST(TailTest, DefaultTenLines) { + TempDir tmp; + tmp.write_file("data.txt", "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n"); + auto f = (tmp.path / "data.txt").string(); + char a0[] = "tail", a1[256]; + std::snprintf(a1, sizeof(a1), "%s", f.c_str()); + char* argv[] = {a0, a1}; + auto out = capture_stdout([&]{ return tail_main(2, argv); }); + // Should have last 10 lines (3..12) + auto count = std::count(out.begin(), out.end(), '\n'); + EXPECT_EQ(count, 10); + EXPECT_NE(out.find("3\n"), std::string::npos); +} + +TEST(TailTest, NLines) { + TempDir tmp; + tmp.write_file("data.txt", "a\nb\nc\nd\ne\n"); + auto f = (tmp.path / "data.txt").string(); + char a0[] = "tail", a1[] = "-n", a2[] = "2", a3[256]; + std::snprintf(a3, sizeof(a3), "%s", f.c_str()); + char* argv[] = {a0, a1, a2, a3}; + auto out = capture_stdout([&]{ return tail_main(4, argv); }); + EXPECT_EQ(out, "d\ne\n"); +} + +TEST(TailTest, CBytes) { + TempDir tmp; + tmp.write_file("data.txt", "abcdef"); + auto f = (tmp.path / "data.txt").string(); + char a0[] = "tail", a1[] = "-c", a2[] = "3", a3[256]; + std::snprintf(a3, sizeof(a3), "%s", f.c_str()); + char* argv[] = {a0, a1, a2, a3}; + auto out = capture_stdout([&]{ return tail_main(4, argv); }); + EXPECT_EQ(out, "def"); +} + +TEST(TailTest, PlusNFromStart) { + TempDir tmp; + tmp.write_file("data.txt", "a\nb\nc\nd\ne\n"); + auto f = (tmp.path / "data.txt").string(); + char a0[] = "tail", a1[] = "-n", a2[] = "+3", a3[256]; + std::snprintf(a3, sizeof(a3), "%s", f.c_str()); + char* argv[] = {a0, a1, a2, a3}; + auto out = capture_stdout([&]{ return tail_main(4, argv); }); + EXPECT_EQ(out, "c\nd\ne\n"); +} + +TEST(TailTest, NonexistentFile) { + char a0[] = "tail", a1[] = "/no/such/file.txt"; + char* argv[] = {a0, a1}; + EXPECT_NE(tail_main(2, argv), 0); +} + +#endif // CFBOX_ENABLE_TAIL diff --git a/tests/unit/test_wc.cpp b/tests/unit/test_wc.cpp new file mode 100644 index 0000000..5ffc102 --- /dev/null +++ b/tests/unit/test_wc.cpp @@ -0,0 +1,83 @@ +#include +#include +#include "test_capture.hpp" +#include + +#if CFBOX_ENABLE_WC + +using namespace cfbox::test; + +TEST(WcTest, DefaultAll) { + TempDir tmp; + tmp.write_file("test.txt", "hello world\nfoo bar\n"); + auto f = (tmp.path / "test.txt").string(); + char a0[] = "wc", a1[256]; + std::snprintf(a1, sizeof(a1), "%s", f.c_str()); + char* argv[] = {a0, a1}; + auto out = capture_stdout([&]{ return wc_main(2, argv); }); + // 2 lines, 4 words, 20 bytes + EXPECT_NE(out.find("2"), std::string::npos); + EXPECT_NE(out.find("4"), std::string::npos); + EXPECT_NE(out.find("20"), std::string::npos); + EXPECT_NE(out.find("test.txt"), std::string::npos); +} + +TEST(WcTest, LinesOnly) { + TempDir tmp; + tmp.write_file("test.txt", "a\nb\nc\n"); + auto f = (tmp.path / "test.txt").string(); + char a0[] = "wc", a1[] = "-l", a2[256]; + std::snprintf(a2, sizeof(a2), "%s", f.c_str()); + char* argv[] = {a0, a1, a2}; + auto out = capture_stdout([&]{ return wc_main(3, argv); }); + EXPECT_NE(out.find("3"), std::string::npos); +} + +TEST(WcTest, WordsOnly) { + TempDir tmp; + tmp.write_file("test.txt", "one two three\n"); + auto f = (tmp.path / "test.txt").string(); + char a0[] = "wc", a1[] = "-w", a2[256]; + std::snprintf(a2, sizeof(a2), "%s", f.c_str()); + char* argv[] = {a0, a1, a2}; + auto out = capture_stdout([&]{ return wc_main(3, argv); }); + EXPECT_NE(out.find("3"), std::string::npos); +} + +TEST(WcTest, BytesOnly) { + TempDir tmp; + tmp.write_file("test.txt", "abc"); + auto f = (tmp.path / "test.txt").string(); + char a0[] = "wc", a1[] = "-c", a2[256]; + std::snprintf(a2, sizeof(a2), "%s", f.c_str()); + char* argv[] = {a0, a1, a2}; + auto out = capture_stdout([&]{ return wc_main(3, argv); }); + EXPECT_NE(out.find("3"), std::string::npos); +} + +TEST(WcTest, EmptyFile) { + TempDir tmp; + tmp.write_file("empty.txt", ""); + auto f = (tmp.path / "empty.txt").string(); + char a0[] = "wc", a1[256]; + std::snprintf(a1, sizeof(a1), "%s", f.c_str()); + char* argv[] = {a0, a1}; + auto out = capture_stdout([&]{ return wc_main(2, argv); }); + EXPECT_NE(out.find("0"), std::string::npos); +} + +TEST(WcTest, MultipleFilesShowsTotal) { + TempDir tmp; + tmp.write_file("a.txt", "aaa\n"); + tmp.write_file("b.txt", "bbb\n"); + auto fa = (tmp.path / "a.txt").string(); + auto fb = (tmp.path / "b.txt").string(); + char a0[] = "wc", a1[256], a2[256]; + std::snprintf(a1, sizeof(a1), "%s", fa.c_str()); + std::snprintf(a2, sizeof(a2), "%s", fb.c_str()); + char* argv[] = {a0, a1, a2}; + auto out = capture_stdout([&]{ return wc_main(3, argv); }); + EXPECT_NE(out.find("total"), std::string::npos); +} + +#endif // CFBOX_ENABLE_WC From da6b606541b4d2c6e8d89dbccae8ecc9061cccaa Mon Sep 17 00:00:00 2001 From: Charliechen114514 <725610365@qq.com> Date: Tue, 26 May 2026 18:35:43 +0800 Subject: [PATCH 5/8] ci: fix hardcoding issye --- include/cfbox/help.hpp | 5 +---- src/applets/init/init.hpp | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/include/cfbox/help.hpp b/include/cfbox/help.hpp index c0f4472..6834f44 100644 --- a/include/cfbox/help.hpp +++ b/include/cfbox/help.hpp @@ -3,12 +3,9 @@ #include #include +#include #include -#ifndef CFBOX_VERSION_STRING -#define CFBOX_VERSION_STRING "0.0.1" -#endif - namespace cfbox::help { struct HelpEntry { diff --git a/src/applets/init/init.hpp b/src/applets/init/init.hpp index a35589e..9fd259c 100644 --- a/src/applets/init/init.hpp +++ b/src/applets/init/init.hpp @@ -11,7 +11,6 @@ #include #include -#include namespace cfbox::init { From f027e790624351ae9d13a57e6c3f6fae538adbdb Mon Sep 17 00:00:00 2001 From: Charliechen114514 <725610365@qq.com> Date: Tue, 26 May 2026 18:53:05 +0800 Subject: [PATCH 6/8] ci: fix the unused issue --- include/cfbox/proc.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/cfbox/proc.hpp b/include/cfbox/proc.hpp index 02e1c61..dfcd703 100644 --- a/include/cfbox/proc.hpp +++ b/include/cfbox/proc.hpp @@ -507,7 +507,10 @@ inline auto read_partitions() -> base::Result> { std::vector parts; char line[256]; // Skip header line - std::fgets(line, sizeof(line), f); + if (!std::fgets(line, sizeof(line), f)) { + std::fclose(f); + return parts; + } while (std::fgets(line, sizeof(line), f)) { if (line[0] == '\n') continue; From c6b2e10e8a125e71704c8026194e05be6298cf12 Mon Sep 17 00:00:00 2001 From: Charliechen114514 <725610365@qq.com> Date: Tue, 26 May 2026 18:58:02 +0800 Subject: [PATCH 7/8] ci: trigger CI run From 2893d720f937371474cac7f6694d9b173f57823d Mon Sep 17 00:00:00 2001 From: Charliechen114514 <725610365@qq.com> Date: Tue, 26 May 2026 19:01:39 +0800 Subject: [PATCH 8/8] ci: trigger CI run --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ffb2c0c..0c26746 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,7 @@ on: branches: [main] pull_request: branches: [main] + workflow_dispatch: jobs: # ── Native build + test ───────────────────────────────────────