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 2009 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/*
31 * rm [-fiRr] file ...
32 */
33
34#include <sys/param.h>
35#include <sys/stat.h>
36#include <dirent.h>
37#include <errno.h>
38#include <fcntl.h>
39#include <langinfo.h>
40#include <limits.h>
41#include <locale.h>
42#include <stdarg.h>
43#include <stdio.h>
44#include <stdlib.h>
45#include <string.h>
46#include <unistd.h>
47#include <values.h>
48#include "getresponse.h"
49
50#define	DIR_CANTCLOSE		1
51
52static struct stat rootdir;
53
54struct dlist {
55	int fd;			/* Stores directory fd */
56	int flags;		/* DIR_* Flags */
57	DIR *dp;		/* Open directory (opened with fd) */
58	long diroff;		/* Saved directory offset when closing */
59	struct dlist *up;	/* Up one step in the tree (toward "/") */
60	struct dlist *down;	/* Down one step in the tree */
61	ino_t ino;		/* st_ino of directory */
62	dev_t dev;		/* st_dev of directory */
63	int pathend;		/* Offset of name end in the pathbuffer */
64};
65
66static struct dlist top = {
67	(int)AT_FDCWD,
68	DIR_CANTCLOSE,
69};
70
71static struct dlist *cur, *rec;
72
73static int rm(const char *, struct dlist *);
74static int confirm(FILE *, const char *, ...);
75static void memerror(void);
76static int checkdir(struct dlist *, struct dlist *);
77static int errcnt;
78static boolean_t silent, interactive, recursive, ontty;
79
80static char *pathbuf;
81static size_t pathbuflen = MAXPATHLEN;
82
83static int maxfds = MAXINT;
84static int nfds;
85
86int
87main(int argc, char **argv)
88{
89	int errflg = 0;
90	int c;
91
92	(void) setlocale(LC_ALL, "");
93#if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
94#define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
95#endif
96	(void) textdomain(TEXT_DOMAIN);
97
98	while ((c = getopt(argc, argv, "frRi")) != EOF)
99		switch (c) {
100		case 'f':
101			silent = B_TRUE;
102#ifdef XPG4
103			interactive = B_FALSE;
104#endif
105			break;
106		case 'i':
107			interactive = B_TRUE;
108#ifdef XPG4
109			silent = B_FALSE;
110#endif
111			break;
112		case 'r':
113		case 'R':
114			recursive = B_TRUE;
115			break;
116		case '?':
117			errflg = 1;
118			break;
119		}
120
121	/*
122	 * For BSD compatibility allow '-' to delimit the end
123	 * of options.  However, if options were already explicitly
124	 * terminated with '--', then treat '-' literally: otherwise,
125	 * "rm -- -" won't remove '-'.
126	 */
127	if (optind < argc &&
128	    strcmp(argv[optind], "-") == 0 &&
129	    strcmp(argv[optind - 1], "--") != 0)
130		optind++;
131
132	argc -= optind;
133	argv = &argv[optind];
134
135	if ((argc < 1 && !silent) || errflg) {
136		(void) fprintf(stderr, gettext("usage: rm [-fiRr] file ...\n"));
137		exit(2);
138	}
139
140	ontty = isatty(STDIN_FILENO) != 0;
141
142	if (recursive && stat("/", &rootdir) != 0) {
143		(void) fprintf(stderr,
144		    gettext("rm: cannot stat root directory: %s\n"),
145		    strerror(errno));
146		exit(2);
147	}
148
149	pathbuf = malloc(pathbuflen);
150	if (pathbuf == NULL)
151		memerror();
152
153	if (init_yes() < 0) {
154		(void) fprintf(stderr, gettext(ERR_MSG_INIT_YES),
155		    strerror(errno));
156		exit(2);
157	}
158
159	for (; *argv != NULL; argv++) {
160		char *p = strrchr(*argv, '/');
161		if (p == NULL)
162			p = *argv;
163		else
164			p = p + 1;
165		if (strcmp(p, ".") == 0 || strcmp(p, "..") == 0) {
166			(void) fprintf(stderr,
167			    gettext("rm of %s is not allowed\n"), *argv);
168			errcnt++;
169			continue;
170		}
171		/* Retry when we can't walk back up. */
172		while (rm(*argv, rec = cur = &top) != 0)
173			;
174	}
175
176	return (errcnt != 0 ? 2 : 0);
177}
178
179static void
180pushfilename(const char *fname)
181{
182	char *p;
183	const char *q = fname;
184
185	if (cur == &top) {
186		p = pathbuf;
187	} else {
188		p = pathbuf + cur->up->pathend;
189		*p++ = '/';
190	}
191	while (*q != '\0') {
192		if (p - pathbuf + 2 >= pathbuflen) {
193			char *np;
194			pathbuflen += MAXPATHLEN;
195			np = realloc(pathbuf, pathbuflen);
196			if (np == NULL)
197				memerror();
198			p = np + (p - pathbuf);
199			pathbuf = np;
200		}
201		*p++ = *q++;
202	}
203	*p = '\0';
204	cur->pathend = p - pathbuf;
205}
206
207static void
208closeframe(struct dlist *frm)
209{
210	if (frm->dp != NULL) {
211		(void) closedir(frm->dp);
212		nfds--;
213		frm->dp = NULL;
214		frm->fd = -1;
215	}
216}
217
218static int
219reclaim(void)
220{
221	while (rec != NULL && (rec->flags & DIR_CANTCLOSE) != 0)
222		rec = rec->down;
223	if (rec == NULL || rec == cur || rec->dp == NULL)
224		return (-1);
225	rec->diroff = telldir(rec->dp);
226	closeframe(rec);
227	rec = rec->down;
228	return (0);
229}
230
231static void
232pushdir(struct dlist *frm)
233{
234	frm->up = cur;
235	frm->down = NULL;
236	cur->down = frm;
237	cur = frm;
238}
239
240static int
241opendirat(int dirfd, const char *entry, struct dlist *frm)
242{
243	int fd;
244
245	if (nfds >= maxfds)
246		(void) reclaim();
247
248	while ((fd = openat(dirfd, entry, O_RDONLY|O_NONBLOCK)) == -1 &&
249	    errno == EMFILE) {
250		if (nfds < maxfds)
251			maxfds = nfds;
252		if (reclaim() != 0)
253			return (-1);
254	}
255	if (fd < 0)
256		return (-1);
257	frm->fd = fd;
258	frm->dp = fdopendir(fd);
259	if (frm->dp == NULL) {
260		(void) close(fd);
261		return (-1);
262	}
263	nfds++;
264	return (0);
265}
266
267/*
268 * Since we never pop the top frame, cur->up can never be NULL.
269 * If we pop beyond a frame we closed, we try to reopen "..".
270 */
271static int
272popdir(boolean_t noerror)
273{
274	struct stat buf;
275	int ret = noerror ? 0 : -1;
276	pathbuf[cur->up->pathend] = '\0';
277
278	if (noerror && cur->up->fd == -1) {
279		rec = cur->up;
280		if (opendirat(cur->fd, "..", rec) != 0 ||
281		    fstat(rec->fd, &buf) != 0) {
282			(void) fprintf(stderr,
283			    gettext("rm: cannot reopen %s: %s\n"),
284			    pathbuf, strerror(errno));
285			exit(2);
286		}
287		if (rec->ino != buf.st_ino || rec->dev != buf.st_dev) {
288			(void) fprintf(stderr, gettext("rm: WARNING: "
289			    "The directory %s was moved or linked to "
290			    "another directory during the execution of rm\n"),
291			    pathbuf);
292			closeframe(rec);
293			ret = -1;
294		} else {
295			/* If telldir failed, we take it from the top. */
296			if (rec->diroff != -1)
297				seekdir(rec->dp, rec->diroff);
298		}
299	} else if (rec == cur)
300		rec = cur->up;
301	closeframe(cur);
302	cur = cur->up;
303	cur->down = NULL;
304	return (ret);
305}
306
307/*
308 * The stack frame of this function is minimized so that we can
309 * recurse quite a bit before we overflow the stack; around
310 * 30,000-40,000 nested directories can be removed with the default
311 * stack limit.
312 */
313static int
314rm(const char *entry, struct dlist *caller)
315{
316	struct dlist frame;
317	int flag;
318	struct stat temp;
319	struct dirent *dent;
320	int err;
321
322	/*
323	 * Construct the pathname: note that the entry may live in memory
324	 * allocated by readdir and that after return from recursion
325	 * the memory is no longer valid.  So after the recursive rm()
326	 * call, we use the global pathbuf instead of the entry argument.
327	 */
328	pushfilename(entry);
329
330	if (fstatat(caller->fd, entry, &temp, AT_SYMLINK_NOFOLLOW) != 0) {
331		if (!silent) {
332			(void) fprintf(stderr, "rm: %s: %s\n", pathbuf,
333			    strerror(errno));
334			errcnt++;
335		}
336		return (0);
337	}
338
339	if (S_ISDIR(temp.st_mode)) {
340		/*
341		 * If "-r" wasn't specified, trying to remove directories
342		 * is an error.
343		 */
344		if (!recursive) {
345			(void) fprintf(stderr,
346			    gettext("rm: %s is a directory\n"), pathbuf);
347			errcnt++;
348			return (0);
349		}
350
351		if (temp.st_ino == rootdir.st_ino &&
352		    temp.st_dev == rootdir.st_dev) {
353			(void) fprintf(stderr,
354			    gettext("rm of %s is not allowed\n"), "/");
355			errcnt++;
356			return (0);
357		}
358		/*
359		 * TRANSLATION_NOTE - The following message will contain the
360		 * first character of the strings for "yes" and "no" defined
361		 * in the file "nl_langinfo.po".  After substitution, the
362		 * message will appear as follows:
363		 *	rm: examine files in directory <directoryname> (y/n)?
364		 * where <directoryname> is the directory to be removed
365		 *
366		 */
367		if (interactive && !confirm(stderr,
368		    gettext("rm: examine files in directory %s (%s/%s)? "),
369		    pathbuf, yesstr, nostr)) {
370			return (0);
371		}
372
373		frame.dev = temp.st_dev;
374		frame.ino = temp.st_ino;
375		frame.flags = 0;
376		flag = AT_REMOVEDIR;
377
378#ifdef XPG4
379		/*
380		 * XCU4 and POSIX.2: If not interactive, check to see whether
381		 * or not directory is readable or writable and if not,
382		 * prompt user for response.
383		 */
384		if (ontty && !interactive && !silent &&
385		    faccessat(caller->fd, entry, W_OK|X_OK, AT_EACCESS) != 0 &&
386		    !confirm(stderr,
387		    gettext("rm: examine files in directory %s (%s/%s)? "),
388		    pathbuf, yesstr, nostr)) {
389			return (0);
390		}
391#endif
392		if (opendirat(caller->fd, entry, &frame) == -1) {
393			err = errno;
394
395			if (interactive) {
396				/*
397				 * Print an error message that
398				 * we could not read the directory
399				 * as the user wanted to examine
400				 * files in the directory.  Only
401				 * affect the error status if
402				 * user doesn't want to remove the
403				 * directory as we still may be able
404				 * remove the directory successfully.
405				 */
406				(void) fprintf(stderr, gettext(
407				    "rm: cannot read directory %s: %s\n"),
408				    pathbuf, strerror(err));
409
410/*
411 * TRANSLATION_NOTE - The following message will contain the
412 * first character of the strings for "yes" and "no" defined
413 * in the file "nl_langinfo.po".  After substitution, the
414 * message will appear as follows:
415 *	rm: remove <filename> (y/n)?
416 * For example, in German, this will appear as
417 * 	rm: l�schen <filename> (j/n)?
418 * where j=ja, n=nein, <filename>=the file to be removed
419 */
420				if (!confirm(stderr,
421				    gettext("rm: remove %s (%s/%s)? "),
422				    pathbuf, yesstr, nostr)) {
423					errcnt++;
424					return (0);
425				}
426			}
427			/* If it's empty we may still be able to rm it */
428			if (unlinkat(caller->fd, entry, flag) == 0)
429				return (0);
430			if (interactive)
431				err = errno;
432			(void) fprintf(stderr,
433			    interactive ?
434			    gettext("rm: Unable to remove directory %s: %s\n") :
435			    gettext("rm: cannot read directory %s: %s\n"),
436			    pathbuf, strerror(err));
437			errcnt++;
438			return (0);
439		}
440
441		/*
442		 * There is a race condition here too; if we open a directory
443		 * we have to make sure it's still the same directory we
444		 * stat'ed and checked against root earlier.  Let's check.
445		 */
446		if (fstat(frame.fd, &temp) != 0 ||
447		    frame.ino != temp.st_ino ||
448		    frame.dev != temp.st_dev) {
449			(void) fprintf(stderr,
450			    gettext("rm: %s: directory renamed\n"), pathbuf);
451			closeframe(&frame);
452			errcnt++;
453			return (0);
454		}
455
456		if (caller != &top) {
457			if (checkdir(caller, &frame) != 0) {
458				closeframe(&frame);
459				goto unlinkit;
460			}
461		}
462		pushdir(&frame);
463
464		/*
465		 * rm() only returns -1 if popdir failed at some point;
466		 * frame.dp is no longer reliable and we must drop out.
467		 */
468		while ((dent = readdir(frame.dp)) != NULL) {
469			if (strcmp(dent->d_name, ".") == 0 ||
470			    strcmp(dent->d_name, "..") == 0)
471				continue;
472
473			if (rm(dent->d_name, &frame) != 0)
474				break;
475		}
476
477		if (popdir(dent == NULL) != 0)
478			return (-1);
479
480		/*
481		 * We recursed and the subdirectory may have set the CANTCLOSE
482		 * flag; we need to clear it except for &top.
483		 * Recursion may have invalidated entry because of closedir().
484		 */
485		if (caller != &top) {
486			caller->flags &= ~DIR_CANTCLOSE;
487			entry = &pathbuf[caller->up->pathend + 1];
488		}
489	} else {
490		flag = 0;
491	}
492unlinkit:
493	/*
494	 * If interactive, ask for acknowledgement.
495	 */
496	if (interactive) {
497		if (!confirm(stderr, gettext("rm: remove %s (%s/%s)? "),
498		    pathbuf, yesstr, nostr)) {
499			return (0);
500		}
501	} else if (!silent && flag == 0) {
502		/*
503		 * If not silent, and stdin is a terminal, and there's
504		 * no write access, and the file isn't a symbolic link,
505		 * ask for permission.  If flag is set, then we know it's
506		 * a directory so we skip this test as it was done above.
507		 *
508		 * TRANSLATION_NOTE - The following message will contain the
509		 * first character of the strings for "yes" and "no" defined
510		 * in the file "nl_langinfo.po".  After substitution, the
511		 * message will appear as follows:
512		 *	rm: <filename>: override protection XXX (y/n)?
513		 * where XXX is the permission mode bits of the file in octal
514		 * and <filename> is the file to be removed
515		 *
516		 */
517		if (ontty && !S_ISLNK(temp.st_mode) &&
518		    faccessat(caller->fd, entry, W_OK, AT_EACCESS) != 0 &&
519		    !confirm(stdout,
520		    gettext("rm: %s: override protection %o (%s/%s)? "),
521		    pathbuf, temp.st_mode & 0777, yesstr, nostr)) {
522			return (0);
523		}
524	}
525
526	if (unlinkat(caller->fd, entry, flag) != 0) {
527		err = errno;
528		if (err == ENOENT)
529			return (0);
530
531		if (flag != 0) {
532			if (err == EINVAL) {
533				(void) fprintf(stderr, gettext(
534				    "rm: Cannot remove any directory in the "
535				    "path of the current working directory\n"
536				    "%s\n"), pathbuf);
537			} else {
538				if (err == EEXIST)
539					err = ENOTEMPTY;
540				(void) fprintf(stderr,
541				    gettext("rm: Unable to remove directory %s:"
542				    " %s\n"), pathbuf, strerror(err));
543			}
544		} else {
545#ifndef XPG4
546			if (!silent || interactive) {
547#endif
548
549				(void) fprintf(stderr,
550				    gettext("rm: %s not removed: %s\n"),
551				    pathbuf, strerror(err));
552#ifndef XPG4
553			}
554#endif
555		}
556		errcnt++;
557	}
558	return (0);
559}
560
561static int
562confirm(FILE *fp, const char *q, ...)
563{
564	va_list ap;
565
566	va_start(ap, q);
567	(void) vfprintf(fp, q, ap);
568	va_end(ap);
569	return (yes());
570}
571
572static void
573memerror(void)
574{
575	(void) fprintf(stderr, gettext("rm: Insufficient memory.\n"));
576	exit(1);
577}
578
579/*
580 * If we can't stat "..", it's either not there or we can't search
581 * the current directory; in that case we can't return back through
582 * "..", so we need to keep the parent open.
583 * Check that we came from "..", if not then this directory entry is an
584 * additional link and there is risk of a filesystem cycle and we also
585 * can't go back up through ".." and we keep the directory open.
586 */
587static int
588checkdir(struct dlist *caller, struct dlist *frmp)
589{
590	struct stat up;
591	struct dlist *ptr;
592
593	if (fstatat(frmp->fd, "..", &up, 0) != 0) {
594		caller->flags |= DIR_CANTCLOSE;
595		return (0);
596	} else if (up.st_ino == caller->ino && up.st_dev == caller->dev) {
597		return (0);
598	}
599
600	/* Directory hard link, check cycle */
601	for (ptr = caller; ptr != NULL; ptr = ptr->up) {
602		if (frmp->dev == ptr->dev && frmp->ino == ptr->ino) {
603			(void) fprintf(stderr,
604			    gettext("rm: cycle detected for %s\n"), pathbuf);
605			errcnt++;
606			return (-1);
607		}
608	}
609	caller->flags |= DIR_CANTCLOSE;
610	return (0);
611}
612