aboutsummaryrefslogtreecommitdiff
path: root/toys/posix/rm.c
blob: eec586c550ea7c8750ce993b84dc2fb2b70b580f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
/* rm.c - remove files
 *
 * Copyright 2012 Rob Landley <rob@landley.net>
 *
 * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/rm.html

USE_RM(NEWTOY(rm, "fiRrv[-fi]", TOYFLAG_BIN))

config RM
  bool "rm"
  default y
  help
    usage: rm [-fiRrv] FILE...

    Remove each argument from the filesystem.

    -f	Force: remove without confirmation, no error if it doesn't exist
    -i	Interactive: prompt for confirmation
    -rR	Recursive: remove directory contents
    -v	Verbose
*/

#define FOR_rm
#include "toys.h"

static int do_rm(struct dirtree *try)
{
  int fd=dirtree_parentfd(try), dir=S_ISDIR(try->st.st_mode), or=0, using=0;

  // Skip . and .. (yes, even explicitly on the command line: posix says to)
  if (isdotdot(try->name)) return 0;

  // Intentionally fail non-recursive attempts to remove even an empty dir
  // (via wrong flags to unlinkat) because POSIX says to.
  if (dir && !(toys.optflags & (FLAG_r|FLAG_R))) goto skip;

  // This is either the posix section 2(b) prompt or the section 3 prompt.
  if (!FLAG(f)
    && (!S_ISLNK(try->st.st_mode) && faccessat(fd, try->name, W_OK, 0))) or++;

  // Posix section 1(a), don't prompt for nonexistent.
  if (or && errno == ENOENT) goto skip;

  if (!(dir && try->again) && ((or && isatty(0)) || FLAG(i))) {
    char *s = dirtree_path(try, 0);

    fprintf(stderr, "rm %s%s%s", or ? "ro " : "", dir ? "dir " : "", s);
    free(s);
    or = yesno(0);
    if (!or) goto nodelete;
  }

  // handle directory recursion
  if (dir) {
    using = AT_REMOVEDIR;
    // Handle chmod 000 directories when -f
    if (faccessat(fd, try->name, R_OK, 0)) {
      if (FLAG(f)) wfchmodat(fd, try->name, 0700);
      else goto skip;
    }
    if (!try->again) return DIRTREE_COMEAGAIN;
    if (try->symlink) goto skip;
    if (FLAG(i)) {
      char *s = dirtree_path(try, 0);

      // This is the section 2(d) prompt. (Yes, posix says to prompt twice.)
      fprintf(stderr, "rmdir %s", s);
      free(s);
      or = yesno(0);
      if (!or) goto nodelete;
    }
  }

skip:
  if (!unlinkat(fd, try->name, using)) {
    if (FLAG(v)) {
      char *s = dirtree_path(try, 0);
      printf("%s%s '%s'\n", toys.which->name, dir ? "dir" : "", s);
      free(s);
    }
  } else {
    if (!dir || try->symlink != (char *)2) perror_msg_raw(try->name);
nodelete:
    if (try->parent) try->parent->symlink = (char *)2;
  }

  return 0;
}

void rm_main(void)
{
  char **s;

  // Can't use <1 in optstring because zero arguments with -f isn't an error
  if (!toys.optc && !FLAG(f)) help_exit("Needs 1 argument");

  for (s = toys.optargs; *s; s++) {
    if (!strcmp(*s, "/")) {
      error_msg("rm /. if you mean it");
      continue;
    }
    // "rm dir/.*" can expand to include .. which generally isn't what you want
    if (!strcmp("..", basename(*s))) {
      error_msg("bad path %s", *s);
      continue;
    }

    // Files that already don't exist aren't errors for -f, so try a quick
    // unlink now to see if it succeeds or reports that it didn't exist.
    if (FLAG(f) && (!unlink(*s) || errno == ENOENT)) continue;

    // There's a race here where a file removed between the above check and
    // dirtree's stat would report the nonexistence as an error, but that's
    // not a normal "it didn't exist" so I'm ok with it.

    dirtree_read(*s, do_rm);
  }
}