Home | History | Annotate | Download | only in posix
      1 /* rm.c - remove files
      2  *
      3  * Copyright 2012 Rob Landley <rob (at) landley.net>
      4  *
      5  * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/rm.html
      6 
      7 USE_RM(NEWTOY(rm, "fiRr[-fi]", TOYFLAG_BIN))
      8 
      9 config RM
     10   bool "rm"
     11   default y
     12   help
     13     usage: rm [-fiRr] FILE...
     14 
     15     Remove each argument from the filesystem.
     16 
     17     -f	force: remove without confirmation, no error if it doesn't exist
     18     -i	interactive: prompt for confirmation
     19     -rR	recursive: remove directory contents
     20 */
     21 
     22 #define FOR_rm
     23 #include "toys.h"
     24 
     25 static int do_rm(struct dirtree *try)
     26 {
     27   int fd = dirtree_parentfd(try), flags = toys.optflags;
     28   int dir = S_ISDIR(try->st.st_mode), or = 0, using = 0;
     29 
     30   // Skip . and .. (yes, even explicitly on the command line: posix says to)
     31   if (isdotdot(try->name)) return 0;
     32 
     33   // Intentionally fail non-recursive attempts to remove even an empty dir
     34   // (via wrong flags to unlinkat) because POSIX says to.
     35   if (dir && !(flags & (FLAG_r|FLAG_R))) goto skip;
     36 
     37   // This is either the posix section 2(b) prompt or the section 3 prompt.
     38   if (!(flags & FLAG_f)
     39     && (!S_ISLNK(try->st.st_mode) && faccessat(fd, try->name, W_OK, 0))) or++;
     40   if (!(dir && try->again) && ((or && isatty(0)) || (flags & FLAG_i))) {
     41     char *s = dirtree_path(try, 0);
     42 
     43     fprintf(stderr, "rm %s%s%s", or ? "ro " : "", dir ? "dir " : "", s);
     44     free(s);
     45     or = yesno(0);
     46     if (!or) goto nodelete;
     47   }
     48 
     49   // handle directory recursion
     50   if (dir) {
     51     using = AT_REMOVEDIR;
     52     // Handle chmod 000 directories when -f
     53     if (faccessat(fd, try->name, R_OK, 0)) {
     54       if (toys.optflags & FLAG_f) wfchmodat(fd, try->name, 0700);
     55       else goto skip;
     56     }
     57     if (!try->again) return DIRTREE_COMEAGAIN;
     58     if (try->symlink) goto skip;
     59     if (flags & FLAG_i) {
     60       char *s = dirtree_path(try, 0);
     61 
     62       // This is the section 2(d) prompt. (Yes, posix says to prompt twice.)
     63       fprintf(stderr, "rmdir %s", s);
     64       free(s);
     65       or = yesno(0);
     66       if (!or) goto nodelete;
     67     }
     68   }
     69 
     70 skip:
     71   if (unlinkat(fd, try->name, using)) {
     72     if (!dir || try->symlink != (char *)2) perror_msg_raw(try->name);
     73 nodelete:
     74     if (try->parent) try->parent->symlink = (char *)2;
     75   }
     76 
     77   return 0;
     78 }
     79 
     80 void rm_main(void)
     81 {
     82   char **s;
     83 
     84   // Can't use <1 in optstring because zero arguments with -f isn't an error
     85   if (!toys.optc && !(toys.optflags & FLAG_f)) error_exit("Needs 1 argument");
     86 
     87   for (s = toys.optargs; *s; s++) {
     88     if (!strcmp(*s, "/")) {
     89       error_msg("rm /. if you mean it");
     90       continue;
     91     }
     92 
     93     // Files that already don't exist aren't errors for -f, so try a quick
     94     // unlink now to see if it succeeds or reports that it didn't exist.
     95     if ((toys.optflags & FLAG_f) && (!unlink(*s) || errno == ENOENT))
     96       continue;
     97 
     98     // There's a race here where a file removed between the above check and
     99     // dirtree's stat would report the nonexistence as an error, but that's
    100     // not a normal "it didn't exist" so I'm ok with it.
    101 
    102     dirtree_read(*s, do_rm);
    103   }
    104 }
    105