Overview

I was recently working with someone else’s C source and I wanted to add some basic error checking without mucking up the code with a bunch of if statements and calls to perror. I ended up implementing a simple must function that checks the return value of an expression, and exits with an error if the return value is less than 0. You use it like this:

must(fd = open("textfile.txt", O_RDONLY));

Or:

must(close(fd));

In the event that an expression returns an error, the code will exit with a message that shows the file, line, and function in which the error occurred, along with the actual text of the called function and the output of perror:

example.c:24 in main: fd = open("does-not-exist.xt", O_RDONLY): [2]: No such file or directory

To be clear, this is only useful when you’re using functions that conform to standard Unix error reporting conventions, and if you’re happy with “exit with an error message” as the failure handling mechanism.

Implementation

The implementation starts with a macro defined in must.h:




#ifndef _MUST
#define _MUST

#define must(x) _must(__FILE__, __LINE__, __func__, #x, (x))

void _must(const char *fileName, int lineNumber, const char *funcName,
           const char *calledFunction, int err);
#endif



The __FILE__, __LINE__, and __func__ symbols are standard predefined symbols provided by gcc; they are documented here. The expression #x is using the stringify operator to convert the macro argument into a string.

The above macro transforms a call to must() into a call to the _must() function, which is defined in must.c:



#include "must.h"

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

void _must(const char *fileName, int lineNumber, const char *funcName,
           const char *calledFunction, int err) {
  if (err < 0) {
    char buf[256];
    snprintf(buf, 256, "%s:%d in %s: %s: [%d]", fileName, lineNumber, funcName,
             calledFunction, errno);
    perror(buf);
    exit(1);
  }
}



In this function we check the value of err (which will be the return value of the expression passed as the argument to the must() macro), and if it evaluates to a number less than 0, we use snprintf() to generate a string that we can pass to perror(), and finally call perror() which will print our information string, a colon, and then the error message corresponding to the value of errno.

Example

You can see must() used in practice in the following example program:



#include "must.h"
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main() {
  int fd;
  char buf[1024];

  printf("opening a file that does exist\n");
  must(fd = open("file-that-exists.txt", O_RDONLY));

  while (1) {
    int nb;
    must(nb = read(fd, buf, sizeof(buf)));
    if (!nb)
      break;
    must(write(STDOUT_FILENO, buf, nb));
  }

  must(close(fd));

  printf("opening a file that doesn't exist\n");
  must(fd = open("file-that-does-not-exist.xt", O_RDONLY));
  return 0;
}



Provided the file-that-exists.txt (a) exists and (b) contains the text Hello, world., and that file-that-does-not-exist.txt does not, in fact, exist, running the above code will produce the following output:

opening a file that does exist
Hello, world.
opening a file that doesn't exist
example.c:24 in main: fd = open("file-that-does-not-exist.xt", O_RDONLY): [2]: No such file or directory