In Linux/POSIX/UNIX programs, errors are reported by the operating system and C library with the errno variable. What’s the portable and thread-safe way to deal with them in C++? If you are like me, and know Linux better than C++, the answer might not be obvious. But it is simple: throw an std::system_error exception of the generic_category, like this:
if (write(fd, buf, count) < 0) {
throw std::system_error(errno,
std::generic_category());
}
Here we attempt to write() some data. If that call fails, it will return a number less than zero, and the error condition will be indicated through the errno variable (as described in POSIX IEEE Std 1003.1-2017 standard). In that case, we throw a std::system_error exception and pass the errno variable to it, along with std::generic_category(), a function that returns a reference to the static error category object that expresses conditions that correspond to the POSIX errno codes.
We can go one better and pass in an explanation string, because the constructors for std::system_error will accept an optional const char * or const std::string &, as shown in this complete example:
int main() {
try {
const char *fname = "/BOGUS";
int fd = open(fname, O_RDONLY);
if (fd < 0) {
throw std::system_error(errno,
std::generic_category(),
fname);
}
}
catch (std::exception &e) {
fprintf(stderr, "error: %s\n", e.what());
}
}
The needed includes are <fcntl.h>, <stdexcept>, and <system_error>. Here we use standard C++ exception handling, and because the example attempts to read from the non-existent file /BOGUS, running this short program gives
error: /BOGUS: No such file or directory
If we had not used a catch to handle the std::exception, then the output would look something like this (using g++ 13.2.0):
terminate called after throwing an instance of 'std::system_error' what(): /BOGUS: No such file or directory Aborted (core dumped)
There is place where you should not throw std::system_error when errno is set: in a destructor. This situation could happen, for instance, if we called close(fd) and got back -1 because fd isn’t a valid file descriptor (EBADF), or the call was interrupted by a signal (EINTR). In this situation, you need to handle each possible error case that may occur, as you would in any proper C program.
If you weren’t aware of std::system_error and the generic_category, you might roll your own solution by deriving a class that represents an errno_exception from class runtime_exception so that it will work with any code that handles standard exceptions. The following discourse will hopefully discourage you from trying, but if you venture down that path, you could use sterror, strerror_r, or strerror_l to produce the appropriate readable string associated with the error identified with the thread-specific error variable errno. There are thread-safety and portability problems lurking in the functions that create those message strings, however. While the errno variable itself is thread-safe (ever since POSIX.1c, which was standardized way back in 1995), the function strerror(), which is commonly used to generate error strings from that variable, is not. In a multi-threaded program, if you use strerror(), your call to that function might report an error condition that occurs in a different thread. The web abounds with tutorials explaining how to use that function that do not mention that it lacks thread-safety. The C library has two other functions for reporting errno strings that are thread-safe: strerror_r() and strerror_l(). The former has portability issues; the GNU C library provides both a compliant and a non-compliant version of strerror_r(). You could address that problem by adding feature test macros to your code, in which case you will want to check out the strerror() man page. Or you could use strerror_l(), which does not have any portability issues. But then you have to deal with locales, as that function requires a locale_t be passed as input. Some implementations at least accept (locale_t)0 as an indication of the default locale, which is probably what you want. The standard (POSIX.1-2008) isn’t actually clear on that last part, but it appears to be correct, or at least consistent with the implementations. If you want to be more cautious, you could rewrite the runtime_error initializer as strerror_l(errno, uselocale((locale_t)0)), a usage that appears in some of the locale documentation. But since the uselocale function itself can set errno, be sure to save the actual errno value that you want to report in a local variable before you invoke that function. Or perhaps just give up, and go with std::system_error and the generic_category.
The <errno.h> header has been around forever and a day, and interestingly, it played an important role in the failed SCO lawsuit against Linux, which had alleged that the file had been copied from the SYSV UNIX source code.
Writing this little snippet of code has been fun, even though researching all of the relevant documents gave me that “you are lost in a maze of twisty standards” feeling.
Art credit: the lovely abstract landscape painting is by Jenny Wilson.
Copyright 2022 David McGrew