Skip to content

Commit a5d2275

Browse files
committed
Merge branch 'imap-nodiskio'
2 parents 3c827e5 + f51de60 commit a5d2275

File tree

18 files changed

+859
-1344
lines changed

18 files changed

+859
-1344
lines changed

doc/pop3.8gx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ Lower clamp is 256.
7171
Default: \fI1024\fP
7272
.TP
7373
\fBcontext_max_mem\fP
74+
Network buffer per client.
75+
.br
7476
Default: \fI2M\fP
7577
.TP
7678
\fBcontext_num\fP

exch/exmdb/names.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: AGPL-3.0-or-later, OR GPL-2.0-or-later WITH linking exception
2-
// SPDX-FileCopyrightText: 2020 grommunio GmbH
2+
// SPDX-FileCopyrightText: 2025 grommunio GmbH
33
// This file is part of Gromox.
44
#include <gromox/defs.h>
55
#include <gromox/exmdb_common_util.hpp>
@@ -151,6 +151,9 @@ static constexpr const char *exmdb_rpc_names[] = {
151151
E(movecopy_folder),
152152
E(create_folder),
153153
E(write_message_v2),
154+
E(imapfile_read),
155+
E(imapfile_write),
156+
E(imapfile_delete),
154157
};
155158
#undef E
156159

@@ -159,7 +162,7 @@ namespace exmdb {
159162
const char *exmdb_rpc_idtoname(exmdb_callid i)
160163
{
161164
auto j = static_cast<uint8_t>(i);
162-
static_assert(std::size(exmdb_rpc_names) == static_cast<uint8_t>(exmdb_callid::write_message_v2) + 1);
165+
static_assert(std::size(exmdb_rpc_names) == static_cast<uint8_t>(exmdb_callid::imapfile_delete) + 1);
163166
auto s = j < std::size(exmdb_rpc_names) ? exmdb_rpc_names[j] : nullptr;
164167
return znul(s);
165168
}

exch/exmdb/store2.cpp

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: AGPL-3.0-or-later
2-
// SPDX-FileCopyrightText: 2022–2024 grommunio GmbH
2+
// SPDX-FileCopyrightText: 2022–2025 grommunio GmbH
33
// This file is part of Gromox.
44
#define _GNU_SOURCE 1 /* AT_* */
55
#include <algorithm>
@@ -16,6 +16,7 @@
1616
#include <utility>
1717
#include <vector>
1818
#include <fmt/core.h>
19+
#include <libHX/io.h>
1920
#include <libHX/string.h>
2021
#include <sys/stat.h>
2122
#include <gromox/database.h>
@@ -606,3 +607,56 @@ BOOL exmdb_server::recalc_store_size(const char *dir, uint32_t flags)
606607
*/
607608
return sql_transact.commit() == SQLITE_OK ? TRUE : false;
608609
}
610+
611+
static bool imapfile_type_ok(const std::string &s)
612+
{
613+
return s == "eml" || s == "ext" || s == "tmp/imap.rfc822";
614+
}
615+
616+
BOOL exmdb_server::imapfile_read(const char *dir, const std::string &type,
617+
const std::string &mid, std::string *data)
618+
{
619+
if (!imapfile_type_ok(type) || mid.find('/') != mid.npos)
620+
return false;
621+
size_t slurp_size = 0;
622+
std::unique_ptr<char[], stdlib_delete> slurp_data(HX_slurp_file((dir + "/"s + type + "/" + mid).c_str(), &slurp_size));
623+
if (slurp_data == nullptr)
624+
return false;
625+
data->assign(slurp_data.get(), slurp_size);
626+
return TRUE;
627+
}
628+
629+
BOOL exmdb_server::imapfile_write(const char *dir, const std::string &type,
630+
const std::string &mid, const std::string &data)
631+
{
632+
if (!imapfile_type_ok(type) || mid.find('/') != mid.npos)
633+
return false;
634+
gromox::tmpfile tf;
635+
auto fd = tf.open_linkable(dir, O_WRONLY, FMODE_PRIVATE);
636+
if (fd < 0)
637+
return false;
638+
auto wrret = HXio_fullwrite(fd, data.data(), data.size());
639+
if (wrret < 0 || static_cast<size_t>(wrret) != data.size())
640+
return false;
641+
auto tgt = fmt::format("{}/{}/{}", dir, type, mid);
642+
auto err = tf.link_to(tgt.c_str());
643+
if (err != 0) {
644+
mlog(LV_ERR, "E-1752: link_to %s: %s", tgt.c_str(), strerror(errno));
645+
return false;
646+
}
647+
return TRUE;
648+
}
649+
650+
BOOL exmdb_server::imapfile_delete(const char *dir, const std::string &type,
651+
const std::string &mid)
652+
{
653+
if (!imapfile_type_ok(type) || mid.find('/') != mid.npos)
654+
return false;
655+
auto fn = dir + "/"s + type + "/" + mid;
656+
if (remove(fn.c_str()) < 0 && errno != ENOENT) {
657+
mlog(LV_WARN, "W-1370: remove %s: %s",
658+
fn.c_str(), strerror(errno));
659+
return false;
660+
}
661+
return TRUE;
662+
}

