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