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


    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();
       };
    }
    

    There are different approaches to solving this issue:

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

  • Thinks are complicated, due to the OS-dependency of the IO-operations.

    I tried a few techniques on OS X 10.10.2, Linux 2.6.32, and FreeBSD 8.2-RELEASE (the later two are slightly older OSes, I used some older VirtualBox VMs).

  • I have not yet found a fool-proof method. If you really want to check, use stat() on the path, or the old-fashioned C open() with fstat() .
  • The seekg(0, std::ios::beg); method suggested by PSkocik did not work for me.
  • For fstreams, the best method is just to open and read from the file, and await an error.
  • Both the badbit AND failbit must be set, particular on OS X, in order to raise all required exceptions. Eg my_file.exceptions(std::ios::failbit | std::ios::badbit);
  • This also causes an end-of-file (EOF) exception after reading a regular file, which requires code to ignore these normal exceptions.
  • my_file.eof() may also be set upon more serious errors, so it is a poor check for the EOF condition.
  • errno is a better indicator: if an exception is raised, but errno is still 0, it is most likely a EOF condition.
  • This is not always true. On FreeBSD 8.2, opening a directory path just returns binary gobbledygook, while no exceptions are ever raised.
  • This is the implementation that seems to handle it somewhat reasonable across the 3 platforms I've tested.

    #include < iostream>
    #include < fstream>
    #include < cerrno>
    #include < cstring>
    
    int main(int argc, char *argv[]) {
       for (int i = 1; i < argc; i++) {
    
          std::ifstream my_file;
          try {
             // Ensure that my_file throws an exception if the fail or bad bit is set.
             my_file.exceptions(std::ios::failbit | std::ios::badbit);
             std::cout << "Read file '" << argv[i] << "'" << std::endl;
             my_file.open(argv[i]);
             my_file.seekg(0, std::ios::end);
          } catch (std::ios_base::failure& err) {
             std::cerr << "  Exception during open(): " << argv[i] << ": " << strerror(errno) << std::endl;
             continue;
          }
    
          try {
             errno = 0; // reset errno before I/O operation.
             std::string line;
             while (std::getline(my_file, line))
             {
                std::cout << "  read line" << std::endl;
                // ...
             }
          } catch (std::ios_base::failure& err) {
             if (errno == 0) {
                std::cerr << "  Exception during read(), but errono is 0. No real error." << std::endl;
                continue; // exception is likely raised due to EOF, no real error.
             }
             std::cerr << "  Exception during read(): " << argv[i] << ": " << strerror(errno) << std::endl;
          }
       }
    }
    
    链接地址: http://www.djcxy.com/p/84246.html

    上一篇: 代码和私人成员

    下一篇: 检查fstream是文件还是目录