summaryrefslogtreecommitdiffstats
path: root/src/lib/mkdir-parents.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/mkdir-parents.c')
-rw-r--r--src/lib/mkdir-parents.c177
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;
+}