Проверьте, является ли fstream файлом или каталогом

Я использую C++ Fstream для чтения файла конфигурации.

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

Прямо сейчас, если я передаю путь к каталогу, он молча игнорирует это. Например my_file.good() возвращает true, даже если my_filename это каталог. Поскольку это непреднамеренный ввод для моей программы, я хотел бы проверить его и выдать исключение.

Как проверить, является ли только что открытый fstream обычным файлом, каталогом или потоком?

Я не могу найти способ либо:

  • получить дескриптор файла из заданного ifstream.
  • используйте какой-то другой механизм, чтобы найти эту информацию в ifstream.

В некоторых обсуждениях на форуме было высказано предположение, что ни одно из них невозможно, поскольку это зависит от ОС и, следовательно, никогда не может быть частью стандарта 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. Отсюда и мой вопрос.

6 ответов

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

Начиная с C++17, у нас есть filesystem доступны нам (на основе boost::filesystem). Это будет намного более переносимым, чем что-либо еще в C++ (хотяstat() тоже очень хорошо работает, я думаю).

У вас есть две функции, одна из которых возвращает код ошибки, если он вам нужен.

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

Вы используете их со следующим:

#include <filesystem>

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

Узнайте больше о cppreference.

(ПРЕДУПРЕЖДЕНИЕ: некоторые компиляторы говорят, что у них есть C++17, но файловая система все еще может быть экспериментальной в них; т.е. GNU C++ имеет полную версию только с g++ v8.0, подробности см. В этом вопросе: Ошибки связи с использованием элементов в C++17)

Существуют разные подходы к решению этой проблемы:

  1. Игнорируй это. Серьезно, если содержимое каталога перейдет в правильную конфигурацию, я был бы удивлен. В противном случае синтаксический анализ все равно не удастся, поэтому вы не рискуете импортировать неверные данные. Кроме того, вы не мешаете пользователям предоставлять канал или что-то подобное, что не является файлом.
  2. Проверьте путь, прежде чем открыть его. Вы могли бы использовать stat() или напрямую используйте Boost.Filesystem или какую-то похожую библиотеку. Я не уверен на 100%, было ли что-то подобное добавлено в C++11. Обратите внимание, что это создает условие гонки, потому что после проверки, но перед открытием, некоторые злоумышленники могут переключить файл с каталогом.
  3. Как правило, есть способы получить дескриптор низкого уровня из fstream, в вашем случае, вероятно, FILE*, Есть также способы создания iostream (не обязательно fstream!) из FILE*, Это всегда зависящие от реализации расширения, поэтому вам нужны #ifdef магия, чтобы адаптировать ваш код, специфичный для используемой реализации stdlibrary. Я бы осмелился положиться на их присутствие, даже если вы не можете создать streambuf на самом деле FILE* если вам нужно портировать на какую-то непонятную систему, которая не предоставляет более простой способ.

Думать сложно, из-за OS-зависимости IO-операций.

Я попробовал несколько приемов на OS X 10.10.2, Linux 2.6.32 и FreeBSD 8.2-RELEASE (последние две - несколько более старые ОС, я использовал несколько старых виртуальных машин VirtualBox).

  • Я еще не нашел надежного метода. Если вы действительно хотите проверить, используйте stat() на пути, или старомодный C open() с fstat(),
  • seekg(0, std::ios::beg); метод, предложенный PSkocik, не работает для меня.
  • Для fstreams лучший способ - просто открыть и прочитать файл и дождаться ошибки.
  • И 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, открытие пути к каталогу просто возвращает двоичный gobbledy gook, при этом никаких исключений не возникает.

Это - реализация, которая, кажется, обрабатывает это несколько разумно для 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;
      }
   }
}

У меня была такая же проблема, я заменил ifstream на fstream, и это сработало.

ifstream предназначен только для чтения файла fstream предназначен для чтения и записи

fstream, вероятно, попытается записать в каталог, когда вы его откроете, что вызовет ошибку, которую вы можете поймать с помощью file.good() в операторе if:

Я изучаю C++ и столкнулся с той же проблемой, поэтому вот что мне помогло:

          std::ifstream   input_file(av[1]);
    if (!input_file)
        throw (OpenInfileError());

    if (input_file.is_open() && input_file.peek() == std::ifstream::traits_type::eof())
        throw (EmptyInfileError());

Первыйifоператор проверяет, существует ли он.
Второй проверяет, есть лиinput_fileпусто. Это также выдаст ошибку, если это папка.

Другие вопросы по тегам