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