From 99f6a432969cd42aa1dcba580eefe9d23601e51b Mon Sep 17 00:00:00 2001 From: Miguel Pineiro Jr Date: Wed, 8 Dec 2021 21:37:28 -0500 Subject: [PATCH] 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] fed1a562c3d1f3cf3cac0dd1413679191ac43002 [2] b82b649aa65fbdff45869d1fd6d72fd2b85946cd --- FIXES | 5 +++++ main.c | 2 +- run.c | 29 ++++++++++++++++++++--------- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/FIXES b/FIXES index f6eb98c..8e49fe9 100644 --- a/FIXES +++ b/FIXES @@ -25,6 +25,11 @@ THIS SOFTWARE. This file lists all bug fixes, changes, etc., made since the AWK book 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. . + Nov 03, 2021: getline accesses uninitialized data after getrec() returns 0 on EOF and leaves the contents of buf unchanged. diff --git a/main.c b/main.c index ee8b82b..986f1a3 100644 --- a/main.c +++ b/main.c @@ -22,7 +22,7 @@ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ****************************************************************/ -const char *version = "version 20211103"; +const char *version = "version 20211208"; #define DEBUG #include diff --git a/run.c b/run.c index 6bff3e1..915ce72 100644 --- a/run.c +++ b/run.c @@ -1872,8 +1872,14 @@ Cell *closefile(Node **a, int n) for (i = 0; i < nfiles; i++) { if (!files[i].fname || strcmp(x->sval, files[i].fname) != 0) continue; - if (ferror(files[i].fp)) - FATAL("i/o error occurred on %s", files[i].fname); + fflush(files[i].fp); + 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 || files[i].fp == stderr) stat = freopen("/dev/null", "r+", files[i].fp) == NULL; @@ -1882,7 +1888,7 @@ Cell *closefile(Node **a, int n) else stat = fclose(files[i].fp) == EOF; 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... */ xfree(files[i].fname); files[i].fname = NULL; /* watch out for ref thru this */ @@ -1903,18 +1909,23 @@ void closeall(void) for (i = 0; i < nfiles; i++) { if (! files[i].fp) continue; - if (ferror(files[i].fp)) - FATAL( "i/o error occurred on %s", files[i].fname ); - if (files[i].fp == stdin) + fflush(files[i].fp); + 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 || + files[i].fp == stderr) continue; if (files[i].mode == '|' || files[i].mode == LE) stat = pclose(files[i].fp) == -1; - else if (files[i].fp == stdout || files[i].fp == stderr) - stat = fflush(files[i].fp) == EOF; else stat = fclose(files[i].fp) == EOF; if (stat) - FATAL( "i/o error occurred while closing %s", files[i].fname ); + WARNING("i/o error occurred while closing %s", files[i].fname); } }