首页 文章

检查fstream是文件还是目录

提问于
浏览
7

我正在使用C fstream来读取配置文件 .

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

现在,如果我传递一个目录的路径,它会默默地忽略它 . 例如 . my_file.good() 返回true,即使 my_filename 是目录 . 由于这是我的程序的意外输入,我喜欢检查它,并抛出异常 .

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

我似乎无法找到一种方法:

  • 从给定的ifstream获取文件描述符 .

  • 使用其他一些机制在ifstream中查找此信息 .

some forum discussion中,有人认为这两者都不可能,因为这是依赖于操作系统的,因此永远不会成为fstream C标准的一部分 .

我能想到的唯一选择是重写我的代码以完全摆脱ifstream并使用文件描述符的C方法( *fp ),以及 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);
}

我更喜欢fstream . 因此,我的问题 .

3 回答

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

    有不同的方法来解决这个问题:

    • 忽略它 . 说真的,如果目录内容作为有效配置传递,我阻止用户提供管道或类似的非文件 .

    • 打开前检查路径 . 您可以使用 stat() 或直接使用Boost.Filesystem或类似的库 . 我不是100%确定是否在C 11中添加了类似内容 . 请注意,这会产生竞争条件,因为在您检查之后但在打开之前,某些攻击者可能会使用目录切换文件 .

    • 通常,有一些方法可以从 fstream 中检索低级句柄,在您的情况下可能是 FILE* . 还有一些方法可以从 FILE* 创建 iostream (不一定是 fstream !) . 这些都是特定于实现的扩展,因此您需要一些 #ifdef 魔术来定制特定于已使用的stdlibrary实现的代码 . 我敢于依赖他们的存在,即使不是你仍然可以创建一个 streambuf ,如果你需要移植到一个不提供更简单方法的模糊系统 .

  • 1

    由于IO操作的操作系统依赖性,因此认为很复杂 .

    我在OS X 10.10.2,Linux 2.6.32和FreeBSD 8.2-RELEASE上尝试了一些技术(后两个是稍微旧的操作系统,我使用了一些较旧的VirtualBox VM) .

    • 我还没有找到一种万无一失的方法 . 如果你真的想检查,请在路径上使用 stat() ,或者使用 fstat() 老式的C open() .

    • PSkocik建议的 seekg(0, std::ios::beg); 方法对我不起作用 .

    • 对于fstream,最好的方法是打开文件并从中读取,并等待错误 .

    • 必须设置badbit和failbit,特别是在OS X上,以便引发所有必需的异常 . 例如 . my_file.exceptions(std::ios::failbit | std::ios::badbit);

    • 这也会在读取常规文件后导致文件结束(EOF)异常,这需要代码忽略这些正常异常 .

    • my_file.eof() 也可能因更严重的错误而设置,因此对EOF条件的检查很差 .

    • errno 是一个更好的指标:如果引发异常,但是errno仍为0,则很可能是EOF条件 .

    • 这并非总是如此 . 在FreeBSD 8.2上,打开一个目录路径只返回二进制gobbledygook,而没有引发异常 .

    这个实现似乎在我测试过的3个平台上处理得有些合理 .

    #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;
          }
       }
    }
    

相关问题