1 /*- 2 * Copyright (c) 1990 The Regents of the University of California. 3 * All rights reserved. 4 * 5 * %sccs.include.redist.c% 6 */ 7 8 #ifndef lint 9 char copyright[] = 10 "@(#) Copyright (c) 1990 The Regents of the University of California.\n\ 11 All rights reserved.\n"; 12 #endif /* not lint */ 13 14 #ifndef lint 15 static char sccsid[] = "@(#)rm.c 5.1 (Berkeley) 04/29/93"; 16 #endif /* not lint */ 17 18 #include <sys/types.h> 19 #include <sys/stat.h> 20 #include <sys/errno.h> 21 22 #include <err.h> 23 #include <fts.h> 24 #include <stdio.h> 25 #include <stdlib.h> 26 #include <string.h> 27 #include <unistd.h> 28 29 int dflag, fflag, iflag, eval, stdin_ok; 30 31 int check __P((char *, char *, struct stat *)); 32 void checkdot __P((char **)); 33 void rmfile __P((char **)); 34 void rmtree __P((char **)); 35 void usage __P((void)); 36 37 /* 38 * rm -- 39 * This rm is different from historic rm's, but is expected to match 40 * POSIX 1003.2 behavior. The most visible difference is that -f 41 * has two specific effects now, ignore non-existent files and force 42 * file removal. 43 */ 44 int 45 main(argc, argv) 46 int argc; 47 char *argv[]; 48 { 49 extern int optind; 50 int ch, rflag; 51 52 rflag = 0; 53 while ((ch = getopt(argc, argv, "dfiRr")) != EOF) 54 switch(ch) { 55 case 'd': 56 dflag = 1; 57 break; 58 case 'f': 59 fflag = 1; 60 iflag = 0; 61 break; 62 case 'i': 63 fflag = 0; 64 iflag = 1; 65 break; 66 case 'R': 67 case 'r': /* compatibility */ 68 rflag = 1; 69 break; 70 case '?': 71 default: 72 usage(); 73 } 74 argc -= optind; 75 argv += optind; 76 77 if (argc < 1) 78 usage(); 79 80 checkdot(argv); 81 if (!*argv) 82 exit (eval); 83 84 stdin_ok = isatty(STDIN_FILENO); 85 86 if (rflag) 87 rmtree(argv); 88 else 89 rmfile(argv); 90 exit (eval); 91 } 92 93 void 94 rmtree(argv) 95 char **argv; 96 { 97 register FTS *fts; 98 register FTSENT *p; 99 register int needstat; 100 101 /* 102 * Remove a file hierarchy. If forcing removal (-f), or interactive 103 * (-i) or can't ask anyway (stdin_ok), don't stat the file. 104 */ 105 needstat = !fflag && !iflag && stdin_ok; 106 107 /* 108 * If the -i option is specified, the user can skip on the pre-order 109 * visit. The fts_number field flags skipped directories. 110 */ 111 #define SKIPPED 1 112 113 if (!(fts = fts_open(argv, 114 needstat ? FTS_PHYSICAL : FTS_PHYSICAL|FTS_NOSTAT, 115 (int (*)())NULL))) 116 err(1, NULL); 117 while (p = fts_read(fts)) { 118 switch (p->fts_info) { 119 case FTS_DNR: 120 case FTS_ERR: 121 err(1, "%s", p->fts_path); 122 123 case FTS_NS: 124 /* 125 * FTS_NS: assume that if can't stat the file, it 126 * can't be unlinked. 127 */ 128 if (!needstat) 129 break; 130 if (!fflag || errno != ENOENT) { 131 warn("%s", p->fts_path); 132 eval = 1; 133 } 134 continue; 135 136 case FTS_D: 137 /* Pre-order: give user chance to skip. */ 138 if (iflag && !check(p->fts_path, p->fts_accpath, 139 p->fts_statp)) { 140 (void)fts_set(fts, p, FTS_SKIP); 141 p->fts_number = SKIPPED; 142 } 143 continue; 144 145 case FTS_DP: 146 /* Post-order: see if user skipped. */ 147 if (p->fts_number == SKIPPED) 148 continue; 149 break; 150 } 151 152 if (!fflag && 153 !check(p->fts_path, p->fts_accpath, p->fts_statp)) 154 continue; 155 156 /* 157 * If we can't read or search the directory, may still be 158 * able to remove it. Don't print out the un{read,search}able 159 * message unless the remove fails. 160 */ 161 if (p->fts_info == FTS_DP || p->fts_info == FTS_DNR) { 162 if (!rmdir(p->fts_accpath)) 163 continue; 164 if (errno == ENOENT) { 165 if (fflag) 166 continue; 167 } else if (p->fts_info != FTS_DP) 168 warnx("%s: unable to read", p->fts_path); 169 } else if (!unlink(p->fts_accpath) || fflag && errno == ENOENT) 170 continue; 171 warn("%s", p->fts_path); 172 eval = 1; 173 } 174 } 175 176 void 177 rmfile(argv) 178 char **argv; 179 { 180 register int df; 181 register char *f; 182 struct stat sb; 183 184 df = dflag; 185 /* 186 * Remove a file. POSIX 1003.2 states that, by default, attempting 187 * to remove a directory is an error, so must always stat the file. 188 */ 189 while (f = *argv++) { 190 /* Assume if can't stat the file, can't unlink it. */ 191 if (lstat(f, &sb)) { 192 if (!fflag || errno != ENOENT) { 193 warn("%s", f); 194 eval = 1; 195 } 196 continue; 197 } 198 if (S_ISDIR(sb.st_mode) && !df) { 199 warnx("%s: is a directory", f); 200 eval = 1; 201 continue; 202 } 203 if (!fflag && !check(f, f, &sb)) 204 continue; 205 if ((S_ISDIR(sb.st_mode) ? rmdir(f) : unlink(f)) && 206 (!fflag || errno != ENOENT)) { 207 warn("%s", f); 208 eval = 1; 209 } 210 } 211 } 212 213 int 214 check(path, name, sp) 215 char *path, *name; 216 struct stat *sp; 217 { 218 register int first, ch; 219 char modep[15]; 220 221 /* Check -i first. */ 222 if (iflag) 223 (void)fprintf(stderr, "remove %s? ", path); 224 else { 225 /* 226 * If it's not a symbolic link and it's unwritable and we're 227 * talking to a terminal, ask. Symbolic links are excluded 228 * because their permissions are meaningless. 229 */ 230 if (S_ISLNK(sp->st_mode) || !stdin_ok || !access(name, W_OK)) 231 return (1); 232 strmode(sp->st_mode, modep); 233 (void)fprintf(stderr, "override %s%s%s/%s for %s? ", 234 modep + 1, modep[9] == ' ' ? "" : " ", 235 user_from_uid(sp->st_uid, 0), 236 group_from_gid(sp->st_gid, 0), path); 237 } 238 (void)fflush(stderr); 239 240 first = ch = getchar(); 241 while (ch != '\n' && ch != EOF) 242 ch = getchar(); 243 return (first == 'y'); 244 } 245 246 #define ISDOT(a) ((a)[0] == '.' && (!(a)[1] || (a)[1] == '.' && !(a)[2])) 247 void 248 checkdot(argv) 249 char **argv; 250 { 251 register char *p, **t, **save; 252 int complained; 253 254 complained = 0; 255 for (t = argv; *t;) { 256 if (p = strrchr(*t, '/')) 257 ++p; 258 else 259 p = *t; 260 if (ISDOT(p)) { 261 if (!complained++) 262 warnx("\".\" and \"..\" may not be removed"); 263 eval = 1; 264 for (save = t; t[0] = t[1]; ++t); 265 t = save; 266 } else 267 ++t; 268 } 269 } 270 271 void 272 usage() 273 { 274 (void)fprintf(stderr, "usage: rm [-dfiRr] file ...\n"); 275 exit(1); 276 } 277