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 2006 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 <stdio.h> 37 #include <fcntl.h> 38 #include <string.h> 39 #include <sys/types.h> 40 #include <sys/stat.h> 41 #include <dirent.h> 42 #include <limits.h> 43 #include <locale.h> 44 #include <langinfo.h> 45 #include <unistd.h> 46 #include <stdlib.h> 47 #include <errno.h> 48 #include <sys/resource.h> 49 #include <sys/avl.h> 50 #include <libcmdutils.h> 51 52 #define ARGCNT 5 /* Number of arguments */ 53 #define CHILD 0 54 #define DIRECTORY ((buffer.st_mode&S_IFMT) == S_IFDIR) 55 #define SYMLINK ((buffer.st_mode&S_IFMT) == S_IFLNK) 56 #define FAIL -1 57 #define MAXFORK 100 /* Maximum number of forking attempts */ 58 #define NAMESIZE MAXNAMLEN + 1 /* "/" + (file name size) */ 59 #define TRUE 1 60 #define FALSE 0 61 #define WRITE 02 62 #define SEARCH 07 63 64 static int errcode; 65 static int interactive, recursive, silent; /* flags for command line options */ 66 67 static int rm(char *, int); 68 static int undir(char *, int, dev_t, ino_t); 69 static int yes(void); 70 static int mypath(dev_t, ino_t); 71 72 static char yeschr[SCHAR_MAX + 2]; 73 static char nochr[SCHAR_MAX + 2]; 74 75 static char *fullpath; 76 static int initdirfd; 77 78 static void push_name(char *name, int first); 79 static int pop_name(int first); 80 static void force_chdir(char *); 81 static void ch_dir(char *); 82 static char *get_filename(char *name); 83 static void chdir_init(void); 84 static void check_initdir(void); 85 static void cleanup(void); 86 87 static char *cwd; /* pathname of init dir, from getcwd() */ 88 static rlim_t maxfiles; /* maximum number of open files */ 89 static int first_dir = 1; /* flag set when first trying to remove a dir */ 90 /* flag set when can't get dev/inode of a parent dir */ 91 static int parent_err = 0; 92 static avl_tree_t *tree; /* tree to keep track of nodes visited */ 93 94 struct dir_id { 95 dev_t dev; 96 ino_t inode; 97 struct dir_id *next; 98 }; 99 100 /* 101 * initdir is the first of a linked list of structures 102 * containing unique identifying device and inode numbers for 103 * each directory, from the initial dir up to the root. 104 * current_dir is a pointer to the most recent directory pushed 105 * on during a recursive rm() call. 106 */ 107 static struct dir_id initdir, *current_dir; 108 109 int 110 main(int argc, char *argv[]) 111 { 112 extern int optind; 113 int errflg = 0; 114 int c; 115 struct rlimit rl; 116 117 (void) setlocale(LC_ALL, ""); 118 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 119 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ 120 #endif 121 (void) textdomain(TEXT_DOMAIN); 122 123 (void) strncpy(yeschr, nl_langinfo(YESSTR), SCHAR_MAX + 1); 124 (void) strncpy(nochr, nl_langinfo(NOSTR), SCHAR_MAX + 1); 125 126 while ((c = getopt(argc, argv, "frRi")) != EOF) 127 switch (c) { 128 case 'f': 129 silent = TRUE; 130 #ifdef XPG4 131 interactive = FALSE; 132 #endif 133 break; 134 case 'i': 135 interactive = TRUE; 136 #ifdef XPG4 137 silent = FALSE; 138 #endif 139 break; 140 case 'r': 141 case 'R': 142 recursive = TRUE; 143 break; 144 case '?': 145 errflg = 1; 146 break; 147 } 148 149 /* 150 * For BSD compatibility allow '-' to delimit the end 151 * of options. However, if options were already explicitly 152 * terminated with '--', then treat '-' literally: otherwise, 153 * "rm -- -" won't remove '-'. 154 */ 155 if (optind < argc && 156 strcmp(argv[optind], "-") == 0 && 157 strcmp(argv[optind - 1], "--") != 0) 158 optind++; 159 160 argc -= optind; 161 argv = &argv[optind]; 162 163 if ((argc < 1 && !silent) || errflg) { 164 (void) fprintf(stderr, 165 gettext("usage: rm [-fiRr] file ...\n")); 166 exit(2); 167 } 168 169 if (getrlimit(RLIMIT_NOFILE, &rl)) { 170 perror("getrlimit"); 171 exit(2); 172 } else 173 maxfiles = rl.rlim_cur - 2; 174 175 while (argc-- > 0) { 176 tree = NULL; 177 /* Retry if rm() fails due to bad chdir */ 178 while (rm(*argv, 1) < 0) 179 ; 180 argv++; 181 destroy_tree(tree); 182 } 183 184 cleanup(); 185 return (errcode ? 2 : 0); 186 /* NOTREACHED */ 187 } 188 189 static int 190 rm(char *path, int first) 191 { 192 struct stat buffer; 193 char *filepath; 194 char *p; 195 char resolved_path[PATH_MAX]; 196 197 /* 198 * Check file to see if it exists. 199 */ 200 if (lstat(path, &buffer) == FAIL) { 201 if (!silent) { 202 perror(path); 203 ++errcode; 204 } 205 return (0); 206 } 207 208 /* prevent removal of / but allow removal of sym-links */ 209 if (!S_ISLNK(buffer.st_mode) && realpath(path, resolved_path) != NULL && 210 strcmp(resolved_path, "/") == 0) { 211 (void) fprintf(stderr, 212 gettext("rm of %s is not allowed\n"), resolved_path); 213 errcode++; 214 return (0); 215 } 216 217 /* prevent removal of . or .. (directly) */ 218 if (p = strrchr(path, '/')) 219 p++; 220 else 221 p = path; 222 if (strcmp(".", p) == 0 || strcmp("..", p) == 0) { 223 (void) fprintf(stderr, 224 gettext("rm of %s is not allowed\n"), path); 225 errcode++; 226 return (0); 227 } 228 /* 229 * If it's a directory, remove its contents. 230 */ 231 if (DIRECTORY) { 232 /* 233 * If "-r" wasn't specified, trying to remove directories 234 * is an error. 235 */ 236 if (!recursive) { 237 (void) fprintf(stderr, 238 gettext("rm: %s is a directory\n"), path); 239 ++errcode; 240 return (0); 241 } 242 243 if (first_dir) { 244 check_initdir(); 245 current_dir = NULL; 246 first_dir = 0; 247 } 248 249 return (undir(path, first, buffer.st_dev, buffer.st_ino)); 250 } 251 252 filepath = get_filename(path); 253 254 /* 255 * If interactive, ask for acknowledgement. 256 * 257 * TRANSLATION_NOTE - The following message will contain the 258 * first character of the strings for "yes" and "no" defined 259 * in the file "nl_langinfo.po". After substitution, the 260 * message will appear as follows: 261 * rm: remove <filename> (y/n)? 262 * For example, in German, this will appear as 263 * rm: l�schen <filename> (j/n)? 264 * where j=ja, n=nein, <filename>=the file to be removed 265 * 266 */ 267 268 269 if (interactive) { 270 (void) fprintf(stderr, gettext("rm: remove %s (%s/%s)? "), 271 filepath, yeschr, nochr); 272 if (!yes()) { 273 free(filepath); 274 return (0); 275 } 276 } else if (!silent) { 277 /* 278 * If not silent, and stdin is a terminal, and there's 279 * no write access, and the file isn't a symbolic link, 280 * ask for permission. 281 * 282 * TRANSLATION_NOTE - The following message will contain the 283 * first character of the strings for "yes" and "no" defined 284 * in the file "nl_langinfo.po". After substitution, the 285 * message will appear as follows: 286 * rm: <filename>: override protection XXX (y/n)? 287 * where XXX is the permission mode bits of the file in octal 288 * and <filename> is the file to be removed 289 * 290 */ 291 if (!SYMLINK && access(path, W_OK) == FAIL && 292 isatty(fileno(stdin))) { 293 (void) printf( 294 gettext("rm: %s: override protection %o (%s/%s)? "), 295 filepath, buffer.st_mode & 0777, yeschr, nochr); 296 /* 297 * If permission isn't given, skip the file. 298 */ 299 if (!yes()) { 300 free(filepath); 301 return (0); 302 } 303 } 304 } 305 306 /* 307 * If the unlink fails, inform the user. For /usr/bin/rm, only inform 308 * the user if interactive or not silent. 309 * If unlink fails with errno = ENOENT because file was removed 310 * in between the lstat call and unlink don't inform the user and 311 * don't change errcode. 312 */ 313 314 if (unlink(path) == FAIL) { 315 if (errno == ENOENT) { 316 free(filepath); 317 return (0); 318 } 319 #ifndef XPG4 320 if (!silent || interactive) { 321 #endif 322 (void) fprintf(stderr, 323 gettext("rm: %s not removed: "), filepath); 324 perror(""); 325 #ifndef XPG4 326 } 327 #endif 328 ++errcode; 329 } 330 331 free(filepath); 332 return (0); 333 } 334 335 static int 336 undir(char *path, int first, dev_t dev, ino_t ino) 337 { 338 char *newpath; 339 DIR *name; 340 struct dirent *direct; 341 int ismypath; 342 int ret; 343 int chdir_failed = 0; 344 int bad_chdir = 0; 345 size_t len; 346 347 push_name(path, first); 348 349 /* 350 * If interactive and this file isn't in the path of the 351 * current working directory, ask for acknowledgement. 352 * 353 * TRANSLATION_NOTE - The following message will contain the 354 * first character of the strings for "yes" and "no" defined 355 * in the file "nl_langinfo.po". After substitution, the 356 * message will appear as follows: 357 * rm: examine files in directory <directoryname> (y/n)? 358 * where <directoryname> is the directory to be removed 359 * 360 */ 361 ismypath = mypath(dev, ino); 362 if (interactive) { 363 (void) fprintf(stderr, 364 gettext("rm: examine files in directory %s (%s/%s)? "), 365 fullpath, yeschr, nochr); 366 /* 367 * If the answer is no, skip the directory. 368 */ 369 if (!yes()) 370 return (pop_name(first)); 371 } 372 373 #ifdef XPG4 374 /* 375 * XCU4 and POSIX.2: If not interactive and file is not in the 376 * path of the current working directory, check to see whether 377 * or not directory is readable or writable and if not, 378 * prompt user for response. 379 */ 380 if (!interactive && !ismypath && 381 (access(path, W_OK|X_OK) == FAIL) && isatty(fileno(stdin))) { 382 if (!silent) { 383 (void) fprintf(stderr, 384 gettext( 385 "rm: examine files in directory %s (%s/%s)? "), 386 fullpath, yeschr, nochr); 387 /* 388 * If the answer is no, skip the directory. 389 */ 390 if (!yes()) 391 return (pop_name(first)); 392 } 393 } 394 #endif 395 396 /* 397 * Add this node to the search tree so we don't 398 * get into a endless loop. If the add fails then 399 * we have visited this node before. 400 */ 401 ret = add_tnode(&tree, dev, ino); 402 if (ret != 1) { 403 if (ret == 0) { 404 (void) fprintf(stderr, 405 gettext("rm: cycle detected for %s\n"), 406 fullpath); 407 } else if (ret == -1) { 408 perror("rm"); 409 } 410 errcode++; 411 return (pop_name(first)); 412 } 413 414 /* 415 * Open the directory for reading. 416 */ 417 if ((name = opendir(path)) == NULL) { 418 int saveerrno = errno; 419 420 /* 421 * If interactive, ask for acknowledgement. 422 */ 423 if (interactive) { 424 /* 425 * Print an error message that 426 * we could not read the directory 427 * as the user wanted to examine 428 * files in the directory. Only 429 * affect the error status if 430 * user doesn't want to remove the 431 * directory as we still may be able 432 * remove the directory successfully. 433 */ 434 (void) fprintf(stderr, gettext( 435 "rm: cannot read directory %s: "), 436 fullpath); 437 errno = saveerrno; 438 perror(""); 439 (void) fprintf(stderr, gettext( 440 "rm: remove %s: (%s/%s)? "), 441 fullpath, yeschr, nochr); 442 if (!yes()) { 443 ++errcode; 444 return (pop_name(first)); 445 } 446 } 447 448 /* 449 * If the directory is empty, we may be able to 450 * go ahead and remove it. 451 */ 452 if (rmdir(path) == FAIL) { 453 if (interactive) { 454 int rmdirerr = errno; 455 (void) fprintf(stderr, gettext( 456 "rm: Unable to remove directory %s: "), 457 fullpath); 458 errno = rmdirerr; 459 perror(""); 460 } else { 461 (void) fprintf(stderr, gettext( 462 "rm: cannot read directory %s: "), 463 fullpath); 464 errno = saveerrno; 465 perror(""); 466 } 467 ++errcode; 468 } 469 470 /* Continue to next file/directory rather than exit */ 471 return (pop_name(first)); 472 } 473 474 /* 475 * XCU4 requires that rm -r descend the directory 476 * hierarchy without regard to PATH_MAX. If we can't 477 * chdir() do not increment error counter and do not 478 * print message. 479 * 480 * However, if we cannot chdir because someone has taken away 481 * execute access we may still be able to delete the directory 482 * if it's empty. The old rm could do this. 483 */ 484 485 if (chdir(path) == -1) { 486 chdir_failed = 1; 487 } 488 489 /* 490 * Read every directory entry. 491 */ 492 while ((direct = readdir(name)) != NULL) { 493 /* 494 * Ignore "." and ".." entries. 495 */ 496 if (strcmp(direct->d_name, ".") == 0 || 497 strcmp(direct->d_name, "..") == 0) 498 continue; 499 /* 500 * Try to remove the file. 501 */ 502 len = strlen(direct->d_name) + 1; 503 if (chdir_failed) { 504 len += strlen(path) + 2; 505 } 506 507 newpath = malloc(len); 508 if (newpath == NULL) { 509 (void) fprintf(stderr, 510 gettext("rm: Insufficient memory.\n")); 511 cleanup(); 512 exit(1); 513 } 514 515 if (!chdir_failed) { 516 (void) strcpy(newpath, direct->d_name); 517 } else { 518 (void) snprintf(newpath, len, "%s/%s", 519 path, direct->d_name); 520 } 521 522 523 /* 524 * If a spare file descriptor is available, just call the 525 * "rm" function with the file name; otherwise close the 526 * directory and reopen it when the child is removed. 527 */ 528 if (name->dd_fd >= maxfiles) { 529 (void) closedir(name); 530 if (rm(newpath, 0) < 0) 531 bad_chdir = -1; 532 if (!chdir_failed) 533 name = opendir("."); 534 else 535 name = opendir(path); 536 if (name == NULL) { 537 (void) fprintf(stderr, 538 gettext("rm: cannot read directory %s: "), 539 fullpath); 540 perror(""); 541 cleanup(); 542 exit(2); 543 } 544 } else if (rm(newpath, 0) < 0) 545 bad_chdir = -1; 546 547 free(newpath); 548 if (bad_chdir) 549 break; 550 } 551 552 /* 553 * Close the directory we just finished reading. 554 */ 555 (void) closedir(name); 556 if (bad_chdir) 557 return (-1); 558 559 /* 560 * The contents of the directory have been removed. If the 561 * directory itself is in the path of the current working 562 * directory, don't try to remove it. 563 * When the directory itself is the current working directory, mypath() 564 * has a return code == 2. 565 * 566 * XCU4: Because we've descended the directory hierarchy in order 567 * to avoid PATH_MAX limitation, we must now start ascending 568 * one level at a time and remove files/directories. 569 */ 570 571 if (!chdir_failed) { 572 if (first) 573 chdir_init(); 574 else if (chdir("..") == -1) { 575 (void) fprintf(stderr, 576 gettext("rm: cannot change to parent of " 577 "directory %s: "), 578 fullpath); 579 perror(""); 580 cleanup(); 581 exit(2); 582 } 583 } 584 585 switch (ismypath) { 586 case 3: 587 return (pop_name(first)); 588 case 2: 589 (void) fprintf(stderr, 590 gettext("rm: Cannot remove any directory in the path " 591 "of the current working directory\n%s\n"), fullpath); 592 ++errcode; 593 return (pop_name(first)); 594 case 1: 595 ++errcode; 596 return (pop_name(first)); 597 case 0: 598 break; 599 } 600 601 /* 602 * If interactive, ask for acknowledgement. 603 */ 604 if (interactive) { 605 (void) fprintf(stderr, gettext("rm: remove %s: (%s/%s)? "), 606 fullpath, yeschr, nochr); 607 if (!yes()) 608 return (pop_name(first)); 609 } 610 if (rmdir(path) == FAIL) { 611 (void) fprintf(stderr, 612 gettext("rm: Unable to remove directory %s: "), 613 fullpath); 614 perror(""); 615 ++errcode; 616 } 617 return (pop_name(first)); 618 } 619 620 621 static int 622 yes(void) 623 { 624 int i, b; 625 char ans[SCHAR_MAX + 1]; 626 627 for (i = 0; ; i++) { 628 b = getchar(); 629 if (b == '\n' || b == '\0' || b == EOF) { 630 ans[i] = 0; 631 break; 632 } 633 if (i < SCHAR_MAX) 634 ans[i] = b; 635 } 636 if (i >= SCHAR_MAX) { 637 i = SCHAR_MAX; 638 ans[SCHAR_MAX] = 0; 639 } 640 if ((i == 0) | (strncmp(yeschr, ans, i))) 641 return (0); 642 return (1); 643 } 644 645 646 static int 647 mypath(dev_t dev, ino_t ino) 648 { 649 struct dir_id *curdir; 650 651 /* 652 * Check to see if this is our current directory 653 * Indicated by return 2; 654 */ 655 if (dev == initdir.dev && ino == initdir.inode) { 656 return (2); 657 } 658 659 curdir = initdir.next; 660 661 while (curdir != NULL) { 662 /* 663 * If we find a match, the directory (dev, ino) passed to 664 * mypath() is an ancestor of ours. Indicated by return 3. 665 */ 666 if (curdir->dev == dev && curdir->inode == ino) 667 return (3); 668 curdir = curdir->next; 669 } 670 /* 671 * parent_err indicates we couldn't stat or chdir to 672 * one of our parent dirs, so the linked list of dir_id structs 673 * is incomplete 674 */ 675 if (parent_err) { 676 #ifndef XPG4 677 if (!silent || interactive) { 678 #endif 679 (void) fprintf(stderr, gettext("rm: cannot determine " 680 "if this is an ancestor of the current " 681 "working directory\n%s\n"), fullpath); 682 #ifndef XPG4 683 } 684 #endif 685 /* assume it is. least dangerous */ 686 return (1); 687 } 688 return (0); 689 } 690 691 static int maxlen; 692 static int curlen; 693 694 static char * 695 get_filename(char *name) 696 { 697 char *path; 698 size_t len; 699 700 if (fullpath == NULL || *fullpath == '\0') { 701 path = strdup(name); 702 if (path == NULL) { 703 (void) fprintf(stderr, 704 gettext("rm: Insufficient memory.\n")); 705 cleanup(); 706 exit(1); 707 } 708 } else { 709 len = strlen(fullpath) + strlen(name) + 2; 710 path = malloc(len); 711 if (path == NULL) { 712 (void) fprintf(stderr, 713 gettext("rm: Insufficient memory.\n")); 714 cleanup(); 715 exit(1); 716 } 717 (void) snprintf(path, len, "%s/%s", fullpath, name); 718 } 719 return (path); 720 } 721 722 static void 723 push_name(char *name, int first) 724 { 725 int namelen; 726 struct stat buffer; 727 struct dir_id *newdir; 728 729 namelen = strlen(name) + 1; /* 1 for "/" */ 730 if ((curlen + namelen) >= maxlen) { 731 maxlen += PATH_MAX; 732 fullpath = (char *)realloc(fullpath, (size_t)(maxlen + 1)); 733 } 734 if (first) { 735 (void) strcpy(fullpath, name); 736 } else { 737 (void) strcat(fullpath, "/"); 738 (void) strcat(fullpath, name); 739 } 740 curlen = strlen(fullpath); 741 742 if (stat(".", &buffer) == -1) { 743 (void) fprintf(stderr, 744 gettext("rm: cannot stat current directory: ")); 745 perror(""); 746 exit(2); 747 } 748 if ((newdir = malloc(sizeof (struct dir_id))) == NULL) { 749 (void) fprintf(stderr, 750 gettext("rm: Insufficient memory.\n")); 751 cleanup(); 752 exit(1); 753 } 754 755 newdir->dev = buffer.st_dev; 756 newdir->inode = buffer.st_ino; 757 newdir->next = current_dir; 758 current_dir = newdir; 759 } 760 761 static int 762 pop_name(int first) 763 { 764 int retval = 0; 765 char *slash; 766 struct stat buffer; 767 struct dir_id *remove_dir; 768 769 if (first) { 770 *fullpath = '\0'; 771 return (0); 772 } 773 slash = strrchr(fullpath, '/'); 774 if (slash) 775 *slash = '\0'; 776 else 777 *fullpath = '\0'; 778 curlen = strlen(fullpath); 779 780 if (stat(".", &buffer) == -1) { 781 (void) fprintf(stderr, 782 gettext("rm: cannot stat current directory: ")); 783 perror(""); 784 exit(2); 785 } 786 787 /* 788 * For each pop operation, verify that the device and inode numbers 789 * of "." match the numbers recorded before the chdir was done into 790 * the directory. If they do not match, it is an indication of 791 * possible malicious activity and rm has chdir to an unintended 792 * directory 793 */ 794 if ((current_dir->inode != buffer.st_ino) || (current_dir->dev != 795 buffer.st_dev)) { 796 (void) fprintf(stderr, gettext("rm: WARNING: " 797 "A subdirectory of %s was moved or linked to " 798 "another directory during the execution of rm\n"), 799 fullpath); 800 retval = -1; 801 } 802 remove_dir = current_dir; 803 current_dir = current_dir->next; 804 free(remove_dir); 805 return (retval); 806 } 807 808 static void 809 force_chdir(char *dirname) 810 { 811 char *pathname, *mp, *tp; 812 813 /* use pathname instead of dirname, so dirname won't be modified */ 814 if ((pathname = strdup(dirname)) == NULL) { 815 (void) fprintf(stderr, gettext("rm: strdup: ")); 816 perror(""); 817 cleanup(); 818 exit(2); 819 } 820 821 /* pathname is an absolute full path from getcwd() */ 822 mp = pathname; 823 while (mp) { 824 tp = strchr(mp, '/'); 825 if (strlen(mp) >= PATH_MAX) { 826 /* 827 * after the first iteration through this 828 * loop, the below will NULL out the '/' 829 * which follows the first dir on pathname 830 */ 831 *tp = 0; 832 tp++; 833 if (*mp == NULL) 834 ch_dir("/"); 835 else 836 /* 837 * mp points to the start of a dirname, 838 * terminated by NULL, so ch_dir() 839 * here will move down one directory 840 */ 841 ch_dir(mp); 842 /* 843 * reset mp to the start of the dirname 844 * which follows the one we just chdir'd to 845 */ 846 mp = tp; 847 continue; /* probably can remove this */ 848 } else { 849 ch_dir(mp); 850 break; 851 } 852 } 853 free(pathname); 854 } 855 856 static void 857 ch_dir(char *dirname) 858 { 859 if (chdir(dirname) == -1) { 860 (void) fprintf(stderr, 861 gettext("rm: cannot change to %s directory: "), dirname); 862 perror(""); 863 cleanup(); 864 exit(2); 865 } 866 } 867 868 static void 869 chdir_init(void) 870 { 871 /* 872 * Go back to init dir--the dir from where rm was executed--using 873 * one of two methods, depending on which method works 874 * for the given permissions of the init dir and its 875 * parent directories. 876 */ 877 if (initdirfd != -1) { 878 if (fchdir(initdirfd) == -1) { 879 (void) fprintf(stderr, 880 gettext("rm: cannot change to starting " 881 "directory: ")); 882 perror(""); 883 cleanup(); 884 exit(2); 885 } 886 } else { 887 if (strlen(cwd) < PATH_MAX) 888 ch_dir(cwd); 889 else 890 force_chdir(cwd); 891 } 892 } 893 894 /* 895 * check_initdir - 896 * is only called the first time rm tries to 897 * remove a directory. It saves the current directory, i.e., 898 * init dir, so we can go back to it after traversing elsewhere. 899 * It also saves all the device and inode numbers of each 900 * dir from the initial dir back to the root in a linked list, so we 901 * can later check, via mypath(), if we are trying to remove our current 902 * dir or an ancestor. 903 */ 904 static void 905 check_initdir(void) 906 { 907 int size; /* size allocated for pathname of init dir (cwd) */ 908 struct stat buffer; 909 struct dir_id *lastdir, *curdir; 910 911 /* 912 * We need to save where we currently are (the "init dir") so 913 * we can return after traversing down directories we're 914 * removing. Two methods are attempted: 915 * 916 * 1) open() the initial dir so we can use the fd 917 * to fchdir() back. This requires read permission 918 * on the initial dir. 919 * 920 * 2) getcwd() so we can chdir() to go back. This 921 * requires search (x) permission on the init dir, 922 * and read and search permission on all parent dirs. Also, 923 * getcwd() will not work if the init dir is > 341 924 * directories deep (see open bugid 4033182 - getcwd needs 925 * to work for pathnames of any depth). 926 * 927 * If neither method works, we can't remove any directories 928 * and rm will fail. 929 * 930 * For future enhancement, a possible 3rd option to use 931 * would be to fork a process to remove a directory, 932 * eliminating the need to chdir back to the initial directory 933 * and eliminating the permission restrictions on the initial dir 934 * or its parent dirs. 935 */ 936 initdirfd = open(".", O_RDONLY); 937 if (initdirfd == -1) { 938 size = PATH_MAX; 939 while ((cwd = getcwd(NULL, size)) == NULL) { 940 if (errno == ERANGE) { 941 size = PATH_MAX + size; 942 continue; 943 } else { 944 (void) fprintf(stderr, 945 gettext("rm: cannot open starting " 946 "directory: ")); 947 perror("pwd"); 948 exit(2); 949 } 950 } 951 } 952 953 /* 954 * since we exit on error here, we're guaranteed to at least 955 * have info in the first dir_id struct, initdir 956 */ 957 if (stat(".", &buffer) == -1) { 958 (void) fprintf(stderr, 959 gettext("rm: cannot stat current directory: ")); 960 perror(""); 961 exit(2); 962 } 963 initdir.dev = buffer.st_dev; 964 initdir.inode = buffer.st_ino; 965 initdir.next = NULL; 966 967 lastdir = &initdir; 968 /* 969 * Starting from current working directory, walk toward the 970 * root, looking at each directory along the way. 971 */ 972 for (;;) { 973 if (chdir("..") == -1 || lstat(".", &buffer) == -1) { 974 parent_err = 1; 975 break; 976 } 977 978 if ((lastdir->next = malloc(sizeof (struct dir_id))) == 979 NULL) { 980 (void) fprintf(stderr, 981 gettext("rm: Insufficient memory.\n")); 982 cleanup(); 983 exit(1); 984 } 985 986 curdir = lastdir->next; 987 curdir->dev = buffer.st_dev; 988 curdir->inode = buffer.st_ino; 989 curdir->next = NULL; 990 991 /* 992 * Stop when we reach the root; note that chdir("..") 993 * at the root dir will stay in root. Get rid of 994 * the redundant dir_id struct for root. 995 */ 996 if (curdir->dev == lastdir->dev && curdir->inode == 997 lastdir->inode) { 998 lastdir->next = NULL; 999 free(curdir); 1000 break; 1001 } 1002 1003 /* loop again to go back another level */ 1004 lastdir = curdir; 1005 } 1006 /* go back to init directory */ 1007 chdir_init(); 1008 } 1009 1010 /* 1011 * cleanup the dynamically-allocated list of device numbers and inodes, 1012 * if any. If initdir was never used, it is external and static so 1013 * it is guaranteed initialized to zero, thus initdir.next would be NULL. 1014 */ 1015 1016 static void 1017 cleanup(void) { 1018 1019 struct dir_id *lastdir, *curdir; 1020 1021 curdir = initdir.next; 1022 1023 while (curdir != NULL) { 1024 lastdir = curdir; 1025 curdir = curdir->next; 1026 free(lastdir); 1027 } 1028 } 1029