1 /*- 2 * Copyright (c) 1990, 1993, 1994 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, 1994\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.8 (Berkeley) 04/27/95"; 16 #endif /* not lint */ 17 18 #include <sys/types.h> 19 #include <sys/stat.h> 20 21 #include <err.h> 22 #include <errno.h> 23 #include <fcntl.h> 24 #include <fts.h> 25 #include <stdio.h> 26 #include <stdlib.h> 27 #include <string.h> 28 #include <unistd.h> 29 30 int dflag, eval, fflag, iflag, Pflag, Wflag, stdin_ok; 31 32 int check __P((char *, char *, struct stat *)); 33 void checkdot __P((char **)); 34 void rm_file __P((char **)); 35 void rm_overwrite __P((char *, struct stat *)); 36 void rm_tree __P((char **)); 37 void usage __P((void)); 38 39 /* 40 * rm -- 41 * This rm is different from historic rm's, but is expected to match 42 * POSIX 1003.2 behavior. The most visible difference is that -f 43 * has two specific effects now, ignore non-existent files and force 44 * file removal. 45 */ 46 int 47 main(argc, argv) 48 int argc; 49 char *argv[]; 50 { 51 int ch, rflag; 52 53 Pflag = rflag = 0; 54 while ((ch = getopt(argc, argv, "dfiPRrW")) != -1) 55 switch(ch) { 56 case 'd': 57 dflag = 1; 58 break; 59 case 'f': 60 fflag = 1; 61 iflag = 0; 62 break; 63 case 'i': 64 fflag = 0; 65 iflag = 1; 66 break; 67 case 'P': 68 Pflag = 1; 69 break; 70 case 'R': 71 case 'r': /* Compatibility. */ 72 rflag = 1; 73 break; 74 case 'W': 75 Wflag = 1; 76 break; 77 case '?': 78 default: 79 usage(); 80 } 81 argc -= optind; 82 argv += optind; 83 84 if (argc < 1) 85 usage(); 86 87 checkdot(argv); 88 89 if (*argv) { 90 stdin_ok = isatty(STDIN_FILENO); 91 92 if (rflag) 93 rm_tree(argv); 94 else 95 rm_file(argv); 96 } 97 98 exit (eval); 99 } 100 101 void 102 rm_tree(argv) 103 char **argv; 104 { 105 FTS *fts; 106 FTSENT *p; 107 int needstat; 108 int flags; 109 110 /* 111 * Remove a file hierarchy. If forcing removal (-f), or interactive 112 * (-i) or can't ask anyway (stdin_ok), don't stat the file. 113 */ 114 needstat = !fflag && !iflag && stdin_ok; 115 116 /* 117 * If the -i option is specified, the user can skip on the pre-order 118 * visit. The fts_number field flags skipped directories. 119 */ 120 #define SKIPPED 1 121 122 flags = FTS_PHYSICAL; 123 if (!needstat) 124 flags |= FTS_NOSTAT; 125 if (Wflag) 126 flags |= FTS_WHITEOUT; 127 if (!(fts = fts_open(argv, flags, (int (*)())NULL))) 128 err(1, NULL); 129 while ((p = fts_read(fts)) != NULL) { 130 switch (p->fts_info) { 131 case FTS_DNR: 132 if (!fflag || p->fts_errno != ENOENT) { 133 warnx("%s: %s", 134 p->fts_path, strerror(p->fts_errno)); 135 eval = 1; 136 } 137 continue; 138 case FTS_ERR: 139 errx(1, "%s: %s", p->fts_path, strerror(p->fts_errno)); 140 case FTS_NS: 141 /* 142 * FTS_NS: assume that if can't stat the file, it 143 * can't be unlinked. 144 */ 145 if (!needstat) 146 break; 147 if (!fflag || p->fts_errno != ENOENT) { 148 warnx("%s: %s", 149 p->fts_path, strerror(p->fts_errno)); 150 eval = 1; 151 } 152 continue; 153 case FTS_D: 154 /* Pre-order: give user chance to skip. */ 155 if (!fflag && !check(p->fts_path, p->fts_accpath, 156 p->fts_statp)) { 157 (void)fts_set(fts, p, FTS_SKIP); 158 p->fts_number = SKIPPED; 159 } 160 continue; 161 case FTS_DP: 162 /* Post-order: see if user skipped. */ 163 if (p->fts_number == SKIPPED) 164 continue; 165 break; 166 default: 167 if (!fflag && 168 !check(p->fts_path, p->fts_accpath, p->fts_statp)) 169 continue; 170 } 171 172 /* 173 * If we can't read or search the directory, may still be 174 * able to remove it. Don't print out the un{read,search}able 175 * message unless the remove fails. 176 */ 177 switch (p->fts_info) { 178 case FTS_DP: 179 case FTS_DNR: 180 if (!rmdir(p->fts_accpath) || fflag && errno == ENOENT) 181 continue; 182 break; 183 184 case FTS_W: 185 if (!undelete(p->fts_accpath) || 186 fflag && errno == ENOENT) 187 continue; 188 break; 189 190 default: 191 if (Pflag) 192 rm_overwrite(p->fts_accpath, NULL); 193 if (!unlink(p->fts_accpath) || fflag && errno == ENOENT) 194 continue; 195 } 196 warn("%s", p->fts_path); 197 eval = 1; 198 } 199 if (errno) 200 err(1, "fts_read"); 201 } 202 203 void 204 rm_file(argv) 205 char **argv; 206 { 207 struct stat sb; 208 int rval; 209 char *f; 210 211 /* 212 * Remove a file. POSIX 1003.2 states that, by default, attempting 213 * to remove a directory is an error, so must always stat the file. 214 */ 215 while ((f = *argv++) != NULL) { 216 /* Assume if can't stat the file, can't unlink it. */ 217 if (lstat(f, &sb)) { 218 if (Wflag) { 219 sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR; 220 } else { 221 if (!fflag || errno != ENOENT) { 222 warn("%s", f); 223 eval = 1; 224 } 225 continue; 226 } 227 } else if (Wflag) { 228 warnx("%s: %s", f, strerror(EEXIST)); 229 eval = 1; 230 continue; 231 } 232 233 if (S_ISDIR(sb.st_mode) && !dflag) { 234 warnx("%s: is a directory", f); 235 eval = 1; 236 continue; 237 } 238 if (!fflag && !S_ISWHT(sb.st_mode) && !check(f, f, &sb)) 239 continue; 240 if (S_ISWHT(sb.st_mode)) 241 rval = undelete(f); 242 else if (S_ISDIR(sb.st_mode)) 243 rval = rmdir(f); 244 else { 245 if (Pflag) 246 rm_overwrite(f, &sb); 247 rval = unlink(f); 248 } 249 if (rval && (!fflag || errno != ENOENT)) { 250 warn("%s", f); 251 eval = 1; 252 } 253 } 254 } 255 256 /* 257 * rm_overwrite -- 258 * Overwrite the file 3 times with varying bit patterns. 259 * 260 * XXX 261 * This is a cheap way to *really* delete files. Note that only regular 262 * files are deleted, directories (and therefore names) will remain. 263 * Also, this assumes a fixed-block file system (like FFS, or a V7 or a 264 * System V file system). In a logging file system, you'll have to have 265 * kernel support. 266 */ 267 void 268 rm_overwrite(file, sbp) 269 char *file; 270 struct stat *sbp; 271 { 272 struct stat sb; 273 off_t len; 274 int fd, wlen; 275 char buf[8 * 1024]; 276 277 fd = -1; 278 if (sbp == NULL) { 279 if (lstat(file, &sb)) 280 goto err; 281 sbp = &sb; 282 } 283 if (!S_ISREG(sbp->st_mode)) 284 return; 285 if ((fd = open(file, O_WRONLY, 0)) == -1) 286 goto err; 287 288 #define PASS(byte) { \ 289 memset(buf, byte, sizeof(buf)); \ 290 for (len = sbp->st_size; len > 0; len -= wlen) { \ 291 wlen = len < sizeof(buf) ? len : sizeof(buf); \ 292 if (write(fd, buf, wlen) != wlen) \ 293 goto err; \ 294 } \ 295 } 296 PASS(0xff); 297 if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET)) 298 goto err; 299 PASS(0x00); 300 if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET)) 301 goto err; 302 PASS(0xff); 303 if (!fsync(fd) && !close(fd)) 304 return; 305 306 err: eval = 1; 307 warn("%s", file); 308 } 309 310 311 int 312 check(path, name, sp) 313 char *path, *name; 314 struct stat *sp; 315 { 316 int ch, first; 317 char modep[15]; 318 319 /* Check -i first. */ 320 if (iflag) 321 (void)fprintf(stderr, "remove %s? ", path); 322 else { 323 /* 324 * If it's not a symbolic link and it's unwritable and we're 325 * talking to a terminal, ask. Symbolic links are excluded 326 * because their permissions are meaningless. Check stdin_ok 327 * first because we may not have stat'ed the file. 328 */ 329 if (!stdin_ok || S_ISLNK(sp->st_mode) || !access(name, W_OK)) 330 return (1); 331 strmode(sp->st_mode, modep); 332 (void)fprintf(stderr, "override %s%s%s/%s for %s? ", 333 modep + 1, modep[9] == ' ' ? "" : " ", 334 user_from_uid(sp->st_uid, 0), 335 group_from_gid(sp->st_gid, 0), path); 336 } 337 (void)fflush(stderr); 338 339 first = ch = getchar(); 340 while (ch != '\n' && ch != EOF) 341 ch = getchar(); 342 return (first == 'y'); 343 } 344 345 #define ISDOT(a) ((a)[0] == '.' && (!(a)[1] || (a)[1] == '.' && !(a)[2])) 346 void 347 checkdot(argv) 348 char **argv; 349 { 350 char *p, **save, **t; 351 int complained; 352 353 complained = 0; 354 for (t = argv; *t;) { 355 if ((p = strrchr(*t, '/')) != NULL) 356 ++p; 357 else 358 p = *t; 359 if (ISDOT(p)) { 360 if (!complained++) 361 warnx("\".\" and \"..\" may not be removed"); 362 eval = 1; 363 for (save = t; (t[0] = t[1]) != NULL; ++t) 364 continue; 365 t = save; 366 } else 367 ++t; 368 } 369 } 370 371 void 372 usage() 373 { 374 375 (void)fprintf(stderr, "usage: rm [-dfiPRrW] file ...\n"); 376 exit(1); 377 } 378