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
7 USE_RM(NEWTOY(rm, "fiRrv[-fi]", TOYFLAG_BIN))
8
9 config RM
10 bool "rm"
11 default y
12 help
13 usage: rm [-fv] FILE or rm [-rv] [PATH]...
14
15 Remove each argument from the filesystem.
16
17 -r Remove empty or non empty directory
18 -f Force: remove without confirmation, no error if it doesn't exist
19 -v Verbose
20 */
21
22 #define FOR_rm
23 #include "toys.h"
24
toybox_cmd_do_rmdir(const char * pathname)25 static int toybox_cmd_do_rmdir(const char *pathname)
26 {
27 struct dirent *dirent = NULL;
28 struct stat stat_info;
29 DIR *dir = NULL;
30 char *fullpath = NULL;
31 int ret;
32
33 (void)memset(&stat_info, 0, sizeof(struct stat));
34 if (stat(pathname, &stat_info) != 0) return -1;
35
36 if (S_ISREG(stat_info.st_mode) || S_ISLNK(stat_info.st_mode)) {
37 return remove(pathname);
38 }
39
40 dir = opendir(pathname);
41 if (dir == NULL) return -1;
42
43 while (1) {
44 dirent = readdir(dir);
45 if (dirent == NULL) break;
46
47 size_t fullpath_buf_size = strlen(pathname) + strlen(dirent->d_name) + 2;
48 if (fullpath_buf_size <= 0) {
49 (void)closedir(dir);
50 return -1;
51 }
52 fullpath = (char *)malloc(fullpath_buf_size);
53 if (fullpath == NULL) {
54 (void)closedir(dir);
55 return -1;
56 }
57 ret = snprintf(fullpath, fullpath_buf_size, "%s/%s", pathname, dirent->d_name);
58 if (ret < 0) {
59 xprintf("name is too long!\n");
60 free(fullpath);
61 (void)closedir(dir);
62 return -1;
63 }
64 (void)toybox_cmd_do_rmdir(fullpath);
65 free(fullpath);
66 }
67 (void)closedir(dir);
68 return rmdir(pathname);
69 }
70
do_rm(struct dirtree * try)71 static int do_rm(struct dirtree *try)
72 {
73 int ret;
74 int len;
75 int fd=dirtree_parentfd(try), dir=S_ISDIR(try->st.st_mode), or=0;
76
77 // Skip . and .. (yes, even explicitly on the command line: posix says to)
78 if (isdotdot(try->name)) {
79 xprintf("refusing to remove '.' or '..'!\n");
80 return 0;
81 }
82
83 // Intentionally fail non-recursive attempts to remove even an empty dir
84 // (via wrong flags to unlinkat) because POSIX says to.
85 if (dir && (toys.optflags & (FLAG_r|FLAG_R))) goto skip;
86
87 // This is either the posix section 2(b) prompt or the section 3 prompt.
88 if (!FLAG(f)
89 && (!S_ISLNK(try->st.st_mode) && faccessat(fd, try->name, W_OK, 0))) or++;
90 if (!(dir && try->again) && ((or && isatty(0)) || FLAG(i))) {
91 char *s = dirtree_path(try, 0);
92
93 fprintf(stderr, "rm %s%s%s\n", or ? "ro " : "", dir ? "dir " : "", s);
94 free(s);
95 }
96 // handle directory recursion
97 if (dir) {
98 // Handle chmod 000 directories when -f
99 if (faccessat(fd, try->name, R_OK, 0)) {
100 if (FLAG(f)) wfchmodat(fd, try->name, 0700);
101 else goto skip;
102 }
103 if (!try->again) return DIRTREE_COMEAGAIN;
104 if (try->symlink) goto skip;
105 if (FLAG(i)) {
106 char *s = dirtree_path(try, 0);
107
108 // This is the section 2(d) prompt. (Yes, posix says to prompt twice.)
109 fprintf(stderr, "rmdir %s", s);
110 free(s);
111 }
112 }
113
114 skip:
115
116 // Delete the last '/' of pathname.
117 len = strlen(try->name);
118 while (len > 0 && try->name[len-1] == '/') len--;
119 try->name[len] = '\0';
120
121 // Remove dir or files
122 if (FLAG(r)) ret = toybox_cmd_do_rmdir(try->name);
123 else ret = unlink(try->name);
124 if (!ret) {
125 if (FLAG(v)) {
126 char *s = dirtree_path(try, 0);
127 xprintf("%s%s '%s'\n", toys.which->name, dir ? "dir" : "", s);
128 free(s);
129 }
130 } else {
131 if (!dir || try->symlink != (char *)2) perror_msg_raw(try->name);
132 }
133 return 0;
134 }
135
rm_main(void)136 void rm_main(void)
137 {
138 char **s;
139
140 // Can't use <1 in optstring because zero arguments with -f isn't an error
141 if (!toys.optc && !FLAG(f)) help_exit("Needs 1 argument");
142
143 for (s = toys.optargs; *s; s++) {
144 if (!strcmp(*s, "/")) {
145 error_msg("rm /. if you mean it");
146 continue;
147 }
148
149 // Files that already don't exist aren't errors for -f, so try a quick
150 // unlink now to see if it succeeds or reports that it didn't exist.
151 if (FLAG(f) && (!unlink(*s) || errno == ENOENT)) continue;
152
153 // There's a race here where a file removed between the above check and
154 // dirtree's stat would report the nonexistence as an error, but that's
155 // not a normal "it didn't exist" so I'm ok with it.
156
157 dirtree_read(*s, do_rm);
158 }
159 }
160