1 /*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2009 Hiroyuki Yamamoto
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
7 * License as published by the Free Software Foundation; either
8 * version 2.1 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 Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23
24 #include <glib.h>
25 #include <glib/gi18n.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 #include <errno.h>
31
32 #ifdef G_OS_WIN32
33 # include <windows.h>
34 # include <io.h>
35 #endif
36
37 #include "prefs.h"
38 #include "codeconv.h"
39 #include "utils.h"
40
41 typedef enum
42 {
43 DUMMY_PARAM
44 } DummyEnum;
45
46 typedef struct _PrefFilePrivate PrefFilePrivate;
47
48 struct _PrefFilePrivate {
49 FILE *fp;
50 gchar *path;
51 gint backup_generation;
52 };
53
54 static void prefs_config_parse_one_line (GHashTable *param_table,
55 const gchar *buf);
56
prefs_param_table_get(PrefParam * param)57 GHashTable *prefs_param_table_get(PrefParam *param)
58 {
59 GHashTable *table;
60 gint i;
61
62 g_return_val_if_fail(param != NULL, NULL);
63
64 table = g_hash_table_new(g_str_hash, g_str_equal);
65
66 for (i = 0; param[i].name != NULL; i++) {
67 g_hash_table_insert(table, param[i].name, ¶m[i]);
68 }
69
70 return table;
71 }
72
prefs_param_table_destroy(GHashTable * param_table)73 void prefs_param_table_destroy(GHashTable *param_table)
74 {
75 g_hash_table_destroy(param_table);
76 }
77
prefs_read_config(PrefParam * param,const gchar * label,const gchar * rcfile,const gchar * encoding)78 void prefs_read_config(PrefParam *param, const gchar *label,
79 const gchar *rcfile, const gchar *encoding)
80 {
81 FILE *fp;
82 gchar buf[PREFSBUFSIZE];
83 gchar *block_label;
84 GHashTable *param_table;
85
86 g_return_if_fail(param != NULL);
87 g_return_if_fail(label != NULL);
88 g_return_if_fail(rcfile != NULL);
89
90 debug_print("Reading configuration...\n");
91
92 prefs_set_default(param);
93
94 if ((fp = g_fopen(rcfile, "rb")) == NULL) {
95 if (ENOENT != errno) FILE_OP_ERROR(rcfile, "fopen");
96 return;
97 }
98
99 block_label = g_strdup_printf("[%s]", label);
100
101 /* search aiming block */
102 while (fgets(buf, sizeof(buf), fp) != NULL) {
103 gint val;
104
105 if (encoding) {
106 gchar *conv_str;
107
108 conv_str = conv_codeset_strdup
109 (buf, encoding, CS_INTERNAL);
110 if (!conv_str)
111 conv_str = g_strdup(buf);
112 val = strncmp
113 (conv_str, block_label, strlen(block_label));
114 g_free(conv_str);
115 } else
116 val = strncmp(buf, block_label, strlen(block_label));
117 if (val == 0) {
118 debug_print("Found %s\n", block_label);
119 break;
120 }
121 }
122 g_free(block_label);
123
124 param_table = prefs_param_table_get(param);
125
126 while (fgets(buf, sizeof(buf), fp) != NULL) {
127 strretchomp(buf);
128 if (buf[0] == '\0') continue;
129 /* reached next block */
130 if (buf[0] == '[') break;
131
132 if (encoding) {
133 gchar *conv_str;
134
135 conv_str = conv_codeset_strdup
136 (buf, encoding, CS_INTERNAL);
137 if (!conv_str)
138 conv_str = g_strdup(buf);
139 prefs_config_parse_one_line(param_table, conv_str);
140 g_free(conv_str);
141 } else
142 prefs_config_parse_one_line(param_table, buf);
143 }
144
145 prefs_param_table_destroy(param_table);
146
147 debug_print("Finished reading configuration.\n");
148 fclose(fp);
149 }
150
prefs_config_parse_one_line(GHashTable * param_table,const gchar * buf)151 static void prefs_config_parse_one_line(GHashTable *param_table,
152 const gchar *buf)
153 {
154 PrefParam *param;
155 const gchar *p = buf;
156 gchar *name;
157 const gchar *value;
158
159 while (*p && *p != '=')
160 p++;
161
162 if (*p != '=') {
163 g_warning("invalid pref line: %s\n", buf);
164 return;
165 }
166
167 name = g_strndup(buf, p - buf);
168 value = p + 1;
169
170 /* debug_print("%s = %s\n", name, value); */
171
172 param = g_hash_table_lookup(param_table, name);
173
174 if (!param) {
175 debug_print("pref key '%s' (value '%s') not found\n",
176 name, value);
177 g_free(name);
178 return;
179 }
180
181 switch (param->type) {
182 case P_STRING:
183 g_free(*((gchar **)param->data));
184 *((gchar **)param->data) = *value ? g_strdup(value) : NULL;
185 break;
186 case P_INT:
187 *((gint *)param->data) = (gint)atoi(value);
188 break;
189 case P_BOOL:
190 *((gboolean *)param->data) =
191 (*value == '0' || *value == '\0') ? FALSE : TRUE;
192 break;
193 case P_ENUM:
194 *((DummyEnum *)param->data) = (DummyEnum)atoi(value);
195 break;
196 case P_USHORT:
197 *((gushort *)param->data) = (gushort)atoi(value);
198 break;
199 default:
200 break;
201 }
202
203 g_free(name);
204 }
205
206 #define TRY(func) \
207 if (!(func)) \
208 { \
209 g_warning(_("failed to write configuration to file\n")); \
210 if (orig_fp) fclose(orig_fp); \
211 prefs_file_close_revert(pfile); \
212 g_free(rcpath); \
213 g_free(block_label); \
214 return; \
215 } \
216
prefs_write_config(PrefParam * param,const gchar * label,const gchar * rcfile)217 void prefs_write_config(PrefParam *param, const gchar *label,
218 const gchar *rcfile)
219 {
220 FILE *orig_fp;
221 PrefFile *pfile;
222 gchar *rcpath;
223 gchar buf[PREFSBUFSIZE];
224 gchar *block_label = NULL;
225 gboolean block_matched = FALSE;
226
227 g_return_if_fail(param != NULL);
228 g_return_if_fail(label != NULL);
229 g_return_if_fail(rcfile != NULL);
230
231 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, rcfile, NULL);
232 if ((orig_fp = g_fopen(rcpath, "rb")) == NULL) {
233 if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen");
234 }
235
236 if ((pfile = prefs_file_open(rcpath)) == NULL) {
237 g_warning(_("failed to write configuration to file\n"));
238 if (orig_fp) fclose(orig_fp);
239 g_free(rcpath);
240 return;
241 }
242
243 block_label = g_strdup_printf("[%s]", label);
244
245 /* search aiming block */
246 if (orig_fp) {
247 while (fgets(buf, sizeof(buf), orig_fp) != NULL) {
248 gint val;
249
250 val = strncmp(buf, block_label, strlen(block_label));
251 if (val == 0) {
252 debug_print(_("Found %s\n"), block_label);
253 block_matched = TRUE;
254 break;
255 } else
256 TRY(fputs(buf, pfile->fp) != EOF);
257 }
258 }
259
260 TRY(fprintf(pfile->fp, "%s\n", block_label) > 0);
261 g_free(block_label);
262 block_label = NULL;
263
264 /* write all param data to file */
265 TRY(prefs_file_write_param(pfile, param) == 0);
266
267 if (block_matched) {
268 while (fgets(buf, sizeof(buf), orig_fp) != NULL) {
269 /* next block */
270 if (buf[0] == '[') {
271 TRY(fputc('\n', pfile->fp) != EOF &&
272 fputs(buf, pfile->fp) != EOF);
273 break;
274 }
275 }
276 while (fgets(buf, sizeof(buf), orig_fp) != NULL)
277 TRY(fputs(buf, pfile->fp) != EOF);
278 }
279
280 if (orig_fp) fclose(orig_fp);
281 if (prefs_file_close(pfile) < 0)
282 g_warning(_("failed to write configuration to file\n"));
283 g_free(rcpath);
284
285 debug_print(_("Configuration is saved.\n"));
286 }
287
prefs_file_write_param(PrefFile * pfile,PrefParam * param)288 gint prefs_file_write_param(PrefFile *pfile, PrefParam *param)
289 {
290 gint i;
291 gchar buf[PREFSBUFSIZE];
292
293 for (i = 0; param[i].name != NULL; i++) {
294 switch (param[i].type) {
295 case P_STRING:
296 g_snprintf(buf, sizeof(buf), "%s=%s\n", param[i].name,
297 *((gchar **)param[i].data) ?
298 *((gchar **)param[i].data) : "");
299 break;
300 case P_INT:
301 g_snprintf(buf, sizeof(buf), "%s=%d\n", param[i].name,
302 *((gint *)param[i].data));
303 break;
304 case P_BOOL:
305 g_snprintf(buf, sizeof(buf), "%s=%d\n", param[i].name,
306 *((gboolean *)param[i].data));
307 break;
308 case P_ENUM:
309 g_snprintf(buf, sizeof(buf), "%s=%d\n", param[i].name,
310 *((DummyEnum *)param[i].data));
311 break;
312 case P_USHORT:
313 g_snprintf(buf, sizeof(buf), "%s=%d\n", param[i].name,
314 *((gushort *)param[i].data));
315 break;
316 default:
317 buf[0] = '\0';
318 }
319
320 if (buf[0] != '\0') {
321 if (fputs(buf, pfile->fp) == EOF) {
322 perror("fputs");
323 return -1;
324 }
325 }
326 }
327
328 return 0;
329 }
330
prefs_file_open(const gchar * path)331 PrefFile *prefs_file_open(const gchar *path)
332 {
333 PrefFilePrivate *pfile;
334 gchar *tmppath;
335 FILE *fp;
336
337 g_return_val_if_fail(path != NULL, NULL);
338
339 tmppath = g_strconcat(path, ".tmp", NULL);
340 if ((fp = g_fopen(tmppath, "wb")) == NULL) {
341 FILE_OP_ERROR(tmppath, "fopen");
342 g_free(tmppath);
343 return NULL;
344 }
345
346 if (change_file_mode_rw(fp, tmppath) < 0)
347 FILE_OP_ERROR(tmppath, "chmod");
348
349 g_free(tmppath);
350
351 pfile = g_new(PrefFilePrivate, 1);
352 pfile->fp = fp;
353 pfile->path = g_strdup(path);
354 pfile->backup_generation = 4;
355
356 return (PrefFile *)pfile;
357 }
358
prefs_file_set_backup_generation(PrefFile * pfile,gint generation)359 void prefs_file_set_backup_generation(PrefFile *pfile, gint generation)
360 {
361 PrefFilePrivate *priv = (PrefFilePrivate *)pfile;
362
363 g_return_if_fail(pfile != NULL);
364
365 priv->backup_generation = generation;
366 }
367
prefs_file_get_backup_generation(PrefFile * pfile)368 gint prefs_file_get_backup_generation(PrefFile *pfile)
369 {
370 PrefFilePrivate *priv = (PrefFilePrivate *)pfile;
371
372 g_return_val_if_fail(pfile != NULL, -1);
373
374 return priv->backup_generation;
375 }
376
prefs_file_close(PrefFile * pfile)377 gint prefs_file_close(PrefFile *pfile)
378 {
379 PrefFilePrivate *priv = (PrefFilePrivate *)pfile;
380 FILE *fp;
381 gchar *path;
382 gchar *tmppath;
383 gchar *bakpath = NULL;
384 gint generation;
385 gint ret = 0;
386
387 g_return_val_if_fail(pfile != NULL, -1);
388
389 fp = pfile->fp;
390 path = pfile->path;
391 generation = priv->backup_generation;
392 g_free(pfile);
393
394 tmppath = g_strconcat(path, ".tmp", NULL);
395 if (fflush(fp) == EOF) {
396 FILE_OP_ERROR(tmppath, "fflush");
397 fclose(fp);
398 ret = -1;
399 goto finish;
400 }
401 #if HAVE_FSYNC
402 if (fsync(fileno(fp)) < 0) {
403 FILE_OP_ERROR(tmppath, "fsync");
404 fclose(fp);
405 ret = -1;
406 goto finish;
407 }
408 #elif defined(G_OS_WIN32)
409 if (_commit(_fileno(fp)) < 0) {
410 FILE_OP_ERROR(tmppath, "_commit");
411 fclose(fp);
412 ret = -1;
413 goto finish;
414 }
415 #endif
416 if (fclose(fp) == EOF) {
417 FILE_OP_ERROR(tmppath, "fclose");
418 ret = -1;
419 goto finish;
420 }
421
422 if (is_file_exist(path)) {
423 bakpath = g_strconcat(path, ".bak", NULL);
424 if (is_file_exist(bakpath)) {
425 gint i;
426 gchar *bakpath_n, *bakpath_p;
427
428 for (i = generation; i > 0; i--) {
429 bakpath_n = g_strdup_printf("%s.%d", bakpath,
430 i);
431 if (i == 1)
432 bakpath_p = g_strdup(bakpath);
433 else
434 bakpath_p = g_strdup_printf
435 ("%s.%d", bakpath, i - 1);
436 if (is_file_exist(bakpath_p)) {
437 if (rename_force(bakpath_p, bakpath_n) < 0) {
438 FILE_OP_ERROR(bakpath_p,
439 "rename");
440 }
441 }
442 g_free(bakpath_p);
443 g_free(bakpath_n);
444 }
445 }
446 if (rename_force(path, bakpath) < 0) {
447 FILE_OP_ERROR(path, "rename");
448 ret = -1;
449 goto finish;
450 }
451 }
452
453 if (rename_force(tmppath, path) < 0) {
454 FILE_OP_ERROR(tmppath, "rename");
455 ret = -1;
456 goto finish;
457 }
458
459 finish:
460 if (ret < 0)
461 g_unlink(tmppath);
462 g_free(path);
463 g_free(tmppath);
464 g_free(bakpath);
465 return ret;
466 }
467
prefs_file_close_revert(PrefFile * pfile)468 gint prefs_file_close_revert(PrefFile *pfile)
469 {
470 gchar *tmppath;
471
472 g_return_val_if_fail(pfile != NULL, -1);
473
474 tmppath = g_strconcat(pfile->path, ".tmp", NULL);
475 fclose(pfile->fp);
476 if (g_unlink(tmppath) < 0)
477 FILE_OP_ERROR(tmppath, "unlink");
478 g_free(tmppath);
479 g_free(pfile->path);
480 g_free(pfile);
481
482 return 0;
483 }
484
prefs_set_default(PrefParam * param)485 void prefs_set_default(PrefParam *param)
486 {
487 gint i;
488
489 g_return_if_fail(param != NULL);
490
491 for (i = 0; param[i].name != NULL; i++) {
492 if (!param[i].data) continue;
493
494 switch (param[i].type) {
495 case P_STRING:
496 if (param[i].defval != NULL) {
497 if (!g_ascii_strncasecmp(param[i].defval, "ENV_", 4)) {
498 const gchar *envstr;
499 gchar *tmp = NULL;
500
501 envstr = g_getenv(param[i].defval + 4);
502 #ifdef G_OS_WIN32
503 tmp = g_strdup(envstr);
504 #else
505 if (envstr) {
506 tmp = conv_codeset_strdup
507 (envstr,
508 conv_get_locale_charset_str(),
509 CS_UTF_8);
510 if (!tmp) {
511 g_warning("failed to convert character set.");
512 tmp = g_strdup(envstr);
513 }
514 }
515 #endif
516 *((gchar **)param[i].data) = tmp;
517 } else if (param[i].defval[0] == '~')
518 *((gchar **)param[i].data) =
519 #ifdef G_OS_WIN32
520 g_strconcat(get_rc_dir(),
521 #else
522 g_strconcat(get_home_dir(),
523 #endif
524 param[i].defval + 1,
525 NULL);
526 else if (param[i].defval[0] != '\0')
527 *((gchar **)param[i].data) =
528 g_strdup(param[i].defval);
529 else
530 *((gchar **)param[i].data) = NULL;
531 } else
532 *((gchar **)param[i].data) = NULL;
533 break;
534 case P_INT:
535 if (param[i].defval != NULL)
536 *((gint *)param[i].data) =
537 (gint)atoi(param[i].defval);
538 else
539 *((gint *)param[i].data) = 0;
540 break;
541 case P_BOOL:
542 if (param[i].defval != NULL) {
543 if (!g_ascii_strcasecmp(param[i].defval, "TRUE"))
544 *((gboolean *)param[i].data) = TRUE;
545 else
546 *((gboolean *)param[i].data) =
547 atoi(param[i].defval) ? TRUE : FALSE;
548 } else
549 *((gboolean *)param[i].data) = FALSE;
550 break;
551 case P_ENUM:
552 if (param[i].defval != NULL)
553 *((DummyEnum*)param[i].data) =
554 (DummyEnum)atoi(param[i].defval);
555 else
556 *((DummyEnum *)param[i].data) = 0;
557 break;
558 case P_USHORT:
559 if (param[i].defval != NULL)
560 *((gushort *)param[i].data) =
561 (gushort)atoi(param[i].defval);
562 else
563 *((gushort *)param[i].data) = 0;
564 break;
565 default:
566 break;
567 }
568 }
569 }
570
prefs_free(PrefParam * param)571 void prefs_free(PrefParam *param)
572 {
573 gint i;
574
575 g_return_if_fail(param != NULL);
576
577 for (i = 0; param[i].name != NULL; i++) {
578 if (!param[i].data) continue;
579
580 switch (param[i].type) {
581 case P_STRING:
582 g_free(*((gchar **)param[i].data));
583 break;
584 default:
585 break;
586 }
587 }
588 }
589