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