xref: /freebsd/bin/cat/cat.c (revision a259b98fa211ed87bfee58c575de4e2de94ee0fa)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 1989, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Kevin Fall.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include <sys/capsicum.h>
36 #include <sys/param.h>
37 #include <sys/stat.h>
38 #ifndef NO_UDOM_SUPPORT
39 #include <sys/socket.h>
40 #include <sys/un.h>
41 #include <netdb.h>
42 #endif
43 
44 #include <capsicum_helpers.h>
45 #include <ctype.h>
46 #include <err.h>
47 #include <errno.h>
48 #include <fcntl.h>
49 #include <locale.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <unistd.h>
54 #include <wchar.h>
55 #include <wctype.h>
56 
57 #include <libcasper.h>
58 #include <casper/cap_fileargs.h>
59 #include <casper/cap_net.h>
60 
61 static int bflag, Eflag, lflag, nflag, sflag, Tflag, vflag;
62 static int rval;
63 static const char *filename;
64 static fileargs_t *fa;
65 
66 static void usage(void) __dead2;
67 static void scanfiles(char *argv[], int cooked);
68 #ifndef BOOTSTRAP_CAT
69 static void cook_cat(FILE *);
70 static ssize_t in_kernel_copy(int);
71 #endif
72 static void raw_cat(int);
73 
74 #ifndef NO_UDOM_SUPPORT
75 static cap_channel_t *capnet;
76 
77 static int udom_open(const char *path, int flags);
78 #endif
79 
80 /*
81  * Memory strategy threshold, in pages: if physmem is larger than this,
82  * use a large buffer.
83  */
84 #define	PHYSPAGES_THRESHOLD (32 * 1024)
85 
86 /* Maximum buffer size in bytes - do not allow it to grow larger than this. */
87 #define	BUFSIZE_MAX (2 * 1024 * 1024)
88 
89 /*
90  * Small (default) buffer size in bytes. It's inefficient for this to be
91  * smaller than MAXPHYS.
92  */
93 #define	BUFSIZE_SMALL (MAXPHYS)
94 
95 
96 /*
97  * For the bootstrapped cat binary (needed for locked appending to METALOG), we
98  * disable all flags except -l and -u to avoid non-portable function calls.
99  * In the future we may instead want to write a small portable bootstrap tool
100  * that locks the output file before writing to it. However, for now
101  * bootstrapping cat without multibyte support is the simpler solution.
102  */
103 #ifdef BOOTSTRAP_CAT
104 #define SUPPORTED_FLAGS "lu"
105 #else
106 #define SUPPORTED_FLAGS "belnstuvAET"
107 #endif
108 
109 #ifndef NO_UDOM_SUPPORT
110 static void
111 init_casper_net(cap_channel_t *casper)
112 {
113 	cap_net_limit_t *limit;
114 	int familylimit;
115 
116 	capnet = cap_service_open(casper, "system.net");
117 	if (capnet == NULL)
118 		err(EXIT_FAILURE, "unable to create network service");
119 
120 	limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR |
121 	    CAPNET_CONNECTDNS);
122 	if (limit == NULL)
123 		err(EXIT_FAILURE, "unable to create limits");
124 
125 	familylimit = AF_LOCAL;
126 	cap_net_limit_name2addr_family(limit, &familylimit, 1);
127 
128 	if (cap_net_limit(limit) != 0)
129 		err(EXIT_FAILURE, "unable to apply limits");
130 }
131 #endif
132 
133 static void
134 init_casper(int argc, char *argv[])
135 {
136 	cap_channel_t *casper;
137 	cap_rights_t rights;
138 
139 	casper = cap_init();
140 	if (casper == NULL)
141 		err(EXIT_FAILURE, "unable to create Casper");
142 
143 	fa = fileargs_cinit(casper, argc, argv, O_RDONLY, 0,
144 	    cap_rights_init(&rights, CAP_READ, CAP_FSTAT, CAP_FCNTL, CAP_SEEK),
145 	    FA_OPEN | FA_REALPATH);
146 	if (fa == NULL)
147 		err(EXIT_FAILURE, "unable to create fileargs");
148 
149 #ifndef NO_UDOM_SUPPORT
150 	init_casper_net(casper);
151 #endif
152 
153 	cap_close(casper);
154 }
155 
156 int
157 main(int argc, char *argv[])
158 {
159 	int ch;
160 	struct flock stdout_lock;
161 
162 	setlocale(LC_CTYPE, "");
163 
164 	while ((ch = getopt(argc, argv, SUPPORTED_FLAGS)) != -1)
165 		switch (ch) {
166 		case 'b':
167 			bflag = nflag = 1;	/* -b implies -n */
168 			break;
169 		case 'A':
170 			Eflag = Tflag = vflag = 1;	/* -A implies -v -E -T */
171 			break;
172 		case 'e':
173 			Eflag = vflag = 1;	/* -e implies -v */
174 			break;
175 		case 'E':
176 			Eflag = 1;
177 			break;
178 		case 'l':
179 			lflag = 1;
180 			break;
181 		case 'n':
182 			nflag = 1;
183 			break;
184 		case 's':
185 			sflag = 1;
186 			break;
187 		case 't':
188 			Tflag = vflag = 1;	/* -t implies -v */
189 			break;
190 		case 'T':
191 			Tflag = 1;
192 			break;
193 		case 'u':
194 			setbuf(stdout, NULL);
195 			break;
196 		case 'v':
197 			vflag = 1;
198 			break;
199 		default:
200 			usage();
201 		}
202 	argv += optind;
203 	argc -= optind;
204 
205 	if (lflag) {
206 		stdout_lock.l_len = 0;
207 		stdout_lock.l_start = 0;
208 		stdout_lock.l_type = F_WRLCK;
209 		stdout_lock.l_whence = SEEK_SET;
210 		if (fcntl(STDOUT_FILENO, F_SETLKW, &stdout_lock) != 0)
211 			err(EXIT_FAILURE, "stdout");
212 	}
213 
214 	init_casper(argc, argv);
215 
216 	caph_cache_catpages();
217 
218 	if (caph_enter_casper() != 0)
219 		err(EXIT_FAILURE, "capsicum");
220 
221 	if (bflag || Eflag || nflag || sflag || Tflag || vflag)
222 		scanfiles(argv, 1);
223 	else
224 		scanfiles(argv, 0);
225 	if (fclose(stdout))
226 		err(1, "stdout");
227 	exit(rval);
228 	/* NOTREACHED */
229 }
230 
231 static void
232 usage(void)
233 {
234 
235 	fprintf(stderr, "usage: cat [-" SUPPORTED_FLAGS "] [file ...]\n");
236 	exit(1);
237 	/* NOTREACHED */
238 }
239 
240 static void
241 scanfiles(char *argv[], int cooked __unused)
242 {
243 	int fd, i;
244 	char *path;
245 #ifndef BOOTSTRAP_CAT
246 	FILE *fp;
247 #endif
248 
249 	i = 0;
250 	fd = -1;
251 	while ((path = argv[i]) != NULL || i == 0) {
252 		if (path == NULL || strcmp(path, "-") == 0) {
253 			filename = "stdin";
254 			fd = STDIN_FILENO;
255 		} else {
256 			filename = path;
257 			fd = fileargs_open(fa, path);
258 #ifndef NO_UDOM_SUPPORT
259 			if (fd < 0 && errno == EOPNOTSUPP)
260 				fd = udom_open(path, O_RDONLY);
261 #endif
262 		}
263 		if (fd < 0) {
264 			warn("%s", path);
265 			rval = 1;
266 #ifndef BOOTSTRAP_CAT
267 		} else if (cooked) {
268 			if (fd == STDIN_FILENO)
269 				cook_cat(stdin);
270 			else {
271 				if ((fp = fdopen(fd, "r")) == NULL)
272 					err(1, "fdopen");
273 				cook_cat(fp);
274 				fclose(fp);
275 			}
276 #endif
277 		} else {
278 #ifndef BOOTSTRAP_CAT
279 			if (in_kernel_copy(fd) != 0) {
280 				if (errno == EINVAL || errno == EBADF ||
281 				    errno == EISDIR)
282 					raw_cat(fd);
283 				else
284 					err(1, "%s", filename);
285 			}
286 #else
287 			raw_cat(fd);
288 #endif
289 			if (fd != STDIN_FILENO)
290 				close(fd);
291 		}
292 		if (path == NULL)
293 			break;
294 		++i;
295 	}
296 }
297 
298 #ifndef BOOTSTRAP_CAT
299 static void
300 cook_cat(FILE *fp)
301 {
302 	int ch, gobble, line, prev;
303 	wint_t wch;
304 
305 	/* Reset EOF condition on stdin. */
306 	if (fp == stdin && feof(stdin))
307 		clearerr(stdin);
308 
309 	line = gobble = 0;
310 	for (prev = '\n'; (ch = getc(fp)) != EOF; prev = ch) {
311 		if (prev == '\n') {
312 			if (sflag) {
313 				if (ch == '\n') {
314 					if (gobble)
315 						continue;
316 					gobble = 1;
317 				} else
318 					gobble = 0;
319 			}
320 			if (nflag) {
321 				if (!bflag || ch != '\n') {
322 					(void)fprintf(stdout, "%6d\t", ++line);
323 					if (ferror(stdout))
324 						break;
325 				} else if (Eflag) {
326 					(void)fprintf(stdout, "%6s\t", "");
327 					if (ferror(stdout))
328 						break;
329 				}
330 			}
331 		}
332 		if (ch == '\n') {
333 			if (Eflag && putchar('$') == EOF)
334 				break;
335 		} else if (ch == '\t') {
336 			if (Tflag) {
337 				if (putchar('^') == EOF || putchar('I') == EOF)
338 					break;
339 				continue;
340 			}
341 		} else if (vflag) {
342 			(void)ungetc(ch, fp);
343 			/*
344 			 * Our getwc(3) doesn't change file position
345 			 * on error.
346 			 */
347 			if ((wch = getwc(fp)) == WEOF) {
348 				if (ferror(fp) && errno == EILSEQ) {
349 					clearerr(fp);
350 					/* Resync attempt. */
351 					memset(&fp->_mbstate, 0, sizeof(mbstate_t));
352 					if ((ch = getc(fp)) == EOF)
353 						break;
354 					wch = ch;
355 					goto ilseq;
356 				} else
357 					break;
358 			}
359 			if (!iswascii(wch) && !iswprint(wch)) {
360 ilseq:
361 				if (putchar('M') == EOF || putchar('-') == EOF)
362 					break;
363 				wch = toascii(wch);
364 			}
365 			if (iswcntrl(wch)) {
366 				ch = toascii(wch);
367 				ch = (ch == '\177') ? '?' : (ch | 0100);
368 				if (putchar('^') == EOF || putchar(ch) == EOF)
369 					break;
370 				continue;
371 			}
372 			if (putwchar(wch) == WEOF)
373 				break;
374 			ch = -1;
375 			continue;
376 		}
377 		if (putchar(ch) == EOF)
378 			break;
379 	}
380 	if (ferror(fp)) {
381 		warn("%s", filename);
382 		rval = 1;
383 		clearerr(fp);
384 	}
385 	if (ferror(stdout))
386 		err(1, "stdout");
387 }
388 
389 static ssize_t
390 in_kernel_copy(int rfd)
391 {
392 	int wfd;
393 	ssize_t ret;
394 
395 	wfd = fileno(stdout);
396 	ret = 1;
397 
398 	while (ret > 0)
399 		ret = copy_file_range(rfd, NULL, wfd, NULL, SSIZE_MAX, 0);
400 
401 	return (ret);
402 }
403 #endif /* BOOTSTRAP_CAT */
404 
405 static void
406 raw_cat(int rfd)
407 {
408 	long pagesize;
409 	int off, wfd;
410 	ssize_t nr, nw;
411 	static size_t bsize;
412 	static char *buf = NULL;
413 	struct stat sbuf;
414 
415 	wfd = fileno(stdout);
416 	if (buf == NULL) {
417 		if (fstat(wfd, &sbuf))
418 			err(1, "stdout");
419 		if (S_ISREG(sbuf.st_mode)) {
420 			/* If there's plenty of RAM, use a large copy buffer */
421 			if (sysconf(_SC_PHYS_PAGES) > PHYSPAGES_THRESHOLD)
422 				bsize = MIN(BUFSIZE_MAX, MAXPHYS * 8);
423 			else
424 				bsize = BUFSIZE_SMALL;
425 		} else {
426 			bsize = sbuf.st_blksize;
427 			pagesize = sysconf(_SC_PAGESIZE);
428 			if (pagesize > 0)
429 				bsize = MAX(bsize, (size_t)pagesize);
430 		}
431 		if ((buf = malloc(bsize)) == NULL)
432 			err(1, "malloc() failure of IO buffer");
433 	}
434 	while ((nr = read(rfd, buf, bsize)) > 0)
435 		for (off = 0; nr; nr -= nw, off += nw)
436 			if ((nw = write(wfd, buf + off, (size_t)nr)) < 0)
437 				err(1, "stdout");
438 	if (nr < 0) {
439 		warn("%s", filename);
440 		rval = 1;
441 	}
442 }
443 
444 #ifndef NO_UDOM_SUPPORT
445 
446 static int
447 udom_open(const char *path, int flags)
448 {
449 	struct addrinfo hints, *res, *res0;
450 	char rpath[PATH_MAX];
451 	int error, fd, serrno;
452 	cap_rights_t rights;
453 
454 	/*
455 	 * Construct the unix domain socket address and attempt to connect.
456 	 */
457 	bzero(&hints, sizeof(hints));
458 	hints.ai_family = AF_LOCAL;
459 
460 	if (fileargs_realpath(fa, path, rpath) == NULL)
461 		return (-1);
462 
463 	error = cap_getaddrinfo(capnet, rpath, NULL, &hints, &res0);
464 	if (error) {
465 		warn("%s", gai_strerror(error));
466 		errno = EINVAL;
467 		return (-1);
468 	}
469 	cap_rights_init(&rights, CAP_CONNECT, CAP_READ, CAP_WRITE,
470 	    CAP_SHUTDOWN, CAP_FSTAT, CAP_FCNTL);
471 
472 	/* Default error if something goes wrong. */
473 	serrno = EINVAL;
474 
475 	for (res = res0; res != NULL; res = res->ai_next) {
476 		fd = socket(res->ai_family, res->ai_socktype,
477 		    res->ai_protocol);
478 		if (fd < 0) {
479 			serrno = errno;
480 			freeaddrinfo(res0);
481 			errno = serrno;
482 			return (-1);
483 		}
484 		if (caph_rights_limit(fd, &rights) != 0) {
485 			serrno = errno;
486 			close(fd);
487 			freeaddrinfo(res0);
488 			errno = serrno;
489 			return (-1);
490 		}
491 		error = cap_connect(capnet, fd, res->ai_addr, res->ai_addrlen);
492 		if (error == 0)
493 			break;
494 		else {
495 			serrno = errno;
496 			close(fd);
497 		}
498 	}
499 	freeaddrinfo(res0);
500 
501 	if (res == NULL) {
502 		errno = serrno;
503 		return (-1);
504 	}
505 
506 	/*
507 	 * handle the open flags by shutting down appropriate directions
508 	 */
509 
510 	switch (flags & O_ACCMODE) {
511 	case O_RDONLY:
512 		cap_rights_clear(&rights, CAP_WRITE);
513 		if (shutdown(fd, SHUT_WR) != 0)
514 			warn(NULL);
515 		break;
516 	case O_WRONLY:
517 		cap_rights_clear(&rights, CAP_READ);
518 		if (shutdown(fd, SHUT_RD) != 0)
519 			warn(NULL);
520 		break;
521 	default:
522 		break;
523 	}
524 
525 	cap_rights_clear(&rights, CAP_CONNECT, CAP_SHUTDOWN);
526 	if (caph_rights_limit(fd, &rights) != 0) {
527 		serrno = errno;
528 		close(fd);
529 		errno = serrno;
530 		return (-1);
531 	}
532 	return (fd);
533 }
534 
535 #endif
536