2015-03-27 4 views
7

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

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

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

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

Я не могу показаться, чтобы найти способ либо:

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

В some forum discussion было высказано предположение, что это невозможно, поскольку это зависит от ОС и, следовательно, никогда не может быть частью стандартного стандарта C++.

Единственной альтернативой я могу думать о том, чтобы переписать свой код, чтобы избавиться от ifstream вообще и прибегать к С-методом дескриптора файла (*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. Следовательно, мой вопрос.

+0

Какая стандартная реализация библиотеки и какая операционная система? Мне трудно поверить, что что-то может пойти так же плохо, как вы описываете. 'good()' должен возвращать false, если 'myfilename' является каталогом. –

+0

Я использую OS X, 10.10, C++ 11. – MacFreek

+1

Я считаю, что старые (или, кажется, не такие старые) Unix-производные разрешали открывать каталоги как файлы для чтения их содержимого, поэтому, хотя это удивительно, это не удивительно. BTW: Каково содержимое этого каталога, если вы читаете его с помощью 'fstream'? –

ответ

1

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

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

Это было явно не добавлено в C++ 11 или C++ 14, и я не знаю серьезных планов по его включению в ближайшем будущем. В комментариях он говорит, что 'open()' также преуспевает, и читает содержимое файлов в папке. –

+0

См. Http://en.cppreference.com/w/cpp/experimental/fs – Julian

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

К сожалению, это не сработало для меня ни на Linux 2.6.32-042stab092.3 i686, ни на Darwin 14.1.0 x86_64. – MacFreek

+1

Мой плохой, он не перекомпилировался для меня для теста «ios :: beg», поэтому я все еще работал с двоичным файлом, скомпилированным с ios :: end. Но 'seekg (0, ios :: end)' работает для меня для разметки каталогов и файлов. К сожалению, тот будет зондировать и для труб. – PSkocik

0

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

Это реализация, которая, по-видимому, справляется с этим несколько разумно на трех платформах, которые я тестировал.

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