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

8

573

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;
}
Sweetie answered 23/10, 2008 at 14:57 Comment(10)
I think I'll give the answer to the access method, despite the stat method being a very reasonable alternative, access gets the job done.Sweetie
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.Conundrum
@Dave Marshall: You logic is backwards. exists() will return 0 if the file can be opened, otherwise 1. Based on the function name I'd expect it to be the other way around.Mooring
@Dave Marshal: Actually opening a file is not very much more expensive than either access() or stat(). For both of those, the main cost is in evaluating the pathname, just as for open(). The rest is relatively cheap.Vivyanne
i don't see - what is wrong with that fopen/fclose way?Brass
@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).Vivyanne
if (file = fopen(fname, "r")) will give a warning. Use parenthesis around statement inside the if-statement if ((file = fopen(fname, "r")))Nikolas
Dumb Q: but does it matter if the file is stored in binary data rather than text? I notice are are trying to open to read text "r". I tried using this function as a test for binary files and it seems to work just fine, but is this bad practice?Tallia
@Nikolas (()) 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)Hypothec
@GregoryFenn Opening a file in "r" vs "rb" only affects how the file is read using methods like fgetc(), fread(). fgets(), et cetera. And, then, only on platforms such as Windows which will do things like silently convert 0x0d0a ("\r\n") to 0x0a ("\n") unless you specify b. Presence or absence of b in the opening mode string should not affect whether or not fopen() succeeds or not (unless there is some weird bug). In this question, there is no attempt to read data from the file, so it doesn’t matter if it is opened in binary mode or not.Padishah
L
792

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.

Leucoderma answered 23/10, 2008 at 14:59 Comment(24)
Let me be picky :) access() is not a standard function. If "cross platform" is to be taken in the broader sense, this may fail :)Rader
Define "standard function". Are there platforms where it does not exist?Leucoderma
"standard function" == "included in the ISO standard". A compiler may decide not to have it in its standard library. I can't offer you an example of a compiler that doesn't have it but how can you be sure that every compiler does it?Rader
I can't. I didn't know it was not in the standard. I checked on Windows, Linux, and Mac and it was in all three, so I figured it was everywhere important. :-)Leucoderma
POSIX is an ISO standard; it defines access(). C is another ISO standard; it does not.Vivyanne
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...]Vivyanne
[...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.Vivyanne
"On success, zero is returned" (man 2 access) If file exists, access return 0. if(0) { file_exist } else { doesn't exists } ???Congregation
I ran across this question when looking for the reason access() broke in my code. I moved from DevC++ to CodeBlocks and it stopped working. So, it's not infallible; +1 more to @Leffler.Claiborn
Which header file provides access() on Windows (MSVC)? I searched but the word "access" is too generic...Queston
It doesn't work for me, no matter file exists or not it returns -1Maestro
@JonathanLeffler While your comments are true, access() should be ok for checking existence of a file (F_OK).Faizabad
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.Vivyanne
what in case of access error? I would check if errno equals ENOENT.Estray
@GraemePerrow Why the return value check is reverted? If you check for != -1 you may get falsse (==-1) while some other error occurred (you should check for it in second {}) and file DOES exist. The correct way is to check == 0 for success I think...Cody
@JonathanLeffler in a SUID or SGID situation, or where support for said situation matters, is it better to use fstat?Coumarone
@AlexejMagura: It depends on which condition you want to check. To some extent, it also depends on whether you have to worry about ACLs (access control lists) or on 'extended attributes', either or both of which can influence whether you can really access the file. Ultimately, this is a LBYL (Look Before You Leap) test, but the reliable way to check whether you're allowed to do something with a file is EAFP (Easier to Ask Forgiveness than Permission) — that is, you should try the access you want and check the return status. […continued…]Vivyanne
[…continuation…] In a process with raised privileges (SUID or SGID or …), you have to assess what you want to do. If the process is going to lower its privileges (quite probably a good idea), it may be sensible to open the file with the raised privileges and then drop them. Or it may be more desirable to have the process with lowered privileges do the open. As always, the code must handle open errors: things can change between any test and the attempt to open, and only the attempt to open is definitive — hence EAFP!Vivyanne
@JonathanLeffler Thanks for all the informative comments. But now I'm very confused.. What is the best way for me cross-platform wise(including Android)? I just need to check if there's a file to read, or sometimes if it's the name I can create a new file with. fopen() will work fine?Atthia
If you want to read the file, just attempt to read from it. Ignore checking if it exists or not. If you use fopen to attempt to read it, and fopen fails, you can determine why it failed. You want to avoid TOUTOC (Time of Use, Time of Check) race conditions that could lead to subtle vulnerabilities.Unfamiliar
This returns -1 even if file exists but its parent directory is not readableMoneymaker
@GraemePerrow I changed the condition on you answer after checking man 2 access. I think it's better read like this, rather than with the double negative.League
As others have commented, with access() "the check is done using the calling process's real UID and GID, rather than the effective IDs". So it's not reliable when the executable file has SUID bit or file capabilities.Golightly
Gives false negatives for SPIFFS partitions on ESP32Kohlrabi
M
149

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;
}
Matri answered 23/10, 2008 at 15:0 Comment(6)
This does not work for files larger than 2GB on all systems.Heisser
@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).Vivyanne
Could either of you point to documentation regarding the failure after 2 GB? Also, what is the alternative in such cases?Kara
@JonathanLeffler Does stat not suffer from the same TOCTOU vulnerability as access? (It's not clear to me that it would be better.)Epifaniaepifano
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.Vivyanne
@Epifaniaepifano 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.Javanese
C
103

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 */
}
Conundrum answered 23/10, 2008 at 17:14 Comment(7)
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.Vivyanne
Jonathan Leffler is right, this example requires O_EXCL to work as written.Jurado
Also, you need to specify the mode as a third argument: open(lock, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR)Moonrise
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.Nematic
Note that to use this with FILE*, you then need to use the posix method fdopen(fd,"flags") to generate a FILE*Reeves
@GemTaylor As of C11, the x file access mode to fopen() provides exclusive open/create semantics similar to O_EXCL.Myceto
@AndrewHenle Hmm looks like someone forgot to tell man pages :-)Reeves
W
36

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).

