Какой лучший способ проверить, существует ли файл в C?

436

Есть ли лучший способ, чем просто попытаться открыть файл?

int exists(const char *fname)
{
    FILE *file;
    if ((file = fopen(fname, "r")))
    {
        fclose(file);
        return 1;
    }
    return 0;
}
Дейв Маршалл
источник
Я думаю, что я дам ответ на метод доступа, несмотря на то, что метод stat является очень разумной альтернативой, доступ выполняет свою работу.
Дэйв Маршалл
1
Вы действительно хотите проверить существование? Или вы хотите проверить и записать файл, если он еще не существует. Если это так, см. Мой ответ ниже, для версии, которая не страдает от условий гонки.
Дэн Ленски
6
я не вижу - что не так с этим способом fopen / fclose?
Йоханнес Шауб -
16
@ JohannesSchaub-litb: одна из особенностей метода fopen()/ fclose()заключается в том, что вы не сможете открыть файл для чтения, даже если он существует. Например, /dev/kmemсуществует, но большинство процессов не может открыть его даже для чтения. /etc/shadowеще один такой файл. Конечно, оба stat()и access()полагаются на возможность доступа к каталогу, содержащему файл; все ставки отключены, если вы не можете сделать это (нет разрешения на выполнение для каталога, содержащего файл).
Джонатан Леффлер
1
if (file = fopen(fname, "r"))даст предупреждение. Используйте круглые скобки в операторе ifif ((file = fopen(fname, "r")))
Joakim

Ответы:

595

Посмотрите на access()функцию, найденную в unistd.h. Вы можете заменить свою функцию на

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

Вы также можете использовать R_OK, W_OKи X_OKвместо того F_OKчтобы проверить для чтения разрешения, разрешений на запись и выполнение разрешения (соответственно) , а не существование, и вы можете или любые из них вместе (т.е. проверить , как для чтения и разрешения записи с помощью R_OK|W_OK)

Обновление : обратите внимание, что в Windows вы не можете использовать W_OKнадежный тест для разрешения записи, так как функция доступа не учитывает DACL. access( fname, W_OK )может вернуть 0 (успех), поскольку файл не имеет установленного атрибута только для чтения, но у вас все еще может не быть разрешения на запись в файл.

Грэм Перроу
источник
67
POSIX является стандартом ISO; это определяет доступ (). C - другой стандарт ISO; Это не.
Джонатан Леффлер
16
Есть ошибки, связанные с доступом (). Существует окно уязвимости TOCTOU (время проверки, время использования) между использованием access () и тем, что вы делаете позже. [... продолжение следует ...]
Джонатан Леффлер
23
[... продолжение ...] Скорее более эзотерически, в системах POSIX access () проверяет, является ли реальный UID и реальный GID, а не эффективным UID и эффективным GID. Это имеет значение только для setuid или setgid программ, но тогда это имеет большое значение, так как может дать «неправильный» ответ.
Джонатан Леффлер
3
Я наткнулся на этот вопрос, когда искал причину access()взлома моего кода. Я перешел с DevC ++ на CodeBlocks, и он перестал работать. Так что это не безошибочно; +1 больше к @Leffler.
Бен
11
В большинстве случаев да (это нормально использовать access()для проверки существования файла), но в программах SUID или SGID даже это может быть неверно. Если проверенный файл находится в каталоге, к которому не может получить доступ настоящий UID или реальный GID, access()такой файл может не появиться, если он существует. Эзотерический и невероятный? Да.
Джонатан Леффлер
116

Используйте statкак это:

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

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

и назовите это так:

#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
источник
4
@LudvigANorin: есть вероятность, что в таких системах access()также есть проблемы, и есть варианты, которые можно использовать для создания access()и stat()работы с большими файлами (более 2 ГБ).
Джонатан Леффлер
14
Может ли кто-нибудь из вас указать на документацию о сбое после 2 ГБ? Кроме того, какова альтернатива в таких случаях?
Chamakits
@JonathanLeffler Не statстрадает от такой же уязвимости TOCTOU, как access? (Мне не ясно, что было бы лучше.)
Телемах
9
Оба stat()и access()страдают от уязвимости TOCTOU (так же lstat(), но fstat()безопасно). Это зависит от того, что вы собираетесь делать, в зависимости от наличия или отсутствия файла. Использование правильных опций open()обычно является лучшим способом решения проблем, но может быть сложно сформулировать правильные варианты. См. Также обсуждения EAFP (Проще просить прощения, чем разрешения) и LBYL (Посмотри, прежде чем ты прыгнешь ) - см., Например, LBYL против EAFP в Java .
Джонатан Леффлер
87

Обычно, когда вы хотите проверить, существует ли файл, это потому, что вы хотите создать этот файл, если он не существует. Ответ Грэма Перроу хорош, если вы не хотите создавать этот файл, но он уязвим для состояния гонки, если вы это сделаете: другой процесс может создать файл между вами, проверив, существует ли он, и вы фактически открываете его, чтобы записать в него , (Не смейтесь ... это может иметь плохие последствия для безопасности, если созданный файл был символической ссылкой!)

Если вы хотите проверить существование и создать файл, если он не существует, атомарно, чтобы не было условий гонки, то используйте это:

