HiveBrain v1.2.0
Get Started
← Back to all entries
debugcppMinor

Better option than "errno" for file IO error handling

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
handlingfileerrorthanbetteroptionerrnofor

Problem

I have the following method for opening a file:

void TankFile::OpenForReading(const std::string & filename)
{
    assert(!filename.empty());

    errno = 0;
    file.exceptions(0); // Don't throw
    file.open(filename, (std::fstream::in | std::fstream::binary));

    if (!file.is_open() || !file.good())
    {
        const char * errorStr = strerror(errno);
        throw TankFileError(Format("Failed to open Tank file \"%s\": '%s'", filename.c_str(), errorStr));
    }
}


The objective here is to attempt to open a file and throw TankFileError with a proper error description on failure. The caller will be expecting this exception type.

Everything works fine and I get a nice error message like this if the exception is thrown:


Failed to open Tank file "unexistent_file": 'No such file or directory'

But what I don't like in that block is having to use the errno global and strerror().

A way around it would be to let the stream throw an exception, then catch it, get the error message from the what() member and re-throw with TankFileError, but I find this solution also a bit hackish, plus, in the tests I did, the resulting error message from std::fstream::failure was pretty cryptic:

void TankFile::OpenForReading(const std::string & filename)
{
    assert(!filename.empty());

    try
    {
        file.exceptions(std::fstream::failbit);
        file.open(filename, (std::fstream::in | std::fstream::binary));
    }
    catch (std::fstream::failure & err)
    {
        throw TankFileError(Format("Failed to open Tank file \"%s\": '%s'", filename.c_str(), err.what()));
    }
}


Produced the error message:


Failed to open Tank file "unexistent_file": 'ios_base::clear: unspecified iostream_category error'.

Is there a better way to implement this? I was hoping that the new C++11 system_error library would provide a way to query this kind of error messages, but from what I've seen, you still have to pass errno around to get an error string.

Solution

I currently don't see any seamless alternative to using errno and ::strerror;.

#include 
#include 
#include 
#include 

#include  // strerror

#include 

#if defined(_LIBCPP_VERSION) && (_LIBCPP_VERSION >= 1000)
#define HAS_IOS_BASE_FAILURE_DERIVED_FROM_SYSTEM_ERROR 1
#else
#define HAS_IOS_BASE_FAILURE_DERIVED_FROM_SYSTEM_ERROR 0
#endif

using std::cerr;
using std::cout;
using std::endl;

int main(/*int argc, char** argv*/) {
    int rv = EXIT_SUCCESS;
    errno = 0;

    try {
        std::ifstream ifs;
        ifs.exceptions(std::ios::badbit | std::ios::failbit);
        ifs.open("DOESN'T EXIST");

    } catch (const std::ios_base::failure& e) {

#if (HAS_IOS_BASE_FAILURE_DERIVED_FROM_SYSTEM_ERROR)
        //
        // e.code() is only available if the lib actually follows iso §27.5.3.1.1
        // and derives ios_base::failure from system_error
        // like e.g. libc++
        // http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/ios?revision=193085&view=markup
        // (line 415)
        //
        // and not keeps on deriving it directly from runtime_error
        // like libstdc++
        // https://github.com/mirrors/gcc/blob/master/libstdc%2B%2B-v3/include/bits/ios_base.h#L209
        //
        cout << "libc++ error #"    << e.code().value()  
                << ' '              << e.code().message()
                << ',' << endl << "    ";
#endif
        cout << "libc error #"      << (rv = errno)
                << ": "             << ::strerror(errno)
                << endl;

        cout << "handled" << endl;
    } 

    return rv;
}


using the the new e.code() semantics yields (with clang 3.4 and libc++ 1101) only the first line of

libc++ error #1 unspecified iostream_category error,
    libc error #2: No such file or directory
handled


Live

So even if one's lucky enough to have a std:: implementation that cares about iso §27.5.3.1.1 and actually derives ios_base::failure from system_error, the msg generated is too poor to be presented to users.

The only thing to be discussed is how the libc messages are best incorporated into wrapper classes.

Code Snippets

#include <stdexcept>
#include <system_error>
#include <string>
#include <iostream>

#include <cstring> // strerror

#include <fstream>

#if defined(_LIBCPP_VERSION) && (_LIBCPP_VERSION >= 1000)
#define HAS_IOS_BASE_FAILURE_DERIVED_FROM_SYSTEM_ERROR 1
#else
#define HAS_IOS_BASE_FAILURE_DERIVED_FROM_SYSTEM_ERROR 0
#endif

using std::cerr;
using std::cout;
using std::endl;


int main(/*int argc, char** argv*/) {
    int rv = EXIT_SUCCESS;
    errno = 0;

    try {
        std::ifstream ifs;
        ifs.exceptions(std::ios::badbit | std::ios::failbit);
        ifs.open("DOESN'T EXIST");

    } catch (const std::ios_base::failure& e) {

#if (HAS_IOS_BASE_FAILURE_DERIVED_FROM_SYSTEM_ERROR)
        //
        // e.code() is only available if the lib actually follows iso §27.5.3.1.1
        // and derives ios_base::failure from system_error
        // like e.g. libc++
        // http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/ios?revision=193085&view=markup
        // (line 415)
        //
        // and not keeps on deriving it directly from runtime_error
        // like libstdc++
        // https://github.com/mirrors/gcc/blob/master/libstdc%2B%2B-v3/include/bits/ios_base.h#L209
        //
        cout << "libc++ error #"    << e.code().value()  
                << ' '              << e.code().message()
                << ',' << endl << "    ";
#endif
        cout << "libc error #"      << (rv = errno)
                << ": "             << ::strerror(errno)
                << endl;

        cout << "handled" << endl;
    } 

    return rv;
}
libc++ error #1 unspecified iostream_category error,
    libc error #2: No such file or directory
handled

Context

StackExchange Code Review Q#57829, answer score: 6

Revisions (0)

No revisions yet.