tee.c (3155B)
- // utils-std: Collection of commonly available Unix tools
- // SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
- // SPDX-License-Identifier: MPL-2.0
- #define _POSIX_C_SOURCE 200809L
- #include "../config.h"
- #include "../libutils/getopt_nolong.h"
- #include <assert.h> /* assert() */
- #include <errno.h> /* errno */
- #include <fcntl.h>
- #include <signal.h> /* signal() */
- #include <stdio.h> /* fprintf() */
- #include <stdlib.h> /* calloc(), free(), abort() */
- #include <string.h> /* strerror() */
- #include <unistd.h> /* getopt(), opt… */
- #ifdef HAS_GETOPT_LONG
- #include <getopt.h>
- #endif
- const char *argv0 = "tee";
- static void
- cleanup(int *fds)
- {
- free(fds);
- }
- int
- main(int argc, char *argv[])
- {
- mode_t mode = O_WRONLY | O_NOCTTY | O_CREAT;
- int *fds = NULL;
- int c;
- #ifdef HAS_GETOPT_LONG
- // Strictly for GNUisms compatibility so no long-only options
- // clang-format off
- static struct option opts[] = {
- {"append", required_argument, NULL, 'a'},
- {"ignore-interrupts", required_argument, NULL, 'i'},
- {0, 0, 0, 0},
- };
- // clang-format on
- // Need + as first character to get POSIX-style option parsing
- for(int c = -1; (c = getopt_long(argc, argv, "+:ai", opts, NULL)) != -1;)
- #else
- for(int c = -1; (c = getopt_nolong(argc, argv, ":ai")) != -1;)
- #endif
- {
- switch(c)
- {
- case 'a':
- mode |= O_APPEND;
- break;
- case 'i': /* ignore SIGINT */;
- signal(SIGINT, SIG_IGN);
- break;
- case ':':
- fprintf(stderr, "tee: error: Missing operand for option: '-%c'\n", optopt);
- return 1;
- case '?':
- GETOPT_UNKNOWN_OPT
- return 1;
- default:
- abort();
- }
- }
- argc -= optind;
- argv += optind;
- if(argc > 0)
- {
- fds = calloc(argc, sizeof(*fds));
- if(!fds)
- {
- fprintf(stderr, "tee: error: Failed to allocate fd array: %s\n", strerror(errno));
- return 1;
- }
- }
- for(int argi = 0; argi < argc; argi++)
- {
- assert(argv[argi]);
- // POSIX: implementations shouldn't treat '-' as stdin
- fds[argi] = open(argv[argi], mode, 0666);
- if(fds[argi] < 0)
- {
- fprintf(stderr, "tee: error: Failed opening file ‘%s’: %s\n", argv[argi], strerror(errno));
- cleanup(fds);
- return 1;
- }
- }
- // main loop, note that failed writes shouldn't make tee exit
- int err = 0;
- while(true)
- {
- static char buf[BUFSIZ];
- ssize_t sread = read(STDIN_FILENO, buf, sizeof(buf));
- if(sread == 0) break;
- if(sread < 0)
- {
- fprintf(stderr, "tee: error: Failed reading from stdin: %s\n", strerror(errno));
- cleanup(fds);
- return 1;
- }
- size_t nread = (size_t)sread;
- if(write(STDOUT_FILENO, buf, nread) < 0)
- {
- fprintf(stderr, "tee: error: Failed writing to stdout: %s\n", strerror(errno));
- err = 1;
- errno = 0;
- }
- for(int argi = 0; argi < argc; argi++)
- {
- if(write(fds[argi], buf, nread) < 0)
- {
- fprintf(
- stderr, "tee: error: Failed writing to file '%s': %s\n", argv[argi], strerror(errno));
- err = 1;
- errno = 0;
- }
- }
- }
- // cleanup
- for(int argi = 0; argi < argc; argi++)
- {
- if(close(fds[argi]) != 0)
- {
- fprintf(stderr, "tee: error: Failed closing file '%s': %s\n", argv[argi], strerror(errno));
- err++;
- }
- }
- cleanup(fds);
- return err;
- }