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