Wie kann ich am besten überprüfen, ob eine Datei in C vorhanden ist?

436

Gibt es einen besseren Weg als einfach zu versuchen, die Datei zu öffnen?

int exists(const char *fname)
{
    FILE *file;
    if ((file = fopen(fname, "r")))
    {
        fclose(file);
        return 1;
    }
    return 0;
}
Dave Marshall
quelle
Ich denke, ich werde die Antwort auf die Zugriffsmethode geben, obwohl die stat-Methode eine sehr vernünftige Alternative ist, erledigt access den Job.
Dave Marshall
1
Wollen Sie wirklich nur nach Existenz suchen? Oder möchten Sie überprüfen und in die Datei schreiben, wenn sie noch nicht vorhanden ist? Wenn ja, siehe meine Antwort unten für eine Version, die nicht unter Rennbedingungen leidet.
Dan Lenski
6
Ich verstehe nicht - was ist falsch an diesem offenen / geschlossenen Weg?
Johannes Schaub - Litb
16
@ JohannesSchaub-litb: Eine Sache, die mit der fopen()/ fclose()-Methode falsch ist, ist, dass Sie möglicherweise keine Datei zum Lesen öffnen können, obwohl sie existiert. Zum Beispiel /dev/kmemexistiert, aber die meisten Prozesse können es nicht einmal zum Lesen öffnen. /etc/shadowist eine andere solche Datei. Natürlich beides stat()und darauf access()angewiesen, auf das Verzeichnis zugreifen zu können, das die Datei enthält; Alle Wetten sind deaktiviert, wenn Sie dies nicht tun können (keine Ausführungsberechtigung für das Verzeichnis, das die Datei enthält).
Jonathan Leffler
1
if (file = fopen(fname, "r"))wird eine Warnung geben. Verwenden Sie Klammern um Anweisung innerhalb der if-Anweisungif ((file = fopen(fname, "r")))
Joakim

Antworten:

595

Suchen Sie nach der access()Funktion in unistd.h. Sie können Ihre Funktion durch ersetzen

if( access( fname, F_OK ) != -1 ) {
    // file exists
} else {
    // file doesn't exist
}

Sie können auch verwendet werden R_OK, W_OKund X_OKanstelle der F_OKfür die Leseberechtigung zu überprüfen, Schreibrechte und Ausführungsrechte (jeweils) statt Existenz, und Sie können oder ein von ihnen zusammen (dh sowohl für Prüflesevorgang und Schreibberechtigung mit R_OK|W_OK)

Update : Beachten Sie, dass Sie unter Windows nicht W_OKzuverlässig auf Schreibberechtigung testen können, da die Zugriffsfunktion DACLs nicht berücksichtigt. access( fname, W_OK )Möglicherweise wird 0 (Erfolg) zurückgegeben, da für die Datei kein schreibgeschütztes Attribut festgelegt ist, Sie jedoch möglicherweise noch keine Berechtigung zum Schreiben in die Datei haben.

