opendir() · readdir() · closedir() · rewinddir()

 

Reading Directories
opendir() · readdir() · closedir() · rewinddir() | EmbeddedPathashala
📂
opendir()
📋
readdir()
🔒
closedir()
↩️
rewinddir()

Reading What’s Inside a Directory

You cannot use read() on a directory — the kernel forbids it. Instead, you use a set of library functions that give you directory entries one at a time. Think of it as a cursor that walks through the directory list entry by entry.

DIRstruct dirent d_inod_name d_typeDT_REG DT_DIRreaddir_r() fdopendir()dirfd()

The Directory Reading API
#include <dirent.h>

/* Open a directory — returns a "stream" pointer */
DIR *opendir(const char *dirpath);

/* Open by file descriptor */
DIR *fdopendir(int fd);

/* Read next entry — returns NULL at end or on error */
struct dirent *readdir(DIR *dirp);

/* Reset to start of directory */
void rewinddir(DIR *dirp);

/* Close the directory stream */
int closedir(DIR *dirp);

/* Get the underlying file descriptor */
int dirfd(DIR *dirp);

The struct dirent returned by readdir() looks like this:

struct dirent {
    ino_t  d_ino;       /* Inode number of the file */
    char   d_name[];    /* Null-terminated filename */
    /* Also available (Linux-specific): */
    /* unsigned char d_type; — file type (DT_REG, DT_DIR, etc.) */
};
📌 readdir() returns entries in filesystem order — NOT alphabetically. Use scandir() if you need sorted output.
⚠️ readdir() reuses the same internal buffer each call. The returned pointer is overwritten on the next call. Copy d_name if you need to keep it.

d_type Values (Linux-specific)
d_type constant Meaning
DT_REG Regular file
DT_DIR Directory
DT_LNK Symbolic link
DT_FIFO Named pipe (FIFO)
DT_SOCK Unix domain socket
DT_BLK Block device
DT_CHR Character device
DT_UNKNOWN Type not known (some filesystems)
d_type is faster than calling lstat() for type, but not all filesystems support it. Always fall back to lstat() if d_type returns DT_UNKNOWN.

Control Flow: How to Correctly Use readdir()
1 dp = opendir("/some/path") — open the directory
2 Set errno = 0 before each readdir() call
3 entry = readdir(dp) — if NULL + errno!=0 → error; if NULL + errno==0 → end
4 Skip “.” and “..” entries explicitly
5 closedir(dp) — always close when done

Code Examples

Example 1: List all files in a directory
#include <stdio.h>
#include <dirent.h>
#include <errno.h>
#include <string.h>

int main(int argc, char *argv[]) {
    const char *path = (argc > 1) ? argv[1] : ".";

    DIR *dp = opendir(path);
    if (dp == NULL) {
        perror("opendir");
        return 1;
    }

    struct dirent *entry;
    errno = 0;

    while ((entry = readdir(dp)) != NULL) {
        /* Skip hidden . and .. */
        if (strcmp(entry->d_name, ".") == 0 ||
            strcmp(entry->d_name, "..") == 0)
            continue;

        printf("%s\n", entry->d_name);
        errno = 0;  /* Reset before next call */
    }

    if (errno != 0)
        perror("readdir error");

    closedir(dp);
    return 0;
}
Example 2: Count files by type using d_type
#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>

int main(int argc, char *argv[]) {
    const char *path = (argc > 1) ? argv[1] : ".";
    int files = 0, dirs = 0, links = 0, others = 0;
    char fullpath[512];
    struct stat sb;

    DIR *dp = opendir(path);
    if (!dp) { perror("opendir"); return 1; }

    struct dirent *entry;
    while ((entry = readdir(dp)) != NULL) {
        if (strcmp(entry->d_name, ".") == 0 ||
            strcmp(entry->d_name, "..") == 0)
            continue;

        /* Use d_type if available, else fall back to lstat */
        int type = entry->d_type;
        if (type == DT_UNKNOWN) {
            snprintf(fullpath, sizeof(fullpath), "%s/%s", path, entry->d_name);
            lstat(fullpath, &sb);
            if (S_ISREG(sb.st_mode))  type = DT_REG;
            else if (S_ISDIR(sb.st_mode)) type = DT_DIR;
            else if (S_ISLNK(sb.st_mode)) type = DT_LNK;
        }

        if (type == DT_REG)  files++;
        else if (type == DT_DIR)  dirs++;
        else if (type == DT_LNK)  links++;
        else others++;
    }
    closedir(dp);

    printf("Regular files : %d\n", files);
    printf("Directories   : %d\n", dirs);
    printf("Symbolic links: %d\n", links);
    printf("Other         : %d\n", others);
    return 0;
}
Example 3: Find all .c files in a directory
#include <stdio.h>
#include <dirent.h>
#include <string.h>

int ends_with(const char *s, const char *suffix) {
    size_t sl = strlen(s), xl = strlen(suffix);
    if (xl > sl) return 0;
    return strcmp(s + sl - xl, suffix) == 0;
}

