Quelle est la meilleure façon de vérifier si un fichier existe en C?

436

Existe-t-il un meilleur moyen que d'essayer simplement d'ouvrir le fichier?

int exists(const char *fname)
{
    FILE *file;
    if ((file = fopen(fname, "r")))
    {
        fclose(file);
        return 1;
    }
    return 0;
}
Dave Marshall
la source
Je pense que je vais donner la réponse à la méthode d'accès, bien que la méthode stat soit une alternative très raisonnable, l'accès fait le travail.
Dave Marshall
1
Voulez-vous vraiment simplement vérifier l'existence? Ou voulez-vous vérifier et écrire dans le fichier s'il n'existe pas déjà. Si oui, voir ma réponse ci-dessous, pour une version qui ne souffre pas des conditions de course.
Dan Lenski
6
je ne vois pas - quel est le problème avec cette manière fopen / fclose?
Johannes Schaub - litb
16
@ JohannesSchaub-litb: une chose qui ne va pas avec la méthode fopen()/ fclose()est que vous ne pourrez peut-être pas ouvrir un fichier en lecture même s'il existe. Par exemple, /dev/kmemexiste, mais la plupart des processus ne peuvent pas l'ouvrir même pour la lecture. /etc/shadowest un autre de ces fichiers. Bien sûr, les deux stat()et access()dépendent de pouvoir accéder au répertoire contenant le fichier; tous les paris sont désactivés si vous ne pouvez pas le faire (aucune autorisation d'exécution sur le répertoire contenant le fichier).
Jonathan Leffler
1
if (file = fopen(fname, "r"))donnera un avertissement. Utilisez des parenthèses autour de l'instruction à l'intérieur de l'instruction ifif ((file = fopen(fname, "r")))
Joakim

Réponses:

595

Recherchez la access()fonction, trouvée dans unistd.h. Vous pouvez remplacer votre fonction par

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

Vous pouvez également utiliser R_OK, W_OKet X_OKà la place de F_OKpour vérifier l'autorisation de lecture, l'autorisation d'écriture et exécuter l'autorisation (respectivement) plutôt que l'existence, et vous pouvez OU l'un ou l'autre ensemble (c.-à-d. Vérifier les autorisations de lecture et d' écriture à l'aide R_OK|W_OK)

Mise à jour : Notez que sous Windows, vous ne pouvez pas utiliser W_OKpour tester de manière fiable l'autorisation d'écriture, car la fonction d'accès ne prend pas en compte les DACL. access( fname, W_OK )peut renvoyer 0 (succès) car le fichier n'a pas l'attribut en lecture seule défini, mais vous ne pouvez toujours pas avoir l'autorisation d'écrire dans le fichier.

Graeme Perrow
la source
67
POSIX est une norme ISO; il définit access (). C est une autre norme ISO; ce ne est pas.
Jonathan Leffler
16
Il y a des pièges associés à access (). Il existe une fenêtre de vulnérabilité TOCTOU (heure de vérification, heure d'utilisation) entre l'utilisation de access () et tout ce que vous faites ensuite. [... à suivre ...]
Jonathan Leffler
23
[... continue ...] De façon plus ésotérique, sur les systèmes POSIX, access () vérifie si le vrai UID et le vrai GID, plutôt que le UID effectif et le GID effectif. Cela n'a d'importance que pour les programmes setuid ou setgid, mais ensuite cela a beaucoup d'importance car il peut donner la «mauvaise» réponse.
Jonathan Leffler,
3
J'ai rencontré cette question en cherchant la raison de la access()rupture de mon code. Je suis passé de DevC ++ à CodeBlocks et cela a cessé de fonctionner. Donc, ce n'est pas infaillible; +1 de plus à @Leffler.
Ben
11
La plupart du temps, oui (il est correct de l'utiliser access()pour vérifier l'existence d'un fichier), mais dans un programme SUID ou SGID, même cela peut être incorrect. Si le fichier testé se trouve dans un répertoire auquel le véritable UID ou le vrai GID ne peut pas accéder, il se access()peut qu'il ne signale aucun fichier de ce type lorsqu'il existe. Ésotérique et improbable? Oui.
Jonathan Leffler
116

Utilisez statcomme ceci:

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

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

et appelez-le comme ceci:

#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
la source
4
@LudvigANorin: sur de tels systèmes, il y a des chances que cela access()pose également des problèmes, et il existe des options à utiliser pour créer access()et stat()travailler avec des fichiers volumineux (plus de 2 Go).
Jonathan Leffler
14
L'un de vous pourrait-il indiquer la documentation concernant l'échec après 2 Go? Quelle est également l'alternative dans de tels cas?
chamakits
@JonathanLeffler Ne statsouffre pas de la même vulnérabilité TOCTOU que access? (Ce n'est pas clair pour moi que ce serait mieux.)
Télémaque
9
Les deux stat()et access()souffrent de la vulnérabilité TOCTOU (il en va de même lstat(), mais fstat()c'est sûr). Cela dépend de ce que vous allez faire en fonction de la présence ou de l'absence du fichier. L'utilisation des bonnes options open()est généralement la meilleure façon de résoudre les problèmes, mais il peut être difficile de formuler les bonnes options. Voir aussi les discussions sur EAFP (plus facile à demander pardon que permission) et LBYL (Look Before You Leap) - voir LBYL vs EAFP en Java , par exemple.
Jonathan Leffler
87

Habituellement, lorsque vous souhaitez vérifier si un fichier existe, c'est parce que vous souhaitez créer ce fichier s'il ne l'est pas. La réponse de Graeme Perrow est bonne si vous ne voulez pas créer ce fichier, mais elle est vulnérable à une condition de concurrence si vous le faites: un autre processus pourrait créer le fichier entre vous en vérifiant s'il existe, et vous l'ouvrez réellement pour y écrire . (Ne riez pas ... cela pourrait avoir de mauvaises implications sur la sécurité si le fichier créé était un lien symbolique!)

Si vous souhaitez vérifier l'existence et créer le fichier s'il n'existe pas, de manière atomique afin qu'il n'y ait pas de conditions de concurrence, alors utilisez ceci:

#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
la source
8
Si vous allez utiliser O_CREAT, vous devez fournir le mode (autorisations) comme troisième argument pour ouvrir (). Vérifiez également si O_TRUNC ou O_EXCL ou O_APPEND doit être utilisé.
Jonathan Leffler
6
Jonathan Leffler a raison, cet exemple nécessite que O_EXCL fonctionne comme écrit.
Randy Proctor
6
De plus, vous devez spécifier le mode comme troisième argument: open (lock, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR)
andrew cooke
4
Il convient de noter que ce n'est aussi sûr que le système de fichiers est compatible POSIX; en particulier, les anciennes versions de NFS ont la condition de concurrence même que O_EXCL était censée éviter! Il existe une solution de contournement, documentée dans open(2)(sous Linux; les pages de manuel de votre système d'exploitation peuvent varier), mais elles sont plutôt laides et peuvent ne pas résister à un attaquant malveillant.
Kevin
Notez que pour l'utiliser avec FILE*, vous devez ensuite utiliser la méthode posix fdopen(fd,"flags")pour générer unFILE*
Gem Taylor
32

Oui. Utilisez stat(). Voir la page de manuel pour stat(2).

stat()échouera si le fichier n'existe pas, sinon il est fort probable qu'il réussisse. S'il existe, mais que vous n'avez pas d'accès en lecture au répertoire où il se trouve, il échouera également, mais dans ce cas, n'importe quelle méthode échouera (comment pouvez-vous inspecter le contenu d'un répertoire que vous ne voyez pas selon les droits d'accès? Tout simplement, vous ne pouvez pas).

Oh, comme quelqu'un l'a mentionné, vous pouvez également l'utiliser access(). Cependant, je préfère stat(), car si le fichier existe, il me fournira immédiatement beaucoup d'informations utiles (quand a-t-il été mis à jour pour la dernière fois, quelle est sa taille, propriétaire et / ou groupe propriétaire du fichier, autorisations d'accès, etc.).

Mecki
la source
5
l'accès est préféré si vous avez seulement besoin de savoir si le fichier existe. Stat () peut avoir une grande écoute si vous n'avez pas besoin de toutes les informations supplémentaires.
Martin Beckett
4
En fait, lorsque j'énumère un répertoire à l'aide de la commande ls, il appelle stat pour chaque fichier qui y est présent et le fait que l'exécution de ls présente une surcharge importante est assez nouveau pour moi. En fait, vous pouvez exécuter ls sur des répertoires contenant des milliers de fichiers et il revient en une fraction de seconde.
Mecki
2
@Mecki: stat a une surcharge supplémentaire non nulle par rapport à l'accès sur les systèmes qui prennent en charge les liens physiques. En effet, l'accès n'a qu'à regarder l'entrée du répertoire, tandis que stat doit également rechercher l'inode. Sur les périphériques de stockage avec un mauvais temps de recherche (par exemple une bande), la différence peut être significative car l'entrée de répertoire et l'inode sont peu susceptibles d'être côte à côte.
Kevin
3
@Kevin Sauf si vous ne lui passez que F_OK, access()vérifie les autorisations d'accès aux fichiers d'un fichier et celles-ci sont stockées dans l'inode pour ce fichier et ne sont pas dans son entrée de répertoire (au moins pour tous les systèmes de fichiers qui ont des structures de type inode) . Il access()faut donc accéder à l'inode exactement de la même manière que stat()pour y accéder. Donc, ce que vous dites n'est vrai que si vous ne vérifiez aucune autorisation! Et en fait sur certains systèmes access()est même implémenté par dessus stat()(par exemple la glibc sur GNU Hurd le fait de cette façon), donc il n'y a aucune garantie en premier lieu.
Mecki
@Mecki: Qui a dit quoi que ce soit sur la vérification des autorisations? Je parlais spécifiquement de F_OK. Et oui, certains systèmes sont mal implémentés. L'accès sera au moins aussi rapide que stat dans tous les cas et peut parfois être plus rapide.
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
la source
1
Cela ne provoquerait-il pas une fuite de mémoire? Vous ne fermez jamais le fichier s'il existe.
LegionMammal978
1
Il s'agit d'une bonne méthode simple. Si vous êtes sous Windows MSVC, utilisez plutôt: (fopen_s(file, "sample.txt", "r"))car fopen()est considéré comme obsolète (ou désactiver les erreurs dépréciées, mais ce n'est pas recommandé).
Nikos
15
fopen()est le standard C, ça ne va nulle part. Il est seulement "déconseillé" par Microsoft. Ne l'utilisez fopen_s()que si vous souhaitez un code non portable spécifique à la plate-forme.
Andrew Henle
Vous appelez fclose () pour rien? Nécessaire pour attribuer la variable «fichier» en premier!
Jenix
1
La variable «fichier» a ici une valeur de poubelle. Pourquoi prendre la peine de le fermer en premier lieu? Vous appelez simplement 'fclose (SOME_RANDOM_ADDRESS);' ..
Jenix
6

De l'aide de Visual C ++, j'aurais tendance à aller avec

/* 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" );
   }
}

Il convient également de noter les valeurs de mode de :_access(const char *path,int mode)

  • 00: Existence uniquement

  • 02: autorisation d'écriture

  • 04: autorisation de lecture

  • 06: autorisation de lecture et d'écriture

Comme votre fopen pourrait échouer dans des situations où le fichier existait mais n'a pas pu être ouvert comme demandé.

Edit: Il suffit de lire le post de Mecki. stat()ressemble à une façon plus nette d'aller. Ho hum.

SmacL
la source
l'accès est préféré si vous avez seulement besoin de savoir si le fichier existe. Stat () peut avoir une grande écoute.
Martin Beckett
4

Vous pouvez utiliser la fonction realpath ().

resolved_file = realpath(file_path, NULL);
if (!resolved_keyfile) {
   /*File dosn't exists*/
   perror(keyfile);
   return -1;
}
bharath reddy
la source
3

Je pense que la fonction access () , qui se trouve dans, unistd.hest un bon choix pour Linux(vous pouvez aussi utiliser stat ).

Vous pouvez l'utiliser comme ceci:

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

Et vous obtenez la sortie suivante:

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
la source