1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 28 /* All Rights Reserved */ 29 30 #pragma ident "%Z%%M% %I% %E% SMI" 31 32 /* 33 * rm [-fiRr] file ... 34 */ 35 36 #include <sys/param.h> 37 #include <sys/stat.h> 38 #include <dirent.h> 39 #include <errno.h> 40 #include <fcntl.h> 41 #include <langinfo.h> 42 #include <limits.h> 43 #include <locale.h> 44 #include <stdarg.h> 45 #include <stdio.h> 46 #include <stdlib.h> 47 #include <string.h> 48 #include <unistd.h> 49 #include <values.h> 50 51 #define E_OK 010 /* make __accessat() use effective ids */ 52 53 #define DIR_CANTCLOSE 1 54 55 static struct stat rootdir; 56 57 struct dlist { 58 int fd; /* Stores directory fd */ 59 int flags; /* DIR_* Flags */ 60 DIR *dp; /* Open directory (opened with fd) */ 61 long diroff; /* Saved directory offset when closing */ 62 struct dlist *up; /* Up one step in the tree (toward "/") */ 63 struct dlist *down; /* Down one step in the tree */ 64 ino_t ino; /* st_ino of directory */ 65 dev_t dev; /* st_dev of directory */ 66 int pathend; /* Offset of name end in the pathbuffer */ 67 }; 68 69 static struct dlist top = { 70 (int)AT_FDCWD, 71 DIR_CANTCLOSE, 72 }; 73 74 static char yeschr[SCHAR_MAX + 2]; 75 static char nochr[SCHAR_MAX + 2]; 76 77 static struct dlist *cur, *rec; 78 79 static int rm(const char *, struct dlist *); 80 static int confirm(FILE *, const char *, ...); 81 static void memerror(void); 82 static int checkdir(struct dlist *, struct dlist *); 83 static int errcnt; 84 static boolean_t silent, interactive, recursive, ontty; 85 86 static char *pathbuf; 87 static size_t pathbuflen = MAXPATHLEN; 88 89 static int maxfds = MAXINT; 90 static int nfds; 91 92 extern int __accessat(int, const char *, int); 93 94 int 95 main(int argc, char **argv) 96 { 97 int errflg = 0; 98 int c; 99 100 (void) setlocale(LC_ALL, ""); 101 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 102 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ 103 #endif 104 (void) textdomain(TEXT_DOMAIN); 105 106 (void) strncpy(yeschr, nl_langinfo(YESSTR), SCHAR_MAX + 1); 107 (void) strncpy(nochr, nl_langinfo(NOSTR), SCHAR_MAX + 1); 108 109 while ((c = getopt(argc, argv, "frRi")) != EOF) 110 switch (c) { 111 case 'f': 112 silent = B_TRUE; 113 #ifdef XPG4 114 interactive = B_FALSE; 115 #endif 116 break; 117 case 'i': 118 interactive = B_TRUE; 119 #ifdef XPG4 120 silent = B_FALSE; 121 #endif 122 break; 123 case 'r': 124 case 'R': 125 recursive = B_TRUE; 126 break; 127 case '?': 128 errflg = 1; 129 break; 130 } 131 132 /* 133 * For BSD compatibility allow '-' to delimit the end 134 * of options. However, if options were already explicitly 135 * terminated with '--', then treat '-' literally: otherwise, 136 * "rm -- -" won't remove '-'. 137 */ 138 if (optind < argc && 139 strcmp(argv[optind], "-") == 0 && 140 strcmp(argv[optind - 1], "--") != 0) 141 optind++; 142 143 argc -= optind; 144 argv = &argv[optind]; 145 146 if ((argc < 1 && !silent) || errflg) { 147 (void) fprintf(stderr, gettext("usage: rm [-fiRr] file ...\n")); 148 exit(2); 149 } 150 151 ontty = isatty(STDIN_FILENO) != 0; 152 153 if (recursive && stat("/", &rootdir) != 0) { 154 (void) fprintf(stderr, 155 gettext("rm: cannot stat root directory: %s\n"), 156 strerror(errno)); 157 exit(2); 158 } 159 160 pathbuf = malloc(pathbuflen); 161 if (pathbuf == NULL) 162 memerror(); 163 164 for (; *argv != NULL; argv++) { 165 char *p = strrchr(*argv, '/'); 166 if (p == NULL) 167 p = *argv; 168 else 169 p = p + 1; 170 if (strcmp(p, ".") == 0 || strcmp(p, "..") == 0) { 171 (void) fprintf(stderr, 172 gettext("rm of %s is not allowed\n"), *argv); 173 errcnt++; 174 continue; 175 } 176 /* Retry when we can't walk back up. */ 177 while (rm(*argv, rec = cur = &top) != 0) 178 ; 179 } 180 181 return (errcnt != 0 ? 2 : 0); 182 } 183 184 static void 185 pushfilename(const char *fname) 186 { 187 char *p; 188 const char *q = fname; 189 190 if (cur == &top) { 191 p = pathbuf; 192 } else { 193 p = pathbuf + cur->up->pathend; 194 *p++ = '/'; 195 } 196 while (*q != '\0') { 197 if (p - pathbuf + 2 >= pathbuflen) { 198 char *np; 199 pathbuflen += MAXPATHLEN; 200 np = realloc(pathbuf, pathbuflen); 201 if (np == NULL) 202 memerror(); 203 p = np + (p - pathbuf); 204 pathbuf = np; 205 } 206 *p++ = *q++; 207 } 208 *p = '\0'; 209 cur->pathend = p - pathbuf; 210 } 211 212 static void 213 closeframe(struct dlist *frm) 214 { 215 if (frm->dp != NULL) { 216 (void) closedir(frm->dp); 217 nfds--; 218 frm->dp = NULL; 219 frm->fd = -1; 220 } 221 } 222 223 static int 224 reclaim(void) 225 { 226 while (rec != NULL && (rec->flags & DIR_CANTCLOSE) != 0) 227 rec = rec->down; 228 if (rec == NULL || rec == cur || rec->dp == NULL) 229 return (-1); 230 rec->diroff = telldir(rec->dp); 231 closeframe(rec); 232 rec = rec->down; 233 return (0); 234 } 235 236 static void 237 pushdir(struct dlist *frm) 238 { 239 frm->up = cur; 240 frm->down = NULL; 241 cur->down = frm; 242 cur = frm; 243 } 244 245 static int 246 opendirat(int dirfd, const char *entry, struct dlist *frm) 247 { 248 int fd; 249 250 if (nfds >= maxfds) 251 (void) reclaim(); 252 253 while ((fd = openat(dirfd, entry, O_RDONLY|O_NONBLOCK)) == -1 && 254 errno == EMFILE) { 255 if (nfds < maxfds) 256 maxfds = nfds; 257 if (reclaim() != 0) 258 return (-1); 259 } 260 if (fd < 0) 261 return (-1); 262 frm->fd = fd; 263 frm->dp = fdopendir(fd); 264 if (frm->dp == NULL) { 265 (void) close(fd); 266 return (-1); 267 } 268 nfds++; 269 return (0); 270 } 271 272 /* 273 * Since we never pop the top frame, cur->up can never be NULL. 274 * If we pop beyond a frame we closed, we try to reopen "..". 275 */ 276 static int 277 popdir(boolean_t noerror) 278 { 279 struct stat buf; 280 int ret = noerror ? 0 : -1; 281 pathbuf[cur->up->pathend] = '\0'; 282 283 if (noerror && cur->up->fd == -1) { 284 rec = cur->up; 285 if (opendirat(cur->fd, "..", rec) != 0 || 286 fstat(rec->fd, &buf) != 0) { 287 (void) fprintf(stderr, 288 gettext("rm: cannot reopen %s: %s\n"), 289 pathbuf, strerror(errno)); 290 exit(2); 291 } 292 if (rec->ino != buf.st_ino || rec->dev != buf.st_dev) { 293 (void) fprintf(stderr, gettext("rm: WARNING: " 294 "The directory %s was moved or linked to " 295 "another directory during the execution of rm\n"), 296 pathbuf); 297 closeframe(rec); 298 ret = -1; 299 } else { 300 /* If telldir failed, we take it from the top. */ 301 if (rec->diroff != -1) 302 seekdir(rec->dp, rec->diroff); 303 } 304 } else if (rec == cur) 305 rec = cur->up; 306 closeframe(cur); 307 cur = cur->up; 308 cur->down = NULL; 309 return (ret); 310 } 311 312 /* 313 * The stack frame of this function is minimized so that we can 314 * recurse quite a bit before we overflow the stack; around 315 * 30,000-40,000 nested directories can be removed with the default 316 * stack limit. 317 */ 318 static int 319 rm(const char *entry, struct dlist *caller) 320 { 321 struct dlist frame; 322 int flag; 323 struct stat temp; 324 struct dirent *dent; 325 int err; 326 327 /* 328 * Construct the pathname: note that the entry may live in memory 329 * allocated by readdir and that after return from recursion 330 * the memory is no longer valid. So after the recursive rm() 331 * call, we use the global pathbuf instead of the entry argument. 332 */ 333 pushfilename(entry); 334 335 if (fstatat(caller->fd, entry, &temp, AT_SYMLINK_NOFOLLOW) != 0) { 336 if (!silent) { 337 (void) fprintf(stderr, "rm: %s: %s\n", pathbuf, 338 strerror(errno)); 339 errcnt++; 340 } 341 return (0); 342 } 343 344 if (S_ISDIR(temp.st_mode)) { 345 /* 346 * If "-r" wasn't specified, trying to remove directories 347 * is an error. 348 */ 349 if (!recursive) { 350 (void) fprintf(stderr, 351 gettext("rm: %s is a directory\n"), pathbuf); 352 errcnt++; 353 return (0); 354 } 355 356 if (temp.st_ino == rootdir.st_ino && 357 temp.st_dev == rootdir.st_dev) { 358 (void) fprintf(stderr, 359 gettext("rm of %s is not allowed\n"), "/"); 360 errcnt++; 361 return (0); 362 } 363 /* 364 * TRANSLATION_NOTE - The following message will contain the 365 * first character of the strings for "yes" and "no" defined 366 * in the file "nl_langinfo.po". After substitution, the 367 * message will appear as follows: 368 * rm: examine files in directory <directoryname> (y/n)? 369 * where <directoryname> is the directory to be removed 370 * 371 */ 372 if (interactive && !confirm(stderr, 373 gettext("rm: examine files in directory %s (%s/%s)? "), 374 pathbuf, yeschr, nochr)) { 375 return (0); 376 } 377 378 frame.dev = temp.st_dev; 379 frame.ino = temp.st_ino; 380 frame.flags = 0; 381 flag = AT_REMOVEDIR; 382 383 #ifdef XPG4 384 /* 385 * XCU4 and POSIX.2: If not interactive, check to see whether 386 * or not directory is readable or writable and if not, 387 * prompt user for response. 388 */ 389 if (ontty && !interactive && !silent && 390 __accessat(caller->fd, entry, W_OK|X_OK|E_OK) != 0 && 391 !confirm(stderr, 392 gettext("rm: examine files in directory %s (%s/%s)? "), 393 pathbuf, yeschr, nochr)) { 394 return (0); 395 } 396 #endif 397 if (opendirat(caller->fd, entry, &frame) == -1) { 398 err = errno; 399 400 if (interactive) { 401 /* 402 * Print an error message that 403 * we could not read the directory 404 * as the user wanted to examine 405 * files in the directory. Only 406 * affect the error status if 407 * user doesn't want to remove the 408 * directory as we still may be able 409 * remove the directory successfully. 410 */ 411 (void) fprintf(stderr, gettext( 412 "rm: cannot read directory %s: %s\n"), 413 pathbuf, strerror(err)); 414 415 /* 416 * TRANSLATION_NOTE - The following message will contain the 417 * first character of the strings for "yes" and "no" defined 418 * in the file "nl_langinfo.po". After substitution, the 419 * message will appear as follows: 420 * rm: remove <filename> (y/n)? 421 * For example, in German, this will appear as 422 * rm: l�schen <filename> (j/n)? 423 * where j=ja, n=nein, <filename>=the file to be removed 424 */ 425 if (!confirm(stderr, 426 gettext("rm: remove %s (%s/%s)? "), 427 pathbuf, yeschr, nochr)) { 428 errcnt++; 429 return (0); 430 } 431 } 432 /* If it's empty we may still be able to rm it */ 433 if (unlinkat(caller->fd, entry, flag) == 0) 434 return (0); 435 if (interactive) 436 err = errno; 437 (void) fprintf(stderr, 438 interactive ? 439 gettext("rm: Unable to remove directory %s: %s\n") : 440 gettext("rm: cannot read directory %s: %s\n"), 441 pathbuf, strerror(err)); 442 errcnt++; 443 return (0); 444 } 445 446 /* 447 * There is a race condition here too; if we open a directory 448 * we have to make sure it's still the same directory we 449 * stat'ed and checked against root earlier. Let's check. 450 */ 451 if (fstat(frame.fd, &temp) != 0 || 452 frame.ino != temp.st_ino || 453 frame.dev != temp.st_dev) { 454 (void) fprintf(stderr, 455 gettext("rm: %s: directory renamed\n"), pathbuf); 456 closeframe(&frame); 457 errcnt++; 458 return (0); 459 } 460 461 if (caller != &top) { 462 if (checkdir(caller, &frame) != 0) { 463 closeframe(&frame); 464 goto unlinkit; 465 } 466 } 467 pushdir(&frame); 468 469 /* 470 * rm() only returns -1 if popdir failed at some point; 471 * frame.dp is no longer reliable and we must drop out. 472 */ 473 while ((dent = readdir(frame.dp)) != NULL) { 474 if (strcmp(dent->d_name, ".") == 0 || 475 strcmp(dent->d_name, "..") == 0) 476 continue; 477 478 if (rm(dent->d_name, &frame) != 0) 479 break; 480 } 481 482 if (popdir(dent == NULL) != 0) 483 return (-1); 484 485 /* 486 * We recursed and the subdirectory may have set the CANTCLOSE 487 * flag; we need to clear it except for &top. 488 * Recursion may have invalidated entry because of closedir(). 489 */ 490 if (caller != &top) { 491 caller->flags &= ~DIR_CANTCLOSE; 492 entry = &pathbuf[caller->up->pathend + 1]; 493 } 494 } else { 495 flag = 0; 496 } 497 unlinkit: 498 /* 499 * If interactive, ask for acknowledgement. 500 */ 501 if (interactive) { 502 if (!confirm(stderr, gettext("rm: remove %s (%s/%s)? "), 503 pathbuf, yeschr, nochr)) { 504 return (0); 505 } 506 } else if (!silent && flag == 0) { 507 /* 508 * If not silent, and stdin is a terminal, and there's 509 * no write access, and the file isn't a symbolic link, 510 * ask for permission. If flag is set, then we know it's 511 * a directory so we skip this test as it was done above. 512 * 513 * TRANSLATION_NOTE - The following message will contain the 514 * first character of the strings for "yes" and "no" defined 515 * in the file "nl_langinfo.po". After substitution, the 516 * message will appear as follows: 517 * rm: <filename>: override protection XXX (y/n)? 518 * where XXX is the permission mode bits of the file in octal 519 * and <filename> is the file to be removed 520 * 521 */ 522 if (ontty && !S_ISLNK(temp.st_mode) && 523 __accessat(caller->fd, entry, W_OK|E_OK) != 0 && 524 !confirm(stdout, 525 gettext("rm: %s: override protection %o (%s/%s)? "), 526 pathbuf, temp.st_mode & 0777, yeschr, nochr)) { 527 return (0); 528 } 529 } 530 531 if (unlinkat(caller->fd, entry, flag) != 0) { 532 err = errno; 533 if (err == ENOENT) 534 return (0); 535 536 if (flag != 0) { 537 if (err == EINVAL) { 538 (void) fprintf(stderr, gettext( 539 "rm: Cannot remove any directory in the " 540 "path of the current working directory\n" 541 "%s\n"), pathbuf); 542 } else { 543 if (err == EEXIST) 544 err = ENOTEMPTY; 545 (void) fprintf(stderr, 546 gettext("rm: Unable to remove directory %s:" 547 " %s\n"), pathbuf, strerror(err)); 548 } 549 } else { 550 #ifndef XPG4 551 if (!silent || interactive) { 552 #endif 553 554 (void) fprintf(stderr, 555 gettext("rm: %s not removed: %s\n"), 556 pathbuf, strerror(err)); 557 #ifndef XPG4 558 } 559 #endif 560 } 561 errcnt++; 562 } 563 return (0); 564 } 565 566 static int 567 yes(void) 568 { 569 int i, b; 570 char ans[SCHAR_MAX + 1]; 571 572 for (i = 0; ; i++) { 573 b = getchar(); 574 if (b == '\n' || b == '\0' || b == EOF) { 575 ans[i] = 0; 576 break; 577 } 578 if (i < SCHAR_MAX) 579 ans[i] = (char)b; 580 } 581 if (i >= SCHAR_MAX) { 582 i = SCHAR_MAX; 583 ans[SCHAR_MAX] = 0; 584 } 585 if ((i == 0) | (strncmp(yeschr, ans, i))) 586 return (0); 587 return (1); 588 } 589 590 static int 591 confirm(FILE *fp, const char *q, ...) 592 { 593 va_list ap; 594 595 va_start(ap, q); 596 (void) vfprintf(fp, q, ap); 597 va_end(ap); 598 return (yes()); 599 } 600 601 static void 602 memerror(void) 603 { 604 (void) fprintf(stderr, gettext("rm: Insufficient memory.\n")); 605 exit(1); 606 } 607 608 /* 609 * If we can't stat "..", it's either not there or we can't search 610 * the current directory; in that case we can't return back through 611 * "..", so we need to keep the parent open. 612 * Check that we came from "..", if not then this directory entry is an 613 * additional link and there is risk of a filesystem cycle and we also 614 * can't go back up through ".." and we keep the directory open. 615 */ 616 static int 617 checkdir(struct dlist *caller, struct dlist *frmp) 618 { 619 struct stat up; 620 struct dlist *ptr; 621 622 if (fstatat(frmp->fd, "..", &up, 0) != 0) { 623 caller->flags |= DIR_CANTCLOSE; 624 return (0); 625 } else if (up.st_ino == caller->ino && up.st_dev == caller->dev) { 626 return (0); 627 } 628 629 /* Directory hard link, check cycle */ 630 for (ptr = caller; ptr != NULL; ptr = ptr->up) { 631 if (frmp->dev == ptr->dev && frmp->ino == ptr->ino) { 632 (void) fprintf(stderr, 633 gettext("rm: cycle detected for %s\n"), pathbuf); 634 errcnt++; 635 return (-1); 636 } 637 } 638 caller->flags |= DIR_CANTCLOSE; 639 return (0); 640 } 641