¿Cuál es la mejor manera de verificar si existe un archivo en C?

436

¿Hay una mejor manera que simplemente intentar abrir el archivo?

int exists(const char *fname)
{
    FILE *file;
    if ((file = fopen(fname, "r")))
    {
        fclose(file);
        return 1;
    }
    return 0;
}
Dave Marshall
fuente
Creo que daré la respuesta al método de acceso, a pesar de que el método de estadísticas es una alternativa muy razonable, el acceso hace el trabajo.
Dave Marshall
1
¿ Realmente solo quieres verificar la existencia? ¿O desea verificar y escribir en el archivo si aún no existe? Si es así, vea mi respuesta a continuación, para una versión que no sufre condiciones de carrera.
Dan Lenski el
66
No veo, ¿qué hay de malo en esa manera de abrir / cerrar?
Johannes Schaub - litb
16
@ JohannesSchaub-litb: una cosa que está mal con el método fopen()/ fclose()es que es posible que no pueda abrir un archivo para leerlo aunque exista. Por ejemplo, /dev/kmemexiste, pero la mayoría de los procesos no pueden abrirlo ni siquiera para leer. /etc/shadowes otro de esos archivos. Por supuesto, tanto stat()y access()confiar en la posibilidad de acceder al directorio que contiene el archivo; todas las apuestas están desactivadas si no puede hacerlo (sin permiso de ejecución en el directorio que contiene el archivo).
Jonathan Leffler
1
if (file = fopen(fname, "r"))Dará una advertencia. Use paréntesis alrededor de la declaración dentro de la declaración ifif ((file = fopen(fname, "r")))
Joakim

Respuestas:

595

Busque la access()función, que se encuentra en unistd.h. Puede reemplazar su función con

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

También puede usar R_OK, W_OKy X_OKen lugar de F_OKverificar el permiso de lectura, el permiso de escritura y el permiso de ejecución (respectivamente) en lugar de la existencia, y puede O cualquiera de ellos juntos (es decir, verificar el permiso de lectura y escritura usando R_OK|W_OK)

Actualización : Tenga en cuenta que en Windows, no puede usar W_OKpara probar de forma confiable el permiso de escritura, ya que la función de acceso no tiene en cuenta las DACL. access( fname, W_OK )puede devolver 0 (correcto) porque el archivo no tiene establecido el atributo de solo lectura, pero es posible que aún no tenga permiso para escribir en el archivo.

Graeme Perrow
fuente
67
POSIX es un estándar ISO; define el acceso (). C es otro estándar ISO; no es asi.
Jonathan Leffler
16
Hay dificultades asociadas con el acceso (). Hay una ventana TOCTOU (tiempo de verificación, tiempo de uso) de vulnerabilidad entre el uso de access () y cualquier otra cosa que haga después. [... continuará ...]
Jonathan Leffler
23
[... continuando ...] Más esotéricamente, en los sistemas POSIX, access () verifica si el UID real y el GID real, en lugar del UID efectivo y el GID efectivo. Esto solo es importante para los programas setuid o setgid, pero luego importa intensamente, ya que puede dar la respuesta 'incorrecta'.
Jonathan Leffler
3
Me encontré con esta pregunta cuando busqué la razón que access()rompió mi código. Me mudé de DevC ++ a CodeBlocks y dejó de funcionar. Entonces, no es infalible; +1 más a @Leffler.
Ben
11
La mayoría de las veces, sí (está bien usar access()para verificar la existencia de un archivo), pero en un programa SUID o SGID, incluso eso podría ser incorrecto. Si el archivo probado está en un directorio al que el UID real o el GID real no pueden acceder, es access()posible que no se informe de dicho archivo cuando exista. ¿Esotérico e improbable? Si.
Jonathan Leffler
116

Usar statasí:

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

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

y llámalo así:

#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
fuente
44
@LudvigANorin: en tales sistemas, lo más probable es que access()también tenga problemas, y hay opciones para usar para crear access()y stat()trabajar con archivos grandes (más de 2 GB).
Jonathan Leffler
14
¿Alguno de ustedes podría señalar documentación relacionada con la falla después de 2 GB? Además, ¿cuál es la alternativa en tales casos?
chamakits
@JonathanLeffler ¿ statNo sufre la misma vulnerabilidad TOCTOU que access? (No está claro para mí que sería mejor.)
Telémaco
99
Ambos stat()y access()sufren la vulnerabilidad TOCTOU (también lo hace lstat(), pero fstat()es seguro). Depende de lo que va a hacer en función de la presencia o ausencia del archivo. Usar las opciones correctas open()suele ser la mejor manera de lidiar con los problemas, pero puede ser complicado formular las opciones correctas. Vea también las discusiones sobre EAFP (más fácil pedir perdón que permiso) y LBYL (mirar antes de saltar ); vea LBYL vs EAFP en Java , por ejemplo.
Jonathan Leffler
87

Por lo general, cuando desea verificar si existe un archivo, es porque desea crear ese archivo si no existe. La respuesta de Graeme Perrow es buena si no desea crear ese archivo, pero es vulnerable a una condición de carrera si lo hace: otro proceso podría crear el archivo entre usted comprobando si existe, y en realidad lo abre para escribir en él . (No se ría ... ¡esto podría tener malas implicaciones de seguridad si el archivo creado fuera un enlace simbólico!)

Si desea verificar la existencia y crear el archivo si no existe, atómicamente para que no haya condiciones de carrera, use esto:

