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