[comp.lang.c] perror/strerror after fopen

chris@mimsy.umd.edu (Chris Torek) (11/29/90)

>In article <1990Nov28.152146.19560@ssd.kodak.com> weimer@ssd.kodak.com
suggests error reporting of the form:
>>    if ((fp[i]=fopen(file_name[i],"r+")) <= 0)
>>        perror("Error opening file");

In article <14603@smoke.brl.mil> gwyn@smoke.brl.mil (Doug Gwyn) writes:
>Please don't do this; on UNIX, perror() will report the reason for the last
>SYSTEM CALL failure, not the reason for failure of a library routine such as
>fopen().  Sometimes there is a close relation between these but at other
>times there is not, leading to such absurd diagnostics as "Error opening
>file: not a tty".

Some of us regard this as a bug, rather than a feature.  I am not
particularly happy with the whole idea of a global `error number',
but there it is.  To make the best of it, the 4.4BSD C library
guarantees that errno *is* meaningful after an error.

It is, of course, true that errno has no portable meaning after an
fopen failure.  Still, you could print something like:

	(void) fprintf(stderr,
	    "%s: error opening file %s for read-update\n"
	    "\tlast system error was %s\n",
	    progname, file_name[i], strerror(errno));

This gets all the facts across, and is helpful when trying to figure
out *why* the open failed.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 405 2750)
Domain:	chris@cs.umd.edu	Path:	uunet!mimsy!chris

dkeisen@Gang-of-Four.Stanford.EDU (Dave Eisen) (11/30/90)

>In article <14603@smoke.brl.mil> gwyn@smoke.brl.mil (Doug Gwyn) writes:
>>Please don't do this; on UNIX, perror() will report the reason for the last
>>SYSTEM CALL failure, not the reason for failure of a library routine such as
>>fopen().  Sometimes there is a close relation between these but at other
>>times there is not, leading to such absurd diagnostics as "Error opening
>>file: not a tty".
>


In article <28078@mimsy.umd.edu> chris@mimsy.umd.edu (Chris Torek) writes:
>Some of us regard this as a bug, rather than a feature.  I am not
>particularly happy with the whole idea of a global `error number',
>but there it is.


And a surprisingly easy bug to deal with. I can't imagine writing
library code that didn't make sure errno (and other appropriate
global error numbers) were set to the most reasonable value possible.

I never saw this as a horrible extra burden in writing the code; I
never saw it as optional, either.




--
Dave Eisen                      	    dkeisen@Gang-of-Four.Stanford.EDU
1447 N. Shoreline Blvd.
Mountain View, CA 94043           
(415) 967-5644

ftw@quasar..westford.ccur.com (Farrell Woods) (12/03/90)

In article <14603@smoke.brl.mil> gwyn@smoke.brl.mil (Doug Gwyn) writes:
>Please don't do this; on UNIX, perror() will report the reason for the last
>SYSTEM CALL failure, not the reason for failure of a library routine such as
>fopen().

