xref: /titanic_53/usr/src/cmd/rm/rm.c (revision 48011479cce51f5534141868012dcb9828a0fd63)
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 /*
23*48011479Ssn199410  * 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 
36*48011479Ssn199410 #include <sys/param.h>
377c478bd9Sstevel@tonic-gate #include <sys/stat.h>
387c478bd9Sstevel@tonic-gate #include <dirent.h>
39*48011479Ssn199410 #include <errno.h>
40*48011479Ssn199410 #include <fcntl.h>
41*48011479Ssn199410 #include <langinfo.h>
427c478bd9Sstevel@tonic-gate #include <limits.h>
437c478bd9Sstevel@tonic-gate #include <locale.h>
44*48011479Ssn199410 #include <stdarg.h>
45*48011479Ssn199410 #include <stdio.h>
467c478bd9Sstevel@tonic-gate #include <stdlib.h>
47*48011479Ssn199410 #include <string.h>
48*48011479Ssn199410 #include <unistd.h>
49*48011479Ssn199410 #include <values.h>
507c478bd9Sstevel@tonic-gate 
51*48011479Ssn199410 #define	E_OK	010		/* make __accessat() use effective ids */
527c478bd9Sstevel@tonic-gate 
53*48011479Ssn199410 #define	DIR_CANTCLOSE		1
547c478bd9Sstevel@tonic-gate 
55*48011479Ssn199410 static struct stat rootdir;
56*48011479Ssn199410 
57*48011479Ssn199410 struct dlist {
58*48011479Ssn199410 	int fd;			/* Stores directory fd */
59*48011479Ssn199410 	int flags;		/* DIR_* Flags */
60*48011479Ssn199410 	DIR *dp;		/* Open directory (opened with fd) */
61*48011479Ssn199410 	long diroff;		/* Saved directory offset when closing */
62*48011479Ssn199410 	struct dlist *up;	/* Up one step in the tree (toward "/") */
63*48011479Ssn199410 	struct dlist *down;	/* Down one step in the tree */
64*48011479Ssn199410 	ino_t ino;		/* st_ino of directory */
65*48011479Ssn199410 	dev_t dev;		/* st_dev of directory */
66*48011479Ssn199410 	int pathend;		/* Offset of name end in the pathbuffer */
67*48011479Ssn199410 };
68*48011479Ssn199410 
69*48011479Ssn199410 static struct dlist top = {
70*48011479Ssn199410 	(int)AT_FDCWD,
71*48011479Ssn199410 	DIR_CANTCLOSE,
72*48011479Ssn199410 };
737c478bd9Sstevel@tonic-gate 
747c478bd9Sstevel@tonic-gate static char yeschr[SCHAR_MAX + 2];
757c478bd9Sstevel@tonic-gate static char nochr[SCHAR_MAX + 2];
767c478bd9Sstevel@tonic-gate 
77*48011479Ssn199410 static struct dlist *cur, *rec;
787c478bd9Sstevel@tonic-gate 
79*48011479Ssn199410 static int rm(const char *, struct dlist *);
80*48011479Ssn199410 static int confirm(FILE *, const char *, ...);
81*48011479Ssn199410 static void memerror(void);
82*48011479Ssn199410 static int checkdir(struct dlist *, struct dlist *);
83*48011479Ssn199410 static int errcnt;
84*48011479Ssn199410 static boolean_t silent, interactive, recursive, ontty;
857c478bd9Sstevel@tonic-gate 
86*48011479Ssn199410 static char *pathbuf;
87*48011479Ssn199410 static size_t pathbuflen;
887c478bd9Sstevel@tonic-gate 
89*48011479Ssn199410 static int maxfds = MAXINT;
90*48011479Ssn199410 static int nfds;
917c478bd9Sstevel@tonic-gate 
92*48011479Ssn199410 extern int __accessat(int, const char *, int);
937c478bd9Sstevel@tonic-gate 
947c478bd9Sstevel@tonic-gate int
95*48011479Ssn199410 main(int argc, char **argv)
967c478bd9Sstevel@tonic-gate {
977c478bd9Sstevel@tonic-gate 	int errflg = 0;
987c478bd9Sstevel@tonic-gate 	int c;
997c478bd9Sstevel@tonic-gate 
1007c478bd9Sstevel@tonic-gate 	(void) setlocale(LC_ALL, "");
1017c478bd9Sstevel@tonic-gate #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
1027c478bd9Sstevel@tonic-gate #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
1037c478bd9Sstevel@tonic-gate #endif
1047c478bd9Sstevel@tonic-gate 	(void) textdomain(TEXT_DOMAIN);
1057c478bd9Sstevel@tonic-gate 
1067c478bd9Sstevel@tonic-gate 	(void) strncpy(yeschr, nl_langinfo(YESSTR), SCHAR_MAX + 1);
1077c478bd9Sstevel@tonic-gate 	(void) strncpy(nochr, nl_langinfo(NOSTR), SCHAR_MAX + 1);
1087c478bd9Sstevel@tonic-gate 
1097c478bd9Sstevel@tonic-gate 	while ((c = getopt(argc, argv, "frRi")) != EOF)
1107c478bd9Sstevel@tonic-gate 		switch (c) {
1117c478bd9Sstevel@tonic-gate 		case 'f':
112*48011479Ssn199410 			silent = B_TRUE;
1137c478bd9Sstevel@tonic-gate #ifdef XPG4
114*48011479Ssn199410 			interactive = B_FALSE;
1157c478bd9Sstevel@tonic-gate #endif
1167c478bd9Sstevel@tonic-gate 			break;
1177c478bd9Sstevel@tonic-gate 		case 'i':
118*48011479Ssn199410 			interactive = B_TRUE;
1197c478bd9Sstevel@tonic-gate #ifdef XPG4
120*48011479Ssn199410 			silent = B_FALSE;
1217c478bd9Sstevel@tonic-gate #endif
1227c478bd9Sstevel@tonic-gate 			break;
1237c478bd9Sstevel@tonic-gate 		case 'r':
1247c478bd9Sstevel@tonic-gate 		case 'R':
125*48011479Ssn199410 			recursive = B_TRUE;
1267c478bd9Sstevel@tonic-gate 			break;
1277c478bd9Sstevel@tonic-gate 		case '?':
1287c478bd9Sstevel@tonic-gate 			errflg = 1;
1297c478bd9Sstevel@tonic-gate 			break;
1307c478bd9Sstevel@tonic-gate 		}
1317c478bd9Sstevel@tonic-gate 
1327c478bd9Sstevel@tonic-gate 	/*
1337c478bd9Sstevel@tonic-gate 	 * For BSD compatibility allow '-' to delimit the end
1347c478bd9Sstevel@tonic-gate 	 * of options.  However, if options were already explicitly
1357c478bd9Sstevel@tonic-gate 	 * terminated with '--', then treat '-' literally: otherwise,
1367c478bd9Sstevel@tonic-gate 	 * "rm -- -" won't remove '-'.
1377c478bd9Sstevel@tonic-gate 	 */
1387c478bd9Sstevel@tonic-gate 	if (optind < argc &&
1397c478bd9Sstevel@tonic-gate 	    strcmp(argv[optind], "-") == 0 &&
1407c478bd9Sstevel@tonic-gate 	    strcmp(argv[optind - 1], "--") != 0)
1417c478bd9Sstevel@tonic-gate 		optind++;
1427c478bd9Sstevel@tonic-gate 
1437c478bd9Sstevel@tonic-gate 	argc -= optind;
1447c478bd9Sstevel@tonic-gate 	argv = &argv[optind];
1457c478bd9Sstevel@tonic-gate 
1467c478bd9Sstevel@tonic-gate 	if ((argc < 1 && !silent) || errflg) {
147*48011479Ssn199410 		(void) fprintf(stderr, gettext("usage: rm [-fiRr] file ...\n"));
148*48011479Ssn199410 		exit(2);
149*48011479Ssn199410 	}
150*48011479Ssn199410 
151*48011479Ssn199410 	ontty = isatty(STDIN_FILENO) != 0;
152*48011479Ssn199410 
153*48011479Ssn199410 	if (recursive && stat("/", &rootdir) != 0) {
1547c478bd9Sstevel@tonic-gate 		(void) fprintf(stderr,
155*48011479Ssn199410 		    gettext("rm: cannot stat root directory: %s\n"),
156*48011479Ssn199410 		    strerror(errno));
1577c478bd9Sstevel@tonic-gate 		exit(2);
1587c478bd9Sstevel@tonic-gate 	}
1597c478bd9Sstevel@tonic-gate 
160*48011479Ssn199410 	for (; *argv != NULL; argv++) {
161*48011479Ssn199410 		char *p = strrchr(*argv, '/');
162*48011479Ssn199410 		if (p == NULL)
163*48011479Ssn199410 			p = *argv;
164*48011479Ssn199410 		else
165*48011479Ssn199410 			p = p + 1;
166*48011479Ssn199410 		if (strcmp(p, ".") == 0 || strcmp(p, "..") == 0) {
167*48011479Ssn199410 			(void) fprintf(stderr,
168*48011479Ssn199410 			    gettext("rm of %s is not allowed\n"), *argv);
169*48011479Ssn199410 			errcnt++;
170*48011479Ssn199410 			continue;
171*48011479Ssn199410 		}
172*48011479Ssn199410 		/* Retry when we can't walk back up. */
173*48011479Ssn199410 		while (rm(*argv, rec = cur = &top) != 0)
174996aa816Ssn199410 			;
1757c478bd9Sstevel@tonic-gate 	}
17612a9e0efSsn199410 
177*48011479Ssn199410 	return (errcnt != 0 ? 2 : 0);
178*48011479Ssn199410 }
179*48011479Ssn199410 
180*48011479Ssn199410 static void
181*48011479Ssn199410 pushfilename(const char *fname)
182*48011479Ssn199410 {
183*48011479Ssn199410 	char *p;
184*48011479Ssn199410 	const char *q = fname;
185*48011479Ssn199410 
186*48011479Ssn199410 	if (cur == &top) {
187*48011479Ssn199410 		p = pathbuf;
188*48011479Ssn199410 	} else {
189*48011479Ssn199410 		p = pathbuf + cur->up->pathend;
190*48011479Ssn199410 		*p++ = '/';
191*48011479Ssn199410 	}
192*48011479Ssn199410 	while (*q != '\0') {
193*48011479Ssn199410 		if (p - pathbuf + 2 >= pathbuflen) {
194*48011479Ssn199410 			char *np;
195*48011479Ssn199410 			pathbuflen += MAXPATHLEN;
196*48011479Ssn199410 			np = realloc(pathbuf, pathbuflen);
197*48011479Ssn199410 			if (np == NULL)
198*48011479Ssn199410 				memerror();
199*48011479Ssn199410 			p = np + (p - pathbuf);
200*48011479Ssn199410 			pathbuf = np;
201*48011479Ssn199410 		}
202*48011479Ssn199410 		*p++ = *q++;
203*48011479Ssn199410 	}
204*48011479Ssn199410 	*p = '\0';
205*48011479Ssn199410 	cur->pathend = p - pathbuf;
206*48011479Ssn199410 }
207*48011479Ssn199410 
208*48011479Ssn199410 static void
209*48011479Ssn199410 closeframe(struct dlist *frm)
210*48011479Ssn199410 {
211*48011479Ssn199410 	if (frm->dp != NULL) {
212*48011479Ssn199410 		(void) closedir(frm->dp);
213*48011479Ssn199410 		nfds--;
214*48011479Ssn199410 		frm->dp = NULL;
215*48011479Ssn199410 		frm->fd = -1;
216*48011479Ssn199410 	}
2177c478bd9Sstevel@tonic-gate }
2187c478bd9Sstevel@tonic-gate 
219996aa816Ssn199410 static int
220*48011479Ssn199410 reclaim(void)
2217c478bd9Sstevel@tonic-gate {
222*48011479Ssn199410 	while (rec != NULL && (rec->flags & DIR_CANTCLOSE) != 0)
223*48011479Ssn199410 		rec = rec->down;
224*48011479Ssn199410 	if (rec == NULL || rec == cur || rec->dp == NULL)
225*48011479Ssn199410 		return (-1);
226*48011479Ssn199410 	rec->diroff = telldir(rec->dp);
227*48011479Ssn199410 	closeframe(rec);
228*48011479Ssn199410 	rec = rec->down;
229*48011479Ssn199410 	return (0);
230*48011479Ssn199410 }
231*48011479Ssn199410 
232*48011479Ssn199410 static void
233*48011479Ssn199410 pushdir(struct dlist *frm)
234*48011479Ssn199410 {
235*48011479Ssn199410 	frm->up = cur;
236*48011479Ssn199410 	frm->down = NULL;
237*48011479Ssn199410 	cur->down = frm;
238*48011479Ssn199410 	cur = frm;
239*48011479Ssn199410 }
240*48011479Ssn199410 
241*48011479Ssn199410 static int
242*48011479Ssn199410 opendirat(int dirfd, const char *entry, struct dlist *frm)
243*48011479Ssn199410 {
244*48011479Ssn199410 	int fd;
245*48011479Ssn199410 
246*48011479Ssn199410 	if (nfds >= maxfds)
247*48011479Ssn199410 		(void) reclaim();
248*48011479Ssn199410 
249*48011479Ssn199410 	while ((fd = openat(dirfd, entry, O_RDONLY|O_NONBLOCK)) == -1 &&
250*48011479Ssn199410 	    errno == EMFILE) {
251*48011479Ssn199410 		if (nfds < maxfds)
252*48011479Ssn199410 			maxfds = nfds;
253*48011479Ssn199410 		if (reclaim() != 0)
254*48011479Ssn199410 			return (-1);
255*48011479Ssn199410 	}
256*48011479Ssn199410 	if (fd < 0)
257*48011479Ssn199410 		return (-1);
258*48011479Ssn199410 	frm->fd = fd;
259*48011479Ssn199410 	frm->dp = fdopendir(fd);
260*48011479Ssn199410 	if (frm->dp == NULL) {
261*48011479Ssn199410 		(void) close(fd);
262*48011479Ssn199410 		return (-1);
263*48011479Ssn199410 	}
264*48011479Ssn199410 	nfds++;
265*48011479Ssn199410 	return (0);
266*48011479Ssn199410 }
2677c478bd9Sstevel@tonic-gate 
2687c478bd9Sstevel@tonic-gate /*
269*48011479Ssn199410  * Since we never pop the top frame, cur->up can never be NULL.
270*48011479Ssn199410  * If we pop beyond a frame we closed, we try to reopen "..".
2717c478bd9Sstevel@tonic-gate  */
272*48011479Ssn199410 static int
273*48011479Ssn199410 popdir(boolean_t noerror)
274*48011479Ssn199410 {
275*48011479Ssn199410 	struct stat buf;
276*48011479Ssn199410 	int ret = noerror ? 0 : -1;
277*48011479Ssn199410 	pathbuf[cur->up->pathend] = '\0';
278*48011479Ssn199410 
279*48011479Ssn199410 	if (noerror && cur->up->fd == -1) {
280*48011479Ssn199410 		rec = cur->up;
281*48011479Ssn199410 		if (opendirat(cur->fd, "..", rec) != 0 ||
282*48011479Ssn199410 		    fstat(rec->fd, &buf) != 0) {
283*48011479Ssn199410 			(void) fprintf(stderr,
284*48011479Ssn199410 			    gettext("rm: cannot reopen %s: %s\n"),
285*48011479Ssn199410 			    pathbuf, strerror(errno));
286*48011479Ssn199410 			exit(2);
287*48011479Ssn199410 		}
288*48011479Ssn199410 		if (rec->ino != buf.st_ino || rec->dev != buf.st_dev) {
289*48011479Ssn199410 			(void) fprintf(stderr, gettext("rm: WARNING: "
290*48011479Ssn199410 			    "The directory %s was moved or linked to "
291*48011479Ssn199410 			    "another directory during the execution of rm\n"),
292*48011479Ssn199410 			    pathbuf);
293*48011479Ssn199410 			closeframe(rec);
294*48011479Ssn199410 			ret = -1;
295*48011479Ssn199410 		} else {
296*48011479Ssn199410 			/* If telldir failed, we take it from the top. */
297*48011479Ssn199410 			if (rec->diroff != -1)
298*48011479Ssn199410 				seekdir(rec->dp, rec->diroff);
299*48011479Ssn199410 		}
300*48011479Ssn199410 	} else if (rec == cur)
301*48011479Ssn199410 		rec = cur->up;
302*48011479Ssn199410 	closeframe(cur);
303*48011479Ssn199410 	cur = cur->up;
304*48011479Ssn199410 	cur->down = NULL;
305*48011479Ssn199410 	return (ret);
306*48011479Ssn199410 }
307*48011479Ssn199410 
308*48011479Ssn199410 /*
309*48011479Ssn199410  * The stack frame of this function is minimized so that we can
310*48011479Ssn199410  * recurse quite a bit before we overflow the stack; around
311*48011479Ssn199410  * 30,000-40,000 nested directories can be removed with the default
312*48011479Ssn199410  * stack limit.
313*48011479Ssn199410  */
314*48011479Ssn199410 static int
315*48011479Ssn199410 rm(const char *entry, struct dlist *caller)
316*48011479Ssn199410 {
317*48011479Ssn199410 	struct dlist frame;
318*48011479Ssn199410 	int flag;
319*48011479Ssn199410 	struct stat temp;
320*48011479Ssn199410 	struct dirent *dent;
321*48011479Ssn199410 	int err;
322*48011479Ssn199410 
323*48011479Ssn199410 	/*
324*48011479Ssn199410 	 * Construct the pathname: note that the entry may live in memory
325*48011479Ssn199410 	 * allocated by readdir and that after return from recursion
326*48011479Ssn199410 	 * the memory is no longer valid.  So after the recursive rm()
327*48011479Ssn199410 	 * call, we use the global pathbuf instead of the entry argument.
328*48011479Ssn199410 	 */
329*48011479Ssn199410 	pushfilename(entry);
330*48011479Ssn199410 
331*48011479Ssn199410 	if (fstatat(caller->fd, entry, &temp, AT_SYMLINK_NOFOLLOW) != 0) {
3327c478bd9Sstevel@tonic-gate 		if (!silent) {
333*48011479Ssn199410 			(void) fprintf(stderr, "rm: %s: %s\n", pathbuf,
334*48011479Ssn199410 			    strerror(errno));
335*48011479Ssn199410 			errcnt++;
3367c478bd9Sstevel@tonic-gate 		}
337996aa816Ssn199410 		return (0);
3387c478bd9Sstevel@tonic-gate 	}
3397c478bd9Sstevel@tonic-gate 
340*48011479Ssn199410 	if (S_ISDIR(temp.st_mode)) {
3417c478bd9Sstevel@tonic-gate 		/*
3427c478bd9Sstevel@tonic-gate 		 * If "-r" wasn't specified, trying to remove directories
3437c478bd9Sstevel@tonic-gate 		 * is an error.
3447c478bd9Sstevel@tonic-gate 		 */
3457c478bd9Sstevel@tonic-gate 		if (!recursive) {
3467c478bd9Sstevel@tonic-gate 			(void) fprintf(stderr,
347*48011479Ssn199410 			    gettext("rm: %s is a directory\n"), pathbuf);
348*48011479Ssn199410 			errcnt++;
349996aa816Ssn199410 			return (0);
3507c478bd9Sstevel@tonic-gate 		}
3517c478bd9Sstevel@tonic-gate 
352*48011479Ssn199410 		if (temp.st_ino == rootdir.st_ino &&
353*48011479Ssn199410 		    temp.st_dev == rootdir.st_dev) {
3547c478bd9Sstevel@tonic-gate 			(void) fprintf(stderr,
355*48011479Ssn199410 			    gettext("rm of %s is not allowed\n"), "/");
356*48011479Ssn199410 			errcnt++;
357996aa816Ssn199410 			return (0);
3587c478bd9Sstevel@tonic-gate 		}
3597c478bd9Sstevel@tonic-gate 		/*
3607c478bd9Sstevel@tonic-gate 		 * TRANSLATION_NOTE - The following message will contain the
3617c478bd9Sstevel@tonic-gate 		 * first character of the strings for "yes" and "no" defined
3627c478bd9Sstevel@tonic-gate 		 * in the file "nl_langinfo.po".  After substitution, the
3637c478bd9Sstevel@tonic-gate 		 * message will appear as follows:
3647c478bd9Sstevel@tonic-gate 		 *	rm: examine files in directory <directoryname> (y/n)?
3657c478bd9Sstevel@tonic-gate 		 * where <directoryname> is the directory to be removed
3667c478bd9Sstevel@tonic-gate 		 *
3677c478bd9Sstevel@tonic-gate 		 */
368*48011479Ssn199410 		if (interactive && !confirm(stderr,
3697c478bd9Sstevel@tonic-gate 		    gettext("rm: examine files in directory %s (%s/%s)? "),
370*48011479Ssn199410 		    pathbuf, yeschr, nochr)) {
371*48011479Ssn199410 			return (0);
3727c478bd9Sstevel@tonic-gate 		}
3737c478bd9Sstevel@tonic-gate 
374*48011479Ssn199410 		frame.dev = temp.st_dev;
375*48011479Ssn199410 		frame.ino = temp.st_ino;
376*48011479Ssn199410 		frame.flags = 0;
377*48011479Ssn199410 		flag = AT_REMOVEDIR;
378*48011479Ssn199410 
3797c478bd9Sstevel@tonic-gate #ifdef XPG4
3807c478bd9Sstevel@tonic-gate 		/*
381*48011479Ssn199410 		 * XCU4 and POSIX.2: If not interactive, check to see whether
3827c478bd9Sstevel@tonic-gate 		 * or not directory is readable or writable and if not,
3837c478bd9Sstevel@tonic-gate 		 * prompt user for response.
3847c478bd9Sstevel@tonic-gate 		 */
385*48011479Ssn199410 		if (ontty && !interactive && !silent &&
386*48011479Ssn199410 		    __accessat(caller->fd, entry, W_OK|X_OK|E_OK) != 0 &&
387*48011479Ssn199410 		    !confirm(stderr,
388*48011479Ssn199410 		    gettext("rm: examine files in directory %s (%s/%s)? "),
389*48011479Ssn199410 		    pathbuf, yeschr, nochr)) {
390*48011479Ssn199410 			return (0);
3917c478bd9Sstevel@tonic-gate 		}
3927c478bd9Sstevel@tonic-gate #endif
393*48011479Ssn199410 		if (opendirat(caller->fd, entry, &frame) == -1) {
394*48011479Ssn199410 			err = errno;
3957c478bd9Sstevel@tonic-gate 
3967c478bd9Sstevel@tonic-gate 			if (interactive) {
3977c478bd9Sstevel@tonic-gate 				/*
3987c478bd9Sstevel@tonic-gate 				 * Print an error message that
3997c478bd9Sstevel@tonic-gate 				 * we could not read the directory
4007c478bd9Sstevel@tonic-gate 				 * as the user wanted to examine
4017c478bd9Sstevel@tonic-gate 				 * files in the directory.  Only
4027c478bd9Sstevel@tonic-gate 				 * affect the error status if
4037c478bd9Sstevel@tonic-gate 				 * user doesn't want to remove the
4047c478bd9Sstevel@tonic-gate 				 * directory as we still may be able
4057c478bd9Sstevel@tonic-gate 				 * remove the directory successfully.
4067c478bd9Sstevel@tonic-gate 				 */
4077c478bd9Sstevel@tonic-gate 				(void) fprintf(stderr, gettext(
408*48011479Ssn199410 				    "rm: cannot read directory %s: %s\n"),
409*48011479Ssn199410 				    pathbuf, strerror(err));
410*48011479Ssn199410 
411*48011479Ssn199410 /*
412*48011479Ssn199410  * TRANSLATION_NOTE - The following message will contain the
413*48011479Ssn199410  * first character of the strings for "yes" and "no" defined
414*48011479Ssn199410  * in the file "nl_langinfo.po".  After substitution, the
415*48011479Ssn199410  * message will appear as follows:
416*48011479Ssn199410  *	rm: remove <filename> (y/n)?
417*48011479Ssn199410  * For example, in German, this will appear as
418*48011479Ssn199410  * 	rm: l�schen <filename> (j/n)?
419*48011479Ssn199410  * where j=ja, n=nein, <filename>=the file to be removed
420*48011479Ssn199410  */
421*48011479Ssn199410 				if (!confirm(stderr,
422*48011479Ssn199410 				    gettext("rm: remove %s (%s/%s)? "),
423*48011479Ssn199410 				    pathbuf, yeschr, nochr)) {
424*48011479Ssn199410 					errcnt++;
425*48011479Ssn199410 					return (0);
4267c478bd9Sstevel@tonic-gate 				}
4277c478bd9Sstevel@tonic-gate 			}
428*48011479Ssn199410 			/* If it's empty we may still be able to rm it */
429*48011479Ssn199410 			if (unlinkat(caller->fd, entry, flag) == 0)
430*48011479Ssn199410 				return (0);
431*48011479Ssn199410 			if (interactive)
432*48011479Ssn199410 				err = errno;
433*48011479Ssn199410 			(void) fprintf(stderr,
434*48011479Ssn199410 			    interactive ?
435*48011479Ssn199410 			    gettext("rm: Unable to remove directory %s: %s\n") :
436*48011479Ssn199410 			    gettext("rm: cannot read directory %s: %s\n"),
437*48011479Ssn199410 			    pathbuf, strerror(err));
438*48011479Ssn199410 			errcnt++;
439*48011479Ssn199410 			return (0);
440*48011479Ssn199410 		}
4417c478bd9Sstevel@tonic-gate 
4427c478bd9Sstevel@tonic-gate 		/*
443*48011479Ssn199410 		 * There is a race condition here too; if we open a directory
444*48011479Ssn199410 		 * we have to make sure it's still the same directory we
445*48011479Ssn199410 		 * stat'ed and checked against root earlier.  Let's check.
4467c478bd9Sstevel@tonic-gate 		 */
447*48011479Ssn199410 		if (fstat(frame.fd, &temp) != 0 ||
448*48011479Ssn199410 		    frame.ino != temp.st_ino ||
449*48011479Ssn199410 		    frame.dev != temp.st_dev) {
450*48011479Ssn199410 			(void) fprintf(stderr,
451*48011479Ssn199410 			    gettext("rm: %s: directory renamed\n"), pathbuf);
452*48011479Ssn199410 			closeframe(&frame);
453*48011479Ssn199410 			errcnt++;
454*48011479Ssn199410 			return (0);
4557c478bd9Sstevel@tonic-gate 		}
4567c478bd9Sstevel@tonic-gate 
457*48011479Ssn199410 		if (caller != &top) {
458*48011479Ssn199410 			if (checkdir(caller, &frame) != 0) {
459*48011479Ssn199410 				closeframe(&frame);
460*48011479Ssn199410 				goto unlinkit;
4617c478bd9Sstevel@tonic-gate 			}
462*48011479Ssn199410 		}
463*48011479Ssn199410 		pushdir(&frame);
4647c478bd9Sstevel@tonic-gate 
4657c478bd9Sstevel@tonic-gate 		/*
466*48011479Ssn199410 		 * rm() only returns -1 if popdir failed at some point;
467*48011479Ssn199410 		 * frame.dp is no longer reliable and we must drop out.
4687c478bd9Sstevel@tonic-gate 		 */
469*48011479Ssn199410 		while ((dent = readdir(frame.dp)) != NULL) {
470*48011479Ssn199410 			if (strcmp(dent->d_name, ".") == 0 ||
471*48011479Ssn199410 			    strcmp(dent->d_name, "..") == 0)
4727c478bd9Sstevel@tonic-gate 				continue;
4737c478bd9Sstevel@tonic-gate 
474*48011479Ssn199410 			if (rm(dent->d_name, &frame) != 0)
475996aa816Ssn199410 				break;
4767c478bd9Sstevel@tonic-gate 		}
4777c478bd9Sstevel@tonic-gate 
478*48011479Ssn199410 		if (popdir(dent == NULL) != 0)
479996aa816Ssn199410 			return (-1);
4807c478bd9Sstevel@tonic-gate 
4817c478bd9Sstevel@tonic-gate 		/*
482*48011479Ssn199410 		 * We recursed and the subdirectory may have set the CANTCLOSE
483*48011479Ssn199410 		 * flag; we need to clear it except for &top.
484*48011479Ssn199410 		 * Recursion may have invalidated entry because of closedir().
4857c478bd9Sstevel@tonic-gate 		 */
486*48011479Ssn199410 		if (caller != &top) {
487*48011479Ssn199410 			caller->flags &= ~DIR_CANTCLOSE;
488*48011479Ssn199410 			entry = &pathbuf[caller->up->pathend + 1];
4897c478bd9Sstevel@tonic-gate 		}
490*48011479Ssn199410 	} else {
491*48011479Ssn199410 		flag = 0;
4927c478bd9Sstevel@tonic-gate 	}
493*48011479Ssn199410 unlinkit:
4947c478bd9Sstevel@tonic-gate 	/*
4957c478bd9Sstevel@tonic-gate 	 * If interactive, ask for acknowledgement.
4967c478bd9Sstevel@tonic-gate 	 */
4977c478bd9Sstevel@tonic-gate 	if (interactive) {
498*48011479Ssn199410 		if (!confirm(stderr, gettext("rm: remove %s (%s/%s)? "),
499*48011479Ssn199410 		    pathbuf, yeschr, nochr)) {
500*48011479Ssn199410 			return (0);
5017c478bd9Sstevel@tonic-gate 		}
502*48011479Ssn199410 	} else if (!silent && flag == 0) {
503*48011479Ssn199410 		/*
504*48011479Ssn199410 		 * If not silent, and stdin is a terminal, and there's
505*48011479Ssn199410 		 * no write access, and the file isn't a symbolic link,
506*48011479Ssn199410 		 * ask for permission.  If flag is set, then we know it's
507*48011479Ssn199410 		 * a directory so we skip this test as it was done above.
508*48011479Ssn199410 		 *
509*48011479Ssn199410 		 * TRANSLATION_NOTE - The following message will contain the
510*48011479Ssn199410 		 * first character of the strings for "yes" and "no" defined
511*48011479Ssn199410 		 * in the file "nl_langinfo.po".  After substitution, the
512*48011479Ssn199410 		 * message will appear as follows:
513*48011479Ssn199410 		 *	rm: <filename>: override protection XXX (y/n)?
514*48011479Ssn199410 		 * where XXX is the permission mode bits of the file in octal
515*48011479Ssn199410 		 * and <filename> is the file to be removed
516*48011479Ssn199410 		 *
517*48011479Ssn199410 		 */
518*48011479Ssn199410 		if (ontty && !S_ISLNK(temp.st_mode) &&
519*48011479Ssn199410 		    __accessat(caller->fd, entry, W_OK|E_OK) != 0 &&
520*48011479Ssn199410 		    !confirm(stdout,
521*48011479Ssn199410 		    gettext("rm: %s: override protection %o (%s/%s)? "),
522*48011479Ssn199410 		    pathbuf, temp.st_mode & 0777, yeschr, nochr)) {
523*48011479Ssn199410 			return (0);
5247c478bd9Sstevel@tonic-gate 		}
5257c478bd9Sstevel@tonic-gate 	}
5267c478bd9Sstevel@tonic-gate 
527*48011479Ssn199410 	if (unlinkat(caller->fd, entry, flag) != 0) {
528*48011479Ssn199410 		err = errno;
529*48011479Ssn199410 		if (err == ENOENT)
530*48011479Ssn199410 			return (0);
531*48011479Ssn199410 
532*48011479Ssn199410 		if (flag != 0) {
533*48011479Ssn199410 			if (err == EINVAL) {
534*48011479Ssn199410 				(void) fprintf(stderr, gettext(
535*48011479Ssn199410 				    "rm: Cannot remove any directory in the "
536*48011479Ssn199410 				    "path of the current working directory\n"
537*48011479Ssn199410 				    "%s\n"), pathbuf);
538*48011479Ssn199410 			} else {
539*48011479Ssn199410 				if (err == EEXIST)
540*48011479Ssn199410 					err = ENOTEMPTY;
541*48011479Ssn199410 				(void) fprintf(stderr,
542*48011479Ssn199410 				    gettext("rm: Unable to remove directory %s:"
543*48011479Ssn199410 				    " %s\n"), pathbuf, strerror(err));
544*48011479Ssn199410 			}
545*48011479Ssn199410 		} else {
546*48011479Ssn199410 #ifndef XPG4
547*48011479Ssn199410 			if (!silent || interactive) {
548*48011479Ssn199410 #endif
549*48011479Ssn199410 
550*48011479Ssn199410 				(void) fprintf(stderr,
551*48011479Ssn199410 				    gettext("rm: %s not removed: %s\n"),
552*48011479Ssn199410 				    pathbuf, strerror(err));
553*48011479Ssn199410 #ifndef XPG4
554*48011479Ssn199410 			}
555*48011479Ssn199410 #endif
556*48011479Ssn199410 		}
557*48011479Ssn199410 		errcnt++;
558*48011479Ssn199410 	}
559*48011479Ssn199410 	return (0);
560*48011479Ssn199410 }
5617c478bd9Sstevel@tonic-gate 
5627c478bd9Sstevel@tonic-gate static int
5637c478bd9Sstevel@tonic-gate yes(void)
5647c478bd9Sstevel@tonic-gate {
5657c478bd9Sstevel@tonic-gate 	int i, b;
5667c478bd9Sstevel@tonic-gate 	char ans[SCHAR_MAX + 1];
5677c478bd9Sstevel@tonic-gate 
5687c478bd9Sstevel@tonic-gate 	for (i = 0; ; i++) {
5697c478bd9Sstevel@tonic-gate 		b = getchar();
5707c478bd9Sstevel@tonic-gate 		if (b == '\n' || b == '\0' || b == EOF) {
5717c478bd9Sstevel@tonic-gate 			ans[i] = 0;
5727c478bd9Sstevel@tonic-gate 			break;
5737c478bd9Sstevel@tonic-gate 		}
5747c478bd9Sstevel@tonic-gate 		if (i < SCHAR_MAX)
575*48011479Ssn199410 			ans[i] = (char)b;
5767c478bd9Sstevel@tonic-gate 	}
5777c478bd9Sstevel@tonic-gate 	if (i >= SCHAR_MAX) {
5787c478bd9Sstevel@tonic-gate 		i = SCHAR_MAX;
5797c478bd9Sstevel@tonic-gate 		ans[SCHAR_MAX] = 0;
5807c478bd9Sstevel@tonic-gate 	}
5817c478bd9Sstevel@tonic-gate 	if ((i == 0) | (strncmp(yeschr, ans, i)))
5827c478bd9Sstevel@tonic-gate 		return (0);
5837c478bd9Sstevel@tonic-gate 	return (1);
5847c478bd9Sstevel@tonic-gate }
5857c478bd9Sstevel@tonic-gate 
5867c478bd9Sstevel@tonic-gate static int
587*48011479Ssn199410 confirm(FILE *fp, const char *q, ...)
5887c478bd9Sstevel@tonic-gate {
589*48011479Ssn199410 	va_list ap;
5907c478bd9Sstevel@tonic-gate 
591*48011479Ssn199410 	va_start(ap, q);
592*48011479Ssn199410 	(void) vfprintf(fp, q, ap);
593*48011479Ssn199410 	va_end(ap);
594*48011479Ssn199410 	return (yes());
5957c478bd9Sstevel@tonic-gate }
5967c478bd9Sstevel@tonic-gate 
597*48011479Ssn199410 static void
598*48011479Ssn199410 memerror(void)
599*48011479Ssn199410 {
600*48011479Ssn199410 	(void) fprintf(stderr, gettext("rm: Insufficient memory.\n"));
601*48011479Ssn199410 	exit(1);
602*48011479Ssn199410 }
6037c478bd9Sstevel@tonic-gate 
6047c478bd9Sstevel@tonic-gate /*
605*48011479Ssn199410  * If we can't stat "..", it's either not there or we can't search
606*48011479Ssn199410  * the current directory; in that case we can't return back through
607*48011479Ssn199410  * "..", so we need to keep the parent open.
608*48011479Ssn199410  * Check that we came from "..", if not then this directory entry is an
609*48011479Ssn199410  * additional link and there is risk of a filesystem cycle and we also
610*48011479Ssn199410  * can't go back up through ".." and we keep the directory open.
6117c478bd9Sstevel@tonic-gate  */
612*48011479Ssn199410 static int
613*48011479Ssn199410 checkdir(struct dlist *caller, struct dlist *frmp)
614*48011479Ssn199410 {
615*48011479Ssn199410 	struct stat up;
616*48011479Ssn199410 	struct dlist *ptr;
617*48011479Ssn199410 
618*48011479Ssn199410 	if (fstatat(frmp->fd, "..", &up, 0) != 0) {
619*48011479Ssn199410 		caller->flags |= DIR_CANTCLOSE;
620*48011479Ssn199410 		return (0);
621*48011479Ssn199410 	} else if (up.st_ino == caller->ino && up.st_dev == caller->dev) {
6227c478bd9Sstevel@tonic-gate 		return (0);
6237c478bd9Sstevel@tonic-gate 	}
6247c478bd9Sstevel@tonic-gate 
625*48011479Ssn199410 	/* Directory hard link, check cycle */
626*48011479Ssn199410 	for (ptr = caller; ptr != NULL; ptr = ptr->up) {
627*48011479Ssn199410 		if (frmp->dev == ptr->dev && frmp->ino == ptr->ino) {
6287c478bd9Sstevel@tonic-gate 			(void) fprintf(stderr,
629*48011479Ssn199410 			    gettext("rm: cycle detected for %s\n"), pathbuf);
630*48011479Ssn199410 			errcnt++;
631*48011479Ssn199410 			return (-1);
6327c478bd9Sstevel@tonic-gate 		}
6337c478bd9Sstevel@tonic-gate 	}
634*48011479Ssn199410 	caller->flags |= DIR_CANTCLOSE;
635996aa816Ssn199410 	return (0);
6367c478bd9Sstevel@tonic-gate }
637