Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check if a fstream is either a file or directory

I'm using C++ fstream to read a config file.

#include <fstream>
std::ifstream my_file(my_filename);

Right now, if I pass the path of a directory, it silently ignores this. E.g. my_file.good() returns true, even if my_filename is a directory. Since this is unintended input for my program, I like to check for it, and throw an exception.

How do I check if a just opened fstream is a regular file, directory or stream?

I can't seem to find a way to either:

  • get the file descriptor from a given ifstream.
  • use some other mechanism to find this info in the ifstream.

In some forum discussion it was suggested that neither is possible because this is OS-dependant, and thus could never be part of the fstream C++ standard.

The only alternative I can think of is to rewrite my code to get rid of ifstream altogether and resort to the C-method of a file descriptor (*fp), along with fstat():

#include <stdio.h>
#include <sys/stat.h>
FILE *fp = fopen(my_filename.c_str(), "r");
// skip code to check if fp is not NULL, and if fstat() returns != -1
struct stat fileInfo;
fstat(fileno(fp), &fileInfo);
if (!S_ISREG(fileInfo.st_mode)) {
    fclose(fp);
    throw std::invalid_argument(std::string("Not a regular file ") + my_filename);
}

I prefer fstream. Hence, my question.

like image 506
MacFreek Avatar asked Mar 27 '15 21:03

MacFreek


People also ask

How do you check if a file is in a directory C++?

Use ifile. open() is mainly used to check if a file exists in the specific directory or not. In the filing, a stream refers to an abstract that signifies a method where input as well as output processes are executed. “ifile. open()” takes one argument that is the name of the file.

Does fstream open a file?

Opening a FileEither ofstream or fstream object may be used to open a file for writing. And ifstream object is used to open a file for reading purpose only.

What is the difference between fstream and ofstream?

ofstream is output file stream which allows you to write contents to a file. fstream allows both reading from and writing to files by default.

What does #include fstream do in C++?

<fstream> library provides functions for files, and we should simply add #include <fstream> directives at the start of our program. To open a file, a filestream object should first be created. This is either an ofstream object for writing, or an ifstream object for reading.


3 Answers

Starting with C++17, we have the filesystem available to us (based on boost::filesystem). This will be much more portable than anything else in C++ (although the stat() works very well too, I think).

You have two functions available, one returns an error code if you need it.

bool is_regular_file( const path& p );
bool is_regular_file( const path& p, error_code& ec );

You use these with the following:

#include <filesystem>

...
    if(!std::filesystem::is_regular_file(path))
    {
        throw std::runtime_error(path + " was expected to be a regular file.");
    }
...

Find more details on cppreference.

(WARNING: some compilers say they have c++17 but filesystem may still be experimental in them; i.e. GNU C++ has the full version only since g++ v8.0, for details, see this question: Link errors using <filesystem> members in C++17)

like image 161
Alexis Wilke Avatar answered Oct 19 '22 20:10

Alexis Wilke


There are different approaches to solving this issue:

  1. Ignore it. Seriously, if the directory content passes as valid configuration, I'd be surprised. If not, parsing will fail anyway, so you run no risk of importing bad data. Also, you don't impede users from providing a pipe or something similar that is not a file.
  2. Check the path before opening it. You could use stat() or directly use Boost.Filesystem or some similar library. I'm not 100% sure if anything similar was added to C++11. Note that this creates a race condition though, because after you checking but before opening, some attacker could switch the file with a directory.
  3. Typically, there are ways to retrieve a low-level handle from the fstream, in your case probably a FILE*. There are also ways to create an iostream (not necessarily an fstream!) from a FILE*. Those are always implementation-specific extensions, so you need some #ifdef magic to tailor your code specific to the used stdlibrary implementation. I would dare to rely on their presence, even if not you can still create a streambuf on tof of a FILE* if you need to port to some obscure system that doesn't provide an easier way.
like image 29
Ulrich Eckhardt Avatar answered Oct 19 '22 19:10

Ulrich Eckhardt


void assertGoodFile(const char* fileName) {
   ifstream fileOrDir(fileName);
   //This will set the fail bit if fileName is a directory (or do nothing if it is already set  
   fileOrDir.seekg(0, ios::end);
   if( !fileOrDir.good()) {
      throw BadFile();
   };
}
like image 3
PSkocik Avatar answered Oct 19 '22 21:10

PSkocik