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