toybox/toys/posix/rm.c
<<
>>
Prefs
   1/* rm.c - remove files
   2 *
   3 * Copyright 2012 Rob Landley <rob@landley.net>
   4 *
   5 * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/rm.html
   6
   7USE_RM(NEWTOY(rm, "fiRr[-fi]", TOYFLAG_BIN))
   8
   9config 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
  25static 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
  70skip:
  71  if (unlinkat(fd, try->name, using)) {
  72    if (!dir || try->symlink != (char *)2) perror_msg_raw(try->name);
  73nodelete:
  74    if (try->parent) try->parent->symlink = (char *)2;
  75  }
  76
  77  return 0;
  78}
  79
  80void 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