Skip to content

Commit 658df07

Browse files
feat: 16 new tools
1 parent 0e35df3 commit 658df07

53 files changed

Lines changed: 2082 additions & 1 deletion

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cmake/Config.cmake

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ include_guard()
66
set(CFBOX_APPLETS
77
echo printf cat head tail wc sort uniq
88
mkdir rm cp mv ls grep find sed init
9+
true false yes pwd
10+
basename dirname uname nproc link
11+
hostname logname whoami tty
12+
sleep id test
913
)
1014

1115
foreach(applet IN LISTS CFBOX_APPLETS)

include/cfbox/applet_config.hpp.in

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,19 @@
2121
#cmakedefine01 CFBOX_ENABLE_FIND
2222
#cmakedefine01 CFBOX_ENABLE_SED
2323
#cmakedefine01 CFBOX_ENABLE_INIT
24+
#cmakedefine01 CFBOX_ENABLE_TRUE
25+
#cmakedefine01 CFBOX_ENABLE_FALSE
26+
#cmakedefine01 CFBOX_ENABLE_YES
27+
#cmakedefine01 CFBOX_ENABLE_PWD
28+
#cmakedefine01 CFBOX_ENABLE_BASENAME
29+
#cmakedefine01 CFBOX_ENABLE_DIRNAME
30+
#cmakedefine01 CFBOX_ENABLE_UNAME
31+
#cmakedefine01 CFBOX_ENABLE_NPROC
32+
#cmakedefine01 CFBOX_ENABLE_LINK
33+
#cmakedefine01 CFBOX_ENABLE_HOSTNAME
34+
#cmakedefine01 CFBOX_ENABLE_LOGNAME
35+
#cmakedefine01 CFBOX_ENABLE_WHOAMI
36+
#cmakedefine01 CFBOX_ENABLE_TTY
37+
#cmakedefine01 CFBOX_ENABLE_SLEEP
38+
#cmakedefine01 CFBOX_ENABLE_ID
39+
#cmakedefine01 CFBOX_ENABLE_TEST

include/cfbox/applets.hpp

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,54 @@ extern auto sed_main(int argc, char* argv[]) -> int;
5555
#if CFBOX_ENABLE_INIT
5656
extern auto init_main(int argc, char* argv[]) -> int;
5757
#endif
58+
#if CFBOX_ENABLE_TRUE
59+
extern auto true_main(int argc, char* argv[]) -> int;
60+
#endif
61+
#if CFBOX_ENABLE_FALSE
62+
extern auto false_main(int argc, char* argv[]) -> int;
63+
#endif
64+
#if CFBOX_ENABLE_YES
65+
extern auto yes_main(int argc, char* argv[]) -> int;
66+
#endif
67+
#if CFBOX_ENABLE_PWD
68+
extern auto pwd_main(int argc, char* argv[]) -> int;
69+
#endif
70+
#if CFBOX_ENABLE_BASENAME
71+
extern auto basename_main(int argc, char* argv[]) -> int;
72+
#endif
73+
#if CFBOX_ENABLE_DIRNAME
74+
extern auto dirname_main(int argc, char* argv[]) -> int;
75+
#endif
76+
#if CFBOX_ENABLE_UNAME
77+
extern auto uname_main(int argc, char* argv[]) -> int;
78+
#endif
79+
#if CFBOX_ENABLE_NPROC
80+
extern auto nproc_main(int argc, char* argv[]) -> int;
81+
#endif
82+
#if CFBOX_ENABLE_LINK
83+
extern auto link_main(int argc, char* argv[]) -> int;
84+
#endif
85+
#if CFBOX_ENABLE_HOSTNAME
86+
extern auto hostname_main(int argc, char* argv[]) -> int;
87+
#endif
88+
#if CFBOX_ENABLE_LOGNAME
89+
extern auto logname_main(int argc, char* argv[]) -> int;
90+
#endif
91+
#if CFBOX_ENABLE_WHOAMI
92+
extern auto whoami_main(int argc, char* argv[]) -> int;
93+
#endif
94+
#if CFBOX_ENABLE_TTY
95+
extern auto tty_main(int argc, char* argv[]) -> int;
96+
#endif
97+
#if CFBOX_ENABLE_SLEEP
98+
extern auto sleep_main(int argc, char* argv[]) -> int;
99+
#endif
100+
#if CFBOX_ENABLE_ID
101+
extern auto id_main(int argc, char* argv[]) -> int;
102+
#endif
103+
#if CFBOX_ENABLE_TEST
104+
extern auto test_main(int argc, char* argv[]) -> int;
105+
#endif
58106

