135
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?

László Papp
  • 51,870
  • 39
  • 111
  • 135
Alex F
  • 42,307
  • 41
  • 144
  • 212
  • 3
    possible duplicate of [C++ ifstream Error Checking](http://stackoverflow.com/questions/13446593/c-ifstream-error-checking) – Matthieu Rouget Jun 27 '13 at 08:19
  • 1
    possible duplicate of [Can you get a specific error condition when a C++ stream open fails?](http://stackoverflow.com/questions/303966/can-you-get-a-specific-error-condition-when-a-c-stream-open-fails) – arne Jun 27 '13 at 08:28
  • 3
    @Alex Farber: Sure. `cerr << "Error code: " << strerror(errno); // Get some info as to why` seems relevant to the question. – Matthieu Rouget Jun 27 '13 at 08:28
  • @MatthieuRouget: Check the possible duplicate I posted -- it seems this is non-standard behaviour only implemented by gcc. – arne Jun 27 '13 at 08:29
  • 1
    @MatthieuRouget: `strerror(errno)` works. Post this as answer, I will accept it. – Alex F Jun 27 '13 at 08:37

5 Answers5

96

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...

S.R
  • 2,411
  • 1
  • 22
  • 33
Matthieu Rouget
  • 3,289
  • 18
  • 23
  • 2
    `e.what()` does not seem to give much information, see updates to my answer. – Arne Mertz Jun 27 '13 at 10:41
  • 20
    `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. – strcat Mar 12 '14 at 06:40
  • 3
    In MSVC, `e.what()` always prints the same message "`iostream stream error`" – rustyx Aug 11 '16 at 11:11
  • `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'` – sergiol Jul 13 '18 at 16:22
  • 2
    @sergiol Those are lies. Ignore them or disable the warning. – S.S. Anne Nov 17 '19 at 18:12
38

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";
}
Community
  • 1
  • 1
Arne Mertz
  • 24,171
  • 3
  • 51
  • 90
  • 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. – Alex F Jun 27 '13 at 09:01
  • 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. – Arne Mertz Jun 27 '13 at 09:10
  • 2
    @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 :-) – Matthieu Rouget Jun 27 '13 at 09:17
  • 1
    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 ;) – arne Jun 27 '13 at 09:43
  • @arne wich platform, compiler, os? – Arne Mertz Jun 27 '13 at 10:04
  • @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) – Arne Mertz Jun 27 '13 at 10:13
  • @ArneMertz: amd64, gcc version 4.7.3 (Ubuntu/Linaro 4.7.3-1ubuntu1), Lubuntu 13.04 with all recent updated installed. – arne Jun 27 '13 at 10:30
  • @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`) – Matthieu Rouget Jun 27 '13 at 11:54
  • This will also throw an exception on successful opening of the file in c++ 20. – Richard Neumann Jan 09 '22 at 12:43
29

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.

rthur
  • 1,466
  • 1
  • 15
  • 15
  • 16
    For me in MSVC 2015 that just prints `iostream stream error`. – rustyx Aug 11 '16 at 11:05
  • 7
    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? – Ruslan Dec 19 '17 at 12:05
  • 2
    Clang 6 on libc++ on macOS: `unspecified iostream_category error`. – akim Mar 21 '18 at 05:39
  • 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. – SMGreenfield May 24 '19 at 17:02
  • 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. – Jim Fischer Oct 11 '20 at 06:45
16

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)
ɲeuroburɳ
  • 6,990
  • 3
  • 24
  • 22
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);