Extending test-client-custom-summary to try e_book_client_get_contacts_uids()
[platform/upstream/evolution-data-server.git] / camel / camel-block-file.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
4  *
5  * Authors: Michael Zucchi <notzed@ximian.com>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of version 2 of the GNU Lesser General Public
9  * License as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <sys/stat.h>
32 #include <sys/types.h>
33
34 #include <glib/gstdio.h>
35
36 #include "camel-block-file.h"
37 #include "camel-file-utils.h"
38
39 #define d(x) /*(printf("%s(%d):%s: ",  __FILE__, __LINE__, __PRETTY_FUNCTION__),(x))*/
40
41 /* Locks must be obtained in the order defined */
42
43 struct _CamelBlockFilePrivate {
44         struct _CamelBlockFile *base;
45
46         GMutex root_lock; /* for modifying the root block */
47         GMutex cache_lock; /* for refcounting, flag manip, cache manip */
48         GMutex io_lock; /* for all io ops */
49
50         guint deleted : 1;
51 };
52
53 #define CAMEL_BLOCK_FILE_LOCK(kf, lock) (g_mutex_lock(&(kf)->priv->lock))
54 #define CAMEL_BLOCK_FILE_TRYLOCK(kf, lock) (g_mutex_trylock(&(kf)->priv->lock))
55 #define CAMEL_BLOCK_FILE_UNLOCK(kf, lock) (g_mutex_unlock(&(kf)->priv->lock))
56
57 #define LOCK(x) g_mutex_lock(&x)
58 #define UNLOCK(x) g_mutex_unlock(&x)
59
60 static GMutex block_file_lock;
61
62 /* lru cache of block files */
63 static GQueue block_file_list = G_QUEUE_INIT;
64 /* list to store block files that are actually intialised */
65 static GQueue block_file_active_list = G_QUEUE_INIT;
66 static gint block_file_count = 0;
67 static gint block_file_threshhold = 10;
68
69 static gint sync_nolock (CamelBlockFile *bs);
70 static gint sync_block_nolock (CamelBlockFile *bs, CamelBlock *bl);
71
72 G_DEFINE_TYPE (CamelBlockFile, camel_block_file, CAMEL_TYPE_OBJECT)
73
74 static gint
75 block_file_validate_root (CamelBlockFile *bs)
76 {
77         CamelBlockRoot *br;
78         struct stat st;
79         gint retval;
80
81         br = bs->root;
82
83         retval = fstat (bs->fd, &st);
84
85         d (printf ("Validate root: '%s'\n", bs->path));
86         d (printf ("version: %.8s (%.8s)\n", bs->root->version, bs->version));
87         d (printf (
88                 "block size: %d (%d)%s\n",
89                 br->block_size, bs->block_size,
90                 br->block_size != bs->block_size ? " BAD":" OK"));
91         d (printf (
92                 "free: %ld (%d add size < %ld)%s\n",
93                 (glong) br->free,
94                 br->free / bs->block_size * bs->block_size,
95                 (glong) st.st_size,
96                 (br->free > st.st_size) ||
97                 (br->free % bs->block_size) != 0 ? " BAD":" OK"));
98         d (printf (
99                 "last: %ld (%d and size: %ld)%s\n",
100                 (glong) br->last,
101                 br->last / bs->block_size * bs->block_size,
102                 (glong) st.st_size,
103                 (br->last != st.st_size) ||
104                 ((br->last % bs->block_size) != 0) ? " BAD": " OK"));
105         d (printf (
106                 "flags: %s\n",
107                 (br->flags & CAMEL_BLOCK_FILE_SYNC) ? "SYNC" : "unSYNC"));
108
109         if (br->last == 0
110             || memcmp (bs->root->version, bs->version, 8) != 0
111             || br->block_size != bs->block_size
112             || (br->free % bs->block_size) != 0
113             || (br->last % bs->block_size) != 0
114             || retval == -1
115             || st.st_size != br->last
116             || br->free > st.st_size
117             || (br->flags & CAMEL_BLOCK_FILE_SYNC) == 0) {
118                 return -1;
119         }
120
121         return 0;
122 }
123
124 static gint
125 block_file_init_root (CamelBlockFile *bs)
126 {
127         CamelBlockRoot *br = bs->root;
128
129         memset (br, 0, bs->block_size);
130         memcpy (br->version, bs->version, 8);
131         br->last = bs->block_size;
132         br->flags = CAMEL_BLOCK_FILE_SYNC;
133         br->free = 0;
134         br->block_size = bs->block_size;
135
136         return 0;
137 }
138
139 static void
140 block_file_finalize (GObject *object)
141 {
142         CamelBlockFile *bs = CAMEL_BLOCK_FILE (object);
143         CamelBlock *bl;
144
145         if (bs->root_block)
146                 camel_block_file_sync (bs);
147
148         /* remove from lru list */
149         LOCK (block_file_lock);
150
151         if (bs->fd != -1)
152                 block_file_count--;
153
154         /* XXX This is only supposed to be in one block file list
155          *     at a time, but not sure if we can guarantee which,
156          *     so try removing from both lists. */
157         g_queue_remove (&block_file_list, bs->priv);
158         g_queue_remove (&block_file_active_list, bs->priv);
159
160         UNLOCK (block_file_lock);
161
162         while ((bl = g_queue_pop_head (&bs->block_cache)) != NULL) {
163                 if (bl->refcount != 0)
164                         g_warning ("Block '%u' still referenced", bl->id);
165                 g_free (bl);
166         }
167
168         g_hash_table_destroy (bs->blocks);
169
170         if (bs->root_block)
171                 camel_block_file_unref_block (bs, bs->root_block);
172         g_free (bs->path);
173         if (bs->fd != -1)
174                 close (bs->fd);
175
176         g_mutex_clear (&bs->priv->io_lock);
177         g_mutex_clear (&bs->priv->cache_lock);
178         g_mutex_clear (&bs->priv->root_lock);
179
180         g_free (bs->priv);
181
182         /* Chain up to parent's finalize() method. */
183         G_OBJECT_CLASS (camel_block_file_parent_class)->finalize (object);
184 }
185
186 static void
187 camel_block_file_class_init (CamelBlockFileClass *class)
188 {
189         GObjectClass *object_class;
190
191         object_class = G_OBJECT_CLASS (class);
192         object_class->finalize = block_file_finalize;
193
194         class->validate_root = block_file_validate_root;
195         class->init_root = block_file_init_root;
196 }
197
198 static guint
199 block_hash_func (gconstpointer v)
200 {
201         return ((camel_block_t) GPOINTER_TO_UINT (v)) >> CAMEL_BLOCK_SIZE_BITS;
202 }
203
204 static void
205 camel_block_file_init (CamelBlockFile *bs)
206 {
207         bs->fd = -1;
208         bs->block_size = CAMEL_BLOCK_SIZE;
209         g_queue_init (&bs->block_cache);
210         bs->blocks = g_hash_table_new ((GHashFunc) block_hash_func, NULL);
211         /* this cache size and the text index size have been tuned for about the best
212          * with moderate memory usage.  Doubling the memory usage barely affects performance. */
213         bs->block_cache_limit = 256;
214
215         bs->priv = g_malloc0 (sizeof (*bs->priv));
216         bs->priv->base = bs;
217
218         g_mutex_init (&bs->priv->root_lock);
219         g_mutex_init (&bs->priv->cache_lock);
220         g_mutex_init (&bs->priv->io_lock);
221
222         /* link into lru list */
223         LOCK (block_file_lock);
224         g_queue_push_head (&block_file_list, bs->priv);
225         UNLOCK (block_file_lock);
226 }
227
228 /* 'use' a block file for io */
229 static gint
230 block_file_use (CamelBlockFile *bs)
231 {
232         CamelBlockFile *bf;
233         GList *link;
234         gint err;
235
236         /* We want to:
237          *  remove file from active list
238          *  lock it
239          *
240          * Then when done:
241          *  unlock it
242          *  add it back to end of active list
243          */
244
245         CAMEL_BLOCK_FILE_LOCK (bs, io_lock);
246
247         if (bs->fd != -1)
248                 return 0;
249         else if (bs->priv->deleted) {
250                 CAMEL_BLOCK_FILE_UNLOCK (bs, io_lock);
251                 errno = ENOENT;
252                 return -1;
253         } else {
254                 d (printf ("Turning block file online: %s\n", bs->path));
255         }
256
257         if ((bs->fd = g_open (bs->path, bs->flags | O_BINARY, 0600)) == -1) {
258                 err = errno;
259                 CAMEL_BLOCK_FILE_UNLOCK (bs, io_lock);
260                 errno = err;
261                 return -1;
262         }
263
264         LOCK (block_file_lock);
265
266         link = g_queue_find (&block_file_list, bs->priv);
267         if (link != NULL) {
268                 g_queue_unlink (&block_file_list, link);
269                 g_queue_push_tail_link (&block_file_active_list, link);
270         }
271
272         block_file_count++;
273
274         link = g_queue_peek_head_link (&block_file_list);
275
276         while (link != NULL && block_file_count > block_file_threshhold) {
277                 struct _CamelBlockFilePrivate *nw = link->data;
278
279                 /* We never hit the current blockfile here, as its removed from the list first */
280                 bf = nw->base;
281                 if (bf->fd != -1) {
282                         /* Need to trylock, as any of these lock levels might be trying
283                          * to lock the block_file_lock, so we need to check and abort if so */
284                         if (CAMEL_BLOCK_FILE_TRYLOCK (bf, root_lock)) {
285                                 if (CAMEL_BLOCK_FILE_TRYLOCK (bf, cache_lock)) {
286                                         if (CAMEL_BLOCK_FILE_TRYLOCK (bf, io_lock)) {
287                                                 d (printf ("[%d] Turning block file offline: %s\n", block_file_count - 1, bf->path));
288                                                 sync_nolock (bf);
289                                                 close (bf->fd);
290                                                 bf->fd = -1;
291                                                 block_file_count--;
292                                                 CAMEL_BLOCK_FILE_UNLOCK (bf, io_lock);
293                                         }
294                                         CAMEL_BLOCK_FILE_UNLOCK (bf, cache_lock);
295                                 }
296                                 CAMEL_BLOCK_FILE_UNLOCK (bf, root_lock);
297                         }
298                 }
299
300                 link = g_list_next (link);
301         }
302
303         UNLOCK (block_file_lock);
304
305         return 0;
306 }
307
308 static void
309 block_file_unuse (CamelBlockFile *bs)
310 {
311         GList *link;
312
313         LOCK (block_file_lock);
314         link = g_queue_find (&block_file_active_list, bs->priv);
315         if (link != NULL) {
316                 g_queue_unlink (&block_file_active_list, link);
317                 g_queue_push_tail_link (&block_file_list, link);
318         }
319         UNLOCK (block_file_lock);
320
321         CAMEL_BLOCK_FILE_UNLOCK (bs, io_lock);
322 }
323
324 /*
325  * o = camel_cache_get (c, key);
326  * camel_cache_unref (c, key);
327  * camel_cache_add (c, key, o);
328  * camel_cache_remove (c, key);
329  */
330
331 /**
332  * camel_block_file_new:
333  * @path:
334  * @:
335  * @block_size:
336  *
337  * Allocate a new block file, stored at @path.  @version contains an 8 character
338  * version string which must match the head of the file, or the file will be
339  * intitialised.
340  *
341  * @block_size is currently ignored and is set to CAMEL_BLOCK_SIZE.
342  *
343  * Returns: The new block file, or NULL if it could not be created.
344  **/
345 CamelBlockFile *
346 camel_block_file_new (const gchar *path,
347                       gint flags,
348                       const gchar version[8],
349                       gsize block_size)
350 {
351         CamelBlockFileClass *class;
352         CamelBlockFile *bs;
353
354         bs = g_object_new (CAMEL_TYPE_BLOCK_FILE, NULL);
355         memcpy (bs->version, version, 8);
356         bs->path = g_strdup (path);
357         bs->flags = flags;
358
359         bs->root_block = camel_block_file_get_block (bs, 0);
360         if (bs->root_block == NULL) {
361                 g_object_unref (bs);
362                 return NULL;
363         }
364         camel_block_file_detach_block (bs, bs->root_block);
365         bs->root = (CamelBlockRoot *) &bs->root_block->data;
366
367         /* we only need these flags on first open */
368         bs->flags &= ~(O_CREAT | O_EXCL | O_TRUNC);
369
370         class = CAMEL_BLOCK_FILE_GET_CLASS (bs);
371
372         /* Do we need to init the root block? */
373         if (class->validate_root (bs) == -1) {
374                 d (printf ("Initialise root block: %.8s\n", version));
375
376                 class->init_root (bs);
377                 camel_block_file_touch_block (bs, bs->root_block);
378                 if (block_file_use (bs) == -1) {
379                         g_object_unref (bs);
380                         return NULL;
381                 }
382                 if (sync_block_nolock (bs, bs->root_block) == -1
383                     || ftruncate (bs->fd, bs->root->last) == -1) {
384                         block_file_unuse (bs);
385                         g_object_unref (bs);
386                         return NULL;
387                 }
388                 block_file_unuse (bs);
389         }
390
391         return bs;
392 }
393
394 gint
395 camel_block_file_rename (CamelBlockFile *bs,
396                          const gchar *path)
397 {
398         gint ret;
399         struct stat st;
400         gint err;
401
402         g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), -1);
403         g_return_val_if_fail (path != NULL, -1);
404
405         CAMEL_BLOCK_FILE_LOCK (bs, io_lock);
406
407         ret = g_rename (bs->path, path);
408         if (ret == -1) {
409                 /* Maybe the rename actually worked */
410                 err = errno;
411                 if (g_stat (path, &st) == 0
412                     && g_stat (bs->path, &st) == -1
413                     && errno == ENOENT)
414                         ret = 0;
415                 errno = err;
416         }
417
418         if (ret != -1) {
419                 g_free (bs->path);
420                 bs->path = g_strdup (path);
421         }
422
423         CAMEL_BLOCK_FILE_UNLOCK (bs, io_lock);
424
425         return ret;
426 }
427
428 gint
429 camel_block_file_delete (CamelBlockFile *bs)
430 {
431         gint ret;
432
433         g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), -1);
434
435         CAMEL_BLOCK_FILE_LOCK (bs, io_lock);
436
437         if (bs->fd != -1) {
438                 LOCK (block_file_lock);
439                 block_file_count--;
440                 UNLOCK (block_file_lock);
441                 close (bs->fd);
442                 bs->fd = -1;
443         }
444
445         bs->priv->deleted = TRUE;
446         ret = g_unlink (bs->path);
447
448         CAMEL_BLOCK_FILE_UNLOCK (bs, io_lock);
449
450         return ret;
451
452 }
453
454 /**
455  * camel_block_file_new_block:
456  * @bs:
457  *
458  * Allocate a new block, return a pointer to it.  Old blocks
459  * may be flushed to disk during this call.
460  *
461  * Returns: The block, or NULL if an error occurred.
462  **/
463 CamelBlock *
464 camel_block_file_new_block (CamelBlockFile *bs)
465 {
466         CamelBlock *bl;
467
468         g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), NULL);
469
470         CAMEL_BLOCK_FILE_LOCK (bs, root_lock);
471
472         if (bs->root->free) {
473                 bl = camel_block_file_get_block (bs, bs->root->free);
474                 if (bl == NULL)
475                         goto fail;
476                 bs->root->free = ((camel_block_t *) bl->data)[0];
477         } else {
478                 bl = camel_block_file_get_block (bs, bs->root->last);
479                 if (bl == NULL)
480                         goto fail;
481                 bs->root->last += CAMEL_BLOCK_SIZE;
482         }
483
484         bs->root_block->flags |= CAMEL_BLOCK_DIRTY;
485
486         bl->flags |= CAMEL_BLOCK_DIRTY;
487         memset (bl->data, 0, CAMEL_BLOCK_SIZE);
488 fail:
489         CAMEL_BLOCK_FILE_UNLOCK (bs, root_lock);
490
491         return bl;
492 }
493
494 /**
495  * camel_block_file_free_block:
496  * @bs:
497  * @id:
498  *
499  *
500  **/
501 gint
502 camel_block_file_free_block (CamelBlockFile *bs,
503                              camel_block_t id)
504 {
505         CamelBlock *bl;
506
507         g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), -1);
508
509         bl = camel_block_file_get_block (bs, id);
510         if (bl == NULL)
511                 return -1;
512
513         CAMEL_BLOCK_FILE_LOCK (bs, root_lock);
514
515         ((camel_block_t *) bl->data)[0] = bs->root->free;
516         bs->root->free = bl->id;
517         bs->root_block->flags |= CAMEL_BLOCK_DIRTY;
518         bl->flags |= CAMEL_BLOCK_DIRTY;
519         camel_block_file_unref_block (bs, bl);
520
521         CAMEL_BLOCK_FILE_UNLOCK (bs, root_lock);
522
523         return 0;
524 }
525
526 /**
527  * camel_block_file_get_block:
528  * @bs:
529  * @id:
530  *
531  * Retreive a block @id.
532  *
533  * Returns: The block, or NULL if blockid is invalid or a file error
534  * occurred.
535  **/
536 CamelBlock *
537 camel_block_file_get_block (CamelBlockFile *bs,
538                             camel_block_t id)
539 {
540         CamelBlock *bl;
541
542         g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), NULL);
543
544         /* Sanity check: Dont allow reading of root block (except before its been read)
545          * or blocks with invalid block id's */
546         if ((bs->root == NULL && id != 0)
547             || (bs->root != NULL && (id > bs->root->last || id == 0))
548             || (id % bs->block_size) != 0) {
549                 errno = EINVAL;
550                 return NULL;
551         }
552
553         CAMEL_BLOCK_FILE_LOCK (bs, cache_lock);
554
555         bl = g_hash_table_lookup (bs->blocks, GUINT_TO_POINTER (id));
556
557         d (printf ("Get  block %08x: %s\n", id, bl?"cached":"must read"));
558
559         if (bl == NULL) {
560                 GQueue trash = G_QUEUE_INIT;
561                 GList *link;
562
563                 /* LOCK io_lock */
564                 if (block_file_use (bs) == -1) {
565                         CAMEL_BLOCK_FILE_UNLOCK (bs, cache_lock);
566                         return NULL;
567                 }
568
569                 bl = g_malloc0 (sizeof (*bl));
570                 bl->id = id;
571                 if (lseek (bs->fd, id, SEEK_SET) == -1 ||
572                     camel_read (bs->fd, (gchar *) bl->data, CAMEL_BLOCK_SIZE, NULL, NULL) == -1) {
573                         block_file_unuse (bs);
574                         CAMEL_BLOCK_FILE_UNLOCK (bs, cache_lock);
575                         g_free (bl);
576                         return NULL;
577                 }
578
579                 bs->block_cache_count++;
580                 g_hash_table_insert (bs->blocks, GUINT_TO_POINTER (bl->id), bl);
581
582                 /* flush old blocks */
583                 link = g_queue_peek_tail_link (&bs->block_cache);
584
585                 while (link != NULL && bs->block_cache_count > bs->block_cache_limit) {
586                         CamelBlock *flush = link->data;
587
588                         if (flush->refcount == 0) {
589                                 if (sync_block_nolock (bs, flush) != -1) {
590                                         g_hash_table_remove (bs->blocks, GUINT_TO_POINTER (flush->id));
591                                         g_queue_push_tail (&trash, link);
592                                         link->data = NULL;
593                                         g_free (flush);
594                                         bs->block_cache_count--;
595                                 }
596                         }
597
598                         link = g_list_previous (link);
599                 }
600
601                 /* Remove deleted blocks from the cache. */
602                 while ((link = g_queue_pop_head (&trash)) != NULL)
603                         g_queue_delete_link (&bs->block_cache, link);
604
605                 /* UNLOCK io_lock */
606                 block_file_unuse (bs);
607         } else {
608                 g_queue_remove (&bs->block_cache, bl);
609         }
610
611         g_queue_push_head (&bs->block_cache, bl);
612         bl->refcount++;
613
614         CAMEL_BLOCK_FILE_UNLOCK (bs, cache_lock);
615
616         d (printf ("Got  block %08x\n", id));
617
618         return bl;
619 }
620
621 /**
622  * camel_block_file_detach_block:
623  * @bs:
624  * @bl:
625  *
626  * Detatch a block from the block file's cache.  The block should
627  * be unref'd or attached when finished with.  The block file will
628  * perform no writes of this block or flushing of it if the cache
629  * fills.
630  **/
631 void
632 camel_block_file_detach_block (CamelBlockFile *bs,
633                                CamelBlock *bl)
634 {
635         g_return_if_fail (CAMEL_IS_BLOCK_FILE (bs));
636         g_return_if_fail (bl != NULL);
637
638         CAMEL_BLOCK_FILE_LOCK (bs, cache_lock);
639
640         g_hash_table_remove (bs->blocks, GUINT_TO_POINTER (bl->id));
641         g_queue_remove (&bs->block_cache, bl);
642         bl->flags |= CAMEL_BLOCK_DETACHED;
643
644         CAMEL_BLOCK_FILE_UNLOCK (bs, cache_lock);
645 }
646
647 /**
648  * camel_block_file_attach_block:
649  * @bs:
650  * @bl:
651  *
652  * Reattach a block that has been detached.
653  **/
654 void
655 camel_block_file_attach_block (CamelBlockFile *bs,
656                                CamelBlock *bl)
657 {
658         g_return_if_fail (CAMEL_IS_BLOCK_FILE (bs));
659         g_return_if_fail (bl != NULL);
660
661         CAMEL_BLOCK_FILE_LOCK (bs, cache_lock);
662
663         g_hash_table_insert (bs->blocks, GUINT_TO_POINTER (bl->id), bl);
664         g_queue_push_tail (&bs->block_cache, bl);
665         bl->flags &= ~CAMEL_BLOCK_DETACHED;
666
667         CAMEL_BLOCK_FILE_UNLOCK (bs, cache_lock);
668 }
669
670 /**
671  * camel_block_file_touch_block:
672  * @bs:
673  * @bl:
674  *
675  * Mark a block as dirty.  The block will be written to disk if
676  * it ever expires from the cache.
677  **/
678 void
679 camel_block_file_touch_block (CamelBlockFile *bs,
680                               CamelBlock *bl)
681 {
682         g_return_if_fail (CAMEL_IS_BLOCK_FILE (bs));
683         g_return_if_fail (bl != NULL);
684
685         CAMEL_BLOCK_FILE_LOCK (bs, root_lock);
686         CAMEL_BLOCK_FILE_LOCK (bs, cache_lock);
687
688         bl->flags |= CAMEL_BLOCK_DIRTY;
689
690         if ((bs->root->flags & CAMEL_BLOCK_FILE_SYNC) && bl != bs->root_block) {
691                 d (printf ("turning off sync flag\n"));
692                 bs->root->flags &= ~CAMEL_BLOCK_FILE_SYNC;
693                 bs->root_block->flags |= CAMEL_BLOCK_DIRTY;
694                 camel_block_file_sync_block (bs, bs->root_block);
695         }
696
697         CAMEL_BLOCK_FILE_UNLOCK (bs, cache_lock);
698         CAMEL_BLOCK_FILE_UNLOCK (bs, root_lock);
699 }
700
701 /**
702  * camel_block_file_unref_block:
703  * @bs:
704  * @bl:
705  *
706  * Mark a block as unused.  If a block is used it will not be
707  * written to disk, or flushed from memory.
708  *
709  * If a block is detatched and this is the last reference, the
710  * block will be freed.
711  **/
712 void
713 camel_block_file_unref_block (CamelBlockFile *bs,
714                               CamelBlock *bl)
715 {
716         g_return_if_fail (CAMEL_IS_BLOCK_FILE (bs));
717         g_return_if_fail (bl != NULL);
718
719         CAMEL_BLOCK_FILE_LOCK (bs, cache_lock);
720
721         if (bl->refcount == 1 && (bl->flags & CAMEL_BLOCK_DETACHED))
722                 g_free (bl);
723         else
724                 bl->refcount--;
725
726         CAMEL_BLOCK_FILE_UNLOCK (bs, cache_lock);
727 }
728
729 static gint
730 sync_block_nolock (CamelBlockFile *bs,
731                    CamelBlock *bl)
732 {
733         d (printf ("Sync block %08x: %s\n", bl->id, (bl->flags & CAMEL_BLOCK_DIRTY)?"dirty":"clean"));
734
735         if (bl->flags & CAMEL_BLOCK_DIRTY) {
736                 if (lseek (bs->fd, bl->id, SEEK_SET) == -1
737                     || write (bs->fd, bl->data, CAMEL_BLOCK_SIZE) != CAMEL_BLOCK_SIZE) {
738                         return -1;
739                 }
740                 bl->flags &= ~CAMEL_BLOCK_DIRTY;
741         }
742
743         return 0;
744 }
745
746 static gint
747 sync_nolock (CamelBlockFile *bs)
748 {
749         GList *head, *link;
750         gint work = FALSE;
751
752         head = g_queue_peek_head_link (&bs->block_cache);
753
754         for (link = head; link != NULL; link = g_list_next (link)) {
755                 CamelBlock *bl = link->data;
756
757                 if (bl->flags & CAMEL_BLOCK_DIRTY) {
758                         work = TRUE;
759                         if (sync_block_nolock (bs, bl) == -1)
760                                 return -1;
761                 }
762         }
763
764         if (!work
765             && (bs->root_block->flags & CAMEL_BLOCK_DIRTY) == 0
766             && (bs->root->flags & CAMEL_BLOCK_FILE_SYNC) != 0)
767                 return 0;
768
769         d (printf ("turning on sync flag\n"));
770
771         bs->root->flags |= CAMEL_BLOCK_FILE_SYNC;
772         bs->root_block->flags |= CAMEL_BLOCK_DIRTY;
773
774         return sync_block_nolock (bs, bs->root_block);
775 }
776
777 /**
778  * camel_block_file_sync_block:
779  * @bs:
780  * @bl:
781  *
782  * Flush a block to disk immediately.  The block will only
783  * be flushed to disk if it is marked as dirty (touched).
784  *
785  * Returns: -1 on io error.
786  **/
787 gint
788 camel_block_file_sync_block (CamelBlockFile *bs,
789                              CamelBlock *bl)
790 {
791         gint ret;
792
793         g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), -1);
794         g_return_val_if_fail (bl != NULL, -1);
795
796         /* LOCK io_lock */
797         if (block_file_use (bs) == -1)
798                 return -1;
799
800         ret = sync_block_nolock (bs, bl);
801
802         block_file_unuse (bs);
803
804         return ret;
805 }
806
807 /**
808  * camel_block_file_sync:
809  * @bs:
810  *
811  * Sync all dirty blocks to disk, including the root block.
812  *
813  * Returns: -1 on io error.
814  **/
815 gint
816 camel_block_file_sync (CamelBlockFile *bs)
817 {
818         gint ret;
819
820         g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), -1);
821
822         CAMEL_BLOCK_FILE_LOCK (bs, root_lock);
823         CAMEL_BLOCK_FILE_LOCK (bs, cache_lock);
824
825         /* LOCK io_lock */
826         if (block_file_use (bs) == -1)
827                 ret = -1;
828         else {
829                 ret = sync_nolock (bs);
830                 block_file_unuse (bs);
831         }
832
833         CAMEL_BLOCK_FILE_UNLOCK (bs, cache_lock);
834         CAMEL_BLOCK_FILE_UNLOCK (bs, root_lock);
835
836         return ret;
837 }
838
839 /* ********************************************************************** */
840
841 struct _CamelKeyFilePrivate {
842         struct _CamelKeyFile *base;
843         GMutex lock;
844         guint deleted : 1;
845 };
846
847 #define CAMEL_KEY_FILE_LOCK(kf, lock) (g_mutex_lock(&(kf)->priv->lock))
848 #define CAMEL_KEY_FILE_TRYLOCK(kf, lock) (g_mutex_trylock(&(kf)->priv->lock))
849 #define CAMEL_KEY_FILE_UNLOCK(kf, lock) (g_mutex_unlock(&(kf)->priv->lock))
850
851 static GMutex key_file_lock;
852
853 /* lru cache of block files */
854 static GQueue key_file_list = G_QUEUE_INIT;
855 static GQueue key_file_active_list = G_QUEUE_INIT;
856 static gint key_file_count = 0;
857 static const gint key_file_threshhold = 10;
858
859 G_DEFINE_TYPE (CamelKeyFile, camel_key_file, CAMEL_TYPE_OBJECT)
860
861 static void
862 key_file_finalize (GObject *object)
863 {
864         CamelKeyFile *bs = CAMEL_KEY_FILE (object);
865
866         LOCK (key_file_lock);
867
868         /* XXX This is only supposed to be in one key file list
869          *     at a time, but not sure if we can guarantee which,
870          *     so try removing from both lists. */
871         g_queue_remove (&key_file_list, bs->priv);
872         g_queue_remove (&key_file_active_list, bs->priv);
873
874         if (bs-> fp) {
875                 key_file_count--;
876                 fclose (bs->fp);
877         }
878
879         UNLOCK (key_file_lock);
880
881         g_free (bs->path);
882
883         g_mutex_clear (&bs->priv->lock);
884
885         g_free (bs->priv);
886
887         /* Chain up to parent's finalize() method. */
888         G_OBJECT_CLASS (camel_key_file_parent_class)->finalize (object);
889 }
890
891 static void
892 camel_key_file_class_init (CamelKeyFileClass *class)
893 {
894         GObjectClass *object_class;
895
896         object_class = G_OBJECT_CLASS (class);
897         object_class->finalize = key_file_finalize;
898 }
899
900 static void
901 camel_key_file_init (CamelKeyFile *bs)
902 {
903         bs->priv = g_malloc0 (sizeof (*bs->priv));
904         bs->priv->base = bs;
905
906         g_mutex_init (&bs->priv->lock);
907
908         LOCK (key_file_lock);
909         g_queue_push_head (&key_file_list, bs->priv);
910         UNLOCK (key_file_lock);
911 }
912
913 /* 'use' a key file for io */
914 static gint
915 key_file_use (CamelKeyFile *bs)
916 {
917         CamelKeyFile *bf;
918         gint err, fd;
919         const gchar *flag;
920         GList *link;
921
922         /* We want to:
923          *  remove file from active list
924          *  lock it
925  *
926          * Then when done:
927          *  unlock it
928          *  add it back to end of active list
929         */
930
931         /* TODO: Check header on reset? */
932
933         CAMEL_KEY_FILE_LOCK (bs, lock);
934
935         if (bs->fp != NULL)
936                 return 0;
937         else if (bs->priv->deleted) {
938                 CAMEL_KEY_FILE_UNLOCK (bs, lock);
939                 errno = ENOENT;
940                 return -1;
941         } else {
942                 d (printf ("Turning key file online: '%s'\n", bs->path));
943         }
944
945         if ((bs->flags & O_ACCMODE) == O_RDONLY)
946                 flag = "rb";
947         else
948                 flag = "a+b";
949
950         if ((fd = g_open (bs->path, bs->flags | O_BINARY, 0600)) == -1
951             || (bs->fp = fdopen (fd, flag)) == NULL) {
952                 err = errno;
953                 if (fd != -1)
954                         close (fd);
955                 CAMEL_KEY_FILE_UNLOCK (bs, lock);
956                 errno = err;
957                 return -1;
958         }
959
960         LOCK (key_file_lock);
961
962         link = g_queue_find (&key_file_list, bs->priv);
963         if (link != NULL) {
964                 g_queue_unlink (&key_file_list, link);
965                 g_queue_push_tail_link (&key_file_active_list, link);
966         }
967
968         key_file_count++;
969
970         link = g_queue_peek_head_link (&key_file_list);
971         while (link != NULL && key_file_count > key_file_threshhold) {
972                 struct _CamelKeyFilePrivate *nw = link->data;
973
974                 /* We never hit the current keyfile here, as its removed from the list first */
975                 bf = nw->base;
976                 if (bf->fp != NULL) {
977                         /* Need to trylock, as any of these lock levels might be trying
978                          * to lock the key_file_lock, so we need to check and abort if so */
979                         if (CAMEL_BLOCK_FILE_TRYLOCK (bf, lock)) {
980                                 d (printf ("Turning key file offline: %s\n", bf->path));
981                                 fclose (bf->fp);
982                                 bf->fp = NULL;
983                                 key_file_count--;
984                                 CAMEL_BLOCK_FILE_UNLOCK (bf, lock);
985                         }
986                 }
987
988                 link = g_list_next (link);
989         }
990
991         UNLOCK (key_file_lock);
992
993         return 0;
994 }
995
996 static void
997 key_file_unuse (CamelKeyFile *bs)
998 {
999         GList *link;
1000
1001         LOCK (key_file_lock);
1002         link = g_queue_find (&key_file_active_list, bs->priv);
1003         if (link != NULL) {
1004                 g_queue_unlink (&key_file_active_list, link);
1005                 g_queue_push_tail_link (&key_file_list, link);
1006         }
1007         UNLOCK (key_file_lock);
1008
1009         CAMEL_KEY_FILE_UNLOCK (bs, lock);
1010 }
1011
1012 /**
1013  * camel_key_file_new:
1014  * @path:
1015  * @flags: open flags
1016  * @version: Version string (header) of file.  Currently
1017  * written but not checked.
1018  *
1019  * Create a new key file.  A linked list of record blocks.
1020  *
1021  * Returns: A new key file, or NULL if the file could not
1022  * be opened/created/initialised.
1023  **/
1024 CamelKeyFile *
1025 camel_key_file_new (const gchar *path,
1026                     gint flags,
1027                     const gchar version[8])
1028 {
1029         CamelKeyFile *kf;
1030         goffset last;
1031         gint err;
1032
1033         d (printf ("New key file '%s'\n", path));
1034
1035         kf = g_object_new (CAMEL_TYPE_KEY_FILE, NULL);
1036         kf->path = g_strdup (path);
1037         kf->fp = NULL;
1038         kf->flags = flags;
1039         kf->last = 8;
1040
1041         if (key_file_use (kf) == -1) {
1042                 g_object_unref (kf);
1043                 kf = NULL;
1044         } else {
1045                 fseek (kf->fp, 0, SEEK_END);
1046                 last = ftell (kf->fp);
1047                 if (last == 0) {
1048                         fwrite (version, 8, 1, kf->fp);
1049                         last += 8;
1050                 }
1051                 kf->last = last;
1052
1053                 err = ferror (kf->fp);
1054                 key_file_unuse (kf);
1055
1056                 /* we only need these flags on first open */
1057                 kf->flags &= ~(O_CREAT | O_EXCL | O_TRUNC);
1058
1059                 if (err) {
1060                         g_object_unref (kf);
1061                         kf = NULL;
1062                 }
1063         }
1064
1065         return kf;
1066 }
1067
1068 gint
1069 camel_key_file_rename (CamelKeyFile *kf,
1070                        const gchar *path)
1071 {
1072         gint ret;
1073         struct stat st;
1074         gint err;
1075
1076         g_return_val_if_fail (CAMEL_IS_KEY_FILE (kf), -1);
1077         g_return_val_if_fail (path != NULL, -1);
1078
1079         CAMEL_KEY_FILE_LOCK (kf, lock);
1080
1081         ret = g_rename (kf->path, path);
1082         if (ret == -1) {
1083                 /* Maybe the rename actually worked */
1084                 err = errno;
1085                 if (g_stat (path, &st) == 0
1086                     && g_stat (kf->path, &st) == -1
1087                     && errno == ENOENT)
1088                         ret = 0;
1089                 errno = err;
1090         }
1091
1092         if (ret != -1) {
1093                 g_free (kf->path);
1094                 kf->path = g_strdup (path);
1095         }
1096
1097         CAMEL_KEY_FILE_UNLOCK (kf, lock);
1098
1099         return ret;
1100 }
1101
1102 gint
1103 camel_key_file_delete (CamelKeyFile *kf)
1104 {
1105         gint ret;
1106
1107         g_return_val_if_fail (CAMEL_IS_KEY_FILE (kf), -1);
1108
1109         CAMEL_KEY_FILE_LOCK (kf, lock);
1110
1111         if (kf->fp) {
1112                 LOCK (key_file_lock);
1113                 key_file_count--;
1114                 UNLOCK (key_file_lock);
1115                 fclose (kf->fp);
1116                 kf->fp = NULL;
1117         }
1118
1119         kf->priv->deleted = TRUE;
1120         ret = g_unlink (kf->path);
1121
1122         CAMEL_KEY_FILE_UNLOCK (kf, lock);
1123
1124         return ret;
1125
1126 }
1127
1128 /**
1129  * camel_key_file_write:
1130  * @kf:
1131  * @parent:
1132  * @len:
1133  * @records:
1134  *
1135  * Write a new list of records to the key file.
1136  *
1137  * Returns: -1 on io error.  The key file will remain unchanged.
1138  **/
1139 gint
1140 camel_key_file_write (CamelKeyFile *kf,
1141                       camel_block_t *parent,
1142                       gsize len,
1143                       camel_key_t *records)
1144 {
1145         camel_block_t next;
1146         guint32 size;
1147         gint ret = -1;
1148
1149         g_return_val_if_fail (CAMEL_IS_KEY_FILE (kf), -1);
1150         g_return_val_if_fail (parent != NULL, -1);
1151         g_return_val_if_fail (records != NULL, -1);
1152
1153         d (printf ("write key %08x len = %d\n", *parent, len));
1154
1155         if (len == 0) {
1156                 d (printf (" new parent = %08x\n", *parent));
1157                 return 0;
1158         }
1159
1160         /* LOCK */
1161         if (key_file_use (kf) == -1)
1162                 return -1;
1163
1164         size = len;
1165
1166         /* FIXME: Use io util functions? */
1167         next = kf->last;
1168         fseek (kf->fp, kf->last, SEEK_SET);
1169         fwrite (parent, sizeof (*parent), 1, kf->fp);
1170         fwrite (&size, sizeof (size), 1, kf->fp);
1171         fwrite (records, sizeof (records[0]), len, kf->fp);
1172
1173         if (ferror (kf->fp)) {
1174                 clearerr (kf->fp);
1175         } else {
1176                 kf->last = ftell (kf->fp);
1177                 *parent = next;
1178                 ret = len;
1179         }
1180
1181         /* UNLOCK */
1182         key_file_unuse (kf);
1183
1184         d (printf (" new parent = %08x\n", *parent));
1185
1186         return ret;
1187 }
1188
1189 /**
1190  * camel_key_file_read:
1191  * @kf:
1192  * @start: The record pointer.  This will be set to the next record pointer on success.
1193  * @len: Number of records read, if != NULL.
1194  * @records: Records, allocated, must be freed with g_free, if != NULL.
1195  *
1196  * Read the next block of data from the key file.  Returns the number of
1197  * records.
1198  *
1199  * Returns: -1 on io error.
1200  **/
1201 gint
1202 camel_key_file_read (CamelKeyFile *kf,
1203                      camel_block_t *start,
1204                      gsize *len,
1205                      camel_key_t **records)
1206 {
1207         guint32 size;
1208         glong pos;
1209         camel_block_t next;
1210         gint ret = -1;
1211
1212         g_return_val_if_fail (CAMEL_IS_KEY_FILE (kf), -1);
1213         g_return_val_if_fail (start != NULL, -1);
1214
1215         pos = *start;
1216         if (pos == 0)
1217                 return 0;
1218
1219         /* LOCK */
1220         if (key_file_use (kf) == -1)
1221                 return -1;
1222
1223         if (fseek (kf->fp, pos, SEEK_SET) == -1
1224             || fread (&next, sizeof (next), 1, kf->fp) != 1
1225             || fread (&size, sizeof (size), 1, kf->fp) != 1
1226             || size > 1024) {
1227                 clearerr (kf->fp);
1228                 goto fail;
1229         }
1230
1231         if (len)
1232                 *len = size;
1233
1234         if (records) {
1235                 camel_key_t *keys = g_malloc (size * sizeof (camel_key_t));
1236
1237                 if (fread (keys, sizeof (camel_key_t), size, kf->fp) != size) {
1238                         g_free (keys);
1239                         goto fail;
1240                 }
1241                 *records = keys;
1242         }
1243
1244         *start = next;
1245
1246         ret = 0;
1247 fail:
1248         /* UNLOCK */
1249         key_file_unuse (kf);
1250
1251         return ret;
1252 }