exch/midb/mail_engine.cpp

Lines changed: 48 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -203,17 +203,6 @@ static std::string make_midb_path(const char *d)
203203
return d + "/exmdb/midb.sqlite3"s;
204204
}
205205

206-
static std::string make_eml_path(const char *d, std::string_view m)
207-
{
208-
/* P2591r5 only for C++26 */
209-
return (d + "/eml/"s) += m;
210-
}
211-
212-
static std::string make_ext_path(const char *d, std::string_view m)
213-
{
214-
return (d + "/ext/"s) += m;
215-
}
216-
217206
static std::unique_ptr<char[]> me_ct_to_utf8(const char *charset,
218207
const char *string) try
219208
{
@@ -248,39 +237,26 @@ static std::unique_ptr<char[]> me_ct_to_utf8(const char *charset,
248237
static uint64_t me_get_digest(sqlite3 *psqlite, const char *mid_string,
249238
Json::Value &digest) try
250239
{
251-
size_t size;
252-
auto ext_path = make_ext_path(cu_get_maildir(), mid_string);
253-
size_t slurp_size = 0;
254-
std::unique_ptr<char[], stdlib_delete> slurp_data(HX_slurp_file(ext_path.c_str(), &slurp_size));
255-
if (slurp_data != nullptr) {
256-
if (!json_from_str(slurp_data.get(), digest))
240+
auto dir = cu_get_maildir();
241+
std::string slurp_data;
242+
if (exmdb_client::imapfile_read(dir, "ext", mid_string, &slurp_data)) {
243+
if (!json_from_str(slurp_data.c_str(), digest))
257244
return 0;
258-
} else if (errno != ENOENT) {
259-
mlog(LV_ERR, "E-2139: read %s: %s", ext_path.c_str(), strerror(errno));
260-
return 0;
261245
} else {
262-
auto eml_path = make_eml_path(cu_get_maildir(), mid_string);
263-
slurp_data.reset(HX_slurp_file(eml_path.c_str(), &slurp_size));
264-
if (slurp_data == nullptr) {
265-
mlog(LV_ERR, "E-1252: %s: %s", eml_path.c_str(), strerror(errno));
246+
if (!exmdb_client::imapfile_read(dir, "eml", mid_string, &slurp_data))
266247
return 0;
267-
}
268248
MAIL imail;
269-
if (!imail.load_from_str(slurp_data.get(), slurp_size))
249+
if (!imail.load_from_str(slurp_data.c_str(), slurp_data.size()))
270250
return 0;
271-
slurp_data.reset();
251+
size_t size = 0;
272252
if (imail.make_digest(&size, digest) <= 0)
273253
return 0;
274-
imail.clear();
275254
digest["file"] = "";
276255
auto djson = json_to_str(digest);
277-
wrapfd fd = open(ext_path.c_str(), O_CREAT | O_TRUNC | O_WRONLY, FMODE_PRIVATE);
278-
if (fd.get() >= 0) {
279-
if (HXio_fullwrite(fd.get(), djson.c_str(), djson.size()) < 0 ||
280-
fd.close_wr() != 0)
281-
mlog(LV_ERR, "E-2082: write %s: %s", ext_path.c_str(), strerror(errno));
282-
} else {
283-
mlog(LV_ERR, "E-2138: open %s for write: %s", ext_path.c_str(), strerror(errno));
256+
if (!exmdb_client::imapfile_write(dir, "ext", mid_string, djson)) {
257+
mlog(LV_ERR, "E-1754: imapfile_write %s/ext/%s did not complete",
258+
dir, mid_string);
259+
return 0;
284260
}
285261
}
286262
auto pstmt = gx_sql_prep(psqlite, "SELECT uid, recent, read,"
@@ -396,70 +372,56 @@ static std::unique_ptr<char[]> me_ct_decode_mime(const char *charset,
396372
static void me_ct_enum_mime(MJSON_MIME *pmime, void *param) try
397373
{
398374
auto penum = static_cast<KEYWORD_ENUM *>(param);
399-
size_t temp_len;
400-
const char *charset;
401-
const char *filename;
402-
403375
if (penum->b_result)
404376
return;
405377
if (pmime->get_mtype() != mime_type::single &&
406378
pmime->get_mtype() != mime_type::single_obj)
407379
return;
408380

409381
if (strncmp(pmime->get_ctype(), "text/", 5) != 0) {
410-
filename = pmime->get_filename();
382+
auto filename = pmime->get_filename();
411383
if ('\0' != filename[0]) {
412384
auto rs = me_ct_decode_mime(penum->charset, filename);
413385
if (rs != nullptr &&
414386
strcasestr(rs.get(), penum->keyword) != nullptr)
415387
penum->b_result = TRUE;
416388
}
417389
}
418-
auto length = pmime->get_content_length();
419-
auto pbuff = std::make_unique<char[]>(2 * length + 1);
420-
auto fd = penum->pjson->seek_fd(pmime->get_id(), MJSON_MIME_CONTENT);
421-
if (fd == -1)
422-
return;
423-
auto read_len = HXio_fullread(fd, pbuff.get(), length);
424-
if (read_len < 0 || static_cast<size_t>(read_len) != length)
390+
std::string content;
391+
if (!exmdb_client::imapfile_read(cu_get_maildir(), "eml",
392+
penum->pjson->filename, &content))
425393
return;
394+
std::string_view ctview(content.data() + pmime->begin,
395+
std::min(content.size(), pmime->get_content_length()));
426396
if (strcasecmp(pmime->get_encoding(), "base64") == 0) {
427-
if (decode64_ex(pbuff.get(), length, &pbuff[length],
428-
length, &temp_len) != 0)
429-
return;
430-
pbuff[length + temp_len] = '\0';
397+
content = base64_decode(ctview);
431398
} else if (strcasecmp(pmime->get_encoding(), "quoted-printable") == 0) {
432-
auto xl = qp_decode_ex(&pbuff[length], length, pbuff.get(), length);
399+
auto xl = qp_decode_ex(&content[0], content.size(), content.c_str(), content.size());
433400
if (xl < 0)
434401
return;
435-
temp_len = xl;
436-
pbuff[length + temp_len] = '\0';
437-
} else {
438-
memcpy(&pbuff[length], pbuff.get(), length);
439-
pbuff[2*length] = '\0';
402+
content.resize(xl);
440403
}
441404

442-
charset = pmime->get_charset();
405+
auto charset = pmime->get_charset();
443406
auto rs = me_ct_to_utf8(*charset != '\0' ?
444-
charset : penum->charset, &pbuff[length]);
407+
charset : penum->charset, content.c_str());
445408
if (rs != nullptr && strcasestr(rs.get(), penum->keyword) != nullptr)
446409
penum->b_result = TRUE;
447410
} catch (const std::bad_alloc &) {
448411
mlog(LV_ERR, "E-1970: ENOMEM");
449412
}
450413

451-
static bool me_ct_search_head(const char *charset,
452-
const char *file_path, const char *tag, const char *value)
414+
static bool me_ct_search_head(const char *charset, const char *mid_string,
415+
const char *tag, const char *value)
453416
{
454-
size_t slurp_size = 0;
455-
std::unique_ptr<char[], stdlib_delete> ct(HX_slurp_file(file_path, &slurp_size));
456-
if (ct == nullptr)
417+
std::string content;
418+
if (!exmdb_client::imapfile_read(cu_get_maildir(), "eml",
419+
mid_string, &content))
457420
return false;
458-
459421
vmime::parsingContext vpctx;
460422
vpctx.setInternationalizedEmailSupport(true); /* RFC 6532 */
461423
vmime::header hdr;
462-
hdr.parse(vpctx, std::string(ct.get(), slurp_size));
424+
hdr.parse(vpctx, content);
463425

464426
for (const auto &hf : hdr.getFieldList()) {
465427
auto hk = hf->getName();
@@ -634,8 +596,7 @@ static bool me_ct_match_mail(sqlite3 *psqlite, const char *charset,
634596
break;
635597
}
636598
case midb_cond::header:
637-
b_result1 = me_ct_search_head(charset,
638-
make_eml_path(cu_get_maildir(), mid_string).c_str(),
599+
b_result1 = me_ct_search_head(charset, mid_string,
639600
ptree_node->ct_headers[0],
640601
ptree_node->ct_headers[1]);
641602
break;
@@ -1371,15 +1332,9 @@ static void me_insert_message(xstmt &stm_insert, uint32_t *puidnext,
13711332

13721333
auto dir = cu_get_maildir();
13731334
std::string djson;
1374-
if (e.midstr.size() > 0) {
1375-
auto ext_path = make_ext_path(dir, e.midstr);
1376-
size_t slurp_size = 0;
1377-
std::unique_ptr<char[], stdlib_delete> slurp_data(HX_slurp_file(ext_path.c_str(), &slurp_size));
1378-
if (slurp_data == nullptr)
1379-
e.midstr.clear();
1380-
else
1381-
djson.assign(slurp_data.get(), slurp_size);
1382-
}
1335+
if (e.midstr.size() > 0 &&
1336+
!exmdb_client::imapfile_read(dir, "ext", e.midstr, &djson))
1337+
e.midstr.clear();
13831338
if (e.midstr.empty()) {
13841339
if (!cu_switch_allocator())
13851340
return;
@@ -1412,28 +1367,18 @@ static void me_insert_message(xstmt &stm_insert, uint32_t *puidnext,
14121367
digest["file"] = "";
14131368
djson = json_to_str(digest);
14141369
e.midstr = std::to_string(time(nullptr)) + "." + std::to_string(++g_sequence_id) + ".midb";
1415-
auto ext_path = make_ext_path(dir, e.midstr);
1416-
wrapfd fd = open(ext_path.c_str(), O_CREAT | O_TRUNC | O_WRONLY, FMODE_PRIVATE);
1417-
if (fd.get() < 0) {
1418-
mlog(LV_ERR, "E-1770: open %s for write: %s", ext_path.c_str(), strerror(errno));
1419-
return;
1420-
}
1421-
if (HXio_fullwrite(fd.get(), djson.c_str(), djson.size()) < 0 ||
1422-
fd.close_wr() != 0) {
1423-
mlog(LV_ERR, "E-1134: write %s: %s", ext_path.c_str(), strerror(errno));
1370+
if (!exmdb_client::imapfile_write(dir, "ext", e.midstr, djson)) {
1371+
mlog(LV_ERR, "E-1770: imapfile_write %s/ext/%s incomplete", dir, e.midstr.c_str());
14241372
return;
14251373
}
1426-
auto eml_path = make_eml_path(dir, e.midstr);
1427-
fd = open(eml_path.c_str(), O_CREAT | O_TRUNC | O_WRONLY, FMODE_PRIVATE);
1428-
if (fd.get() < 0) {
1429-
mlog(LV_ERR, "E-1771: open %s for write: %s", eml_path.c_str(), strerror(errno));
1374+
std::string emlcontent;
1375+
auto err = imail.to_str(emlcontent);
1376+
if (err != 0) {
1377+
mlog(LV_ERR, "E-1771: imail.to_string failed: %s", strerror(err));
14301378
return;
14311379
}
1432-
auto err = imail.to_fd(fd.get());
1433-
if (err == 0)
1434-
err = fd.close_wr();
1435-
if (err != 0) {
1436-
mlog(LV_ERR, "E-1772: to_file %s failed: %s", eml_path.c_str(), strerror(err));
1380+
if (!exmdb_client::imapfile_write(dir, "eml", e.midstr, emlcontent)) {
1381+
mlog(LV_ERR, "E-1772: imapfile_write %s/eml/%s failed", dir, e.midstr.c_str());
14371382
return;
14381383
}
14391384
}
@@ -2173,32 +2118,24 @@ static int me_minst(int argc, char **argv, int sockd) try
21732118

21742119
uint8_t b_unsent = strchr(argv[4], midb_flag::unsent) != nullptr;
21752120
uint8_t b_read = strchr(argv[4], midb_flag::seen) != nullptr;
2176-
auto eml_path = make_eml_path(argv[1], argv[3]);
2177-
size_t slurp_size = 0;
2178-
std::unique_ptr<char[], stdlib_delete> pbuff(HX_slurp_file(eml_path.c_str(), &slurp_size));
2179-
if (pbuff == nullptr) {
2180-
mlog(LV_ERR, "E-2071: read %s: %s", eml_path.c_str(), strerror(errno));
2181-
return errno == ENOMEM ? MIDB_E_NO_MEMORY : MIDB_E_DISK_ERROR;
2121+
std::string pbuff;
2122+
if (!exmdb_client::imapfile_read(argv[1], "eml", argv[3], &pbuff)) {
2123+
mlog(LV_ERR, "E-2071: imapfile_read %s/eml/%s failed", argv[1], argv[3]);
2124+
return MIDB_E_DISK_ERROR;
21822125
}
21832126

21842127
MAIL imail;
2185-
if (!imail.load_from_str(pbuff.get(), slurp_size))
2128+
if (!imail.load_from_str(pbuff.c_str(), pbuff.size()))
21862129
return MIDB_E_IMAIL_RETRIEVE;
21872130
Json::Value digest;
21882131
if (imail.make_digest(&mess_len, digest) <= 0)
21892132
return MIDB_E_IMAIL_DIGEST;
21902133
digest["file"] = "";
21912134
auto djson = json_to_str(digest);
2192-
auto ext_path = make_ext_path(argv[1], argv[3]);
2193-
wrapfd fd = open(ext_path.c_str(), O_CREAT | O_TRUNC | O_WRONLY, FMODE_PRIVATE);
2194-
if (fd.get() < 0) {
2195-
mlog(LV_ERR, "E-2073: Opening %s for writing failed: %s",
2196-
ext_path.c_str(), strerror(errno));
2135+
if (!exmdb_client::imapfile_write(argv[1], "ext", argv[3], djson)) {
2136+
mlog(LV_ERR, "E-2073: imapfile_write %s/ext/%s failed", argv[1], argv[3]);
21972137
return MIDB_E_DISK_ERROR;
21982138
}
2199-
if (HXio_fullwrite(fd.get(), djson.data(), djson.size()) < 0 ||
2200-
fd.close_wr() != 0)
2201-
mlog(LV_ERR, "E-2085: write %s: %s", ext_path.c_str(), strerror(errno));
22022139
auto pidb = me_get_idb(argv[1]);
22032140
if (pidb == nullptr)
22042141
return MIDB_E_HASHTABLE_FULL;
@@ -2222,7 +2159,7 @@ static int me_minst(int argc, char **argv, int sockd) try
22222159
auto pmsgctnt = oxcmail_import(charset, tmzone, &imail,
22232160
cu_alloc_bytes, cu_get_propids_create);
22242161
imail.clear();
2225-
pbuff.reset();
2162+
pbuff.clear();
22262163
if (pmsgctnt == nullptr)
22272164
return MIDB_E_OXCMAIL_IMPORT;
22282165
auto cl_msg = make_scope_exit([&]() { message_content_free(pmsgctnt); });

include/gromox/exmdb_idef.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,6 @@ EXMIDL(purge_datafiles, (const char *dir))
137137
EXMIDL(autoreply_tsquery, (const char *dir, const char *peer, uint64_t window, IDLOUT uint64_t *tdiff))
138138
EXMIDL(autoreply_tsupdate, (const char *dir, const char *peer))
139139
EXMIDL(recalc_store_size, (const char *dir, uint32_t flags))
140+
EXMIDL(imapfile_read, (const char *dir, const std::string &type, const std::string &mid, IDLOUT std::string *data))
141+
EXMIDL(imapfile_write, (const char *dir, const std::string &type, const std::string &mid, const std::string &data))
142+
EXMIDL(imapfile_delete, (const char *dir, const std::string &type, const std::string &mid))

0 commit comments

Comments
 (0)