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 (!dirtree_notdotdot(try)) 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    fprintf(stderr, "rm %s%s", or ? "ro " : "", dir ? "dir " : "");
43    or = yesno(s, 0);
44    free(s);
45    if (!or) goto nodelete;
46  }
47
48  // handle directory recursion
49  if (dir) {
50    using = AT_REMOVEDIR;
51    // Handle chmod 000 directories when -f
52    if (faccessat(fd, try->name, R_OK, 0)) {
53      if (toys.optflags & FLAG_f) wfchmodat(fd, try->name, 0700);
54      else goto skip;
55    }
56    if (!try->again) return DIRTREE_COMEAGAIN;
57    if (try->symlink) goto skip;
58    if (flags & FLAG_i) {
59      char *s = dirtree_path(try, 0);
60      // This is the section 2(d) prompt. (Yes, posix says to prompt twice.)
61      fprintf(stderr, "rmdir ");
62      or = yesno(s, 0);
63      free(s);
64      if (!or) goto nodelete;
65    }
66  }
67
68skip:
69  if (unlinkat(fd, try->name, using)) {
70    if (!dir || try->symlink != (char *)2) perror_msg("%s", try->name);
71nodelete:
72    if (try->parent) try->parent->symlink = (char *)2;
73  }
74
75  return 0;
76}
77
78void rm_main(void)
79{
80  char **s;
81
82  // Can't use <1 in optstring because zero arguments with -f isn't an error
83  if (!toys.optc && !(toys.optflags & FLAG_f)) error_exit("Needs 1 argument");
84
85  for (s = toys.optargs; *s; s++) {
86    if (!strcmp(*s, "/")) {
87      error_msg("rm /. if you mean it");
88      continue;
89    }
90
91    // Files that already don't exist aren't errors for -f, so try a quick
92    // unlink now to see if it succeeds or reports that it didn't exist.
93    if ((toys.optflags & FLAG_f) && (!unlink(*s) || errno == ENOENT))
94      continue;
95
96    // There's a race here where a file removed between the above check and
97    // dirtree's stat would report the nonexistence as an error, but that's
98    // not a normal "it didn't exist" so I'm ok with it.
99
100    dirtree_read(*s, do_rm);
101  }
102}
103