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, "fiRrv[-fi]", TOYFLAG_BIN))
   8
   9config RM
  10  bool "rm"
  11  default y
  12  help
  13    usage: rm [-fiRrv] 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    -v  Verbose
  21*/
  22
  23#define FOR_rm
  24#include "toys.h"
  25
  26static int do_rm(struct dirtree *try)
  27{
  28  int fd=dirtree_parentfd(try), 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 && !(toys.optflags & (FLAG_r|FLAG_R))) goto skip;
  36
  37  // This is either the posix section 2(b) prompt or the section 3 prompt.
  38  if (!FLAG(f)
  39    && (!S_ISLNK(try->st.st_mode) && faccessat(fd, try->name, W_OK, 0))) or++;
  40
  41  // Posix section 1(a), don't prompt for nonexistent.
  42  if (or && errno == ENOENT) goto skip;
  43
  44  if (!(dir && try->again) && ((or && isatty(0)) || FLAG(i))) {
  45    char *s = dirtree_path(try, 0);
  46
  47    fprintf(stderr, "rm %s%s%s", or ? "ro " : "", dir ? "dir " : "", s);
  48    free(s);
  49    or = yesno(0);
  50    if (!or) goto nodelete;
  51  }
  52
  53  // handle directory recursion
  54  if (dir) {
  55    using = AT_REMOVEDIR;
  56    // Handle chmod 000 directories when -f
  57    if (faccessat(fd, try->name, R_OK, 0)) {
  58      if (FLAG(f)) wfchmodat(fd, try->name, 0700);
  59      else goto skip;
  60    }
  61    if (!try->again) return DIRTREE_COMEAGAIN;
  62    if (try->symlink) goto skip;
  63    if (FLAG(i)) {
  64      char *s = dirtree_path(try, 0);
  65
  66      // This is the section 2(d) prompt. (Yes, posix says to prompt twice.)
  67      fprintf(stderr, "rmdir %s", s);
  68      free(s);
  69      or = yesno(0);
  70      if (!or) goto nodelete;
  71    }
  72  }
  73
  74skip:
  75  if (!unlinkat(fd, try->name, using)) {
  76    if (FLAG(v)) {
  77      char *s = dirtree_path(try, 0);
  78      printf("%s%s '%s'\n", toys.which->name, dir ? "dir" : "", s);
  79      free(s);
  80    }
  81  } else {
  82    if (!dir || try->symlink != (char *)2) perror_msg_raw(try->name);
  83nodelete:
  84    if (try->parent) try->parent->symlink = (char *)2;
  85  }
  86
  87  return 0;
  88}
  89
  90void rm_main(void)
  91{
  92  char **s;
  93
  94  // Can't use <1 in optstring because zero arguments with -f isn't an error
  95  if (!toys.optc && !FLAG(f)) help_exit("Needs 1 argument");
  96
  97  for (s = toys.optargs; *s; s++) {
  98    if (!strcmp(*s, "/")) {
  99      error_msg("rm /. if you mean it");
 100      continue;
 101    }
 102    // "rm dir/.*" can expand to include .. which generally isn't what you want
 103    if (!strcmp("..", basename(*s))) {
 104      error_msg("bad path %s", *s);
 105      continue;
 106    }
 107
 108    // Files that already don't exist aren't errors for -f. Use lstat() instead
 109    // of faccessat() because bionic doesn't support AT_SYMLINK_NOFOLLOW
 110    if (FLAG(f) && lstat(*s, (void *)toybuf) && errno == ENOENT) continue;
 111
 112    // There's a race here where a file removed between the above check and
 113    // dirtree's stat would report the nonexistence as an error, but that's
 114    // not a normal "it didn't exist" so I'm ok with it.
 115    dirtree_read(*s, do_rm);
 116  }
 117}
 118