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, &param[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