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