/src/neomutt/bcache/bcache.c
Line | Count | Source (jump to first uncovered line) |
1 | | /** |
2 | | * @file |
3 | | * Body Caching (local copies of email bodies) |
4 | | * |
5 | | * @authors |
6 | | * Copyright (C) 2017-2023 Richard Russon <rich@flatcap.org> |
7 | | * Copyright (C) 2020 Pietro Cerutti <gahr@gahr.ch> |
8 | | * |
9 | | * @copyright |
10 | | * This program is free software: you can redistribute it and/or modify it under |
11 | | * the terms of the GNU General Public License as published by the Free Software |
12 | | * Foundation, either version 2 of the License, or (at your option) any later |
13 | | * version. |
14 | | * |
15 | | * This program is distributed in the hope that it will be useful, but WITHOUT |
16 | | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
17 | | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
18 | | * details. |
19 | | * |
20 | | * You should have received a copy of the GNU General Public License along with |
21 | | * this program. If not, see <http://www.gnu.org/licenses/>. |
22 | | */ |
23 | | |
24 | | /** |
25 | | * @page bcache_bcache Body Cache functions |
26 | | * |
27 | | * Body Caching (Local copies of email bodies) |
28 | | */ |
29 | | |
30 | | #include "config.h" |
31 | | #include <dirent.h> |
32 | | #include <errno.h> |
33 | | #include <stdio.h> |
34 | | #include <string.h> |
35 | | #include <sys/stat.h> |
36 | | #include <unistd.h> |
37 | | #include "mutt/lib.h" |
38 | | #include "config/lib.h" |
39 | | #include "email/lib.h" |
40 | | #include "core/lib.h" |
41 | | #include "conn/lib.h" |
42 | | #include "lib.h" |
43 | | #include "muttlib.h" |
44 | | |
45 | | /** |
46 | | * struct BodyCache - Local cache of email bodies |
47 | | */ |
48 | | struct BodyCache |
49 | | { |
50 | | char *path; ///< On-disk path to the file |
51 | | }; |
52 | | |
53 | | /** |
54 | | * bcache_path - Create the cache path for a given account/mailbox |
55 | | * @param account Account info |
56 | | * @param mailbox Mailbox name |
57 | | * @param bcache Body cache |
58 | | * @retval 0 Success |
59 | | * @retval -1 Failure |
60 | | */ |
61 | | static int bcache_path(struct ConnAccount *account, const char *mailbox, struct BodyCache *bcache) |
62 | 0 | { |
63 | 0 | const char *const c_message_cache_dir = cs_subset_path(NeoMutt->sub, "message_cache_dir"); |
64 | 0 | if (!account || !c_message_cache_dir || !bcache) |
65 | 0 | return -1; |
66 | | |
67 | 0 | struct stat st = { 0 }; |
68 | 0 | if (!((stat(c_message_cache_dir, &st) == 0) && S_ISDIR(st.st_mode))) |
69 | 0 | { |
70 | 0 | mutt_error(_("Cache disabled, $message_cache_dir isn't a directory: %s"), c_message_cache_dir); |
71 | 0 | return -1; |
72 | 0 | } |
73 | | |
74 | 0 | struct Url url = { 0 }; |
75 | 0 | struct Buffer *host = buf_pool_get(); |
76 | | |
77 | | /* make up a Url we can turn into a string */ |
78 | 0 | account_to_url(account, &url); |
79 | | /* account_to_url() just sets up some pointers; |
80 | | * if this ever changes, we have a memleak here */ |
81 | 0 | url.path = NULL; |
82 | 0 | if (url_tostring(&url, host->data, host->dsize, U_PATH) < 0) |
83 | 0 | { |
84 | 0 | mutt_debug(LL_DEBUG1, "URL to string failed\n"); |
85 | 0 | buf_pool_release(&host); |
86 | 0 | return -1; |
87 | 0 | } |
88 | 0 | buf_fix_dptr(host); |
89 | |
|
90 | 0 | buf_addstr(host, mailbox); |
91 | |
|
92 | 0 | struct Buffer *path = buf_pool_get(); |
93 | 0 | struct Buffer *dst = buf_pool_get(); |
94 | 0 | mutt_encode_path(path, buf_string(host)); |
95 | |
|
96 | 0 | buf_printf(dst, "%s/%s", c_message_cache_dir, buf_string(path)); |
97 | 0 | if (*(dst->dptr - 1) != '/') |
98 | 0 | buf_addch(dst, '/'); |
99 | |
|
100 | 0 | mutt_debug(LL_DEBUG3, "path: '%s'\n", buf_string(dst)); |
101 | 0 | bcache->path = buf_strdup(dst); |
102 | |
|
103 | 0 | buf_pool_release(&host); |
104 | 0 | buf_pool_release(&path); |
105 | 0 | buf_pool_release(&dst); |
106 | 0 | return 0; |
107 | 0 | } |
108 | | |
109 | | /** |
110 | | * mutt_bcache_move - Change the id of a message in the cache |
111 | | * @param bcache Body cache |
112 | | * @param id Per-mailbox unique identifier for the message |
113 | | * @param newid New id for the message |
114 | | * @retval 0 Success |
115 | | * @retval -1 Error |
116 | | */ |
117 | | static int mutt_bcache_move(struct BodyCache *bcache, const char *id, const char *newid) |
118 | 0 | { |
119 | 0 | if (!bcache || !id || (*id == '\0') || !newid || (*newid == '\0')) |
120 | 0 | return -1; |
121 | | |
122 | 0 | struct Buffer *path = buf_pool_get(); |
123 | 0 | struct Buffer *newpath = buf_pool_get(); |
124 | |
|
125 | 0 | buf_printf(path, "%s%s", bcache->path, id); |
126 | 0 | buf_printf(newpath, "%s%s", bcache->path, newid); |
127 | |
|
128 | 0 | mutt_debug(LL_DEBUG3, "bcache: mv: '%s' '%s'\n", buf_string(path), buf_string(newpath)); |
129 | |
|
130 | 0 | int rc = rename(buf_string(path), buf_string(newpath)); |
131 | 0 | buf_pool_release(&path); |
132 | 0 | buf_pool_release(&newpath); |
133 | 0 | return rc; |
134 | 0 | } |
135 | | |
136 | | /** |
137 | | * mutt_bcache_open - Open an Email-Body Cache |
138 | | * @param account current mailbox' account (required) |
139 | | * @param mailbox path to the mailbox of the account (optional) |
140 | | * @retval NULL Failure |
141 | | * |
142 | | * The driver using it is responsible for ensuring that hierarchies are |
143 | | * separated by '/' (if it knows of such a concepts like mailboxes or |
144 | | * hierarchies) |
145 | | */ |
146 | | struct BodyCache *mutt_bcache_open(struct ConnAccount *account, const char *mailbox) |
147 | 0 | { |
148 | 0 | if (!account) |
149 | 0 | return NULL; |
150 | | |
151 | 0 | struct BodyCache *bcache = MUTT_MEM_CALLOC(1, struct BodyCache); |
152 | 0 | if (bcache_path(account, mailbox, bcache) < 0) |
153 | 0 | { |
154 | 0 | mutt_bcache_close(&bcache); |
155 | 0 | return NULL; |
156 | 0 | } |
157 | | |
158 | 0 | return bcache; |
159 | 0 | } |
160 | | |
161 | | /** |
162 | | * mutt_bcache_close - Close an Email-Body Cache |
163 | | * @param[out] ptr Body cache |
164 | | * |
165 | | * Free all memory of bcache and finally FREE() it, too. |
166 | | */ |
167 | | void mutt_bcache_close(struct BodyCache **ptr) |
168 | 0 | { |
169 | 0 | if (!ptr || !*ptr) |
170 | 0 | return; |
171 | | |
172 | 0 | struct BodyCache *bcache = *ptr; |
173 | 0 | FREE(&bcache->path); |
174 | |
|
175 | 0 | FREE(ptr); |
176 | 0 | } |
177 | | |
178 | | /** |
179 | | * mutt_bcache_get - Open a file in the Body Cache |
180 | | * @param bcache Body Cache from mutt_bcache_open() |
181 | | * @param id Per-mailbox unique identifier for the message |
182 | | * @retval ptr Success |
183 | | * @retval NULL Failure |
184 | | */ |
185 | | FILE *mutt_bcache_get(struct BodyCache *bcache, const char *id) |
186 | 0 | { |
187 | 0 | if (!id || (*id == '\0') || !bcache) |
188 | 0 | return NULL; |
189 | | |
190 | 0 | struct Buffer *path = buf_pool_get(); |
191 | 0 | buf_addstr(path, bcache->path); |
192 | 0 | buf_addstr(path, id); |
193 | |
|
194 | 0 | FILE *fp = mutt_file_fopen(buf_string(path), "r"); |
195 | |
|
196 | 0 | mutt_debug(LL_DEBUG3, "bcache: get: '%s': %s\n", buf_string(path), fp ? "yes" : "no"); |
197 | |
|
198 | 0 | buf_pool_release(&path); |
199 | 0 | return fp; |
200 | 0 | } |
201 | | |
202 | | /** |
203 | | * mutt_bcache_put - Create a file in the Body Cache |
204 | | * @param bcache Body Cache from mutt_bcache_open() |
205 | | * @param id Per-mailbox unique identifier for the message |
206 | | * @retval ptr Success |
207 | | * @retval NULL Failure |
208 | | * |
209 | | * The returned FILE* is in a temporary location. |
210 | | * Use mutt_bcache_commit to put it into place |
211 | | */ |
212 | | FILE *mutt_bcache_put(struct BodyCache *bcache, const char *id) |
213 | 0 | { |
214 | 0 | if (!id || (*id == '\0') || !bcache) |
215 | 0 | return NULL; |
216 | | |
217 | 0 | struct Buffer *path = buf_pool_get(); |
218 | 0 | buf_printf(path, "%s%s%s", bcache->path, id, ".tmp"); |
219 | |
|
220 | 0 | struct stat st = { 0 }; |
221 | 0 | if (stat(bcache->path, &st) == 0) |
222 | 0 | { |
223 | 0 | if (!S_ISDIR(st.st_mode)) |
224 | 0 | { |
225 | 0 | mutt_error(_("Message cache isn't a directory: %s"), bcache->path); |
226 | 0 | return NULL; |
227 | 0 | } |
228 | 0 | } |
229 | 0 | else |
230 | 0 | { |
231 | 0 | if (mutt_file_mkdir(bcache->path, S_IRWXU | S_IRWXG | S_IRWXO) < 0) |
232 | 0 | { |
233 | 0 | mutt_error(_("Can't create %s: %s"), bcache->path, strerror(errno)); |
234 | 0 | return NULL; |
235 | 0 | } |
236 | 0 | } |
237 | | |
238 | 0 | mutt_debug(LL_DEBUG3, "bcache: put: '%s'\n", buf_string(path)); |
239 | |
|
240 | 0 | FILE *fp = mutt_file_fopen(buf_string(path), "w+"); |
241 | 0 | buf_pool_release(&path); |
242 | 0 | return fp; |
243 | 0 | } |
244 | | |
245 | | /** |
246 | | * mutt_bcache_commit - Move a temporary file into the Body Cache |
247 | | * @param bcache Body Cache from mutt_bcache_open() |
248 | | * @param id Per-mailbox unique identifier for the message |
249 | | * @retval 0 Success |
250 | | * @retval -1 Failure |
251 | | */ |
252 | | int mutt_bcache_commit(struct BodyCache *bcache, const char *id) |
253 | 0 | { |
254 | 0 | struct Buffer *tmpid = buf_pool_get(); |
255 | 0 | buf_printf(tmpid, "%s.tmp", id); |
256 | |
|
257 | 0 | int rc = mutt_bcache_move(bcache, buf_string(tmpid), id); |
258 | 0 | buf_pool_release(&tmpid); |
259 | 0 | return rc; |
260 | 0 | } |
261 | | |
262 | | /** |
263 | | * mutt_bcache_del - Delete a file from the Body Cache |
264 | | * @param bcache Body Cache from mutt_bcache_open() |
265 | | * @param id Per-mailbox unique identifier for the message |
266 | | * @retval 0 Success |
267 | | * @retval -1 Failure |
268 | | */ |
269 | | int mutt_bcache_del(struct BodyCache *bcache, const char *id) |
270 | 0 | { |
271 | 0 | if (!id || (*id == '\0') || !bcache) |
272 | 0 | return -1; |
273 | | |
274 | 0 | struct Buffer *path = buf_pool_get(); |
275 | 0 | buf_addstr(path, bcache->path); |
276 | 0 | buf_addstr(path, id); |
277 | |
|
278 | 0 | mutt_debug(LL_DEBUG3, "bcache: del: '%s'\n", buf_string(path)); |
279 | |
|
280 | 0 | int rc = unlink(buf_string(path)); |
281 | 0 | buf_pool_release(&path); |
282 | 0 | return rc; |
283 | 0 | } |
284 | | |
285 | | /** |
286 | | * mutt_bcache_exists - Check if a file exists in the Body Cache |
287 | | * @param bcache Body Cache from mutt_bcache_open() |
288 | | * @param id Per-mailbox unique identifier for the message |
289 | | * @retval 0 Success |
290 | | * @retval -1 Failure |
291 | | */ |
292 | | int mutt_bcache_exists(struct BodyCache *bcache, const char *id) |
293 | 0 | { |
294 | 0 | if (!id || (*id == '\0') || !bcache) |
295 | 0 | return -1; |
296 | | |
297 | 0 | struct Buffer *path = buf_pool_get(); |
298 | 0 | buf_addstr(path, bcache->path); |
299 | 0 | buf_addstr(path, id); |
300 | |
|
301 | 0 | int rc = 0; |
302 | 0 | struct stat st = { 0 }; |
303 | 0 | if (stat(buf_string(path), &st) < 0) |
304 | 0 | rc = -1; |
305 | 0 | else |
306 | 0 | rc = (S_ISREG(st.st_mode) && (st.st_size != 0)) ? 0 : -1; |
307 | |
|
308 | 0 | mutt_debug(LL_DEBUG3, "bcache: exists: '%s': %s\n", buf_string(path), |
309 | 0 | (rc == 0) ? "yes" : "no"); |
310 | |
|
311 | 0 | buf_pool_release(&path); |
312 | 0 | return rc; |
313 | 0 | } |
314 | | |
315 | | /** |
316 | | * mutt_bcache_list - Find matching entries in the Body Cache |
317 | | * @param bcache Body Cache from mutt_bcache_open() |
318 | | * @param want_id Callback function called for each match |
319 | | * @param data Data to pass to the callback function |
320 | | * @retval -1 Failure |
321 | | * @retval >=0 count of matching items |
322 | | * |
323 | | * This more or less "examines" the cache and calls a function with |
324 | | * each id it finds if given. |
325 | | * |
326 | | * The optional callback function gets the id of a message, the very same |
327 | | * body cache handle mutt_bcache_list() is called with (to, perhaps, |
328 | | * perform further operations on the bcache), and a data cookie which is |
329 | | * just passed as-is. If the return value of the callback is non-zero, the |
330 | | * listing is aborted and continued otherwise. The callback is optional |
331 | | * so that this function can be used to count the items in the cache |
332 | | * (see below for return value). |
333 | | */ |
334 | | int mutt_bcache_list(struct BodyCache *bcache, bcache_list_t want_id, void *data) |
335 | 0 | { |
336 | 0 | DIR *dir = NULL; |
337 | 0 | struct dirent *de = NULL; |
338 | 0 | int rc = -1; |
339 | |
|
340 | 0 | if (!bcache || !(dir = mutt_file_opendir(bcache->path, MUTT_OPENDIR_NONE))) |
341 | 0 | goto out; |
342 | | |
343 | 0 | rc = 0; |
344 | |
|
345 | 0 | mutt_debug(LL_DEBUG3, "bcache: list: dir: '%s'\n", bcache->path); |
346 | |
|
347 | 0 | while ((de = readdir(dir))) |
348 | 0 | { |
349 | 0 | if (mutt_str_startswith(de->d_name, ".") || mutt_str_startswith(de->d_name, "..")) |
350 | 0 | { |
351 | 0 | continue; |
352 | 0 | } |
353 | | |
354 | 0 | mutt_debug(LL_DEBUG3, "bcache: list: dir: '%s', id :'%s'\n", bcache->path, de->d_name); |
355 | |
|
356 | 0 | if (want_id && (want_id(de->d_name, bcache, data) != 0)) |
357 | 0 | goto out; |
358 | | |
359 | 0 | rc++; |
360 | 0 | } |
361 | | |
362 | 0 | out: |
363 | 0 | if (dir) |
364 | 0 | { |
365 | 0 | if (closedir(dir) < 0) |
366 | 0 | rc = -1; |
367 | 0 | } |
368 | 0 | mutt_debug(LL_DEBUG3, "bcache: list: did %d entries\n", rc); |
369 | 0 | return rc; |
370 | 0 | } |