xref: /illumos-gate/usr/src/cmd/rm/rm.c (revision 60405de4d8688d96dd05157c28db3ade5c9bc234)
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 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 		destroy_tree(tree);
178 	}
179 	cleanup();
180 	return (errcode ? 2 : 0);
181 	/* NOTREACHED */
182 }
183 
184 static void
185 rm(char *path, int first)
186 {
187 	struct stat buffer;
188 	char	*filepath;
189 	char	*p;
190 	char	resolved_path[PATH_MAX];
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 	/* prevent removal of / but allow removal of sym-links */
204 	if (!S_ISLNK(buffer.st_mode) && realpath(path, resolved_path) != NULL &&
205 	    strcmp(resolved_path, "/") == 0) {
206 		(void) fprintf(stderr,
207 		    gettext("rm of %s is not allowed\n"), resolved_path);
208 		errcode++;
209 		return;
210 	}
211 
212 	/* prevent removal of . or .. (directly) */
213 	if (p = strrchr(path, '/'))
214 		p++;
215 	else
216 		p = path;
217 	if (strcmp(".", p) == 0 || strcmp("..", p) == 0) {
218 		(void) fprintf(stderr,
219 			gettext("rm of %s is not allowed\n"), path);
220 		errcode++;
221 		return;
222 	}
223 	/*
224 	 * If it's a directory, remove its contents.
225 	 */
226 	if (DIRECTORY) {
227 		/*
228 		 * If "-r" wasn't specified, trying to remove directories
229 		 * is an error.
230 		 */
231 		if (!recursive) {
232 			(void) fprintf(stderr,
233 			    gettext("rm: %s is a directory\n"), path);
234 			++errcode;
235 			return;
236 		}
237 
238 		if (first_dir) {
239 			check_homedir();
240 			first_dir = 0;
241 		}
242 
243 		undir(path, first, buffer.st_dev, buffer.st_ino);
244 		return;
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	ret;
336 	int	chdir_failed = 0;
337 	size_t	len;
338 
339 	push_name(path, first);
340 
341 	/*
342 	 * If interactive and this file isn't in the path of the
343 	 * current working directory, ask for acknowledgement.
344 	 *
345 	 * TRANSLATION_NOTE - The following message will contain the
346 	 * first character of the strings for "yes" and "no" defined
347 	 * in the file "nl_langinfo.po".  After substitution, the
348 	 * message will appear as follows:
349 	 *	rm: examine files in directory <directoryname> (y/n)?
350 	 * where <directoryname> is the directory to be removed
351 	 *
352 	 */
353 	ismypath = mypath(dev, ino);
354 	if (interactive) {
355 		(void) fprintf(stderr,
356 		    gettext("rm: examine files in directory %s (%s/%s)? "),
357 			fullpath, yeschr, nochr);
358 		/*
359 		 * If the answer is no, skip the directory.
360 		 */
361 		if (!yes()) {
362 			pop_name(first);
363 			return;
364 		}
365 	}
366 
367 #ifdef XPG4
368 	/*
369 	 * XCU4 and POSIX.2: If not interactive and file is not in the
370 	 * path of the current working directory, check to see whether
371 	 * or not directory is readable or writable and if not,
372 	 * prompt user for response.
373 	 */
374 	if (!interactive && !ismypath &&
375 	    (access(path, W_OK|X_OK) == FAIL) && isatty(fileno(stdin))) {
376 		if (!silent) {
377 			(void) fprintf(stderr,
378 			    gettext(
379 				"rm: examine files in directory %s (%s/%s)? "),
380 			    fullpath, yeschr, nochr);
381 			/*
382 			 * If the answer is no, skip the directory.
383 			 */
384 			if (!yes()) {
385 				pop_name(first);
386 				return;
387 			}
388 		}
389 	}
390 #endif
391 
392 	/*
393 	 * Add this node to the search tree so we don't
394 	 * get into a endless loop. If the add fails then
395 	 * we have visited this node before.
396 	 */
397 	ret = add_tnode(&tree, dev, ino);
398 	if (ret != 1) {
399 		if (ret == 0) {
400 			(void) fprintf(stderr,
401 			    gettext("rm: cycle detected for %s\n"),
402 			    fullpath);
403 		} else if (ret == -1) {
404 			perror("rm");
405 		}
406 		errcode++;
407 		pop_name(first);
408 		return;
409 	}
410 
411 	/*
412 	 * Open the directory for reading.
413 	 */
414 	if ((name = opendir(path)) == NULL) {
415 		int	saveerrno = errno;
416 
417 		/*
418 		 * If interactive, ask for acknowledgement.
419 		 */
420 		if (interactive) {
421 			/*
422 			 * Print an error message that
423 			 * we could not read the directory
424 			 * as the user wanted to examine
425 			 * files in the directory.  Only
426 			 * affect the error status if
427 			 * user doesn't want to remove the
428 			 * directory as we still may be able
429 			 * remove the directory successfully.
430 			 */
431 			(void) fprintf(stderr, gettext(
432 			    "rm: cannot read directory %s: "),
433 			    fullpath);
434 			errno = saveerrno;
435 			perror("");
436 			(void) fprintf(stderr, gettext(
437 			    "rm: remove %s: (%s/%s)? "),
438 			    fullpath, yeschr, nochr);
439 			if (!yes()) {
440 				++errcode;
441 				pop_name(first);
442 				return;
443 			}
444 		}
445 
446 		/*
447 		 * If the directory is empty, we may be able to
448 		 * go ahead and remove it.
449 		 */
450 		if (rmdir(path) == FAIL) {
451 			if (interactive) {
452 				int	rmdirerr = errno;
453 				(void) fprintf(stderr, gettext(
454 				    "rm: Unable to remove directory %s: "),
455 				    fullpath);
456 				errno = rmdirerr;
457 				perror("");
458 			} else {
459 				(void) fprintf(stderr, gettext(
460 				    "rm: cannot read directory %s: "),
461 				    fullpath);
462 				errno = saveerrno;
463 				perror("");
464 			}
465 			++errcode;
466 		}
467 
468 		/* Continue to next file/directory rather than exit */
469 		pop_name(first);
470 		return;
471 	}
472 
473 	/*
474 	 * XCU4 requires that rm -r descend the directory
475 	 * hierarchy without regard to PATH_MAX.  If we can't
476 	 * chdir() do not increment error counter and do not
477 	 * print message.
478 	 *
479 	 * However, if we cannot chdir because someone has taken away
480 	 * execute access we may still be able to delete the directory
481 	 * if it's empty. The old rm could do this.
482 	 */
483 
484 	if (chdir(path) == -1) {
485 		chdir_failed = 1;
486 	}
487 
488 	/*
489 	 * Read every directory entry.
490 	 */
491 	while ((direct = readdir(name)) != NULL) {
492 		/*
493 		 * Ignore "." and ".." entries.
494 		 */
495 		if (strcmp(direct->d_name, ".") == 0 ||
496 		    strcmp(direct->d_name, "..") == 0)
497 			continue;
498 		/*
499 		 * Try to remove the file.
500 		 */
501 		len = strlen(direct->d_name) + 1;
502 		if (chdir_failed) {
503 			len += strlen(path) + 2;
504 		}
505 
506 		newpath = malloc(len);
507 		if (newpath == NULL) {
508 			(void) fprintf(stderr,
509 			    gettext("rm: Insufficient memory.\n"));
510 			cleanup();
511 			exit(1);
512 		}
513 
514 		if (!chdir_failed) {
515 			(void) strcpy(newpath, direct->d_name);
516 		} else {
517 			(void) snprintf(newpath, len, "%s/%s",
518 			    path, direct->d_name);
519 		}
520 
521 
522 		/*
523 		 * If a spare file descriptor is available, just call the
524 		 * "rm" function with the file name; otherwise close the
525 		 * directory and reopen it when the child is removed.
526 		 */
527 		if (name->dd_fd >= maxfiles) {
528 			(void) closedir(name);
529 			rm(newpath, 0);
530 			if (!chdir_failed)
531 				name = opendir(".");
532 			else
533 				name = opendir(path);
534 			if (name == NULL) {
535 				(void) fprintf(stderr,
536 				    gettext("rm: cannot read directory %s: "),
537 				    fullpath);
538 				perror("");
539 				cleanup();
540 				exit(2);
541 			}
542 		} else
543 			rm(newpath, 0);
544 
545 		free(newpath);
546 	}
547 
548 	/*
549 	 * Close the directory we just finished reading.
550 	 */
551 	(void) closedir(name);
552 
553 	/*
554 	 * The contents of the directory have been removed.  If the
555 	 * directory itself is in the path of the current working
556 	 * directory, don't try to remove it.
557 	 * When the directory itself is the current working directory, mypath()
558 	 * has a return code == 2.
559 	 *
560 	 * XCU4: Because we've descended the directory hierarchy in order
561 	 * to avoid PATH_MAX limitation, we must now start ascending
562 	 * one level at a time and remove files/directories.
563 	 */
564 
565 	if (!chdir_failed) {
566 		if (first)
567 			chdir_home();
568 		else if (chdir("..") == -1) {
569 			(void) fprintf(stderr,
570 			    gettext("rm: cannot change to parent of "
571 				    "directory %s: "),
572 			    fullpath);
573 			perror("");
574 			cleanup();
575 			exit(2);
576 		}
577 	}
578 
579 	switch (ismypath) {
580 	case 3:
581 		pop_name(first);
582 		return;
583 	case 2:
584 		(void) fprintf(stderr,
585 		    gettext("rm: Cannot remove any directory in the path "
586 			"of the current working directory\n%s\n"), fullpath);
587 		++errcode;
588 		pop_name(first);
589 		return;
590 	case 1:
591 		++errcode;
592 		pop_name(first);
593 		return;
594 	case 0:
595 		break;
596 	}
597 
598 	/*
599 	 * If interactive, ask for acknowledgement.
600 	 */
601 	if (interactive) {
602 		(void) fprintf(stderr, gettext("rm: remove %s: (%s/%s)? "),
603 			fullpath, yeschr, nochr);
604 		if (!yes()) {
605 			pop_name(first);
606 			return;
607 		}
608 	}
609 	if (rmdir(path) == FAIL) {
610 		(void) fprintf(stderr,
611 			gettext("rm: Unable to remove directory %s: "),
612 			fullpath);
613 		perror("");
614 		++errcode;
615 	}
616 	pop_name(first);
617 }
618 
619 
620 static int
621 yes(void)
622 {
623 	int	i, b;
624 	char	ans[SCHAR_MAX + 1];
625 
626 	for (i = 0; ; i++) {
627 		b = getchar();
628 		if (b == '\n' || b == '\0' || b == EOF) {
629 			ans[i] = 0;
630 			break;
631 		}
632 		if (i < SCHAR_MAX)
633 			ans[i] = b;
634 	}
635 	if (i >= SCHAR_MAX) {
636 		i = SCHAR_MAX;
637 		ans[SCHAR_MAX] = 0;
638 	}
639 	if ((i == 0) | (strncmp(yeschr, ans, i)))
640 		return (0);
641 	return (1);
642 }
643 
644 
645 static int
646 mypath(dev_t dev, ino_t ino)
647 {
648 	struct dir_id *curdir;
649 
650 	/*
651 	 * Check to see if this is our current directory
652 	 * Indicated by return 2;
653 	 */
654 	if (dev == homedir.dev && ino == homedir.inode) {
655 		return (2);
656 	}
657 
658 	curdir = homedir.next;
659 
660 	while (curdir != NULL) {
661 		/*
662 		 * If we find a match, the directory (dev, ino) passed to
663 		 * mypath() is an ancestor of ours. Indicated by return 3.
664 		 */
665 		if (curdir->dev == dev && curdir->inode == ino)
666 			return (3);
667 		curdir = curdir->next;
668 	}
669 	/*
670 	 * parent_err indicates we couldn't stat or chdir to
671 	 * one of our parent dirs, so the linked list of dir_id structs
672 	 * is incomplete
673 	 */
674 	if (parent_err) {
675 #ifndef XPG4
676 		if (!silent || interactive) {
677 #endif
678 			(void) fprintf(stderr, gettext("rm: cannot determine "
679 			    "if this is an ancestor of the current "
680 			    "working directory\n%s\n"), fullpath);
681 #ifndef XPG4
682 		}
683 #endif
684 		/* assume it is. least dangerous */
685 		return (1);
686 	}
687 	return (0);
688 }
689 
690 static int maxlen;
691 static int curlen;
692 
693 static char *
694 get_filename(char *name)
695 {
696 	char *path;
697 	size_t len;
698 
699 	if (fullpath == NULL || *fullpath == '\0') {
700 		path = strdup(name);
701 		if (path == NULL) {
702 			(void) fprintf(stderr,
703 			    gettext("rm: Insufficient memory.\n"));
704 			cleanup();
705 			exit(1);
706 		}
707 	} else {
708 		len = strlen(fullpath) + strlen(name) + 2;
709 		path = malloc(len);
710 		if (path == NULL) {
711 			(void) fprintf(stderr,
712 			    gettext("rm: Insufficient memory.\n"));
713 			cleanup();
714 			exit(1);
715 		}
716 		(void) snprintf(path, len, "%s/%s", fullpath, name);
717 	}
718 	return (path);
719 }
720 
721 static void
722 push_name(char *name, int first)
723 {
724 	int	namelen;
725 
726 	namelen = strlen(name) + 1; /* 1 for "/" */
727 	if ((curlen + namelen) >= maxlen) {
728 		maxlen += PATH_MAX;
729 		fullpath = (char *)realloc(fullpath, (size_t)(maxlen + 1));
730 	}
731 	if (first) {
732 		(void) strcpy(fullpath, name);
733 	} else {
734 		(void) strcat(fullpath, "/");
735 		(void) strcat(fullpath, name);
736 	}
737 	curlen = strlen(fullpath);
738 }
739 
740 static void
741 pop_name(int first)
742 {
743 	char *slash;
744 	if (first) {
745 		*fullpath = '\0';
746 		return;
747 	}
748 	slash = strrchr(fullpath, '/');
749 	if (slash)
750 		*slash = '\0';
751 	else
752 		*fullpath = '\0';
753 	curlen = strlen(fullpath);
754 }
755 
756 static void
757 force_chdir(char *dirname)
758 {
759 	char 	*pathname, *mp, *tp;
760 
761 	/* use pathname instead of dirname, so dirname won't be modified */
762 	if ((pathname = strdup(dirname)) == NULL) {
763 		(void) fprintf(stderr, gettext("rm: strdup: "));
764 		perror("");
765 		cleanup();
766 		exit(2);
767 	}
768 
769 	/* pathname is an absolute full path from getcwd() */
770 	mp = pathname;
771 	while (mp) {
772 		tp = strchr(mp, '/');
773 		if (strlen(mp) >= PATH_MAX) {
774 			/*
775 			 * after the first iteration through this
776 			 * loop, the below will NULL out the '/'
777 			 * which follows the first dir on pathname
778 			 */
779 			*tp = 0;
780 			tp++;
781 			if (*mp == NULL)
782 				ch_dir("/");
783 			else
784 				/*
785 				 * mp points to the start of a dirname,
786 				 * terminated by NULL, so ch_dir()
787 				 * here will move down one directory
788 				 */
789 				ch_dir(mp);
790 			/*
791 			 * reset mp to the start of the dirname
792 			 * which follows the one we just chdir'd to
793 			 */
794 			mp = tp;
795 			continue;	/* probably can remove this */
796 		} else {
797 			ch_dir(mp);
798 			break;
799 		}
800 	}
801 	free(pathname);
802 }
803 
804 static void
805 ch_dir(char *dirname)
806 {
807 	if (chdir(dirname) == -1) {
808 		(void) fprintf(stderr,
809 		gettext("rm: cannot change to %s directory: "), dirname);
810 			perror("");
811 			cleanup();
812 			exit(2);
813 	}
814 }
815 
816 static void
817 chdir_home(void)
818 {
819 	/*
820 	 * Go back to home dir--the dir from where rm was executed--using
821 	 * one of two methods, depending on which method works
822 	 * for the given permissions of the home dir and its
823 	 * parent directories.
824 	 */
825 	if (homedirfd != -1) {
826 		if (fchdir(homedirfd) == -1) {
827 			(void) fprintf(stderr,
828 			    gettext("rm: cannot change to starting "
829 			    "directory: "));
830 			perror("");
831 			cleanup();
832 			exit(2);
833 		}
834 	} else {
835 		if (strlen(cwd) < PATH_MAX)
836 			ch_dir(cwd);
837 		else
838 			force_chdir(cwd);
839 	}
840 }
841 
842 /*
843  * check_homedir -
844  * is only called the first time rm tries to
845  * remove a directory.  It saves the current directory, i.e.,
846  * home dir, so we can go back to it after traversing elsewhere.
847  * It also saves all the device and inode numbers of each
848  * dir from the home dir back to the root in a linked list, so we
849  * can later check, via mypath(), if we are trying to remove our current
850  * dir or an ancestor.
851  */
852 static void
853 check_homedir(void)
854 {
855 	int	size;	/* size allocated for pathname of home dir (cwd) */
856 	struct stat buffer;
857 	struct dir_id *lastdir, *curdir;
858 
859 	/*
860 	 * We need to save where we currently are (the "home dir") so
861 	 * we can return after traversing down directories we're
862 	 * removing.  Two methods are attempted:
863 	 *
864 	 * 1) open() the home dir so we can use the fd
865 	 *    to fchdir() back.  This requires read permission
866 	 *    on the home dir.
867 	 *
868 	 * 2) getcwd() so we can chdir() to go back.  This
869 	 *    requires search (x) permission on the home dir,
870 	 *    and read and search permission on all parent dirs.  Also,
871 	 *    getcwd() will not work if the home dir is > 341
872 	 *    directories deep (see open bugid 4033182 - getcwd needs
873 	 *    to work for pathnames of any depth).
874 	 *
875 	 * If neither method works, we can't remove any directories
876 	 * and rm will fail.
877 	 *
878 	 * For future enhancement, a possible 3rd option to use
879 	 * would be to fork a process to remove a directory,
880 	 * eliminating the need to chdir back to the home directory
881 	 * and eliminating the permission restrictions on the home dir
882 	 * or its parent dirs.
883 	 */
884 	homedirfd = open(".", O_RDONLY);
885 	if (homedirfd == -1) {
886 		size = PATH_MAX;
887 		while ((cwd = getcwd(NULL, size)) == NULL) {
888 			if (errno == ERANGE) {
889 				size = PATH_MAX + size;
890 				continue;
891 			} else {
892 				(void) fprintf(stderr,
893 				    gettext("rm: cannot open starting "
894 				    "directory: "));
895 				perror("pwd");
896 				exit(2);
897 			}
898 		}
899 	}
900 
901 	/*
902 	 * since we exit on error here, we're guaranteed to at least
903 	 * have info in the first dir_id struct, homedir
904 	 */
905 	if (stat(".", &buffer) == -1) {
906 		(void) fprintf(stderr,
907 		    gettext("rm: cannot stat current directory: "));
908 		perror("");
909 		exit(2);
910 	}
911 	homedir.dev = buffer.st_dev;
912 	homedir.inode = buffer.st_ino;
913 	homedir.next = NULL;
914 
915 	lastdir = &homedir;
916 	/*
917 	 * Starting from current working directory, walk toward the
918 	 * root, looking at each directory along the way.
919 	 */
920 	for (;;) {
921 		if (chdir("..") == -1 || lstat(".", &buffer) == -1) {
922 			parent_err = 1;
923 			break;
924 		}
925 
926 		if ((lastdir->next = malloc(sizeof (struct dir_id))) ==
927 		    NULL) {
928 			(void) fprintf(stderr,
929 			    gettext("rm: Insufficient memory.\n"));
930 			cleanup();
931 			exit(1);
932 		}
933 
934 		curdir = lastdir->next;
935 		curdir->dev = buffer.st_dev;
936 		curdir->inode = buffer.st_ino;
937 		curdir->next = NULL;
938 
939 		/*
940 		 * Stop when we reach the root; note that chdir("..")
941 		 * at the root dir will stay in root. Get rid of
942 		 * the redundant dir_id struct for root.
943 		 */
944 		if (curdir->dev == lastdir->dev && curdir->inode ==
945 		    lastdir->inode) {
946 			lastdir->next = NULL;
947 			free(curdir);
948 			break;
949 		}
950 
951 			/* loop again to go back another level */
952 		lastdir = curdir;
953 	}
954 		/* go back to home directory */
955 	chdir_home();
956 }
957 
958 /*
959  * cleanup the dynamically-allocated list of device numbers and inodes,
960  * if any.  If homedir was never used, it is external and static so
961  * it is guaranteed initialized to zero, thus homedir.next would be NULL.
962  */
963 
964 static void
965 cleanup(void) {
966 	struct dir_id *lastdir, *curdir;
967 
968 	curdir = homedir.next;
969 
970 	while (curdir != NULL) {
971 		lastdir = curdir;
972 		curdir = curdir->next;
973 		free(lastdir);
974 	}
975 }
976