int main(int argc, char *argv[]) {
    const char *path = (argc > 1) ? argv[1] : ".";

    DIR *dp = opendir(path);
    if (!dp) { perror("opendir"); return 1; }

    struct dirent *entry;
    printf("C source files in '%s':\n", path);

    while ((entry = readdir(dp)) != NULL) {
        if (ends_with(entry->d_name, ".c"))
            printf("  %s\n", entry->d_name);
    }
    closedir(dp);
    return 0;
}
Example 4: rewinddir() — scan a directory twice
#include <stdio.h>
#include <dirent.h>
#include <string.h>

int main() {
    DIR *dp = opendir("/tmp");
    struct dirent *entry;
    int count = 0;

    /* First pass: count entries */
    while ((entry = readdir(dp)) != NULL) {
        if (strcmp(entry->d_name, ".") != 0 &&
            strcmp(entry->d_name, "..") != 0)
            count++;
    }
    printf("First pass: %d entries found\n", count);

    /* Rewind and scan again */
    rewinddir(dp);
    int count2 = 0;
    while ((entry = readdir(dp)) != NULL) {
        if (strcmp(entry->d_name, ".") != 0 &&
            strcmp(entry->d_name, "..") != 0)
            count2++;
    }
    printf("Second pass: %d entries found\n", count2);

    closedir(dp);
    return 0;
}
Example 5: fdopendir() — open directory by fd (avoids race conditions)
#include <stdio.h>
#include <fcntl.h>
#include <dirent.h>
#include <string.h>

int main() {
    /* Open directory as file descriptor first */
    int dfd = open("/tmp", O_RDONLY | O_DIRECTORY);
    if (dfd == -1) { perror("open"); return 1; }

    /* Convert to DIR stream */
    DIR *dp = fdopendir(dfd);
    if (!dp) { perror("fdopendir"); return 1; }

    /* Now read entries — safe from TOCTOU race */
    struct dirent *entry;
    while ((entry = readdir(dp)) != NULL) {
        if (strcmp(entry->d_name, ".") == 0 ||
            strcmp(entry->d_name, "..") == 0) continue;
        printf("%s\n", entry->d_name);
    }

    closedir(dp);  /* This also closes dfd */
    return 0;
}
After fdopendir(), the fd is owned by the DIR stream. Do NOT close it manually — closedir() does that.
Example 6: Print full path of every entry (with stat info)
#include <stdio.h>
#include <dirent.h>
#include <sys/stat.h>
#include <string.h>

int main(int argc, char *argv[]) {
    const char *dir = (argc > 1) ? argv[1] : ".";
    DIR *dp = opendir(dir);
    struct dirent *entry;
    struct stat sb;
    char path[512];

    if (!dp) { perror("opendir"); return 1; }

    printf("%-30s %-10s %s\n", "Name", "Size", "Type");
    printf("%-30s %-10s %s\n", "----", "----", "----");

    while ((entry = readdir(dp)) != NULL) {
        if (strcmp(entry->d_name, ".") == 0 ||
            strcmp(entry->d_name, "..") == 0) continue;

        snprintf(path, sizeof(path), "%s/%s", dir, entry->d_name);
        lstat(path, &sb);

        const char *type = "other";
        if (S_ISREG(sb.st_mode)) type = "file";
        else if (S_ISDIR(sb.st_mode)) type = "dir";
        else if (S_ISLNK(sb.st_mode)) type = "symlink";

        printf("%-30s %-10ld %s\n", entry->d_name, (long)sb.st_size, type);
    }
    closedir(dp);
    return 0;
}

Interview Questions

Q1: Why can’t you use read() on a directory?
Directories have a special internal format managed by the kernel. Allowing raw read() would expose filesystem-internal structures and be non-portable. The opendir()/readdir() API gives a portable, stable interface.
Q2: How do you distinguish end-of-directory from an error in readdir()?
Set errno=0 before calling readdir(). If readdir() returns NULL and errno is still 0, you’ve reached end-of-directory. If errno != 0, there was an error.
Q3: Are directory entries returned in alphabetical order?
No. They are returned in the order the filesystem stores them, which is typically creation order or filesystem-dependent. Use scandir() with a comparison function for sorted output.
Q4: What is d_type and why should you check for DT_UNKNOWN?
d_type is a Linux-specific field in struct dirent that tells you the file type without a stat() call. However, not all filesystems populate it — some return DT_UNKNOWN. You must fall back to lstat() in that case.
Q5: What is the difference between opendir() and fdopendir()?
opendir() takes a path string. fdopendir() takes an open file descriptor. Using fdopendir() avoids TOCTOU race conditions because you hold the directory open by fd before you start reading it.
Q6: Is readdir() thread-safe? What is readdir_r()?
readdir() is NOT thread-safe because it uses a static buffer. readdir_r() is the reentrant version — it writes the entry into a caller-provided buffer. For multi-threaded code, use readdir_r() or protect readdir() with a mutex.
Q7: What happens if you modify a directory while scanning it with readdir()?
SUSv3 says behavior is unspecified for entries added or removed after opendir() but before readdir() returns. Entries that existed at opendir() time and haven’t changed are guaranteed to be returned. New entries may or may not appear.

Leave a Reply

Your email address will not be published. Required fields are marked *