1 /* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
2 /* Balsa E-Mail Client
3  *
4  * Copyright (C) 1997-2013 Stuart Parmenter and others,
5  *                         See the file AUTHORS for a list.
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2, or (at your option)
10  * any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20  * 02111-1307, USA.
21  */
22 
23 #if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
24 # include "config.h"
25 #endif                          /* HAVE_CONFIG_H */
26 #include "libbalsa-conf.h"
27 
28 #include <string.h>
29 
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <fcntl.h>
33 #include <unistd.h>
34 #include <stdio.h>
35 #include <glib/gstdio.h>
36 
37 #include "libbalsa.h"
38 #include "misc.h"
39 #include <glib/gi18n.h>
40 
41 typedef struct {
42     GKeyFile *key_file;
43     gchar *path;
44     guint changes;
45     time_t mtime;
46 } LibBalsaConf;
47 
48 static LibBalsaConf lbc_conf;
49 static LibBalsaConf lbc_conf_priv;
50 static GSList *lbc_groups;
51 
52 #define BALSA_KEY_FILE "config"
53 #define DEBUG FALSE
54 #define LBC_KEY_FILE(priv) \
55     ((priv) ? lbc_conf_priv.key_file : lbc_conf.key_file)
56 #define LBC_CHANGED(priv) \
57     ((priv) ? ++lbc_conf_priv.changes : ++lbc_conf.changes)
58 
59 static gchar *
lbc_readfile(const gchar * filename)60 lbc_readfile(const gchar * filename)
61 {
62     gchar *buf;
63     gchar **split;
64 
65     if (!g_file_get_contents(filename, &buf, NULL, NULL)) {
66 #if DEBUG
67         g_message("Failed to read \"%s\"\n", filename);
68 #endif                          /* DEBUG */
69         return NULL;
70     }
71 
72     split = g_strsplit(buf, "\\\\ ", 0);
73     g_free(buf);
74     buf = g_strjoinv("\\ ", split);
75     g_strfreev(split);
76 
77     return buf;
78 }
79 
80 static void
lbc_init(LibBalsaConf * conf,const gchar * filename,const gchar * old_dir)81 lbc_init(LibBalsaConf * conf, const gchar * filename,
82          const gchar * old_dir)
83 {
84     struct stat buf;
85     GError *error = NULL;
86     gint rc;
87 
88     if (!conf->path)
89         conf->path =
90             g_build_filename(g_get_home_dir(), ".balsa", filename, NULL);
91     rc = stat(conf->path, &buf);
92     if (conf->key_file) {
93         if (rc >= 0 && buf.st_mtime <= conf->mtime)
94             /* found the config file, and it hasn't been touched since
95              * we loaded it */
96             return;
97     } else {
98         conf->key_file = g_key_file_new();
99         if (rc < 0)
100             /* no config file--must be first time startup */
101             return;
102     }
103     conf->mtime = buf.st_mtime;
104 
105     libbalsa_assure_balsa_dir();
106     if (!g_key_file_load_from_file
107         (conf->key_file, conf->path, G_KEY_FILE_NONE, &error)) {
108         gchar *old_path;
109         gchar *buf;
110         static gboolean warn = TRUE;
111 
112         old_path =
113             g_build_filename(g_get_home_dir(), old_dir, "balsa", NULL);
114 #if DEBUG
115         g_message("Could not load config from \"%s\":\n %s\n"
116                   " trying \"%s\"", conf->path, error->message, old_path);
117 #endif                          /* DEBUG */
118         g_clear_error(&error);
119 
120         buf = lbc_readfile(old_path);
121         if (buf) {
122             /* GnomeConfig used ' ' as the list separator... */
123             g_key_file_set_list_separator(conf->key_file, ' ');
124             g_key_file_load_from_data(conf->key_file, buf, -1,
125                                       G_KEY_FILE_KEEP_COMMENTS, &error);
126             g_free(buf);
127             /* ...but GKeyFile doesn't handle it properly, so we'll
128              * revert to the default ';'. */
129             g_key_file_set_list_separator(conf->key_file, ';');
130         }
131         if (!buf || error) {
132 #if DEBUG
133             g_message("Could not load key file from file \"%s\": %s",
134                       old_path,
135                       error ? error->message : g_strerror(errno));
136 #endif                          /* DEBUG */
137             g_clear_error(&error);
138             warn = FALSE;
139         }
140         g_free(old_path);
141         if (warn)
142             libbalsa_information(LIBBALSA_INFORMATION_WARNING,
143                                  _("Your Balsa configuration "
144                                    "is now stored in "
145                                    "\"~/.balsa/config\"."));
146     }
147 }
148 
149 #ifdef BALSA_USE_THREADS
150 #if GLIB_CHECK_VERSION(2, 32, 0)
151 static GRecMutex lbc_mutex;
152 #else                           /* GLIB_CHECK_VERSION(2, 32, 0) */
153 static GStaticRecMutex lbc_mutex = G_STATIC_REC_MUTEX_INIT;
154 #endif                          /* GLIB_CHECK_VERSION(2, 32, 0) */
155 #endif                          /* BALSA_USE_THREADS */
156 
157 static void
lbc_lock(void)158 lbc_lock(void)
159 {
160     static gboolean initialized = FALSE;
161 
162 #ifdef BALSA_USE_THREADS
163 #if GLIB_CHECK_VERSION(2, 32, 0)
164     g_rec_mutex_lock(&lbc_mutex);
165 #else                           /* GLIB_CHECK_VERSION(2, 32, 0) */
166     g_static_rec_mutex_lock(&lbc_mutex);
167 #endif                          /* GLIB_CHECK_VERSION(2, 32, 0) */
168 #endif                          /* BALSA_USE_THREADS */
169     if (!initialized) {
170         lbc_init(&lbc_conf, "config", ".gnome2");
171         lbc_init(&lbc_conf_priv, "config-private", ".gnome2_private");
172         initialized = TRUE;
173     }
174 }
175 
176 static void
lbc_unlock(void)177 lbc_unlock(void)
178 {
179 #ifdef BALSA_USE_THREADS
180 #if GLIB_CHECK_VERSION(2, 32, 0)
181     g_rec_mutex_unlock(&lbc_mutex);
182 #else                           /* GLIB_CHECK_VERSION(2, 32, 0) */
183     g_static_rec_mutex_unlock(&lbc_mutex);
184 #endif                          /* GLIB_CHECK_VERSION(2, 32, 0) */
185 #endif                          /* BALSA_USE_THREADS */
186 }
187 
188 /*
189  * Call @func for each group that begins with @prefix.
190  * @func is called with arguments:
191  *   const gchar * @key		the group;
192  *   const gchar * @value	the trailing part of the group name,
193  *   				following the @prefix;
194  *   gpointer @data		the @data passed in.
195  * Iteration terminates when @func returns TRUE.
196  */
197 void
libbalsa_conf_foreach_group(const gchar * prefix,LibBalsaConfForeachFunc func,gpointer data)198 libbalsa_conf_foreach_group(const gchar * prefix,
199                             LibBalsaConfForeachFunc func, gpointer data)
200 {
201     gchar **groups, **group;
202     gsize pref_len = strlen(prefix);
203 
204     lbc_lock();
205 
206     groups = g_key_file_get_groups(lbc_conf.key_file, NULL);
207     for (group = groups; *group; group++) {
208         if (g_str_has_prefix(*group, prefix)
209             && func(*group, *group + pref_len, data))
210             break;
211     }
212     g_strfreev(groups);
213 
214     lbc_unlock();
215 }
216 
217 void
libbalsa_conf_foreach_keys(const gchar * group,LibBalsaConfForeachFunc func,gpointer data)218 libbalsa_conf_foreach_keys(const gchar * group,
219 			   LibBalsaConfForeachFunc func, gpointer data)
220 {
221     gchar **keys, **key;
222 
223     lbc_lock();
224 
225     if ((keys = g_key_file_get_keys(lbc_conf.key_file, group, NULL, NULL))) {
226 	for (key = keys; *key; key++) {
227 	    gchar * val = g_key_file_get_value(lbc_conf.key_file, group, *key, NULL);
228 	    if (func(*key, val, data)) {
229 		g_free(val);
230 		break;
231 	    }
232 	    g_free(val);
233 	}
234 	g_strfreev(keys);
235     }
236 
237     lbc_unlock();
238 }
239 
240 void
libbalsa_conf_push_group(const gchar * group)241 libbalsa_conf_push_group(const gchar * group)
242 {
243 
244     lbc_lock();                 /* Will be held until prefix is popped. */
245     lbc_groups = g_slist_prepend(lbc_groups, g_strdup(group));
246 }
247 
248 void
libbalsa_conf_pop_group(void)249 libbalsa_conf_pop_group(void)
250 {
251     g_free(lbc_groups->data);
252     lbc_groups = g_slist_delete_link(lbc_groups, lbc_groups);
253     lbc_unlock();               /* Held since prefix was pushed. */
254 }
255 
256 void
libbalsa_conf_remove_group_(const char * group,gboolean priv)257 libbalsa_conf_remove_group_(const char *group, gboolean priv)
258 {
259     lbc_lock();
260     g_key_file_remove_group(LBC_KEY_FILE(priv), group, NULL);
261     LBC_CHANGED(priv);
262     lbc_unlock();
263 }
264 
265 gboolean
libbalsa_conf_has_group(const char * group)266 libbalsa_conf_has_group(const char *group)
267 {
268     return (g_key_file_has_group(lbc_conf.key_file, group) ||
269             g_key_file_has_group(lbc_conf_priv.key_file, group));
270 }
271 
272 gboolean
libbalsa_conf_has_key(const gchar * key)273 libbalsa_conf_has_key(const gchar * key)
274 {
275     /* g_key_file_has_key returns FALSE on error, but that is OK */
276     return (g_key_file_has_key(lbc_conf.key_file, lbc_groups->data,
277                                key, NULL) ||
278             g_key_file_has_key(lbc_conf_priv.key_file, lbc_groups->data,
279                                   key, NULL));
280 }
281 
282 static void
lbc_remove_key(LibBalsaConf * conf,const char * key)283 lbc_remove_key(LibBalsaConf * conf, const char *key)
284 {
285     GError *error = NULL;
286 
287     g_key_file_remove_key(conf->key_file, lbc_groups->data, key, &error);
288     if (error)
289         g_error_free(error);
290     else
291         ++conf->changes;
292 }
293 
294 void
libbalsa_conf_clean_key(const char * key)295 libbalsa_conf_clean_key(const char *key)
296 {
297     lbc_lock();
298     lbc_remove_key(&lbc_conf, key);
299     lbc_remove_key(&lbc_conf_priv, key);
300     lbc_unlock();
301 }
302 
303 void
libbalsa_conf_set_bool_(const char * path,gboolean value,gboolean priv)304 libbalsa_conf_set_bool_(const char *path, gboolean value, gboolean priv)
305 {
306     g_key_file_set_boolean(LBC_KEY_FILE(priv), lbc_groups->data, path,
307                            value);
308     LBC_CHANGED(priv);
309 }
310 
311 static gchar *
lbc_get_key(const char * path,const char ** defval)312 lbc_get_key(const char *path, const char **defval)
313 {
314     const gchar *equals;
315     gchar *key;
316 
317     equals = strchr(path, '=');
318     key = equals ? g_strndup(path, equals - path) : g_strdup(path);
319     if (defval)
320         *defval = equals ? ++equals : NULL;
321 
322     return key;
323 }
324 
325 gboolean
libbalsa_conf_get_bool_with_default_(const char * path,gboolean * def,gboolean priv)326 libbalsa_conf_get_bool_with_default_(const char *path, gboolean * def,
327                                      gboolean priv)
328 {
329     gchar *key;
330     const gchar *defval;
331     gboolean retval;
332     GError *error = NULL;
333 
334     key = lbc_get_key(path, &defval);
335     retval =
336         g_key_file_get_boolean(LBC_KEY_FILE(priv), lbc_groups->data, key,
337                                &error);
338     g_free(key);
339     if (error) {
340         g_error_free(error);
341         if (defval)
342             retval = strcmp(defval, "true") == 0;
343     }
344 
345     if (def)
346         *def = error != NULL;
347 
348     return retval;
349 }
350 
351 gint
libbalsa_conf_get_int_with_default_(const char * path,gboolean * def,gboolean priv)352 libbalsa_conf_get_int_with_default_(const char *path,
353                                     gboolean * def, gboolean priv)
354 {
355     gchar *key;
356     const gchar *defval;
357     gint retval;
358     GError *error = NULL;
359 
360     key = lbc_get_key(path, &defval);
361     retval =
362         g_key_file_get_integer(LBC_KEY_FILE(priv), lbc_groups->data, key,
363                                &error);
364     g_free(key);
365     if (error) {
366         g_error_free(error);
367         if (defval)
368             retval = g_ascii_strtoull(defval, NULL, 10);
369     }
370 
371     if (def)
372         *def = error != NULL;
373 
374     return retval;
375 }
376 
377 gdouble
libbalsa_conf_get_double_with_default_(const char * path,gboolean * def,gboolean priv)378 libbalsa_conf_get_double_with_default_(const char *path,
379 				       gboolean * def, gboolean priv)
380 {
381     gchar *key;
382     const gchar *defval;
383     gdouble retval;
384     GError *error = NULL;
385 
386     key = lbc_get_key(path, &defval);
387     retval =
388         g_key_file_get_double(LBC_KEY_FILE(priv), lbc_groups->data, key,
389 			      &error);
390     g_free(key);
391     if (error) {
392         g_error_free(error);
393         if (defval)
394             retval = g_ascii_strtod(defval, NULL);
395     }
396 
397     if (def)
398         *def = error != NULL;
399 
400     return retval;
401 }
402 
403 void
libbalsa_conf_set_int_(const char * path,int value,gboolean priv)404 libbalsa_conf_set_int_(const char *path, int value, gboolean priv)
405 {
406     g_key_file_set_integer(LBC_KEY_FILE(priv), lbc_groups->data, path,
407                            value);
408     LBC_CHANGED(priv);
409 }
410 
411 void
libbalsa_conf_set_double_(const char * path,double value,gboolean priv)412 libbalsa_conf_set_double_(const char *path, double value, gboolean priv)
413 {
414     g_key_file_set_double(LBC_KEY_FILE(priv), lbc_groups->data, path,
415 			  value);
416     LBC_CHANGED(priv);
417 }
418 
419 void
libbalsa_conf_set_string_(const char * path,const char * value,gboolean priv)420 libbalsa_conf_set_string_(const char *path, const char *value,
421                           gboolean priv)
422 {
423     g_key_file_set_string(LBC_KEY_FILE(priv), lbc_groups->data, path,
424                           value ? value : "");
425     LBC_CHANGED(priv);
426 }
427 
428 gchar *
libbalsa_conf_get_string_with_default_(const char * path,gboolean * def,gboolean priv)429 libbalsa_conf_get_string_with_default_(const char *path, gboolean * def,
430                                        gboolean priv)
431 {
432     gchar *key;
433     const gchar *defval;
434     gchar *retval;
435     GError *error = NULL;
436 
437     key = lbc_get_key(path, &defval);
438     retval =
439         g_key_file_get_string(LBC_KEY_FILE(priv), lbc_groups->data, key,
440                               &error);
441     g_free(key);
442     if (error) {
443         g_error_free(error);
444         retval = g_strdup(defval);
445     }
446 
447     if (def)
448         *def = error != NULL;
449 
450     return retval;
451 }
452 
453 void
libbalsa_conf_set_vector(const char * path,int argc,const char * const argv[])454 libbalsa_conf_set_vector(const char *path, int argc,
455                          const char *const argv[])
456 {
457     g_key_file_set_string_list(lbc_conf.key_file, lbc_groups->data, path,
458                                argv, argc);
459     ++lbc_conf.changes;
460 }
461 
462 void
libbalsa_conf_get_vector_with_default(const char * path,gint * argcp,char *** argvp,gboolean * def)463 libbalsa_conf_get_vector_with_default(const char *path, gint * argcp,
464                                       char ***argvp, gboolean * def)
465 {
466     GError *error = NULL;
467     gsize len;
468 
469     *argvp =
470         g_key_file_get_string_list(lbc_conf.key_file, lbc_groups->data,
471                                    path, &len, &error);
472     *argcp = len;
473     if (error)
474         g_error_free(error);
475 
476     if (def)
477         *def = error != NULL;
478 }
479 
480 static void
lbc_drop_all(LibBalsaConf * conf)481 lbc_drop_all(LibBalsaConf * conf)
482 {
483     g_key_file_free(conf->key_file);
484     conf->key_file = NULL;
485     g_free(conf->path);
486     conf->path = NULL;
487     conf->changes = 0;
488 }
489 
490 void
libbalsa_conf_drop_all(void)491 libbalsa_conf_drop_all(void)
492 {
493     lbc_lock();
494     lbc_drop_all(&lbc_conf);
495     lbc_drop_all(&lbc_conf_priv);
496     lbc_unlock();
497 }
498 
499 static void
lbc_sync(LibBalsaConf * conf)500 lbc_sync(LibBalsaConf * conf)
501 {
502     gchar *buf;
503     gsize len;
504     GError *error = NULL;
505 
506     if (!conf->changes)
507         return;
508 
509     buf = g_key_file_to_data(conf->key_file, &len, &error);
510     if (error) {
511 #if DEBUG
512         g_message("Failed to sync config file \"%s\": %s\n"
513                   " changes not saved", conf->path, error->message);
514 #endif                          /* DEBUG */
515         g_error_free(error);
516         g_free(buf);
517         return;
518     }
519 
520     conf->mtime = time(NULL);
521     if (!g_file_set_contents(conf->path, buf, len, &error)) {
522         if (error) {
523 #if DEBUG
524             g_message("Failed to rewrite config file \"%s\": %s\n"
525                       " changes not saved", conf->path, error->message);
526 #endif                          /* DEBUG */
527             g_error_free(error);
528 #if DEBUG
529         } else {
530                 g_message("Failed to rewrite config file \"%s\";"
531                           " changes not saved", conf->path);
532 #endif                          /* DEBUG */
533         }
534     }
535 
536     g_free(buf);
537 }
538 
539 static guint lbc_sync_idle_id;
540 #ifdef BALSA_USE_THREADS
541 G_LOCK_DEFINE_STATIC(lbc_sync_idle_id);
542 #endif                          /* BALSA_USE_THREADS */
543 
544 void
libbalsa_conf_sync(void)545 libbalsa_conf_sync(void)
546 {
547 #ifdef BALSA_USE_THREADS
548     G_LOCK(lbc_sync_idle_id);
549 #endif                          /* BALSA_USE_THREADS */
550 #if DEBUG
551     g_print("%s id %d, will be cleared\n", __func__, lbc_sync_idle_id);
552 #endif                          /* DEBUG */
553     if (lbc_sync_idle_id) {
554         g_source_remove(lbc_sync_idle_id);
555         lbc_sync_idle_id = 0;
556     }
557 #ifdef BALSA_USE_THREADS
558     G_UNLOCK(lbc_sync_idle_id);
559 #endif                          /* BALSA_USE_THREADS */
560     lbc_lock();
561     lbc_sync(&lbc_conf);
562     lbc_sync(&lbc_conf_priv);
563     lbc_unlock();
564 }
565 
566 static gboolean
libbalsa_conf_sync_idle_cb(gpointer data)567 libbalsa_conf_sync_idle_cb(gpointer data)
568 {
569     libbalsa_conf_sync();
570 
571     return FALSE;
572 }
573 
574 void
libbalsa_conf_queue_sync(void)575 libbalsa_conf_queue_sync(void)
576 {
577 #ifdef BALSA_USE_THREADS
578     G_LOCK(lbc_sync_idle_id);
579 #endif                          /* BALSA_USE_THREADS */
580 #if DEBUG
581     g_print("%s id %d, will be set if zero\n", __func__, lbc_sync_idle_id);
582 #endif                          /* DEBUG */
583     if (!lbc_sync_idle_id)
584         lbc_sync_idle_id =
585             g_idle_add((GSourceFunc) libbalsa_conf_sync_idle_cb, NULL);
586 #if DEBUG
587     g_print("%s id now %d\n", __func__, lbc_sync_idle_id);
588 #endif                          /* DEBUG */
589 #ifdef BALSA_USE_THREADS
590     G_UNLOCK(lbc_sync_idle_id);
591 #endif                          /* BALSA_USE_THREADS */
592 }
593