POSIX Filesystem Remove files recursively (openat and unlinkat, thread-safe)


Example

#include <stddef.h>   /* for offsetof() */
#include <stdlib.h>   /* for exit() */
#include <stdio.h>    /* for perror() */
#include <string.h>   /* for strcmp() */
#include <unistd.h>   /* for close(), unlink() */
#include <fcntl.h>    /* for open() */
#include <dirent.h>   /* for DIR */
#include <sys/stat.h> /* for stat */
#include <limits.h>   /* for NAME_MAX */

int rm_rf(const char* path)
{
    if (unlink(path) == 0) {
        return 0;
    } else {
        int dirfd = open(path, O_RDONLY | O_DIRECTORY);
        if (dirfd == -1) {
            perror("open");
            return -1;
        }
        if (rm_children(dirfd) == -1) {
            return -1;
        }
        if (rmdir(path) == -1) {
            perror("rmdir");
            return -1;
        }
        return 0;
    }
}

int rm_children(int dirfd)
{
    DIR* dir = fdopendir(dirfd);
    if (dir == NULL) {
        perror("fdopendir");
        if (close(dirfd) == -1) {
            perror("close");
        }
        return -1;
    }

    char buf[offsetof(struct dirent, d_name) + NAME_MAX + 1];

    struct dirent* dbuf = (struct dirent*)buf;
    struct dirent* dent = NULL;

    int ret = 0;

    for (;;) {
        if ((ret = readdir_r(dir, dbuf, &dent)) == -1) {
            perror("readdir_r");
            break;
        }
        if (dent == NULL) {
            break;
        }
        if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) {
            continue;
        }
        if ((ret = rm_at(dirfd, dent->d_name)) == -1) {
            break;
        }
    }

    if (closedir(dir) == -1) {
        perror("closedir");
        ret = -1;
    }

    return ret;
}

int rm_at(int dirfd, const char* name)
{
    int fd = openat(dirfd, name, O_RDONLY);
    if (fd == -1) {
        perror("openat");
        return -1;
    }

    int ret = 0;

    struct stat st;
    if ((ret = fstat(fd, &st)) == -1) {
        perror("fstat");
        goto out;
    }

    if (S_ISDIR(st.st_mode)) {
        ret = rm_children(fd);
        fd = -1;
        if (ret == -1) {
            goto out;
        }
    }

    ret = unlinkat(dirfd, name, S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0);
    if (ret == -1) {
        perror("unlinkat");
        goto out;
    }

out:
    if (fd != -1) {
        if (close(fd) == -1) {
            perror("close");
            ret = -1;
        }
    }

    return ret;
}

Note: this method is thread-safe, but uses stack for uncontrolled recursion. Each tree level adds at least NAME_MAX bytes + one frame size.