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 }