1/*- 2 * Copyright (c) 1990, 1993, 1994 3 * The Regents of the University of California. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. All advertising materials mentioning features or use of this software 14 * must display the following acknowledgement: 15 * This product includes software developed by the University of 16 * California, Berkeley and its contributors. 17 * 4. Neither the name of the University nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 34#include <sys/cdefs.h> 35#ifndef lint 36__used static const char copyright[] = 37"@(#) Copyright (c) 1990, 1993, 1994\n\ 38 The Regents of the University of California. All rights reserved.\n"; 39#endif /* not lint */ 40 41#ifndef lint 42#if 0 43static char sccsid[] = "@(#)rm.c 8.5 (Berkeley) 4/18/94"; 44#else 45__used static const char rcsid[] = 46 "$FreeBSD: src/bin/rm/rm.c,v 1.33 2001/06/13 15:01:25 ru Exp $"; 47#endif 48#endif /* not lint */ 49 50#include <sys/stat.h> 51#include <sys/param.h> 52#include <sys/mount.h> 53 54#include <err.h> 55#include <errno.h> 56#include <fcntl.h> 57#include <fts.h> 58#include <stdio.h> 59#include <stdlib.h> 60#include <string.h> 61#include <sysexits.h> 62#include <unistd.h> 63 64#ifdef __APPLE__ 65#include <removefile.h> 66#include <pwd.h> 67#include <grp.h> 68#include "get_compat.h" 69#else 70#define COMPAT_MODE(func, mode) 1 71#endif 72 73int dflag, eval, fflag, iflag, Pflag, vflag, Wflag, stdin_ok; 74uid_t uid; 75 76int check __P((char *, char *, struct stat *)); 77int checkdir __P((char *)); 78int yes_or_no __P((void)); 79void checkdot __P((char **)); 80void rm_file __P((char **)); 81void rm_overwrite __P((char *, struct stat *)); 82void rm_tree __P((char **)); 83void usage __P((void)); 84 85/* 86 * rm -- 87 * This rm is different from historic rm's, but is expected to match 88 * POSIX 1003.2 behavior. The most visible difference is that -f 89 * has two specific effects now, ignore non-existent files and force 90 * file removal. 91 */ 92int 93main(argc, argv) 94 int argc; 95 char *argv[]; 96{ 97 int ch, rflag; 98 char *p; 99 100 if (argc < 1) 101 usage(); 102 103 /* 104 * Test for the special case where the utility is called as 105 * "unlink", for which the functionality provided is greatly 106 * simplified. 107 */ 108 if ((p = rindex(argv[0], '/')) == NULL) 109 p = argv[0]; 110 else 111 ++p; 112 uid = geteuid(); 113 if (strcmp(p, "unlink") == 0) { 114 if (argc == 2) { 115 rm_file(&argv[1]); 116 exit(eval); 117 } else 118 usage(); 119 } 120 121 Pflag = rflag = 0; 122 while ((ch = getopt(argc, argv, "dfiPRrvW")) != -1) 123 switch(ch) { 124 case 'd': 125 dflag = 1; 126 break; 127 case 'f': 128 fflag = 1; 129 iflag = 0; 130 break; 131 case 'i': 132 fflag = 0; 133 iflag = 1; 134 break; 135 case 'P': 136 Pflag = 1; 137 break; 138 case 'R': 139 case 'r': /* Compatibility. */ 140 rflag = 1; 141 break; 142 case 'v': 143 vflag = 1; 144 break; 145 case 'W': 146 Wflag = 1; 147 break; 148 default: 149 usage(); 150 } 151 argc -= optind; 152 argv += optind; 153 154 if (argc < 1) { 155 if (fflag) 156 return 0; 157 usage(); 158 } 159 160 checkdot(argv); 161 162 if (*argv) { 163 stdin_ok = isatty(STDIN_FILENO); 164 165 if (rflag) 166 rm_tree(argv); 167 else 168 rm_file(argv); 169 } 170 171 exit (eval); 172} 173 174void 175rm_tree(argv) 176 char **argv; 177{ 178 FTS *fts; 179 FTSENT *p; 180 int needstat; 181 int flags; 182 int rval; 183 int wantConformance = COMPAT_MODE("bin/rm", "unix2003"); 184 /* 185 * Remove a file hierarchy. If forcing removal (-f), or interactive 186 * (-i) or can't ask anyway (stdin_ok), don't stat the file. 187 */ 188 needstat = !uid || (!fflag && !iflag && stdin_ok); 189 190 /* 191 * If the -i option is specified, the user can skip on the pre-order 192 * visit. The fts_number field flags skipped directories. 193 */ 194#define SKIPPED 1 195 196 flags = FTS_PHYSICAL; 197 if (!needstat) 198 flags |= FTS_NOSTAT; 199 if (Wflag) 200 flags |= FTS_WHITEOUT; 201 if (!(fts = fts_open(argv, flags, NULL))) { 202 if (fflag && errno == ENOENT) 203 return; 204 err(1, NULL); 205 } 206 while ((p = fts_read(fts)) != NULL) { 207 switch (p->fts_info) { 208 case FTS_DNR: 209 if (!fflag || p->fts_errno != ENOENT) { 210 warnx("%s: %s", 211 p->fts_path, strerror(p->fts_errno)); 212 eval = 1; 213 } 214 continue; 215 case FTS_ERR: 216 errx(1, "%s: %s", p->fts_path, strerror(p->fts_errno)); 217 case FTS_NS: 218 /* 219 * FTS_NS: assume that if can't stat the file, it 220 * can't be unlinked. 221 */ 222 if (!needstat) 223 break; 224 if (!fflag || p->fts_errno != ENOENT) { 225 warnx("%s: %s", 226 p->fts_path, strerror(p->fts_errno)); 227 eval = 1; 228 } 229 continue; 230 case FTS_D: 231 /* Pre-order: give user chance to skip. */ 232 /* In conformance mode the user is prompted to skip processing the contents. 233 * Then the option to delete the dir is presented post-order */ 234 if (!fflag && 235 ( (wantConformance && !checkdir(p->fts_path)) || 236 (!wantConformance && !check(p->fts_path, p->fts_accpath, p->fts_statp)) 237 ) 238 ){ 239 (void)fts_set(fts, p, FTS_SKIP); 240 p->fts_number = SKIPPED; 241 } 242 else if (!uid && 243 (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) && 244 !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) && 245 chflags(p->fts_accpath, 246 p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE)) < 0) 247 goto err; 248 continue; 249 case FTS_DP: 250 /* Post-order: see if user skipped. */ 251 if(p->fts_number == SKIPPED)/*in legacy mode, the user was prompted pre-order */ 252 continue; 253 else if(wantConformance) 254 { 255 /* delete directory if force is on, or if user answers Y to prompt */ 256 if(fflag || check(p->fts_path, p->fts_accpath, p->fts_statp)) 257 break; 258 else 259 continue; 260 } 261 break; 262 default: 263 if (!fflag && 264 !check(p->fts_path, p->fts_accpath, p->fts_statp)) 265 continue; 266 } 267 268 rval = 0; 269 if (!uid && 270 (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) && 271 !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE))) 272 rval = chflags(p->fts_accpath, 273 p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE)); 274 if (rval == 0) { 275 /* 276 * If we can't read or search the directory, may still be 277 * able to remove it. Don't print out the un{read,search}able 278 * message unless the remove fails. 279 */ 280 switch (p->fts_info) { 281 case FTS_DP: 282 case FTS_DNR: 283 rval = rmdir(p->fts_accpath); 284 if (rval == 0 || (fflag && errno == ENOENT)) { 285 if (rval == 0 && vflag) 286 (void)printf("%s\n", 287 p->fts_path); 288 continue; 289 } 290 break; 291 292 case FTS_W: 293 rval = undelete(p->fts_accpath); 294 if (rval == 0 && (fflag && errno == ENOENT)) { 295 if (vflag) 296 (void)printf("%s\n", 297 p->fts_path); 298 continue; 299 } 300 break; 301 302 default: 303#ifdef __APPLE__ 304 if (Pflag) { 305 if (removefile(p->fts_accpath, NULL, REMOVEFILE_SECURE_7_PASS)) /* overwrites and unlinks */ 306 eval = rval = 1; 307 } else 308 rval = unlink(p->fts_accpath); 309#else /* !__APPLE_ */ 310 if (Pflag) 311 rm_overwrite(p->fts_accpath, NULL); 312 rval = unlink(p->fts_accpath); 313#endif /* __APPLE__ */ 314 if (rval == 0 || (fflag && errno == ENOENT)) { 315 if (rval == 0 && vflag) 316 (void)printf("%s\n", 317 p->fts_path); 318 continue; 319 } 320 } 321 } 322err: 323 warn("%s", p->fts_path); 324 eval = 1; 325 } 326 if (errno) 327 err(1, "fts_read"); 328 fts_close(fts); 329} 330 331void 332rm_file(argv) 333 char **argv; 334{ 335 struct stat sb; 336 int rval; 337 char *f; 338 339 /* 340 * Remove a file. POSIX 1003.2 states that, by default, attempting 341 * to remove a directory is an error, so must always stat the file. 342 */ 343 while ((f = *argv++) != NULL) { 344 /* Assume if can't stat the file, can't unlink it. */ 345 if (lstat(f, &sb)) { 346 if (Wflag) { 347 sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR; 348 } else { 349 if (!fflag || errno != ENOENT) { 350 warn("%s", f); 351 eval = 1; 352 } 353 continue; 354 } 355 } else if (Wflag) { 356 warnx("%s: %s", f, strerror(EEXIST)); 357 eval = 1; 358 continue; 359 } 360 361 if (S_ISDIR(sb.st_mode) && !dflag) { 362 warnx("%s: is a directory", f); 363 eval = 1; 364 continue; 365 } 366 if (!fflag && !S_ISWHT(sb.st_mode) && !check(f, f, &sb)) 367 continue; 368 rval = 0; 369 if (!uid && 370 (sb.st_flags & (UF_APPEND|UF_IMMUTABLE)) && 371 !(sb.st_flags & (SF_APPEND|SF_IMMUTABLE))) 372 rval = chflags(f, sb.st_flags & ~(UF_APPEND|UF_IMMUTABLE)); 373 if (rval == 0) { 374 if (S_ISWHT(sb.st_mode)) 375 rval = undelete(f); 376 else if (S_ISDIR(sb.st_mode)) 377 rval = rmdir(f); 378 else { 379#ifdef __APPLE__ 380 if (Pflag) { 381 if (removefile(f, NULL, REMOVEFILE_SECURE_7_PASS)) /* overwrites and unlinks */ 382 eval = rval = 1; 383 } else 384 rval = unlink(f); 385#else /* !__APPLE__ */ 386 if (Pflag) 387 rm_overwrite(f, &sb); 388 rval = unlink(f); 389#endif /* __APPLE__ */ 390 } 391 } 392 if (rval && (!fflag || errno != ENOENT)) { 393 warn("%s", f); 394 eval = 1; 395 } 396 if (vflag && rval == 0) 397 (void)printf("%s\n", f); 398 } 399} 400 401/* 402 * rm_overwrite -- 403 * Overwrite the file 3 times with varying bit patterns. 404 * 405 * XXX 406 * This is a cheap way to *really* delete files. Note that only regular 407 * files are deleted, directories (and therefore names) will remain. 408 * Also, this assumes a fixed-block file system (like FFS, or a V7 or a 409 * System V file system). In a logging file system, you'll have to have 410 * kernel support. 411 */ 412void 413rm_overwrite(file, sbp) 414 char *file; 415 struct stat *sbp; 416{ 417 struct stat sb; 418 struct statfs fsb; 419 off_t len; 420 int bsize, fd, wlen; 421 char *buf = NULL; 422 423 if (sbp == NULL) { 424 if (lstat(file, &sb)) 425 goto err; 426 sbp = &sb; 427 } 428 if (!S_ISREG(sbp->st_mode)) 429 return; 430 if ((fd = open(file, O_WRONLY, 0)) == -1) 431 goto err; 432 if (fstatfs(fd, &fsb) == -1) 433 goto err; 434 bsize = MAX(fsb.f_iosize, 1024); 435 if ((buf = malloc(bsize)) == NULL) 436 err(1, "malloc"); 437 438#define PASS(byte) { \ 439 memset(buf, byte, bsize); \ 440 for (len = sbp->st_size; len > 0; len -= wlen) { \ 441 wlen = len < bsize ? (int)len : bsize; \ 442 if (write(fd, buf, wlen) != wlen) \ 443 goto err; \ 444 } \ 445} 446 PASS(0xff); 447 if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET)) 448 goto err; 449 PASS(0x00); 450 if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET)) 451 goto err; 452 PASS(0xff); 453 if (!fsync(fd) && !close(fd)) { 454 free(buf); 455 return; 456 } 457 458err: eval = 1; 459 if (buf) 460 free(buf); 461 warn("%s", file); 462} 463 464int 465yes_or_no() 466{ 467 int ch, first; 468 (void)fflush(stderr); 469 470 first = ch = getchar(); 471 while (ch != '\n' && ch != EOF) 472 ch = getchar(); 473 return (first == 'y' || first == 'Y'); 474} 475 476int 477checkdir(path) 478 char *path; 479{ 480 if(!iflag) 481 return 1; //if not interactive, process directory's contents 482 (void)fprintf(stderr, "examine files in directory %s? ", path); 483 return yes_or_no(); 484} 485 486int 487check(path, name, sp) 488 char *path, *name; 489 struct stat *sp; 490{ 491 char modep[15], *flagsp; 492 493 /* Check -i first. */ 494 if (iflag) 495 (void)fprintf(stderr, "remove %s? ", path); 496 else { 497 /* 498 * If it's not a symbolic link and it's unwritable and we're 499 * talking to a terminal, ask. Symbolic links are excluded 500 * because their permissions are meaningless. Check stdin_ok 501 * first because we may not have stat'ed the file. 502 */ 503 if (!stdin_ok || S_ISLNK(sp->st_mode) || 504 (!access(name, W_OK) && 505 !(sp->st_flags & (SF_APPEND|SF_IMMUTABLE)) && 506 (!(sp->st_flags & (UF_APPEND|UF_IMMUTABLE)) || !uid))) 507 return (1); 508 strmode(sp->st_mode, modep); 509 if ((flagsp = fflagstostr(sp->st_flags)) == NULL) 510 err(1, NULL); 511 (void)fprintf(stderr, "override %s%s%s/%s %s%sfor %s? ", 512 modep + 1, modep[9] == ' ' ? "" : " ", 513 user_from_uid(sp->st_uid, 0), 514 group_from_gid(sp->st_gid, 0), 515 *flagsp ? flagsp : "", *flagsp ? " " : "", 516 path); 517 free(flagsp); 518 } 519 return yes_or_no(); 520} 521 522 523#define ISDOT(a) ((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2]))) 524void 525checkdot(argv) 526 char **argv; 527{ 528 char *p, **save, **t; 529 int complained; 530 531 complained = 0; 532 for (t = argv; *t;) { 533 if ((p = strrchr(*t, '/')) != NULL) 534 ++p; 535 else 536 p = *t; 537 if (ISDOT(p)) { 538 if (!complained++) 539 warnx("\".\" and \"..\" may not be removed"); 540 eval = 1; 541 for (save = t; (t[0] = t[1]) != NULL; ++t) 542 continue; 543 t = save; 544 } else 545 ++t; 546 } 547} 548 549void 550usage() 551{ 552 553 (void)fprintf(stderr, "%s\n%s\n", 554 "usage: rm [-f | -i] [-dPRrvW] file ...", 555 " unlink file"); 556 exit(EX_USAGE); 557} 558