ChatGPT解决这个技术问题 Extra ChatGPT

What's the best way to check if a file exists in C?

Is there a better way than simply trying to open the file?

int exists(const char *fname)
{
    FILE *file;
    if ((file = fopen(fname, "r")))
    {
        fclose(file);
        return 1;
    }
    return 0;
}
Do you really just want to check for existence? Or do you want to check, and write to the file if it doesn't already exist. If so, see my answer below, for a version that doesn't suffer from race conditions.
i don't see - what is wrong with that fopen/fclose way?
@JohannesSchaub-litb: one thing that's wrong with the fopen()/fclose() method is that you may not be able to open a file for reading even though it exists. For example, /dev/kmem exists, but most processes can't open it even for reading. /etc/shadow is another such file. Of course, both stat() and access() rely on being able to access the directory containing the file; all bets are off if you can't do that (no execute permission on the directory containing the file).
if (file = fopen(fname, "r")) will give a warning. Use parenthesis around statement inside the if-statement if ((file = fopen(fname, "r")))
@Joakim (()) is solving the symptoms, not the problem. Just separate it into to lines; an extra line won't hurt that much. file = fopen(fname, "r"); if (file)

p
pevik

Look up the access() function, found in unistd.h. You can replace your function with

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

Under Windows (VC) unistd.h does not exist. To make it work it is necessary to define:

#ifdef WIN32
#include <io.h>
#define F_OK 0
#define access _access
#endif

You can also use R_OK, W_OK, and X_OK in place of F_OK to check for read permission, write permission, and execute permission (respectively) rather than existence, and you can OR any of them together (i.e. check for both read and write permission using R_OK|W_OK)

Update: Note that on Windows, you can't use W_OK to reliably test for write permission, since the access function does not take DACLs into account. access( fname, W_OK ) may return 0 (success) because the file does not have the read-only attribute set, but you still may not have permission to write to the file.


Let me be picky :) access() is not a standard function. If "cross platform" is to be taken in the broader sense, this may fail :)
POSIX is an ISO standard; it defines access(). C is another ISO standard; it does not.
There are pitfalls associated with access(). There is a TOCTOU (time of check, time of use) window of vulnerability between using access() and whatever else you do afterwards. [...to be continued...]
[...continuing...] Rather more esoterically, on POSIX systems, access() checks whether the real UID and real GID, rather than the effective UID and effective GID. This only matters to setuid or setgid programs, but then it matters intensely as it may give the 'wrong' answer.
Most of the time, yes (it is OK to use access() to check for a file's existence), but in a SUID or SGID program, even that could be incorrect. If the tested file is in a directory that the real UID or real GID cannot access, access() might report no such file when it does exist. Esoteric and improbable? Yes.
A
Adam Liss

Use stat like this:

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

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

and call it like this:

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

@LudvigANorin: on such systems, the chances are that access() also has problems, and there are options to use to make access() and stat() work with large files (bigger than 2 GB).
Could either of you point to documentation regarding the failure after 2 GB? Also, what is the alternative in such cases?
@JonathanLeffler Does stat not suffer from the same TOCTOU vulnerability as access? (It's not clear to me that it would be better.)
Both stat() and access() suffer from the TOCTOU vulnerability (so does lstat(), but fstat() is safe). It depends what you're going to do based on the presence or absence of the file. Using the correct options to open() is usually the best way of dealing with the problems, but it can be tricky formulating the right options. See also discussions on EAFP (Easier to Ask for Forgiveness than Permission) and LBYL (Look Before You Leap) -- see LBYL vs EAFP in Java, for example.
@Telemachus if you need to avoid TOCTOU, at least on Linux systems you can just open() the file in TOC (the open() result then becomes the check for file-exisence), then use this descriptor in TOU. This way even if file does not exists anymore at TOU, you still can access it through file-descriptor. Its content will be kept as long as there are processes that have it opened.
G
Gabriel Staples

Usually when you want to check if a file exists, it's because you want to create that file if it doesn't. Graeme Perrow's answer is good if you don't want to create that file, but it's vulnerable to a race condition if you do: another process could create the file in between you checking if it exists, and you actually opening it to write to it. (Don't laugh... this could have bad security implications if the file created was a symlink!)

If you want to check for existence and create the file if it doesn't exist, atomically so that there are no race conditions, then use this:

#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 */
}