In article <28078@mimsy.umd.edu> chris@mimsy.umd.edu (Chris Torek) writes:
>Some of us regard this as a bug, rather than a feature.  I am not
>particularly happy with the whole idea of a global `error number',
>but there it is.

In article <1990Nov29.164552.452@Neon.Stanford.EDU> dkeisen@Gang-of-Four.Stanford.EDU (Dave Eisen) writes:
>And a surprisingly easy bug to deal with. I can't imagine writing
>library code that didn't make sure errno (and other appropriate
>global error numbers) were set to the most reasonable value possible.

From what I've read here and elsewhere recently, errno is basically
overloaded.  This is especially true with anything defined in <math.h>,
where those functions must modify errno for certain error conditions.

Bill Plauger gives an interesting discussion of this subject in the
current issue of the _C Users Journal_.
--
Farrell T. Woods				Voice:  (508) 392-2471
Concurrent Computer Corporation			Domain: ftw@westford.ccur.com
1 Technology Way				uucp:  ...!uunet!masscomp!ftw
Westford, MA 01886				"I can't drive...fifty-five!"

scs@adam.mit.edu (Steve Summit) (12/06/90)

In article <14603@smoke.brl.mil> gwyn@smoke.brl.mil (Doug Gwyn) writes:
>In article <1990Nov28.152146.19560@ssd.kodak.com> weimer@ssd.kodak.com (Gary Weimer) writes:
>>    if ((fp[i]=fopen(file_name[i],"r+")) <= 0)
>>        perror("Error opening file");
>
>Please don't do this; on UNIX, perror() will report the reason for the last
>SYSTEM CALL failure, not the reason for failure of a library routine such as
>fopen().  Sometimes there is a close relation between these but at other
>times there is not, leading to such absurd diagnostics as "Error opening
>file: not a tty".

I have to disagree with Doug here.  The "close relation" is much
more likely than the "absurd diagnostic."  I find it more absurd,
and downright frustrating to boot, when a program prints

	Error opening file
or
	Error opening file /usr/lib/blort/piffle

with no indication whatsoever of what the problem was.

A generally agreed upon set of useful information which should be
included in any error message is:

     1.	name of program reporting error
     2.	pathname (or other pertinent argument) to failing system
	call (or library routine)
     3.	perror text, if appropriate
     4.	file name and line number of source or other input file
	being read, if applicable

Even in the presence of good standards, programming is still a
pragmatic activity, and it is much more important that error
messages include pertinent information (such as the above) most
of the time than that they are strictly conforming or never print
an occasional oddity such as "not a typewriter."  There are other
ways to reduce or eliminate occasional incorrect perror messages;
read on.

In article <WNW7ZR7@xds13.ferranti.com> peter@ficc.ferranti.com (Peter da Silva) writes:
>In every case where I've got the source to a program and have figured out
>where something like that came from it's been one of the two following
>cases:
>	if(!(fp = fopen(...))) {
>		fprintf(stderr, "%s: ", argv[0]);
>		perror(...);
>	}
>(where the fprintf stomped on errno when deciding what buffering to do) or:
[other case deleted]

This is a highly unfortunate case.  I believe that stderr should
never be buffered; I'll discuss the tradeoffs in another article.
I've written code like this, thinking that the only possible
error in the fprintf call was unwritability of stderr, in which
case the perror wasn't likely to do much good anyway.
Unfortunately, ANSI X3.159 section 4.9.3 says that "the standard
error stream is not fully buffered;" which means that it can be
line buffered, which means that Peter's objection is legitimate.
(Technically, of course, X3.159 allows fprintf to step on errno
for any reason, buffering decisions or otherwise.)  In any case,
a much better alternative to fprintf/perror, which I have used
for some time in serious programs, appears later in this article.

In article <28078@mimsy.umd.edu> chris@mimsy.umd.edu (Chris Torek) writes:
>Some of us regard this as a bug, rather than a feature.
>To make the best of it, the 4.4BSD C library
>guarantees that errno *is* meaningful after an error.

As Posix requires; see below.

>It is, of course, true that errno has no portable meaning after an
>fopen failure.  Still, you could print something like:
>	(void) fprintf(stderr,
>	    "%s: error opening file %s for read-update\n"
>	    "\tlast system error was %s\n",
>	    progname, file_name[i], strerror(errno));
>This gets all the facts across, and is helpful when trying to figure
>out *why* the open failed.

Which I claim is a requirement (of good code).  The "last system
error" dodge is useful if you're seriously worried about fopen
_not_ setting errno appropriately on failure.

In article <4573@oasys.dt.navy.mil> stuart@oasys.dt.navy.mil (Stuart Pearlman) writes:
>Is perror() specified in the ansi C standard?  I avoid using perror()
>after library routines such as fopen() because they can fail for other
>reasons besides the system calls they make failing.  Are any of the
>library functions specified in the standard guaranteed to set errno?
>(Is errno even guaranteed to exist?)  What about posix 1003.1?  Does
>it specify what functions you can call perror() after?

In my experience, the only fopen failure not accurately reflected
by errno is insufficient FILE structure availability.  Once upon a
time, there were typically 20 low-level, int fd's available, and
20 FILEs, so this essentially never happened.  Lately, some
systems have increased the availability of int fd's without
allocating more FILEs; this is a bug, or at least a quality-of-
implementation issue.  Modern stdio implementations avoid fixed
FILE tables, replacing (or supplanting) them with malloc'ed
FILEs; this makes running out of FILEs quite difficult (unless,
of course, malloc runs out of memory).

There is no particular reason why fopen couldn't set errno to
EMFILE ("too many open files").  fopen implementations often do
not, partly because of a general reluctance to play with errno at
all (preferring always leaving it to contain the value from the
last failed syscall, which is usually appropriate), and partly
because EMFILE is theoretically supposed to mean that it is
low-level, int fd's that have been used up (by various system
calls), and not FILE structures (by stdio).  However, the
"implementation defined" clauses with which every description of
errno is sprinkled mean that having fopen set EMFILE should not
strictly be nonconformant.

In article <14617@smoke.brl.mil> gwyn@smoke.brl.mil (Doug Gwyn) writes:
>perror() is required for ANSI C conformance.  errno is required, but
>only a few of the standard functions (mainly math functions) are
>required to set errno (upon certain conditions).  IEEE Std 1003.1
>specifies that errno is to be set for error returns from a large
>number of functions (mainly those normally implemented on UNIX as
>genuine system calls).

It's unfortunate that X3.159 can't say more about errno and
stdio, but being a C language standard it is generally extremely
reluctant to address operating system specifics.  I hope that
this situation (as exemplified in Doug's earlier recommendation)
doesn't discourage people from using errno and/or perror with
stdio, since it can be made to work (and proven so; read on).  It
would be extremely unfortunate if people concluded that they
could only reliably use errno and/or perror after the low-level,
Unixy (and Posixy) calls such as open, read, and write, since it
is highly recommended for portability's sake (not to mention
performance) that fopen and the rest of stdio be used instead.

All we have to do is guarantee that fopen and the rest of the
stdio calls leave errno set appropriately after errors.  As Dave
Eisen has already pointed out, this is not hard for a stdio
implementor to do.  (I can confirm this, having implemented stdio
a number of times myself.)  We can ask that implementors do this
as a part of good quality-of-implementation, but Posix is on our
side: section 8.2.3.11 (Error Reporting) says that

	If any of the functions above return an error indication
	caused by a condition that would be detected by the
	corresponding underlying functions listed above, the
	value returned in errno shall be the one provided for
	["by"?] the underlying function.

(To be sure, this is comp.lang.c, not comp.std.unix; so invoking
Posix is not strictly playing fair, and people worrying about
strict conformance may not be able to make use of this.  On the
other hand, I don't think calling perror or the like after fopen
breaks strict conformance, it just isn't guaranteed to print
anything meaningful.)

Assuming that "the functions above" refers to those listed in
sections 8.2.3.1 through 8.2.3.10, they include all of the stdio
functions we might be concerned about; i.e. the answer to Stuart
Pearlman's question "Does [posix 1003.1] specify what functions
you can call perror() after?" is "yes; most of them."  (What
section 8.2 is all about is "importing" the stdio routines, "as
defined by the C Standard," with amendments and additions to
support their "interactions with other functions defined by this
[Posix] Standard."

The "corresponding underlying functions listed above" are the
low-level, int-fd-using calls (open, read, write, etc.) upon
which the stdio routines are based (although Posix does not
specifically "require that there be any relation between the
implementations of the stream function and its underlying
functions").  Again, sections 8.2.3.1 through 8.2.3.10 spell out
the details.

To sum up, I'm satisfied that, for a Posix-compliant implementation
(or, for that matter, any high-quality C implementation whether
or not it's Posix), errno will be meaningful after stdio errors.
(To repeat, even if there are systems for which this is not true,
and to which code I write might be ported, I'll take that chance.
I'd rather generate useful error messages on most systems than
avoid potentially misleading messages on a few systems by dumbing-
down the messages on all systems.)

The remaining question is, how to generate useful messages
(incorporating all four pieces of information listed above)
conveniently without introducing a potentially errno-destructive
fprintf call before the call to perror.  A simple perror
replacement, which I usually call errorp, suffices.  Its usage is
exemplified by

	extern void errorp(char *, ...);

	if((fp = fopen(filename, "r")) == NULL)
		{
		errorp("%s: can't open %s", progname, filename);
		return ERROR;
		}

Like perror, errorp prints its arguments followed by a colon and
a system-dependent error message derived from errno.  The
advantage is that errorp's arguments are those of printf: a
format string and any number of additional arguments for filling
in the %'s.  (Of course, unlike printf, errorp's output goes to
stderr.)  A public domain, portable implementation of errorp is
included following my signature.  (It could obviously be
trivially rewritten to do something like Chris's "last system
error was" suggestion.)

                                                 Steve Summit
                                                 scs@adam.mit.edu

----------------------------errorp.c-----------------------------
/*
 *  perror replacement.
 *  Prints its first argument a la printf (but to stderr),
 *  interpolating additional arguments via % format specifiers,
 *  and followed by ": ", a system-specific error message
 *  derived from errno, and a newline.
 *
 *  Placed in the Public Domain; use it in good health and
 *  without restriction.
 *
 *  scs 12/5/90
 */

#include <stdio.h>
#include <stdarg.h>
#include <errno.h>	/* or extern int errno; */
#include <string.h>	/* or extern char *strerror(); */

/* VARARGS1 */

void errorp(char *fmt, ...)
{
int saverrno = errno;
va_list argp;

if(fmt != NULL && *fmt != '\0')
	{
	va_start(argp, fmt);

	vfprintf(stderr, fmt, argp);

	va_end(argp);

	fprintf(stderr, ": ");
	}

fprintf(stderr, "%s\n", strerror(saverrno));
}
-------------------------end of errorp.c-------------------------

If you don't have <stdarg.h>, vfprintf, or strerror, I can supply
versions that will probably work on your system.  The FAQ list
includes notes on retrofitting stdarg-using code to use <varargs.h>.