rm: Add -i and cleanup rm() - sbase - suckless unix tools
 (HTM) git clone git://git.suckless.org/sbase
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 0df8cdc12d7a5600ad0c2b9420a14be4e2af340b
 (DIR) parent 948e5161902920705f0c3a6458533fc017452173
 (HTM) 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:
         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(-)
       ---
 (DIR) 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             .
 (DIR) 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;
 (DIR) 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;
                }
        }
 (DIR) 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;
 (DIR) 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;