xref: /illumos-gate/usr/src/cmd/rm/rm.c (revision fb9b0aa8c76ff829a7069d38161a2cecc656b091)
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 
52 static struct stat rootdir;
53 
54 struct 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 
66 static struct dlist top = {
67 	(int)AT_FDCWD,
68 	DIR_CANTCLOSE,
69 };
70 
71 static struct dlist *cur, *rec;
72 
73 static int rm(const char *, struct dlist *);
74 static int confirm(FILE *, const char *, ...);
75 static void memerror(void);
76 static int checkdir(struct dlist *, struct dlist *);
77 static int errcnt;
78 static boolean_t silent, interactive, recursive, ontty;
79 
80 static char *pathbuf;
81 static size_t pathbuflen = MAXPATHLEN;
82 
83 static int maxfds = MAXINT;
84 static int nfds;
85 
86 int
87 main(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 
179 static void
180 pushfilename(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 
207 static void
208 closeframe(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 
218 static int
219 reclaim(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 
231 static void
232 pushdir(struct dlist *frm)
233 {
234 	frm->up = cur;
235 	frm->down = NULL;
236 	cur->down = frm;
237 	cur = frm;
238 }
239 
240 static int
241 opendirat(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  */
271 static int
272 popdir(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  */
313 static int
314 rm(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 	}
492 unlinkit:
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 
561 static int
562 confirm(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 
572 static void
573 memerror(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  */
587 static int
588 checkdir(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