talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

msgcache.c (25086B)


      1 /*
      2  * Claws Mail -- a GTK based, lightweight, and fast e-mail client
      3  * Copyright (C) 1999-2025 the Claws Mail team & Hiroyuki Yamamoto
      4  *
      5  * This program is free software; you can redistribute it and/or modify
      6  * it under the terms of the GNU General Public License as published by
      7  * the Free Software Foundation; either version 3 of the License, or
      8  * (at your option) any later version.
      9  *
     10  * This program is distributed in the hope that it will be useful,
     11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  * GNU General Public License for more details.
     14  *
     15  * You should have received a copy of the GNU General Public License
     16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
     17  *
     18  */
     19 
     20 #include "defs.h"
     21 
     22 #include <stdio.h>
     23 
     24 #include <glib.h>
     25 #include <glib/gi18n.h>
     26 #include <sys/mman.h>
     27 #include <sys/types.h>
     28 #include <sys/stat.h>
     29 
     30 #include <time.h>
     31 
     32 #include "msgcache.h"
     33 #include "utils.h"
     34 #include "procmsg.h"
     35 #include "codeconv.h"
     36 #include "prefs_common.h"
     37 #include "file-utils.h"
     38 
     39 #if G_BYTE_ORDER == G_BIG_ENDIAN
     40 #define bswap_32(x) \
     41      ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >>  8) | \
     42       (((x) & 0x0000ff00) <<  8) | (((x) & 0x000000ff) << 24))
     43 
     44 #define MMAP_TO_GUINT32(x)	\
     45 	(((x[3]&0xff)) |	\
     46 	 ((x[2]&0xff) << 8) |	\
     47 	 ((x[1]&0xff) << 16) |	\
     48 	 ((x[0]&0xff) << 24))
     49 
     50 #define MMAP_TO_GUINT32_SWAPPED(x)	\
     51 	(((x[0]&0xff)) |		\
     52 	 ((x[1]&0xff) << 8) |		\
     53 	 ((x[2]&0xff) << 16) |		\
     54 	 ((x[3]&0xff) << 24))
     55 
     56 static gboolean msgcache_use_mmap_read = TRUE;
     57 
     58 #else
     59 #define bswap_32(x) (x)
     60 
     61 #define MMAP_TO_GUINT32(x)	\
     62 	(((x[0]&0xff)) |	\
     63 	 ((x[1]&0xff) << 8) |	\
     64 	 ((x[2]&0xff) << 16) |	\
     65 	 ((x[3]&0xff) << 24))
     66 
     67 #define MMAP_TO_GUINT32_SWAPPED(x)	\
     68 	(((x[0]&0xff)) |		\
     69 	 ((x[1]&0xff) << 8) |		\
     70 	 ((x[2]&0xff) << 16) |		\
     71 	 ((x[3]&0xff) << 24))
     72 
     73 static gboolean msgcache_use_mmap_read = TRUE;
     74 #endif
     75 
     76 static gboolean swapping = TRUE;
     77 
     78 typedef enum
     79 {
     80 	DATA_READ,
     81 	DATA_WRITE,
     82 	DATA_APPEND
     83 } DataOpenMode;
     84 
     85 struct _MsgCache {
     86 	GHashTable	*msgnum_table;
     87 	GHashTable	*msgid_table;
     88 	guint		 memusage;
     89 	time_t		 last_access;
     90 };
     91 
     92 typedef struct _StringConverter StringConverter;
     93 struct _StringConverter {
     94 	gchar *(*convert) (StringConverter *converter, gchar *srcstr);
     95 	void   (*free)    (StringConverter *converter);
     96 };
     97 
     98 typedef struct _StrdupConverter StrdupConverter;
     99 struct _StrdupConverter {
    100 	StringConverter converter;
    101 };
    102 
    103 typedef struct _CharsetConverter CharsetConverter;
    104 struct _CharsetConverter {
    105 	StringConverter converter;
    106 
    107 	gchar *srccharset;
    108 	gchar *dstcharset;
    109 };
    110 
    111 MsgCache *msgcache_new(void)
    112 {
    113 	MsgCache *cache;
    114 
    115 	cache = g_new0(MsgCache, 1),
    116 	cache->msgnum_table = g_hash_table_new(g_int_hash, g_int_equal);
    117 	cache->msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
    118 	cache->last_access = time(NULL);
    119 
    120 	return cache;
    121 }
    122 
    123 static gboolean msgcache_msginfo_free_func(gpointer num, gpointer msginfo, gpointer user_data)
    124 {
    125 	procmsg_msginfo_free((MsgInfo **)&msginfo);
    126 	return TRUE;
    127 }
    128 
    129 void msgcache_destroy(MsgCache *cache)
    130 {
    131 	g_hash_table_foreach_remove(cache->msgnum_table, msgcache_msginfo_free_func, NULL);
    132 	g_hash_table_destroy(cache->msgid_table);
    133 	g_hash_table_destroy(cache->msgnum_table);
    134 	g_free(cache);
    135 }
    136 
    137 void msgcache_add_msg(MsgCache *cache, MsgInfo *msginfo)
    138 {
    139 	MsgInfo *newmsginfo;
    140 
    141 	newmsginfo = procmsg_msginfo_new_ref(msginfo);
    142 	g_hash_table_insert(cache->msgnum_table, &newmsginfo->msgnum, newmsginfo);
    143 	if(newmsginfo->msgid != NULL)
    144 		g_hash_table_insert(cache->msgid_table, newmsginfo->msgid, newmsginfo);
    145 	cache->memusage += procmsg_msginfo_memusage(msginfo);
    146 	cache->last_access = time(NULL);
    147 
    148 	msginfo->folder->cache_dirty = TRUE;
    149 
    150 	debug_print("Cache size: %d messages, %u bytes\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
    151 }
    152 
    153 void msgcache_remove_msg(MsgCache *cache, guint msgnum)
    154 {
    155 	MsgInfo *msginfo;
    156 
    157 	msginfo = (MsgInfo *) g_hash_table_lookup(cache->msgnum_table, &msgnum);
    158 	if(!msginfo)
    159 		return;
    160 
    161 	cache->memusage -= procmsg_msginfo_memusage(msginfo);
    162 	if(msginfo->msgid)
    163 		g_hash_table_remove(cache->msgid_table, msginfo->msgid);
    164 	g_hash_table_remove(cache->msgnum_table, &msginfo->msgnum);
    165 
    166 	msginfo->folder->cache_dirty = TRUE;
    167 
    168 	procmsg_msginfo_free(&msginfo);
    169 	cache->last_access = time(NULL);
    170 
    171 
    172 	debug_print("Cache size: %d messages, %u bytes\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
    173 }
    174 
    175 void msgcache_update_msg(MsgCache *cache, MsgInfo *msginfo)
    176 {
    177 	MsgInfo *oldmsginfo, *newmsginfo;
    178 
    179 	oldmsginfo = g_hash_table_lookup(cache->msgnum_table, &msginfo->msgnum);
    180 	if(oldmsginfo && oldmsginfo->msgid)
    181 		g_hash_table_remove(cache->msgid_table, oldmsginfo->msgid);
    182 	if (oldmsginfo) {
    183 		g_hash_table_remove(cache->msgnum_table, &oldmsginfo->msgnum);
    184 		cache->memusage -= procmsg_msginfo_memusage(oldmsginfo);
    185 		procmsg_msginfo_free(&oldmsginfo);
    186 	}
    187 
    188 	newmsginfo = procmsg_msginfo_new_ref(msginfo);
    189 	g_hash_table_insert(cache->msgnum_table, &newmsginfo->msgnum, newmsginfo);
    190 	if(newmsginfo->msgid)
    191 		g_hash_table_insert(cache->msgid_table, newmsginfo->msgid, newmsginfo);
    192 	cache->memusage += procmsg_msginfo_memusage(newmsginfo);
    193 	cache->last_access = time(NULL);
    194 
    195 	debug_print("Cache size: %d messages, %u bytes\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
    196 
    197 	msginfo->folder->cache_dirty = TRUE;
    198 
    199 	return;
    200 }
    201 
    202 MsgInfo *msgcache_get_msg(MsgCache *cache, guint num)
    203 {
    204 	MsgInfo *msginfo;
    205 
    206 	msginfo = g_hash_table_lookup(cache->msgnum_table, &num);
    207 	if(!msginfo)
    208 		return NULL;
    209 	cache->last_access = time(NULL);
    210 
    211 	return procmsg_msginfo_new_ref(msginfo);
    212 }
    213 
    214 MsgInfo *msgcache_get_msg_by_id(MsgCache *cache, const gchar *msgid)
    215 {
    216 	MsgInfo *msginfo;
    217 
    218 	msginfo = g_hash_table_lookup(cache->msgid_table, msgid);
    219 	if(!msginfo)
    220 		return NULL;
    221 	cache->last_access = time(NULL);
    222 
    223 	return procmsg_msginfo_new_ref(msginfo);
    224 }
    225 
    226 static void msgcache_get_msg_list_func(gpointer key, gpointer value, gpointer user_data)
    227 {
    228 	MsgInfoList **listptr = user_data;
    229 	MsgInfo *msginfo = value;
    230 
    231 	*listptr = g_slist_prepend(*listptr, procmsg_msginfo_new_ref(msginfo));
    232 }
    233 
    234 MsgInfoList *msgcache_get_msg_list(MsgCache *cache)
    235 {
    236 	MsgInfoList *msg_list = NULL;
    237 	g_hash_table_foreach((GHashTable *)cache->msgnum_table, msgcache_get_msg_list_func, (gpointer)&msg_list);
    238 	cache->last_access = time(NULL);
    239 
    240 	msg_list = g_slist_reverse(msg_list);
    241 	return msg_list;
    242 }
    243 
    244 time_t msgcache_get_last_access_time(MsgCache *cache)
    245 {
    246 
    247 	return cache->last_access;
    248 }
    249 
    250 gint msgcache_get_memory_usage(MsgCache *cache)
    251 {
    252 
    253 	return cache->memusage;
    254 }
    255 
    256 /*
    257  *  Cache saving functions
    258  */
    259 
    260 #define READ_CACHE_DATA(data, fp, total_len) \
    261 { \
    262 	if ((tmp_len = msgcache_read_cache_data_str(fp, &data, conv)) < 0) { \
    263 		procmsg_msginfo_free(&msginfo); \
    264 		error = TRUE; \
    265 		goto bail_err; \
    266 	} \
    267 	total_len += tmp_len; \
    268 }
    269 
    270 #define READ_CACHE_DATA_INT(n, fp) \
    271 { \
    272 	guint32 idata; \
    273 	size_t ni; \
    274  \
    275 	if ((ni = fread(&idata, sizeof(idata), 1, fp)) != 1) { \
    276 		g_warning("read_int: cache data corrupted, read %"G_GSIZE_FORMAT" of %"G_GSIZE_FORMAT" at " \
    277 			  "offset %ld", ni, sizeof(idata), ftell(fp)); \
    278 		procmsg_msginfo_free(&msginfo); \
    279 		error = TRUE; \
    280 		goto bail_err; \
    281 	} else \
    282 		n = swapping ? bswap_32(idata) : (idata);\
    283 }
    284 
    285 #define GET_CACHE_DATA_INT(n)									\
    286 {												\
    287 	if (rem_len < 4) {									\
    288 		g_print("error at rem_len:%d\n", rem_len);					\
    289 		procmsg_msginfo_free(&msginfo); \
    290 		error = TRUE;									\
    291 		goto bail_err;									\
    292 	}											\
    293 	n = (swapping ? (MMAP_TO_GUINT32_SWAPPED(walk_data)):(MMAP_TO_GUINT32(walk_data))); 	\
    294 	walk_data += 4;	rem_len -= 4;								\
    295 }
    296 
    297 #define GET_CACHE_DATA(data, total_len) \
    298 { \
    299 	GET_CACHE_DATA_INT(tmp_len);	\
    300 	if (rem_len < tmp_len) {								\
    301 		g_print("error at rem_len:%d (tmp_len %d)\n", rem_len, tmp_len);		\
    302 		procmsg_msginfo_free(&msginfo); \
    303 		error = TRUE;									\
    304 		goto bail_err;									\
    305 	}											\
    306 	if ((tmp_len = msgcache_get_cache_data_str(walk_data, &data, tmp_len, conv)) < 0) { \
    307 		g_print("error at rem_len:%d\n", rem_len);\
    308 		procmsg_msginfo_free(&msginfo); \
    309 		error = TRUE; \
    310 		goto bail_err; \
    311 	} \
    312 	total_len += tmp_len; \
    313 	walk_data += tmp_len; rem_len -= tmp_len; \
    314 }
    315 
    316 
    317 #define WRITE_CACHE_DATA_INT(n, fp)			\
    318 {							\
    319 	guint32 idata;					\
    320 							\
    321 	idata = (guint32)bswap_32(n);			\
    322 	if (fwrite(&idata, sizeof(idata), 1, fp) != 1)	\
    323 		w_err = 1;				\
    324 	wrote += 4;					\
    325 }
    326 
    327 #define PUT_CACHE_DATA_INT(n)				\
    328 {							\
    329 	walk_data[0]=(((guint32)n)&0x000000ff);			\
    330 	walk_data[1]=(((guint32)n)&0x0000ff00)>>8;		\
    331 	walk_data[2]=(((guint32)n)&0x00ff0000)>>16;		\
    332 	walk_data[3]=(((guint32)n)&0xff000000)>>24;		\
    333 	walk_data += 4;					\
    334 	wrote += 4;					\
    335 }
    336 
    337 #define WRITE_CACHE_DATA(data, fp) \
    338 { \
    339 	size_t len;					\
    340 	if (data == NULL)				\
    341 		len = 0;				\
    342 	else						\
    343 		len = strlen(data);			\
    344 	WRITE_CACHE_DATA_INT(len, fp);			\
    345 	if (w_err == 0 && len > 0) {			\
    346 		if (fwrite(data, 1, len, fp) != len)	\
    347 			w_err = 1;			\
    348 		wrote += len;				\
    349 	} \
    350 }
    351 
    352 #define PUT_CACHE_DATA(data)				\
    353 {							\
    354 	size_t len;					\
    355 	if (data == NULL)				\
    356 		len = 0;				\
    357 	else						\
    358 		len = strlen(data);			\
    359 	PUT_CACHE_DATA_INT(len);			\
    360 	if (len > 0) {					\
    361 		memcpy(walk_data, data, len);		\
    362 		walk_data += len;			\
    363 		wrote += len;				\
    364 	}						\
    365 }
    366 
    367 static FILE *msgcache_open_data_file(const gchar *file, guint version,
    368 				     DataOpenMode mode,
    369 				     gchar *buf, size_t buf_size)
    370 {
    371 	FILE *fp;
    372 	gint32 data_ver = 0;
    373 
    374 	if (mode == DATA_WRITE) {
    375 		int w_err = 0, wrote = 0;
    376 		if ((fp = g_fopen(file, "wb")) == NULL) {
    377 			FILE_OP_ERROR(file, "g_fopen");
    378 			return NULL;
    379 		}
    380 		WRITE_CACHE_DATA_INT(version, fp);
    381 		if (w_err != 0) {
    382 			g_warning("failed to write int");
    383 			fclose(fp);
    384 			return NULL;
    385 		}
    386 		return fp;
    387 	}
    388 
    389 	/* check version */
    390 	if ((fp = g_fopen(file, "rb")) == NULL)
    391 		debug_print("Mark/Cache file '%s' not found\n", file);
    392 	else {
    393 		if (buf && buf_size > 0)
    394 			setvbuf(fp, buf, _IOFBF, buf_size);
    395 		if (fread(&data_ver, sizeof(data_ver), 1, fp) != 1 ||
    396 			 version != bswap_32(data_ver)) {
    397 			g_message("%s: Mark/Cache version is different (%u != %u).\n",
    398 				  file, bswap_32(data_ver), version);
    399 			fclose(fp);
    400 			fp = NULL;
    401 		}
    402 		data_ver = bswap_32(data_ver);
    403 	}
    404 
    405 	if (mode == DATA_READ)
    406 		return fp;
    407 
    408 	if (fp) {
    409 		/* reopen with append mode */
    410 		fclose(fp);
    411 		if ((fp = g_fopen(file, "ab")) == NULL)
    412 			FILE_OP_ERROR(file, "g_fopen");
    413 	} else {
    414 		/* open with overwrite mode if mark file doesn't exist or
    415 		   version is different */
    416 		fp = msgcache_open_data_file(file, version, DATA_WRITE, buf,
    417 					    buf_size);
    418 	}
    419 
    420 	return fp;
    421 }
    422 
    423 static gint msgcache_read_cache_data_str(FILE *fp, gchar **str,
    424 					 StringConverter *conv)
    425 {
    426 	gchar *tmpstr = NULL;
    427 	size_t ni;
    428 	guint32 len;
    429 
    430 	*str = NULL;
    431 	if (!swapping) {
    432 		if ((ni = fread(&len, sizeof(len), 1, fp) != 1) ||
    433 		    len > G_MAXINT) {
    434 			g_warning("read_data_str: cache data (len) corrupted, read %"G_GSIZE_FORMAT
    435 				  " of %"G_GSIZE_FORMAT" bytes at offset %ld", ni, sizeof(len),
    436 				  ftell(fp));
    437 			return -1;
    438 		}
    439 	} else {
    440 		if ((ni = fread(&len, sizeof(len), 1, fp) != 1) ||
    441 		    bswap_32(len) > G_MAXINT) {
    442 			g_warning("read_data_str: cache data (len) corrupted, read %"G_GSIZE_FORMAT
    443 				  " of %"G_GSIZE_FORMAT" bytes at offset %ld", ni, sizeof(len),
    444 				  ftell(fp));
    445 			return -1;
    446 		}
    447 		len = bswap_32(len);
    448 	}
    449 
    450 	if (len == 0)
    451 		return 0;
    452 
    453 	tmpstr = g_try_malloc(len + 1);
    454 
    455 	if(tmpstr == NULL) {
    456 		return -1;
    457 	}
    458 
    459 	if ((ni = fread(tmpstr, 1, len, fp)) != len) {
    460 		g_warning("read_data_str: cache data corrupted, read %"G_GSIZE_FORMAT" of %u "
    461 			  "bytes at offset %ld",
    462 			  ni, len, ftell(fp));
    463 		g_free(tmpstr);
    464 		return -1;
    465 	}
    466 	tmpstr[len] = 0;
    467 
    468 	if (conv != NULL) {
    469 		*str = conv->convert(conv, tmpstr);
    470 		g_free(tmpstr);
    471 	} else
    472 		*str = tmpstr;
    473 
    474 	return len;
    475 }
    476 
    477 static gint msgcache_get_cache_data_str(gchar *src, gchar **str, gint len,
    478 					 StringConverter *conv)
    479 {
    480 	gchar *tmpstr = NULL;
    481 
    482 	*str = NULL;
    483 
    484 	if (len == 0)
    485 		return 0;
    486 
    487 	if(len > 2*1024*1024) {
    488 		g_warning("read_data_str: refusing to allocate %d bytes", len);
    489 		return -1;
    490 	}
    491 
    492 	tmpstr = g_try_malloc(len + 1);
    493 
    494 	if(tmpstr == NULL) {
    495 		return -1;
    496 	}
    497 
    498 	memcpy(tmpstr, src, len);
    499 	tmpstr[len] = 0;
    500 
    501 	if (conv != NULL) {
    502 		*str = conv->convert(conv, tmpstr);
    503 		g_free(tmpstr);
    504 	} else
    505 		*str = tmpstr;
    506 
    507 	return len;
    508 }
    509 
    510 static gchar *strconv_charset_convert(StringConverter *conv, gchar *srcstr)
    511 {
    512 	CharsetConverter *charsetconv = (CharsetConverter *) conv;
    513 
    514 	return conv_codeset_strdup(srcstr, charsetconv->srccharset, charsetconv->dstcharset);
    515 }
    516 
    517 static void strconv_charset_free(StringConverter *conv)
    518 {
    519 	CharsetConverter *charsetconv = (CharsetConverter *) conv;
    520 
    521 	g_free(charsetconv->srccharset);
    522 	g_free(charsetconv->dstcharset);
    523 }
    524 
    525 MsgCache *msgcache_read_cache(FolderItem *item, const gchar *cache_file)
    526 {
    527 	MsgCache *cache;
    528 	FILE *fp;
    529 	MsgInfo *msginfo;
    530 	MsgTmpFlags tmp_flags = 0;
    531 	gchar file_buf[BUFFSIZE];
    532 	guint32 num;
    533         guint refnum;
    534 	gboolean error = FALSE;
    535 	StringConverter *conv = NULL;
    536 	gchar *srccharset = NULL;
    537 	const gchar *dstcharset = NULL;
    538 	gchar *ref = NULL;
    539 	guint memusage = 0;
    540 	gint tmp_len = 0, map_len = -1;
    541 	char *cache_data = NULL;
    542 	struct stat st;
    543 
    544 	swapping = TRUE;
    545 
    546 	/* In case we can't open the mark file with MARK_VERSION, check if we can open it with the
    547 	 * swapped MARK_VERSION. As msgcache_open_data_file swaps it too, if this succeeds,
    548 	 * it means it's the old version (not little-endian) on a big-endian machine. The code has
    549 	 * no effect on x86 as their file doesn't change. */
    550 
    551 	if ((fp = msgcache_open_data_file
    552 		(cache_file, CACHE_VERSION, DATA_READ, file_buf, sizeof(file_buf))) == NULL) {
    553 		if ((fp = msgcache_open_data_file
    554 		(cache_file, bswap_32(CACHE_VERSION), DATA_READ, file_buf, sizeof(file_buf))) == NULL)
    555 			return NULL;
    556 		else
    557 			swapping = FALSE;
    558 	}
    559 
    560 	debug_print("\tReading %sswapped message cache from %s...\n", swapping?"":"un", cache_file);
    561 
    562 	if (folder_has_parent_of_type(item, F_QUEUE)) {
    563 		tmp_flags |= MSG_QUEUED;
    564 	} else if (folder_has_parent_of_type(item, F_DRAFT)) {
    565 		tmp_flags |= MSG_DRAFT;
    566 	}
    567 
    568 	if (msgcache_read_cache_data_str(fp, &srccharset, NULL) < 0) {
    569 		fclose(fp);
    570 		return NULL;
    571 	}
    572 	dstcharset = CS_UTF_8;
    573 	if (srccharset == NULL || dstcharset == NULL) {
    574 		conv = NULL;
    575 	} else if (strcmp(srccharset, dstcharset) == 0) {
    576 		debug_print("using Noop Converter\n");
    577 
    578 		conv = NULL;
    579 	} else {
    580 		CharsetConverter *charsetconv;
    581 
    582 		debug_print("using CharsetConverter\n");
    583 
    584 		charsetconv = g_new0(CharsetConverter, 1);
    585 		charsetconv->converter.convert = strconv_charset_convert;
    586 		charsetconv->converter.free = strconv_charset_free;
    587 		charsetconv->srccharset = g_strdup(srccharset);
    588 		charsetconv->dstcharset = g_strdup(dstcharset);
    589 
    590 		conv = (StringConverter *) charsetconv;
    591 	}
    592 	g_free(srccharset);
    593 
    594 	cache = msgcache_new();
    595 
    596 	if (msgcache_use_mmap_read == TRUE) {
    597 		if (fstat(fileno(fp), &st) >= 0)
    598 			map_len = st.st_size;
    599 		else
    600 			map_len = -1;
    601 		if (map_len > 0) {
    602 			cache_data = mmap(NULL, map_len, PROT_READ, MAP_PRIVATE, fileno(fp), 0);
    603 		}
    604 	} else {
    605 		cache_data = NULL;
    606 	}
    607 	if (cache_data != NULL && cache_data != MAP_FAILED) {
    608 		int rem_len = map_len-ftell(fp);
    609 		char *walk_data = cache_data+ftell(fp);
    610 
    611 		while(rem_len > 0) {
    612 			msginfo = procmsg_msginfo_new();
    613 
    614 			GET_CACHE_DATA_INT(num);
    615 
    616 			msginfo->msgnum = num;
    617 			memusage += sizeof(MsgInfo);
    618 
    619 			GET_CACHE_DATA_INT(msginfo->size);
    620 			GET_CACHE_DATA_INT(msginfo->mtime);
    621 			GET_CACHE_DATA_INT(msginfo->date_t);
    622 			GET_CACHE_DATA_INT(msginfo->flags.tmp_flags);
    623 
    624 			GET_CACHE_DATA(msginfo->fromname, memusage);
    625 
    626 			GET_CACHE_DATA(msginfo->date, memusage);
    627 			GET_CACHE_DATA(msginfo->from, memusage);
    628 			GET_CACHE_DATA(msginfo->to, memusage);
    629 			GET_CACHE_DATA(msginfo->cc, memusage);
    630 			GET_CACHE_DATA(msginfo->subject, memusage);
    631 			GET_CACHE_DATA(msginfo->msgid, memusage);
    632 			GET_CACHE_DATA(msginfo->inreplyto, memusage);
    633 			GET_CACHE_DATA(msginfo->xref, memusage);
    634 
    635 			GET_CACHE_DATA_INT(msginfo->planned_download);
    636 			GET_CACHE_DATA_INT(msginfo->total_size);
    637 			GET_CACHE_DATA_INT(refnum);
    638 
    639 			for (; refnum != 0; refnum--) {
    640 				ref = NULL;
    641 
    642 				GET_CACHE_DATA(ref, memusage);
    643 
    644 				if (ref) {
    645 					if (*ref) {
    646 						msginfo->references =
    647 							g_slist_prepend(msginfo->references, ref);
    648                     } else {
    649 						g_free(ref);
    650 						ref = NULL;
    651 					}
    652                 }
    653 			}
    654 			if (msginfo->references)
    655 				msginfo->references =
    656 					g_slist_reverse(msginfo->references);
    657 
    658 			msginfo->folder = item;
    659 			msginfo->flags.tmp_flags |= tmp_flags;
    660 
    661 			g_hash_table_insert(cache->msgnum_table, &msginfo->msgnum, msginfo);
    662 			if(msginfo->msgid)
    663 				g_hash_table_insert(cache->msgid_table, msginfo->msgid, msginfo);
    664 		}
    665 	} else {
    666 		while (fread(&num, sizeof(num), 1, fp) == 1) {
    667 			if (swapping)
    668 				num = bswap_32(num);
    669 
    670 			msginfo = procmsg_msginfo_new();
    671 			msginfo->msgnum = num;
    672 			memusage += sizeof(MsgInfo);
    673 
    674 			READ_CACHE_DATA_INT(msginfo->size, fp);
    675 			READ_CACHE_DATA_INT(msginfo->mtime, fp);
    676 			READ_CACHE_DATA_INT(msginfo->date_t, fp);
    677 			READ_CACHE_DATA_INT(msginfo->flags.tmp_flags, fp);
    678 
    679 			READ_CACHE_DATA(msginfo->fromname, fp, memusage);
    680 
    681 			READ_CACHE_DATA(msginfo->date, fp, memusage);
    682 			READ_CACHE_DATA(msginfo->from, fp, memusage);
    683 			READ_CACHE_DATA(msginfo->to, fp, memusage);
    684 			READ_CACHE_DATA(msginfo->cc, fp, memusage);
    685 			READ_CACHE_DATA(msginfo->subject, fp, memusage);
    686 			READ_CACHE_DATA(msginfo->msgid, fp, memusage);
    687 			READ_CACHE_DATA(msginfo->inreplyto, fp, memusage);
    688 			READ_CACHE_DATA(msginfo->xref, fp, memusage);
    689 
    690 			READ_CACHE_DATA_INT(msginfo->planned_download, fp);
    691 			READ_CACHE_DATA_INT(msginfo->total_size, fp);
    692 			READ_CACHE_DATA_INT(refnum, fp);
    693 
    694 			for (; refnum != 0; refnum--) {
    695 				ref = NULL;
    696 
    697 				READ_CACHE_DATA(ref, fp, memusage);
    698 
    699 				if (ref) {
    700 					if (*ref) {
    701 						msginfo->references =
    702 							g_slist_prepend(msginfo->references, ref);
    703                     } else {
    704 						g_free(ref);
    705 						ref = NULL;
    706 					}
    707                 }
    708 			}
    709 			if (msginfo->references)
    710 				msginfo->references =
    711 					g_slist_reverse(msginfo->references);
    712 
    713 			msginfo->folder = item;
    714 			msginfo->flags.tmp_flags |= tmp_flags;
    715 
    716 			g_hash_table_insert(cache->msgnum_table, &msginfo->msgnum, msginfo);
    717 			if(msginfo->msgid)
    718 				g_hash_table_insert(cache->msgid_table, msginfo->msgid, msginfo);
    719 		}
    720 	}
    721 bail_err:
    722 	if (cache_data != NULL && cache_data != MAP_FAILED) {
    723 		munmap(cache_data, map_len);
    724 	}
    725 	fclose(fp);
    726 	if (conv != NULL) {
    727 		if (conv->free != NULL)
    728 			conv->free(conv);
    729 		g_free(conv);
    730 	}
    731 
    732 	if(error) {
    733 		msgcache_destroy(cache);
    734 		return NULL;
    735 	}
    736 
    737 	cache->last_access = time(NULL);
    738 	cache->memusage = memusage;
    739 
    740 	debug_print("done. (%d items read)\n", g_hash_table_size(cache->msgnum_table));
    741 	debug_print("Cache size: %d messages, %u bytes\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
    742 
    743 	return cache;
    744 }
    745 
    746 void msgcache_read_mark(MsgCache *cache, const gchar *mark_file)
    747 {
    748 	FILE *fp;
    749 	MsgInfo *msginfo;
    750 	MsgPermFlags perm_flags;
    751 	guint32 num;
    752 	gint map_len = -1;
    753 	char *cache_data = NULL;
    754 	struct stat st;
    755 	gboolean error = FALSE;
    756 
    757 	swapping = TRUE;
    758 
    759 	/* In case we can't open the mark file with MARK_VERSION, check if we can open it with the
    760 	 * swapped MARK_VERSION. As msgcache_open_data_file swaps it too, if this succeeds,
    761 	 * it means it's the old version (not little-endian) on a big-endian machine. The code has
    762 	 * no effect on x86 as their file doesn't change. */
    763 
    764 	if ((fp = msgcache_open_data_file(mark_file, MARK_VERSION, DATA_READ, NULL, 0)) == NULL) {
    765 		/* see if it isn't swapped ? */
    766 		if ((fp = msgcache_open_data_file(mark_file, bswap_32(MARK_VERSION), DATA_READ, NULL, 0)) == NULL)
    767 			return;
    768 		else
    769 			swapping = FALSE; /* yay */
    770 	}
    771 	debug_print("reading %sswapped mark file.\n", swapping?"":"un");
    772 
    773 	if (msgcache_use_mmap_read) {
    774 		if (fstat(fileno(fp), &st) >= 0)
    775 			map_len = st.st_size;
    776 		else
    777 			map_len = -1;
    778 		if (map_len > 0) {
    779 			cache_data = mmap(NULL, map_len, PROT_READ, MAP_PRIVATE, fileno(fp), 0);
    780 		}
    781 	} else {
    782 		cache_data = NULL;
    783 	}
    784 	if (cache_data != NULL && cache_data != MAP_FAILED) {
    785 		int rem_len = map_len-ftell(fp);
    786 		char *walk_data = cache_data+ftell(fp);
    787 
    788 		while(rem_len > 0) {
    789 			GET_CACHE_DATA_INT(num);
    790 			GET_CACHE_DATA_INT(perm_flags);
    791 			msginfo = g_hash_table_lookup(cache->msgnum_table, &num);
    792 			if(msginfo) {
    793 				msginfo->flags.perm_flags = perm_flags;
    794 			}
    795 		}
    796 	} else {
    797 		while (fread(&num, sizeof(num), 1, fp) == 1) {
    798 			if (swapping)
    799 				num = bswap_32(num);
    800 			if (fread(&perm_flags, sizeof(perm_flags), 1, fp) != 1) {
    801 				error = TRUE;
    802 				break;
    803 			}
    804 			if (swapping)
    805 				perm_flags = bswap_32(perm_flags);
    806 			msginfo = g_hash_table_lookup(cache->msgnum_table, &num);
    807 			if(msginfo) {
    808 				msginfo->flags.perm_flags = perm_flags;
    809 			}
    810 		}
    811 	}
    812 bail_err:
    813 	if (cache_data != NULL && cache_data != MAP_FAILED) {
    814 		munmap(cache_data, map_len);
    815 	}
    816 	fclose(fp);
    817 	if (error) {
    818 		debug_print("error reading cache mark from %s\n", mark_file);
    819 	}
    820 }
    821 
    822 static int msgcache_write_cache(MsgInfo *msginfo, FILE *fp)
    823 {
    824 	MsgTmpFlags flags = msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK;
    825 	GSList *cur;
    826 	int w_err = 0, wrote = 0;
    827 
    828 	WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
    829 	WRITE_CACHE_DATA_INT(msginfo->size, fp);
    830 	WRITE_CACHE_DATA_INT(msginfo->mtime, fp);
    831 	WRITE_CACHE_DATA_INT(msginfo->date_t, fp);
    832 	WRITE_CACHE_DATA_INT(flags, fp);
    833 
    834 	WRITE_CACHE_DATA(msginfo->fromname, fp);
    835 
    836 	WRITE_CACHE_DATA(msginfo->date, fp);
    837 	WRITE_CACHE_DATA(msginfo->from, fp);
    838 	WRITE_CACHE_DATA(msginfo->to, fp);
    839 	WRITE_CACHE_DATA(msginfo->cc, fp);
    840 	WRITE_CACHE_DATA(msginfo->subject, fp);
    841 	WRITE_CACHE_DATA(msginfo->msgid, fp);
    842 	WRITE_CACHE_DATA(msginfo->inreplyto, fp);
    843 	WRITE_CACHE_DATA(msginfo->xref, fp);
    844 	WRITE_CACHE_DATA_INT(msginfo->planned_download, fp);
    845 	WRITE_CACHE_DATA_INT(msginfo->total_size, fp);
    846 
    847 	WRITE_CACHE_DATA_INT(g_slist_length(msginfo->references), fp);
    848 
    849 	for (cur = msginfo->references; cur != NULL; cur = cur->next) {
    850 		WRITE_CACHE_DATA((gchar *)cur->data, fp);
    851 	}
    852 	return w_err ? -1 : wrote;
    853 }
    854 
    855 static int msgcache_write_flags(MsgInfo *msginfo, FILE *fp)
    856 {
    857 	MsgPermFlags flags = msginfo->flags.perm_flags;
    858 	int w_err = 0, wrote = 0;
    859 	WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
    860 	WRITE_CACHE_DATA_INT(flags, fp);
    861 	return w_err ? -1 : wrote;
    862 }
    863 
    864 struct write_fps
    865 {
    866 	FILE *cache_fp;
    867 	FILE *mark_fp;
    868 	FILE *tags_fp;
    869 	int error;
    870 	guint cache_size;
    871 	guint mark_size;
    872 	guint tags_size;
    873 };
    874 
    875 static void msgcache_write_func(gpointer key, gpointer value, gpointer user_data)
    876 {
    877 	MsgInfo *msginfo;
    878 	struct write_fps *write_fps;
    879 	int tmp;
    880 
    881 	msginfo = (MsgInfo *)value;
    882 	write_fps = user_data;
    883 
    884 	if (write_fps->cache_fp) {
    885 		tmp = msgcache_write_cache(msginfo, write_fps->cache_fp);
    886 		if (tmp < 0)
    887 			write_fps->error = 1;
    888 		else
    889 			write_fps->cache_size += tmp;
    890 	}
    891 	if (write_fps->mark_fp) {
    892 		tmp = msgcache_write_flags(msginfo, write_fps->mark_fp);
    893 		if (tmp < 0)
    894 			write_fps->error = 1;
    895 		else
    896 			write_fps->mark_size += tmp;
    897 	}
    898 }
    899 
    900 gint msgcache_write(const gchar *cache_file, const gchar *mark_file, const gchar *tags_file, MsgCache *cache)
    901 {
    902 	struct write_fps write_fps;
    903 	gchar *new_cache = NULL, *new_mark = NULL;
    904 	int w_err = 0, wrote = 0;
    905 
    906 	if (cache_file)
    907 		new_cache = g_strconcat(cache_file, ".new", NULL);
    908 	if (mark_file)
    909 		new_mark  = g_strconcat(mark_file, ".new", NULL);
    910 
    911 	write_fps.error = 0;
    912 	write_fps.cache_size = 0;
    913 	write_fps.mark_size = 0;
    914 	write_fps.tags_size = 0;
    915 
    916 	/* open files and write headers */
    917 
    918 	if (cache_file) {
    919 		write_fps.cache_fp = msgcache_open_data_file(new_cache, CACHE_VERSION,
    920 			DATA_WRITE, NULL, 0);
    921 		if (write_fps.cache_fp == NULL) {
    922 			g_free(new_cache);
    923 			g_free(new_mark);
    924 			return -1;
    925 		}
    926 		WRITE_CACHE_DATA(CS_UTF_8, write_fps.cache_fp);
    927 	} else {
    928 		write_fps.cache_fp = NULL;
    929 	}
    930 
    931 	if (w_err != 0) {
    932 		g_warning("failed to write charset");
    933 		if (write_fps.cache_fp)
    934 			fclose(write_fps.cache_fp);
    935 		unlink(new_cache);
    936 		g_free(new_cache);
    937 		g_free(new_mark);
    938 		return -1;
    939 	}
    940 
    941 	if (mark_file) {
    942 		write_fps.mark_fp = msgcache_open_data_file(new_mark, MARK_VERSION,
    943 			DATA_WRITE, NULL, 0);
    944 		if (write_fps.mark_fp == NULL) {
    945 			if (write_fps.cache_fp)
    946 				fclose(write_fps.cache_fp);
    947 			unlink(new_cache);
    948 			g_free(new_cache);
    949 			g_free(new_mark);
    950 			return -1;
    951 		}
    952 	} else {
    953 		write_fps.mark_fp = NULL;
    954 	}
    955 
    956 	write_fps.tags_fp = NULL;
    957 
    958 	/* headers written, note file size */
    959 	if (write_fps.cache_fp)
    960 		write_fps.cache_size = ftell(write_fps.cache_fp);
    961 	if (write_fps.mark_fp)
    962 		write_fps.mark_size = ftell(write_fps.mark_fp);
    963 
    964 	/* write data to the files */
    965 	g_hash_table_foreach(cache->msgnum_table, msgcache_write_func, (gpointer)&write_fps);
    966 
    967 	/* close files */
    968 	if (write_fps.cache_fp)
    969 		write_fps.error |= (fclose(write_fps.cache_fp) != 0);
    970 	if (write_fps.mark_fp)
    971 		write_fps.error |= (fclose(write_fps.mark_fp) != 0);
    972 
    973 	if (write_fps.error != 0) {
    974 		/* in case of error, forget all */
    975 		unlink(new_cache);
    976 		unlink(new_mark);
    977 		g_free(new_cache);
    978 		g_free(new_mark);
    979 		return -1;
    980 	} else {
    981 		/* switch files */
    982 		if (cache_file)
    983 			rename(new_cache, cache_file);
    984 		if (mark_file)
    985 			rename(new_mark, mark_file);
    986 		cache->last_access = time(NULL);
    987 	}
    988 
    989 	g_free(new_cache);
    990 	g_free(new_mark);
    991 	debug_print("msgcache_write() done.\n");
    992 	return 0;
    993 }
    994