Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

stdio error detection: ferror versus fclose

In standard C, how do you reliably check whether all output that was written to a standard I/O stream was successfully saved to disk?

The C standard states that fclose will return 0 on success, or EOF if "any errors were detected".

But does that mean "any errors were detected during the fclose call"? Or does it mean "any errors were detected since the last call to clearerr"?

In other words, is it sufficient for a program to just check the return value of fclose, or is it also necessary to check ferror? Are there any implementations where, if ferror returns non-zero, a subsequent call to fclose might return 0?

The same goes for fflush: is it always the case that if fflush returns 0, a subsequent call to ferror will also return 0, and if fflush returns EOF, a subsequent call to ferror will return non-zero? Are there any implementations for which this is not the case?

(Of course, I'm not considering power outages, gremlins, etc. Yes, a program that requires guaranteed durability should use fsync, but that's outside the scope of standard C.)

like image 483
floppus Avatar asked May 15 '20 21:05

floppus


People also ask

What is the purpose of Ferror ()?

The ferror() function tests for an error in reading from or writing to the given stream . If an error occurs, the error indicator for the stream remains set until you close stream , call the rewind() function, or call the clearerr() function.

Which one of the following is the return type of Ferror ()?

The ferror() function returns a non-zero value if there is an error associated with fptr . If there is no error associated with fptr , the function returns zero.

Does Ferror set errno?

Return value If no error has occurred on stream , ferror returns 0. Otherwise, it returns a nonzero value. If stream is NULL , ferror invokes the invalid parameter handler, as described in Parameter validation. If execution is allowed to continue, this function sets errno to EINVAL and returns 0.


1 Answers

In the absence of a better answer:

  • the Standard tells me that ferror() returns the state of the 'error indicator' which is defined to be part of the stream state.

  • the Standard tells me a lot about when the 'error indicator' is set, but nothing about when it may be cleared -- except that clearerr() and rewind() are defined to clear it.

  • the Standard tells me nothing about what any function (other than ferror()) is expected to do if the 'error indicator' is set when the function is called.

Your questions seem to be based on the possibility that the 'error indicator' is set as soon as there is an error and only cleared when explicitly cleared (it is "latched"). In which case:

  1. ferror() would tell you that an error has occurred in some stdio function, since the fopen() (or the most recent clearerr() or rewind()).

    I don't think the Standard requires that, but it doesn't say it may not.

  2. fclose() might return an error (a) if one occurs while closing, or (b) if the 'error indicator' was already set.

    And, if so, a successful fclose() would mean that all was and has been well, since the fopen() (or the most recent clearerr() or rewind()).

    I don't think the Standard requires that, but it doesn't say it may not.

Where the Standard does not explicitly require something and does not explicitly rule something out, we have ourselves a cat which is neither alive nor dead.

In short, I don't think the Standard answers any of your questions, one way or another.

A conservative reading of the standard would be:

  • to check for errors immediately after every stdio function call, and proceed accordingly.

  • in general, after a read or write error, giving up and closing the stream is the obvious response.

    fclose() may return the current error (again), or a new error or no error at all. Where an error is to be returned I would return the original read/write error. Where errors are to be reported I would report both the original read/write error and any error returned by fclose().

  • if the decision is to keep on inputting/outputting, the 'error indicator' may (or may not) affect further functions, and may (or may not) be cleared by most further functions...

    ... so it is probably best to clearerr(), to avoid any possible confusion.

    However, I am almost convinced that fgetc() must either:

      a. give up immediately if 'error indicator' is already set (and set errno, again)

    or:

      b. clear 'error indicator' -- for if not, what does it mean if fgetc() then succeeds and returns EOF ?

    The same is true of the other get and put char and wide-char. Not quite so true of all the other functions, where the return value is not ambiguous.

  • to not expect ferror() to tell you anything about errors which may have happened before the most recent stdio function call.

    It is clear that ferror() is useful for those few functions where the error return is the same as an EOF or WEOF return. (And feof() is useful if it is possible that a character with the value EOF or WEOF may appear !).

    It is less clear whether ferror() and the 'error indicator' are useful for anything else.


FWIW: what I found in the Standard

The Standard says (§7.21.7.1) that for fgetc():

  1. If the end-of-file indicator for the input stream pointed to by stream is not set and a next character is present, the fgetc function obtains that character as an unsigned char converted to an int and advances the associated file position indicator for the stream (if defined).

Returns

  1. If the end-of-file indicator for the stream is set, or if the stream is at end-of-file, the end-of-file indicator for the stream is set and the fgetc function returns EOF. Otherwise, the fgetc function returns the next character from the input stream pointed to by stream. If a read error occurs, the error indicator for the stream is set and the fgetc function returns EOF 293).

293) An end-of-file and a read error can be distinguished by use of the feof and ferror functions.

I note that this is very clear on what it should do if the 'end-of-file indicator' is set when the function is called. In contrast, it does not say, one way or another, what fgetc() should do if the 'error indicator' is already set:

  1. should fgetc() fail immediately ?

    If so, should it set errno to the same value as when the 'error indicator' was first set ?

    However, if the 'error indicator' were previously set by (say) EINTR, it would make no sense to take any notice of it.

otherwise:

  1. should fgetc() clear the 'error indicator' if this call succeeds ?

    If not, then 'error indicator' could be deemed to be a "latched" state, indicating that at some time since it was last cleared, an error has occurred.

    Again, if the 'error indicator' were previously set by (say) EINTR, it would make no sense to leave it set. Mind you, C knows nothing of EINTR... so the implementation is free to do different things with the 'error indicator', depending on how it was set.

    AND, if fgetc() just happens to fetch a character with value EOF or just happens to hit actual EOF, then NOT clearing the 'error indicator' would be a mistake !

The Standard says (§7.21.7.3) that for fputc():

  1. The fputc function writes the character specified by c (converted to an unsigned char) to the output stream ...

Returns

  1. The fputc function returns the character written. If a write error occurs, the error indicator for the stream is set and fputc returns EOF.

And again, this does not specify what fputc() should do if the 'error indicator' is already set.

And the same applies to fgetwc() and fputwc().

Every other input/output function is defined to work "as if" they are repeated fgetc(), fputc(), fgetwc() and fputwc()`.

The Standard says (§7.21.10.3) that for ferror():

  1. The ferror function tests the error indicator for the stream pointed to by stream.

Returns

  1. The ferror function returns nonzero if and only if the error indicator is set for stream.

That's it. Footnote 293 above is the most concrete guide we have for how ferror() and the 'error indicator' should be used.

fflush() (§7.21.5.2), fseek() (§7.21.9.2) and fsetpos() (§7.21.9.3) are all defined to set the 'error indicator' in the event of an error.

rewind() (§7.21.9.5) and clearerr() (§7.21.10.1) are defined to clear the 'error indicator'.

I found no other references to the 'error indicator'.

like image 50
Chris Hall Avatar answered Oct 09 '22 15:10

Chris Hall