qLibc
qconfig.c
Go to the documentation of this file.
1/******************************************************************************
2 * qLibc
3 *
4 * Copyright (c) 2010-2015 Seungyoung Kim.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright notice,
11 * this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 *****************************************************************************/
28
29/**
30 * @file qconfig.c INI-style configuration file parser.
31 */
32
33#ifndef DISABLE_QCONFIG
34
35#include <stdio.h>
36#include <stdlib.h>
37#include <stdbool.h>
38#include <string.h>
39#include <limits.h>
40#include <errno.h>
41#include "qinternal.h"
42#include "utilities/qfile.h"
43#include "utilities/qstring.h"
44#include "utilities/qsystem.h"
45#include "extensions/qconfig.h"
46
47#define _INCLUDE_DIRECTIVE "@INCLUDE "
48
49#ifndef _DOXYGEN_SKIP
50#define _VAR '$'
51#define _VAR_OPEN '{'
52#define _VAR_CLOSE '}'
53#define _VAR_CMD '!'
54#define _VAR_ENV '%'
55
56/* internal functions */
57static char *_parsestr(qlisttbl_t *tbl, const char *str);
58#endif
59
60/**
61 * Load & parse configuration file
62 *
63 * @param tbl a pointer of qlisttbl_t. NULL will generate a new table.
64 * @param filepath configuration file path
65 * @param sepchar separater used in configuration file to divice key and value
66 *
67 * @return a pointer of qlisttbl_t in case of successful,
68 * otherwise(file not found) returns NULL
69 *
70 * @code
71 * # This is "config.conf" file.
72 * # A line which starts with # character is comment
73 *
74 * @INCLUDE config.def => include 'config.def' file.
75 *
76 * prefix=/tmp => set static value. 'prefix' is the key for this entry.
77 * log=${prefix}/log => get the value from previously defined key 'prefix'.
78 * user=${%USER} => get environment variable.
79 * host=${!/bin/hostname -s} => run external command and put it's output.
80 * id=${user}@${host}
81 *
82 * # now entering into 'system' section.
83 * [system] => a key 'system.' with value 'system' will be inserted.
84 * ostype=${%OSTYPE} => 'system.ostype' is the key for this entry.
85 * machtype=${%MACHTYPE} => 'system.machtype' is the key for this entry.
86 *
87 * # entering into 'daemon' section.
88 * [daemon]
89 * port=1234
90 * name=${user}_${host}_${system.ostype}_${system.machtype}
91 *
92 * # escape section. (go back to root)
93 * []
94 * rev=822
95 * @endcode
96 *
97 * @code
98 * # This is "config.def" file.
99 * prefix = /usr/local
100 * bin = ${prefix}/bin
101 * log = ${prefix}/log
102 * user = unknown
103 * host = unknown
104 * @endcode
105 *
106 * @code
107 * qlisttbl_t *tbl = qconfig_parse_file(NULL, "config.conf", '=', true);
108 * tbl->debug(tbl, stdout);
109 *
110 * [Output]
111 * bin=/usr/local/bin? (15)
112 * prefix=/tmp? (5)
113 * log=/tmp/log? (9)
114 * user=seungyoung.kim? (9)
115 * host=eng22? (6)
116 * id=seungyoung.kim@eng22? (15)
117 * system.=system? (7)
118 * system.ostype=linux? (6)
119 * system.machtype=x86_64? (7)
120 * daemon.=daemon? (7)
121 * daemon.port=1234? (5)
122 * daemon.name=seungyoung.kim_eng22_linux_x86_64? (28)
123 * rev=822? (4)
124 * @endcode
125 */
126qlisttbl_t *qconfig_parse_file(qlisttbl_t *tbl, const char *filepath,
127 char sepchar) {
128 char *str = qfile_load(filepath, NULL);
129 if (str == NULL)
130 return NULL;
131
132 // process include directive
133 char *strp = str;
134
135 while ((strp = strstr(strp, _INCLUDE_DIRECTIVE)) != NULL) {
136 if (strp == str || strp[-1] == '\n') {
137 char buf[PATH_MAX];
138
139 // parse filename
140 char *tmpp;
141 for (tmpp = strp + CONST_STRLEN(_INCLUDE_DIRECTIVE);
142 *tmpp != '\n' && *tmpp != '\0'; tmpp++)
143 ;
144 int len = tmpp - (strp + CONST_STRLEN(_INCLUDE_DIRECTIVE));
145 if (len >= sizeof(buf)) {
146 DEBUG("Can't process %s directive.", _INCLUDE_DIRECTIVE);
147 free(str);
148 return NULL;
149 }
150
151 strncpy(buf, strp + CONST_STRLEN(_INCLUDE_DIRECTIVE), len);
152 buf[len] = '\0';
153 qstrtrim(buf);
154
155 // get full file path
156 if (!(buf[0] == '/' || buf[0] == '\\')) {
157 char tmp[PATH_MAX];
158 char *dir = qfile_get_dir(filepath);
159 if (strlen(dir) + 1 + strlen(buf) >= sizeof(buf)) {
160 DEBUG("Can't process %s directive.", _INCLUDE_DIRECTIVE);
161 free(dir);
162 free(str);
163 return NULL;
164 }
165 snprintf(tmp, sizeof(tmp), "%s/%s", dir, buf);
166 free(dir);
167
168 strcpy(buf, tmp);
169 }
170
171 // read file
172 char *incdata;
173 if (strlen(buf) == 0 || (incdata = qfile_load(buf, NULL)) == NULL) {
174 DEBUG("Can't process '%s%s' directive.", _INCLUDE_DIRECTIVE,
175 buf);
176 free(str);
177 return NULL;
178 }
179
180 // replace
181 strncpy(buf, strp, CONST_STRLEN(_INCLUDE_DIRECTIVE) + len);
182 buf[CONST_STRLEN(_INCLUDE_DIRECTIVE) + len] = '\0';
183 strp = qstrreplace("sn", str, buf, incdata);
184 free(incdata);
185 free(str);
186 str = strp;
187 } else {
188 strp += CONST_STRLEN(_INCLUDE_DIRECTIVE);
189 }
190 }
191
192 // parse
193 tbl = qconfig_parse_str(tbl, str, sepchar);
194 free(str);
195
196 return tbl;
197}
198
199/**
200 * Parse string
201 *
202 * @param tbl a pointer of qlisttbl_t. NULL will generate a new table.
203 * @param str key, value pair strings
204 * @param sepchar separater used in configuration file to divice key and value
205 *
206 * @return a pointer of qlisttbl_t in case of successful,
207 * otherwise(file not found) returns NULL
208 *
209 * @see qconfig_parse_file
210 *
211 * @code
212 * qlisttbl_t *tbl;
213 * tbl = qconfig_parse_str(NULL, "key = value\nhello = world", '=');
214 * @endcode
215 */
216qlisttbl_t *qconfig_parse_str(qlisttbl_t *tbl, const char *str, char sepchar) {
217 if (str == NULL)
218 return NULL;
219
220 if (tbl == NULL) {
221 tbl = qlisttbl(0);
222 if (tbl == NULL)
223 return NULL;
224 }
225
226 char *section = NULL;
227 char *org, *buf, *offset;
228 for (org = buf = offset = strdup(str); *offset != '\0';) {
229 // get one line into buf
230 for (buf = offset; *offset != '\n' && *offset != '\0'; offset++)
231 ;
232 if (*offset != '\0') {
233 *offset = '\0';
234 offset++;
235 }
236 qstrtrim(buf);
237
238 // skip blank or comment line
239 if ((buf[0] == '#') || (buf[0] == '\0'))
240 continue;
241
242 // section header
243 if ((buf[0] == '[') && (buf[strlen(buf) - 1] == ']')) {
244 // extract section name
245 if (section != NULL)
246 free(section);
247 section = strdup(buf + 1);
248 section[strlen(section) - 1] = '\0';
249 qstrtrim(section);
250
251 // remove section if section name is empty. ex) []
252 if (section[0] == '\0') {
253 free(section);
254 section = NULL;
255 continue;
256 }
257
258 // in order to put 'section.=section'
259 sprintf(buf, "%c%s", sepchar, section);
260 }
261
262 // parse & store
263 char *value = strdup(buf);
264 char *name = _q_makeword(value, sepchar);
265 qstrtrim(value);
266 qstrtrim(name);
267
268 // put section name as a prefix
269 if (section != NULL) {
270 char *newname = qstrdupf("%s.%s", section, name);
271 free(name);
272 name = newname;
273 }
274
275 // get parsed string
276 char *newvalue = _parsestr(tbl, value);
277 if (newvalue != NULL) {
278 tbl->putstr(tbl, name, newvalue);
279 free(newvalue);
280 }
281
282 free(name);
283 free(value);
284 }
285 free(org);
286 if (section != NULL)
287 free(section);
288
289 return tbl;
290}
291
292#ifndef _DOXYGEN_SKIP
293
294/**
295 * (qlisttbl_t*)->parsestr(): Parse a string and replace variables in the
296 * string to the data in this list.
297 *
298 * @param tbl qlisttbl container pointer.
299 * @param str string value which may contain variables like ${...}
300 *
301 * @return malloced string if successful, otherwise returns NULL.
302 * @retval errno will be set in error condition.
303 * - EINVAL : Invalid argument.
304 *
305 * @code
306 * ${key_name} - replace this with a matched value data in this list.
307 * ${!system_command} - run external command and put it's output here.
308 * ${%PATH} - get environment variable.
309 * @endcode
310 *
311 * @code
312 * --[tbl Table]------------------------
313 * NAME = qLibc
314 * -------------------------------------
315 *
316 * char *str = _parsestr(tbl, "${NAME}, ${%HOME}, ${!date -u}");
317 * if(str != NULL) {
318 * printf("%s\n", str);
319 * free(str);
320 * }
321 *
322 * [Output]
323 * qLibc, /home/qlibc, Wed Nov 24 00:30:58 UTC 2010
324 * @endcode
325 */
326static char *_parsestr(qlisttbl_t *tbl, const char *str) {
327 if (str == NULL) {
328 errno = EINVAL;
329 return NULL;
330 }
331
332 bool loop;
333 char *value = strdup(str);
334 do {
335 loop = false;
336
337 // find ${
338 char *s, *e;
339 int openedbrakets;
340 for (s = value; *s != '\0'; s++) {
341 if (!(*s == _VAR && *(s + 1) == _VAR_OPEN))
342 continue;
343
344 // found ${, try to find }. s points $
345 openedbrakets = 1; // braket open counter
346 for (e = s + 2; *e != '\0'; e++) {
347 if (*e == _VAR && *(e + 1) == _VAR_OPEN) { // found internal ${
348 // e is always bigger than s, negative overflow never occure
349 s = e - 1;
350 break;
351 } else if (*e == _VAR_OPEN)
352 openedbrakets++;
353 else if (*e == _VAR_CLOSE)
354 openedbrakets--;
355 else
356 continue;
357
358 if (openedbrakets == 0)
359 break;
360 }
361 if (*e == '\0')
362 break; // braket mismatch
363 if (openedbrakets > 0)
364 continue; // found internal ${
365
366 // pick string between ${, }
367 int varlen = e - s - 2; // length between ${ , }
368 char *varstr = (char *) malloc(varlen + 3 + 1);
369 if (varstr == NULL)
370 continue;
371 strncpy(varstr, s + 2, varlen);
372 varstr[varlen] = '\0';
373
374 // get the new string to replace
375 char *newstr = NULL;
376 switch (varstr[0]) {
377 case _VAR_CMD: {
378 if (varlen - 1 == 0) {
379 newstr = strdup("");
380 break;
381 }
382 if ((newstr = qstrtrim(qsyscmd(varstr + 1))) == NULL) {
383 newstr = strdup("");
384 }
385 break;
386 }
387 case _VAR_ENV: {
388 if (varlen - 1 == 0) {
389 newstr = strdup("");
390 break;
391 }
392 newstr = strdup(qgetenv(varstr + 1, ""));
393 break;
394 }
395 default: {
396 if (varlen == 0) {
397 newstr = strdup("");
398 break;
399 }
400 if ((newstr = tbl->getstr(tbl, varstr, true)) == NULL) {
401 s = e; // not found
402 continue;
403 }
404 break;
405 }
406 }
407
408 // replace
409 strncpy(varstr, s, varlen + 3); // ${str}
410 varstr[varlen + 3] = '\0';
411
412 s = qstrreplace("sn", value, varstr, newstr);
413 free(newstr);
414 free(varstr);
415 free(value);
416 value = s;
417
418 loop = true;
419 break;
420 }
421 } while (loop == true);
422
423 return value;
424}
425
426#endif /* _DOXYGEN_SKIP */
427
428#endif /* DISABLE_QCONFIG */
429
qlisttbl_t * qconfig_parse_file(qlisttbl_t *tbl, const char *filepath, char sepchar)
Load & parse configuration file.
Definition qconfig.c:126
qlisttbl_t * qconfig_parse_str(qlisttbl_t *tbl, const char *str, char sepchar)
Parse string.
Definition qconfig.c:216
void * qfile_load(const char *filepath, size_t *nbytes)
Load file into memory.
Definition qfile.c:159
char * qfile_get_dir(const char *filepath)
Get directory suffix from filepath.
Definition qfile.c:371
qlisttbl_t * qlisttbl(int options)
Create a new Q_LIST linked-list container.
Definition qlisttbl.c:150
char * qstrtrim(char *str)
Remove white spaces(including CR, LF) from head and tail of the string.
Definition qstring.c:55
char * qstrdupf(const char *format,...)
Duplicate a formatted string.
Definition qstring.c:363
char * qstrreplace(const char *mode, char *srcstr, const char *tokstr, const char *word)
Replace string or tokens as word from source string with given mode.
Definition qstring.c:236
const char * qgetenv(const char *envname, const char *defstr)
Get system environment variable.
Definition qsystem.c:54
char * qsyscmd(const char *cmd)
Get the result string of external command execution.
Definition qsystem.c:71