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
main(int argc,char ** argv)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
cat(FILE * fi,struct stat * statp,struct stat * outp,char * filenm)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
vncat(fi)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 }
637