rm: Add -i and cleanup rm() - sbase - suckless unix tools | |
git clone git://git.suckless.org/sbase | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
commit 0df8cdc12d7a5600ad0c2b9420a14be4e2af340b | |
parent 948e5161902920705f0c3a6458533fc017452173 | |
Author: Roberto E. Vargas Caballero <[email protected]> | |
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: | |
M README | 2 +- | |
M fs.h | 2 ++ | |
M libutil/rm.c | 41 ++++++++++++++++++++++-------… | |
M mv.c | 2 +- | |
M rm.c | 9 ++++++--- | |
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 *… | |
{ | |
- 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; |