-
-
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; | |
| } |
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_MAXproblems - Check that existing paths are really directories, and return
ENOTDIRif they are not - Don't leak
EEXISTon success - Change default mode from
0700to0777to match command linemkdir -pbehavior (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
ptris a null pointer, no action shall occur.
https://stackoverflow.com/a/2360462 oh looks like my best-practices is a bit out-of-date…
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.