xref: /titanic_51/usr/src/cmd/rm/rm.c (revision 5f8e16172ef40e14cf931b329fedb86ea369a42c)
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