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 /*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T     */
  22 /*        All Rights Reserved   */
  23 
  24 
  25 /*
  26  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
  27  * Use is subject to license terms.
  28  */
  29 
  30 /*
  31  * Copyright (c) 2018, Joyent, Inc.
  32  */
  33 
  34 /*
  35  *      Concatenate files.
  36  */
  37 
  38 #include        <stdio.h>
  39 #include        <stdlib.h>
  40 #include        <ctype.h>
  41 #include        <sys/types.h>
  42 #include        <sys/stat.h>
  43 #include        <locale.h>
  44 #include        <unistd.h>
  45 #include        <sys/mman.h>
  46 #include        <errno.h>
  47 #include        <string.h>
  48 
  49 #include        <widec.h>
  50 #include        <wctype.h>
  51 #include        <limits.h>
  52 #include        <libintl.h>
  53 #define IDENTICAL(A, B) (A.st_dev == B.st_dev && A.st_ino == B.st_ino)
  54 
  55 #define MAXMAPSIZE      (8*1024*1024)   /* map at most 8MB */
  56 #define SMALLFILESIZE   (32*1024)       /* don't use mmap on little files */
  57 
  58 static int vncat(FILE *);
  59 static int cat(FILE *, struct stat *, struct stat *, char *);
  60 
  61 static int      silent = 0;             /* s flag */
  62 static int      visi_mode = 0;          /* v flag */
  63 static int      visi_tab = 0;           /* t flag */
  64 static int      visi_newline = 0;       /* e flag */
  65 static int      bflg = 0;               /* b flag */
  66 static int      nflg = 0;               /* n flag */
  67 static long     ibsize;
  68 static long     obsize;
  69 static unsigned char    buf[SMALLFILESIZE];
  70 
  71 
  72 int
  73 main(int argc, char **argv)
  74 {
  75         FILE *fi;
  76         int c;
  77         extern  int optind;
  78         int     errflg = 0;
  79         int     stdinflg = 0;
  80         int     status = 0;
  81         int     estatus = 0;
  82         struct stat source, target;
  83 
  84         (void) setlocale(LC_ALL, "");
  85 #if !defined(TEXT_DOMAIN)       /* Should be defined by cc -D */
  86 #define TEXT_DOMAIN "SYS_TEST"  /* Use this only if it weren't */
  87 #endif
  88         (void) textdomain(TEXT_DOMAIN);
  89 
  90 #ifdef STANDALONE
  91         /*
  92          * If the first argument is NULL,
  93          * discard arguments until we find cat.
  94          */
  95         if (argv[0][0] == '\0')
  96                 argc = getargv("cat", &argv, 0);
  97 #endif
  98 
  99         /*
 100          * Process the options for cat.
 101          */
 102 
 103         while ((c = getopt(argc, argv, "usvtebn")) != EOF) {
 104                 switch (c) {
 105 
 106                 case 'u':
 107 
 108                         /*
 109                          * If not standalone, set stdout to
 110                          * completely unbuffered I/O when
 111                          * the 'u' option is used.
 112                          */
 113 
 114 #ifndef STANDALONE
 115                         setbuf(stdout, (char *)NULL);
 116 #endif
 117                         continue;
 118 
 119                 case 's':
 120 
 121                         /*
 122                          * The 's' option requests silent mode
 123                          * where no messages are written.
 124                          */
 125 
 126                         silent++;
 127                         continue;
 128 
 129                 case 'v':
 130 
 131                         /*
 132                          * The 'v' option requests that non-printing
 133                          * characters (with the exception of newlines,
 134                          * form-feeds, and tabs) be displayed visibly.
 135                          *
 136                          * Control characters are printed as "^x".
 137                          * DEL characters are printed as "^?".
 138                          * Non-printable  and non-contrlol characters with the
 139                          * 8th bit set are printed as "M-x".
 140                          */
 141 
 142                         visi_mode++;
 143                         continue;
 144 
 145                 case 't':
 146 
 147                         /*
 148                          * When in visi_mode, this option causes tabs
 149                          * to be displayed as "^I".
 150                          */
 151 
 152                         visi_tab++;
 153                         continue;
 154 
 155                 case 'e':
 156 
 157                         /*
 158                          * When in visi_mode, this option causes newlines
 159                          * and form-feeds to be displayed as "$" at the end
 160                          * of the line prior to the newline.
 161                          */
 162 
 163                         visi_newline++;
 164                         continue;
 165 
 166                 case 'b':
 167 
 168                         /*
 169                          * Precede each line output with its line number,
 170                          * but omit the line numbers from blank lines.
 171                          */
 172 
 173                         bflg++;
 174                         nflg++;
 175                         continue;
 176 
 177                 case 'n':
 178 
 179                         /*
 180                          * Precede each line output with its line number.
 181                          */
 182 
 183                         nflg++;
 184                         continue;
 185 
 186                 case '?':
 187                         errflg++;
 188                         break;
 189                 }
 190                 break;
 191         }
 192 
 193         if (errflg) {
 194                 if (!silent)
 195                         (void) fprintf(stderr,
 196                             gettext("usage: cat [ -usvtebn ] [-|file] ...\n"));
 197                 exit(2);
 198         }
 199 
 200         /*
 201          * Stat stdout to be sure it is defined.
 202          */
 203 
 204         if (fstat(fileno(stdout), &target) < 0) {
 205                 if (!silent)
 206                         (void) fprintf(stderr,
 207                             gettext("cat: Cannot stat stdout\n"));
 208                 exit(2);
 209         }
 210         obsize = target.st_blksize;
 211 
 212         /*
 213          * If no arguments given, then use stdin for input.
 214          */
 215 
 216         if (optind == argc) {
 217                 argc++;
 218                 stdinflg++;
 219         }
 220 
 221         /*
 222          * Process each remaining argument,
 223          * unless there is an error with stdout.
 224          */
 225 
 226 
 227         for (argv = &argv[optind];
 228             optind < argc && !ferror(stdout); optind++, argv++) {
 229 
 230                 /*
 231                  * If the argument was '-' or there were no files
 232                  * specified, take the input from stdin.
 233                  */
 234 
 235                 if (stdinflg ||
 236                     ((*argv)[0] == '-' && (*argv)[1] == '\0'))
 237                         fi = stdin;
 238                 else {
 239                         /*
 240                          * Attempt to open each specified file.
 241                          */
 242 
 243                         if ((fi = fopen(*argv, "r")) == NULL) {
 244                                 if (!silent)
 245                                         (void) fprintf(stderr, gettext(
 246                                             "cat: cannot open %s: %s\n"),
 247                                             *argv, strerror(errno));
 248                                 status = 2;
 249                                 continue;
 250                         }
 251                 }
 252 
 253                 /*
 254                  * Stat source to make sure it is defined.
 255                  */
 256 
 257                 if (fstat(fileno(fi), &source) < 0) {
 258                         if (!silent)
 259                                 (void) fprintf(stderr,
 260                                     gettext("cat: cannot stat %s: %s\n"),
 261                                     (stdinflg) ? "-" : *argv, strerror(errno));
 262                         status = 2;
 263                         continue;
 264                 }
 265 
 266 
 267                 /*
 268                  * If the source is not a character special file, socket or a
 269                  * block special file, make sure it is not identical
 270                  * to the target.
 271                  */
 272 
 273                 if (!S_ISCHR(target.st_mode) &&
 274                     !S_ISBLK(target.st_mode) &&
 275                     !S_ISSOCK(target.st_mode) &&
 276                     IDENTICAL(target, source)) {
 277                         if (!silent) {
 278                                 (void) fprintf(stderr, gettext("cat: "
 279                                     "input/output files '%s' identical\n"),
 280                                     stdinflg?"-": *argv);
 281                         }
 282 
 283                         if (fclose(fi) != 0)
 284                                 (void) fprintf(stderr,
 285                                     gettext("cat: close error: %s\n"),
 286                                     strerror(errno));
 287                         status = 2;
 288                         continue;
 289                 }
 290                 ibsize = source.st_blksize;
 291 
 292                 /*
 293                  * If in visible mode and/or nflg, use vncat;
 294                  * otherwise, use cat.
 295                  */
 296 
 297                 if (visi_mode || nflg)
 298                         estatus = vncat(fi);
 299                 else
 300                         estatus = cat(fi, &source, &target,
 301                             fi != stdin ? *argv : "standard input");
 302 
 303                 if (estatus)
 304                         status = estatus;
 305 
 306                 /*
 307                  * If the input is not stdin, close the source file.
 308                  */
 309 
 310                 if (fi != stdin) {
 311                         if (fclose(fi) != 0)
 312                                 if (!silent)
 313                                         (void) fprintf(stderr,
 314                                             gettext("cat: close error: %s\n"),
 315                                             strerror(errno));
 316                 }
 317         }
 318 
 319         /*
 320          * Display any error with stdout operations.
 321          */
 322 
 323         if (fclose(stdout) != 0) {
 324                 if (!silent)
 325                         perror(gettext("cat: close error"));
 326                 status = 2;
 327         }
 328         return (status);
 329 }
 330 
 331 
 332 
 333 static int
 334 cat(FILE *fi, struct stat *statp, struct stat *outp, char *filenm)
 335 {
 336         int nitems;
 337         int nwritten;
 338         int offset;
 339         int fi_desc;
 340         long buffsize;
 341         char *bufferp;
 342         off_t mapsize, munmapsize;
 343         off_t filesize;
 344         off_t mapoffset;
 345 
 346         fi_desc = fileno(fi);
 347         if (S_ISREG(statp->st_mode) && (lseek(fi_desc, (off_t)0, SEEK_CUR)
 348             == 0) && (statp->st_size > SMALLFILESIZE)) {
 349                 mapsize = (off_t)MAXMAPSIZE;
 350                 if (statp->st_size < mapsize)
 351                         mapsize = statp->st_size;
 352                 munmapsize = mapsize;
 353 
 354                 /*
 355                  * Mmap time!
 356                  */
 357                 bufferp = mmap((caddr_t)NULL, (size_t)mapsize, PROT_READ,
 358                     MAP_SHARED, fi_desc, (off_t)0);
 359                 if (bufferp == (caddr_t)-1)
 360                         mapsize = 0;    /* I guess we can't mmap today */
 361         } else
 362                 mapsize = 0;            /* can't mmap non-regular files */
 363 
 364         if (mapsize != 0) {
 365                 int     read_error = 0;
 366                 char    x;
 367 
 368                 /*
 369                  * NFS V2 will let root open a file it does not have permission
 370                  * to read. This read() is here to make sure that the access
 371                  * time on the input file will be updated. The VSC tests for
 372                  * cat do this:
 373                  *      cat file > /dev/null
 374                  * In this case the write()/mmap() pair will not read the file
 375                  * and the access time will not be updated.
 376                  */
 377 
 378                 if (read(fi_desc, &x, 1) == -1)
 379                         read_error = 1;
 380                 mapoffset = 0;
 381                 filesize = statp->st_size;
 382                 for (;;) {
 383                         /*
 384                          * Note that on some systems (V7), very large writes to
 385                          * a pipe return less than the requested size of the
 386                          * write.  In this case, multiple writes are required.
 387                          */
 388                         offset = 0;
 389                         nitems = (int)mapsize;
 390                         do {
 391                                 if ((nwritten = write(fileno(stdout),
 392                                     &bufferp[offset], (size_t)nitems)) < 0) {
 393                                         if (!silent) {
 394                                                 if (read_error == 1)
 395                                                         (void) fprintf(
 396                                                             stderr, gettext(
 397                                                             "cat: cannot read "
 398                                                             "%s: "), filenm);
 399                                                 else
 400                                                         (void) fprintf(stderr,
 401                                                             gettext(
 402                                                             "cat: write "
 403                                                             "error: "));
 404                                                 perror("");
 405                                         }
 406                                         (void) munmap(bufferp,
 407                                             (size_t)munmapsize);
 408                                         (void) lseek(fi_desc, (off_t)mapoffset,
 409                                             SEEK_SET);
 410                                         return (2);
 411                                 }
 412                                 offset += nwritten;
 413                         } while ((nitems -= nwritten) > 0);
 414 
 415                         filesize -= mapsize;
 416                         mapoffset += mapsize;
 417                         if (filesize == 0)
 418                                 break;
 419                         if (filesize < mapsize)
 420                                 mapsize = filesize;
 421                         if (mmap(bufferp, (size_t)mapsize, PROT_READ,
 422                             MAP_SHARED|MAP_FIXED, fi_desc,
 423                             mapoffset) == (caddr_t)-1) {
 424                                 if (!silent)
 425                                         perror(gettext("cat: mmap error"));
 426                                 (void) munmap(bufferp, (size_t)munmapsize);
 427                                 (void) lseek(fi_desc, (off_t)mapoffset,
 428                                     SEEK_SET);
 429                                 return (1);
 430                         }
 431                 }
 432                 /*
 433                  * Move the file pointer past what we read. Shell scripts
 434                  * rely on cat to do this, so that successive commands in
 435                  * the script won't re-read the same data.
 436                  */
 437                 (void) lseek(fi_desc, (off_t)mapoffset, SEEK_SET);
 438                 (void) munmap(bufferp, (size_t)munmapsize);
 439         } else {
 440                 if (S_ISREG(statp->st_mode) && S_ISREG(outp->st_mode)) {
 441                         bufferp = (char *)buf;
 442                         buffsize = SMALLFILESIZE;
 443                 } else {
 444                         if (obsize)
 445                                 /*
 446                                  * common case, use output blksize
 447                                  */
 448                                 buffsize = obsize;
 449                         else if (ibsize)
 450                                 buffsize = ibsize;
 451                         else
 452                                 buffsize = (long)BUFSIZ;
 453 
 454                         if (buffsize <= SMALLFILESIZE) {
 455                                 bufferp = (char *)buf;
 456                         } else if ((bufferp =
 457                             malloc((size_t)buffsize)) == NULL) {
 458                                 perror(gettext("cat: no memory"));
 459                                 return (1);
 460                         }
 461                 }
 462 
 463                 /*
 464                  * While not end of file, copy blocks to stdout.
 465                  */
 466                 while ((nitems = read(fi_desc, bufferp, (size_t)buffsize)) >
 467                     0) {
 468                         offset = 0;
 469                         /*
 470                          * Note that on some systems (V7), very large writes
 471                          * to a pipe return less than the requested size of
 472                          * the write.  In this case, multiple writes are
 473                          * required.
 474                          */
 475                         do {
 476                                 nwritten = write(1, bufferp+offset,
 477                                     (size_t)nitems);
 478                                 if (nwritten < 0) {
 479                                         if (!silent) {
 480                                                 if (nwritten == -1)
 481                                                         nwritten = 0l;
 482                                                 (void) fprintf(stderr, gettext(\
 483 "cat: output error (%d/%d characters written)\n"), nwritten, nitems);
 484                                                 perror("");
 485                                         }
 486                                         if (bufferp != (char *)buf)
 487                                                 free(bufferp);
 488                                         return (2);
 489                                 }
 490                                 offset += nwritten;
 491                         } while ((nitems -= nwritten) > 0);
 492                 }
 493                 if (bufferp != (char *)buf)
 494                         free(bufferp);
 495                 if (nitems < 0) {
 496                         (void) fprintf(stderr,
 497                             gettext("cat: input error on %s: "), filenm);
 498                         perror("");
 499                         return (1);
 500                 }
 501         }
 502 
 503         return (0);
 504 }
 505 
 506 static int
 507 vncat(fi)
 508         FILE *fi;
 509 {
 510         int c;
 511         int     lno;
 512         int     boln;   /* = 1 if at beginning of line */
 513                         /* = 0 otherwise */
 514         wchar_t wc;
 515         int     len, n;
 516         unsigned char   *p1, *p2;
 517 
 518         lno = 1;
 519         boln = 1;
 520         p1 = p2 = buf;
 521         for (;;) {
 522                 if (p1 >= p2) {
 523                         p1 = buf;
 524                         if ((len = fread(p1, 1, BUFSIZ, fi)) <= 0)
 525                                 break;
 526                         p2 = p1 + len;
 527                 }
 528                 c = *p1++;
 529 
 530                 /*
 531                  * Display newlines as "$<newline>"
 532                  * if visi_newline set
 533                  */
 534                 if (c == '\n') {
 535                         if (nflg && boln && !bflg)
 536                                 (void) printf("%6d\t", lno++);
 537                         boln = 1;
 538 
 539                         if (visi_mode && visi_newline)
 540                                 (void) putchar('$');
 541                         (void) putchar(c);
 542                         continue;
 543                 }
 544 
 545                 if (nflg && boln)
 546                         (void) printf("%6d\t", lno++);
 547                 boln = 0;
 548 
 549                 /*
 550                  * For non-printable and non-cntrl chars,
 551                  * use the "M-x" notation.
 552                  */
 553 
 554                 if (isascii(c)) {
 555                         if (isprint(c) || visi_mode == 0) {
 556                                 (void) putchar(c);
 557                                 continue;
 558                         }
 559 
 560                         /*
 561                          * For non-printable ascii characters.
 562                          */
 563 
 564                         if (iscntrl(c)) {
 565                                 /* For cntrl characters. */
 566                                 if ((c == '\t') || (c == '\f')) {
 567                                         /*
 568                                          * Display tab as "^I" if visi_tab set
 569                                          */
 570                                         if (visi_mode && visi_tab) {
 571                                                 (void) putchar('^');
 572                                                 (void) putchar(c^0100);
 573                                         } else
 574                                                 (void) putchar(c);
 575                                         continue;
 576                                 }
 577                                 (void) putchar('^');
 578                                 (void) putchar(c^0100);
 579                                 continue;
 580                         }
 581                         continue;
 582                 }
 583 
 584                 /*
 585                  * For non-ascii characters.
 586                  */
 587                 p1--;
 588                 if ((len = (p2 - p1)) < MB_LEN_MAX) {
 589                         for (n = 0; n < len; n++)
 590                                 buf[n] = *p1++;
 591                         p1 = buf;
 592                         p2 = p1 + n;
 593                         if ((len = fread(p2, 1, BUFSIZ - n, fi)) > 0)
 594                                 p2 += len;
 595                 }
 596 
 597                 if ((len = (p2 - p1)) > MB_LEN_MAX)
 598                         len = MB_LEN_MAX;
 599 
 600                 if ((len = mbtowc(&wc, (char *)p1, len)) > 0) {
 601                         if (iswprint(wc) || visi_mode == 0) {
 602                                 (void) putwchar(wc);
 603                                 p1 += len;
 604                                 continue;
 605                         }
 606                 }
 607 
 608                 (void) putchar('M');
 609                 (void) putchar('-');
 610                 c -= 0200;
 611 
 612                 if (isprint(c)) {
 613                         (void) putchar(c);
 614                 }
 615 
 616                 /* For non-printable characters. */
 617                 if (iscntrl(c)) {
 618                         /* For cntrl characters. */
 619                         if ((c == '\t') || (c == '\f')) {
 620                                 /*
 621                                  * Display tab as "^I" if visi_tab set
 622                                  */
 623                                 if (visi_mode && visi_tab) {
 624                                         (void) putchar('^');
 625                                         (void) putchar(c^0100);
 626                                 } else
 627                                         (void) putchar(c);
 628                         } else {
 629                                 (void) putchar('^');
 630                                 (void) putchar(c^0100);
 631                         }
 632                 }
 633                 p1++;
 634         }
 635         return (0);
 636 }