#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
static int
_mkpath(int dfd, const char *path, mode_t omode, const char ** firstdir)
{
char *apath = NULL, *opath = NULL, *s, *sn, *sl;
unsigned int depth = 0;
mode_t chmod_mode = 0;
int retval = 0;
int old_errno = errno;
struct stat sbuf;
if (0 == mkdirat(dfd, path, omode)) {
if (firstdir) {
*firstdir = strdup(path);
}
goto mkpath_exit;
}
switch (errno) {
case ENOENT:
break;
case EEXIST:
if (fstatat(dfd, path, &sbuf, 0) == 0) {
if (S_ISDIR(sbuf.st_mode)) {
retval = EEXIST;
} else {
retval = ENOTDIR;
}
} else {
retval = EIO;
}
goto mkpath_exit;
case EISDIR:
retval = EEXIST;
goto mkpath_exit;
default:
retval = errno;
goto mkpath_exit;
}
apath = strdup(path);
if (apath == NULL) {
retval = ENOMEM;
goto mkpath_exit;
}
sl = s = apath + strlen(apath) - 1;
do {
sn = s;
if (s - 1 > apath && *s == '.' && *(s - 1) == '/')
s -= 2;
if (s > apath && *s == '/')
s--;
} while (s < sn);
if (s < sl) {
s[1] = '\0';
path = opath = strdup(apath);
if (opath == NULL) {
retval = ENOMEM;
goto mkpath_exit;
}
}
if (0 == mkdirat(dfd, path, omode)) {
if (firstdir) {
*firstdir = strdup(path);
}
goto mkpath_exit;
}
while (1) {
s = strrchr(apath, '/');
if (!s) {
retval = ENOENT;
goto mkpath_exit;
}
*s = '\0';
depth++;
if (0 == mkdirat(dfd, apath, S_IRWXU | S_IRWXG | S_IRWXO)) {
struct stat dirstat;
if (-1 == fstatat(dfd, apath, &dirstat, 0)) {
retval = ENOENT;
goto mkpath_exit;
}
if ((dirstat.st_mode & (S_IWUSR | S_IXUSR)) != (S_IWUSR | S_IXUSR)) {
chmod_mode = dirstat.st_mode | S_IWUSR | S_IXUSR;
if (-1 == fchmodat(dfd, apath, chmod_mode, 0)) {
retval = ENOENT;
goto mkpath_exit;
}
}
if (firstdir) {
*firstdir = strdup(apath);
}
break;
} else if (errno == EEXIST) {
if (fstatat(dfd, apath, &sbuf, 0) == 0 &&
S_ISDIR(sbuf.st_mode)) {
if (firstdir) {
*firstdir = strdup(apath);
}
break;
}
retval = ENOTDIR;
goto mkpath_exit;
} else if (errno != ENOENT) {
retval = errno;
goto mkpath_exit;
}
}
while (depth > 1) {
s = strrchr(apath, '\0');
*s = '/';
depth--;
if (-1 == mkdirat(dfd, apath, S_IRWXU | S_IRWXG | S_IRWXO)) {
if (errno == EEXIST)
continue;
retval = errno;
goto mkpath_exit;
}
if (chmod_mode) {
if (-1 == fchmodat(dfd, apath, chmod_mode, 0)) {
retval = ENOENT;
goto mkpath_exit;
}
}
}
if (-1 == mkdirat(dfd, path, omode)) {
retval = errno;
if (errno == EEXIST &&
fstatat(dfd, path, &sbuf, 0) == 0 &&
!S_ISDIR(sbuf.st_mode)) {
retval = ENOTDIR;
}
}
mkpath_exit:
free(apath);
free(opath);
errno = old_errno;
return retval;
}
int
_mkpath_np(const char *path, mode_t omode, const char ** firstdir) {
return _mkpath(AT_FDCWD, path, omode, firstdir);
}
int mkpath_np(const char *path, mode_t omode) {
return _mkpath(AT_FDCWD, path, omode, NULL);
}
int mkpathat_np(int dfd, const char *path, mode_t omode) {
return _mkpath(dfd, path, omode, NULL);
}