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 }