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