If you are going to use O_CREAT, you need to supply the mode (permissions) as the third argument to open(). Also consider whether O_TRUNC or O_EXCL or O_APPEND should be used.
Jonathan Leffler is right, this example requires O_EXCL to work as written.
Also, you need to specify the mode as a third argument: open(lock, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR)
It should be noted that this is only as safe as the filesystem is POSIX compliant; in particular, old versions of NFS have the very race condition O_EXCL was supposed to avoid! There is a workaround, documented in open(2) (on Linux; your OS's man pages may vary), but it's rather ugly and may not be resistant to a malicious attacker.
Note that to use this with FILE*, you then need to use the posix method fdopen(fd,"flags") to generate a FILE*
a
a stone arachnid

Yes. Use stat(). See the man page forstat(2).

stat() will fail if the file doesn't exist, otherwise most likely succeed. If it does exist, but you have no read access to the directory where it exists, it will also fail, but in that case any method will fail (how can you inspect the content of a directory you may not see according to access rights? Simply, you can't).

Oh, as someone else mentioned, you can also use access(). However I prefer stat(), as if the file exists it will immediately get me lots of useful information (when was it last updated, how big is it, owner and/or group that owns the file, access permissions, and so on).


access is preffered if you only need to know if the file exists. Stat() can have a large overheard if you don't need all the extra info.
Actually when I list a directory using ls-command, it calls stat for every file being present there and that running ls has a large overhead is pretty new to me. Actually you can run ls on directories with thousands of files and it returns in a fraction of a second.
@Mecki: stat has nonzero additional overhead compared to access on systems which support hardlinks. This is because access only has to look at the directory entry, while stat has to look up the inode as well. On storage devices with bad seek time (e.g. tape), the difference can be significant since the directory entry and inode are unlikely to be next to each other.
@Kevin Unless you only pass F_OK to it, access() checks the file access permissions of a file and these are stored in in the inode for that file and are not in its directory entry (at least for all file systems that have inode-like structures). So access() has to access the inode exactly the same way that stat() has to access it. So what you say only holds true if you don't check for any permissions! And actually on some systems access() is even implemented on top of stat() (e.g. glibc on GNU Hurd does it that way), so there is no guarantee in the first place.
@Mecki: Who said anything about checking permissions? I was specifically talking about F_OK. And yes, some systems are poorly implemented. Access will be at least as fast as stat in every case and may be faster some of the time.
C
Christian Gingras
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
        }

fopen() is standard C, it's not going anywhere. It's only "deprecated" by Microsoft. Don't use fopen_s() unless you want platform-specific, non-portable code.
This is the most compatible for all systems old and new.
where is the boolean set?
b
bharath reddy

You can use realpath() function.

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

G
Govind Parmar

I think that access() function, which is found in unistd.h is a good choice for Linux (you can use stat too).

You can Use it like this:

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

And you get the following Output:

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

a
a stone arachnid

From the Visual C++ help, I'd tend to go with

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

Also worth noting mode values of _access(const char *path,int mode):

00: Existence only

02: Write permission

04: Read permission

06: Read and write permission

As your fopen could fail in situations where the file existed but could not be opened as requested.

Edit: Just read Mecki's post. stat() does look like a neater way to go. Ho hum.


access is preffered if you only need to know if the file exists. Stat() can have a large overheard.
Never use void main. Use int main.
@Anic17 Why should we not use void main?
@user16217248 there are only two valid signatures for main() defined in the C standards, and both of them return int. If a compiler/environment supports void main() then it's only doing so as an unofficial extension.