diff options
Diffstat (limited to 'src/tee.c')
-rw-r--r-- | src/tee.c | 344 |
1 files changed, 344 insertions, 0 deletions
diff --git a/src/tee.c b/src/tee.c new file mode 100644 index 0000000..27323a3 --- /dev/null +++ b/src/tee.c @@ -0,0 +1,344 @@ +/* tee - read from standard input and write to standard output and files. + Copyright (C) 1985-2023 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +/* Mike Parker, Richard M. Stallman, and David MacKenzie */ + +#include <config.h> +#include <sys/types.h> +#include <signal.h> +#include <getopt.h> + +#include "system.h" +#include "argmatch.h" +#include "fadvise.h" +#include "iopoll.h" +#include "stdio--.h" +#include "xbinary-io.h" +#include "iopoll.h" + +/* The official name of this program (e.g., no 'g' prefix). */ +#define PROGRAM_NAME "tee" + +#define AUTHORS \ + proper_name ("Mike Parker"), \ + proper_name ("Richard M. Stallman"), \ + proper_name ("David MacKenzie") + +static bool tee_files (int nfiles, char **files, bool); + +/* If true, append to output files rather than truncating them. */ +static bool append; + +/* If true, ignore interrupts. */ +static bool ignore_interrupts; + +enum output_error + { + output_error_sigpipe, /* traditional behavior, sigpipe enabled. */ + output_error_warn, /* warn on EPIPE, but continue. */ + output_error_warn_nopipe, /* ignore EPIPE, continue. */ + output_error_exit, /* exit on any output error. */ + output_error_exit_nopipe /* exit on any output error except EPIPE. */ + }; + +static enum output_error output_error; + +static struct option const long_options[] = +{ + {"append", no_argument, nullptr, 'a'}, + {"ignore-interrupts", no_argument, nullptr, 'i'}, + {"output-error", optional_argument, nullptr, 'p'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {nullptr, 0, nullptr, 0} +}; + +static char const *const output_error_args[] = +{ + "warn", "warn-nopipe", "exit", "exit-nopipe", nullptr +}; +static enum output_error const output_error_types[] = +{ + output_error_warn, output_error_warn_nopipe, + output_error_exit, output_error_exit_nopipe +}; +ARGMATCH_VERIFY (output_error_args, output_error_types); + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + emit_try_help (); + else + { + printf (_("Usage: %s [OPTION]... [FILE]...\n"), program_name); + fputs (_("\ +Copy standard input to each FILE, and also to standard output.\n\ +\n\ + -a, --append append to the given FILEs, do not overwrite\n\ + -i, --ignore-interrupts ignore interrupt signals\n\ +"), stdout); + fputs (_("\ + -p operate in a more appropriate MODE with pipes.\n\ + --output-error[=MODE] set behavior on write error. See MODE below\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +MODE determines behavior with write errors on the outputs:\n\ + warn diagnose errors writing to any output\n\ + warn-nopipe diagnose errors writing to any output not a pipe\n\ + exit exit on error writing to any output\n\ + exit-nopipe exit on error writing to any output not a pipe\n\ +The default MODE for the -p option is 'warn-nopipe'.\n\ +With \"nopipe\" MODEs, exit immediately if all outputs become broken pipes.\n\ +The default operation when --output-error is not specified, is to\n\ +exit immediately on error writing to a pipe, and diagnose errors\n\ +writing to non pipe outputs.\n\ +"), stdout); + emit_ancillary_info (PROGRAM_NAME); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + initialize_main (&argc, &argv); + set_program_name (argv[0]); + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + append = false; + ignore_interrupts = false; + + int optc; + while ((optc = getopt_long (argc, argv, "aip", long_options, nullptr)) != -1) + { + switch (optc) + { + case 'a': + append = true; + break; + + case 'i': + ignore_interrupts = true; + break; + + case 'p': + if (optarg) + output_error = XARGMATCH ("--output-error", optarg, + output_error_args, output_error_types); + else + output_error = output_error_warn_nopipe; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + + if (ignore_interrupts) + signal (SIGINT, SIG_IGN); + + if (output_error != output_error_sigpipe) + signal (SIGPIPE, SIG_IGN); + + /* Whether to detect and close a broken pipe output. + There is no need if the input is always ready for reading. */ + bool pipe_check = ((output_error == output_error_warn_nopipe + || output_error == output_error_exit_nopipe) + && iopoll_input_ok (STDIN_FILENO)); + + /* Do *not* warn if tee is given no file arguments. + POSIX requires that it work when given no arguments. */ + + bool ok = tee_files (argc - optind, &argv[optind], pipe_check); + if (close (STDIN_FILENO) != 0) + error (EXIT_FAILURE, errno, "%s", _("standard input")); + + return ok ? EXIT_SUCCESS : EXIT_FAILURE; +} + + +/* Return the index of the first non-null descriptor after idx, + or -1 if all are null. */ + +static int +get_next_out (FILE **descriptors, int nfiles, int idx) +{ + for (idx++; idx <= nfiles; idx++) + if (descriptors[idx]) + return idx; + return -1; /* no outputs remaining */ +} + +/* Remove descriptors[i] due to write failure or broken pipe. + Return true if this indicates a reportable error. */ + +static bool +fail_output (FILE **descriptors, char **files, int i) +{ + int w_errno = errno; + bool fail = errno != EPIPE + || output_error == output_error_exit + || output_error == output_error_warn; + if (descriptors[i] == stdout) + clearerr (stdout); /* Avoid redundant close_stdout diagnostic. */ + if (fail) + { + error (output_error == output_error_exit + || output_error == output_error_exit_nopipe, + w_errno, "%s", quotef (files[i])); + } + descriptors[i] = nullptr; + return fail; +} + + +/* Copy the standard input into each of the NFILES files in FILES + and into the standard output. As a side effect, modify FILES[-1]. + Return true if successful. */ + +static bool +tee_files (int nfiles, char **files, bool pipe_check) +{ + size_t n_outputs = 0; + FILE **descriptors; + bool *out_pollable IF_LINT ( = nullptr); + char buffer[BUFSIZ]; + ssize_t bytes_read = 0; + int i; + int first_out = 0; /* idx of first non-null output in descriptors */ + bool ok = true; + char const *mode_string = + (O_BINARY + ? (append ? "ab" : "wb") + : (append ? "a" : "w")); + + xset_binary_mode (STDIN_FILENO, O_BINARY); + xset_binary_mode (STDOUT_FILENO, O_BINARY); + fadvise (stdin, FADVISE_SEQUENTIAL); + + /* Set up FILES[0 .. NFILES] and DESCRIPTORS[0 .. NFILES]. + In both arrays, entry 0 corresponds to standard output. */ + + descriptors = xnmalloc (nfiles + 1, sizeof *descriptors); + if (pipe_check) + out_pollable = xnmalloc (nfiles + 1, sizeof *out_pollable); + files--; + descriptors[0] = stdout; + if (pipe_check) + out_pollable[0] = iopoll_output_ok (fileno (descriptors[0])); + files[0] = bad_cast (_("standard output")); + setvbuf (stdout, nullptr, _IONBF, 0); + n_outputs++; + + for (i = 1; i <= nfiles; i++) + { + /* Do not treat "-" specially - as mandated by POSIX. */ + descriptors[i] = fopen (files[i], mode_string); + if (descriptors[i] == nullptr) + { + if (pipe_check) + out_pollable[i] = false; + error (output_error == output_error_exit + || output_error == output_error_exit_nopipe, + errno, "%s", quotef (files[i])); + ok = false; + } + else + { + if (pipe_check) + out_pollable[i] = iopoll_output_ok (fileno (descriptors[i])); + setvbuf (descriptors[i], nullptr, _IONBF, 0); + n_outputs++; + } + } + + while (n_outputs) + { + if (pipe_check && out_pollable[first_out]) + { + /* Monitor for input, or errors on first valid output. */ + int err = iopoll (STDIN_FILENO, fileno (descriptors[first_out]), + true); + + /* Close the output if it became a broken pipe. */ + if (err == IOPOLL_BROKEN_OUTPUT) + { + errno = EPIPE; /* behave like write produced EPIPE */ + if (fail_output (descriptors, files, first_out)) + ok = false; + n_outputs--; + first_out = get_next_out (descriptors, nfiles, first_out); + continue; + } + else if (err == IOPOLL_ERROR) + { + error (0, errno, _("iopoll error")); + ok = false; + } + } + + bytes_read = read (STDIN_FILENO, buffer, sizeof buffer); + if (bytes_read < 0 && errno == EINTR) + continue; + if (bytes_read <= 0) + break; + + /* Write to all NFILES + 1 descriptors. + Standard output is the first one. */ + for (i = 0; i <= nfiles; i++) + if (descriptors[i] + && ! fwrite_wait (buffer, bytes_read, descriptors[i])) + { + if (fail_output (descriptors, files, i)) + ok = false; + n_outputs--; + if (i == first_out) + first_out = get_next_out (descriptors, nfiles, first_out); + } + } + + if (bytes_read == -1) + { + error (0, errno, _("read error")); + ok = false; + } + + /* Close the files, but not standard output. */ + for (i = 1; i <= nfiles; i++) + if (descriptors[i] && ! fclose_wait (descriptors[i])) + { + error (0, errno, "%s", quotef (files[i])); + ok = false; + } + + free (descriptors); + if (pipe_check) + free (out_pollable); + + return ok; +} |