Reporting on errno Values

When an error occurs, it is simple for the program to test for a specific case and act upon it. The problem becomes more complex when all you want to do is report the error to the user. Users do not like to memorize error codes, so a method must exist to translate an errno code into a readable message.

Meaningful error messages can be reported by a UNIX program to the user, in the following ways:

  • Use the perror(3) function to generate a message from the errno value and report it to stderr.

  • Use the provided sys_errlist[] array of messages (on FreeBSD this is described by the man page strerror(3)).

  • Use the strerror(3) function to return a message for the error code provided in the function argument.

Using the perror(3) Function

One function provided for reporting errors is the library function perror(3). This function takes one string argument and writes that string to stderr, followed by a colon and then a message for the current errno value. The function synopsis is as follows:

#include <stdio.h>

void perror(const char *s);

This function is easily tested by simply assigning an error of our choice to errno and calling perror(3). An example is provided in Listing 3.1.

Code Listing 3.1. perror.c—A Test Program for perror(3)
1:   #include <stdio.h>
2:   #include <errno.h>
3:
4:   int
5:   main(int argc,char **argv) {
6:
7:       errno = EIO;
8:       perror("Test EIO Message");
9:       return 0;
10:  }

Line 7 shows how an I/O error was assigned to the errno variable (the error code was arbitrarily chosen to simulate an error). Line 8 calls upon the perror(3) function to report the error. The test session is shown below:

$ make perror
cc -c -D_POSIX_C_SOURCE=199309L -Wall perror.c
cc perror.o -o perror
$ ./perror
Test EIO Message: Input/output error
$

The session output shows the program-supplied message, which is followed by a colon and then by an interpretation of the error code that was assigned to variable errno. The value, EIO in this example, was translated to the message Input/output error.

Evaluating the perror(3) Function

At first sight, the perror(3) function might appear to be a good solution. In practice, however, this function is not very useful. The first problem is that the message must go to standard error. If the message must be

  • Written to a log file

  • Reported to an X Window pop-up

  • Reported in a different format

  • Stored as a string

then the function perror(3) is not able to help. Another problem that often occurs is this: What if the error code is not coming from errno but some other variable? The perror(3) function seems best left to academic examples because of its simplicity.

Using the sys_errlist[] Array

If you look up the perror(3) function in the FreeBSD man(1) pages (and on most UNIX platforms), you will also see that it describes the sys_errlist[] array. The synopsis of this array is this:

#include <stdio.h>     /* Defines sys_errlist[] and sys_nerr */

extern const char *sys_errlist[];
extern const int sys_nerr;

Variations:

#include <errno.h>     /* HPUX 10.2 & 11.0 */
/* None */             /* AIX 4.3 */
/* None */             /* SunOS 5.6, Solaris 8 */
/* None */             /* UnixWare 7 */
#include <errno.h>     /* SGI IRIX 6.5 */

The sys_errlist[] array is an external array of pointers to string constants. Each string describes a particular error that corresponds to an errno code. The array and the error codes are structured so that the error message can be obtained by using the errno value as the subscript into the array. For example

