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