Clean up environment at exit.
[gedcom-parse.git] / gedcom / encoding.c
1 /* Conversion between encodings.
2    Copyright (C) 2001,2002 The Genes Development Team
3    This file is part of the Gedcom parser library.
4    Contributed by Peter Verthez <Peter.Verthez@advalvas.be>, 2001.
5
6    The Gedcom parser library is free software; you can redistribute it
7    and/or modify it under the terms of the GNU Lesser General Public
8    License as published by the Free Software Foundation; either
9    version 2.1 of the License, or (at your option) any later version.
10
11    The Gedcom parser library is distributed in the hope that it will be
12    useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Lesser General Public License for more details.
15
16    You should have received a copy of the GNU Lesser General Public
17    License along with the Gedcom parser library; if not, write to the
18    Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19    02111-1307 USA.  */
20
21 /* $Id$ */
22 /* $Name$ */
23
24 #include <string.h>
25 #include <iconv.h>
26 #include <stdio.h>
27 #include <limits.h>
28 #include <stdlib.h>
29 #include "gedcom_internal.h"
30 #include "gedcom.h"
31 #include "encoding.h"
32 #include "hash.h"
33
34 #define ENCODING_CONF_FILE "gedcom.enc"
35 #define GCONV_SEARCH_PATH "GCONV_PATH"
36 #define MAXBUF 255
37
38 static iconv_t cd_to_internal = (iconv_t) -1;
39 static ENCODING the_enc = ONE_BYTE;
40 static hash_t *encodings = NULL;
41
42 const char* charwidth_string[] = { "1", "2_HILO", "2_LOHI" };
43
44 hnode_t *node_alloc(void *c __attribute__((unused)))
45 {
46   return (hnode_t *)malloc(sizeof *node_alloc(NULL));
47 }
48
49 void node_free(hnode_t *n, void *c __attribute__((unused)))
50 {
51   free((void*)hnode_getkey(n));
52   free(hnode_get(n));
53   free(n);
54 }
55
56 void add_encoding(const char *gedcom_n, const char* charwidth,
57                   const char *iconv_n)
58 {
59   char *key, *val;
60
61   key = (char *) malloc(strlen(gedcom_n) + strlen(charwidth) + 3);
62   val = (char *) malloc(strlen(iconv_n) + 1);
63
64   if (key && val) {
65     /* sprintf is safe here (malloc'ed before) */
66     sprintf(key, "%s(%s)", gedcom_n, charwidth);
67     strcpy(val, iconv_n);
68     
69     if (hash_lookup(encodings, key)) {
70       gedcom_warning(_("Duplicate entry found for encoding '%s', ignoring"),
71                      gedcom_n);
72       free(key);
73       free(val);
74     }
75     else {
76       hash_alloc_insert(encodings, key, val);
77     }
78   }
79   else
80     MEMORY_ERROR;
81 }
82
83 char* get_encoding(const char* gedcom_n, ENCODING enc)
84 {
85   char *key;
86   hnode_t *node;
87   
88   key = (char*)malloc(strlen(gedcom_n) + strlen(charwidth_string[enc]) + 3);
89
90   if (key) {
91     /* sprintf is safe here (malloc'ed before) */
92     sprintf(key, "%s(%s)", gedcom_n, charwidth_string[enc]);
93     
94     node = hash_lookup(encodings, key);
95     free(key);
96     if (node) {
97       return hnode_get(node);
98     }
99     else {
100       gedcom_error(_("No encoding defined for '%s'"), gedcom_n);
101       return NULL;
102     }
103   }
104   else {
105     MEMORY_ERROR;
106     return NULL;
107   }
108 }
109
110 static char *new_gconv_path;
111
112 void cleanup_encodings()
113 {
114   hash_free(encodings);
115   /* Clean up environment */
116   putenv(GCONV_SEARCH_PATH);
117   if (new_gconv_path)
118     free(new_gconv_path);
119 }
120
121 /* Let function be called before main() */
122 void update_gconv_search_path() __attribute__ ((constructor));
123
124 /* Note:
125
126    The environment variable GCONV_PATH has to be adjusted before the very
127    first call of iconv_open.  For the most general case, it means that we
128    have to make our own constructor here (in case some of the other library
129    constructors would use iconv_open).
130
131    However, it looks like a change of an environment variable in a constructor
132    doesn't always survive until the main() function.  This is the case if
133    the environment variable is a new one, for which there was no room yet
134    in the initial environment.  The initial environment is located on the
135    stack, but when variables are added, it is moved to the heap (to be able
136    to grow).  Now, the main function takes again the one from the stack, not
137    from the heap, so changes are lost.
138
139    For this, the function below will also be called in gedcom_init(), which
140    needs to be called as early as possible in the program.
141  */
142
143 void update_gconv_search_path()
144 {
145   char *gconv_path;
146   /* Add gedcom data directory to gconv search path */
147   gconv_path = getenv(GCONV_SEARCH_PATH);
148   if (gconv_path == NULL || strstr(gconv_path, PKGDATADIR) == NULL) {
149     if (gconv_path == NULL) {
150       new_gconv_path = (char *)malloc(strlen(GCONV_SEARCH_PATH)
151                                       + strlen(PKGDATADIR)
152                                       + 2);
153       if (new_gconv_path)
154         sprintf(new_gconv_path, "%s=%s", GCONV_SEARCH_PATH, PKGDATADIR);
155     }
156     else {
157       new_gconv_path = (char *)malloc(strlen(GCONV_SEARCH_PATH)
158                                       + strlen(gconv_path)
159                                       + strlen(PKGDATADIR)
160                                       + 3);
161       if (new_gconv_path)
162         sprintf(new_gconv_path, "%s=%s:%s",
163                 GCONV_SEARCH_PATH, gconv_path, PKGDATADIR);
164     }
165     if (new_gconv_path) 
166       /* Ignore failures of putenv (can't do anything about it anyway) */
167       putenv(new_gconv_path);
168     else {
169       fprintf(stderr, "Could not allocate memory at %s, %d\n",
170               __FILE__, __LINE__);
171       abort();
172     }
173   }
174 }
175
176 void init_encodings()
177 {
178   if (encodings == NULL) {
179     FILE *in;
180     char buffer[MAXBUF + 1];
181     char gedcom_n[MAXBUF + 1];
182     char charwidth[MAXBUF + 1];
183     char iconv_n[MAXBUF + 1];
184
185     if (atexit(cleanup_encodings) != 0) {
186       gedcom_warning(_("Could not register encoding cleanup function"));
187     }
188     
189     encodings = hash_create(HASHCOUNT_T_MAX, NULL, NULL);
190     hash_set_allocator(encodings, node_alloc, node_free, NULL);
191     
192     /* Open gedcom configuration file and read */
193     in = fopen(ENCODING_CONF_FILE, "r");
194     if (in == NULL) {
195       char path[PATH_MAX];
196       sprintf(path, "%s/%s", PKGDATADIR, ENCODING_CONF_FILE);
197       in = fopen(path, "r");
198     }
199     if (in == NULL) {
200       gedcom_warning(_("Could not open encoding configuration file '%s': %s"),
201                      ENCODING_CONF_FILE, strerror(errno));
202     }
203     else {
204       line_no = 1;
205       while (fgets(buffer, sizeof(buffer), in) != NULL) {
206         if (buffer[strlen(buffer) - 1] != '\n') {
207           gedcom_error(_("Line too long in encoding configuration file '%s'"),
208                        ENCODING_CONF_FILE);
209           return;
210         }
211         else if ((buffer[0] != '#') && (strcmp(buffer, "\n") != 0)) {
212           if (sscanf(buffer, "%s %s %s", gedcom_n, charwidth, iconv_n) == 3) {
213             add_encoding(gedcom_n, charwidth, iconv_n);
214           }
215           else {
216             gedcom_error(_("Missing data in encoding configuration file '%s'"),
217                          ENCODING_CONF_FILE);
218             return;
219           }
220         }
221       }
222       if (fclose(in) != 0) {
223         gedcom_warning(_("Error closing file '%s': %s"),
224                        ENCODING_CONF_FILE, strerror(errno));
225       }
226     }
227   }
228 }
229
230 void set_encoding_width(ENCODING enc)
231 {
232   the_enc = enc;
233 }
234
235 static char conv_buf[MAXGEDCLINELEN * 2];
236 static size_t conv_buf_size;
237
238 int open_conv_to_internal(const char* fromcode)
239 {
240   const char *encoding = get_encoding(fromcode, the_enc);
241   if (cd_to_internal != (iconv_t) -1)
242     iconv_close(cd_to_internal);
243   if (encoding == NULL) {
244     cd_to_internal = (iconv_t) -1;
245   }
246   else {
247     memset(conv_buf, 0, sizeof(conv_buf));
248     conv_buf_size = 0;
249     cd_to_internal = iconv_open(INTERNAL_ENCODING, encoding);
250     if (cd_to_internal == (iconv_t) -1) {
251       gedcom_error(_("Error opening conversion context for encoding %s: %s"),
252                    encoding, strerror(errno));
253     }
254   }
255   return (cd_to_internal != (iconv_t) -1);  
256 }
257
258 void close_conv_to_internal()
259 {
260   if (iconv_close(cd_to_internal) != 0) {
261     gedcom_warning(_("Error closing conversion context: %s"), strerror(errno));
262   }
263   cd_to_internal = (iconv_t) -1;
264 }
265
266 char* to_internal(const char* str, size_t len,
267                   char* output_buffer, size_t out_len)
268 {
269   size_t res;
270   size_t outsize = out_len;
271   char *wrptr = output_buffer;
272   char *rdptr = conv_buf;
273   char *retval = output_buffer;
274   /* set up input buffer (concatenate to what was left previous time) */
275   /* can't use strcpy, because possible null bytes from unicode */
276   memcpy(conv_buf + conv_buf_size, str, len);
277   conv_buf_size += len;
278   /* set up output buffer (empty it) */
279   memset(output_buffer, 0, out_len);
280   /* do the conversion */
281   res = iconv(cd_to_internal, &rdptr, &conv_buf_size, &wrptr, &outsize);
282   if (res == (size_t)-1) {
283     if (errno == EILSEQ) {
284       /* restart from an empty state and return NULL */
285       iconv(cd_to_internal, NULL, NULL, NULL, NULL);
286       retval = NULL;
287       rdptr++;
288       conv_buf_size--;
289     }
290     else if (errno == EINVAL) {
291       /* Do nothing, leave it to next iteration */
292     }
293     else {
294       gedcom_error(_("Error in converting characters: %s"), strerror(errno));
295     }
296   }
297   /* then shift what is left over to the head of the input buffer */
298   memmove(conv_buf, rdptr, conv_buf_size);
299   memset(conv_buf + conv_buf_size, 0, sizeof(conv_buf) - conv_buf_size);
300   return retval;
301 }