Graeme Perrow
quelle
67
POSIX ist ein ISO-Standard. es definiert access (). C ist ein weiterer ISO-Standard; Es tut nicht.
Jonathan Leffler
16
Mit access () sind Fallstricke verbunden. Es gibt ein TOCTOU-Fenster (Zeitpunkt der Überprüfung, Zeitpunkt der Verwendung) der Sicherheitsanfälligkeit zwischen der Verwendung von access () und allem, was Sie danach tun. [... Fortsetzung
Jonathan Leffler
23
[... weiter ...] Eher esoterischer prüft access () auf POSIX-Systemen, ob die echte UID und die echte GID und nicht die effektive UID und die effektive GID. Dies ist nur für setuid- oder setgid-Programme von Bedeutung, aber dann ist es sehr wichtig, da es möglicherweise die "falsche" Antwort gibt.
Jonathan Leffler
3
Ich bin auf diese Frage gestoßen, als ich nach dem Grund gesucht habe, der access()meinen Code gebrochen hat. Ich bin von DevC ++ zu CodeBlocks gewechselt und es hat nicht mehr funktioniert. Es ist also nicht unfehlbar; +1 mehr an @Leffler.
Ben
11
Meistens ja (es ist in Ordnung, access()die Existenz einer Datei zu überprüfen), aber in einem SUID- oder SGID-Programm kann auch das falsch sein. Wenn sich die getestete Datei in einem Verzeichnis befindet, auf das die echte UID oder die echte GID nicht zugreifen kann, wird access()möglicherweise keine solche Datei gemeldet, wenn sie vorhanden ist. Esoterisch und unwahrscheinlich? Ja.
Jonathan Leffler
116

Verwenden Sie statwie folgt:

#include <sys/stat.h>   // stat
#include <stdbool.h>    // bool type

bool file_exists (char *filename) {
  struct stat   buffer;   
  return (stat (filename, &buffer) == 0);
}

und nenne es so:

#include <stdio.h>      // printf

int main(int ac, char **av) {
    if (ac != 2)
        return 1;

    if (file_exists(av[1]))
        printf("%s exists\n", av[1]);
    else
        printf("%s does not exist\n", av[1]);

    return 0;
}
Codebunny
quelle
4
@LudvigANorin: Auf solchen Systemen besteht die Möglichkeit, dass access()auch Probleme auftreten, und es gibt Optionen zum Erstellen access()und stat()Arbeiten mit großen Dateien (größer als 2 GB).
Jonathan Leffler
14
Könnte einer von Ihnen auf eine Dokumentation bezüglich des Fehlers nach 2 GB verweisen? Was ist in solchen Fällen die Alternative?
Chamakits
@ JonathanLeffler Leidet statnicht unter der gleichen TOCTOU-Sicherheitslücke wie access? (Mir ist nicht klar, dass es besser wäre.)
Telemachos
9
Sowohl stat()und access()leiden unter der TOCTOU Verwundbarkeit (so tut lstat(), aber fstat()ist sicher). Es hängt davon ab, was Sie tun werden, basierend auf dem Vorhandensein oder Fehlen der Datei. Die Verwendung der richtigen Optionen open()ist normalerweise der beste Weg, um mit den Problemen umzugehen, aber es kann schwierig sein, die richtigen Optionen zu formulieren. Siehe auch Diskussionen zu EAFP (leichter um Vergebung zu bitten als um Erlaubnis) und LBYL (Look Before You Leap) - siehe beispielsweise LBYL vs EAFP in Java .
Jonathan Leffler
87

Wenn Sie überprüfen möchten, ob eine Datei vorhanden ist, möchten Sie diese Datei normalerweise erstellen , wenn dies nicht der Fall ist. Die Antwort von Graeme Perrow ist gut, wenn Sie diese Datei nicht erstellen möchten, aber sie ist anfällig für eine Race-Bedingung, wenn Sie dies tun: Ein anderer Prozess könnte die Datei zwischen Ihnen erstellen, indem Sie prüfen, ob sie vorhanden ist, und Sie sie tatsächlich öffnen, um darauf zu schreiben . (Nicht lachen ... dies könnte schlechte Auswirkungen auf die Sicherheit haben, wenn die erstellte Datei ein Symlink ist!)

Wenn Sie die Existenz überprüfen und die Datei erstellen möchten, wenn sie nicht vorhanden ist, atomar, damit keine Rennbedingungen vorliegen, verwenden Sie Folgendes :

#include <fcntl.h>
#include <errno.h>

fd = open(pathname, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
if (fd < 0) {
  /* failure */
  if (errno == EEXIST) {
    /* the file already existed */
    ...
  }
} else {
  /* now you can use the file */
}
Dan Lenski
quelle
8
Wenn Sie O_CREAT verwenden möchten, müssen Sie den Modus (Berechtigungen) als drittes Argument für open () angeben. Überlegen Sie auch, ob O_TRUNC oder O_EXCL oder O_APPEND verwendet werden soll.
Jonathan Leffler
6
Jonathan Leffler hat recht. In diesem Beispiel muss O_EXCL wie geschrieben funktionieren.
Randy Proctor
6
Außerdem müssen Sie den Modus als drittes Argument angeben: open (lock, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR)
andrew cooke
4
Es ist zu beachten, dass dies nur so sicher ist, wie das Dateisystem POSIX-kompatibel ist. Insbesondere alte Versionen von NFS haben genau die Rennbedingung, die O_EXCL vermeiden sollte! Es gibt eine Problemumgehung, die in dokumentiert ist open(2)(unter Linux; die Manpages Ihres Betriebssystems können variieren), aber sie ist ziemlich hässlich und möglicherweise nicht resistent gegen einen böswilligen Angreifer.
Kevin
Beachten Sie, dass FILE*Sie dazu die Posix-Methode verwenden müssen fdopen(fd,"flags"), um einenFILE*
Gem Taylor
32

Ja. Verwenden Sie stat(). Siehe die Manpage für stat(2).

stat()schlägt fehl, wenn die Datei nicht vorhanden ist, andernfalls ist dies höchstwahrscheinlich erfolgreich. Wenn es vorhanden ist, Sie aber keinen Lesezugriff auf das Verzeichnis haben, in dem es vorhanden ist, schlägt es ebenfalls fehl. In diesem Fall schlägt jedoch jede Methode fehl (wie können Sie den Inhalt eines Verzeichnisses überprüfen, das Sie möglicherweise nicht nach Zugriffsrechten sehen? Einfach nicht).

Oh, wie jemand anderes erwähnt hat, können Sie auch verwenden access(). Ich bevorzuge jedoch stat(), dass die Datei, wenn sie existiert, sofort viele nützliche Informationen enthält (wann sie zuletzt aktualisiert wurde, wie groß sie ist, Eigentümer und / oder Gruppe, der die Datei gehört, Zugriffsberechtigungen usw.).

Mecki
quelle
5
Der Zugriff wird bevorzugt, wenn Sie nur wissen müssen, ob die Datei vorhanden ist. Stat () kann stark belauscht werden, wenn Sie nicht alle zusätzlichen Informationen benötigen.
Martin Beckett
4
Wenn ich ein Verzeichnis mit dem Befehl ls-list aufliste, ruft es stat für jede Datei auf, die dort vorhanden ist, und dass das Ausführen von ls einen großen Overhead hat, ist für mich ziemlich neu. Tatsächlich können Sie ls in Verzeichnissen mit Tausenden von Dateien ausführen und es wird in Sekundenbruchteilen zurückgegeben.
Mecki
2
@Mecki: stat hat einen zusätzlichen Overhead ungleich Null im Vergleich zum Zugriff auf Systeme, die Hardlinks unterstützen. Dies liegt daran, dass der Zugriff nur den Verzeichniseintrag anzeigen muss, während stat auch den Inode nachschlagen muss. Auf Speichergeräten mit schlechter Suchzeit (z. B. Band) kann der Unterschied erheblich sein, da der Verzeichniseintrag und der Inode wahrscheinlich nicht nebeneinander liegen.
Kevin
3
@Kevin Wenn Sie nicht nur F_OK übergeben, access()werden die Dateizugriffsberechtigungen einer Datei überprüft. Diese werden im Inode für diese Datei gespeichert und befinden sich nicht in ihrem Verzeichniseintrag (zumindest für alle Dateisysteme mit inodeähnlichen Strukturen). . So access()hat die Inode für den Zugriff auf genau die gleiche Art und Weise , die stat()es zuzugreifen. Was Sie also sagen, gilt nur, wenn Sie nicht nach Berechtigungen suchen! Und tatsächlich wird auf einigen Systemen access()sogar zusätzlich implementiert stat()(z. B. glibc auf GNU Hurd macht es so), so dass es überhaupt keine Garantie gibt.
Mecki
@Mecki: Wer hat etwas über das Überprüfen von Berechtigungen gesagt ? Ich habe speziell über F_OK gesprochen. Und ja, einige Systeme sind schlecht implementiert. Der Zugriff ist in jedem Fall mindestens so schnell wie stat und kann manchmal schneller sein.
Kevin
9
FILE *file;
    if((file = fopen("sample.txt","r"))!=NULL)
        {
            // file exists
            fclose(file);
        }
    else
        {
            //File not found, no memory leak since 'file' == NULL
            //fclose(file) would cause an error
        }
Mesutpiskin
quelle
1
Würde dies nicht zu einem Speicherverlust führen? Sie schließen die Datei niemals, wenn sie existiert.
LegionMammal978
1
Dies ist eine gute, einfache Methode. Wenn Sie sich in Windows MSVC befinden, verwenden Sie stattdessen Folgendes: (fopen_s(file, "sample.txt", "r"))da fopen()gilt als veraltet (oder deaktivieren Sie veraltete Fehler, dies wird jedoch nicht empfohlen).
Nikos
15
fopen()ist Standard C, es geht nirgendwo hin. Es ist nur von Microsoft "veraltet". Verwenden fopen_s()Sie diese Option nur, wenn Sie plattformspezifischen, nicht portablen Code möchten.
Andrew Henle
Fclose () umsonst aufrufen? Muss zuerst die Variable 'Datei' zuweisen!
Jenix
1
Die Variable 'Datei' hat hier einen Müllwert. Warum sollte man es überhaupt schließen? Sie rufen nur 'fclose (SOME_RANDOM_ADDRESS) an;' ..
Jenix
6

In der Visual C ++ - Hilfe würde ich eher mitgehen

/* ACCESS.C: This example uses _access to check the
 * file named "ACCESS.C" to see if it exists and if
 * writing is allowed.
 */

#include  <io.h>
#include  <stdio.h>
#include  <stdlib.h>

void main( void )
{
   /* Check for existence */
   if( (_access( "ACCESS.C", 0 )) != -1 )
   {
      printf( "File ACCESS.C exists\n" );
      /* Check for write permission */
      if( (_access( "ACCESS.C", 2 )) != -1 )
         printf( "File ACCESS.C has write permission\n" );
   }
}

Erwähnenswert sind auch die Moduswerte von :_access(const char *path,int mode)

  • 00: Nur Existenz

  • 02: Schreibberechtigung

  • 04: Erlaubnis lesen

  • 06: Lese- und Schreibberechtigung

Als Ihr fopenkönnte in Situationen versagen , wo die Datei existiert , aber nicht geöffnet , wie verlangt werden könnte.

Edit: Lies einfach Meckis Beitrag. stat()sieht aus wie ein ordentlicher Weg zu gehen. Ho hum.

SmacL
quelle
Der Zugriff wird bevorzugt, wenn Sie nur wissen müssen, ob die Datei vorhanden ist. Stat () kann stark belauscht werden.
Martin Beckett
4

Sie können die Funktion realpath () verwenden.

resolved_file = realpath(file_path, NULL);
if (!resolved_keyfile) {
   /*File dosn't exists*/
   perror(keyfile);
   return -1;
}
Bharath Reddy
quelle
3

Ich denke, dass die access () -Funktion, die in gefunden unistd.hwird, eine gute Wahl ist für Linux(Sie können auch stat verwenden ).

Sie können es so verwenden:

#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>

void fileCheck(const char *fileName);

int main (void) {
    char *fileName = "/etc/sudoers";

    fileCheck(fileName);
    return 0;
}

void fileCheck(const char *fileName){

    if(!access(fileName, F_OK )){
        printf("The File %s\t was Found\n",fileName);
    }else{
        printf("The File %s\t not Found\n",fileName);
    }

    if(!access(fileName, R_OK )){
        printf("The File %s\t can be read\n",fileName);
    }else{
        printf("The File %s\t cannot be read\n",fileName);
    }

    if(!access( fileName, W_OK )){
        printf("The File %s\t it can be Edited\n",fileName);
    }else{
        printf("The File %s\t it cannot be Edited\n",fileName);
    }

    if(!access( fileName, X_OK )){
        printf("The File %s\t is an Executable\n",fileName);
    }else{
        printf("The File %s\t is not an Executable\n",fileName);
    }
}

Und Sie erhalten die folgende Ausgabe:

The File /etc/sudoers    was Found
The File /etc/sudoers    cannot be read
The File /etc/sudoers    it cannot be Edited
The File /etc/sudoers    is not an Executable
Michi
quelle