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 }