Coverage Report

Created: 2025-04-22 06:17

/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
}