How to get error message when ifstream open fails
Asked Answered
F

5

141
ifstream f;
f.open(fileName);

if ( f.fail() )
{
    // I need error message here, like "File not found" etc. -
    // the reason of the failure
}

How to get error message as string?

Finesse answered 27/6, 2013 at 7:51 Comment(5)
possible duplicate of C++ ifstream Error CheckingChessa
possible duplicate of Can you get a specific error condition when a C++ stream open fails?Reld
@Alex Farber: Sure. cerr << "Error code: " << strerror(errno); // Get some info as to why seems relevant to the question.Chessa
@MatthieuRouget: Check the possible duplicate I posted -- it seems this is non-standard behaviour only implemented by gcc.Reld
@MatthieuRouget: strerror(errno) works. Post this as answer, I will accept it.Finesse
C
100

Every system call that fails update the errno value.

Thus, you can have more information about what happens when a ifstream open fails by using something like :

cerr << "Error: " << strerror(errno);

However, since every system call updates the global errno value, you may have issues in a multithreaded application, if another system call triggers an error between the execution of the f.open and use of errno.

On system with POSIX standard:

errno is thread-local; setting it in one thread does not affect its value in any other thread.


Edit (thanks to Arne Mertz and other people in the comments):

e.what() seemed at first to be a more C++-idiomatically correct way of implementing this, however the string returned by this function is implementation-dependant and (at least in G++'s libstdc++) this string has no useful information about the reason behind the error...

Chessa answered 27/6, 2013 at 9:2 Comment(6)
e.what() does not seem to give much information, see updates to my answer.Examen
errno uses thread-local storage on modern operating systems. However, there's no guarantee that the fstream functions will not clobber errno after an errno occurs. The underlying functions may not set errno at all (direct system calls on Linux, or Win32). This doesn't work on many real world implementations.Gormandize
In MSVC, e.what() always prints the same message "iostream stream error"Topazolite
warning C4996: 'strerror': This function or variable may be unsafe. Consider using strerror_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details. 1> C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\string.h(168) : see declaration of 'strerror'Flirt
@Flirt Those are lies. Ignore them or disable the warning.Hopeless
@S.S.Anne is stderror thread-safe?Rivera
E
42

You could try letting the stream throw an exception on failure:

std::ifstream f;
//prepare f to throw if failbit gets set
std::ios_base::iostate exceptionMask = f.exceptions() | std::ios::failbit;
f.exceptions(exceptionMask);

try {
  f.open(fileName);
}
catch (std::ios_base::failure& e) {
  std::cerr << e.what() << '\n';
}

e.what(), however, does not seem to be very helpful:

  • I tried it on Win7, Embarcadero RAD Studio 2010 where it gives "ios_base::failbit set" whereas strerror(errno) gives "No such file or directory."
  • On Ubuntu 13.04, gcc 4.7.3 the exception says "basic_ios::clear" (thanks to arne)

If e.what() does not work for you (I don't know what it will tell you about the error, since that's not standardized), try using std::make_error_condition (C++11 only):

catch (std::ios_base::failure& e) {
  if ( e.code() == std::make_error_condition(std::io_errc::stream) )
    std::cerr << "Stream error!\n"; 
  else
    std::cerr << "Unknown failure opening file.\n";
}
Examen answered 27/6, 2013 at 8:19 Comment(9)
Thanks. I didn't test this because strerror(errno) posted in the comments works and very simple for using. I think that e.what will work, since errno works.Finesse
Then see the annotaions about multithreading in Matthieus answer - my guess is that e.what() will be what strerror returns, in a threadsafe way. Both will probably platform dependent.Examen
@AlexFarber: I think that Arne's answer is better than mine. My solution is not the C++-way of solving your issue. However, I did not find official information about how the C++ library maps system call errors to exception.what(). May be a good opportunity to dive into the libstdc++ source code :-)Chessa
I tried this out: Tried to open a nonexisting file and the exception message read basic_ios::clear, nothing else. This isn't really helpful. That's why I didn't post ;)Reld
@Reld wich platform, compiler, os?Examen
@MatthieuRouget see my update to the answer - I'd prefer strerror on the platform I am using. (though I would prefer using another compiler, tbh)Examen
@ArneMertz: amd64, gcc version 4.7.3 (Ubuntu/Linaro 4.7.3-1ubuntu1), Lubuntu 13.04 with all recent updated installed.Reld
@All: OK interesting. Sadly, while my answer seems to be the good one, it looks very "old school". Updated my answer. As a side note, I saw nothing else than static strings in the call of ios_base::failure's constructor in the libstdc++ source code.... (i.e. no reference to somthing that looks like errno)Chessa
This will also throw an exception on successful opening of the file in c++ 20.Sideways
T
30

Following on @Arne Mertz's answer, as of C++11 std::ios_base::failure inherits from system_error (see http://www.cplusplus.com/reference/ios/ios_base/failure/), which contains both the error code and message that strerror(errno) would return.

std::ifstream f;

// Set exceptions to be thrown on failure
f.exceptions(std::ifstream::failbit | std::ifstream::badbit);

try {
    f.open(fileName);
} catch (std::system_error& e) {
    std::cerr << e.code().message() << std::endl;
}

This prints No such file or directory. if fileName doesn't exist.

Tammeratammi answered 14/4, 2016 at 4:35 Comment(5)
For me in MSVC 2015 that just prints iostream stream error.Topazolite
For me GCC 6.3 also prints iostream error. What compiler did you test this on? Does any compiler actually provide a user-readable reason for failure?Underplay
Clang 6 on libc++ on macOS: unspecified iostream_category error.Mikkel
Xcode 10.2.1 (Clang) / libc++ (C++17) on MacOS 10.14.x: also "Unspecified iostream_category error". strerror(errno) SEEMS to be the only way to get this right. I suppose I could catch it first by asking std::filesystem if the path.exists(), and examining the std::error_code it returns.Toscanini
In the example program, the statement f.open(fileName) throws an exception of type std::ios_base::failure, which is derived from std::system_error. The exception is caught by the catch block. Within the catch block, e.code() invokes std::ios_base::failure::code() which returns an object of type std::error_code. The error codes defined by class std::error_code are platform-dependent--i.e., e.code().message() and e.code().value() both return platform-dependent values.Marciemarcile
N
18

You can also throw a std::system_error as shown in the test code below. This method seems to produce more readable output than f.exception(...).

#include <exception> // <-- requires this
#include <fstream>
#include <iostream>

void process(const std::string& fileName) {
    std::ifstream f;
    f.open(fileName);

    // after open, check f and throw std::system_error with the errno
    if (!f)
        throw std::system_error(errno, std::system_category(), "failed to open "+fileName);

    std::clog << "opened " << fileName << std::endl;
}

int main(int argc, char* argv[]) {
    try {
        process(argv[1]);
    } catch (const std::system_error& e) {
        std::clog << e.what() << " (" << e.code() << ")" << std::endl;
    }
    return 0;
}

Example output (Ubuntu w/clang):

$ ./test /root/.profile
failed to open /root/.profile: Permission denied (system:13)
$ ./test missing.txt
failed to open missing.txt: No such file or directory (system:2)
$ ./test ./test
opened ./test
$ ./test $(printf '%0999x')
failed to open 000...000: File name too long (system:36)
Necaise answered 30/6, 2018 at 22:27 Comment(0)
A
1

The std::system_error example above is slightly incorrect. std::system_category() will map the error codes from system's native error code facility. For *nix, this is errno. For Win32, it is GetLastError(). ie, on Windows, the above example will print

failed to open C:\path\to\forbidden: The data is invalid

because EACCES is 13 which is the Win32 error code ERROR_INVALID_DATA

To fix it, either use the system's native error code facility, eg on Win32

throw new std::system_error(GetLastError(), std::system_category(), "failed to open"+ filename);

Or use errno and std::generic_category(), eg

throw new std::system_error(errno, std::generic_category(), "failed to open"+ filename);
Alisealisen answered 16/2, 2021 at 17:54 Comment(1)
Note that you shouldn't throw raw pointers unless your framework requires you to. Use throw std::make_shared<std::system_error>(...), or even better, throw std::system_error(...).Patrology

© 2022 - 2024 — McMap. All rights reserved.