all: avoid repeated diagnostic upon write error

* cfg.mk (sc_some_programs_must_avoid_exit_failure): Adjust to
avoid false positive.
(sc_prohibit_exit_write_error): A new syntax check to prohibit
open coding error(..., "write error"); instead directing to use...
* src/system.h (write_error): ... a new function to clear stdout errors
before we explicitly diagnose a write error and exit.
* src/basenc.c: Use write_error() to ensure no repeated diagnostics.
* src/cat.c: Likewise.
* src/expand.c: Likewise.
* src/factor.c: Likewise.
* src/paste.c: Likewise.
* src/seq.c: Likewise.
* src/shuf.c: Likewise.
* src/split.c: Likewise.
* src/tail.c: Likewise.
* src/tr.c: Likewise.
* src/unexpand.c: Likewise.
* tests/misc/write-errors.sh: Remove TODOs for the fixed utilities:
expand, factor, paste, shuf, tr, unexpand.
This commit is contained in:
Pádraig Brady 2023-07-15 20:41:44 +01:00
parent ef47b928d0
commit 0b2ff7637f
14 changed files with 48 additions and 53 deletions

9
cfg.mk
View File

@ -503,6 +503,12 @@ sc_prohibit_man_see_also_period:
{ echo '$(ME): do not end "SEE ALSO" section with a period' \
1>&2; exit 1; } || :
sc_prohibit_exit_write_error:
@prohibit='error.*EXIT_FAILURE.*write error' \
in_vc_files='\.c$$' \
halt='Use write_error() instead' \
$(_sc_search_regexp)
# Don't use "indent-tabs-mode: nil" anymore. No longer needed.
sc_prohibit_emacs__indent_tabs_mode__setting:
@prohibit='^( *[*#] *)?indent-tabs-mode:' \
@ -620,7 +626,8 @@ sc_prohibit_test_empty:
sc_some_programs_must_avoid_exit_failure:
@cd $(srcdir) \
&& grep -nw EXIT_FAILURE \
$$(git grep -El '[^T]_FAILURE|EXIT_CANCELED' $(srcdir)/src) \
$$(git grep -El '[^T]_FAILURE|EXIT_CANCELED' src/) \
| grep -v '^src/system\.h:' \
| grep -vE '= EXIT_FAILURE|return .* \?' | grep . \
&& { echo '$(ME): do not use EXIT_FAILURE in the above' \
1>&2; exit 1; } || :

View File

@ -924,7 +924,7 @@ wrap_write (char const *buffer, idx_t len,
{
/* Simple write. */
if (fwrite (buffer, 1, len, stdout) < len)
error (EXIT_FAILURE, errno, _("write error"));
write_error ();
}
else
for (idx_t written = 0; written < len; )
@ -934,13 +934,13 @@ wrap_write (char const *buffer, idx_t len,
if (to_write == 0)
{
if (fputc ('\n', out) == EOF)
error (EXIT_FAILURE, errno, _("write error"));
write_error ();
*current_column = 0;
}
else
{
if (fwrite (buffer + written, 1, to_write, stdout) < to_write)
error (EXIT_FAILURE, errno, _("write error"));
write_error ();
*current_column += to_write;
written += to_write;
}
@ -997,7 +997,7 @@ do_encode (FILE *in, char const *infile, FILE *out, idx_t wrap_column)
/* When wrapping, terminate last line. */
if (wrap_column && current_column > 0 && fputc ('\n', out) == EOF)
error (EXIT_FAILURE, errno, _("write error"));
write_error ();
if (ferror (in))
error (EXIT_FAILURE, errno, _("read error"));
@ -1060,7 +1060,7 @@ do_decode (FILE *in, char const *infile, FILE *out, bool ignore_garbage)
ok = base_decode_ctx (&ctx, inbuf, (k == 0 ? sum : 0), outbuf, &n);
if (fwrite (outbuf, 1, n, out) < n)
error (EXIT_FAILURE, errno, _("write error"));
write_error ();
if (!ok)
error (EXIT_FAILURE, 0, _("invalid input"));

View File

@ -178,7 +178,7 @@ simple_cat (char *buf, idx_t bufsize)
/* Write this block out. */
if (full_write (STDOUT_FILENO, buf, n_read) != n_read)
error (EXIT_FAILURE, errno, _("write error"));
write_error ();
}
}
@ -193,7 +193,7 @@ write_pending (char *outbuf, char **bpout)
if (0 < n_write)
{
if (full_write (STDOUT_FILENO, outbuf, n_write) != n_write)
error (EXIT_FAILURE, errno, _("write error"));
write_error ();
*bpout = outbuf;
}
}
@ -257,7 +257,7 @@ cat (char *inbuf, idx_t insize, char *outbuf, idx_t outsize,
do
{
if (full_write (STDOUT_FILENO, wp, outsize) != outsize)
error (EXIT_FAILURE, errno, _("write error"));
write_error ();
wp += outsize;
remaining_bytes = bpout - wp;
}
@ -794,7 +794,7 @@ main (int argc, char **argv)
if (pending_cr)
{
if (full_write (STDOUT_FILENO, "\r", 1) != 1)
error (EXIT_FAILURE, errno, _("write error"));
write_error ();
}
if (have_read_stdin && close (STDIN_FILENO) < 0)

View File

@ -144,7 +144,7 @@ expand (void)
while (++column < next_tab_column)
if (putchar (' ') < 0)
error (EXIT_FAILURE, errno, _("write error"));
write_error ();
c = ' ';
}
@ -169,7 +169,7 @@ expand (void)
return;
if (putchar (c) < 0)
error (EXIT_FAILURE, errno, _("write error"));
write_error ();
}
while (c != '\n');
}

