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