Fix error handling in closefile and closeall

printstat and awkprintf are very clear: print statement errors
are fatal.

In Jan 2020 [1], to prevent fatal print errors from masquerading
as fclose warnings, every WARNING in closefile and closeall became
FATAL. This broke awk's close and getline functions.

close no longer returns if there's an error, unless the stream
doesn't exist.

getline read errors still return -1, but they are no longer
ignorable. Eventually, one of the closing functions will inspect the
stream with ferror and call FATAL.

In Jul 2020 [2], fatal stdout write errors which had been detectable by
closefile for a few months became invisible, a consequence of switching
standard streams from fclose (which reports flush errors) to freopen
(which ignores them). The Jan 2020 changes which broke getline and
close were themselves partially broken.

The solution is to finish printing before closing. That is to flush
and ferror every stream opened for writing before calling fclose,
pclose, or freopen. A failure to write print statement data is
fatal. A failure to close a flushed stream is a warning. They must
be handled separately.

Every redirected print statement is finished in printstat or awkprintf.

The same is not true of unredirected print statements. To finish
these, stdout must be flushed at some point after the final such
statement. Any problem with that flush is fatal.

Though only stdout needs it, let's defensively finish every stream
opened for writing, so this bug won't recur if someone changes how
redirected streams are flushed.

Write errors on stderr by the implementation are never fatal. When
closing, we only warn of them. Write errors from an application
attempting a redirected print to /dev/stderr are as immediately fatal
as every other redirected print statement.

[1] fed1a562c3
[2] b82b649aa6
This commit is contained in:
Miguel Pineiro Jr 2021-12-08 21:37:28 -05:00
parent 1d780ac4f8
commit 99f6a43296
3 changed files with 26 additions and 10 deletions

5
FIXES
View File

@ -25,6 +25,11 @@ THIS SOFTWARE.
This file lists all bug fixes, changes, etc., made since the AWK book This file lists all bug fixes, changes, etc., made since the AWK book
was sent to the printers in August, 1987. was sent to the printers in August, 1987.
December 8, 2021:
The error handling in closefile and closeall was mangled. Long
standing warnings had been made fatal and some fatal errors went
undetected. Thanks to Miguel Pineiro Jr. <mpj@pineiro.cc>.
Nov 03, 2021: Nov 03, 2021:
getline accesses uninitialized data after getrec() getline accesses uninitialized data after getrec()
returns 0 on EOF and leaves the contents of buf unchanged. returns 0 on EOF and leaves the contents of buf unchanged.

2
main.c
View File

@ -22,7 +22,7 @@ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE. THIS SOFTWARE.
****************************************************************/ ****************************************************************/
const char *version = "version 20211103"; const char *version = "version 20211208";
#define DEBUG #define DEBUG
#include <stdio.h> #include <stdio.h>

29
run.c
View File

@ -1872,8 +1872,14 @@ Cell *closefile(Node **a, int n)
for (i = 0; i < nfiles; i++) { for (i = 0; i < nfiles; i++) {
if (!files[i].fname || strcmp(x->sval, files[i].fname) != 0) if (!files[i].fname || strcmp(x->sval, files[i].fname) != 0)
continue; continue;
if (ferror(files[i].fp)) fflush(files[i].fp);
FATAL("i/o error occurred on %s", files[i].fname); if (ferror(files[i].fp)) {
if ((files[i].mode == GT && files[i].fp != stderr)
|| files[i].mode == '|')
FATAL("write error on %s", files[i].fname);
else
WARNING("i/o error occurred on %s", files[i].fname);
}
if (files[i].fp == stdin || files[i].fp == stdout || if (files[i].fp == stdin || files[i].fp == stdout ||
files[i].fp == stderr) files[i].fp == stderr)
stat = freopen("/dev/null", "r+", files[i].fp) == NULL; stat = freopen("/dev/null", "r+", files[i].fp) == NULL;
@ -1882,7 +1888,7 @@ Cell *closefile(Node **a, int n)
else else
stat = fclose(files[i].fp) == EOF; stat = fclose(files[i].fp) == EOF;
if (stat) if (stat)
FATAL("i/o error occurred closing %s", files[i].fname); WARNING("i/o error occurred closing %s", files[i].fname);
if (i > 2) /* don't do /dev/std... */ if (i > 2) /* don't do /dev/std... */
xfree(files[i].fname); xfree(files[i].fname);
files[i].fname = NULL; /* watch out for ref thru this */ files[i].fname = NULL; /* watch out for ref thru this */
@ -1903,18 +1909,23 @@ void closeall(void)
for (i = 0; i < nfiles; i++) { for (i = 0; i < nfiles; i++) {
if (! files[i].fp) if (! files[i].fp)
continue; continue;
if (ferror(files[i].fp)) fflush(files[i].fp);
FATAL( "i/o error occurred on %s", files[i].fname ); if (ferror(files[i].fp)) {
if (files[i].fp == stdin) if ((files[i].mode == GT && files[i].fp != stderr)
|| files[i].mode == '|')
FATAL("write error on %s", files[i].fname);
else
WARNING("i/o error occurred on %s", files[i].fname);
}
if (files[i].fp == stdin || files[i].fp == stdout ||
files[i].fp == stderr)
continue; continue;
if (files[i].mode == '|' || files[i].mode == LE) if (files[i].mode == '|' || files[i].mode == LE)
stat = pclose(files[i].fp) == -1; stat = pclose(files[i].fp) == -1;
else if (files[i].fp == stdout || files[i].fp == stderr)
stat = fflush(files[i].fp) == EOF;
else else
stat = fclose(files[i].fp) == EOF; stat = fclose(files[i].fp) == EOF;
if (stat) if (stat)
FATAL( "i/o error occurred while closing %s", files[i].fname ); WARNING("i/o error occurred while closing %s", files[i].fname);
} }
} }