1 /* XQF - Quake server browser and launcher
2  * Copyright (C) 1998-2000 Roman Pozlevich <roma@botik.ru>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
17  */
18 
19 /*
20  *  Simple 'ini'-file management.
21  *  Function calls should be compatible with GNOME ones.
22  */
23 
24 #include <sys/types.h>
25 #include <stdio.h>  /* FILE */
26 #include <string.h> /* strchr, strcmp, strlen */
27 #include <stdlib.h> /* strtol, strtod */
28 #include <unistd.h> /* unlink */
29 
30 #include <glib.h>
31 
32 #include "utils.h"
33 #include "debug.h"
34 #include "config.h"
35 
36 
37 static GSList* directories = NULL;
38 static GList *files = NULL;
39 static GList *prefix_stack = NULL;
40 
41 struct config_file {
42 	char *filename;
43 	GList *sections;
44 	unsigned dirty : 1;
45 	unsigned read_only : 1;
46 };
47 
48 struct config_section {
49 	char *name;
50 	GList *keys;
51 };
52 
53 struct config_key {
54 	char *name;
55 	char *value;
56 };
57 
58 /* never used
59 #ifdef DEBUG
60 static void dump_base (void) {
61 	GList *fptr, *sptr, *kptr;
62 	struct config_file *file;
63 	struct config_section *section;
64 	struct config_key *key;
65 
66 	for (fptr = files; fptr; fptr = fptr->next) {
67 		file = (struct config_file *) fptr->data;
68 		fprintf (stderr, "---- %s\n", file->filename);
69 
70 		for (sptr = file->sections; sptr; sptr = sptr->next) {
71 			section = (struct config_section *) sptr->data;
72 			fprintf (stderr, "[%s]\n", section->name);
73 
74 			for (kptr = section->keys; kptr; kptr = kptr->next) {
75 				key = (struct config_key *) kptr->data;
76 				fprintf (stderr, "%s=%s\n", key->name, key->value);
77 			}
78 		}
79 	}
80 	fprintf (stderr, "----\n");
81 }
82 #endif
83 */
84 
85 /**
86  * lookup keyname in secname in filename (secname and keyname can be NULL).
87  * When create is set, appropriate entries will be created if they don't
88  * already exist. When file1, section1 and key1 are not NULL (all three are
89  * independent), pointers to the found/created structs are stored there.
90  **/
find_key(const char * filename,const char * secname,const char * keyname,int create,struct config_file ** file1,struct config_section ** section1,struct config_key ** key1)91 static void find_key (const char *filename,
92 		const char *secname,
93 		const char *keyname,
94 		int create,
95 		struct config_file **file1,
96 		struct config_section **section1,
97 		struct config_key **key1) {
98 
99 	struct config_file *file = NULL;
100 	struct config_section *section = NULL;
101 	struct config_key *key = NULL;
102 	GList *list;
103 
104 	if (file1) *file1 = NULL;
105 	if (section1) *section1 = NULL;
106 	if (key1) *key1 = NULL;
107 
108 	// look if file is already known
109 	for (list = files; list; list = list->next) {
110 		file = (struct config_file *) list->data;
111 		if (strcmp (filename, file->filename) == 0)
112 			break;
113 	}
114 
115 	// otherwise create one
116 	if (!list) {
117 
118 		if (!create)
119 			return;
120 
121 		file = g_malloc0 (sizeof (struct config_file));
122 		file->filename = g_strdup (filename);
123 		files = g_list_append (files, file);
124 	}
125 
126 	if (file1)
127 		*file1 = file;
128 
129 	if (secname) {
130 		// see if section is alreay known in file
131 		for (list = file->sections; list; list = list->next) {
132 			section = (struct config_section *) list->data;
133 			if (g_ascii_strcasecmp (secname, section->name) == 0)
134 				break;
135 		}
136 
137 		// otherwise create one
138 		if (!list) {
139 
140 			if (!create)
141 				return;
142 
143 			section = g_malloc0 (sizeof (struct config_section));
144 			section->name = g_strdup (secname);
145 			file->sections = g_list_append (file->sections, section);
146 		}
147 
148 		if (section1)
149 			*section1 = section;
150 	}
151 
152 	if (secname && keyname) {
153 		// see if key is already in section
154 		for (list = section->keys; list; list = list->next) {
155 			key = (struct config_key *) list->data;
156 			if (g_ascii_strcasecmp (keyname, key->name) == 0)
157 				break;
158 		}
159 
160 		// otherwise create one
161 		if (!list) {
162 
163 			if (!create)
164 				return;
165 
166 			key = g_malloc0 (sizeof (struct config_key));
167 			key->name = g_strdup (keyname);
168 			section->keys = g_list_append (section->keys, key);
169 		}
170 
171 		if (key1)
172 			*key1 = key;
173 	}
174 }
175 
176 /**
177  * lookup keyname in section, if not found create a new config_key.
178  * returns pointer to found or created key
179  */
key_in_section(struct config_section * section,char * keyname)180 static struct config_key *key_in_section (struct config_section *section,
181 		char *keyname) {
182 	struct config_key *key;
183 	GList *list;
184 
185 	for (list = section->keys; list; list = list->next) {
186 		key = (struct config_key *) list->data;
187 		if (g_ascii_strcasecmp (keyname, key->name) == 0)
188 			break;
189 	}
190 
191 	if (!list) {
192 		key = g_malloc0 (sizeof (struct config_key));
193 		key->name = g_strdup (keyname);
194 		section->keys = g_list_append (section->keys, key);
195 	}
196 
197 	return key;
198 }
199 
200 
201 #define BUF_SIZE	(1024*8)
202 
203 struct state_s
204 {
205 	const char* filename;
206 	char *secname;
207 	struct config_section *section;
208 	void* data;
209 };
210 
parse_line_file(char * buf,struct state_s * state)211 static gboolean parse_line_file(char* buf, struct state_s* state) {
212 	struct config_key *key = NULL;
213 	char *p;
214 	unsigned len;
215 
216 	len = strlen(buf);
217 
218 	if (len <= 1)
219 		return TRUE;
220 
221 	if (buf[len - 1] == '\n') {
222 		buf[len - 1] = '\0';
223 		len--;
224 	}
225 
226 	if (buf[len - 1] == '\r') {
227 		buf[len - 1] = '\0';
228 		len--;
229 	}
230 
231 	if (*buf == '[') {
232 		p = strchr (buf + 1, ']');
233 		if (p) {
234 			*p = '\0';
235 
236 			if (state->secname)
237 				g_free (state->secname);
238 
239 			state->secname = g_strdup (buf + 1);
240 			state->section = NULL;
241 		}
242 	}
243 	else {
244 		p = strchr (buf, '=');
245 		if (p) {
246 			*p++ = '\0';
247 
248 			if (!state->section && !state->secname)
249 				return TRUE;
250 
251 			if (!state->section)
252 				find_key (state->filename, state->secname, buf, TRUE, NULL, &state->section, &key);
253 			else
254 				key = key_in_section (state->section, buf);
255 
256 			if (key->value)
257 				g_free (key->value);
258 
259 			key->value = g_strdup (p);
260 		}
261 	}
262 
263 	return TRUE;
264 }
265 
266 #define BEGIN_XQF_INFO  "### BEGIN XQF INFO"
267 #define END_XQF_INFO    "### END XQF INFO"
parse_line_script(char * buf,struct state_s * state)268 static gboolean parse_line_script(char* buf, struct state_s* state) {
269 	switch(GPOINTER_TO_INT(state->data)) {
270 		case 0:
271 			if (!strncmp(buf, BEGIN_XQF_INFO, strlen(BEGIN_XQF_INFO)))
272 				state->data = GINT_TO_POINTER(1);
273 			return TRUE;
274 
275 		case 1:
276 			if (!strncmp(buf, "# ", 2))
277 				return parse_line_file(buf+2, state);
278 			else if (!strncmp(buf, END_XQF_INFO, strlen(END_XQF_INFO)))
279 				return FALSE;
280 	}
281 	return FALSE;
282 }
283 #undef BEGIN_XQF_INFO
284 #undef END_XQF_INFO
285 
load_file(const char * filename)286 static void load_file (const char *filename) {
287 	FILE *f = NULL;
288 	char buf[BUF_SIZE];
289 	char *fn;
290 	struct state_s state = {0};
291 	GSList* l;
292 	gboolean (*parse_line)(char* buf, struct state_s* state);
293 	struct config_file* file = NULL;
294 
295 	debug (4, "%s", filename);
296 
297 	for (l = directories; l && !f; l = g_slist_next(l)) {
298 		fn = file_in_dir (l->data, filename);
299 		f = fopen (fn, "r");
300 		if (f) debug(4, "loaded %s", fn);
301 		g_free (fn);
302 	}
303 
304 	/* Create empty file record and thus cache lookups of non-existent files.
305 	 * Don't mark it 'dirty' -- nothing was changed really.
306 	 */
307 
308 	find_key (filename, NULL, NULL, TRUE, &file, NULL, NULL);
309 
310 	if (!f)
311 		return;
312 
313 	state.filename = filename;
314 
315 	// XXX:
316 	if (!strncmp(filename, "scripts/", strlen("scripts/"))) {
317 		parse_line = parse_line_script;
318 		file->read_only = 1;
319 	}
320 	else
321 		parse_line = parse_line_file;
322 
323 	while (fgets (buf, BUF_SIZE, f))
324 		parse_line(buf, &state);
325 
326 	g_free(state.secname);
327 	fclose (f);
328 }
329 
330 
unlink_file(const char * filename)331 static void unlink_file (const char *filename) {
332 	char *fn;
333 
334 	fn = file_in_dir (directories->data, filename);
335 #ifdef DEBUG
336 	fprintf (stderr, "config.c: unlink_file (%s)\n", fn);
337 #endif
338 	unlink (fn);
339 	g_free (fn);
340 }
341 
342 
path_concat(const char * str1,const char * str2)343 static char *path_concat (const char *str1, const char *str2) {
344 	if (str1[strlen (str1) - 1] != '/')
345 		return g_strconcat (str1, "/", str2, NULL);
346 	else
347 		return g_strconcat (str1, str2, NULL);
348 }
349 
350 
parse_path(const char * path,int create,char ** defval,struct config_file ** file1,struct config_section ** section1)351 static struct config_key *parse_path (const char *path,
352 		int create,
353 		char **defval,
354 		struct config_file **file1,
355 		struct config_section **section1) {
356 	const char *filename;
357 	char *secname = NULL;
358 	char *keyname = NULL;
359 	char *def;
360 	char *buf = NULL;
361 	char *ptr = NULL;
362 	struct config_file *file;
363 	struct config_key *key = NULL;
364 
365 	if (defval) {
366 		*defval = NULL;
367 		def = strchr (path, '=');
368 		if (def)
369 			*defval = def + 1;
370 	}
371 
372 	if (*path != '/') {
373 		if (prefix_stack)
374 			buf = path_concat ((const char *) prefix_stack->data, path);
375 		else
376 			return NULL;
377 
378 		if (*buf != '/') {
379 			g_free (buf);
380 			return NULL;
381 		}
382 	}
383 	else {
384 		buf = g_strdup (path);
385 	}
386 
387 	def = strchr (buf, '=');
388 	if (def)
389 		*def = '\0';
390 
391 	filename = ptr = &buf[1];
392 
393 	// XXX:
394 	if (!strncmp(buf, "/scripts", strlen("/scripts"))) {
395 		ptr = strchr (ptr, '/');
396 		if (ptr)
397 			++ptr;
398 	}
399 
400 	secname = strchr (ptr, '/');
401 	if (secname) {
402 		*secname++ = '\0';
403 
404 		keyname = strchr (secname, '/');
405 		if (keyname)
406 			*keyname++ = '\0';
407 	}
408 
409 	/* Ensure that the file is loaded */
410 
411 	find_key (filename, NULL, NULL, FALSE, &file, NULL, NULL);
412 	if (!file)
413 		load_file (filename);
414 
415 	/* Actually look up the key */
416 
417 	find_key (filename, secname, keyname, create, file1, section1, &key);
418 
419 	if (buf)
420 		g_free (buf);
421 
422 	return key;
423 }
424 
425 
string_escape(const char * s)426 static char *string_escape (const char *s) {
427 	const char *c;
428 	char *p;
429 	char *res;
430 	int n = 0;
431 
432 	if (!s)
433 		return NULL;
434 
435 	for (c = s; *c; c++) {
436 		if (*c == '\n' || *c == '\r' || *c == '\\') {
437 			n++;
438 		}
439 		n++;
440 	}
441 
442 	p = res = g_malloc (n + 1);
443 
444 	do {
445 		switch (*s) {
446 
447 			case '\n':
448 				*p++ = '\\';
449 				*p++ = 'n';
450 				break;
451 
452 			case '\r':
453 				*p++ = '\\';
454 				*p++ = 'r';
455 				break;
456 
457 			case '\\':
458 				*p++ = '\\';
459 				*p++ = '\\';
460 				break;
461 
462 			default:
463 				*p++ = *s;
464 				break;
465 
466 		}
467 	} while (*s++);
468 
469 	return res;
470 }
471 
472 
string_unescape(const char * s)473 static char *string_unescape (const char *s) {
474 	const char *c;
475 	char *p;
476 	char *res;
477 	int n = 0;
478 
479 	if (!s)
480 		return NULL;
481 
482 	for (c = s; *c; c++) {
483 		if (*c == '\\' && (c[1] == 'n' || c[1] == 'r' || c[1] == '\\'))
484 			c++;
485 		n++;
486 	}
487 
488 	p = res = g_malloc (n + 1);
489 
490 	do {
491 
492 		if (*s != '\\') {
493 			*p++ = *s;
494 		}
495 		else {
496 			s++;
497 
498 			switch (*s) {
499 
500 				case 'n':
501 					*p++ = '\n';
502 					break;
503 
504 				case 'r':
505 					*p++ = '\r';
506 					break;
507 
508 				case '\\':
509 					*p++ = '\\';
510 					break;
511 
512 				default:
513 					*p++ = '\\';
514 					*p++ = *s;
515 					break;
516 
517 			}
518 		}
519 
520 	} while (*s++);
521 
522 	return res;
523 }
524 
525 
config_get_raw_with_default(const char * path,int * def)526 static char *config_get_raw_with_default (const char *path, int *def) {
527 	struct config_file *file;
528 	struct config_key *key;
529 	char *val;
530 
531 	key = parse_path (path, FALSE, &val, &file, NULL);
532 	if (key) {
533 		if (def) *def = FALSE;
534 		val = key->value;
535 	}
536 	else {
537 		if (def) *def = TRUE;
538 	}
539 	return val;
540 }
541 
542 
config_get_int_with_default(const char * path,int * def)543 int config_get_int_with_default (const char *path, int *def) {
544 	char *val;
545 
546 	val = config_get_raw_with_default (path, def);
547 	return (val)? strtol (val, NULL, 10) : 0;
548 }
549 
550 
config_get_float_with_default(const char * path,int * def)551 double config_get_float_with_default (const char *path, int *def) {
552 	char *val;
553 
554 	val = config_get_raw_with_default (path, def);
555 	return (val)? strtod (val, NULL) : 0.0;
556 }
557 
558 
config_get_bool_with_default(const char * path,int * def)559 int config_get_bool_with_default (const char *path, int *def) {
560 	char *val;
561 
562 	val = config_get_raw_with_default (path, def);
563 
564 	/* Gnome returns FALSE as default value */
565 
566 	if (val && !g_ascii_strcasecmp (val, "true"))
567 		return TRUE;
568 	else
569 		return FALSE;
570 }
571 
572 
config_get_string_with_default(const char * path,int * def)573 char *config_get_string_with_default (const char *path, int *def) {
574 	char *val;
575 
576 	val = config_get_raw_with_default (path, def);
577 	return (val)? string_unescape (val) : NULL;
578 }
579 
580 
config_set_raw(const char * path,char * raw_value)581 static void config_set_raw (const char *path, char *raw_value) {
582 	struct config_key *key;
583 	struct config_file *file;
584 
585 	key = parse_path (path, TRUE, NULL, &file, NULL);
586 	if (key) {
587 		if (key->value)
588 			g_free (key->value);
589 		key->value = raw_value;
590 		if (file)
591 			file->dirty = TRUE;
592 	}
593 }
594 
595 
config_set_int(const char * path,int i)596 void config_set_int (const char *path, int i) {
597 	config_set_raw (path, g_strdup_printf ("%d", i));
598 }
599 
600 
config_set_float(const char * path,double f)601 void config_set_float (const char *path, double f) {
602 	config_set_raw (path, g_strdup_printf ("%g", f));
603 }
604 
605 
config_set_bool(const char * path,gboolean b)606 void config_set_bool (const char *path, gboolean b) {
607 	config_set_raw (path, g_strdup ((b)? "true" : "false"));
608 }
609 
610 
config_set_string(const char * path,const char * str)611 void config_set_string (const char *path, const char *str) {
612 	config_set_raw (path, string_escape (str));
613 }
614 
615 
config_init_iterator(const char * path)616 config_key_iterator* config_init_iterator (const char *path) {
617 	struct config_section *section;
618 
619 	parse_path (path, FALSE, NULL, NULL, &section);
620 	return (section)? (config_key_iterator*)section->keys : NULL;
621 }
622 
623 
config_iterator_next(config_key_iterator * iterator,char ** key,char ** val)624 config_key_iterator* config_iterator_next (config_key_iterator* iterator, char **key, char **val) {
625 	struct config_key *cfg_key;
626 
627 	if (!iterator)
628 		return NULL;
629 
630 	cfg_key = ((GList *) iterator)->data;
631 
632 	if (key) *key = g_strdup (cfg_key->name);
633 	if (val) *val = g_strdup (cfg_key->value);
634 
635 	return (config_key_iterator*)((GList *) iterator)->next;
636 }
637 
config_init_section_iterator(const char * path)638 config_section_iterator* config_init_section_iterator (const char *path) {
639 	struct config_file *file;
640 
641 	parse_path (path, FALSE, NULL, &file, NULL);
642 	return (file)? (config_section_iterator*)file->sections : NULL;
643 }
644 
645 
config_section_iterator_next(config_section_iterator * iterator,char ** section)646 config_section_iterator* config_section_iterator_next (config_section_iterator* iterator, char **section) {
647 	struct config_section *cfg_sect;
648 
649 	if (!iterator)
650 		return NULL;
651 
652 	cfg_sect = ((GList *) iterator)->data;
653 
654 	if (section) *section = g_strdup (cfg_sect->name);
655 
656 	return (config_section_iterator*)((GList *) iterator)->next;
657 }
658 
659 
660 
dump_file(struct config_file * file)661 static void dump_file (struct config_file *file) {
662 	GList *sptr, *kptr;
663 	struct config_section *section;
664 	struct config_key *key;
665 	FILE *f;
666 	char *fn;
667 
668 	if (!file->dirty)
669 		return;
670 
671 	if (file->read_only)
672 		return;
673 
674 	if (!file->sections) {
675 		unlink_file (file->filename);
676 		return;
677 	}
678 
679 	fn = file_in_dir (directories->data, file->filename);
680 	f = fopen (fn, "w");
681 	g_free (fn);
682 
683 	if (f) {
684 		for (sptr = file->sections; sptr; sptr = sptr->next) {
685 			section = (struct config_section *) sptr->data;
686 			fprintf (f, "[%s]\n", section->name);
687 
688 			for (kptr = section->keys; kptr; kptr = kptr->next) {
689 				key = (struct config_key *) kptr->data;
690 				fprintf (f, "%s=%s\n", key->name, key->value);
691 			}
692 
693 			fprintf (f, "\n");
694 		}
695 		fclose (f);
696 	}
697 }
698 
699 
config_sync(void)700 void config_sync (void) {
701 	GList *list;
702 	struct config_file *file;
703 
704 	for (list = files; list; list = list->next) {
705 		file = (struct config_file *) list->data;
706 		dump_file (file);
707 	}
708 	/* config_drop_all (); */
709 }
710 
711 
drop_file(struct config_file * file)712 static void drop_file (struct config_file *file) {
713 	GList *sptr, *kptr;
714 	struct config_section *section;
715 	struct config_key *key;
716 
717 #ifdef DEBUG
718 	fprintf (stderr, "config.c: drop_file (%s)\n", file->filename);
719 #endif
720 
721 	for (sptr = file->sections; sptr; sptr = sptr->next) {
722 		section = (struct config_section *) sptr->data;
723 
724 		for (kptr = section->keys; kptr; kptr = kptr->next) {
725 			key = (struct config_key *) kptr->data;
726 			g_free (key->name);
727 			g_free (key->value);
728 			g_free(key);
729 			key=NULL;
730 		}
731 
732 		g_list_free (section->keys);
733 		g_free (section->name);
734 		g_free(section);
735 		section=NULL;
736 	}
737 
738 	g_list_free (file->sections);
739 	g_free (file->filename);
740 
741 	files = g_list_remove (files, file);
742 
743 	g_free(file);
744 }
745 
746 
drop_section(struct config_file * file,struct config_section * section)747 static void drop_section (struct config_file *file,
748 		struct config_section *section) {
749 	GList *kptr;
750 	struct config_key *key;
751 
752 	for (kptr = section->keys; kptr; kptr = kptr->next) {
753 		key = (struct config_key *) kptr->data;
754 		g_free (key->name);
755 		g_free (key->value);
756 		g_free (key);
757 	}
758 	g_list_free (section->keys);
759 	g_free (section->name);
760 	file->sections = g_list_remove (file->sections, section);
761 
762 	g_free(section);
763 
764 	file->dirty = TRUE;
765 }
766 
767 
drop_key(struct config_file * file,struct config_section * section,struct config_key * key)768 static void drop_key (struct config_file *file,
769 		struct config_section *section,
770 		struct config_key *key) {
771 	g_free (key->name);
772 	g_free (key->value);
773 	section->keys = g_list_remove (section->keys, key);
774 	g_free(key);
775 
776 	file->dirty = TRUE;
777 
778 	if (section->keys == NULL)
779 		drop_section (file, section);
780 }
781 
782 
config_drop_all(void)783 void config_drop_all (void) {
784 	while (files) {
785 		drop_file ((struct config_file *) files->data);
786 	}
787 }
788 
789 
config_drop_file(const char * path)790 void config_drop_file (const char *path) {
791 	struct config_file *file = NULL;
792 
793 	parse_path (path, FALSE, NULL, &file, NULL);
794 	if (file)
795 		drop_file (file);
796 }
797 
798 
config_clean_key(const char * path)799 void config_clean_key (const char *path) {
800 	struct config_file *file;
801 	struct config_section *section;
802 	struct config_key *key;
803 
804 	key = parse_path (path, FALSE, NULL, &file, &section);
805 	if (key)
806 		drop_key (file, section, key);
807 }
808 
809 
config_clean_section(const char * path)810 void config_clean_section (const char *path) {
811 	struct config_file *file;
812 	struct config_section *section = NULL;
813 
814 	parse_path (path, FALSE, NULL, &file, &section);
815 	if (section)
816 		drop_section (file, section);
817 }
818 
819 
config_clean_file(const char * path)820 void config_clean_file (const char *path) {
821 	struct config_file *file = NULL;
822 	char *filename;
823 
824 	parse_path (path, FALSE, NULL, &file, NULL);
825 	if (file) {
826 		filename = g_strdup (file->filename);
827 
828 		drop_file (file);
829 
830 		/* Re-create file record and mark it as 'dirty' */
831 
832 		find_key (filename, NULL, NULL, TRUE, &file, NULL, NULL);
833 		file->dirty = TRUE;
834 
835 		g_free (filename);
836 	}
837 }
838 
839 
config_push_prefix(const char * prefix)840 void config_push_prefix (const char *prefix) {
841 	if (prefix && *prefix)
842 		prefix_stack = g_list_prepend (prefix_stack, g_strdup (prefix));
843 }
844 
845 
config_pop_prefix(void)846 void config_pop_prefix (void) {
847 	void *data;
848 
849 	if (prefix_stack) {
850 		data = prefix_stack->data;
851 		prefix_stack = g_list_remove (prefix_stack, data);
852 		g_free (data);
853 	}
854 }
855 
856 
config_add_dir(const char * dir)857 void config_add_dir (const char *dir) {
858 	directories = g_slist_prepend(directories,g_strdup (dir));
859 }
860 
861