errno = EIO;                    /* Simulate an error */
printf("The EIO Message is '%s'
",sys_errlist[errno]);

Having access to the error message text for each error code provides much more flexibility. When the fopen(3) call fails, you can report the reason for the failure, the pathname being opened, and whether it is being opened for reading or writing:

FILE *fp = fopen(pathname,"r");   /* Attempt to open a file */

if ( !fp ) {                       /* Did the open fail? */
    fprintf(stderr,"%s: Unable to open %s for read.
",
        sys_errlist[errno],       /* The error message text */
        pathname);                /* The file being opened */
    exit(13);
}

This example shows a typical format for error messages from UNIX programs. This typical format used can be summarized as

Explanation of error code: Explanation of the operation being attempted

Notice that this convention contradicts the format used by the perror(3) function.

Using sys_nerr to Range Check Errors

The largest error code that is provided for in the sys_errlist[] array is given by the external integer value of sys_nerr minus one. To be safe, you should technically always test the errno value before using it as a subscript:

int fd;                         /* File descriptor */

fd = open(pathname,O_RDONLY);   /* Attempt to open for read */
if ( fd == -1 ) {               /* Did open(2) fail? */
    /* The open(2) call failed: */
    fprintf(stderr,"%s: opening %s for read
",
        errno < sys_nerr ? sys_errlist[errno] : "?",
        pathname);

In the example shown, the C operator ? is used to test errno to make sure that it is less than the value of sys_nerr. If it is, the value of sys_errlist[errno] can be safely supplied to fprintf(3). If the errno value fails to be less than the sys_nerr value, the C string "?" is supplied instead, to prevent a program abort.

Evaluating the sys_errlist[] Array Method

While range-checking errno with the sys_nerr value is the correct thing to do, it is considered tedious and pedantic by many programmers. Therefore, many programmers ignore this test completely. Because programmers fail to apply this test, the practice of using the sys_errlist[] array has fallen out of favor, and another way has been subsequently provided.

Note

The man(1) pages provided by SGI for its IRIX 6.5 operating system state "Code using sys_errlist, and sys_errlist directly, will not be able to display any errno greater than 152." It is unclear from this text whether it is simply stating the SGI value of sys_nerr or whether this is a limitation of using the array on that platform.

The tone of the message suggests that the sys_errlist[] array falls short of strerror(3) and thus should be avoided in new code. A possible reason for this is that dynamic content could be provided by the strerror(3) function for errors with codes greater than 152.


The strerror(3) Function

This is the last of the error code conversion methods that will be examined. The synopsis of the strerror(3) function is as follows:

#include <string.h>

char *strerror(int errnum);

Tip

A common mistake is to include the file <errno.h> instead of <string.h>. It is commonly assumed that the strerror(3) function is declared in the <errno.h> include file because it reports an error message. However, this function is grouped with the string functions, instead.


The strerror(3) function provides the flexibility afforded by the sys_errlist[] array, but it also performs the necessary range check on the error code being converted. If the error code is outside of the known list of error codes, an unknown error message is returned instead of a bad pointer.

Using the strerror(3) Function

Listing 3.2 shows a short program that we can use to test the strerror(3) function.

Code Listing 3.2. strerror.c—Test Program for strerror(3)
1:   #include <stdio.h>
2:   #include <errno.h>
3:   #include <string.h>
4:
5:   extern int sys_nerr;    /* Highest supported error code */
6:
7:   int
8:   main(int argc,char **argv) {
9:       int x;
10:      static int ecodes[] = {  -1, EIO, 0 } ;
11:
12:      /* Get maximum code and add 4096 */
13:      ecodes[2] = sys_nerr + 4096;    /* A very high code */
14:
15:      for ( x=0; x<3; ++x ) {
16:          errno = ecodes[x];
17:          printf("%4d = '%s'
",ecodes[x],strerror(errno));
18:      }
19:
20:      return 0;
21: }

This test program tries strerror(3) with a -1 value, EIO, and a very high error code, which should not exist.

Testing the Range Check in strerror(3)

When the program in Listing 3.2 is compiled and run, the following results are obtained under FreeBSD (3.4 Release):

$ make strerror
cc -c -D_POSIX_C_SOURCE=199309L -Wall strerror.c
cc strerror.o -o strerror
$ ./strerror
  -1 = 'Unknown error: -1'
   5 = 'Input/output error'
4183 = 'Unknown error: 4183'
$

This shows how well behaved the strerror(3) function is, despite the bad errno values that were provided to it. The error code 5 (EIO) correctly translated to the message Input/output error. The values -1 and 4183 both provided a meaningful clue to a programming problem with a message of the form Unknown error: 4183. Had this program used the sys_errlist[] array instead, a program abort may have occurred.

Applying strerror(3) Correctly

One important thing to note about using the strerror(3) function is that the pointer returned by this function is only valid until the next call to the same function is made. The following code is incorrect:

char *eptr1 = strerror(EIO);
char *eptr2 = strerror(ENOENT); /*** value of eptr1 is now invalid ***/

printf("Msg1='%s', msg2='%s'
",eptr1,eptr2);       /*** INCORRECT ***/

This code is not acceptable because by the time strerror(3) is called the second time and its return value is assigned to eptr2, the pointer value eptr1 is rubbish. Even if your experimentation proves this practice to be apparently safe, code should not be written to rely on this behavior. There is a possibility that someday (if not already), strerror(3) may return dynamic content and cause this to fail.

Warning

The value returned by strerror(3) is valid only until the next call to strerror(3).


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.144.32