A Guide to Error Handling in C

As promised in a previous article, here is my tutorial on the errno.h header file. From what I’ve seen, there doesn’t seem to be much documentation of this header outside of the Unix man pages. There’s some reference material but not much tutorial content. I pretty much just figured out how to use errno by reading the manual and looking at the header file itself. But combing through man pages and header files can be rather cumbersome, and no one’s done an actual tutorial on this subject as far as I can tell – at least not a tutorial where explanations of all of C’s error handling features are conveniently aggregated in one place – so I thought I would do one such tutorial myself.

errno is a global variable defined in the header file errno.h. Certain functions set this variable’s value when they return an error. The variable is used to examine the error status of a function when it fails to perform some task. The error status is simply an integer; it can be used to decide what error message to print, to fix the error within the program, or to aid in post-mortem debugging by setting the exit status to the errno value. errno.h defines a list of macros that correspond to these numerical values so that the interface can be clean, user-friendly, and implementation-independent.

There are many ways to access and use this variable. The most obvious way is to access it directly:


switch( errno ){
        case EPERM : fprintfstderr"Not owner" ); break;
        case ENOENTfprintfstderr"No such file or directory" ); break;
        case EIO   : fprintfstderr"I/O error" ); break;
        case EACCESfprintfstderr"Permission denied" ); break;
}

There are of course easier and less tedious ways to access the errno variable. Several C Standard Library functions allow you to access errno indirectly to generate the appropriate string. The first of these functions that we will look at is strerror(), from the string.h header file. Its use is fairly simple: all it does is take an error code as input and produce the corresponding string as output.


fprintfstderr"%s%s%s\n", argv[0], argv[1], strerror( errno ) );

If all you want to do is print the error message, you can use the perror() function from stdio.h as a shortcut. This prints the inputted string followed by the error message. Because this function doesn’t do formatted output, if you want to do formatted output with it, you will have to build a string with sprintf() or its memory-safe equivalent snprintf().


snprintf( errorstring, strlen( argv[0] ) + strlen( argv[1] ) + 2"%s%s", argv[0], argv[1] );
perror( errorstring );

There are two other error-handling functions in stdio.h, and those are ferror() and clearerr(). They’re not in the same class as the two previous functions since they use a Boolean error indicator rather than errno. This Boolean indicator is analogous to errno, except it only has two possible values, it has no name and is not directly accessible, and there is a separate copy of the variable for every open file stream. This variable indicates whether or not an error has occurred on the given stream since the last time the value was cleared. ferror() returns the value of this indicator variable, and clearerr() clears its value. Here is a trivial program that shows these two functions in action:


 1 #include <stdio.h>
 2 
 3 int mainint argc, char **argv ){
 4         FILE *fp = stdin;
 5         fprintf( fp, "hello" );
 6         ifferror( fp ) ) printf"Error on stream.\n" );
 7         else printf"No error on stream.\n" );
 8         clearerr( fp );
 9         ifferror( fp ) ) printf"Error on stream.\n" );
10         else printf"No error on stream.\n" );
11         return 0;
12 }

This program outputs:


Error on stream.
No error on stream.

To perform post-mortem debugging, you can exit with a status of errno:


exit( errno );

Then you can examine the exit status using the following command:


echo $?

You can then look in the errno.h header file or whatever file is included in that header (for me it’s sys/errno.h) for the macro that has that value. This will tell you the exact type of error that occurred.

Of course no discussion of error handling in C would be complete without going through at least some of the standard error statuses and explaining what they mean. So that is what I will do now.

  • EPERM: Current process’s owner does not have the appropriate permissions on the given resource to perform the given operation.
  • ENOENT: No such file or directory. Specified resource does not exist.
  • ESRCH: No process with the specified ID exists.
  • EINTR: System call was interrupted.
  • E2BIG: Too many arguments.
  • ENOEXEC: Invalid executable format.
  • EIO: Reading/writing of medium failed.
  • EBADF: Requested operation on the given file descriptor failed.
  • ECHILD: Tried to operate on a child process when there were none.
  • EFAULT: Low-level memory error, i.e. a segfault or bus error.
  • EEXIST: File exists (e.g. when trying to create a file with the same name).
  • EAGAIN: Resource temporarily unavailable.
  • ENOMEM: Not enough memory to complete the operation.
  • EACCES: Permission denied.
  • EBUSY: Specified device or resource is busy and thus can’t be accessed at this time.
  • EXDEV: Cross-device link, i.e. a link between two completely different filesystems, possibly stored on different physical media.
  • ENODEV: No such device.
  • EISDIR: Non-directory operation attempted on a directory.
  • ENOTDIR: Directory operation attempted on a file that isn’t a directory.
  • ENOTEMPTY: Directory is not empty.
  • ENAMETOOLONG: Filename or pathname is too long.
  • EINVAL: Invalid function parameter.
  • ENFILE: Open file count has exceeded system limit.
  • EMFILE: Value of specified file descriptor exceeds the system limit.
  • ENOTTY: Character device operation attempted on something other than a character device.
  • ENOTBLK: Block device operation attempted on something other than a block device.
  • ETXTBSY: Text file busy.
  • EFBIG: Maximum file size exceeded.
  • ENOSPC: No space left on device (you can test this error by operating on /dev/full).
  • ESPIPE: Invalid seek operation.
  • EROFS: Write operation attempted on a read-only filesystem.
  • EMLINK: Link count on specified file has exceeded system limit (this of course refers to hard links).
  • ELOOP: Too many levels of symbolic links.
  • EPIPE: Broken pipe, in other words, the process at the other end of the pipe is no longer running.
  • EDOM: Argument to a mathematical function outside the domain of that function.
  • ERANGE: Result of a numerical function is out of range.
  • ENOMSG: No message of the desired type.
  • EIDRM: Identifier removed.
  • EOVERFLOW: Overflow occurred (meaning the result was too large to fit in the given data type).
  • ECANCELED: Operation cancelled.
  • EDEADLK: Deadlock encountered (a deadlock is a situation in which two processes each need a resource held by the other process to continue, causing the processes to lock permanently).
  • ENOLCK: Process has requested a lock on a resource when none is available (resource locks are used in process synchronization and IPC).
  • ENOSTR: Stream operation attempted on something other than a file stream.
  • ENODATA: No data available
  • ENOLINK: The virtual circuit between this process’s socket and the remote process’s socket has been severed.
  • EPROTO: TCP/IP protocol error.
  • ETIME: Timeout encountered on an ioctl stream operation.
  • ENOSR: No stream resources left.
  • EMULTIHOP: Multihop attempted (I assume this refers to router hops).
  • EBADMSG: Bad message.
  • ENOSYS: Function not implemented.
  • EOPNOTSUPP: Socket operation not supported.
  • ECONNRESET: Connection reset by another host.
  • EAFNOSUPPORT: Address family (which usually means either IPv4, IPv6, or Unix domain) not supported by protocol.
  • EPROTOTYPE: Wrong transport layer protocol for socket type.
  • ENOTSOCK: Socket operation attempted on something other than a socket.
  • ENOPROTOOPT: Protocol not available.
  • ECONNREFUSED: Connection refused.
  • EADDRINUSE: IP address already in use.
  • ECONNABORTED: Connection was aborted.
  • ENETUNREACH: Network unreachable.
  • EHOSTUNREACH: Host unreachable.
  • ENETDOWN: Network interface is not configured.
  • ETIMEDOUT: Network connection timed out.
  • EINPROGRESS: Connection already in progress.
  • EALREADY: Socket already connected.
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s