From: Collin Funk Date: Sun, 15 Mar 2026 03:21:53 +0000 (-0700) Subject: tee: prefer file descriptors over streams X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9b855166ed3366f0d335477c2bfa99e5cf9b800f;p=thirdparty%2Fcoreutils.git tee: prefer file descriptors over streams We disable buffering on the streams anyways, so we were effectively calling the write system call previously despite using streams. * src/iopoll.h (fclose_wait, fwrite_wait): Remove declarations. (close_wait, write_wait): Add declarations. * src/iopoll.c (fwait_for_nonblocking_write, fclose_wait, fwrite_wait): Remove functions. (wait_for_nonblocking_write): New function based on fwait_for_nonblocking_write. (close_wait): New function based on fclose_wait. (write_wait): New function based on fwrite_wait. * src/tee.c: Include fcntl--.h. Don't include stdio--.h. (get_next_out): Operate on file descriptors instead of streams. (fail_output): Likewise. Remove clearerr call since we no longer call fwrite on stdout. (tee_files): Operate on file descriptors instead of streams. Remove calls to setvbuf. --- diff --git a/src/iopoll.c b/src/iopoll.c index 5d87839a3b..ebeebf85c0 100644 --- a/src/iopoll.c +++ b/src/iopoll.c @@ -177,62 +177,53 @@ iopoll_output_ok (int fdout) Return true, if EAGAIN has been successfully handled. */ static bool -fwait_for_nonblocking_write (FILE *f) +wait_for_nonblocking_write (int fd) { if (! IS_EAGAIN (errno)) /* non-recoverable write error */ return false; - int fd = fileno (f); - if (fd == -1) - goto fail; - /* wait for the file descriptor to become writable */ if (iopoll_internal (-1, fd, true, false) != 0) - goto fail; + { + errno = EAGAIN; + return false; + } /* successfully waited for the descriptor to become writable */ - clearerr (f); return true; - -fail: - errno = EAGAIN; - return false; } - -/* wrapper for fclose() that also waits for F if non blocking. */ +/* wrapper for close() that also waits for FD if non blocking. */ extern bool -fclose_wait (FILE *f) +close_wait (int fd) { - for (;;) - { - if (fflush (f) == 0) - break; - - if (! fwait_for_nonblocking_write (f)) - break; - } - - return fclose (f) == 0; + while (wait_for_nonblocking_write (fd)) + ; + return close (fd) == 0; } -/* wrapper for fwrite() that also waits for F if non blocking. */ +/* wrapper for write() that also waits for FD if non blocking. */ extern bool -fwrite_wait (char const *buf, ssize_t size, FILE *f) +write_wait (int fd, void const *buffer, size_t size) { - for (;;) + unsigned char const *buf = buffer; + + while (true) { - const size_t written = fwrite (buf, 1, size, f); + ssize_t written = write (fd, buf, size); + if (written < 0) + written = 0; + size -= written; affirm (size >= 0); if (size <= 0) /* everything written */ return true; - if (! fwait_for_nonblocking_write (f)) + if (! wait_for_nonblocking_write (fd)) return false; buf += written; diff --git a/src/iopoll.h b/src/iopoll.h index 0177a4d25c..1711fdab92 100644 --- a/src/iopoll.h +++ b/src/iopoll.h @@ -5,5 +5,5 @@ int iopoll (int fdin, int fdout, bool block); bool iopoll_input_ok (int fdin); bool iopoll_output_ok (int fdout); -bool fclose_wait (FILE *f); -bool fwrite_wait (char const *buf, ssize_t size, FILE *f); +bool close_wait (int fd); +bool write_wait (int fd, void const *buffer, size_t size); diff --git a/src/tee.c b/src/tee.c index ffd4b20ec9..32a18e340c 100644 --- a/src/tee.c +++ b/src/tee.c @@ -24,8 +24,8 @@ #include "system.h" #include "argmatch.h" #include "fadvise.h" +#include "fcntl--.h" #include "iopoll.h" -#include "stdio--.h" #include "xbinary-io.h" #include "iopoll.h" @@ -194,10 +194,10 @@ main (int argc, char **argv) ATTRIBUTE_PURE static int -get_next_out (FILE **descriptors, int nfiles, int idx) +get_next_out (int *descriptors, int nfiles, int idx) { for (idx++; idx <= nfiles; idx++) - if (descriptors[idx]) + if (0 <= descriptors[idx]) return idx; return -1; /* no outputs remaining */ } @@ -206,21 +206,19 @@ get_next_out (FILE **descriptors, int nfiles, int idx) Return true if this indicates a reportable error. */ static bool -fail_output (FILE **descriptors, char **files, int i) +fail_output (int *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] = NULL; + descriptors[i] = -1; return fail; } @@ -233,16 +231,13 @@ static bool tee_files (int nfiles, char **files, bool pipe_check) { size_t n_outputs = 0; - FILE **descriptors; + int *descriptors; bool *out_pollable IF_LINT ( = NULL); char buffer[BUFSIZ]; ssize_t bytes_read = 0; 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")); + int flags = O_WRONLY | O_CREAT | O_BINARY | (append ? O_APPEND : O_TRUNC); xset_binary_mode (STDIN_FILENO, O_BINARY); xset_binary_mode (STDOUT_FILENO, O_BINARY); @@ -255,18 +250,17 @@ tee_files (int nfiles, char **files, bool pipe_check) if (pipe_check) out_pollable = xnmalloc (nfiles + 1, sizeof *out_pollable); files--; - descriptors[0] = stdout; + descriptors[0] = STDOUT_FILENO; if (pipe_check) - out_pollable[0] = iopoll_output_ok (fileno (descriptors[0])); + out_pollable[0] = iopoll_output_ok (descriptors[0]); files[0] = bad_cast (_("standard output")); - setvbuf (stdout, NULL, _IONBF, 0); n_outputs++; for (int i = 1; i <= nfiles; i++) { /* Do not treat "-" specially - as mandated by POSIX. */ - descriptors[i] = fopen (files[i], mode_string); - if (descriptors[i] == NULL) + descriptors[i] = open (files[i], flags, MODE_RW_UGO); + if (descriptors[i] < 0) { if (pipe_check) out_pollable[i] = false; @@ -278,8 +272,7 @@ tee_files (int nfiles, char **files, bool pipe_check) else { if (pipe_check) - out_pollable[i] = iopoll_output_ok (fileno (descriptors[i])); - setvbuf (descriptors[i], NULL, _IONBF, 0); + out_pollable[i] = iopoll_output_ok (descriptors[i]); n_outputs++; } } @@ -289,8 +282,7 @@ tee_files (int nfiles, char **files, bool pipe_check) 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); + int err = iopoll (STDIN_FILENO, descriptors[first_out], true); /* Close the output if it became a broken pipe. */ if (err == IOPOLL_BROKEN_OUTPUT) @@ -318,8 +310,8 @@ tee_files (int nfiles, char **files, bool pipe_check) /* Write to all NFILES + 1 descriptors. Standard output is the first one. */ for (int i = 0; i <= nfiles; i++) - if (descriptors[i] - && ! fwrite_wait (buffer, bytes_read, descriptors[i])) + if (0 <= descriptors[i] + && ! write_wait (descriptors[i], buffer, bytes_read)) { if (fail_output (descriptors, files, i)) ok = false; @@ -337,7 +329,7 @@ tee_files (int nfiles, char **files, bool pipe_check) /* Close the files, but not standard output. */ for (int i = 1; i <= nfiles; i++) - if (descriptors[i] && ! fclose_wait (descriptors[i])) + if (0 <= descriptors[i] && ! close_wait (descriptors[i])) { error (0, errno, "%s", quotef (files[i])); ok = false;