View File

@ -2361,7 +2361,7 @@ lbuf_flush (void)
{
size_t size = lbuf.end - lbuf.buf;
if (full_write (STDOUT_FILENO, lbuf.buf, size) != size)
error (EXIT_FAILURE, errno, "%s", _("write error"));
write_error ();
lbuf.end = lbuf.buf;
}

View File

@ -152,14 +152,6 @@ collapse_escapes (char const *strptr)
return backslash_at_end ? 1 : 0;
}
/* Report a write error and exit. */
static void
write_error (void)
{
error (EXIT_FAILURE, errno, _("write error"));
}
/* Output a single byte, reporting any write errors. */
static inline void

View File

@ -285,14 +285,6 @@ long_double_format (char const *fmt, struct layout *layout)
}
}
static void
io_error (void)
{
/* FIXME: consider option to silently ignore errno=EPIPE */
clearerr (stdout);
error (EXIT_FAILURE, errno, _("write error"));
}
/* Actually print the sequence of numbers in the specified range, with the
given or default stepping and format. */
@ -311,7 +303,7 @@ print_numbers (char const *fmt, struct layout layout,
{
long double x0 = x;
if (printf (fmt, x) < 0)
io_error ();
write_error ();
if (out_of_range)
break;
x = first + i * step;
@ -358,11 +350,11 @@ print_numbers (char const *fmt, struct layout layout,
}
if (fputs (separator, stdout) == EOF)
io_error ();
write_error ();
}
if (fputs (terminator, stdout) == EOF)
io_error ();
write_error ();
}
}
@ -539,7 +531,7 @@ seq_fast (char const *a, char const *b, uintmax_t step)
if (buf_end - (p_len + 1) < bufp)
{
if (fwrite (buf, bufp - buf, 1, stdout) != 1)
io_error ();
write_error ();
bufp = buf;
}
}
@ -547,7 +539,7 @@ seq_fast (char const *a, char const *b, uintmax_t step)
/* Write any remaining buffered output, and the terminator. */
*bufp++ = *terminator;
if (fwrite (buf, bufp - buf, 1, stdout) != 1)
io_error ();
write_error ();
}
if (ok)

View File

@ -597,7 +597,7 @@ main (int argc, char **argv)
}
if (i != 0)
error (EXIT_FAILURE, errno, _("write error"));
write_error ();
main_exit (EXIT_SUCCESS);
}

View File

@ -957,7 +957,7 @@ lines_chunk_split (intmax_t k, intmax_t n, char *buf, idx_t bufsize,
large chunks from an existing file, so it's more efficient
to write out directly. */
if (full_write (STDOUT_FILENO, bp, to_write) != to_write)
error (EXIT_FAILURE, errno, "%s", _("write error"));
write_error ();
}
else if (! k)
cwrite (new_file_flag, bp, to_write);
@ -1214,12 +1214,11 @@ lines_rr (intmax_t k, intmax_t n, char *buf, idx_t bufsize, of_t **filesp)
if (line_no == k && unbuffered)
{
if (full_write (STDOUT_FILENO, bp, to_write) != to_write)
error (EXIT_FAILURE, errno, "%s", _("write error"));
write_error ();
}
else if (line_no == k && fwrite (bp, to_write, 1, stdout) != 1)
{
clearerr (stdout); /* To silence close_stdout(). */
error (EXIT_FAILURE, errno, "%s", _("write error"));
write_error ();
}
if (next)
line_no = (line_no == n) ? 1 : line_no + 1;

