1 /*
2  * Copyright (c) 1998, 1999, 2000, 2001, 2002, 2005, 2010, 2011,
3  *	2014, 2015
4  *	Tama Communications Corporation
5  *
6  * This file is part of GNU GLOBAL.
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 #include <assert.h>
26 #include <ctype.h>
27 #ifdef STDC_HEADERS
28 #include <stdlib.h>
29 #endif
30 #ifdef HAVE_STRING_H
31 #include <string.h>
32 #else
33 #include <strings.h>
34 #endif
35 #if defined(_WIN32) && !defined(__CYGWIN__)
36 #define WIN32_LEAN_AND_MEAN
37 #include <windows.h>
38 #endif
39 
40 #include "gparam.h"
41 #include "char.h"
42 #include "checkalloc.h"
43 #include "conf.h"
44 #include "die.h"
45 #include "env.h"
46 #include "langmap.h"
47 #include "locatestring.h"
48 #include "makepath.h"
49 #include "path.h"
50 #include "strbuf.h"
51 #include "strlimcpy.h"
52 #include "strmake.h"
53 #include "test.h"
54 #include "usable.h"
55 
56 static FILE *fp;
57 static STRBUF *ib;
58 static char *confline;
59 static char *config_path;
60 static char *config_label;
61 /**
62  * 32 level nested tc= or include= is allowed.
63  */
64 static int allowed_nest_level = 32;
65 static int opened;
66 
67 static void trim(char *);
68 static const char *readrecord(FILE *, const char *);
69 static void includelabel(FILE *, STRBUF *, const char *, int);
70 
71 #ifndef isblank
72 #define isblank(c)	((c) == ' ' || (c) == '\t')
73 #endif
74 
75 /**
76  * trim: trim string.
77  *
78  * : var1=a b :
79  *	|
80  *	v
81  * :var1=a b :
82  */
83 static void
trim(char * l)84 trim(char *l)
85 {
86 	char *f, *b;
87 	int colon = 0;
88 
89 	/*
90 	 * delete blanks.
91 	 */
92 	for (f = b = l; *f; f++) {
93 		if (colon && isblank(*f))
94 			continue;
95 		colon = 0;
96 		if ((*b++ = *f) == ':')
97 			colon = 1;
98 	}
99 	*b = 0;
100 	/*
101 	 * delete duplicate semi colons.
102 	 */
103 	for (f = b = l; *f;) {
104 		if ((*b++ = *f++) == ':') {
105 			while (*f == ':')
106 				f++;
107 		}
108 	}
109 	*b = 0;
110 }
111 /**
112  * readrecord: read recoed indexed by label.
113  *
114  *	@param[in]	label	label in config file
115  *	@return		record or NULL
116  *
117  * Jobs:
118  * - skip comment.
119  * - append following line.
120  * - format check.
121  */
122 static const char *
readrecord(FILE * fp,const char * label)123 readrecord(FILE *fp, const char *label)
124 {
125 	char *p;
126 	int flag = STRBUF_NOCRLF|STRBUF_SHARPSKIP;
127 	int count = 0;
128 
129 	rewind(fp);
130 	while ((p = strbuf_fgets(ib, fp, flag)) != NULL) {
131 		count++;
132 		/*
133 		 * ignore \<new line>.
134 		 */
135 		flag &= ~STRBUF_APPEND;
136 		if (*p == '\0')
137 			continue;
138 		if (strbuf_unputc(ib, '\\')) {
139 			flag |= STRBUF_APPEND;
140 			continue;
141 		}
142 		trim(p);
143 		for (;;) {
144 			const char *candidate;
145 			/*
146 			 * pick up candidate.
147 			 */
148 			if ((candidate = strmake(p, "|:")) == NULL)
149 				die("invalid config file format (%s line: %d).", config_path, count);
150 			if (!strcmp(label, candidate)) {
151 				if (!(p = locatestring(p, ":", MATCH_FIRST)))
152 					die("invalid config file format (%s line: %d).", config_path, count);
153 				return check_strdup(p);
154 			}
155 			/*
156 			 * locate next candidate.
157 			 */
158 			p += strlen(candidate);
159 			if (*p == ':')
160 				break;
161 			else if (*p == '|')
162 				p++;
163 			else
164 				die("invalid config file format (%s line: %d).", config_path, count);
165 		}
166 	}
167 	/*
168 	 * config line not found.
169 	 */
170 	return NULL;
171 }
172 /**
173  * includelabel: procedure for tc= (or include=)
174  *
175  *	@param[in]	fp	file pointer
176  *	@param[out]	sb	string buffer
177  *	@param[in]	label	record label
178  *	@param[in]	level	nest level for check
179  *
180  * This function may call itself (recursive)
181  */
182 static void
includelabel(FILE * fp,STRBUF * sb,const char * label,int level)183 includelabel(FILE *fp, STRBUF *sb, const char *label, int level)
184 {
185 	const char *savep, *p, *q;
186 	char *file;
187 
188 	if (++level > allowed_nest_level)
189 		die("nested include= (or tc=) over flow in '%s'.", config_path);
190 	/*
191 	 * Label can include a '@' and a following path name.
192 	 * Label: <label>[@<path>]
193 	 */
194 	if ((file = locatestring(label, "@", MATCH_FIRST)) != NULL) {
195 		*file++ = '\0';
196 		if ((p = makepath_with_tilde(file)) == NULL)
197 			die("config file must be absolute path in '%s'. (%s)", config_path, file);
198 		fp = fopen(p, "r");
199 		if (fp == NULL)
200 			die("cannot open config file. (%s)", p);
201 	}
202 	if (!(savep = p = readrecord(fp, label)))
203 		die("label '%s' not found in '%s'.", label, config_path);
204 	while ((q = locatestring(p, ":include=", MATCH_FIRST)) || (q = locatestring(p, ":tc=", MATCH_FIRST))) {
205 		STRBUF *inc = strbuf_open(0);
206 
207 		strbuf_nputs(sb, p, q - p);
208 		q = locatestring(q, "=", MATCH_FIRST) + 1;
209 		for (; *q && *q != ':'; q++)
210 			strbuf_putc(inc, *q);
211 		includelabel(fp, sb, strbuf_value(inc), level);
212 		p = q;
213 		strbuf_close(inc);
214 	}
215 	strbuf_puts(sb, p);
216 	free((void *)savep);
217 	if (file)
218 		fclose(fp);
219 }
220 static char *
gtagsobjdir(const char * rootdir)221 gtagsobjdir(const char *rootdir) {
222 	const char *objdir = "obj";
223 	STATIC_STRBUF(sb);
224 	strbuf_clear(sb);
225 	if (rootdir == NULL)
226 		return NULL;
227 	if (getenv("GTAGSOBJDIR"))
228 		objdir = getenv("GTAGSOBJDIR");
229 	else if (getenv("MAKEOBJDIR"))
230 		objdir = getenv("MAKEOBJDIR");
231 	strbuf_puts(sb, rootdir);
232 	strbuf_putc(sb, '/');
233 	strbuf_puts(sb, objdir);
234 	return strbuf_value(sb);
235 }
236 /**
237  * configpath: get path of configuration file.
238  *
239  *	@param[in]	rootdir	Project root directory
240  *	@return		path name of the configuration file or NULL
241  */
242 static char *
configpath(const char * rootdir)243 configpath(const char *rootdir)
244 {
245 	const char *objdir = gtagsobjdir(rootdir);
246 	STATIC_STRBUF(sb);
247 	const char *p;
248 
249 	strbuf_clear(sb);
250 	/*
251 	 * at first, check environment variable GTAGSCONF.
252 	 */
253 	if (getenv("GTAGSCONF") != NULL)
254 		strbuf_puts(sb, getenv("GTAGSCONF"));
255 	/*
256 	 * if GTAGSCONF not set then check standard config files.
257 	 */
258 	else if (rootdir && *rootdir && test("r", makepath(rootdir, "gtags.conf", NULL)))
259 		strbuf_puts(sb, makepath(rootdir, "gtags.conf", NULL));
260 	else if (objdir && test("r", makepath(objdir, "gtags.conf", NULL)))
261 		strbuf_puts(sb, makepath(objdir, "gtags.conf", NULL));
262 	else if ((p = get_home_directory()) && test("r", makepath(p, GTAGSRC, NULL)))
263 		strbuf_puts(sb, makepath(p, GTAGSRC, NULL));
264 #ifdef __DJGPP__
265 	else if ((p = get_home_directory()) && test("r", makepath(p, DOS_GTAGSRC, NULL)))
266 		strbuf_puts(sb, makepath(p, DOS_GTAGSRC, NULL));
267 #endif
268 	else if (test("r", GTAGSCONF))
269 		strbuf_puts(sb, GTAGSCONF);
270 	else if (test("r", DEBIANCONF))
271 		strbuf_puts(sb, DEBIANCONF);
272 	else if (test("r", makepath(SYSCONFDIR, "gtags.conf", NULL)))
273 		strbuf_puts(sb, makepath(SYSCONFDIR, "gtags.conf", NULL));
274 	else
275 		return NULL;
276 	return strbuf_value(sb);
277 }
278 /**
279  * openconf: load configuration file.
280  *
281  *	@param[in]	rootdir	Project root directory
282  *
283  * Globals used (output):
284  *	confline:	 specified entry
285  */
286 void
openconf(const char * rootdir)287 openconf(const char *rootdir)
288 {
289 	STRBUF *sb;
290 
291 	if (opened)
292 		return;
293 	opened = 1;
294 	/*
295 	 * if config file not found then return default value.
296 	 */
297 	if (!(config_path = configpath(rootdir)))
298 		confline = check_strdup("");
299 	/*
300 	 * if it is not an absolute path then assumed config value itself.
301 	 */
302 	else if (!isabspath(config_path)) {
303 		confline = check_strdup(config_path);
304 		if (!locatestring(confline, ":", MATCH_FIRST))
305 			die("GTAGSCONF must be absolute path name.");
306 	}
307 	/*
308 	 * else load value from config file.
309 	 */
310 	else {
311 		if (test("d", config_path))
312 			die("config file '%s' is a directory.", config_path);
313 		if (!test("f", config_path))
314 			die("config file '%s' not found.", config_path);
315 		if (!test("r", config_path))
316 			die("config file '%s' is not readable.", config_path);
317 		if ((config_label = getenv("GTAGSLABEL")) == NULL)
318 			config_label = "default";
319 
320 		if (!(fp = fopen(config_path, "r")))
321 			die("cannot open '%s'.", config_path);
322 		ib = strbuf_open(MAXBUFLEN);
323 		sb = strbuf_open(0);
324 		includelabel(fp, sb, config_label, 0);
325 		confline = check_strdup(strbuf_value(sb));
326 		strbuf_close(ib);
327 		strbuf_close(sb);
328 		fclose(fp);
329 	}
330 	/*
331 	 * make up required variables.
332 	 */
333 	sb = strbuf_open(0);
334 	strbuf_puts(sb, confline);
335 	strbuf_unputc(sb, ':');
336 
337 	if (!getconfs("langmap", NULL)) {
338 		strbuf_puts(sb, ":langmap=");
339 		strbuf_puts(sb, quote_chars(DEFAULTLANGMAP, ':'));
340 	}
341 	if (!getconfs("skip", NULL)) {
342 		strbuf_puts(sb, ":skip=");
343 		strbuf_puts(sb, DEFAULTSKIP);
344 	}
345 	strbuf_unputc(sb, ':');
346 	strbuf_putc(sb, ':');
347 	confline = check_strdup(strbuf_value(sb));
348 	strbuf_close(sb);
349 	trim(confline);
350 	return;
351 }
352 /**
353  * getconfn: get property number
354  *
355  *	@param[in]	name	property name
356  *	@param[out]	num	value (if not NULL)
357  *	@return		1: found, 0: not found
358  */
359 int
getconfn(const char * name,int * num)360 getconfn(const char *name, int *num)
361 {
362 	const char *p;
363 	char buf[MAXPROPLEN];
364 
365 	if (!opened)
366 		die("configuration file not opened.");
367 	snprintf(buf, sizeof(buf), ":%s#", name);
368 	if ((p = locatestring(confline, buf, MATCH_FIRST)) != NULL) {
369 		p += strlen(buf);
370 		if (num != NULL)
371 			*num = atoi(p);
372 		return 1;
373 	}
374 	return 0;
375 }
376 /**
377  * replace_variables: replace variables in the string.
378  *
379  *	@param[in]	sb
380  */
381 int recursive_call = 0;
382 static void
replace_variables(STRBUF * sb)383 replace_variables(STRBUF *sb)
384 {
385 	STRBUF *result = strbuf_open(0);
386 	STRBUF *word = strbuf_open(0);
387 	const char *p = strbuf_value(sb);
388 
389 	/*
390 	 * Simple of detecting infinite loop.
391 	 */
392 	if (++recursive_call > 32)
393 		die("Seems to be a never-ending referring in '%s'.", config_path);
394 	for (;;) {
395 		for (; *p; p++) {
396 			if (*p == '$')
397 				break;
398 			if (*p == '\\' && *(p + 1) != 0)
399 				p++;
400 			strbuf_putc(result, *p);
401 		}
402 		if (*p == 0)
403 			break;
404 		/*
405 		 * $<word> or ${<word>}
406 		 */
407 		if (*p == '$') {
408 			strbuf_reset(word);
409 			if (*++p == '{') {
410 				for (p++; *p && *p != '}'; p++)
411 					strbuf_putc(word, *p);
412 				if (*p++ != '}')
413 					die("invalid variable in '%s'.", config_path);
414 			} else {
415 				for (; *p && (isalnum(*p) || *p == '_'); p++)
416 					strbuf_putc(word, *p);
417 			}
418 			getconfs(strbuf_value(word), result);
419 		}
420 	}
421 	strbuf_reset(sb);
422 	strbuf_puts(sb, strbuf_value(result));
423 	strbuf_close(result);
424 	strbuf_close(word);
425 	recursive_call--;
426 }
427 /**
428  * getconfs: get property string
429  *
430  *	@param[in]	name	property name
431  *	@param[out]	result	string buffer (if not NULL)
432  *	@return		1: found, 0: not found
433  */
434 int
getconfs(const char * name,STRBUF * result)435 getconfs(const char *name, STRBUF *result)
436 {
437 	STRBUF *sb = NULL;
438 	const char *p;
439 	char buf[MAXPROPLEN];
440 	int all = 0;
441 	int exist = 0;
442 	int bufsize;
443 
444 	if (!opened)
445 		die("configuration file not opened.");
446 	/* 'path' is reserved name for the current path of configuration file */
447 	if (!strcmp(name, "path")) {
448 		if (config_path && result)
449 			strbuf_puts(result, config_path);
450 		return 1;
451 	}
452 	sb = strbuf_open(0);
453 	if (!strcmp(name, "skip") || !strcmp(name, "gtags_parser") || !strcmp(name, "langmap"))
454 		all = 1;
455 	snprintf(buf, sizeof(buf), ":%s=", name);
456 	bufsize = strlen(buf);
457 	p = confline;
458 	while ((p = locatestring(p, buf, MATCH_FIRST)) != NULL) {
459 		if (exist && sb)
460 			strbuf_putc(sb, ',');
461 		exist = 1;
462 		for (p += bufsize; *p; p++) {
463 			if (*p == ':')
464 				break;
465 			if (*p == '\\' && *(p + 1) == ':')	/* quoted character */
466 				p++;
467 			if (sb)
468 				strbuf_putc(sb, *p);
469 		}
470 		if (!all)
471 			break;
472 	}
473 	/*
474 	 * If 'bindir' and 'datadir' are not defined then
475 	 * return system configuration value.
476 	 */
477 	if (!exist) {
478 		if (!strcmp(name, "bindir")) {
479 			if (sb)
480 				strbuf_puts(sb, BINDIR);
481 			exist = 1;
482 		} else if (!strcmp(name, "datadir")) {
483 #if defined(_WIN32) && !defined(__CYGWIN__)
484 			/*
485 			 * Test if this directory exists, and if not, take the
486 			 * directory relative to the binary.
487 			 */
488 			if (test("d", DATADIR)) {
489 				if (sb)
490 					strbuf_puts(sb, DATADIR);
491 			} else {
492 				char path[MAX_PATH], *name, *p;
493 				GetModuleFileName(NULL, path, MAX_PATH);
494 				for (p = name = path; *p; ++p) {
495 					if (*p == '\\') {
496 						*p = '/';
497 						name = p+1;
498 					}
499 				}
500 				strcpy(name, "../share");
501 				if (sb)
502 					strbuf_puts(sb, path);
503 			}
504 #else
505 			if (sb)
506 				strbuf_puts(sb, DATADIR);
507 #endif
508 			exist = 1;
509 		} else if (!strcmp(name, "libdir")) {
510 			if (sb)
511 				strbuf_puts(sb, LIBDIR);
512 			exist = 1;
513 		} else if (!strcmp(name, "localstatedir")) {
514 			if (sb)
515 				strbuf_puts(sb, LOCALSTATEDIR);
516 			exist = 1;
517 		} else if (!strcmp(name, "sysconfdir")) {
518 			if (sb)
519 				strbuf_puts(sb, SYSCONFDIR);
520 			exist = 1;
521 		}
522 	}
523 	replace_variables(sb);
524 	if (result)
525 		strbuf_puts(result, !strcmp(name, "langmap") ?
526 			trim_langmap(strbuf_value(sb)) :
527 			strbuf_value(sb));
528 	strbuf_close(sb);
529 	return exist;
530 }
531 /**
532  * getconfb: get property bool value
533  *
534  *	@param[in]	name	property name
535  *	@return		1: TRUE, 0: FALSE
536  */
537 int
getconfb(const char * name)538 getconfb(const char *name)
539 {
540 	char buf[MAXPROPLEN];
541 
542 	if (!opened)
543 		die("configuration file not opened.");
544 	snprintf(buf, sizeof(buf), ":%s:", name);
545 	if (locatestring(confline, buf, MATCH_FIRST) != NULL)
546 		return 1;
547 	return 0;
548 }
549 /**
550  * getconfline: print loaded config entry.
551  */
552 const char *
getconfline(void)553 getconfline(void)
554 {
555 	if (!opened)
556 		die("configuration file not opened.");
557 	return confline;
558 }
559 /**
560  * getconfigpath: get path of configuration file.
561  */
562 const char *
getconfigpath()563 getconfigpath()
564 {
565 	return config_path;
566 }
567 /**
568  * getconfiglabel: get label of configuration file.
569  */
570 const char *
getconfiglabel()571 getconfiglabel()
572 {
573 	return config_label;
574 }
575 void
closeconf(void)576 closeconf(void)
577 {
578 	if (!opened)
579 		return;
580 	free(confline);
581 	confline = NULL;
582 	opened = 0;
583 }
584