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