1 /* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "str.h"
5 #include "safe-mkstemp.h"
6 #include "mkdir-parents.h"
7 #include "file-lock.h"
8 #include "file-create-locked.h"
9 
10 #include <unistd.h>
11 #include <fcntl.h>
12 #include <sys/stat.h>
13 
14 /* Try mkdir() + lock creation multiple times. This allows the lock file
15    creation to work even while the directory is simultaneously being
16    rmdir()ed. */
17 #define MAX_MKDIR_COUNT 10
18 #define MAX_RETRY_COUNT 1000
19 
20 static int
try_lock_existing(int fd,const char * path,const struct file_create_settings * set,struct file_lock ** lock_r,const char ** error_r)21 try_lock_existing(int fd, const char *path,
22 		  const struct file_create_settings *set,
23 		  struct file_lock **lock_r, const char **error_r)
24 {
25 	struct file_lock_settings lock_set = set->lock_settings;
26 	struct stat st1, st2;
27 	int ret;
28 
29 	lock_set.unlink_on_free = FALSE;
30 	lock_set.close_on_free = FALSE;
31 
32 	if (fstat(fd, &st1) < 0) {
33 		*error_r = t_strdup_printf("fstat(%s) failed: %m", path);
34 		return -1;
35 	}
36 	if (file_wait_lock(fd, path, F_WRLCK, &lock_set, set->lock_timeout_secs,
37 			   lock_r, error_r) <= 0)
38 		return -1;
39 	if (stat(path, &st2) == 0) {
40 		ret = st1.st_ino == st2.st_ino &&
41 			CMP_DEV_T(st1.st_dev, st2.st_dev) ? 1 : 0;
42 	} else if (errno == ENOENT) {
43 		ret = 0;
44 	} else {
45 		*error_r = t_strdup_printf("stat(%s) failed: %m", path);
46 		ret = -1;
47 	}
48 	if (ret <= 0) {
49 		/* the fd is closed next - no need to unlock */
50 		file_lock_free(lock_r);
51 	} else {
52 		file_lock_set_unlink_on_free(
53 			*lock_r, set->lock_settings.unlink_on_free);
54 		file_lock_set_close_on_free(
55 			*lock_r, set->lock_settings.close_on_free);
56 	}
57 	return ret;
58 }
59 
60 static int
try_mkdir(const char * path,const struct file_create_settings * set,const char ** error_r)61 try_mkdir(const char *path, const struct file_create_settings *set,
62 	  const char **error_r)
63 {
64 	uid_t uid = set->mkdir_uid != 0 ? set->mkdir_uid : (uid_t)-1;
65 	gid_t gid = set->mkdir_gid != 0 ? set->mkdir_gid : (gid_t)-1;
66 	const char *p = strrchr(path, '/');
67 	if (p == NULL)
68 		return 0;
69 
70 	const char *dir = t_strdup_until(path, p);
71 	int ret;
72 	if (uid != (uid_t)-1)
73 		ret = mkdir_parents_chown(dir, set->mkdir_mode, uid, gid);
74 	else {
75 		ret = mkdir_parents_chgrp(dir, set->mkdir_mode,
76 					  gid, set->gid_origin);
77 	}
78 	if (ret < 0 && errno != EEXIST) {
79 		*error_r = t_strdup_printf("mkdir_parents(%s) failed: %m", dir);
80 		return -1;
81 	}
82 	return 1;
83 }
84 
85 static int
try_create_new(const char * path,const struct file_create_settings * set,int * fd_r,struct file_lock ** lock_r,const char ** error_r)86 try_create_new(const char *path, const struct file_create_settings *set,
87 	       int *fd_r, struct file_lock **lock_r,  const char **error_r)
88 {
89 	string_t *temp_path = t_str_new(128);
90 	int fd, orig_errno, ret = 1;
91 	int mode = set->mode != 0 ? set->mode : 0600;
92 	uid_t uid = set->uid != 0 ? set->uid : (uid_t)-1;
93 	uid_t gid = set->gid != 0 ? set->gid : (gid_t)-1;
94 
95 	str_append(temp_path, path);
96 	for (unsigned int i = 0; ret > 0; i++) {
97 		if (uid != (uid_t)-1)
98 			fd = safe_mkstemp(temp_path, mode, uid, gid);
99 		else
100 			fd = safe_mkstemp_group(temp_path, mode, gid, set->gid_origin);
101 		if (fd != -1 || errno != ENOENT || set->mkdir_mode == 0 ||
102 		    i >= MAX_MKDIR_COUNT)
103 			break;
104 
105 		int orig_errno = errno;
106 		if ((ret = try_mkdir(path, set, error_r)) < 0)
107 			return -1;
108 		errno = orig_errno;
109 	}
110 	if (fd == -1) {
111 		*error_r = t_strdup_printf("safe_mkstemp(%s) failed: %m", path);
112 		return -1;
113 	}
114 
115 	struct file_lock_settings lock_set = set->lock_settings;
116 	lock_set.unlink_on_free = FALSE;
117 	lock_set.close_on_free = FALSE;
118 
119 	ret = -1;
120 	if (file_try_lock(fd, str_c(temp_path), F_WRLCK, &lock_set,
121 			  lock_r, error_r) <= 0) {
122 	} else if (link(str_c(temp_path), path) < 0) {
123 		if (errno == EEXIST) {
124 			/* just created by somebody else */
125 			ret = 0;
126 		} else if (errno == ENOENT) {
127 			/* nobody should be deleting the temp file unless the
128 			   entire directory is deleted. */
129 			*error_r = t_strdup_printf(
130 				"Temporary file %s was unexpectedly deleted",
131 				str_c(temp_path));
132 		} else {
133 			*error_r = t_strdup_printf("link(%s, %s) failed: %m",
134 						   str_c(temp_path), path);
135 		}
136 		file_lock_free(lock_r);
137 	} else {
138 		file_lock_set_path(*lock_r, path);
139 		file_lock_set_unlink_on_free(
140 			*lock_r, set->lock_settings.unlink_on_free);
141 		file_lock_set_close_on_free(
142 			*lock_r, set->lock_settings.close_on_free);
143 		i_unlink_if_exists(str_c(temp_path));
144 		*fd_r = fd;
145 		return 1;
146 	}
147 	orig_errno = errno;
148 	i_close_fd(&fd);
149 	i_unlink_if_exists(str_c(temp_path));
150 	errno = orig_errno;
151 	return ret;
152 }
153 
file_create_locked(const char * path,const struct file_create_settings * set,struct file_lock ** lock_r,bool * created_r,const char ** error_r)154 int file_create_locked(const char *path, const struct file_create_settings *set,
155 		       struct file_lock **lock_r, bool *created_r,
156 		       const char **error_r)
157 {
158 	unsigned int i;
159 	int fd, ret;
160 
161 	for (i = 0; i < MAX_RETRY_COUNT; i++) {
162 		fd = open(path, O_RDWR);
163 		if (fd != -1) {
164 			ret = try_lock_existing(fd, path, set, lock_r, error_r);
165 			if (ret > 0) {
166 				/* successfully locked an existing file */
167 				*created_r = FALSE;
168 				return fd;
169 			}
170 			i_close_fd(&fd);
171 			if (ret < 0)
172 				return -1;
173 		} else if (errno != ENOENT) {
174 			*error_r = t_strdup_printf("open(%s) failed: %m", path);
175 			return -1;
176 		} else {
177 			/* try to create the file */
178 			ret = try_create_new(path, set, &fd, lock_r, error_r);
179 			if (ret < 0)
180 				return -1;
181 			if (ret > 0) {
182 				/* successfully created a new locked file */
183 				*created_r = TRUE;
184 				return fd;
185 			}
186 			/* the file was just created - try again opening and
187 			   locking it */
188 		}
189 	}
190 	*error_r = t_strdup_printf("Creating a locked file %s keeps failing", path);
191 	errno = EINVAL;
192 	return -1;
193 }
194