2

You can use the stat() function to check if an object is a file or directory, like this. My issue is, <sys/stat.h> isn't on Windows, and I'm not sure what the Windows equivalent or Windows function is. This question is how do I do this, but specifically in a cross platform manner.

While avoiding conditionally compiled code, how can I cross platform check if an object at a path is a file or a directory in C? Although there's an almost identical question, it cannot help me as it's unanswered, and the author is deleted so the question cannot be maintained.

To my understanding fopen() is cross platform, and so is opendir(), readdir(), scandir() etc, so why wouldn't simply checking if an object is a file or directory be cross plaform? If there is no way, and conditionally compiled code is the only way, how do I do it in Windows?

  • There's no way in standard C. Conditional compilation of however it's done on Windows and `stat()` on other OSes is probably going to cover 99%+ of (non-embedded) uses though. – Shawn Jul 12 '20 at 02:50
  • @Shawn Well how is it done on Windows? –  Jul 12 '20 at 02:51
  • Beats me; I don't do a lot of Windows programming. – Shawn Jul 12 '20 at 02:54
  • [`GetFileAttributes()`](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileattributesa) and [`GetFileType()`](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfiletype) seem to be the relevant functions. – Shawn Jul 12 '20 at 03:02

1 Answers1

0

Unfortunately in C there is not an easy, portable or direct way to do this.

To that though, here's a bit of C code I converted from some C++ code I wrote for a cross platform library that can be used to check if a file or directory exists:

#include <sys/stat.h>
#include <sys/types.h>
#if defined(OS_WIN)
    #include <windows.h>
#else
    #include <dirent.h> // for *Nix directory access
    #include <unistd.h>
#endif

bool file_exists(const char* file)
{
    if (file == NULL) { return false; }
    #if defined(OS_WIN)
        #if defined(WIN_API)
            // if you want the WinAPI, versus CRT
            if (strnlen(file, MAX_PATH+1) > MAX_PATH) {
                // ... throw error here or ...
                return false;
            }
            DWORD res = GetFileAttributesA(file);
            return (res != INVALID_FILE_ATTRIBUTES &&
                !(res & FILE_ATTRIBUTE_DIRECTORY));
        #else
            // Use Win CRT
            struct stat fi;
            if (_stat(file, &fi) == 0) {
                #if defined(S_ISSOCK)
                    // sockets come back as a 'file' on some systems
                    // so make sure it's not a socket or directory
                    // (in other words, make sure it's an actual file)
                    return !(S_ISDIR(fi.st_mode)) &&
                        !(S_ISSOCK(fi.st_mode));
                #else
                    return !(S_ISDIR(fi.st_mode));
                #endif
            }
            return false;
        #endif
    #else
        struct stat fi;
        if (stat(file, &fi) == 0) {
            #if defined(S_ISSOCK)
                return !(S_ISDIR(fi.st_mode)) &&
                    !(S_ISSOCK(fi.st_mode));
            #else
                return !(S_ISDIR(fi.st_mode));
            #endif
        }
        return false;
    #endif
}

bool dir_exists(const char* folder)
{
    if (folder == NULL) { return false; }
    #if defined(OS_WIN)
        #if defined(WIN_API)
            // if you want the WinAPI, versus CRT
            if (strnlen(folder, MAX_PATH+1) > MAX_PATH) {
                // ... throw error here or ...
                return false;
            }
            DWORD res = GetFileAttributesA(folder);
            return (res != INVALID_FILE_ATTRIBUTES &&
                (res & FILE_ATTRIBUTE_DIRECTORY));
        #else
            struct stat fi;
            if (_stat(folder, &fi) == 0) {
                return (S_ISDIR(fi.st_mode));
            }
            return false;    
        #endif
    #else
        struct stat fi;
        if (stat(folder, &fi) == 0) {
            return (S_ISDIR(fi.st_mode));
        }
        return false;
    #endif
}

To build it for Windows, simply define OS_WIN for that build; additionally, if you want to stick with full WinAPI, versus using the Windows CRT, you can define WIN_API.

To check if the given path is a file or directory, you can use something like the following code:

bool is_file(const char* path)
{
    return file_exists(path) && !dir_exists(path);
}

bool is_dir(const char* path)
{
    return dir_exists(path) && !file_exists(path);
}

You can obviously change the code to whatever suits your needs, but that should get you in the direction you need to build your code on Windows/macOS/*nix systems.

I hope that can help.

txtechhelp
  • 6,625
  • 1
  • 30
  • 39
  • What is and why do I need `#include `? Could you please clarify that? –  Jul 12 '20 at 06:09
  • @user13783520 .. sorry, `fcntl.h` includes other functions for I/O control .. you don't need it for this specific code; modified answer to reflect that. – txtechhelp Jul 12 '20 at 10:21