cbashdirectorymkdirstat

How can I detect an existing directory in ~/ with stat in C?


If the directory ~/testdir not exists, I want to create it. Before the test, I create the directory ~/testdir, but stat is not detecting it, and the program tries to create it once more. I only has discovered this problem in ~/.


#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>

void main()
{
   system("mkdir ~/testdir");
   struct stat st = {0};
   if (stat("~/testdir", &st) == -1)
   {
       system("mkdir ~/testdir");
   }
}

Result:

mkdir: cannot create directory ‘/home/pi/testdir’: File exists

Solution

  • You should never test whether a file or directory exists before trying to do something with it. That has an inherent "race condition": in between the test and the actual action, another process can come in and change things, causing the action to malfunction. "Malfunction" doesn't just mean "fail", this kind of race bug can cause catastrophic damage to unrelated files and/or puncture security. It's such a common and severe bug that it has its very own CWE code, CWE-367.

    What you should do instead is go ahead and do the thing you want to do and see if that failed. In this case, you should always try to create the directory, and if it fails with errno code EEXIST ("File exists") then ignore the error and proceed (but don't ignore any other errors). The mkdir shell utility has a -p option that makes it do exactly this, so your test program can be cut down to

    #include <stdio.h>
    
    int main(void)
    {
       return system("mkdir -p ~/testdir") ? 1 : 0;
    }
    

    You can do the same thing directly in C using the mkdir function plus an explicit check of errno:

    #include <errno.h>
    #include <stdio.h>
    #include <sys/stat.h>
    
    int ensure_directory(const char *name, mode_t mode)
    {
        if (mkdir(name, mode) == 0)
            return 0;
        if (errno == EEXIST)
            return 0;
        if (errno == ENOENT) {
            // parent directory doesn't exist, recurse up and create it
            // this is left as an exercise
        }
        perror(name);
        return 1;
    }
    

    However, this will not convert a leading ~ to the name of the user's home directory. In principle you could use wordexp to do that part without invoking a shell, but it's enough extra work that I would probably stick with system("mkdir -p ~/testdir"), as long as the name of the directory to create is hardcoded.

    If any part of ~/testdir comes from user input (including config files) in your full program, then stuffing it into system becomes unsafe and you need to ask a new question about how to safely expand ~ and possibly also $VARIABLE without doing any of the other things wordexp does.