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