View File

@ -762,6 +762,17 @@ The following directory is part of the cycle:\n %s\n"), \
} \
while (0)
/* exit with a _single_ "write error" diagnostic. */
static inline void
write_error (void)
{
int saved_errno = errno;
fflush (stdout); /* Ensure nothing buffered that might induce an error. */
clearerr (stdout); /* To avoid extraneous diagnostic from close_stdout. */
error (EXIT_FAILURE, saved_errno, _("write error"));
}
/* Like stpncpy, but do ensure that the result is NUL-terminated,
and do not NUL-pad out to LEN. I.e., when strnlen (src, len) == len,
this function writes a NUL byte into dest[len]. Thus, the length

View File

@ -1263,7 +1263,7 @@ tail_forever (struct File_spec *f, size_t n_files, double sleep_interval)
}
if ((!any_input || blocking) && fflush (stdout) != 0)
error (EXIT_FAILURE, errno, _("write error"));
write_error ();
check_output_alive ();
@ -1417,7 +1417,7 @@ check_fspec (struct File_spec *fspec, struct File_spec **prev_fspec)
{
*prev_fspec = fspec;
if (fflush (stdout) != 0)
error (EXIT_FAILURE, errno, _("write error"));
write_error ();
}
}
@ -2454,7 +2454,7 @@ main (int argc, char **argv)
tail_forever_inotify flushes only after writing,
not before reading. */
if (fflush (stdout) != 0)
error (EXIT_FAILURE, errno, _("write error"));
write_error ();
Hash_table *ht;
tail_forever_inotify (wd, F, n_files, sleep_interval, &ht);

View File

@ -1571,7 +1571,7 @@ squeeze_filter (char *buf, size_t size, size_t (*reader) (char *, size_t))
}
if (out_len > 0
&& fwrite (&buf[begin], 1, out_len, stdout) != out_len)
error (EXIT_FAILURE, errno, _("write error"));
write_error ();
}
if (char_to_squeeze != NOT_A_CHAR)
@ -1797,7 +1797,7 @@ main (int argc, char **argv)
if (nr == 0)
break;
if (fwrite (io_buf, 1, nr, stdout) != nr)
error (EXIT_FAILURE, errno, _("write error"));
write_error ();
}
}
else if (squeeze_repeats && delete && non_option_args == 2)
@ -1889,7 +1889,7 @@ main (int argc, char **argv)
if (bytes_read == 0)
break;
if (fwrite (io_buf, 1, bytes_read, stdout) != bytes_read)
error (EXIT_FAILURE, errno, _("write error"));
write_error ();
}
}
}

View File

@ -228,7 +228,7 @@ unexpand (void)
if (pending > 1 && one_blank_before_tab_stop)
pending_blank[0] = '\t';
if (fwrite (pending_blank, 1, pending, stdout) != pending)
error (EXIT_FAILURE, errno, _("write error"));
write_error ();
pending = 0;
one_blank_before_tab_stop = false;
}
@ -244,7 +244,7 @@ unexpand (void)
}
if (putchar (c) < 0)
error (EXIT_FAILURE, errno, _("write error"));
write_error ();
}
while (c != '\n');
}

View File

@ -31,9 +31,7 @@ cat /dev/zero
# TODO: cut -z -c1- /dev/zero
dd if=/dev/zero
expand /dev/zero
# TODO: avoid double error from expand
factor --version; yes 1 | factor
# TODO: avoid double error from factor
# TODO: fmt /dev/zero
# TODO: fold -b /dev/zero
head -z -n-1 /dev/zero
@ -42,16 +40,12 @@ head -z -n-1 /dev/zero
# TODO: numfmt --version; yes 1 | numfmt
# TODO: od -v /dev/zero
paste /dev/zero
# TODO: avoid double error from paste
# TODO: pr /dev/zero
seq inf
# TODO: avoid double error from shuf
tail -n+1 -z /dev/zero
tee < /dev/zero
tr . . < /dev/zero
# TODO: avoid double error from tr
unexpand /dev/zero
# TODO: avoid double error from unexpand
# TODO: uniq -z -D /dev/zero
yes
" |