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