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