Home | History | Annotate | Download | only in cat
      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 }
    632