1 /*
2    Copyright (c) 2000-2002 Perry Rapp
3    "The MIT license"
4    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
7 */
8 
9 /*==========================================================
10  * lloptions.c -- Read options from config file (& db user options)
11  *   added in 3.0.6 by Perry Rapp
12  *========================================================*/
13 
14 #ifdef HAVE_LOCALE_H
15 #include <locale.h>
16 #endif
17 #include "llstdlib.h"
18 #include "gedcom.h"
19 #include "gedcomi.h"
20 #include "lloptions.h"
21 
22 
23 /*********************************************
24  * external variables
25  *********************************************/
26 
27 extern STRING qSopt2long;
28 
29 /*********************************************
30  * local function prototypes
31  *********************************************/
32 
33 /* alphabetical */
34 static void copy_process(STRING dest, STRING src);
35 static void expand_variables(STRING valbuf, INT max);
36 static INT load_config_file(STRING file, STRING * pmsg, STRING *chain);
37 static void send_notifications(void);
38 
39 /*********************************************
40  * local variables
41  *********************************************/
42 
43 /* table holding option values, both keys & values in heap */
44 /* listed in descending priority */
45 static TABLE f_cmd=0; /* option values from command line of current execution */
46 static TABLE f_rpt=0; /* option values local to currently running report program */
47 	/* NB: cannot actually set any report option values currently 2002-10-07 */
48 static TABLE f_db=0; /* option values in current database (user/options) */
49 static TABLE f_global=0; /* option values from lines config file */
50 static TABLE f_predef=0; /* predefined variables during config file processing */
51 static TABLE f_fallback=0; /* lowest priority option values */
52 static LIST f_notifications=0; /* collection of callbacks for option table changes */
53 
54 /*********************************************
55  * local function definitions
56  * body of module
57  *********************************************/
58 
59 /*==========================================
60  * copy_process -- copy config value line,
61  *  converting any escape characters
62  *  This handles \n, \t, and \\
63  *  We do not trim out backslashes, unless they
64  *  are part of escape sequences. (This is mostly
65  *  because backslashes are so prevalent in
66  *  MS-Windows paths.)
67  * The output (dest) is no longer than the input (src).
68  * Created: 2001/11/09, Perry Rapp
69  *========================================*/
70 static void
copy_process(STRING dest,STRING src)71 copy_process (STRING dest, STRING src)
72 {
73 	STRING q=dest,p=src;
74 	while ((*q = *p++)) {
75 		if (*q == '\\') {
76 			switch (*p++) {
77 			case 0:
78 				*++q = 0;
79 				break;
80 			case 'n':
81 				*q = '\n';
82 				break;
83 			case 't':
84 				*q  = '\t';
85 				break;
86 			case '\\':
87 				*q = '\\';
88 				break;
89 			default:
90 				--p;
91 			}
92 		}
93 		++q;
94 	}
95 }
96 /*==========================================
97  * expand_variables -- do any variable substitutions
98  *  (variables are option properties starting & ending with %
99  * Created: 2002/10/21, Perry Rapp
100  *========================================*/
101 static void
expand_variables(STRING valbuf,INT max)102 expand_variables (STRING valbuf, INT max)
103 {
104 	STRING start, end;
105 	STRING ptr; /* remainder of valbuf to check */
106 	ptr = valbuf;
107 	while ((start=strchr(ptr, '%')) && (end=strchr(start+1, '%'))) {
108 		STRING name = allocsubbytes(start, 0, end-start+1);
109 		STRING value = valueof_str(f_global, name);
110 		if (!value)
111 			value = valueof_str(f_predef, name);
112 		if (value) {
113 			INT newlen = strlen(valbuf)-(end-start+1)+strlen(value);
114 			if (newlen < max) {
115 				STRING copy = strdup(valbuf);
116 				if (start>valbuf)
117 					strncpy(valbuf, copy, start-valbuf);
118 				strcpy(start, value);
119 				strcpy(start+strlen(value), copy+(end-valbuf+1));
120 				stdfree(copy);
121 			}
122 		}
123 		stdfree(name);
124 		ptr = end+1;
125 	}
126 }
127 /*==========================================
128  * dir_from_file -- return directory of file
129  * heap-allocated
130  *========================================*/
131 static STRING
dir_from_file(STRING file)132 dir_from_file (STRING file)
133 {
134 	STRING thisdir = strdup(file);
135 	STRING ptr;
136 	for (ptr=thisdir+strlen(thisdir)-1; ptr>thisdir; --ptr) {
137 		if (is_dir_sep(*ptr))
138 			break;
139 	}
140 	*ptr = 0;
141 	return thisdir;
142 }
143 /*==========================================
144  * load_config_file -- read options in config file
145  *  and load into table (f_global)
146  * returns 1 for success, 0 for not found, -1 for error (with pmsg)
147  *========================================*/
148 static INT
load_config_file(STRING file,STRING * pmsg,STRING * chain)149 load_config_file (STRING file, STRING * pmsg, STRING *chain)
150 {
151 	FILE * fp = 0;
152 	STRING ptr, val, key;
153 	STRING thisdir = dir_from_file(file);
154 	BOOLEAN failed, noesc;
155 	char buffer[MAXLINELEN],valbuf[MAXLINELEN];
156 	INT len;
157 	fp = fopen(file, LLREADTEXT);
158 	if (!fp) {
159 		free(thisdir);
160 		return 0; /* 0 for not found */
161 	}
162 	f_predef = create_table_str();
163 
164 	insert_table_str(f_predef, "%thisdir%", thisdir);
165 	strfree(&thisdir);
166 	/* read thru config file til done (or error) */
167 	while (fgets(buffer, sizeof(buffer), fp)) {
168 		noesc = FALSE;
169 		len = strlen(buffer);
170 		if (len == 0)
171 			continue; /* ignore blank lines */
172 		if (buffer[0] == '#')
173 			continue; /* ignore lines starting with # */
174 		if (!feof(fp) && buffer[len-1] != '\n') {
175 			/* bail out if line too long */
176 			break;
177 		}
178 		chomp(buffer); /* trim any trailing CR or LF */
179 		/* find =, which separates key from value */
180 		for (ptr = buffer; *ptr && *ptr!='='; ptr++)
181 			;
182 		if (*ptr != '=' || ptr==buffer)
183 			continue; /* ignore lines without = or key */
184 		*ptr=0; /* zero-terminate key */
185 		if (ptr[-1] == ':') {
186 			noesc = TRUE; /* := means don't do backslash escapes */
187 			ptr[-1] = 0;
188 		}
189 		/* ignore any previous value, it will be overwritten */
190 		/* advance over separator to value */
191 		ptr++;
192 		/*
193 		process the value into valbuf
194 		this handles escapes (eg, "\n")
195 		the output (valbuf) is no longer than the input (ptr)
196 		*/
197 		if (noesc)
198 			llstrncpy(valbuf, ptr, sizeof(valbuf), uu8);
199 		else
200 			copy_process(valbuf, ptr);
201 		expand_variables(valbuf, sizeof(valbuf));
202 		key = buffer; /* key is in beginning of buffer, we zero-terminated it */
203 		val = valbuf;
204 		if (strcmp(key,"LLCONFIGFILE") == 0) {
205 		    /* LLCONFIGFILE is not entered in table, only used to
206 		     * chain to another config file
207 		     */
208 		    *chain = strsave(val);
209 		} else {
210 		    insert_table_str(f_global, key, val);
211 		}
212 	}
213 	failed = !feof(fp);
214 	fclose(fp);
215 	if (failed) {
216 		/* error is in heap */
217 		*pmsg = strsave(_(qSopt2long));
218 		return -1; /* -1 for error */
219 	}
220 	free_optable(&f_predef);
221 	send_notifications();
222 	return 1; /* 1 for ok */
223 }
224 /*=================================
225  * load_global_options --
226  *  Load internal table of global options from caller-specified config file
227  * STRING * pmsg: heap-alloc'd error string if fails
228  * returns 1 for ok, 0 for not found, -1 for error
229  *===============================*/
230 INT
load_global_options(STRING configfile,STRING * pmsg)231 load_global_options (STRING configfile, STRING * pmsg)
232 {
233 	STRING chain = NULL;
234 	INT rtn = 0;
235 	INT cnt = 0;
236 	*pmsg = NULL;
237 	if (!f_global)
238 		f_global= create_table_str();
239 	do {
240 	    if (chain) strfree(&chain);
241 	    rtn = load_config_file(configfile, pmsg, &chain);
242 	    if (rtn == -1) {
243 		if (chain) strfree(&chain);
244 		return rtn;
245 	    }
246 	    if (++cnt > 100) {
247 	        return -1;  /* prevent infinite recursion */
248 	    }
249 	} while (chain);
250 
251 	return rtn;
252 }
253 /*=================================
254  * set_cmd_options -- Store cmdline options from caller
255  *===============================*/
256 void
set_cmd_options(TABLE opts)257 set_cmd_options (TABLE opts)
258 {
259 	ASSERT(opts);
260 	release_table(f_cmd);
261 	f_cmd = opts;
262 	addref_table(f_cmd);
263 	send_notifications();
264 }
265 /*=================================
266  * set_db_options -- Store db options from caller
267  * Created: 2002/06/16, Perry Rapp
268  *===============================*/
269 void
set_db_options(TABLE opts)270 set_db_options (TABLE opts)
271 {
272 	ASSERT(opts);
273 	release_table(f_db);
274 	f_db = opts;
275 	addref_table(f_db);
276 	send_notifications();
277 }
278 /*=================================
279  * get_db_options -- Copy db options to caller's table
280  * Created: 2002/06/16, Perry Rapp
281  *===============================*/
282 void
get_db_options(TABLE opts)283 get_db_options (TABLE opts)
284 {
285 	if (!f_db)
286 		f_db = create_table_str();
287 	copy_table(f_db, opts);
288 }
289 /*==========================================
290  * free_optable -- free a table if it exists
291  *========================================*/
292 void
free_optable(TABLE * ptab)293 free_optable (TABLE * ptab)
294 {
295 	if (*ptab) {
296 		release_table(*ptab);
297 		*ptab = 0;
298 	}
299 }
300 /*==========================================
301  * term_lloptions -- deallocate structures
302  * used by lloptions at program termination
303  * Safe to be called more than once
304  * Created: 2001/04/30, Matt Emmerton
305  *========================================*/
306 void
term_lloptions(void)307 term_lloptions (void)
308 {
309 	free_optable(&f_cmd);
310 	free_optable(&f_rpt);
311 	free_optable(&f_db);
312 	free_optable(&f_global);
313 	free_optable(&f_fallback);
314 	remove_listeners(&f_notifications);
315 }
316 /*===============================================
317  * getlloptstr -- get an option (from db or from global)
318  * Example:
319  *  str = getlloptstr("HDR_SUBM", "1 SUBM");
320  * returns string belonging to table
321  *=============================================*/
322 STRING
getlloptstr(CNSTRING optname,STRING defval)323 getlloptstr (CNSTRING optname, STRING defval)
324 {
325 	STRING str = 0;
326 	if (!str && f_cmd)
327 		str = valueof_str(f_cmd, optname);
328 	if (!str && f_db)
329 		str = valueof_str(f_db, optname);
330 	if (!str && f_global)
331 		str = valueof_str(f_global, optname);
332 	if (!str && f_fallback)
333 		str = valueof_str(f_fallback, optname);
334 	if (!str)
335 		str = defval;
336 	return str;
337 }
338 /*===============================================
339  * getlloptstr_rpt -- get an option (checking report-local options first)
340  * Example:
341  *  str = getlloptstr_rpt("HDR_SUBM", "1 SUBM");
342  * Created: 2002/06/16, Perry Rapp
343  *=============================================*/
344 STRING
getlloptstr_rpt(CNSTRING optname,STRING defval)345 getlloptstr_rpt (CNSTRING optname, STRING defval)
346 {
347 	STRING str = 0;
348 	if (!str && f_rpt)
349 		str = valueof_str(f_rpt, optname);
350 	if (!str)
351 		str = getlloptstr(optname, defval);
352 	return str;
353 }
354 /*===============================================
355  * getlloptstr_dbonly -- get an option (but only look at db options)
356  * Example:
357  *  str = getlloptstr_dbonly("codeset", 0);
358  * Created: 2002/06/16, Perry Rapp
359  *=============================================*/
360 STRING
getlloptstr_dbonly(CNSTRING optname,STRING defval)361 getlloptstr_dbonly (CNSTRING optname, STRING defval)
362 {
363 	STRING str = 0;
364 	if (f_db)
365 		str = valueof_str(f_db, optname);
366 	if (!str)
367 		str = defval;
368 	return str;
369 }
370 /*===============================================
371  * getlloptint -- get a numerical option
372  *  First tries user option table (looks up optname)
373  *  Then tries config option table
374  *  Finally defaults to defval
375  * Example:
376 	if (getlloptint("FullReportCallStack", 0) > 0)
377  * Created: 2001/11/22, Perry Rapp
378  *=============================================*/
379 INT
getlloptint(CNSTRING optname,INT defval)380 getlloptint (CNSTRING optname, INT defval)
381 {
382 	STRING str = getlloptstr(optname, 0);
383 	return str ? atoi(str) : defval;
384 }
385 /*===============================================
386  * setoptstr_fallback -- Set option fallback value
387  *=============================================*/
388 void
setoptstr_fallback(STRING optname,STRING newval)389 setoptstr_fallback (STRING optname, STRING newval)
390 {
391 	if (!f_fallback)
392 		f_fallback = create_table_str();
393 	replace_table_str(f_fallback, optname, newval);
394 	send_notifications();
395 }
396 /*===============================================
397  * register_notify -- Caller wants to be notified when options change
398  *=============================================*/
399 void
register_notify(CALLBACK_FNC fncptr)400 register_notify (CALLBACK_FNC fncptr)
401 {
402 	add_listener(&f_notifications, fncptr, 0);
403 }
404 /*===============================================
405  * unregister_notify -- Caller no longer wants to be notified when options change
406  *=============================================*/
407 void
unregister_notify(CALLBACK_FNC fncptr)408 unregister_notify (CALLBACK_FNC fncptr)
409 {
410 	delete_listener(&f_notifications, fncptr, 0);
411 }
412 /*===============================================
413  * send_notifications -- Send notifications to any registered listeners
414  *=============================================*/
415 static void
send_notifications(void)416 send_notifications (void)
417 {
418 	notify_listeners(&f_notifications);
419 }
420