• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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