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