1 /* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
2
3 #include "lib.h"
4 #include "ioloop.h"
5 #include "module-context.h"
6 #include "eacces-error.h"
7 #include "mail-index-private.h"
8 #include "mail-index-alloc-cache.h"
9
10 #define MAIL_INDEX_ALLOC_CACHE_CONTEXT(obj) \
11 MODULE_CONTEXT(obj, mail_index_alloc_cache_index_module)
12
13 /* How many seconds to keep index opened for reuse after it's been closed */
14 #define INDEX_CACHE_TIMEOUT 10
15 /* How many closed indexes to keep */
16 #define INDEX_CACHE_MAX 3
17
18 struct mail_index_alloc_cache_list {
19 union mail_index_module_context module_ctx;
20 struct mail_index_alloc_cache_list *next;
21
22 struct mail_index *index;
23 char *mailbox_path;
24 int refcount;
25 bool referenced;
26
27 dev_t index_dir_dev;
28 ino_t index_dir_ino;
29
30 time_t destroy_time;
31 };
32
33 static MODULE_CONTEXT_DEFINE_INIT(mail_index_alloc_cache_index_module,
34 &mail_index_module_register);
35 static struct mail_index_alloc_cache_list *indexes = NULL;
36 static unsigned int indexes_cache_references_count = 0;
37 static struct timeout *to_index = NULL;
38
39 static struct mail_index_alloc_cache_list *
mail_index_alloc_cache_add(struct mail_index * index,const char * mailbox_path,struct stat * st)40 mail_index_alloc_cache_add(struct mail_index *index,
41 const char *mailbox_path, struct stat *st)
42 {
43 struct mail_index_alloc_cache_list *list;
44
45 list = i_new(struct mail_index_alloc_cache_list, 1);
46 list->refcount = 1;
47 list->index = index;
48
49 list->mailbox_path = i_strdup(mailbox_path);
50 list->index_dir_dev = st->st_dev;
51 list->index_dir_ino = st->st_ino;
52
53 list->next = indexes;
54 indexes = list;
55
56 MODULE_CONTEXT_SET(index, mail_index_alloc_cache_index_module, list);
57 return list;
58 }
59
60 static void
mail_index_alloc_cache_list_unref(struct mail_index_alloc_cache_list * list)61 mail_index_alloc_cache_list_unref(struct mail_index_alloc_cache_list *list)
62 {
63 i_assert(list->referenced);
64 i_assert(indexes_cache_references_count > 0);
65
66 indexes_cache_references_count--;
67 mail_index_close(list->index);
68 list->referenced = FALSE;
69 }
70
71 static void
mail_index_alloc_cache_list_free(struct mail_index_alloc_cache_list * list)72 mail_index_alloc_cache_list_free(struct mail_index_alloc_cache_list *list)
73 {
74 i_assert(list->refcount == 0);
75
76 if (list->referenced)
77 mail_index_alloc_cache_list_unref(list);
78 mail_index_free(&list->index);
79 i_free(list->mailbox_path);
80 i_free(list);
81 }
82
83 static struct mail_index_alloc_cache_list *
mail_index_alloc_cache_find_and_expire(const char * mailbox_path,const char * index_dir,const struct stat * index_st)84 mail_index_alloc_cache_find_and_expire(const char *mailbox_path,
85 const char *index_dir,
86 const struct stat *index_st)
87 {
88 struct mail_index_alloc_cache_list **indexp, *rec, *match;
89 unsigned int destroy_count;
90 struct stat st;
91
92 destroy_count = 0; match = NULL;
93 for (indexp = &indexes; *indexp != NULL;) {
94 rec = *indexp;
95
96 if (match != NULL) {
97 /* already found the index. we're just going through
98 the rest of them to drop 0 refcounts */
99 } else if (rec->refcount == 0 && rec->index->open_count == 0) {
100 /* index is already closed. don't even try to
101 reuse it. */
102 } else if (index_dir != NULL && rec->index_dir_ino != 0) {
103 if (index_st->st_ino == rec->index_dir_ino &&
104 CMP_DEV_T(index_st->st_dev, rec->index_dir_dev)) {
105 /* make sure the directory still exists.
106 it might have been renamed and we're trying
107 to access it via its new path now. */
108 if (stat(rec->index->dir, &st) < 0 ||
109 st.st_ino != index_st->st_ino ||
110 !CMP_DEV_T(st.st_dev, index_st->st_dev))
111 rec->destroy_time = 0;
112 else
113 match = rec;
114 }
115 } else if (mailbox_path != NULL && rec->mailbox_path != NULL &&
116 index_dir == NULL && rec->index_dir_ino == 0) {
117 if (strcmp(mailbox_path, rec->mailbox_path) == 0)
118 match = rec;
119 }
120
121 if (rec->refcount == 0 && rec != match) {
122 if (rec->destroy_time <= ioloop_time ||
123 destroy_count >= INDEX_CACHE_MAX) {
124 *indexp = rec->next;
125 mail_index_alloc_cache_list_free(rec);
126 continue;
127 } else {
128 destroy_count++;
129 }
130 }
131
132 indexp = &(*indexp)->next;
133 }
134 return match;
135 }
136
137 struct mail_index *
mail_index_alloc_cache_get(struct event * parent_event,const char * mailbox_path,const char * index_dir,const char * prefix)138 mail_index_alloc_cache_get(struct event *parent_event, const char *mailbox_path,
139 const char *index_dir, const char *prefix)
140 {
141 struct mail_index_alloc_cache_list *match;
142 struct stat st;
143
144 /* compare index_dir inodes so we don't break even with symlinks.
145 if index_dir doesn't exist yet or if using in-memory indexes, just
146 compare mailbox paths */
147 i_zero(&st);
148 if (index_dir == NULL) {
149 /* in-memory indexes */
150 } else if (stat(index_dir, &st) < 0) {
151 if (errno == ENOENT) {
152 /* it'll be created later */
153 } else if (errno == EACCES) {
154 e_error(parent_event, "%s",
155 eacces_error_get("stat", index_dir));
156 } else {
157 e_error(parent_event, "stat(%s) failed: %m", index_dir);
158 }
159 }
160
161 match = mail_index_alloc_cache_find_and_expire(mailbox_path,
162 index_dir, &st);
163 if (match == NULL) {
164 struct mail_index *index =
165 mail_index_alloc(parent_event, index_dir, prefix);
166 match = mail_index_alloc_cache_add(index, mailbox_path, &st);
167 } else {
168 match->refcount++;
169 }
170 i_assert(match->index != NULL);
171 return match->index;
172 }
173
174 struct mail_index *
mail_index_alloc_cache_find(const char * index_dir)175 mail_index_alloc_cache_find(const char *index_dir)
176 {
177 struct mail_index_alloc_cache_list *rec;
178 struct stat st;
179
180 if (stat(index_dir, &st) < 0) {
181 if (errno != ENOENT)
182 i_error("stat(%s) failed: %m", index_dir);
183 return NULL;
184 }
185
186 for (rec = indexes; rec != NULL; rec = rec->next) {
187 if (st.st_ino == rec->index_dir_ino &&
188 CMP_DEV_T(st.st_dev, rec->index_dir_dev))
189 return rec->index;
190 }
191 return NULL;
192 }
193
destroy_unrefed(unsigned int min_destroy_count)194 static bool destroy_unrefed(unsigned int min_destroy_count)
195 {
196 struct mail_index_alloc_cache_list **list, *rec;
197 bool destroyed = FALSE;
198 bool seen_ref0 = FALSE;
199
200 for (list = &indexes; *list != NULL;) {
201 rec = *list;
202
203 if (rec->refcount == 0 &&
204 (min_destroy_count > 0 || rec->destroy_time <= ioloop_time)) {
205 *list = rec->next;
206 destroyed = TRUE;
207 mail_index_alloc_cache_list_free(rec);
208 if (min_destroy_count > 0)
209 min_destroy_count--;
210 } else {
211 if (rec->refcount == 0)
212 seen_ref0 = TRUE;
213 if (min_destroy_count > 0 &&
214 rec->index->open_count == 1 &&
215 rec->referenced) {
216 /* we're the only one keeping this index open.
217 we might be here, because the caller is
218 deleting this mailbox and wants its indexes
219 to be closed. so close it. */
220 destroyed = TRUE;
221 mail_index_alloc_cache_list_unref(rec);
222 }
223 list = &(*list)->next;
224 }
225 }
226
227 if (!seen_ref0 && to_index != NULL)
228 timeout_remove(&to_index);
229 return destroyed;
230 }
231
232 static void ATTR_NULL(1)
index_removal_timeout(void * context ATTR_UNUSED)233 index_removal_timeout(void *context ATTR_UNUSED)
234 {
235 destroy_unrefed(0);
236 }
237
mail_index_alloc_cache_unref(struct mail_index ** _index)238 void mail_index_alloc_cache_unref(struct mail_index **_index)
239 {
240 struct mail_index *index = *_index;
241 struct mail_index_alloc_cache_list *list, **listp;
242
243 *_index = NULL;
244 list = NULL;
245 for (listp = &indexes; *listp != NULL; listp = &(*listp)->next) {
246 if ((*listp)->index == index) {
247 list = *listp;
248 break;
249 }
250 }
251
252 i_assert(list != NULL);
253 i_assert(list->refcount > 0);
254
255 list->refcount--;
256 list->destroy_time = ioloop_time + INDEX_CACHE_TIMEOUT;
257
258 if (list->refcount == 0 && index->open_count == 0) {
259 /* index was already closed. don't even try to cache it. */
260 *listp = list->next;
261 mail_index_alloc_cache_list_free(list);
262 } else if (to_index == NULL) {
263 /* Add to root ioloop in case we got here from an inner
264 ioloop which gets destroyed too early. */
265 to_index = timeout_add_to(io_loop_get_root(),
266 INDEX_CACHE_TIMEOUT*1000/2,
267 index_removal_timeout, NULL);
268 }
269 }
270
mail_index_alloc_cache_destroy_unrefed(void)271 void mail_index_alloc_cache_destroy_unrefed(void)
272 {
273 destroy_unrefed(UINT_MAX);
274 }
275
mail_index_alloc_cache_index_opened(struct mail_index * index)276 void mail_index_alloc_cache_index_opened(struct mail_index *index)
277 {
278 struct mail_index_alloc_cache_list *list =
279 MAIL_INDEX_ALLOC_CACHE_CONTEXT(index);
280 struct stat st;
281
282 if (list != NULL && list->index_dir_ino == 0 &&
283 !MAIL_INDEX_IS_IN_MEMORY(index)) {
284 /* newly created index directory. update its stat. */
285 if (stat(index->dir, &st) == 0) {
286 list->index_dir_ino = st.st_ino;
287 list->index_dir_dev = st.st_dev;
288 }
289 }
290 }
291
mail_index_alloc_cache_index_closing(struct mail_index * index)292 void mail_index_alloc_cache_index_closing(struct mail_index *index)
293 {
294 struct mail_index_alloc_cache_list *list =
295 MAIL_INDEX_ALLOC_CACHE_CONTEXT(index);
296
297 i_assert(index->open_count > 0);
298 if (index->open_count > 1 || list == NULL)
299 return;
300
301 if (list->referenced) {
302 /* we're closing our referenced index */
303 return;
304 }
305 while (indexes_cache_references_count > INDEX_CACHE_MAX) {
306 if (!destroy_unrefed(1)) {
307 /* our cache is full already, don't keep more */
308 return;
309 }
310 }
311 /* keep the index referenced for caching */
312 indexes_cache_references_count++;
313 list->referenced = TRUE;
314 index->open_count++;
315 }
316