6

Is there a way to list all subdirectories in a given directory path in C? I was hoping I would be able to do it with the stat() function but it only works on files.

T.E.D.
  • 44,016
  • 10
  • 73
  • 134
KenjiOne
  • 149
  • 2
  • 3
  • 10
  • Richie: `stat()` should tell at least that it's a UNIXoid OS. – Joey Nov 12 '09 at 15:33
  • 1
    You are correct that `stat()` works on files, but directory entries are also files. So `stat("/etc/passwd", &buf)` and `stat("/etc/", &buf)` will both work. – Sean Bright Nov 12 '09 at 15:37
  • The Operating System is Linux. – KenjiOne Nov 12 '09 at 16:03
  • My overall goal is to use threads to find the size of a given directory and sub-directories. I am trying to approach this by finding how many sub-directories are in the given directory and then creating that amount of threads. – KenjiOne Nov 12 '09 at 16:06
  • This isn't really a C question. It's a platform question (Linux, I guess), with "and I'm using C" tacked onto the end. – T.E.D. Nov 12 '09 at 16:08

4 Answers4

12

stat works on directories too.

#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>

int num_dirs(const char* path)
{
    int dir_count = 0;
    struct dirent* dent;
    DIR* srcdir = opendir(path);

    if (srcdir == NULL)
    {
        perror("opendir");
        return -1;
    }

    while((dent = readdir(srcdir)) != NULL)
    {
        struct stat st;

        if(strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0)
            continue;

        if (fstatat(dirfd(srcdir), dent->d_name, &st, 0) < 0)
        {
            perror(dent->d_name);
            continue;
        }

        if (S_ISDIR(st.st_mode)) dir_count++;
    }
    closedir(srcdir);
    return dir_count;
}

Guy L
  • 2,824
  • 2
  • 27
  • 37
CodingLab
  • 1,441
  • 2
  • 13
  • 37
  • 1
    thanks, i man'd quite a bit of the functions you used to understand what was going on and it works! – KenjiOne Nov 12 '09 at 21:10
  • 1
    Because this seemed like such a good answer, I've edited it slightly to make it a bit more robust - replacing `stat` with `fstatat` (which means you don't have to muck about creating the full path, and also avoid race conditions), and handling errors from `opendir` and `fstatat` (which are reasonably likely in practice - things like "permission denied"). – caf Nov 13 '09 at 00:07
  • @GuyL your edit was unfortunately rejected. I don't know a lot of straight UNIX api stuff, but it might be easier to be approved if you add the correct includes so it's easy to verify that it compiles. – Rob Neuhaus Dec 26 '13 at 21:11
5

You want readdir(3).

Nikolai Fetissov
  • 82,306
  • 11
  • 110
  • 171
3
/*
I had need in something like this not so long ago (my difference is I
needed recursive scan) so I added only some comments... Sorry for recursion
but I was short of time and this was only part of internal one-time tool.
*/

/* Print all the dirs starting from <path> [maybe recursive]. */
int print_dirs(const char *path, int recursive)
{
    struct dirent *direntp = NULL;
    DIR *dirp = NULL;
    size_t path_len;

    /* Check input parameters. */
    if (!path)
        return -1;
    path_len = strlen(path);  

    if (!path || !path_len || (path_len > _POSIX_PATH_MAX))
        return -1;

    /* Open directory */
    dirp = opendir(path);
    if (dirp == NULL)
        return -1;

    while ((direntp = readdir(dirp)) != NULL)
    {
        /* For every directory entry... */
        struct stat fstat;
        char full_name[_POSIX_PATH_MAX + 1];

        /* Calculate full name, check we are in file length limts */
        if ((path_len + strlen(direntp->d_name) + 1) > _POSIX_PATH_MAX)
            continue;

        strcpy(full_name, path);
        if (full_name[path_len - 1] != '/')
            strcat(full_name, "/");
        strcat(full_name, direntp->d_name);

        /* Ignore special directories. */
        if ((strcmp(direntp->d_name, ".") == 0) ||
            (strcmp(direntp->d_name, "..") == 0))
            continue;

        /* Print only if it is really directory. */
        if (stat(full_name, &fstat) < 0)
            continue;
        if (S_ISDIR(fstat.st_mode))
        {
            printf("%s\n", full_name);
            if (recursive)
                print_dirs(full_name, 1);
        }
    }

    /* Finalize resources. */
    (void)closedir(dirp);
    return 0;
}

/* We are taking first argument as initial path name. */
int main(int argc, const char* argv[])
{
    if (argc < 2)
        return -1;

    print_dirs(argv[1], 1);
    return 0;
}
Roman Nikitchenko
  • 12,800
  • 7
  • 74
  • 110
2

As others have noted, stat(2) works fine on files and devices of all types. It reads through symbolic links to the file at the far end; if you need the information about the symbolic link itself, use lstat(2).

To list the names of all directories within a single directory (non-recursively), use a combination of the readdir(3) family of functions.

To list the names of all directories recursively, use the ftw(3) or nftw(3) functions to do a 'file tree walk' (from whence cometh their names; 'n' is for 'new').

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278