Initialize the gmime for upstream
[platform/upstream/gmime.git] / gmime / gmime-iconv.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*  GMime
3  *  Copyright (C) 2000-2012 Jeffrey Stedfast
4  *
5  *  This library is free software; you can redistribute it and/or
6  *  modify it under the terms of the GNU Lesser General Public License
7  *  as published by the Free Software Foundation; either version 2.1
8  *  of the License, or (at your option) any later version.
9  *
10  *  This library 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 GNU
13  *  Lesser General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Lesser General Public
16  *  License along with this library; if not, write to the Free
17  *  Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA
18  *  02110-1301, USA.
19  */
20
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <glib.h>
27 #include <errno.h>
28 #include <string.h>
29 #include <stdio.h>
30
31 #include "gmime-charset.h"
32 #include "gmime-iconv.h"
33 #include "cache.h"
34
35 #ifdef ENABLE_WARNINGS
36 #define w(x) x
37 #else
38 #define w(x)
39 #endif /* ENABLE_WARNINGS */
40
41
42 /**
43  * SECTION: gmime-iconv
44  * @title: gmime-iconv
45  * @short_description: Low-level routines for converting text from one charset to another
46  * @see_also:
47  *
48  * These functions are wrappers around the system iconv(3)
49  * routines. The purpose of these wrappers are two-fold:
50  *
51  * 1. Cache iconv_t descriptors for you in order to optimize
52  * opening/closing many descriptors frequently
53  *
54  * and
55  *
56  * 2. To use the appropriate system charset alias for the MIME charset
57  * names given as arguments.
58  **/
59
60
61 #define ICONV_CACHE_SIZE   (16)
62
63 typedef struct {
64         CacheNode node;
65         guint32 refcount : 31;
66         guint32 used : 1;
67         iconv_t cd;
68 } IconvCacheNode;
69
70
71 static Cache *iconv_cache = NULL;
72 static GHashTable *iconv_open_hash = NULL;
73
74 #ifdef GMIME_ICONV_DEBUG
75 static int cache_misses = 0;
76 static int shutdown = 0;
77 #define d(x) x
78 #else
79 #define d(x)
80 #endif /* GMIME_ICONV_DEBUG */
81
82 #ifdef G_THREADS_ENABLED
83 static GStaticMutex iconv_cache_lock = G_STATIC_MUTEX_INIT;
84 #define ICONV_CACHE_LOCK()   g_static_mutex_lock (&iconv_cache_lock)
85 #define ICONV_CACHE_UNLOCK() g_static_mutex_unlock (&iconv_cache_lock)
86 #else
87 #define ICONV_CACHE_LOCK()
88 #define ICONV_CACHE_UNLOCK()
89 #endif /* G_THREADS_ENABLED */
90
91
92 /* caller *must* hold the iconv_cache_lock to call any of the following functions */
93
94
95 /**
96  * iconv_cache_node_new:
97  * @key: cache key
98  * @cd: iconv descriptor
99  *
100  * Creates a new cache node, inserts it into the cache and increments
101  * the cache size.
102  *
103  * Returns: a pointer to the newly allocated cache node.
104  **/
105 static IconvCacheNode *
106 iconv_cache_node_new (const char *key, iconv_t cd)
107 {
108         IconvCacheNode *node;
109         
110 #ifdef GMIME_ICONV_DEBUG
111         cache_misses++;
112 #endif
113         
114         node = (IconvCacheNode *) cache_node_insert (iconv_cache, key);
115         node->refcount = 1;
116         node->used = TRUE;
117         node->cd = cd;
118         
119         return node;
120 }
121
122
123 static void
124 iconv_cache_node_free (CacheNode *node)
125 {
126         IconvCacheNode *inode = (IconvCacheNode *) node;
127         
128 #ifdef GMIME_ICONV_DEBUG
129         if (shutdown) {
130                 fprintf (stderr, "%s: open=%d; used=%s\n", node->key,
131                          inode->refcount, inode->used ? "yes" : "no");
132         }
133 #endif
134         
135         iconv_close (inode->cd);
136 }
137
138
139 /**
140  * iconv_cache_node_expire:
141  * @node: cache node
142  *
143  * Decides whether or not a cache node should be expired.
144  **/
145 static gboolean
146 iconv_cache_node_expire (Cache *cache, CacheNode *node)
147 {
148         IconvCacheNode *inode = (IconvCacheNode *) node;
149         
150         if (inode->refcount == 0)
151                 return TRUE;
152         
153         return FALSE;
154 }
155
156
157 static void
158 iconv_open_node_free (gpointer key, gpointer value, gpointer user_data)
159 {
160         iconv_t cd = (iconv_t) key;
161         IconvCacheNode *node;
162         
163         node = (IconvCacheNode *) cache_node_lookup (iconv_cache, value, FALSE);
164         g_assert (node);
165         
166         if (cd != node->cd) {
167                 node->refcount--;
168                 iconv_close (cd);
169         }
170 }
171
172
173 /**
174  * g_mime_iconv_shutdown:
175  *
176  * Frees internal iconv caches created in g_mime_iconv_init().
177  *
178  * Note: this function is called for you by g_mime_shutdown().
179  **/
180 void
181 g_mime_iconv_shutdown (void)
182 {
183         if (!iconv_cache)
184                 return;
185         
186 #ifdef GMIME_ICONV_DEBUG
187         fprintf (stderr, "There were %d iconv cache misses\n", cache_misses);
188         fprintf (stderr, "The following %d iconv cache buckets are still open:\n", iconv_cache->size);
189         shutdown = 1;
190 #endif
191         
192         g_hash_table_foreach (iconv_open_hash, iconv_open_node_free, NULL);
193         g_hash_table_destroy (iconv_open_hash);
194         iconv_open_hash = NULL;
195         
196         cache_free (iconv_cache);
197         iconv_cache = NULL;
198 }
199
200
201 /**
202  * g_mime_iconv_init:
203  *
204  * Initialize GMime's iconv cache. This *MUST* be called before any
205  * gmime-iconv interfaces will work correctly.
206  *
207  * Note: this function is called for you by g_mime_init().
208  **/
209 void
210 g_mime_iconv_init (void)
211 {
212         if (iconv_cache)
213                 return;
214         
215         g_mime_charset_map_init ();
216         
217         iconv_open_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
218         iconv_cache = cache_new (iconv_cache_node_expire, iconv_cache_node_free,
219                                  sizeof (IconvCacheNode), ICONV_CACHE_SIZE);
220 }
221
222
223 /**
224  * g_mime_iconv_open:
225  * @to: charset to convert to
226  * @from: charset to convert from
227  *
228  * Allocates a coversion descriptor suitable for converting byte
229  * sequences from charset @from to charset @to. The resulting
230  * descriptor can be used with iconv() (or the g_mime_iconv() wrapper) any
231  * number of times until closed using g_mime_iconv_close().
232  *
233  * See the manual page for iconv_open(3) for further details.
234  *
235  * Returns: a new conversion descriptor for use with g_mime_iconv() on
236  * success or (iconv_t) %-1 on fail as well as setting an appropriate
237  * errno value.
238  **/
239 iconv_t
240 g_mime_iconv_open (const char *to, const char *from)
241 {
242         IconvCacheNode *node;
243         iconv_t cd;
244         char *key;
245         
246         if (from == NULL || to == NULL) {
247                 errno = EINVAL;
248                 return (iconv_t) -1;
249         }
250         
251         if (!g_ascii_strcasecmp (from, "x-unknown"))
252                 from = g_mime_locale_charset ();
253         
254         from = g_mime_charset_iconv_name (from);
255         to = g_mime_charset_iconv_name (to);
256         key = g_alloca (strlen (from) + strlen (to) + 2);
257         sprintf (key, "%s:%s", from, to);
258         
259         ICONV_CACHE_LOCK ();
260         
261         if ((node = (IconvCacheNode *) cache_node_lookup (iconv_cache, key, TRUE))) {
262                 if (node->used) {
263                         if ((cd = iconv_open (to, from)) == (iconv_t) -1)
264                                 goto exception;
265                 } else {
266                         /* Apparently iconv on Solaris <= 7 segfaults if you pass in
267                          * NULL for anything but inbuf; work around that. (NULL outbuf
268                          * or NULL *outbuf is allowed by Unix98.)
269                          */
270                         size_t inleft = 0, outleft = 0;
271                         char *outbuf = NULL;
272                         
273                         cd = node->cd;
274                         node->used = TRUE;
275                         
276                         /* reset the descriptor */
277                         iconv (cd, NULL, &inleft, &outbuf, &outleft);
278                 }
279                 
280                 node->refcount++;
281         } else {
282                 if ((cd = iconv_open (to, from)) == (iconv_t) -1)
283                         goto exception;
284                 
285                 node = iconv_cache_node_new (key, cd);
286         }
287         
288         g_hash_table_insert (iconv_open_hash, cd, ((CacheNode *) node)->key);
289         
290         ICONV_CACHE_UNLOCK ();
291         
292         return cd;
293         
294  exception:
295         
296         ICONV_CACHE_UNLOCK ();
297         
298 #if w(!)0
299         if (errno == EINVAL)
300                 g_warning ("Conversion from '%s' to '%s' is not supported", from, to);
301         else
302                 g_warning ("Could not open converter from '%s' to '%s': %s",
303                            from, to, strerror (errno));
304 #endif
305         
306         return cd;
307 }
308
309
310 /**
311  * g_mime_iconv_close:
312  * @cd: iconv conversion descriptor
313  *
314  * Closes the iconv descriptor @cd.
315  *
316  * See the manual page for iconv_close(3) for further details.
317  *
318  * Returns: %0 on success or %-1 on fail as well as setting an
319  * appropriate errno value.
320  **/
321 int
322 g_mime_iconv_close (iconv_t cd)
323 {
324         IconvCacheNode *node;
325         const char *key;
326         
327         if (cd == (iconv_t) -1)
328                 return 0;
329         
330         ICONV_CACHE_LOCK ();
331         
332         if ((key = g_hash_table_lookup (iconv_open_hash, cd))) {
333                 g_hash_table_remove (iconv_open_hash, cd);
334                 
335                 node = (IconvCacheNode *) cache_node_lookup (iconv_cache, key, FALSE);
336                 g_assert (node);
337                 
338                 if (iconv_cache->size > ICONV_CACHE_SIZE) {
339                         /* expire before unreffing this node so that it wont get uncached */
340                         cache_expire_unused (iconv_cache);
341                 }
342                 
343                 node->refcount--;
344                 
345                 if (cd == node->cd)
346                         node->used = FALSE;
347                 else
348                         iconv_close (cd);
349         } else {
350                 ICONV_CACHE_UNLOCK ();
351                 
352                 d(g_warning ("This iconv context wasn't opened using g_mime_iconv_open()"));
353                 
354                 return iconv_close (cd);
355         }
356         
357         ICONV_CACHE_UNLOCK ();
358         
359         return 0;
360 }