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
main(int argc,char ** argv)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
pushfilename(const char * fname)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
closeframe(struct dlist * frm)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
reclaim(void)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
pushdir(struct dlist * frm)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
opendirat(int dirfd,const char * entry,struct dlist * frm)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
popdir(boolean_t noerror)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
rm(const char * entry,struct dlist * caller)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
confirm(FILE * fp,const char * q,...)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
memerror(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
checkdir(struct dlist * caller,struct dlist * frmp)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