1 /* $NetBSD: rm.c,v 1.26 1999/11/09 15:06:32 drochner Exp $ */ 2 3 /*- 4 * Copyright (c) 1990, 1993, 1994 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. All advertising materials mentioning features or use of this software 16 * must display the following acknowledgement: 17 * This product includes software developed by the University of 18 * California, Berkeley and its contributors. 19 * 4. Neither the name of the University nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36 #include <sys/cdefs.h> 37 #ifndef lint 38 __COPYRIGHT("@(#) Copyright (c) 1990, 1993, 1994\n\ 39 The Regents of the University of California. All rights reserved.\n"); 40 #endif /* not lint */ 41 42 #ifndef lint 43 #if 0 44 static char sccsid[] = "@(#)rm.c 8.8 (Berkeley) 4/27/95"; 45 #else 46 __RCSID("$NetBSD: rm.c,v 1.26 1999/11/09 15:06:32 drochner Exp $"); 47 #endif 48 #endif /* not lint */ 49 50 #include <sys/types.h> 51 #include <sys/stat.h> 52 53 #include <locale.h> 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 <unistd.h> 62 #include <pwd.h> 63 #include <grp.h> 64 65 int dflag, eval, fflag, iflag, Pflag, Wflag, stdin_ok; 66 67 int check __P((char *, char *, struct stat *)); 68 void checkdot __P((char **)); 69 void rm_file __P((char **)); 70 void rm_overwrite __P((char *, struct stat *)); 71 void rm_tree __P((char **)); 72 void usage __P((void)); 73 int main __P((int, char *[])); 74 75 /* 76 * For the sake of the `-f' flag, check whether an error number indicates the 77 * failure of an operation due to an non-existent file, either per se (ENOENT) 78 * or because its filename argument was illegal (ENAMETOOLONG, ENOTDIR). 79 */ 80 #define NONEXISTENT(x) \ 81 ((x) == ENOENT || (x) == ENAMETOOLONG || (x) == ENOTDIR) 82 83 /* 84 * rm -- 85 * This rm is different from historic rm's, but is expected to match 86 * POSIX 1003.2 behavior. The most visible difference is that -f 87 * has two specific effects now, ignore non-existent files and force 88 * file removal. 89 */ 90 int 91 main(argc, argv) 92 int argc; 93 char *argv[]; 94 { 95 int ch, rflag; 96 97 (void)setlocale(LC_ALL, ""); 98 99 Pflag = rflag = 0; 100 while ((ch = getopt(argc, argv, "dfiPRrW")) != -1) 101 switch(ch) { 102 case 'd': 103 dflag = 1; 104 break; 105 case 'f': 106 fflag = 1; 107 iflag = 0; 108 break; 109 case 'i': 110 fflag = 0; 111 iflag = 1; 112 break; 113 case 'P': 114 Pflag = 1; 115 break; 116 case 'R': 117 case 'r': /* Compatibility. */ 118 rflag = 1; 119 break; 120 case 'W': 121 Wflag = 1; 122 break; 123 case '?': 124 default: 125 usage(); 126 } 127 argc -= optind; 128 argv += optind; 129 130 if (argc < 1) 131 usage(); 132 133 checkdot(argv); 134 135 if (*argv) { 136 stdin_ok = isatty(STDIN_FILENO); 137 138 if (rflag) 139 rm_tree(argv); 140 else 141 rm_file(argv); 142 } 143 144 exit(eval); 145 /* NOTREACHED */ 146 } 147 148 void 149 rm_tree(argv) 150 char **argv; 151 { 152 FTS *fts; 153 FTSENT *p; 154 int needstat; 155 int flags; 156 157 /* 158 * Remove a file hierarchy. If forcing removal (-f), or interactive 159 * (-i) or can't ask anyway (stdin_ok), don't stat the file. 160 */ 161 needstat = !fflag && !iflag && stdin_ok; 162 163 /* 164 * If the -i option is specified, the user can skip on the pre-order 165 * visit. The fts_number field flags skipped directories. 166 */ 167 #define SKIPPED 1 168 169 flags = FTS_PHYSICAL; 170 if (!needstat) 171 flags |= FTS_NOSTAT; 172 if (Wflag) 173 flags |= FTS_WHITEOUT; 174 if (!(fts = fts_open(argv, flags, 175 (int (*) __P((const FTSENT **, const FTSENT **)))NULL))) 176 err(1, NULL); 177 while ((p = fts_read(fts)) != NULL) { 178 switch (p->fts_info) { 179 case FTS_DNR: 180 if (!fflag || p->fts_errno != ENOENT) { 181 warnx("%s: %s", 182 p->fts_path, strerror(p->fts_errno)); 183 eval = 1; 184 } 185 continue; 186 case FTS_ERR: 187 errx(1, "%s: %s", p->fts_path, strerror(p->fts_errno)); 188 /* NOTREACHED */ 189 case FTS_NS: 190 /* 191 * FTS_NS: assume that if can't stat the file, it 192 * can't be unlinked. 193 */ 194 if (!needstat) 195 break; 196 if (!fflag || !NONEXISTENT(p->fts_errno)) { 197 warnx("%s: %s", 198 p->fts_path, strerror(p->fts_errno)); 199 eval = 1; 200 } 201 continue; 202 case FTS_D: 203 /* Pre-order: give user chance to skip. */ 204 if (!fflag && !check(p->fts_path, p->fts_accpath, 205 p->fts_statp)) { 206 (void)fts_set(fts, p, FTS_SKIP); 207 p->fts_number = SKIPPED; 208 } 209 continue; 210 case FTS_DP: 211 /* Post-order: see if user skipped. */ 212 if (p->fts_number == SKIPPED) 213 continue; 214 break; 215 default: 216 if (!fflag && 217 !check(p->fts_path, p->fts_accpath, p->fts_statp)) 218 continue; 219 } 220 221 /* 222 * If we can't read or search the directory, may still be 223 * able to remove it. Don't print out the un{read,search}able 224 * message unless the remove fails. 225 */ 226 switch (p->fts_info) { 227 case FTS_DP: 228 case FTS_DNR: 229 if (!rmdir(p->fts_accpath) || 230 (fflag && errno == ENOENT)) 231 continue; 232 break; 233 234 case FTS_W: 235 if (!undelete(p->fts_accpath) || 236 (fflag && errno == ENOENT)) 237 continue; 238 break; 239 240 default: 241 if (Pflag) 242 rm_overwrite(p->fts_accpath, NULL); 243 if (!unlink(p->fts_accpath) || 244 (fflag && NONEXISTENT(errno))) 245 continue; 246 } 247 warn("%s", p->fts_path); 248 eval = 1; 249 } 250 if (errno) 251 err(1, "fts_read"); 252 } 253 254 void 255 rm_file(argv) 256 char **argv; 257 { 258 struct stat sb; 259 int rval; 260 char *f; 261 262 /* 263 * Remove a file. POSIX 1003.2 states that, by default, attempting 264 * to remove a directory is an error, so must always stat the file. 265 */ 266 while ((f = *argv++) != NULL) { 267 /* Assume if can't stat the file, can't unlink it. */ 268 if (lstat(f, &sb)) { 269 if (Wflag) { 270 sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR; 271 } else { 272 if (!fflag || !NONEXISTENT(errno)) { 273 warn("%s", f); 274 eval = 1; 275 } 276 continue; 277 } 278 } else if (Wflag) { 279 warnx("%s: %s", f, strerror(EEXIST)); 280 eval = 1; 281 continue; 282 } 283 284 if (S_ISDIR(sb.st_mode) && !dflag) { 285 warnx("%s: is a directory", f); 286 eval = 1; 287 continue; 288 } 289 if (!fflag && !S_ISWHT(sb.st_mode) && !check(f, f, &sb)) 290 continue; 291 if (S_ISWHT(sb.st_mode)) 292 rval = undelete(f); 293 else if (S_ISDIR(sb.st_mode)) 294 rval = rmdir(f); 295 else { 296 if (Pflag) 297 rm_overwrite(f, &sb); 298 rval = unlink(f); 299 } 300 if (rval && (!fflag || !NONEXISTENT(errno))) { 301 warn("%s", f); 302 eval = 1; 303 } 304 } 305 } 306 307 /* 308 * rm_overwrite -- 309 * Overwrite the file 3 times with varying bit patterns. 310 * 311 * XXX 312 * This is a cheap way to *really* delete files. Note that only regular 313 * files are deleted, directories (and therefore names) will remain. 314 * Also, this assumes a fixed-block file system (like FFS, or a V7 or a 315 * System V file system). In a logging file system, you'll have to have 316 * kernel support. 317 */ 318 void 319 rm_overwrite(file, sbp) 320 char *file; 321 struct stat *sbp; 322 { 323 struct stat sb; 324 off_t len; 325 int fd, wlen; 326 char buf[8 * 1024]; 327 328 fd = -1; 329 if (sbp == NULL) { 330 if (lstat(file, &sb)) 331 goto err; 332 sbp = &sb; 333 } 334 if (!S_ISREG(sbp->st_mode)) 335 return; 336 if ((fd = open(file, O_WRONLY, 0)) == -1) 337 goto err; 338 339 #define PASS(byte) { \ 340 memset(buf, byte, sizeof(buf)); \ 341 for (len = sbp->st_size; len > 0; len -= wlen) { \ 342 wlen = len < sizeof(buf) ? len : sizeof(buf); \ 343 if (write(fd, buf, wlen) != wlen) \ 344 goto err; \ 345 } \ 346 } 347 PASS(0xff); 348 if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET)) 349 goto err; 350 PASS(0x00); 351 if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET)) 352 goto err; 353 PASS(0xff); 354 if (!fsync(fd) && !close(fd)) 355 return; 356 357 err: eval = 1; 358 warn("%s", file); 359 } 360 361 362 int 363 check(path, name, sp) 364 char *path, *name; 365 struct stat *sp; 366 { 367 int ch, first; 368 char modep[15]; 369 370 /* Check -i first. */ 371 if (iflag) 372 (void)fprintf(stderr, "remove %s? ", path); 373 else { 374 /* 375 * If it's not a symbolic link and it's unwritable and we're 376 * talking to a terminal, ask. Symbolic links are excluded 377 * because their permissions are meaningless. Check stdin_ok 378 * first because we may not have stat'ed the file. 379 */ 380 if (!stdin_ok || S_ISLNK(sp->st_mode) || 381 !(access(name, W_OK) && (errno != ETXTBSY))) 382 return (1); 383 strmode(sp->st_mode, modep); 384 (void)fprintf(stderr, "override %s%s%s/%s for %s? ", 385 modep + 1, modep[9] == ' ' ? "" : " ", 386 user_from_uid(sp->st_uid, 0), 387 group_from_gid(sp->st_gid, 0), path); 388 } 389 (void)fflush(stderr); 390 391 first = ch = getchar(); 392 while (ch != '\n' && ch != EOF) 393 ch = getchar(); 394 return (first == 'y' || first == 'Y'); 395 } 396 397 /* 398 * POSIX.2 requires that if "." or ".." are specified as the basename 399 * portion of an operand, a diagnostic message be written to standard 400 * error and nothing more be done with such operands. 401 * 402 * Since POSIX.2 defines basename as the final portion of a path after 403 * trailing slashes have been removed, we'll remove them here. 404 */ 405 #define ISDOT(a) ((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2]))) 406 void 407 checkdot(argv) 408 char **argv; 409 { 410 char *p, **save, **t; 411 int complained; 412 413 complained = 0; 414 for (t = argv; *t;) { 415 /* strip trailing slashes */ 416 p = strrchr (*t, '\0'); 417 while (--p > *t && *p == '/') 418 *p = '\0'; 419 420 /* extract basename */ 421 if ((p = strrchr(*t, '/')) != NULL) 422 ++p; 423 else 424 p = *t; 425 426 if (ISDOT(p)) { 427 if (!complained++) 428 warnx("\".\" and \"..\" may not be removed"); 429 eval = 1; 430 for (save = t; (t[0] = t[1]) != NULL; ++t) 431 continue; 432 t = save; 433 } else 434 ++t; 435 } 436 } 437 438 void 439 usage() 440 { 441 442 (void)fprintf(stderr, "usage: rm [-dfiPRrW] file ...\n"); 443 exit(1); 444 /* NOTREACHED */ 445 } 446