commit 0df8cdc12d7a5600ad0c2b9420a14be4e2af340b
parent 948e5161902920705f0c3a6458533fc017452173
Author: Roberto E. Vargas Caballero <k0ga@shike2.com>
Date: Wed, 23 Apr 2025 22:12:52 +0200
rm: Add -i and cleanup rm()
POSIX mandates that if the input of rm is a tty and it does not have
write rights over a file/dir then it should ask for confirmation, in
the same way that is done with the -i flag. To accomodate both things
the code has been rearrenged a bit to have only one case instead of
having two. Also, this rework adds the error message when a directory
is removed without a -r flag.
Diffstat:
5 files changed, 39 insertions(+), 17 deletions(-)
diff --git a/README b/README
@@ -102,7 +102,7 @@ The following tools are implemented:
0=*|x readlink .
0=*|o renice .
0#* x rev .
-0=*|o rm (-i)
+0=*|o rm .
0=*|o rmdir .
# sed .
0=*|x seq .
diff --git a/fs.h b/fs.h
@@ -24,6 +24,8 @@ enum {
SAMEDEV = 1 << 0,
DIRFIRST = 1 << 1,
SILENT = 1 << 2,
+ CONFIRM = 1 << 3,
+ IGNORE = 1 << 4,
};
extern int cp_aflag;
diff --git a/libutil/rm.c b/libutil/rm.c
@@ -14,19 +14,36 @@ int rm_status = 0;
void
rm(int dirfd, const char *name, struct stat *st, void *data, struct recursor *r)
{
- if (!r->maxdepth && S_ISDIR(st->st_mode)) {
+ int quiet, ask, write, flags, ignore;
+
+ ignore = r->flags & IGNORE;
+ quiet = r->flags & SILENT;
+ ask = r->flags & CONFIRM;
+ write = faccessat(dirfd, name, W_OK, 0) == 0;
+ flags = 0;
+
+ if (S_ISDIR(st->st_mode) && r->maxdepth) {
+ errno = EISDIR;
+ goto err;
+ }
+
+ if (!quiet && (!write && isatty(0) || ask)) {
+ if (!confirm("remove file '%s'", r->path));
+ return;
+ }
+
+ if (S_ISDIR(st->st_mode)) {
+ flags = AT_REMOVEDIR;
recurse(dirfd, name, NULL, r);
+ }
+
+ if (unlinkat(dirfd, name, flags) < 0)
+ goto err;
+ return;
- if (unlinkat(dirfd, name, AT_REMOVEDIR) < 0) {
- if (!(r->flags & SILENT))
- weprintf("rmdir %s:", r->path);
- if (!((r->flags & SILENT) && errno == ENOENT))
- rm_status = 1;
- }
- } else if (unlinkat(dirfd, name, 0) < 0) {
- if (!(r->flags & SILENT))
- weprintf("unlink %s:", r->path);
- if (!((r->flags & SILENT) && errno == ENOENT))
- rm_status = 1;
+err:
+ if (!ignore) {
+ weprintf("cannot remove '%s':", r->path);
+ rm_status = 1;
}
}
diff --git a/mv.c b/mv.c
@@ -13,7 +13,7 @@ static int mv_status = 0;
static int
mv(const char *s1, const char *s2, int depth)
{
- struct recursor r = { .fn = rm, .follow = 'P' };
+ struct recursor r = { .fn = rm, .follow = 'P', .flags = SILENT };
if (!rename(s1, s2))
return 0;
diff --git a/rm.c b/rm.c
@@ -7,7 +7,7 @@
static void
usage(void)
{
- eprintf("usage: %s [-f] [-Rr] file ...\n", argv0);
+ eprintf("usage: %s [-f] [-iRr] file ...\n", argv0);
}
int
@@ -17,7 +17,10 @@ main(int argc, char *argv[])
ARGBEGIN {
case 'f':
- r.flags |= SILENT;
+ r.flags |= SILENT | IGNORE;
+ break;
+ case 'i':
+ r.flags |= CONFIRM;
break;
case 'R':
case 'r':
@@ -28,7 +31,7 @@ main(int argc, char *argv[])
} ARGEND
if (!argc) {
- if (!(r.flags & SILENT))
+ if (!(r.flags & IGNORE))
usage();
else
return 0;