Skip to content

Commit c99a474

Browse files
Merge pull request #10 from Awesome-Embedded-Learning-Studio/feat/add_phase0_commands
feat: chgrp, chmod, chown, celar, mountpoint and which
2 parents 7835d27 + cc03e00 commit c99a474

15 files changed

Lines changed: 841 additions & 0 deletions

cmake/Config.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ set(CFBOX_APPLETS
2727
pwdx pstree pmap fuser iostat
2828
watch top
2929
dmesg hexdump more rev cal renice
30+
clear which mountpoint chmod chown chgrp
3031
)
3132

3233
foreach(applet IN LISTS CFBOX_APPLETS)

include/cfbox/applet_config.hpp.in

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,9 @@
113113
#cmakedefine01 CFBOX_ENABLE_REV
114114
#cmakedefine01 CFBOX_ENABLE_CAL
115115
#cmakedefine01 CFBOX_ENABLE_RENICE
116+
#cmakedefine01 CFBOX_ENABLE_CLEAR
117+
#cmakedefine01 CFBOX_ENABLE_WHICH
118+
#cmakedefine01 CFBOX_ENABLE_MOUNTPOINT
119+
#cmakedefine01 CFBOX_ENABLE_CHMOD
120+
#cmakedefine01 CFBOX_ENABLE_CHOWN
121+
#cmakedefine01 CFBOX_ENABLE_CHGRP

include/cfbox/applets.hpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,24 @@ extern auto cal_main(int argc, char* argv[]) -> int;
331331
#if CFBOX_ENABLE_RENICE
332332
extern auto renice_main(int argc, char* argv[]) -> int;
333333
#endif
334+
#if CFBOX_ENABLE_CLEAR
335+
extern auto clear_main(int argc, char* argv[]) -> int;
336+
#endif
337+
#if CFBOX_ENABLE_WHICH
338+
extern auto which_main(int argc, char* argv[]) -> int;
339+
#endif
340+
#if CFBOX_ENABLE_MOUNTPOINT
341+
extern auto mountpoint_main(int argc, char* argv[]) -> int;
342+
#endif
343+
#if CFBOX_ENABLE_CHMOD
344+
extern auto chmod_main(int argc, char* argv[]) -> int;
345+
#endif
346+
#if CFBOX_ENABLE_CHOWN
347+
extern auto chown_main(int argc, char* argv[]) -> int;
348+
#endif
349+
#if CFBOX_ENABLE_CHGRP
350+
extern auto chgrp_main(int argc, char* argv[]) -> int;
351+
#endif
334352

