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