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, "fiRrv[-fi]", TOYFLAG_BIN)) 8 9config RM 10 bool "rm" 11 default y 12 help 13 usage: rm [-fiRrv] 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 -v Verbose 21*/ 22 23#define FOR_rm 24#include "toys.h" 25 26static int do_rm(struct dirtree *try) 27{ 28 int fd=dirtree_parentfd(try), 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 && !(toys.optflags & (FLAG_r|FLAG_R))) goto skip; 36 37 // This is either the posix section 2(b) prompt or the section 3 prompt. 38 if (!FLAG(f) 39 && (!S_ISLNK(try->st.st_mode) && faccessat(fd, try->name, W_OK, 0))) or++; 40 41 // Posix section 1(a), don't prompt for nonexistent. 42 if (or && errno == ENOENT) goto skip; 43 44 if (!(dir && try->again) && ((or && isatty(0)) || FLAG(i))) { 45 char *s = dirtree_path(try, 0); 46 47 fprintf(stderr, "rm %s%s%s", or ? "ro " : "", dir ? "dir " : "", s); 48 free(s); 49 or = yesno(0); 50 if (!or) goto nodelete; 51 } 52 53 // handle directory recursion 54 if (dir) { 55 using = AT_REMOVEDIR; 56 // Handle chmod 000 directories when -f 57 if (faccessat(fd, try->name, R_OK, 0)) { 58 if (FLAG(f)) wfchmodat(fd, try->name, 0700); 59 else goto skip; 60 } 61 if (!try->again) return DIRTREE_COMEAGAIN; 62 if (try->symlink) goto skip; 63 if (FLAG(i)) { 64 char *s = dirtree_path(try, 0); 65 66 // This is the section 2(d) prompt. (Yes, posix says to prompt twice.) 67 fprintf(stderr, "rmdir %s", s); 68 free(s); 69 or = yesno(0); 70 if (!or) goto nodelete; 71 } 72 } 73 74skip: 75 if (!unlinkat(fd, try->name, using)) { 76 if (FLAG(v)) { 77 char *s = dirtree_path(try, 0); 78 printf("%s%s '%s'\n", toys.which->name, dir ? "dir" : "", s); 79 free(s); 80 } 81 } else { 82 if (!dir || try->symlink != (char *)2) perror_msg_raw(try->name); 83nodelete: 84 if (try->parent) try->parent->symlink = (char *)2; 85 } 86 87 return 0; 88} 89 90void rm_main(void) 91{ 92 char **s; 93 94 // Can't use <1 in optstring because zero arguments with -f isn't an error 95 if (!toys.optc && !FLAG(f)) help_exit("Needs 1 argument"); 96 97 for (s = toys.optargs; *s; s++) { 98 if (!strcmp(*s, "/")) { 99 error_msg("rm /. if you mean it"); 100 continue; 101 } 102 // "rm dir/.*" can expand to include .. which generally isn't what you want 103 if (!strcmp("..", basename(*s))) { 104 error_msg("bad path %s", *s); 105 continue; 106 } 107 108 // Files that already don't exist aren't errors for -f. Use lstat() instead 109 // of faccessat() because bionic doesn't support AT_SYMLINK_NOFOLLOW 110 if (FLAG(f) && lstat(*s, (void *)toybuf) && errno == ENOENT) continue; 111 112 // There's a race here where a file removed between the above check and 113 // dirtree's stat would report the nonexistence as an error, but that's 114 // not a normal "it didn't exist" so I'm ok with it. 115 dirtree_read(*s, do_rm); 116 } 117} 118