commit 02c289707bbb69398aae135989183b748127e15e
parent 394ddc08a8d6f1e3549843c66802418a2c964a1f
Author: Drew DeVault <sir@cmpwn.com>
Date: Fri, 4 Mar 2022 12:27:06 +0100
rm: new utility
Skipped the interactive mode (-i) intentionally
Diffstat:
3 files changed, 72 insertions(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
@@ -5,9 +5,10 @@ env
false
head
nl
+pwd
+rm
sleep
tee
true
uname
wc
-pwd
diff --git a/Makefile b/Makefile
@@ -12,6 +12,7 @@ utils=\
head \
nl \
pwd \
+ rm \
sleep \
tee \
true \
diff --git a/rm.ha b/rm.ha
@@ -0,0 +1,69 @@
+use fmt;
+use fs;
+use getopt;
+use main;
+use os;
+
+type config = struct {
+ status: int,
+ force: bool,
+ recur: bool,
+};
+
+export fn utilmain() (void | main::error) = {
+ const cmd = getopt::parse(os::args,
+ "remove files",
+ ('f', "remove read-only files and ignore errors"),
+ ('r', "remove directories and their contents"),
+ "files...");
+ defer getopt::finish(&cmd);
+
+ let conf = config { ... };
+ for (let i = 0z; i < len(cmd.opts); i += 1) {
+ switch (cmd.opts[i].0) {
+ case 'f' =>
+ conf.force = true;
+ case 'r' =>
+ conf.recur = true;
+ case => abort();
+ };
+ };
+
+ for (let i = 0z; i < len(cmd.args); i += 1) {
+ const target = cmd.args[i];
+ match (remove(&conf, target)) {
+ case let err: fs::error =>
+ conf.status = 1;
+ if (!conf.force) {
+ fmt::errorfln("{}: Error: {}",
+ target, fs::strerror(err))!;
+ };
+ case void => void;
+ };
+ };
+
+ os::exit(conf.status);
+};
+
+fn remove(conf: *config, path: str) (void | fs::error) = {
+ const st = os::stat(path)?;
+ if (fs::isdir(st.mode) && !conf.recur) {
+ if (!conf.force) {
+ conf.status = 1;
+ fmt::errorfln("{}: skipping directory", path)?;
+ };
+ return;
+ };
+
+ if (!os::access(path, os::amode::W_OK)? && !conf.force) {
+ conf.status = 1;
+ fmt::errorfln("{}: skipping read-only file", path)?;
+ return;
+ };
+
+ if (fs::isdir(st.mode)) {
+ os::rmdirall(path)?;
+ } else {
+ os::remove(path)?;
+ };
+};