HOWTO · C

How to Create a New Directory in C

Learn to create directories in C with mkdir, choose effective permission bits, handle existing paths and other errors, and use mkdirat for directory-relative creation.

On this page

Use the POSIX mkdir function to create one directory directly from C. It takes a pathname and permission bits, and reports failure through its return value and errno. The requested permissions are filtered by the process umask, so the resulting mode can be more restrictive than the mode passed to the call. When a pathname must be interpreted relative to a particular open directory rather than the process working directory, mkdirat provides that pattern.

Create a directory with mkdir

Include <sys/stat.h> for mkdir. The first argument is the path to the new directory; the second is a mode such as 0750. In that mode, the owner may read, write, and search the directory, while members of the group may read and search it. Other users receive no permissions from these requested bits. The process umask can remove permissions, but cannot add them.

Run this example:

#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>

int main(void) {
    char parent[] = "/tmp/mkdir-demo-XXXXXX";
    char path[sizeof(parent) + sizeof("/reports")];

    if (mkdtemp(parent) == NULL) {
        perror("mkdtemp");
        return EXIT_FAILURE;
    }
    int path_length = snprintf(path, sizeof(path), "%s/reports", parent);
    if (path_length < 0 || (size_t)path_length >= sizeof(path)) {
        puts("path formatting failed");
        if (rmdir(parent) == -1) {
            perror("rmdir parent");
        }
        return EXIT_FAILURE;
    }

    if (mkdir(path, 0750) == -1) {
        perror("mkdir");
        if (rmdir(parent) == -1) {
            perror("rmdir parent");
        }
        return EXIT_FAILURE;
    }

    puts("Created reports directory.");
    int result = EXIT_SUCCESS;
    if (rmdir(path) == -1) {
        perror("rmdir child");
        result = EXIT_FAILURE;
    }
    if (rmdir(parent) == -1) {
        perror("rmdir parent");
        result = EXIT_FAILURE;
    }
    return result;
}
Created reports directory.

The supplied runtime record shows the successful case printing Created reports directory. Failure cases are not represented in that record. The example creates a temporary parent, creates reports beneath it, prints the success message, and removes both directories. In your application, replace the temporary pathname with the location you need and decide whether the selected permissions are appropriate for the data stored there.

Handle errors and existing paths

mkdir returns 0 on success and -1 on failure. On failure, it sets errno; call perror promptly or save errno before doing other work that may change it. Common causes include a parent directory that does not exist, insufficient permission to modify the parent, a pathname that exceeds the system limit, or a path component that is not a directory. If the target already exists, mkdir also fails with EEXIST—whether that target is a directory or another file type.

Treat the creation result as authoritative. A stat check before mkdir cannot guarantee that the path remains absent until creation: another process can create it between the check and the call. If your program accepts an existing directory as success, handle EEXIST and then inspect the path with stat or lstat to confirm its type, while accounting for races if the path may be changed concurrently. Do not silently accept an existing regular file.

The 0750 argument is a requested mode, not necessarily the final mode. For example, a restrictive umask can clear group or owner bits. If exact permissions are a requirement, check the resulting directory mode and apply a deliberate permission policy after creation; consider the security implications of changing permissions on a path that another process may have replaced.

Create a directory relative to an open directory with mkdirat

mkdirat takes a directory file descriptor, a pathname, and a mode. When the pathname is relative, the kernel resolves it from that directory descriptor, independent of later changes to the process working directory. This is useful when an application already has an open directory and wants subsequent operations anchored there. An absolute pathname is not relative to the descriptor.

The example opens a temporary parent directory, creates a child relative to its descriptor, and closes the descriptor. The temporary directory is removed afterward. The supplied runtime record shows the successful case completing with no standard output; it does not report failure-case executions.

#define _POSIX_C_SOURCE 200809L
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>

int main(void) {
    char parent[] = "/tmp/mkdirat-demo-XXXXXX";
    char child[sizeof(parent) + sizeof("/reports")];
    int dirfd;
    int result = EXIT_FAILURE;
    int created = 0;

    if (mkdtemp(parent) == NULL) {
        perror("mkdtemp");
        return EXIT_FAILURE;
    }
    dirfd = open(parent, O_RDONLY);
    if (dirfd == -1) {
        perror("open");
        if (rmdir(parent) == -1) {
            perror("rmdir parent");
        }
        return EXIT_FAILURE;
    }

    if (mkdirat(dirfd, "reports", 0750) == -1) {
        perror("mkdirat");
    } else {
        created = 1;
        result = EXIT_SUCCESS;
    }

    if (close(dirfd) == -1) {
        perror("close");
        result = EXIT_FAILURE;
    }
    if (created) {
        int child_length = snprintf(child, sizeof(child), "%s/reports", parent);
        if (child_length < 0 || (size_t)child_length >= sizeof(child)) {
            puts("path formatting failed");
            result = EXIT_FAILURE;
        } else {
            if (rmdir(child) == -1) {
                perror("rmdir child");
                result = EXIT_FAILURE;
            }
        }
    }
    if (rmdir(parent) == -1) {
        perror("rmdir parent");
        result = EXIT_FAILURE;
    }
    return result;
}

Keep the descriptor open for as long as it is needed and close it on every path. In production code, ensure cleanup removes only objects your program created. For security-sensitive operations, consider opening the directory with flags suited to your platform and validate the directory you intend to use; mkdirat anchors relative resolution but does not by itself validate every component of a supplied pathname.

Why avoid system() for directory creation

Calling system() to run a shell command is generally unnecessary when C already provides mkdir. It adds shell startup and command interpretation, and building a command string from a path can let shell metacharacters in an untrusted path change what gets executed. Quoting paths correctly across shells is difficult. Call mkdir or mkdirat directly, check the result, and report errno instead. A shell is appropriate only when the application intentionally needs shell behavior and handles command construction and execution with a separate, carefully designed security policy.