14

Currently I use this code to check if file exists on Windows and POSIX-compatible OSes (Linux, Android, MacOS, iOS, BlackBerry 10):

bool FileExist( const std::string& Name ) { #ifdef OS_WINDOWS struct _stat buf; int Result = _stat( Name.c_str(), &buf ); #else struct stat buf; int Result = stat( Name.c_str(), &buf ); #endif return Result == 0; } 

Questions:

  1. Does this code have any pitfalls? (maybe an OS where it cannot be compiled)

  2. Is it possible to do it in a truly portable way using only C/C++ standard library?

  3. How to improve it? Looking for canonical example.

8
  • 1
    What is the purpose of checking if it exists, e.g. are you going to open the file if it exists, or print an error message, or something else? Commented Aug 19, 2013 at 18:25
  • 4
    It should work fine. I would specifically check for both Windows and POSIX with the default being something POSIX-like. You should probably have a project-specific OS define as well, as these names themselves might change from system to system. Commented Aug 19, 2013 at 18:26
  • 3
    @MatsPetersson: opening a file is a bad idea - it can exist but open by another process in an unshared way. Commented Aug 19, 2013 at 18:31
  • 2
    I used access for testing existence. I have no idea if it is better. I actually think it is easier as you have done. Example: if (access("file", F_OK) == 0) ... It can be done on windows and linux. Commented Aug 19, 2013 at 18:47
  • 2
    @P0W: it looks like a potential pit-fall. Could you summarize it as an answer? Commented Aug 19, 2013 at 18:55

3 Answers 3

20

Because C++ is also tagged, I would use boost::filesystem:

#include <boost/filesystem.hpp> bool FileExist( const std::string& Name ) { return boost::filesystem::exists(Name); } 

Behind the scenes

Apparently, boost is using stat on POSIX and DWORD attr(::GetFileAttributesW(FileName)); on Windows (Note: I've extracted the relevant parts of code here, it could be that I did something wrong, but this should be it).

Basically, besides return value, boost is checking errno value in order to check if file really does not exist, or your stat failed for a different reason.

#ifdef BOOST_POSIX_API struct stat path_stat; if (::stat(p.c_str(), &path_stat)!= 0) { if (ec != 0) // always report errno, even though some ec->assign(errno, system_category()); // errno values are not status_errors if (not_found_error(errno)) { return fs::file_status(fs::file_not_found, fs::no_perms); } if (ec == 0) BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::status", p, error_code(errno, system_category()))); return fs::file_status(fs::status_error); } #else DWORD attr(::GetFileAttributesW(p.c_str())); if (attr == 0xFFFFFFFF) { int errval(::GetLastError()); if (not_found_error(errval)) { return fs::file_status(fs::file_not_found, fs::no_perms); } } #endif 

not_found_error is defined separately for Windows and for POSIX:

Windows:

bool not_found_error(int errval) { return errval == ERROR_FILE_NOT_FOUND || errval == ERROR_PATH_NOT_FOUND || errval == ERROR_INVALID_NAME // "tools/jam/src/:sys:stat.h", "//foo" || errval == ERROR_INVALID_DRIVE // USB card reader with no card inserted || errval == ERROR_NOT_READY // CD/DVD drive with no disc inserted || errval == ERROR_INVALID_PARAMETER // ":sys:stat.h" || errval == ERROR_BAD_PATHNAME // "//nosuch" on Win64 || errval == ERROR_BAD_NETPATH; // "//nosuch" on Win32 } 

POSIX:

bool not_found_error(int errval) { return errno == ENOENT || errno == ENOTDIR; } 
Sign up to request clarification or add additional context in comments.

8 Comments

+1 for Boost. But using Boost only for this function (we don't use Boost for anything else) is a bit greedy.
Maybe you can expand your answer by explaining what is inside boost::filesystem::exists? It can be a good answer.
@SergeyK.: The insides are similar to your code, with different implementations for different platforms. The library (which is due to become part of the standard library next year) provides a portable interface to that nastiness.
This is a nice answer!
@ZacHowland: the comment looks like an answer actually. Don't be shy, just add it!
|
4

I perosnally like to just try to open the file:

bool FileExist( const std::string& Name ) { std::ifstream f(name.c_str()); // New enough C++ library will accept just name return f.is_open(); } 

should work on anything that has files [not required by the C++ standard] and since it's using C++ std::string, I don't see why std::ifstream should be a problem.

8 Comments

Opening a file is a bad idea - it can exist but open by another process in an unshared way.
I'm not sure there is any way that is 100% fool proof - the file may also exist right now, and be deleted the next time this process gets to run. Or it may belong to a different user, so we don't have rights to open (or stat, etc) it. Any "file exists" method is advisory at best. If the knowledge of whether it exists is for the purpose of "avoid saving over an existing file", then inability to open the file is not a problem, because you also can't open the file for writing a line or three later [aside from race-conditions, of course - by any method will have that].
But the question was how to improve the existing code. Your code removes #ifdef but adds another assumption. This is not an improvement, it is a trade-off.
It is a trade-off - it improves portability and removes a dependency on the system having stat or _stat (which some OS's may not have).
Can you explain under what circumstance the "false negative" of "file is open by another process" is a big problem?
|
1
  1. Does this code have any pitfalls? (maybe an OS where it cannot be compiled)

Result == 0 "skips" ENAMETOOLONG , ELOOP, errors etc. as per this

I can think of this : ENAMETOOLONG path is too long as:-

In many cases ,during a recursive scan, the sub-folder/directories keep on increasing, if path is "too" long it may result into this error, but still file do exists !

Similar cases might happen with other errors too.

Also,

As per this, I'ld prefer to use the overloaded boost::filesystem::exists method

bool exists(const path& p, system::error_code& ec) noexcept;

2 Comments

@SergeyK. Not sure, may be we can try changing directory on each recurs and then start a scan.
@SergeyK. See how boost did it - basically - besides return value, you need to check last error code set.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.