1 /*- 2 * Copyright (c) 1990, 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * %sccs.include.redist.c% 6 */ 7 8 #ifndef lint 9 static char copyright[] = 10 "@(#) Copyright (c) 1990, 1993\n\ 11 The Regents of the University of California. All rights reserved.\n"; 12 #endif /* not lint */ 13 14 #ifndef lint 15 static char sccsid[] = "@(#)rm.c 8.2 (Berkeley) 01/02/94"; 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 if (!fflag || errno != ENOENT) { 121 warn("%s", p->fts_path); 122 eval = 1; 123 } 124 continue; 125 case FTS_ERR: 126 err(1, "%s", p->fts_path); 127 case FTS_NS: 128 /* 129 * FTS_NS: assume that if can't stat the file, it 130 * can't be unlinked. 131 */ 132 if (!needstat) 133 break; 134 if (!fflag || errno != ENOENT) { 135 warn("%s", p->fts_path); 136 eval = 1; 137 } 138 continue; 139 case FTS_D: 140 /* Pre-order: give user chance to skip. */ 141 if (iflag && !check(p->fts_path, p->fts_accpath, 142 p->fts_statp)) { 143 (void)fts_set(fts, p, FTS_SKIP); 144 p->fts_number = SKIPPED; 145 } 146 continue; 147 case FTS_DP: 148 /* Post-order: see if user skipped. */ 149 if (p->fts_number == SKIPPED) 150 continue; 151 break; 152 } 153 if (!fflag && 154 !check(p->fts_path, p->fts_accpath, p->fts_statp)) 155 continue; 156 157 /* 158 * If we can't read or search the directory, may still be 159 * able to remove it. Don't print out the un{read,search}able 160 * message unless the remove fails. 161 */ 162 if (p->fts_info == FTS_DP || p->fts_info == FTS_DNR) { 163 if (!rmdir(p->fts_accpath)) 164 continue; 165 if (errno == ENOENT) { 166 if (fflag) 167 continue; 168 } else if (p->fts_info != FTS_DP) 169 warnx("%s: unable to read", p->fts_path); 170 } else if (!unlink(p->fts_accpath) || fflag && errno == ENOENT) 171 continue; 172 warn("%s", p->fts_path); 173 eval = 1; 174 } 175 } 176 177 void 178 rmfile(argv) 179 char **argv; 180 { 181 register int df; 182 register char *f; 183 struct stat sb; 184 185 df = dflag; 186 /* 187 * Remove a file. POSIX 1003.2 states that, by default, attempting 188 * to remove a directory is an error, so must always stat the file. 189 */ 190 while (f = *argv++) { 191 /* Assume if can't stat the file, can't unlink it. */ 192 if (lstat(f, &sb)) { 193 if (!fflag || errno != ENOENT) { 194 warn("%s", f); 195 eval = 1; 196 } 197 continue; 198 } 199 if (S_ISDIR(sb.st_mode) && !df) { 200 warnx("%s: is a directory", f); 201 eval = 1; 202 continue; 203 } 204 if (!fflag && !check(f, f, &sb)) 205 continue; 206 if ((S_ISDIR(sb.st_mode) ? rmdir(f) : unlink(f)) && 207 (!fflag || errno != ENOENT)) { 208 warn("%s", f); 209 eval = 1; 210 } 211 } 212 } 213 214 int 215 check(path, name, sp) 216 char *path, *name; 217 struct stat *sp; 218 { 219 register int first, ch; 220 char modep[15]; 221 222 /* Check -i first. */ 223 if (iflag) 224 (void)fprintf(stderr, "remove %s? ", path); 225 else { 226 /* 227 * If it's not a symbolic link and it's unwritable and we're 228 * talking to a terminal, ask. Symbolic links are excluded 229 * because their permissions are meaningless. Check stdin_ok 230 * first because we may not have stat'ed the file. 231 */ 232 if (!stdin_ok || S_ISLNK(sp->st_mode) || !access(name, W_OK)) 233 return (1); 234 strmode(sp->st_mode, modep); 235 (void)fprintf(stderr, "override %s%s%s/%s for %s? ", 236 modep + 1, modep[9] == ' ' ? "" : " ", 237 user_from_uid(sp->st_uid, 0), 238 group_from_gid(sp->st_gid, 0), path); 239 } 240 (void)fflush(stderr); 241 242 first = ch = getchar(); 243 while (ch != '\n' && ch != EOF) 244 ch = getchar(); 245 return (first == 'y'); 246 } 247 248 #define ISDOT(a) ((a)[0] == '.' && (!(a)[1] || (a)[1] == '.' && !(a)[2])) 249 void 250 checkdot(argv) 251 char **argv; 252 { 253 register char *p, **t, **save; 254 int complained; 255 256 complained = 0; 257 for (t = argv; *t;) { 258 if (p = strrchr(*t, '/')) 259 ++p; 260 else 261 p = *t; 262 if (ISDOT(p)) { 263 if (!complained++) 264 warnx("\".\" and \"..\" may not be removed"); 265 eval = 1; 266 for (save = t; t[0] = t[1]; ++t); 267 t = save; 268 } else 269 ++t; 270 } 271 } 272 273 void 274 usage() 275 { 276 (void)fprintf(stderr, "usage: rm [-dfiRr] file ...\n"); 277 exit(1); 278 } 279