Westberry answered 23/10, 2008 at 14:59 Comment(5)
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.Ambrosine
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.Westberry
@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.Nematic
@Nematic 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.Westberry
@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.Nematic
L
21
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
        }
Loeffler answered 8/4, 2015 at 9:8 Comment(6)
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.Myceto
This is the most compatible for all systems old and new.Combustion
where is the boolean set?Rademacher
Seems to be an inefficient way to do this. The fact that this is the only 'compatible' way shows that the C filesystem interface is wanting.Steroid
@Jean-FrançoisFabre The boolean is the expression (file = fopen("sample.txt","r"))!=NULL.Steroid
Always use r+ as file mode instead of r to check if a file exists, since mode r can return non-NULL when opening a directory (confirmed on Linux), while r+ does not.Root
F
7

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
Family answered 9/12, 2015 at 17:14 Comment(0)
A
7

You can use realpath() function.

resolved_file = realpath(file_path, NULL);
if (!resolved_keyfile) {
   /*File dosn't exists*/
   perror(keyfile);
   return -1;
}
Avaavadavat answered 8/4, 2016 at 6:14 Comment(2)
If the 2nd argument is NULL, then realpath() uses malloc to allocate a buffer that should be deallocated by the caller using free.Ostiole
realpath() also sets errno to ENOENT to indicate that the named file does actually not exist.Ostiole
W
6

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.

Woodberry answered 23/10, 2008 at 15:7 Comment(6)
access is preffered if you only need to know if the file exists. Stat() can have a large overheard.Ambrosine
Never use void main. Use int main.Publicist
@Publicist Why should we not use void main?Steroid
@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.Hallway
@Hallway Does using void main() invoke undefined behavior as far as the standard is concerned?Steroid
As far as the standard is concerned, yes. As far as most compilers are concerned, no. Most compilers will just force a main function with a void return type to return 0 as the exit code.Calorific

© 2022 - 2024 — McMap. All rights reserved.