#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
fuente
8
Si va a utilizar O_CREAT, debe proporcionar el modo (permisos) como tercer argumento para abrir (). También considere si se deben usar O_TRUNC u O_EXCL o O_APPEND.
Jonathan Leffler
66
Jonathan Leffler tiene razón, este ejemplo requiere que O_EXCL funcione como está escrito.
Randy Proctor
66
Además, debe especificar el modo como un tercer argumento: abierto (bloqueo, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR)
Andrew Cooke
44
Cabe señalar que esto es tan seguro como el sistema de archivos es compatible con POSIX; en particular, las versiones antiguas de NFS tienen la misma condición de carrera que se suponía que O_EXCL debía evitar. Hay una solución, documentada en open(2)(en Linux; las páginas de manual de su sistema operativo pueden variar), pero es bastante fea y puede no ser resistente a un atacante malintencionado.
Kevin
Tenga en cuenta que para usar esto FILE*, debe usar el método posix fdopen(fd,"flags")para generar unFILE*
Gem Taylor
32

Si. Uso stat(). Vea la página del manual para stat(2).

stat()fallará si el archivo no existe, de lo contrario lo más probable es que tenga éxito. Si existe, pero no tiene acceso de lectura al directorio donde existe, también fallará, pero en ese caso fallará cualquier método (¿cómo puede inspeccionar el contenido de un directorio que puede no ver de acuerdo con los derechos de acceso? Simplemente no puedes).

Oh, como alguien más mencionó, también puedes usar access(). Sin embargo, prefiero stat(), ya que si el archivo existe, inmediatamente me dará mucha información útil (cuándo se actualizó por última vez, qué tan grande es, el propietario y / o grupo que posee el archivo, los permisos de acceso, etc.).

Mecki
fuente
55
Se prefiere el acceso si solo necesita saber si el archivo existe. Stat () puede tener una gran audiencia si no necesita toda la información adicional.
Martin Beckett el
44
En realidad, cuando enumero un directorio usando ls-command, llama a stat para cada archivo que está presente allí y que ejecutar ls tiene una gran sobrecarga es bastante nuevo para mí. En realidad, puede ejecutar ls en directorios con miles de archivos y vuelve en una fracción de segundo.
Mecki
2
@Mecki: stat tiene una sobrecarga adicional distinta de cero en comparación con el acceso en sistemas que admiten enlaces duros. Esto se debe a que el acceso solo tiene que mirar la entrada del directorio, mientras que stat también tiene que buscar el inodo. En dispositivos de almacenamiento con mal tiempo de búsqueda (p. Ej., Cinta), la diferencia puede ser significativa ya que es poco probable que la entrada de directorio y el inodo estén uno al lado del otro.
Kevin
3
@Kevin A menos que solo le pase F_OK, access()verifica los permisos de acceso a archivos de un archivo y estos se almacenan en el inodo para ese archivo y no están en su entrada de directorio (al menos para todos los sistemas de archivos que tienen estructuras tipo inodo) . Entonces access()tiene que acceder al inodo exactamente de la misma manera que stat()tiene que acceder a él. ¡Entonces lo que dices solo es cierto si no verificas los permisos! Y en realidad, en algunos sistemas access()incluso se implementa por encima stat()(por ejemplo, glibc en GNU Hurd lo hace de esa manera), por lo que no hay garantía en primer lugar.
Mecki
@Mecki: ¿Quién dijo algo sobre la verificación de permisos? Estaba hablando específicamente de F_OK. Y sí, algunos sistemas están mal implementados. El acceso será al menos tan rápido como la estadística en todos los casos y puede ser más rápido algunas veces.
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
fuente
1
¿No causaría esto una pérdida de memoria? Nunca cierra el archivo si existe.
LegionMammal978
1
Este es un buen método simple. Si está en Windows MSVC, use esto en su lugar: (fopen_s(file, "sample.txt", "r"))ya que fopen()se considera obsoleto (o deshabilite los errores obsoletos, pero eso no se recomienda).
Nikos
15
fopen()es estándar C, no va a ninguna parte. Solo está "en desuso" por Microsoft. No lo use a fopen_s()menos que desee un código no portátil específico de la plataforma.
Andrew Henle
¿Llamar a fclose () por nada? ¡Necesario para asignar la variable 'archivo' primero!
Jenix
1
La variable 'archivo' aquí tiene un valor basura. ¿Por qué molestarse en cerrarlo en primer lugar? Simplemente estás llamando 'fclose (SOME_RANDOM_ADDRESS);' ..
Jenix
6

De la ayuda de Visual C ++, tendería a ir con

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

También vale la pena señalar los valores de modo de :_access(const char *path,int mode)

  • 00: solo existencia

  • 02: Escribir permiso

  • 04: permiso de lectura

  • 06: permiso de lectura y escritura

Como fopenpodría fallar en situaciones donde el archivo existía pero no se podía abrir como se solicitó.

Editar: Acabo de leer la publicación de Mecki. stat()parece un camino más ordenado. Ho hum.

SmacL
fuente
Se prefiere el acceso si solo necesita saber si el archivo existe. Stat () puede tener una gran escucha.
Martin Beckett el
4

Puede usar la función realpath ().

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

Creo que la función access () , que se encuentra en unistd.hes una buena opción para Linux(también puede usar stat ).

Puedes usarlo así:

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

Y obtienes el siguiente resultado:

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
fuente