2015-03-27 17 views
7

Sto usando C++ fstream per leggere un file di configurazione.Controlla se un fstream è un file o una directory

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

In questo momento, se si passa il percorso di una directory, silenziosamente ignora questo. Per esempio. my_file.good() restituisce true, anche se my_filename è una directory. Dato che questo è un input non voluto per il mio programma, mi piace controllarlo e lanciare un'eccezione.

Come faccio a verificare se un fstream appena aperto è un file, una directory o uno stream normale?

io non riesco a trovare un modo per entrambi:

  • ottenere il descrittore di file da un dato ifstream.
  • utilizzare qualche altro meccanismo per trovare queste informazioni nel ifstream.

In some forum discussion è stato suggerito che nessuno dei due è possibile perché questo è dipendente dal sistema operativo, e quindi non potrebbe mai essere parte dello standard di flickr C++.

L'unica alternativa che posso pensare è quello di riscrivere il mio codice per sbarazzarsi di ifstream del tutto e ricorrere al C-metodo di un descrittore di file (*fp), insieme a 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); 
} 

preferisco fstream. Quindi, la mia domanda.

+0

Quale implementazione di libreria standard e su quale sistema operativo? Trovo difficile credere che qualcosa possa andare storto come descrivi. 'good()' dovrebbe restituire false se 'myfilename' è una directory. –

+0

Sto usando OS X, 10.10, C++ 11. – MacFreek

+1

Credo che le vecchie (o, sembra, non così vecchie) derivate da Unix permettessero di aprire le directory come file per leggere il loro contenuto, quindi mentre questo è sorprendente, non è così sorprendente. BTW: Qual è il contenuto di quella directory se lo leggi con 'fstream'? –

risposta

1

Ci sono diversi approcci per risolvere questo problema:

  1. ignorarlo. Seriamente, se il contenuto della directory passa come configurazione valida, sarei sorpreso. In caso contrario, l'analisi fallirà comunque, quindi non si corre il rischio di importare dati errati. Inoltre, non impedisci agli utenti di fornire una pipe o qualcosa di simile che non sia un file.
  2. Controllare il percorso prima di aprirlo. È possibile utilizzare stat() o utilizzare direttamente Boost.Filesystem o una libreria simile. Non sono sicuro al 100% se qualcosa di simile sia stato aggiunto a C++ 11. Si noti che questo crea una condizione di competizione, perché, dopo aver controllato ma prima di aprire, un utente malintenzionato potrebbe cambiare il file con una directory.
  3. In genere, è possibile recuperare un handle di livello basso da fstream, nel tuo caso probabilmente uno FILE*. Ci sono anche modi per creare un iostream (non necessariamente uno fstream!) Da un FILE*. Si tratta sempre di estensioni specifiche dell'implementazione, quindi è necessario un po 'di magia #ifdef per adattare il codice specifico all'implementazione stdlibrary utilizzata. Oserei fare affidamento sulla loro presenza, anche se non è ancora possibile creare un streambuf su un numero FILE* se è necessario effettuare il porting su un sistema oscuro che non fornisce un modo più semplice.
+1

Non è stato definitivamente aggiunto a C++ 11 né C++ 14 né sono a conoscenza di piani seri per l'inserimento nel prossimo futuro. Nei commenti dice anche che 'open()' riesce e legge il contenuto dei file nella cartella. –

+0

Vedere 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

Sfortunatamente, questo non ha funzionato per me su Linux 2.6.32-042stab092.3 i686, né su Darwin 14.1.0 x86_64. – MacFreek

+1

Il mio male, non mi ha ricompilato per il test 'ios :: beg' quindi stavo ancora eseguendo il binario compilato con ios :: end. Ma 'seekg (0, ios :: end)' funziona per me per raccontare directory e file separati. Sfortunatamente, quello probabilmente getterà anche per tubi. – PSkocik

0

Pensa sono complicate, a causa del sistema operativo dipendenza degli IO-operazioni.

Ho provato alcune tecniche su OS X 10.10.2, Linux 2.6.32 e FreeBSD 8.2-RELEASE (i due successivi sono sistemi meno recenti, ho usato alcuni VirtualBox VM meno recenti).

  • Non ho ancora trovato un metodo infallibile. Se si desidera veramente controllare, utilizzare stat() sul percorso o C open() vecchio stile con fstat().
  • Il metodo seekg(0, std::ios::beg); suggerito da PSkocik non ha funzionato per me.
  • Per gli stream, il metodo migliore è solo quello di aprire e leggere dal file e attendere un errore.
  • È necessario impostare sia il bitbit che il failbit, in particolare su OS X, per aumentare tutte le eccezioni richieste. Per esempio. my_file.exceptions(std::ios::failbit | std::ios::badbit);
  • Ciò causa anche un'eccezione di fine file (EOF) dopo la lettura di un file normale, che richiede che il codice ignori queste normali eccezioni.
  • my_file.eof() può essere impostato anche su errori più gravi, quindi è un controllo insufficiente per la condizione EOF.
  • errno è un indicatore migliore: se viene sollevata un'eccezione, ma errno è ancora 0, è molto probabile una condizione EOF.
  • Questo non è sempre vero. Su FreeBSD 8.2, l'apertura di un percorso di directory restituisce semplicemente gobbledygook binario, mentre non vengono mai sollevate eccezioni.

Questa è l'implementazione che sembra gestirla in modo abbastanza ragionevole tra le 3 piattaforme che ho testato.

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