[comp.lang.c] Perror complication

hoey@nrl-aic.arpa (Dan Hoey) (08/20/87)

    From: der Mouse <mouse@mcgill-vision.uucp>
    Subject: Re: Accessing argc & argv from a functi
    Message-Id: <853@mcgill-vision.UUCP>
    Date: 5 Aug 87 07:33:09 GMT

    ... Well over half of all the calls to perror() I write look like
    perror((char *)0); because the prefix is more complicated than a
    single string, so I have fprintf(stderr,....); before the
    perror().

Then you had better save and restore errno around the call to fprintf,
in case fprint makes a failing syscall.

    Wish perror() were printflike, perhaps like syslog() - syslog()
    accepts a printf format, except that %m means insert
    sys_errlst[errno].

Maybe that's why sendmail sometimes says ``Not a typewriter''?

Dan Hoey
HOEY@NRL-AIC.ARPA

chris@mimsy.UUCP (Chris Torek) (08/21/87)

In article <8913@brl-adm.ARPA> hoey@nrl-aic.arpa (Dan Hoey) writes:
>you had better save and restore errno around the call to fprintf,
>in case fprintf makes a failing syscall.

This is something I had thought about; since my error() takes errno
as a parameter, it does not have this problem.  On the other hand,
the only time fprintf(stderr, ...) currently fails is when stderr
is not connected anywhere, in which case none of the perror() text
will go anywhere.

>Maybe that's why sendmail sometimes says ``Not a typewriter''?

No, this is (almost?) always a result of the isatty() calls in
_flsbuf(), followed by a syslog() with no intervening section 2
call errors.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7690)
Domain:	chris@mimsy.umd.edu	Path:	seismo!mimsy!chris

rpw3@amdcad.AMD.COM (Rob Warnock) (08/22/87)

Sometimes when I get really frustrated by the fact that a successful
system call does *not* clear errno, I use the following macro:

	#define ERRED(x)   ((errno = 0), (x), errno != 0)

So you can say things like:

	while (ERRED(pid = fork())) {
		/* handle fork() failure */
		...decide how many times to sleep + loop, or
		   whether to exit() or longjmp() or what...
	}
	if (pid) {
		...parent...
	} else {
		...child...
	}

This bypasses problems with syscalls that can return "-1" as a valid successful
result, such as "nice()", and also avoids the "Not a typewriter" syndrome.

The problem is I don't use it consistently (nor does anyone else) and
therefore it can sometimes be a hindrance to maintenance rather than a help
(the old "Bourne Shell ALGOL" problem).  ;-}  ;-}


Rob Warnock
Systems Architecture Consultant

UUCP:	  {amdcad,fortune,sun,attmail}!redwood!rpw3
ATTmail:  !rpw3
DDD:	  (415)572-2607
USPS:	  627 26th Ave, San Mateo, CA  94403

brian@ncrcan.UUCP (Brian Onn) (08/24/87)

In article <18005@amdcad.AMD.COM> rpw3@amdcad.UUCP (Rob Warnock) writes:
>This bypasses problems with syscalls that can return "-1" as a valid successful
>result, such as "nice()", and also avoids the "Not a typewriter" syndrome.

Sorry if this has been hashed here before. but what is this syndrome?  I 
remember working with a guy who was writing some code, and he kept complaining
that after his system calls, errno was being set to 25 (ENOTTY).  I beleive
this is what you are talking about, but what causes it?

rpw3@amdcad.AMD.COM (Rob Warnock) (08/26/87)

Tutorial time. All old-timers please "n" now...

In article <301@ncrcan.UUCP> brian@ncrcan.UUCP () writes:
+---------------
| In article <18005@amdcad.AMD.COM> rpw3@amdcad.UUCP (Rob Warnock) writes:
| > ...and also avoids the "Not a typewriter" syndrome.
| Sorry if this has been hashed here before. but what is this syndrome?  I 
| remember working with a guy who was writing some code, and he kept complaining
| that after his system calls, errno was being set to 25 (ENOTTY).  I beleive
| this is what you are talking about, but what causes it?
+---------------

Chain of causes/events:

1. Unix does not clear errno on successful system calls, it only *sets* it
on unsuccessful ones. (Why? Who knows/cares! It's there, and I'm sure some
program somewhere would break if you changed it at this late date...)