335353
// registry — one line per applet, conditionally compiled
336354
constexpr auto APPLET_REGISTRY = std::to_array<cfbox::applet::AppEntry>({
@@ -663,4 +681,22 @@ constexpr auto APPLET_REGISTRY = std::to_array<cfbox::applet::AppEntry>({
663681
#if CFBOX_ENABLE_RENICE
664682
{"renice", renice_main, "alter priority of running processes"},
665683
#endif
684+
#if CFBOX_ENABLE_CLEAR
685+
{"clear", clear_main, "clear the terminal screen"},
686+
#endif
687+
#if CFBOX_ENABLE_WHICH
688+
{"which", which_main, "locate a command"},
689+
#endif
690+
#if CFBOX_ENABLE_MOUNTPOINT
691+
{"mountpoint", mountpoint_main, "check if a path is a mountpoint"},
692+
#endif
693+
#if CFBOX_ENABLE_CHMOD
694+
{"chmod", chmod_main, "change file mode bits"},
695+
#endif
696+
#if CFBOX_ENABLE_CHOWN
697+
{"chown", chown_main, "change file owner and group"},
698+
#endif
699+
#if CFBOX_ENABLE_CHGRP
700+
{"chgrp", chgrp_main, "change group ownership"},
701+
#endif
666702
});

src/applets/chgrp.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#include <cerrno>
2+
#include <cstdio>
3+
#include <cstring>
4+
#include <filesystem>
5+
#include <grp.h>
6+
#include <string>
7+
#include <unistd.h>
8+
9+
#include <cfbox/args.hpp>
10+
#include <cfbox/help.hpp>
11+
12+
namespace {
13+
14+
constexpr cfbox::help::HelpEntry HELP = {
15+
.name = "chgrp",
16+
.version = CFBOX_VERSION_STRING,
17+
.one_line = "change group ownership",
18+
.usage = "chgrp [-R] [-v] GROUP FILE...",
19+
.options = " -R operate on files and directories recursively\n"
20+
" -v output a diagnostic for every file processed",
21+
.extra = "",
22+
};
23+
24+
} // namespace
25+
26+
auto chgrp_main(int argc, char* argv[]) -> int {
27+
auto parsed = cfbox::args::parse(argc, argv, {
28+
cfbox::args::OptSpec{'R', false, "recursive"},
29+
cfbox::args::OptSpec{'v', false, "verbose"},
30+
});
31+
32+
if (parsed.has_long("help")) { cfbox::help::print_help(HELP); return 0; }
33+
if (parsed.has_long("version")) { cfbox::help::print_version(HELP); return 0; }
34+
35+
bool recursive = parsed.has('R');
36+
bool verbose = parsed.has('v');
37+
const auto& pos = parsed.positional();
38+
39+
if (pos.size() < 2) {
40+
std::fprintf(stderr, "cfbox chgrp: missing operand\n");
41+
return 2;
42+
}
43+
44+
std::string group_name(pos[0]);
45+
gid_t gid;
46+
auto* gr = getgrnam(group_name.c_str());
47+
if (gr) {
48+
gid = gr->gr_gid;
49+
} else {
50+
gid = static_cast<gid_t>(std::strtoul(group_name.c_str(), nullptr, 10));
51+
}
52+
53+
auto chgrp_one = [&](const std::string& path) -> int {
54+
if (::chown(path.c_str(), static_cast<uid_t>(-1), gid) != 0) {
55+
std::fprintf(stderr, "cfbox chgrp: %s: %s\n", path.c_str(), std::strerror(errno));
56+
return 1;
57+
}
58+
if (verbose) std::printf("group of '%s' changed\n", path.c_str());
59+
return 0;
60+
};
61+
62+
int rc = 0;
63+
for (size_t i = 1; i < pos.size(); i++) {
64+
std::string path(pos[i]);
65+
if (recursive && std::filesystem::is_directory(path)) {
66+
std::error_code ec;
67+
for (const auto& entry : std::filesystem::recursive_directory_iterator(path, ec)) {
68+
if (ec) continue;
69+
if (chgrp_one(entry.path().string()) != 0) rc = 1;
70+
}
71+
}
72+
if (chgrp_one(path) != 0) rc = 1;
73+
}
74+
return rc;
75+
}

src/applets/chmod.cpp

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
#include <cstdio>
2+
#include <filesystem>
3+
#include <optional>
4+
#include <string>
5+
#include <string_view>
6+
7+
#include <cfbox/args.hpp>
8+
#include <cfbox/fs_util.hpp>
9+
#include <cfbox/help.hpp>
10+
11+
namespace {
12+
13+
constexpr cfbox::help::HelpEntry HELP = {
14+
.name = "chmod",
15+
.version = CFBOX_VERSION_STRING,
16+
.one_line = "change file mode bits",
17+
.usage = "chmod [-R] [-v] MODE FILE...",
18+
.options = " -R change files and directories recursively\n"
19+
" -v output a diagnostic for every file processed\n"
20+
" --reference=RFILE use RFILE's mode instead of MODE value",
21+
.extra = "MODE can be octal (755) or symbolic (u+x,go-w,a=rX).",
22+
};
23+
24+
auto parse_octal(std::string_view s) -> std::optional<std::filesystem::perms> {
25+
if (s.empty() || s.size() > 4) return std::nullopt;
26+
for (char c : s) if (c < '0' || c > '7') return std::nullopt;
27+
unsigned mode = 0;
28+
for (char c : s) mode = mode * 8 + (c - '0');
29+
return static_cast<std::filesystem::perms>(mode);
30+
}
31+
32+
auto apply_symbolic(std::filesystem::perms current, std::string_view spec) -> std::optional<std::filesystem::perms> {
33+
auto who_end = spec.find_first_of("-+=");
34+
if (who_end == std::string_view::npos) return std::nullopt;
35+
36+
auto who = spec.substr(0, who_end);
37+
if (who.empty()) who = "a";
38+
39+
char op = spec[who_end];
40+
auto rest = spec.substr(who_end + 1);
41+
42+
auto bits_for = [&](char perm) -> std::filesystem::perms {
43+
switch (perm) {
44+
case 'r': return std::filesystem::perms::owner_read | std::filesystem::perms::group_read | std::filesystem::perms::others_read;
45+
case 'w': return std::filesystem::perms::owner_write | std::filesystem::perms::group_write | std::filesystem::perms::others_write;
46+
case 'x': return std::filesystem::perms::owner_exec | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec;
47+
case 'X': return std::filesystem::perms::owner_exec | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec;
48+
default: return std::filesystem::perms::none;
49+
}
50+
};
51+
52+
std::filesystem::perms result = current;
53+
for (char w : who) {
54+
for (char p : rest) {
55+
auto bits = bits_for(p);
56+
if (bits == std::filesystem::perms::none) continue;
57+
58+
std::filesystem::perms mask;
59+
switch (w) {
60+
case 'u': mask = std::filesystem::perms::owner_read | std::filesystem::perms::owner_write | std::filesystem::perms::owner_exec; break;
61+
case 'g': mask = std::filesystem::perms::group_read | std::filesystem::perms::group_write | std::filesystem::perms::group_exec; break;
62+
case 'o': mask = std::filesystem::perms::others_read | std::filesystem::perms::others_write | std::filesystem::perms::others_exec; break;
63+
case 'a': mask = std::filesystem::perms::owner_read | std::filesystem::perms::owner_write | std::filesystem::perms::owner_exec
64+
| std::filesystem::perms::group_read | std::filesystem::perms::group_write | std::filesystem::perms::group_exec
65+
| std::filesystem::perms::others_read | std::filesystem::perms::others_write | std::filesystem::perms::others_exec; break;
66+
default: continue;
67+
}
68+
69+
auto target = bits & mask;
70+
switch (op) {
71+
case '+': result |= target; break;
72+
case '-': result &= ~target; break;
73+
case '=': result = (result & ~mask) | target; break;
74+
}
75+
}
76+
}
77+
return result;
78+
}
79+
80+
auto chmod_one(const std::string& path, std::filesystem::perms mode, bool verbose) -> int {
81+
auto result = cfbox::fs::permissions(path, mode);
82+
if (!result) {
83+
std::fprintf(stderr, "cfbox chmod: %s: %s\n", path.c_str(), result.error().msg.c_str());
84+
return 1;
85+
}
86+
if (verbose) std::printf("mode of '%s' changed\n", path.c_str());
87+
return 0;
88+
}
89+
90+
} // namespace
91+
92+
auto chmod_main(int argc, char* argv[]) -> int {
93+
auto parsed = cfbox::args::parse(argc, argv, {
94+
cfbox::args::OptSpec{'R', false, "recursive"},
95+
cfbox::args::OptSpec{'v', false, "verbose"},
96+
cfbox::args::OptSpec{'\0', true, "reference"},
97+
});
98+
99+
if (parsed.has_long("help")) { cfbox::help::print_help(HELP); return 0; }
100+
if (parsed.has_long("version")) { cfbox::help::print_version(HELP); return 0; }
101+
102+
bool recursive = parsed.has('R');
103+
bool verbose = parsed.has('v');
104+
const auto& pos = parsed.positional();
105+
106+
std::filesystem::perms target_mode;
107+
int files_start = 0;
108+
109+
if (parsed.has_long("reference")) {
110+
auto rfile = parsed.get_long("reference");
111+
if (!rfile) {
112+
std::fprintf(stderr, "cfbox chmod: --reference requires an argument\n");
113+
return 2;
114+
}
115+
std::string rfile_str(*rfile);
116+
auto st = cfbox::fs::status(rfile_str);
117+
if (!st) {
118+
std::fprintf(stderr, "cfbox chmod: %s: %s\n", rfile_str.c_str(), st.error().msg.c_str());
119+
return 1;
120+
}
121+
target_mode = st->permissions();
122+
files_start = 0;
123+
if (pos.empty()) {
124+
std::fprintf(stderr, "cfbox chmod: missing operand\n");
125+
return 2;
126+
}
127+
} else {
128+
if (pos.size() < 2) {
129+
std::fprintf(stderr, "cfbox chmod: missing operand\n");
130+
return 2;
131+
}
132+
auto octal = parse_octal(pos[0]);
133+
if (octal) {
134+
target_mode = *octal;
135+
} else {
136+
int rc = 0;
137+
for (size_t i = 1; i < pos.size(); i++) {
138+
std::string path(pos[i]);
139+
auto st = cfbox::fs::status(path);
140+
if (!st) {
141+
std::fprintf(stderr, "cfbox chmod: %s: %s\n", path.c_str(), st.error().msg.c_str());
142+
rc = 1;
143+
continue;
144+
}
145+
auto new_mode = apply_symbolic(st->permissions(), pos[0]);
146+
if (!new_mode) {
147+
std::fprintf(stderr, "cfbox chmod: invalid mode: %s\n", std::string(pos[0]).c_str());
148+
return 2;
149+
}
150+
if (chmod_one(path, *new_mode, verbose) != 0) rc = 1;
151+
}
152+
return rc;
153+
}
154+
files_start = 1;
155+
}
156+
157+
int rc = 0;
158+
for (size_t i = files_start; i < pos.size(); i++) {
159+
std::string path(pos[i]);
160+
if (recursive && cfbox::fs::is_directory(path)) {
161+
std::error_code ec;
162+
for (const auto& entry : std::filesystem::recursive_directory_iterator(path, ec)) {
163+
if (ec) continue;
164+
if (chmod_one(entry.path().string(), target_mode, verbose) != 0) rc = 1;
165+
}
166+
}
167+
if (chmod_one(path, target_mode, verbose) != 0) rc = 1;
168+
}
169+
return rc;
170+
}

0 commit comments

Comments
 (0)