59107
// registry — one line per applet, conditionally compiled
60108
constexpr auto APPLET_REGISTRY = std::to_array<cfbox::applet::AppEntry>({
@@ -109,4 +157,53 @@ constexpr auto APPLET_REGISTRY = std::to_array<cfbox::applet::AppEntry>({
109157
#if CFBOX_ENABLE_INIT
110158
{"init", init_main, "system init for boot testing (PID 1)"},
111159
#endif
160+
#if CFBOX_ENABLE_TRUE
161+
{"true", true_main, "do nothing, exit with status 0"},
162+
#endif
163+
#if CFBOX_ENABLE_FALSE
164+
{"false", false_main, "do nothing, exit with status 1"},
165+
#endif
166+
#if CFBOX_ENABLE_YES
167+
{"yes", yes_main, "output a string repeatedly until killed"},
168+
#endif
169+
#if CFBOX_ENABLE_PWD
170+
{"pwd", pwd_main, "print working directory"},
171+
#endif
172+
#if CFBOX_ENABLE_BASENAME
173+
{"basename", basename_main, "strip directory and suffix from file names"},
174+
#endif
175+
#if CFBOX_ENABLE_DIRNAME
176+
{"dirname", dirname_main, "strip last component from file name"},
177+
#endif
178+
#if CFBOX_ENABLE_UNAME
179+
{"uname", uname_main, "print system information"},
180+
#endif
181+
#if CFBOX_ENABLE_NPROC
182+
{"nproc", nproc_main, "print number of available processors"},
183+
#endif
184+
#if CFBOX_ENABLE_LINK
185+
{"link", link_main, "create a hard link"},
186+
#endif
187+
#if CFBOX_ENABLE_HOSTNAME
188+
{"hostname", hostname_main, "show or set the system host name"},
189+
#endif
190+
#if CFBOX_ENABLE_LOGNAME
191+
{"logname", logname_main, "print the user's login name"},
192+
#endif
193+
#if CFBOX_ENABLE_WHOAMI
194+
{"whoami", whoami_main, "print effective user ID"},
195+
#endif
196+
#if CFBOX_ENABLE_TTY
197+
{"tty", tty_main, "print the file name of the terminal connected to stdin"},
198+
#endif
199+
#if CFBOX_ENABLE_SLEEP
200+
{"sleep", sleep_main, "delay for a specified amount of time"},
201+
#endif
202+
#if CFBOX_ENABLE_ID
203+
{"id", id_main, "print real and effective user and group IDs"},
204+
#endif
205+
#if CFBOX_ENABLE_TEST
206+
{"test", test_main, "evaluate conditional expression"},
207+
{"[", test_main, "evaluate conditional expression"},
208+
#endif
112209
});

include/cfbox/fs_util.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,18 @@ inline auto rename(std::string_view old_path, std::string_view new_path) -> base
112112
return {};
113113
}
114114

115+
inline auto create_hard_link(std::string_view target, std::string_view link_path) -> base::Result<void> {
116+
std::error_code ec;
117+
std::filesystem::create_hard_link(
118+
std::filesystem::path{target},
119+
std::filesystem::path{link_path},
120+
ec);
121+
if (ec) {
122+
return std::unexpected(base::Error{static_cast<int>(ec.value()), ec.message()});
123+
}
124+
return {};
125+
}
126+
115127
inline auto current_path() -> base::Result<std::string> {
116128
std::error_code ec;
117129
auto p = std::filesystem::current_path(ec);

src/applets/basename.cpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#include <cstdio>
2+
#include <string>
3+
#include <string_view>
4+
5+
#include <cfbox/args.hpp>
6+
#include <cfbox/help.hpp>
7+
8+
namespace {
9+
constexpr cfbox::help::HelpEntry HELP = {
10+
.name = "basename",
11+
.version = CFBOX_VERSION_STRING,
12+
.one_line = "strip directory and suffix from file names",
13+
.usage = "basename PATH [SUFFIX]",
14+
.options = "",
15+
.extra = "",
16+
};
17+
18+
auto do_basename(std::string_view path, std::string_view suffix) -> std::string {
19+
// Remove trailing slashes (but keep root "/")
20+
while (path.size() > 1 && path.back() == '/') {
21+
path.remove_suffix(1);
22+
}
23+
24+
std::string result;
25+
if (path.empty()) {
26+
result = ".";
27+
} else if (path == "/") {
28+
result = "/";
29+
} else {
30+
// Find last component after last '/'
31+
auto pos = path.rfind('/');
32+
if (pos == std::string_view::npos) {
33+
result = std::string{path};
34+
} else {
35+
result = std::string{path.substr(pos + 1)};
36+
}
37+
}
38+
39+
// Strip suffix if given and result ends with it (but not if suffix == result)
40+
if (!suffix.empty() && result.size() > suffix.size()) {
41+
if (result.compare(result.size() - suffix.size(), suffix.size(), suffix) == 0) {
42+
result.erase(result.size() - suffix.size());
43+
}
44+
}
45+
46+
return result;
47+
}
48+
49+
} // namespace
50+
51+
auto basename_main(int argc, char* argv[]) -> int {
52+
auto parsed = cfbox::args::parse(argc, argv, {});
53+
54+
if (parsed.has_long("help")) { cfbox::help::print_help(HELP); return 0; }
55+
if (parsed.has_long("version")) { cfbox::help::print_version(HELP); return 0; }
56+
57+
const auto& pos = parsed.positional();
58+
if (pos.empty()) {
59+
std::fprintf(stderr, "cfbox basename: missing operand\n");
60+
return 1;
61+
}
62+
63+
std::string_view suffix;
64+
if (pos.size() >= 2) {
65+
suffix = pos[1];
66+
}
67+
68+
std::puts(do_basename(pos[0], suffix).c_str());
69+
return 0;
70+
}

src/applets/dirname.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#include <cstdio>
2+
#include <string>
3+
#include <string_view>
4+
5+
#include <cfbox/args.hpp>
6+
#include <cfbox/help.hpp>
7+
8+
namespace {
9+
constexpr cfbox::help::HelpEntry HELP = {
10+
.name = "dirname",
11+
.version = CFBOX_VERSION_STRING,
12+
.one_line = "strip last component from file name",
13+
.usage = "dirname PATH",
14+
.options = "",
15+
.extra = "",
16+
};
17+
18+
auto do_dirname(std::string_view path) -> std::string {
19+
if (path.empty()) return ".";
20+
21+
// Remove trailing slashes
22+
while (path.size() > 1 && path.back() == '/') {
23+
path.remove_suffix(1);
24+
}
25+
26+
if (path.empty()) return "/";
27+
28+
auto pos = path.rfind('/');
29+
if (pos == std::string_view::npos) return ".";
30+
31+
// Remove trailing slashes from the parent part
32+
auto parent = path.substr(0, pos);
33+
while (parent.size() > 1 && parent.back() == '/') {
34+
parent.remove_suffix(1);
35+
}
36+
37+
if (parent.empty()) return "/";
38+
return std::string{parent};
39+
}
40+
41+
} // namespace
42+
43+
auto dirname_main(int argc, char* argv[]) -> int {
44+
auto parsed = cfbox::args::parse(argc, argv, {});
45+
46+
if (parsed.has_long("help")) { cfbox::help::print_help(HELP); return 0; }
47+
if (parsed.has_long("version")) { cfbox::help::print_version(HELP); return 0; }
48+
49+
const auto& pos = parsed.positional();
50+
if (pos.empty()) {
51+
std::fprintf(stderr, "cfbox dirname: missing operand\n");
52+
return 1;
53+
}
54+
55+
std::puts(do_dirname(pos[0]).c_str());
56+
return 0;
57+
}

src/applets/false.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#include <cfbox/args.hpp>
2+
#include <cfbox/help.hpp>
3+
4+
namespace {
5+
constexpr cfbox::help::HelpEntry HELP = {
6+
.name = "false",
7+
.version = CFBOX_VERSION_STRING,
8+
.one_line = "do nothing, exit with status 1",
9+
.usage = "false",
10+
.options = "",
11+
.extra = "",
12+
};
13+
} // namespace
14+
15+
auto false_main(int argc, char* argv[]) -> int {
16+
auto parsed = cfbox::args::parse(argc, argv, {});
17+
if (parsed.has_long("help")) { cfbox::help::print_help(HELP); return 0; }
18+
if (parsed.has_long("version")) { cfbox::help::print_version(HELP); return 0; }
19+
return 1;
20+
}

src/applets/hostname.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#include <cstdio>
2+
#include <cstring>
3+
#include <string>
4+
#include <unistd.h>
5+
6+
#include <cfbox/args.hpp>
7+
#include <cfbox/help.hpp>
8+
9+
namespace {
10+
constexpr cfbox::help::HelpEntry HELP = {
11+
.name = "hostname",
12+
.version = CFBOX_VERSION_STRING,
13+
.one_line = "show or set the system host name",
14+
.usage = "hostname [-s]",
15+
.options = " -s short hostname (truncate at first dot)",
16+
.extra = "",
17+
};
18+
} // namespace
19+
20+
auto hostname_main(int argc, char* argv[]) -> int {
21+
auto parsed = cfbox::args::parse(argc, argv, {
22+
cfbox::args::OptSpec{'s', false},
23+
});
24+
25+
if (parsed.has_long("help")) { cfbox::help::print_help(HELP); return 0; }
26+
if (parsed.has_long("version")) { cfbox::help::print_version(HELP); return 0; }
27+
28+
char buf[256];
29+
if (gethostname(buf, sizeof(buf)) != 0) {
30+
std::fprintf(stderr, "cfbox hostname: cannot get hostname\n");
31+
return 1;
32+
}
33+
34+
std::string name{buf};
35+
if (parsed.has('s')) {
36+
auto dot = name.find('.');
37+
if (dot != std::string::npos) {
38+
name.erase(dot);
39+
}
40+
}
41+
42+
std::puts(name.c_str());
43+
return 0;
44+
}

0 commit comments

Comments
 (0)