2. Stdio packages which do "clever" line buffering of stdout on terminals
(BSD 4.x and some System-V's) call "isatty()" to see if the file descriptor
is a terminal. "Isatty()" does the system call "ioctl(fd, TIOCGETP, &tmp)"
(Berkeley) or "ioctl(fd, TCGETA, &tmp)" (System-V), attempting to get the TTY
characteristics. If the call succeeds, fine, set line buffering. If it fails
(e.g., when your stdout is a pipe or a file), errno gets set to 25 (ENOTTY).

3. Many programs have a common routine [often called "panic()", "fatal()",
or "die()"] they use to print error messages and exit, and some of those
blindly call "perror()" (or private equivalent) to print an error message,
and seeing errno set [due to #1 & #2 above], print the corresponding message
from "sys_errlist[]".  By the way, the usual string in "sys_errlist[25]"
is "Not a typewriter".

4. The program detects an error of some kind which is *NOT* the immediate
result of a unsuccessful system call, and because of #3, prints "Not a
typewriter" instead of anything useful. Result? User is confused.

How can #4 happen? At least three ways:

a. An actually successful system call returns a value which the program
   mistakenly is in error. A typical example is a negative return value
   (including "-1") which the program wasn't expecting [such as from "nice()"].

b. The program detects an inconsistency in its input *data*, and "panic()"s.
   Errno is set even though no user-visible system call has failed. Per #3,
   "panic()" mistakenly assumes non-zero errno means use "perror()" (or equiv).

This one is not quite so stupid as it looks. Many programs have an internal
convention of "-1" or a negative value as an error return from lower-level
routines. If an error occurs in a low-level routine, each level sees the
"-1" return and backs out to the next higher. At the top level, some error
handling occurs, using the value left in errno by the low-level routine that
detected the error. Unfortunately, sloppy code may return "-1" as an error
(due to bad input data, etc.) *without* explicitly setting errno, so the
left-over "25" gets used.

c. An actual system call error occurs, but while handling the error stdio is
   touched for the first time, and the errno value from the user's system call
   gets stomped on by the errno value from the failing "ioctl()" in "isatty()".

   Example:

	char *fname = "/No/such/file/exists";
	if (open(fname, 0) < 0) {
		fprintf(stderr, "File '%s', ", fname);
		perror("open failed");
		exit(1);
	}

   Between the failing "open()" and the "perror()", "fprintf()" opens stderr
   for the first time. If stderr has been redirected to a non-TTY, errno will
   be set to ENOTTY by the "isatty()".  (While 4.3 BSD will not not call
   "isatty()" except on stdout, some programs are so broken as to do output
   to stdout between the failing system call and the "perror()"!)

The solution is not fun, but starts with always clearing errno before a
system call (which was what the "ERRED(x)" macro was about in the previous
article). Then, *ALWAYS* make sure that you don't use errno when it's not
germane. One way is to include an error-code argument in any "die()" routine;
you can always say 'die(errno, "message...")' when errno's known to be valid.
If an error is not due to a system call, either call "die(0,...)" or explicitly
use some error value chosen to be "appropriate". For example, if the user has
violated some protection boundary, say 'die(EACCESS, "That's not allowed")'.
(Actually, please use a better message than that!) Finally, in any error-
handling routine, save errno early so it's not lost, and *ALWAYS* print the
numeric code in addition to any other messages.


- Rob ("Help stamp out 'Not a typewriter'!") Warnock

UUCP:	  {amdcad,fortune,sun,attmail}!redwood!rpw3
ATTmail:  !rpw3
DDD:	  (415)572-2607
USPS:	  627 26th Ave, San Mateo, CA  94403

karl@haddock.ISC.COM (Karl Heuer) (08/31/87)

In article <18082@amdcad.AMD.COM> rpw3@amdcad.UUCP (Rob Warnock) writes:
[explanation of the "Not a typewriter" syndrome]

I avoid this problem by using my own copy of isatty(); one which does not have
the side effect of setting errno.  Lately I've noticed some official versions
of isatty() have also fixed this.  If yours doesn't, help yourself to the PD
implementation below.  (Actually, the one I keep in my libhack.a is from an
assembler version.)

Karl W. Z. Heuer (ima!haddock!karl or karl@haddock.isc.com), The Walking Lint
---- cut here ----
#!/bin/sh
cat <<\! >bool.h
typedef int bool;
#define YES     1
#define NO      0
!
cat <<\! >isatty.c
/* Test whether a file descriptor is a terminal; preserve errno */
#include "bool.h"
#ifdef USG
#include <sys/termio.h>
#define	ioarg	struct termio
#define	ISATTY	TCGETA
#else
#include <sgtty.h>
#define	ioarg	struct sgttyb
#define	ISATTY	TIOCGETP
#endif

extern int ioctl();

bool isatty(f) int f; {
    extern int errno;
    ioarg dummy;
    register int saverrno = errno;
    if (ioctl(f, ISATTY, &dummy) < 0) {
	errno = saverrno;
	return (NO);
    } else {
	return (YES);
    }
}
!
exit 0