diff options
Diffstat (limited to 'src/lib/mkdir-parents.c')
-rw-r--r-- | src/lib/mkdir-parents.c | 177 |
1 files changed, 177 insertions, 0 deletions
diff --git a/src/lib/mkdir-parents.c b/src/lib/mkdir-parents.c new file mode 100644 index 0000000..93aa87d --- /dev/null +++ b/src/lib/mkdir-parents.c @@ -0,0 +1,177 @@ +/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "eacces-error.h" +#include "mkdir-parents.h" +#include "ipwd.h" + +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +static int ATTR_NULL(5) +mkdir_chown_full(const char *path, mode_t mode, uid_t uid, + gid_t gid, const char *gid_origin) +{ + string_t *str; + mode_t old_mask; + unsigned int i; + int ret, fd = -1, orig_errno; + + for (i = 0;; i++) { + old_mask = umask(0); + ret = mkdir(path, mode); + umask(old_mask); + if (ret < 0) + break; + fd = open(path, O_RDONLY); + if (fd != -1) + break; + if (errno != ENOENT || i == 3) { + i_error("open(%s) failed: %m", path); + return -1; + } + /* it was just rmdir()ed by someone else? retry */ + } + + if (ret < 0) { + if (errno == EISDIR || errno == ENOSYS) { + /* EISDIR check is for BSD/OS which returns it if path + contains '/' at the end and it exists. + + ENOSYS check is for NFS mount points. */ + errno = EEXIST; + } + i_assert(fd == -1); + return -1; + } + if (fchown(fd, uid, gid) < 0) { + i_close_fd(&fd); + orig_errno = errno; + if (rmdir(path) < 0 && errno != ENOENT) + i_error("rmdir(%s) failed: %m", path); + errno = orig_errno; + + if (errno == EPERM && uid == (uid_t)-1) { + i_error("%s", eperm_error_get_chgrp("fchown", path, gid, + gid_origin)); + return -1; + } + + str = t_str_new(256); + str_printfa(str, "fchown(%s, %ld", path, + uid == (uid_t)-1 ? -1L : (long)uid); + if (uid != (uid_t)-1) { + struct passwd pw; + + if (i_getpwuid(uid, &pw) > 0) + str_printfa(str, "(%s)", pw.pw_name); + + } + str_printfa(str, ", %ld", + gid == (gid_t)-1 ? -1L : (long)gid); + if (gid != (gid_t)-1) { + struct group gr; + + if (i_getgrgid(uid, &gr) > 0) + str_printfa(str, "(%s)", gr.gr_name); + } + errno = orig_errno; + i_error("%s) failed: %m", str_c(str)); + return -1; + } + if (gid != (gid_t)-1 && (mode & S_ISGID) == 0) { + /* make sure the directory doesn't have setgid bit enabled + (in case its parent had) */ + if (fchmod(fd, mode) < 0) { + orig_errno = errno; + if (rmdir(path) < 0 && errno != ENOENT) + i_error("rmdir(%s) failed: %m", path); + errno = orig_errno; + i_error("fchmod(%s) failed: %m", path); + i_close_fd(&fd); + return -1; + } + } + i_close_fd(&fd); + return 0; +} + +int mkdir_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) +{ + return mkdir_chown_full(path, mode, uid, gid, NULL); +} + +int mkdir_chgrp(const char *path, mode_t mode, + gid_t gid, const char *gid_origin) +{ + return mkdir_chown_full(path, mode, (uid_t)-1, gid, gid_origin); +} + +static int ATTR_NULL(5) +mkdir_parents_chown_full(const char *path, mode_t mode, uid_t uid, gid_t gid, + const char *gid_origin) +{ + const char *p; + int ret; + + if (mkdir_chown_full(path, mode, uid, gid, gid_origin) < 0) { + if (errno != ENOENT) + return -1; + + /* doesn't exist, try recursively creating our parent dir */ + p = strrchr(path, '/'); + if (p == NULL || p == path) + return -1; /* shouldn't happen */ + + T_BEGIN { + ret = mkdir_parents_chown_full(t_strdup_until(path, p), + mode, uid, + gid, gid_origin); + } T_END; + if (ret < 0 && errno != EEXIST) + return -1; + + /* should work now */ + if (mkdir_chown_full(path, mode, uid, gid, gid_origin) < 0) + return -1; + } + return 0; +} + +int mkdir_parents_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) +{ + return mkdir_parents_chown_full(path, mode, uid, gid, NULL); +} + +int mkdir_parents_chgrp(const char *path, mode_t mode, + gid_t gid, const char *gid_origin) +{ + return mkdir_parents_chown_full(path, mode, (uid_t)-1, gid, gid_origin); +} + +int mkdir_parents(const char *path, mode_t mode) +{ + return mkdir_parents_chown(path, mode, (uid_t)-1, (gid_t)-1); +} + +int stat_first_parent(const char *path, const char **root_dir_r, + struct stat *st_r) +{ + const char *p; + + while (stat(path, st_r) < 0) { + if (errno != ENOENT || strcmp(path, "/") == 0) { + *root_dir_r = path; + return -1; + } + p = strrchr(path, '/'); + if (p == NULL) + path = "/"; + else + path = t_strdup_until(path, p); + } + *root_dir_r = path; + return 0; +} |