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