xref: /illumos-gate/usr/src/cmd/rm/rm.c (revision 355b4669e025ff377602b6fc7caaf30dbc218371)
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