#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 */
}
Дэн Ленски
источник
8
Если вы собираетесь использовать O_CREAT, вам нужно предоставить режим (права доступа) в качестве третьего аргумента для open (). Также подумайте, следует ли использовать O_TRUNC, O_EXCL или O_APPEND.
Джонатан Леффлер
6
Джонатан Леффлер прав, этот пример требует, чтобы O_EXCL работал как написано.
Рэнди Проктор
6
Кроме того, вам нужно указать режим в качестве третьего аргумента: open (блокировка, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR)
Эндрю Кук
4
Следует отметить, что это настолько безопасно, насколько файловая система совместима с POSIX; в частности, старые версии NFS имеют то же самое состояние гонки, которого O_EXCL должен был избегать! Существует обходной путь, задокументированный в open(2)(для Linux; страницы руководства вашей ОС могут отличаться), но он довольно уродлив и не может быть устойчивым к злоумышленнику.
Кевин
Обратите внимание, что для использования этого FILE*вам необходимо использовать метод posix fdopen(fd,"flags")для генерацииFILE*
Gem Taylor
32

Да. Использование stat(). Смотрите справочную страницу для stat(2).

stat()потерпит неудачу, если файл не существует, в противном случае, скорее всего, удастся. Если он существует, но у вас нет доступа на чтение к каталогу, в котором он существует, он также потерпит неудачу, но в этом случае произойдет сбой любого метода (как вы можете проверить содержимое каталога, который вы можете не видеть в соответствии с правами доступа? Просто не можешь)

О, как кто-то еще упоминал, вы также можете использовать access(). Однако я предпочитаю stat(), так как, если файл существует, он сразу же получит много полезной информации (когда он последний раз обновлялся, насколько он велик, владелец и / или группа, которой принадлежит файл, права доступа и т. Д.).

Mecki
источник
5
доступ предпочтителен, если вам нужно только знать, существует ли файл. Stat () может быть подслушано, если вам не нужна вся дополнительная информация.
Мартин Беккет
4
На самом деле, когда я перечисляю каталог с помощью команды ls, он вызывает stat для каждого файла, присутствующего там, и то, что запуск ls имеет большие накладные расходы, является для меня довольно новым. На самом деле вы можете запускать ls для каталогов с тысячами файлов, и он возвращается за доли секунды.
Меки
2
@Mecki: stat имеет ненулевые дополнительные издержки по сравнению с доступом в системах, которые поддерживают жесткие ссылки. Это связано с тем, что доступ должен смотреть только на запись каталога, в то время как stat должен искать и индекс. На устройствах хранения с плохим временем поиска (например, на ленте) разница может быть значительной, так как запись каталога и inode вряд ли будут находиться рядом друг с другом.
Кевин
3
@Kevin Если только вы не передаете ему F_OK, access()проверяет права доступа к файлу для файла, и они хранятся в inode для этого файла и не находятся в его записи каталога (по крайней мере для всех файловых систем, которые имеют структуры, подобные inode) , Так что access()должен получить доступ к иноду точно так же, как stat()и к нему. То, что вы говорите, справедливо, только если вы не проверяете какие-либо разрешения! И на самом деле в некоторых системах access()даже реализовано поверх stat()(например, glibc в GNU Hurd делает это таким образом), поэтому в первую очередь нет никакой гарантии.
Меки
@Mecki: Кто сказал что-нибудь о проверке разрешений? Я специально говорил о F_OK. И да, некоторые системы плохо реализованы. Доступ будет, по крайней мере, так же быстрым, как и в любом случае, и иногда может быть быстрее.
Кевин
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
источник
1
Не приведет ли это к утечке памяти? Вы никогда не закрываете файл, если он существует.
LegionMammal978
1
Это хороший, простой метод. Если вы находитесь в Windows MSVC, используйте это вместо этого: (fopen_s(file, "sample.txt", "r"))поскольку fopen()считается устаревшим (или отключить устаревшие ошибки, но это не рекомендуется).
Никос
15
fopen()это стандартный C, он никуда не денется. Это только «устарело» от Microsoft. Не используйте, fopen_s()если вам не нужен платформо-зависимый непереносимый код.
Эндрю Хенле
Вызывать fclose () даром? Необходимо сначала назначить переменную file
Дженикс,
1
Переменная 'file' здесь имеет мусорное значение. Зачем вообще закрывать его? Вы просто вызываете 'fclose (SOME_RANDOM_ADDRESS);' ..
Дженикс
6

Из справки Visual C ++ я бы предпочел

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

Также стоит отметить значения режима :_access(const char *path,int mode)

  • 00: только существование

  • 02: разрешение на запись

  • 04: разрешение на чтение

  • 06: разрешение на чтение и запись

Так как вы fopenможете потерпеть неудачу в ситуациях, когда файл существует, но не может быть открыт по запросу.

Изменить: Просто прочитайте пост Меки. stat()выглядит аккуратнее. Хо гул.

SmacL
источник
доступ предпочтителен, если вам нужно только знать, существует ли файл. Stat () может иметь большой подслушанный.
Мартин Беккет
4

Вы можете использовать функцию realpath ().

resolved_file = realpath(file_path, NULL);
if (!resolved_keyfile) {
   /*File dosn't exists*/
   perror(keyfile);
   return -1;
}
Бхарат Редди
источник
3

Я думаю, что функция access () , которая находится в, unistd.hявляется хорошим выбором для Linux(вы также можете использовать stat ).

Вы можете использовать это так:

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

И вы получите следующий результат:

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
Мичи
источник