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