/src/dovecot/src/lib-storage/mailbox-uidvalidity.c
Line | Count | Source |
1 | | /* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ |
2 | | |
3 | | #include "lib.h" |
4 | | #include "ioloop.h" |
5 | | #include "str.h" |
6 | | #include "read-full.h" |
7 | | #include "write-full.h" |
8 | | #include "eacces-error.h" |
9 | | #include "mail-user.h" |
10 | | #include "mailbox-list.h" |
11 | | #include "mailbox-uidvalidity.h" |
12 | | |
13 | | #include <stdio.h> |
14 | | #include <unistd.h> |
15 | | #include <dirent.h> |
16 | | #include <fcntl.h> |
17 | | #include <sys/stat.h> |
18 | | |
19 | 0 | #define RETRY_COUNT 10 |
20 | | |
21 | | static uint32_t mailbox_uidvalidity_next_fallback(void) |
22 | 0 | { |
23 | 0 | static uint32_t uid_validity = 0; |
24 | | |
25 | | /* we failed to use the uidvalidity file. don't fail the mailbox |
26 | | creation because of it though, most of the time it's safe enough |
27 | | to use the current time as the uidvalidity value. */ |
28 | 0 | if (uid_validity < ioloop_time32) |
29 | 0 | uid_validity = ioloop_time32; |
30 | 0 | else |
31 | 0 | uid_validity++; |
32 | 0 | if (uid_validity == 0) |
33 | 0 | uid_validity = 1; |
34 | 0 | return uid_validity; |
35 | 0 | } |
36 | | |
37 | | static void mailbox_uidvalidity_write(struct mailbox_list *list, |
38 | | const char *path, uint32_t uid_validity) |
39 | 0 | { |
40 | 0 | struct mail_user *user = mailbox_list_get_user(list); |
41 | 0 | char buf[8+1]; |
42 | 0 | int fd; |
43 | 0 | struct mailbox_permissions perm; |
44 | 0 | mode_t old_mask; |
45 | |
|
46 | 0 | mailbox_list_get_root_permissions(list, &perm); |
47 | |
|
48 | 0 | old_mask = umask(0666 & ~perm.file_create_mode); |
49 | 0 | fd = open(path, O_RDWR | O_CREAT | O_NOFOLLOW, 0666); |
50 | 0 | umask(old_mask); |
51 | 0 | if (fd == -1) { |
52 | 0 | e_error(user->event, "open(%s) failed: %m", path); |
53 | 0 | return; |
54 | 0 | } |
55 | 0 | if (perm.file_create_gid != (gid_t)-1 && |
56 | 0 | fchown(fd, (uid_t)-1, perm.file_create_gid) < 0) { |
57 | 0 | if (errno == EPERM) { |
58 | 0 | e_error(user->event, "%s", |
59 | 0 | eperm_error_get_chgrp("fchown", path, |
60 | 0 | perm.file_create_gid, |
61 | 0 | perm.file_create_gid_origin)); |
62 | 0 | } else { |
63 | 0 | e_error(mailbox_list_get_user(list)->event, |
64 | 0 | "fchown(%s, -1, %ld) failed: %m", |
65 | 0 | path, (long)perm.file_create_gid); |
66 | 0 | } |
67 | 0 | } |
68 | |
|
69 | 0 | if (i_snprintf(buf, sizeof(buf), "%08x", uid_validity) < 0) |
70 | 0 | i_unreached(); |
71 | 0 | if (pwrite_full(fd, buf, strlen(buf), 0) < 0) |
72 | 0 | e_error(user->event, "write(%s) failed: %m", path); |
73 | 0 | if (close(fd) < 0) |
74 | 0 | e_error(user->event, "close(%s) failed: %m", path); |
75 | 0 | } |
76 | | |
77 | | static int |
78 | | mailbox_uidvalidity_rename(struct mailbox_list *list, const char *path, |
79 | | uint32_t *uid_validity, bool log_enoent) |
80 | 0 | { |
81 | 0 | string_t *src, *dest; |
82 | 0 | unsigned int i; |
83 | 0 | size_t prefix_len; |
84 | 0 | int ret; |
85 | |
|
86 | 0 | src = t_str_new(256); |
87 | 0 | str_append(src, path); |
88 | 0 | dest = t_str_new(256); |
89 | 0 | str_append(dest, path); |
90 | 0 | prefix_len = str_len(src); |
91 | |
|
92 | 0 | for (i = 0; i < RETRY_COUNT; i++) { |
93 | 0 | str_truncate(src, prefix_len); |
94 | 0 | str_truncate(dest, prefix_len); |
95 | |
|
96 | 0 | str_printfa(src, ".%08x", *uid_validity); |
97 | 0 | *uid_validity += 1; |
98 | 0 | if (*uid_validity == 0) |
99 | 0 | *uid_validity += 1; |
100 | 0 | str_printfa(dest, ".%08x", *uid_validity); |
101 | |
|
102 | 0 | if ((ret = rename(str_c(src), str_c(dest))) == 0 || |
103 | 0 | errno != ENOENT) |
104 | 0 | break; |
105 | | |
106 | | /* possibly a race condition. try the next value. */ |
107 | 0 | } |
108 | 0 | if (ret < 0 && (errno != ENOENT || log_enoent)) |
109 | 0 | e_error(mailbox_list_get_user(list)->event, |
110 | 0 | "rename(%s, %s) failed: %m", str_c(src), str_c(dest)); |
111 | 0 | return ret; |
112 | 0 | } |
113 | | |
114 | | static uint32_t |
115 | | mailbox_uidvalidity_next_rescan(struct mailbox_list *list, const char *path) |
116 | 0 | { |
117 | 0 | DIR *d; |
118 | 0 | struct dirent *dp; |
119 | 0 | const char *fname, *dir, *prefix, *tmp; |
120 | 0 | unsigned int i; |
121 | 0 | size_t prefix_len; |
122 | 0 | uint32_t cur_value, min_value, max_value; |
123 | 0 | mode_t old_mask; |
124 | 0 | int fd; |
125 | |
|
126 | 0 | fname = strrchr(path, '/'); |
127 | 0 | if (fname == NULL) { |
128 | 0 | dir = "."; |
129 | 0 | fname = path; |
130 | 0 | } else { |
131 | 0 | dir = t_strdup_until(path, fname); |
132 | 0 | fname++; |
133 | 0 | } |
134 | |
|
135 | 0 | d = opendir(dir); |
136 | 0 | if (d == NULL && errno == ENOENT) { |
137 | | /* FIXME: the PATH_TYPE_CONTROL should come as a parameter, but |
138 | | that's an API change, do it in v2.3. it's not really a |
139 | | problem though, since currently all backends use control |
140 | | dirs for the uidvalidity file. */ |
141 | 0 | (void)mailbox_list_mkdir_root(list, dir, MAILBOX_LIST_PATH_TYPE_CONTROL); |
142 | 0 | d = opendir(dir); |
143 | 0 | } |
144 | 0 | if (d == NULL) { |
145 | 0 | e_error(mailbox_list_get_user(list)->event, |
146 | 0 | "opendir(%s) failed: %m", dir); |
147 | 0 | return mailbox_uidvalidity_next_fallback(); |
148 | 0 | } |
149 | 0 | prefix = t_strconcat(fname, ".", NULL); |
150 | 0 | prefix_len = strlen(prefix); |
151 | | |
152 | | /* just in case there happens to be multiple matching uidvalidity |
153 | | files, track the min/max values. use the max value and delete the |
154 | | min value file. */ |
155 | 0 | max_value = 0; min_value = (uint32_t)-1; |
156 | 0 | while ((dp = readdir(d)) != NULL) { |
157 | 0 | if (strncmp(dp->d_name, prefix, prefix_len) == 0) { |
158 | 0 | if (str_to_uint32_hex(dp->d_name + prefix_len, &cur_value) >= 0) { |
159 | 0 | if (min_value > cur_value) |
160 | 0 | min_value = cur_value; |
161 | 0 | if (max_value < cur_value) |
162 | 0 | max_value = cur_value; |
163 | 0 | } |
164 | 0 | } |
165 | 0 | } |
166 | 0 | if (closedir(d) < 0) |
167 | 0 | e_error(mailbox_list_get_user(list)->event, |
168 | 0 | "closedir(%s) failed: %m", dir); |
169 | |
|
170 | 0 | if (max_value == 0) { |
171 | | /* no uidvalidity files. create one. */ |
172 | 0 | for (i = 0; i < RETRY_COUNT; i++) { |
173 | 0 | cur_value = mailbox_uidvalidity_next_fallback(); |
174 | 0 | tmp = t_strdup_printf("%s.%08x", path, cur_value); |
175 | | /* the file is empty, don't bother with permissions */ |
176 | 0 | old_mask = umask(0); |
177 | 0 | fd = open(tmp, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW, 0444); |
178 | 0 | umask(old_mask); |
179 | 0 | if (fd != -1 || errno != EEXIST) |
180 | 0 | break; |
181 | | /* already exists. although it's quite unlikely we'll |
182 | | hit this race condition. more likely we'll create |
183 | | a duplicate file.. */ |
184 | 0 | } |
185 | 0 | if (fd == -1) { |
186 | 0 | e_error(mailbox_list_get_user(list)->event, |
187 | 0 | "creat(%s) failed: %m", tmp); |
188 | 0 | return cur_value; |
189 | 0 | } |
190 | 0 | i_close_fd(&fd); |
191 | 0 | mailbox_uidvalidity_write(list, path, cur_value); |
192 | 0 | return cur_value; |
193 | 0 | } |
194 | 0 | if (min_value != max_value) { |
195 | | /* duplicate uidvalidity files, delete the oldest */ |
196 | 0 | tmp = t_strdup_printf("%s.%08x", path, min_value); |
197 | 0 | i_unlink_if_exists(tmp); |
198 | 0 | } |
199 | |
|
200 | 0 | cur_value = max_value; |
201 | 0 | if (mailbox_uidvalidity_rename(list, path, &cur_value, TRUE) < 0) |
202 | 0 | return mailbox_uidvalidity_next_fallback(); |
203 | 0 | mailbox_uidvalidity_write(list, path, cur_value); |
204 | 0 | return cur_value; |
205 | 0 | } |
206 | | |
207 | | uint32_t mailbox_uidvalidity_next(struct mailbox_list *list, const char *path) |
208 | 0 | { |
209 | 0 | struct mail_user *user = mailbox_list_get_user(list); |
210 | 0 | char buf[8+1]; |
211 | 0 | uint32_t cur_value; |
212 | 0 | int fd, ret; |
213 | |
|
214 | 0 | fd = open(path, O_RDWR); |
215 | 0 | if (fd == -1) { |
216 | 0 | if (errno != ENOENT) |
217 | 0 | e_error(user->event, "open(%s) failed: %m", path); |
218 | 0 | return mailbox_uidvalidity_next_rescan(list, path); |
219 | 0 | } |
220 | 0 | ret = read_full(fd, buf, sizeof(buf)-1); |
221 | 0 | if (ret < 0) { |
222 | 0 | e_error(user->event, "read(%s) failed: %m", path); |
223 | 0 | i_close_fd(&fd); |
224 | 0 | return mailbox_uidvalidity_next_rescan(list, path); |
225 | 0 | } |
226 | 0 | buf[sizeof(buf)-1] = 0; |
227 | 0 | if (ret == 0 || str_to_uint32_hex(buf, &cur_value) < 0 || |
228 | 0 | cur_value == 0) { |
229 | | /* broken value */ |
230 | 0 | i_close_fd(&fd); |
231 | 0 | return mailbox_uidvalidity_next_rescan(list, path); |
232 | 0 | } |
233 | | |
234 | | /* we now have the current uidvalidity value that's hopefully correct */ |
235 | 0 | if (mailbox_uidvalidity_rename(list, path, &cur_value, FALSE) < 0) { |
236 | 0 | i_close_fd(&fd); |
237 | 0 | return mailbox_uidvalidity_next_rescan(list, path); |
238 | 0 | } |
239 | | |
240 | | /* fast path succeeded. write the current value to the main |
241 | | uidvalidity file. */ |
242 | 0 | if (i_snprintf(buf, sizeof(buf), "%08x", cur_value) < 0) |
243 | 0 | i_unreached(); |
244 | 0 | if (pwrite_full(fd, buf, strlen(buf), 0) < 0) |
245 | 0 | e_error(user->event, "write(%s) failed: %m", path); |
246 | 0 | if (close(fd) < 0) |
247 | 0 | e_error(user->event, "close(%s) failed: %m", path); |
248 | 0 | return cur_value; |
249 | 0 | } |