Last active
May 19, 2025 04:39
-
-
Save JonathonReinhart/8c0d90191c38af2dcadb102c4e202950 to your computer and use it in GitHub Desktop.
mkdir -p implemented in C
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdlib.h> | |
#include <string.h> | |
#include <sys/stat.h> /* mkdir(2) */ | |
#include <errno.h> | |
/* Make a directory; already existing dir okay */ | |
static int maybe_mkdir(const char* path, mode_t mode) | |
{ | |
struct stat st; | |
errno = 0; | |
/* Try to make the directory */ | |
if (mkdir(path, mode) == 0) | |
return 0; | |
/* If it fails for any reason but EEXIST, fail */ | |
if (errno != EEXIST) | |
return -1; | |
/* Check if the existing path is a directory */ | |
if (stat(path, &st) != 0) | |
return -1; | |
/* If not, fail with ENOTDIR */ | |
if (!S_ISDIR(st.st_mode)) { | |
errno = ENOTDIR; | |
return -1; | |
} | |
errno = 0; | |
return 0; | |
} | |
int mkdir_p(const char *path) | |
{ | |
/* Adapted from http://stackoverflow.com/a/2336245/119527 */ | |
char *_path = NULL; | |
char *p; | |
int result = -1; | |
mode_t mode = 0777; | |
errno = 0; | |
/* Copy string so it's mutable */ | |
_path = strdup(path); | |
if (_path == NULL) | |
goto out; | |
/* Iterate the string */ | |
for (p = _path + 1; *p; p++) { | |
if (*p == '/') { | |
/* Temporarily truncate */ | |
*p = '\0'; | |
if (maybe_mkdir(_path, mode) != 0) | |
goto out; | |
*p = '/'; | |
} | |
} | |
if (maybe_mkdir(_path, mode) != 0) | |
goto out; | |
result = 0; | |
out: | |
free(_path); | |
return result; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef MKDIR_P_H | |
#define MKDIR_P_H | |
int mkdir_p(const char *path); | |
#endif /* MKDIR_P_H */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
env = Environment( | |
CCFLAGS = ['-Wall', '-Werror'], | |
) | |
env.Program('mkdir_p_test', ['mkdir_p.c', 'test.c']) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include "mkdir_p.h" | |
int main(int argc, char **argv) | |
{ | |
const char *path; | |
int rc; | |
if (argc < 2) { | |
fprintf(stderr, "Missing argument: path\n"); | |
return 1; | |
} | |
path = argv[1]; | |
rc = mkdir_p(path); | |
fprintf(stderr, "mkdir_p(\"%s\") returned %d: %m\n", path, rc); | |
return (rc == 0) ? 0 : 2; | |
} |
BTW mkdir_p( "" ) accesses beyond allocated memory since it assumes the path length is non-zero. Seems it should return ENOENT in this case, as mkdir does for an empty string.
As for optimization, would it make sense to just try maybe_mkdir in the beginning, for the probably-common case where the parent directories already exist?
Thanks for the useful functions and careful implementation.
This doesn't make sense:
if (_path == NULL)
goto out;
Because it executes free(_path);
which is undefined for NULL
.
This doesn't make sense:
if (_path == NULL) goto out;Because it executes
free(_path);
which is undefined forNULL
.
Incorrect. From free(3p)
:
If
ptr
is a null pointer, no action shall occur.
https://stackoverflow.com/a/2360462 oh looks like my best-practices is a bit out-of-date…
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@llothar @tangxinfa Thanks everyone for the feedback. I made a few improvements:
PATH_MAX
problemsENOTDIR
if they are notEEXIST
on success0700
to0777
to match command linemkdir -p
behavior (will be affected by umask)