xref: /titanic_53/usr/src/cmd/rm/rm.c (revision 3d63ea05cb8474d8036d3588cf8299306a994b8c)
17c478bd9Sstevel@tonic-gate /*
27c478bd9Sstevel@tonic-gate  * CDDL HEADER START
37c478bd9Sstevel@tonic-gate  *
47c478bd9Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
579033acbSas145665  * Common Development and Distribution License (the "License").
679033acbSas145665  * You may not use this file except in compliance with the License.
77c478bd9Sstevel@tonic-gate  *
87c478bd9Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
97c478bd9Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
107c478bd9Sstevel@tonic-gate  * See the License for the specific language governing permissions
117c478bd9Sstevel@tonic-gate  * and limitations under the License.
127c478bd9Sstevel@tonic-gate  *
137c478bd9Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
147c478bd9Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
157c478bd9Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
167c478bd9Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
177c478bd9Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
187c478bd9Sstevel@tonic-gate  *
197c478bd9Sstevel@tonic-gate  * CDDL HEADER END
207c478bd9Sstevel@tonic-gate  */
217c478bd9Sstevel@tonic-gate 
227c478bd9Sstevel@tonic-gate /*
2348011479Ssn199410  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
247c478bd9Sstevel@tonic-gate  * Use is subject to license terms.
257c478bd9Sstevel@tonic-gate  */
267c478bd9Sstevel@tonic-gate 
27014a7923Sas145665 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
28014a7923Sas145665 /*	All Rights Reserved   */
29014a7923Sas145665 
307c478bd9Sstevel@tonic-gate #pragma ident	"%Z%%M%	%I%	%E% SMI"
317c478bd9Sstevel@tonic-gate 
327c478bd9Sstevel@tonic-gate /*
337c478bd9Sstevel@tonic-gate  * rm [-fiRr] file ...
347c478bd9Sstevel@tonic-gate  */
357c478bd9Sstevel@tonic-gate 
3648011479Ssn199410 #include <sys/param.h>
377c478bd9Sstevel@tonic-gate #include <sys/stat.h>
387c478bd9Sstevel@tonic-gate #include <dirent.h>
3948011479Ssn199410 #include <errno.h>
4048011479Ssn199410 #include <fcntl.h>
4148011479Ssn199410 #include <langinfo.h>
427c478bd9Sstevel@tonic-gate #include <limits.h>
437c478bd9Sstevel@tonic-gate #include <locale.h>
4448011479Ssn199410 #include <stdarg.h>
4548011479Ssn199410 #include <stdio.h>
467c478bd9Sstevel@tonic-gate #include <stdlib.h>
4748011479Ssn199410 #include <string.h>
4848011479Ssn199410 #include <unistd.h>
4948011479Ssn199410 #include <values.h>
50*3d63ea05Sas145665 #include "getresponse.h"
517c478bd9Sstevel@tonic-gate 
5248011479Ssn199410 #define	E_OK	010		/* make __accessat() use effective ids */
537c478bd9Sstevel@tonic-gate 
5448011479Ssn199410 #define	DIR_CANTCLOSE		1
557c478bd9Sstevel@tonic-gate 
5648011479Ssn199410 static struct stat rootdir;
5748011479Ssn199410 
5848011479Ssn199410 struct dlist {
5948011479Ssn199410 	int fd;			/* Stores directory fd */
6048011479Ssn199410 	int flags;		/* DIR_* Flags */
6148011479Ssn199410 	DIR *dp;		/* Open directory (opened with fd) */
6248011479Ssn199410 	long diroff;		/* Saved directory offset when closing */
6348011479Ssn199410 	struct dlist *up;	/* Up one step in the tree (toward "/") */
6448011479Ssn199410 	struct dlist *down;	/* Down one step in the tree */
6548011479Ssn199410 	ino_t ino;		/* st_ino of directory */
6648011479Ssn199410 	dev_t dev;		/* st_dev of directory */
6748011479Ssn199410 	int pathend;		/* Offset of name end in the pathbuffer */
6848011479Ssn199410 };
6948011479Ssn199410 
7048011479Ssn199410 static struct dlist top = {
7148011479Ssn199410 	(int)AT_FDCWD,
7248011479Ssn199410 	DIR_CANTCLOSE,
7348011479Ssn199410 };
747c478bd9Sstevel@tonic-gate 
7548011479Ssn199410 static struct dlist *cur, *rec;
767c478bd9Sstevel@tonic-gate 
7748011479Ssn199410 static int rm(const char *, struct dlist *);
7848011479Ssn199410 static int confirm(FILE *, const char *, ...);
7948011479Ssn199410 static void memerror(void);
8048011479Ssn199410 static int checkdir(struct dlist *, struct dlist *);
8148011479Ssn199410 static int errcnt;
8248011479Ssn199410 static boolean_t silent, interactive, recursive, ontty;
837c478bd9Sstevel@tonic-gate 
8448011479Ssn199410 static char *pathbuf;
85131f4edeSsn199410 static size_t pathbuflen = MAXPATHLEN;
867c478bd9Sstevel@tonic-gate 
8748011479Ssn199410 static int maxfds = MAXINT;
8848011479Ssn199410 static int nfds;
897c478bd9Sstevel@tonic-gate 
9048011479Ssn199410 extern int __accessat(int, const char *, int);
917c478bd9Sstevel@tonic-gate 
927c478bd9Sstevel@tonic-gate int
9348011479Ssn199410 main(int argc, char **argv)
947c478bd9Sstevel@tonic-gate {
957c478bd9Sstevel@tonic-gate 	int errflg = 0;
967c478bd9Sstevel@tonic-gate 	int c;
977c478bd9Sstevel@tonic-gate 
987c478bd9Sstevel@tonic-gate 	(void) setlocale(LC_ALL, "");
997c478bd9Sstevel@tonic-gate #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
1007c478bd9Sstevel@tonic-gate #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
1017c478bd9Sstevel@tonic-gate #endif
1027c478bd9Sstevel@tonic-gate 	(void) textdomain(TEXT_DOMAIN);
1037c478bd9Sstevel@tonic-gate 
1047c478bd9Sstevel@tonic-gate 	while ((c = getopt(argc, argv, "frRi")) != EOF)
1057c478bd9Sstevel@tonic-gate 		switch (c) {
1067c478bd9Sstevel@tonic-gate 		case 'f':
10748011479Ssn199410 			silent = B_TRUE;
1087c478bd9Sstevel@tonic-gate #ifdef XPG4
10948011479Ssn199410 			interactive = B_FALSE;
1107c478bd9Sstevel@tonic-gate #endif
1117c478bd9Sstevel@tonic-gate 			break;
1127c478bd9Sstevel@tonic-gate 		case 'i':
11348011479Ssn199410 			interactive = B_TRUE;
1147c478bd9Sstevel@tonic-gate #ifdef XPG4
11548011479Ssn199410 			silent = B_FALSE;
1167c478bd9Sstevel@tonic-gate #endif
1177c478bd9Sstevel@tonic-gate 			break;
1187c478bd9Sstevel@tonic-gate 		case 'r':
1197c478bd9Sstevel@tonic-gate 		case 'R':
12048011479Ssn199410 			recursive = B_TRUE;
1217c478bd9Sstevel@tonic-gate 			break;
1227c478bd9Sstevel@tonic-gate 		case '?':
1237c478bd9Sstevel@tonic-gate 			errflg = 1;
1247c478bd9Sstevel@tonic-gate 			break;
1257c478bd9Sstevel@tonic-gate 		}
1267c478bd9Sstevel@tonic-gate 
1277c478bd9Sstevel@tonic-gate 	/*
1287c478bd9Sstevel@tonic-gate 	 * For BSD compatibility allow '-' to delimit the end
1297c478bd9Sstevel@tonic-gate 	 * of options.  However, if options were already explicitly
1307c478bd9Sstevel@tonic-gate 	 * terminated with '--', then treat '-' literally: otherwise,
1317c478bd9Sstevel@tonic-gate 	 * "rm -- -" won't remove '-'.
1327c478bd9Sstevel@tonic-gate 	 */
1337c478bd9Sstevel@tonic-gate 	if (optind < argc &&
1347c478bd9Sstevel@tonic-gate 	    strcmp(argv[optind], "-") == 0 &&
1357c478bd9Sstevel@tonic-gate 	    strcmp(argv[optind - 1], "--") != 0)
1367c478bd9Sstevel@tonic-gate 		optind++;
1377c478bd9Sstevel@tonic-gate 
1387c478bd9Sstevel@tonic-gate 	argc -= optind;
1397c478bd9Sstevel@tonic-gate 	argv = &argv[optind];
1407c478bd9Sstevel@tonic-gate 
1417c478bd9Sstevel@tonic-gate 	if ((argc < 1 && !silent) || errflg) {
14248011479Ssn199410 		(void) fprintf(stderr, gettext("usage: rm [-fiRr] file ...\n"));
14348011479Ssn199410 		exit(2);
14448011479Ssn199410 	}
14548011479Ssn199410 
14648011479Ssn199410 	ontty = isatty(STDIN_FILENO) != 0;
14748011479Ssn199410 
14848011479Ssn199410 	if (recursive && stat("/", &rootdir) != 0) {
1497c478bd9Sstevel@tonic-gate 		(void) fprintf(stderr,
15048011479Ssn199410 		    gettext("rm: cannot stat root directory: %s\n"),
15148011479Ssn199410 		    strerror(errno));
1527c478bd9Sstevel@tonic-gate 		exit(2);
1537c478bd9Sstevel@tonic-gate 	}
1547c478bd9Sstevel@tonic-gate 
155131f4edeSsn199410 	pathbuf = malloc(pathbuflen);
156131f4edeSsn199410 	if (pathbuf == NULL)
157131f4edeSsn199410 		memerror();
158131f4edeSsn199410 
159*3d63ea05Sas145665 	if (init_yes() < 0) {
160*3d63ea05Sas145665 		(void) fprintf(stderr, gettext(ERR_MSG_INIT_YES),
161*3d63ea05Sas145665 		    strerror(errno));
162*3d63ea05Sas145665 		exit(2);
163*3d63ea05Sas145665 	}
164*3d63ea05Sas145665 
16548011479Ssn199410 	for (; *argv != NULL; argv++) {
16648011479Ssn199410 		char *p = strrchr(*argv, '/');
16748011479Ssn199410 		if (p == NULL)
16848011479Ssn199410 			p = *argv;
16948011479Ssn199410 		else
17048011479Ssn199410 			p = p + 1;
17148011479Ssn199410 		if (strcmp(p, ".") == 0 || strcmp(p, "..") == 0) {
17248011479Ssn199410 			(void) fprintf(stderr,
17348011479Ssn199410 			    gettext("rm of %s is not allowed\n"), *argv);
17448011479Ssn199410 			errcnt++;
17548011479Ssn199410 			continue;
17648011479Ssn199410 		}
17748011479Ssn199410 		/* Retry when we can't walk back up. */
17848011479Ssn199410 		while (rm(*argv, rec = cur = &top) != 0)
179996aa816Ssn199410 			;
1807c478bd9Sstevel@tonic-gate 	}
18112a9e0efSsn199410 
18248011479Ssn199410 	return (errcnt != 0 ? 2 : 0);
18348011479Ssn199410 }
18448011479Ssn199410 
18548011479Ssn199410 static void
18648011479Ssn199410 pushfilename(const char *fname)
18748011479Ssn199410 {
18848011479Ssn199410 	char *p;
18948011479Ssn199410 	const char *q = fname;
19048011479Ssn199410 
19148011479Ssn199410 	if (cur == &top) {
19248011479Ssn199410 		p = pathbuf;
19348011479Ssn199410 	} else {
19448011479Ssn199410 		p = pathbuf + cur->up->pathend;
19548011479Ssn199410 		*p++ = '/';
19648011479Ssn199410 	}
19748011479Ssn199410 	while (*q != '\0') {
19848011479Ssn199410 		if (p - pathbuf + 2 >= pathbuflen) {
19948011479Ssn199410 			char *np;
20048011479Ssn199410 			pathbuflen += MAXPATHLEN;
20148011479Ssn199410 			np = realloc(pathbuf, pathbuflen);
20248011479Ssn199410 			if (np == NULL)
20348011479Ssn199410 				memerror();
20448011479Ssn199410 			p = np + (p - pathbuf);
20548011479Ssn199410 			pathbuf = np;
20648011479Ssn199410 		}
20748011479Ssn199410 		*p++ = *q++;
20848011479Ssn199410 	}
20948011479Ssn199410 	*p = '\0';
21048011479Ssn199410 	cur->pathend = p - pathbuf;
21148011479Ssn199410 }
21248011479Ssn199410 
21348011479Ssn199410 static void
21448011479Ssn199410 closeframe(struct dlist *frm)
21548011479Ssn199410 {
21648011479Ssn199410 	if (frm->dp != NULL) {
21748011479Ssn199410 		(void) closedir(frm->dp);
21848011479Ssn199410 		nfds--;
21948011479Ssn199410 		frm->dp = NULL;
22048011479Ssn199410 		frm->fd = -1;
22148011479Ssn199410 	}
2227c478bd9Sstevel@tonic-gate }
2237c478bd9Sstevel@tonic-gate 
224996aa816Ssn199410 static int
22548011479Ssn199410 reclaim(void)
2267c478bd9Sstevel@tonic-gate {
22748011479Ssn199410 	while (rec != NULL && (rec->flags & DIR_CANTCLOSE) != 0)
22848011479Ssn199410 		rec = rec->down;
22948011479Ssn199410 	if (rec == NULL || rec == cur || rec->dp == NULL)
23048011479Ssn199410 		return (-1);
23148011479Ssn199410 	rec->diroff = telldir(rec->dp);
23248011479Ssn199410 	closeframe(rec);
23348011479Ssn199410 	rec = rec->down;
23448011479Ssn199410 	return (0);
23548011479Ssn199410 }
23648011479Ssn199410 
23748011479Ssn199410 static void
23848011479Ssn199410 pushdir(struct dlist *frm)
23948011479Ssn199410 {
24048011479Ssn199410 	frm->up = cur;
24148011479Ssn199410 	frm->down = NULL;
24248011479Ssn199410 	cur->down = frm;
24348011479Ssn199410 	cur = frm;
24448011479Ssn199410 }
24548011479Ssn199410 
24648011479Ssn199410 static int
24748011479Ssn199410 opendirat(int dirfd, const char *entry, struct dlist *frm)
24848011479Ssn199410 {
24948011479Ssn199410 	int fd;
25048011479Ssn199410 
25148011479Ssn199410 	if (nfds >= maxfds)
25248011479Ssn199410 		(void) reclaim();
25348011479Ssn199410 
25448011479Ssn199410 	while ((fd = openat(dirfd, entry, O_RDONLY|O_NONBLOCK)) == -1 &&
25548011479Ssn199410 	    errno == EMFILE) {
25648011479Ssn199410 		if (nfds < maxfds)
25748011479Ssn199410 			maxfds = nfds;
25848011479Ssn199410 		if (reclaim() != 0)
25948011479Ssn199410 			return (-1);
26048011479Ssn199410 	}
26148011479Ssn199410 	if (fd < 0)
26248011479Ssn199410 		return (-1);
26348011479Ssn199410 	frm->fd = fd;
26448011479Ssn199410 	frm->dp = fdopendir(fd);
26548011479Ssn199410 	if (frm->dp == NULL) {
26648011479Ssn199410 		(void) close(fd);
26748011479Ssn199410 		return (-1);
26848011479Ssn199410 	}
26948011479Ssn199410 	nfds++;
27048011479Ssn199410 	return (0);
27148011479Ssn199410 }
2727c478bd9Sstevel@tonic-gate 
2737c478bd9Sstevel@tonic-gate /*
27448011479Ssn199410  * Since we never pop the top frame, cur->up can never be NULL.
27548011479Ssn199410  * If we pop beyond a frame we closed, we try to reopen "..".
2767c478bd9Sstevel@tonic-gate  */
27748011479Ssn199410 static int
27848011479Ssn199410 popdir(boolean_t noerror)
27948011479Ssn199410 {
28048011479Ssn199410 	struct stat buf;
28148011479Ssn199410 	int ret = noerror ? 0 : -1;
28248011479Ssn199410 	pathbuf[cur->up->pathend] = '\0';
28348011479Ssn199410 
28448011479Ssn199410 	if (noerror && cur->up->fd == -1) {
28548011479Ssn199410 		rec = cur->up;
28648011479Ssn199410 		if (opendirat(cur->fd, "..", rec) != 0 ||
28748011479Ssn199410 		    fstat(rec->fd, &buf) != 0) {
28848011479Ssn199410 			(void) fprintf(stderr,
28948011479Ssn199410 			    gettext("rm: cannot reopen %s: %s\n"),
29048011479Ssn199410 			    pathbuf, strerror(errno));
29148011479Ssn199410 			exit(2);
29248011479Ssn199410 		}
29348011479Ssn199410 		if (rec->ino != buf.st_ino || rec->dev != buf.st_dev) {
29448011479Ssn199410 			(void) fprintf(stderr, gettext("rm: WARNING: "
29548011479Ssn199410 			    "The directory %s was moved or linked to "
29648011479Ssn199410 			    "another directory during the execution of rm\n"),
29748011479Ssn199410 			    pathbuf);
29848011479Ssn199410 			closeframe(rec);
29948011479Ssn199410 			ret = -1;
30048011479Ssn199410 		} else {
30148011479Ssn199410 			/* If telldir failed, we take it from the top. */
30248011479Ssn199410 			if (rec->diroff != -1)
30348011479Ssn199410 				seekdir(rec->dp, rec->diroff);
30448011479Ssn199410 		}
30548011479Ssn199410 	} else if (rec == cur)
30648011479Ssn199410 		rec = cur->up;
30748011479Ssn199410 	closeframe(cur);
30848011479Ssn199410 	cur = cur->up;
30948011479Ssn199410 	cur->down = NULL;
31048011479Ssn199410 	return (ret);
31148011479Ssn199410 }
31248011479Ssn199410 
31348011479Ssn199410 /*
31448011479Ssn199410  * The stack frame of this function is minimized so that we can
31548011479Ssn199410  * recurse quite a bit before we overflow the stack; around
31648011479Ssn199410  * 30,000-40,000 nested directories can be removed with the default
31748011479Ssn199410  * stack limit.
31848011479Ssn199410  */
31948011479Ssn199410 static int
32048011479Ssn199410 rm(const char *entry, struct dlist *caller)
32148011479Ssn199410 {
32248011479Ssn199410 	struct dlist frame;
32348011479Ssn199410 	int flag;
32448011479Ssn199410 	struct stat temp;
32548011479Ssn199410 	struct dirent *dent;
32648011479Ssn199410 	int err;
32748011479Ssn199410 
32848011479Ssn199410 	/*
32948011479Ssn199410 	 * Construct the pathname: note that the entry may live in memory
33048011479Ssn199410 	 * allocated by readdir and that after return from recursion
33148011479Ssn199410 	 * the memory is no longer valid.  So after the recursive rm()
33248011479Ssn199410 	 * call, we use the global pathbuf instead of the entry argument.
33348011479Ssn199410 	 */
33448011479Ssn199410 	pushfilename(entry);
33548011479Ssn199410 
33648011479Ssn199410 	if (fstatat(caller->fd, entry, &temp, AT_SYMLINK_NOFOLLOW) != 0) {
3377c478bd9Sstevel@tonic-gate 		if (!silent) {
33848011479Ssn199410 			(void) fprintf(stderr, "rm: %s: %s\n", pathbuf,
33948011479Ssn199410 			    strerror(errno));
34048011479Ssn199410 			errcnt++;
3417c478bd9Sstevel@tonic-gate 		}
342996aa816Ssn199410 		return (0);
3437c478bd9Sstevel@tonic-gate 	}
3447c478bd9Sstevel@tonic-gate 
34548011479Ssn199410 	if (S_ISDIR(temp.st_mode)) {
3467c478bd9Sstevel@tonic-gate 		/*
3477c478bd9Sstevel@tonic-gate 		 * If "-r" wasn't specified, trying to remove directories
3487c478bd9Sstevel@tonic-gate 		 * is an error.
3497c478bd9Sstevel@tonic-gate 		 */
3507c478bd9Sstevel@tonic-gate 		if (!recursive) {
3517c478bd9Sstevel@tonic-gate 			(void) fprintf(stderr,
35248011479Ssn199410 			    gettext("rm: %s is a directory\n"), pathbuf);
35348011479Ssn199410 			errcnt++;
354996aa816Ssn199410 			return (0);
3557c478bd9Sstevel@tonic-gate 		}
3567c478bd9Sstevel@tonic-gate 
35748011479Ssn199410 		if (temp.st_ino == rootdir.st_ino &&
35848011479Ssn199410 		    temp.st_dev == rootdir.st_dev) {
3597c478bd9Sstevel@tonic-gate 			(void) fprintf(stderr,
36048011479Ssn199410 			    gettext("rm of %s is not allowed\n"), "/");
36148011479Ssn199410 			errcnt++;
362996aa816Ssn199410 			return (0);
3637c478bd9Sstevel@tonic-gate 		}
3647c478bd9Sstevel@tonic-gate 		/*
3657c478bd9Sstevel@tonic-gate 		 * TRANSLATION_NOTE - The following message will contain the
3667c478bd9Sstevel@tonic-gate 		 * first character of the strings for "yes" and "no" defined
3677c478bd9Sstevel@tonic-gate 		 * in the file "nl_langinfo.po".  After substitution, the
3687c478bd9Sstevel@tonic-gate 		 * message will appear as follows:
3697c478bd9Sstevel@tonic-gate 		 *	rm: examine files in directory <directoryname> (y/n)?
3707c478bd9Sstevel@tonic-gate 		 * where <directoryname> is the directory to be removed
3717c478bd9Sstevel@tonic-gate 		 *
3727c478bd9Sstevel@tonic-gate 		 */
37348011479Ssn199410 		if (interactive && !confirm(stderr,
3747c478bd9Sstevel@tonic-gate 		    gettext("rm: examine files in directory %s (%s/%s)? "),
375*3d63ea05Sas145665 		    pathbuf, yesstr, nostr)) {
37648011479Ssn199410 			return (0);
3777c478bd9Sstevel@tonic-gate 		}
3787c478bd9Sstevel@tonic-gate 
37948011479Ssn199410 		frame.dev = temp.st_dev;
38048011479Ssn199410 		frame.ino = temp.st_ino;
38148011479Ssn199410 		frame.flags = 0;
38248011479Ssn199410 		flag = AT_REMOVEDIR;
38348011479Ssn199410 
3847c478bd9Sstevel@tonic-gate #ifdef XPG4
3857c478bd9Sstevel@tonic-gate 		/*
38648011479Ssn199410 		 * XCU4 and POSIX.2: If not interactive, check to see whether
3877c478bd9Sstevel@tonic-gate 		 * or not directory is readable or writable and if not,
3887c478bd9Sstevel@tonic-gate 		 * prompt user for response.
3897c478bd9Sstevel@tonic-gate 		 */
39048011479Ssn199410 		if (ontty && !interactive && !silent &&
39148011479Ssn199410 		    __accessat(caller->fd, entry, W_OK|X_OK|E_OK) != 0 &&
39248011479Ssn199410 		    !confirm(stderr,
39348011479Ssn199410 		    gettext("rm: examine files in directory %s (%s/%s)? "),
394*3d63ea05Sas145665 		    pathbuf, yesstr, nostr)) {
39548011479Ssn199410 			return (0);
3967c478bd9Sstevel@tonic-gate 		}
3977c478bd9Sstevel@tonic-gate #endif
39848011479Ssn199410 		if (opendirat(caller->fd, entry, &frame) == -1) {
39948011479Ssn199410 			err = errno;
4007c478bd9Sstevel@tonic-gate 
4017c478bd9Sstevel@tonic-gate 			if (interactive) {
4027c478bd9Sstevel@tonic-gate 				/*
4037c478bd9Sstevel@tonic-gate 				 * Print an error message that
4047c478bd9Sstevel@tonic-gate 				 * we could not read the directory
4057c478bd9Sstevel@tonic-gate 				 * as the user wanted to examine
4067c478bd9Sstevel@tonic-gate 				 * files in the directory.  Only
4077c478bd9Sstevel@tonic-gate 				 * affect the error status if
4087c478bd9Sstevel@tonic-gate 				 * user doesn't want to remove the
4097c478bd9Sstevel@tonic-gate 				 * directory as we still may be able
4107c478bd9Sstevel@tonic-gate 				 * remove the directory successfully.
4117c478bd9Sstevel@tonic-gate 				 */
4127c478bd9Sstevel@tonic-gate 				(void) fprintf(stderr, gettext(
41348011479Ssn199410 				    "rm: cannot read directory %s: %s\n"),
41448011479Ssn199410 				    pathbuf, strerror(err));
41548011479Ssn199410 
41648011479Ssn199410 /*
41748011479Ssn199410  * TRANSLATION_NOTE - The following message will contain the
41848011479Ssn199410  * first character of the strings for "yes" and "no" defined
41948011479Ssn199410  * in the file "nl_langinfo.po".  After substitution, the
42048011479Ssn199410  * message will appear as follows:
42148011479Ssn199410  *	rm: remove <filename> (y/n)?
42248011479Ssn199410  * For example, in German, this will appear as
42348011479Ssn199410  * 	rm: l�schen <filename> (j/n)?
42448011479Ssn199410  * where j=ja, n=nein, <filename>=the file to be removed
42548011479Ssn199410  */
42648011479Ssn199410 				if (!confirm(stderr,
42748011479Ssn199410 				    gettext("rm: remove %s (%s/%s)? "),
428*3d63ea05Sas145665 				    pathbuf, yesstr, nostr)) {
42948011479Ssn199410 					errcnt++;
43048011479Ssn199410 					return (0);
4317c478bd9Sstevel@tonic-gate 				}
4327c478bd9Sstevel@tonic-gate 			}
43348011479Ssn199410 			/* If it's empty we may still be able to rm it */
43448011479Ssn199410 			if (unlinkat(caller->fd, entry, flag) == 0)
43548011479Ssn199410 				return (0);
43648011479Ssn199410 			if (interactive)
43748011479Ssn199410 				err = errno;
43848011479Ssn199410 			(void) fprintf(stderr,
43948011479Ssn199410 			    interactive ?
44048011479Ssn199410 			    gettext("rm: Unable to remove directory %s: %s\n") :
44148011479Ssn199410 			    gettext("rm: cannot read directory %s: %s\n"),
44248011479Ssn199410 			    pathbuf, strerror(err));
44348011479Ssn199410 			errcnt++;
44448011479Ssn199410 			return (0);
44548011479Ssn199410 		}
4467c478bd9Sstevel@tonic-gate 
4477c478bd9Sstevel@tonic-gate 		/*
44848011479Ssn199410 		 * There is a race condition here too; if we open a directory
44948011479Ssn199410 		 * we have to make sure it's still the same directory we
45048011479Ssn199410 		 * stat'ed and checked against root earlier.  Let's check.
4517c478bd9Sstevel@tonic-gate 		 */
45248011479Ssn199410 		if (fstat(frame.fd, &temp) != 0 ||
45348011479Ssn199410 		    frame.ino != temp.st_ino ||
45448011479Ssn199410 		    frame.dev != temp.st_dev) {
45548011479Ssn199410 			(void) fprintf(stderr,
45648011479Ssn199410 			    gettext("rm: %s: directory renamed\n"), pathbuf);
45748011479Ssn199410 			closeframe(&frame);
45848011479Ssn199410 			errcnt++;
45948011479Ssn199410 			return (0);
4607c478bd9Sstevel@tonic-gate 		}
4617c478bd9Sstevel@tonic-gate 
46248011479Ssn199410 		if (caller != &top) {
46348011479Ssn199410 			if (checkdir(caller, &frame) != 0) {
46448011479Ssn199410 				closeframe(&frame);
46548011479Ssn199410 				goto unlinkit;
4667c478bd9Sstevel@tonic-gate 			}
46748011479Ssn199410 		}
46848011479Ssn199410 		pushdir(&frame);
4697c478bd9Sstevel@tonic-gate 
4707c478bd9Sstevel@tonic-gate 		/*
47148011479Ssn199410 		 * rm() only returns -1 if popdir failed at some point;
47248011479Ssn199410 		 * frame.dp is no longer reliable and we must drop out.
4737c478bd9Sstevel@tonic-gate 		 */
47448011479Ssn199410 		while ((dent = readdir(frame.dp)) != NULL) {
47548011479Ssn199410 			if (strcmp(dent->d_name, ".") == 0 ||
47648011479Ssn199410 			    strcmp(dent->d_name, "..") == 0)
4777c478bd9Sstevel@tonic-gate 				continue;
4787c478bd9Sstevel@tonic-gate 
47948011479Ssn199410 			if (rm(dent->d_name, &frame) != 0)
480996aa816Ssn199410 				break;
4817c478bd9Sstevel@tonic-gate 		}
4827c478bd9Sstevel@tonic-gate 
48348011479Ssn199410 		if (popdir(dent == NULL) != 0)
484996aa816Ssn199410 			return (-1);
4857c478bd9Sstevel@tonic-gate 
4867c478bd9Sstevel@tonic-gate 		/*
48748011479Ssn199410 		 * We recursed and the subdirectory may have set the CANTCLOSE
48848011479Ssn199410 		 * flag; we need to clear it except for &top.
48948011479Ssn199410 		 * Recursion may have invalidated entry because of closedir().
4907c478bd9Sstevel@tonic-gate 		 */
49148011479Ssn199410 		if (caller != &top) {
49248011479Ssn199410 			caller->flags &= ~DIR_CANTCLOSE;
49348011479Ssn199410 			entry = &pathbuf[caller->up->pathend + 1];
4947c478bd9Sstevel@tonic-gate 		}
49548011479Ssn199410 	} else {
49648011479Ssn199410 		flag = 0;
4977c478bd9Sstevel@tonic-gate 	}
49848011479Ssn199410 unlinkit:
4997c478bd9Sstevel@tonic-gate 	/*
5007c478bd9Sstevel@tonic-gate 	 * If interactive, ask for acknowledgement.
5017c478bd9Sstevel@tonic-gate 	 */
5027c478bd9Sstevel@tonic-gate 	if (interactive) {
50348011479Ssn199410 		if (!confirm(stderr, gettext("rm: remove %s (%s/%s)? "),
504*3d63ea05Sas145665 		    pathbuf, yesstr, nostr)) {
50548011479Ssn199410 			return (0);
5067c478bd9Sstevel@tonic-gate 		}
50748011479Ssn199410 	} else if (!silent && flag == 0) {
50848011479Ssn199410 		/*
50948011479Ssn199410 		 * If not silent, and stdin is a terminal, and there's
51048011479Ssn199410 		 * no write access, and the file isn't a symbolic link,
51148011479Ssn199410 		 * ask for permission.  If flag is set, then we know it's
51248011479Ssn199410 		 * a directory so we skip this test as it was done above.
51348011479Ssn199410 		 *
51448011479Ssn199410 		 * TRANSLATION_NOTE - The following message will contain the
51548011479Ssn199410 		 * first character of the strings for "yes" and "no" defined
51648011479Ssn199410 		 * in the file "nl_langinfo.po".  After substitution, the
51748011479Ssn199410 		 * message will appear as follows:
51848011479Ssn199410 		 *	rm: <filename>: override protection XXX (y/n)?
51948011479Ssn199410 		 * where XXX is the permission mode bits of the file in octal
52048011479Ssn199410 		 * and <filename> is the file to be removed
52148011479Ssn199410 		 *
52248011479Ssn199410 		 */
52348011479Ssn199410 		if (ontty && !S_ISLNK(temp.st_mode) &&
52448011479Ssn199410 		    __accessat(caller->fd, entry, W_OK|E_OK) != 0 &&
52548011479Ssn199410 		    !confirm(stdout,
52648011479Ssn199410 		    gettext("rm: %s: override protection %o (%s/%s)? "),
527*3d63ea05Sas145665 		    pathbuf, temp.st_mode & 0777, yesstr, nostr)) {
52848011479Ssn199410 			return (0);
5297c478bd9Sstevel@tonic-gate 		}
5307c478bd9Sstevel@tonic-gate 	}
5317c478bd9Sstevel@tonic-gate 
53248011479Ssn199410 	if (unlinkat(caller->fd, entry, flag) != 0) {
53348011479Ssn199410 		err = errno;
53448011479Ssn199410 		if (err == ENOENT)
53548011479Ssn199410 			return (0);
53648011479Ssn199410 
53748011479Ssn199410 		if (flag != 0) {
53848011479Ssn199410 			if (err == EINVAL) {
53948011479Ssn199410 				(void) fprintf(stderr, gettext(
54048011479Ssn199410 				    "rm: Cannot remove any directory in the "
54148011479Ssn199410 				    "path of the current working directory\n"
54248011479Ssn199410 				    "%s\n"), pathbuf);
54348011479Ssn199410 			} else {
54448011479Ssn199410 				if (err == EEXIST)
54548011479Ssn199410 					err = ENOTEMPTY;
54648011479Ssn199410 				(void) fprintf(stderr,
54748011479Ssn199410 				    gettext("rm: Unable to remove directory %s:"
54848011479Ssn199410 				    " %s\n"), pathbuf, strerror(err));
54948011479Ssn199410 			}
55048011479Ssn199410 		} else {
55148011479Ssn199410 #ifndef XPG4
55248011479Ssn199410 			if (!silent || interactive) {
55348011479Ssn199410 #endif
55448011479Ssn199410 
55548011479Ssn199410 				(void) fprintf(stderr,
55648011479Ssn199410 				    gettext("rm: %s not removed: %s\n"),
55748011479Ssn199410 				    pathbuf, strerror(err));
55848011479Ssn199410 #ifndef XPG4
55948011479Ssn199410 			}
56048011479Ssn199410 #endif
56148011479Ssn199410 		}
56248011479Ssn199410 		errcnt++;
56348011479Ssn199410 	}
56448011479Ssn199410 	return (0);
56548011479Ssn199410 }
5667c478bd9Sstevel@tonic-gate 
5677c478bd9Sstevel@tonic-gate static int
56848011479Ssn199410 confirm(FILE *fp, const char *q, ...)
5697c478bd9Sstevel@tonic-gate {
57048011479Ssn199410 	va_list ap;
5717c478bd9Sstevel@tonic-gate 
57248011479Ssn199410 	va_start(ap, q);
57348011479Ssn199410 	(void) vfprintf(fp, q, ap);
57448011479Ssn199410 	va_end(ap);
57548011479Ssn199410 	return (yes());
5767c478bd9Sstevel@tonic-gate }
5777c478bd9Sstevel@tonic-gate 
57848011479Ssn199410 static void
57948011479Ssn199410 memerror(void)
58048011479Ssn199410 {
58148011479Ssn199410 	(void) fprintf(stderr, gettext("rm: Insufficient memory.\n"));
58248011479Ssn199410 	exit(1);
58348011479Ssn199410 }
5847c478bd9Sstevel@tonic-gate 
5857c478bd9Sstevel@tonic-gate /*
58648011479Ssn199410  * If we can't stat "..", it's either not there or we can't search
58748011479Ssn199410  * the current directory; in that case we can't return back through
58848011479Ssn199410  * "..", so we need to keep the parent open.
58948011479Ssn199410  * Check that we came from "..", if not then this directory entry is an
59048011479Ssn199410  * additional link and there is risk of a filesystem cycle and we also
59148011479Ssn199410  * can't go back up through ".." and we keep the directory open.
5927c478bd9Sstevel@tonic-gate  */
59348011479Ssn199410 static int
59448011479Ssn199410 checkdir(struct dlist *caller, struct dlist *frmp)
59548011479Ssn199410 {
59648011479Ssn199410 	struct stat up;
59748011479Ssn199410 	struct dlist *ptr;
59848011479Ssn199410 
59948011479Ssn199410 	if (fstatat(frmp->fd, "..", &up, 0) != 0) {
60048011479Ssn199410 		caller->flags |= DIR_CANTCLOSE;
60148011479Ssn199410 		return (0);
60248011479Ssn199410 	} else if (up.st_ino == caller->ino && up.st_dev == caller->dev) {
6037c478bd9Sstevel@tonic-gate 		return (0);
6047c478bd9Sstevel@tonic-gate 	}
6057c478bd9Sstevel@tonic-gate 
60648011479Ssn199410 	/* Directory hard link, check cycle */
60748011479Ssn199410 	for (ptr = caller; ptr != NULL; ptr = ptr->up) {
60848011479Ssn199410 		if (frmp->dev == ptr->dev && frmp->ino == ptr->ino) {
6097c478bd9Sstevel@tonic-gate 			(void) fprintf(stderr,
61048011479Ssn199410 			    gettext("rm: cycle detected for %s\n"), pathbuf);
61148011479Ssn199410 			errcnt++;
61248011479Ssn199410 			return (-1);
6137c478bd9Sstevel@tonic-gate 		}
6147c478bd9Sstevel@tonic-gate 	}
61548011479Ssn199410 	caller->flags |= DIR_CANTCLOSE;
616996aa816Ssn199410 	return (0);
6177c478bd9Sstevel@tonic-gate }
618