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 */
57 static 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  */
126 qlisttbl_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  */
216 qlisttbl_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  */
326 static 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 ((newstr = qstrtrim(qsyscmd(varstr + 1))) == NULL) {
379  newstr = strdup("");
380  }
381  break;
382  }
383  case _VAR_ENV: {
384  newstr = strdup(qgetenv(varstr + 1, ""));
385  break;
386  }
387  default: {
388  if ((newstr = tbl->getstr(tbl, varstr, true)) == NULL) {
389  s = e; // not found
390  continue;
391  }
392  break;
393  }
394  }
395 
396  // replace
397  strncpy(varstr, s, varlen + 3); // ${str}
398  varstr[varlen + 3] = '\0';
399 
400  s = qstrreplace("sn", value, varstr, newstr);
401  free(newstr);
402  free(varstr);
403  free(value);
404  value = s;
405 
406  loop = true;
407  break;
408  }
409  } while (loop == true);
410 
411  return value;
412 }
413 
414 #endif /* _DOXYGEN_SKIP */
415 
416 #endif /* DISABLE_QCONFIG */
417 
qlisttbl_t * qconfig_parse_file(qlisttbl_t *tbl, const char *filepath, char sepchar)
Load & parse configuration file.
Definition: qconfig.c:126
char * qstrdupf(const char *format,...)
Duplicate a formatted string.
Definition: qstring.c:358
qlisttbl_t * qlisttbl(int options)
Create a new Q_LIST linked-list container.
Definition: qlisttbl.c:150
const char * qgetenv(const char *envname, const char *defstr)
Get system environment variable.
Definition: qsystem.c:54
char * qfile_get_dir(const char *filepath)
Get directory suffix from filepath.
Definition: qfile.c:371
char * qstrtrim(char *str)
Remove white spaces(including CR, LF) from head and tail of the string.
Definition: qstring.c:55
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 * 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
char * qsyscmd(const char *cmd)
Get the result string of external command execution.
Definition: qsystem.c:71