-
-
Save JonathonReinhart/8c0d90191c38af2dcadb102c4e202950 to your computer and use it in GitHub Desktop.
#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; | |
} |
#ifndef MKDIR_P_H | |
#define MKDIR_P_H | |
int mkdir_p(const char *path); | |
#endif /* MKDIR_P_H */ |
env = Environment( | |
CCFLAGS = ['-Wall', '-Werror'], | |
) | |
env.Program('mkdir_p_test', ['mkdir_p.c', 'test.c']) |
#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; | |
} |
PATH_MAX
is provided by<linux/limits.h>
, not<limits.h>
.
Yes. It's true.
In <limits.h>, we may have some thing like POSIX_PATH_MAX (=256) which is imported from <bits/posix1_lim.h> when we turn USE_POSIX on.
In <linux/limits.h>, PATH_MAX = 4096.
Never use PATH_MAX.
Never.
It's never the truth even if you try to get it programmatically from sys_config.
Always use malloc/free.
if (mkdir(_path, S_IRWXU) != 0) {
if (errno != EEXIST)
return -1;
}
Must check whether _path is really a directory if errno is EEXIST, see my fork:
if (errno == EEXIST) {
struct stat st;
ret = stat(path, &st);
if (ret != 0) {
return ret;
} else if (S_ISDIR(st.st_mode)) {
return 0;
} else {
return -1;
}
}
@llothar @tangxinfa Thanks everyone for the feedback. I made a few improvements:
- Switch from fixed array to dynamic allocation to avoid
PATH_MAX
problems - Check that existing paths are really directories, and return
ENOTDIR
if they are not - Don't leak
EEXIST
on success - Change default mode from
0700
to0777
to match command linemkdir -p
behavior (will be affected by umask) - Added a test app
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…
Your check to continue if an error is just because of an existing path component, could also include checking that that component is a directory.