ciniparser.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. /* Copyright (c) 2000-2007 by Nicolas Devillard.
  2. * Copyright (x) 2009 by Tim Post <tinkertim@gmail.com>
  3. * MIT License
  4. *
  5. * Permission is hereby granted, free of charge, to any person obtaining a
  6. * copy of this software and associated documentation files (the "Software"),
  7. * to deal in the Software without restriction, including without limitation
  8. * the rights to use, copy, modify, merge, publish, distribute, sublicense,
  9. * and/or sell copies of the Software, and to permit persons to whom the
  10. * Software is furnished to do so, subject to the following conditions:
  11. *
  12. * The above copyright notice and this permission notice shall be included in
  13. * all copies or substantial portions of the Software.
  14. *
  15. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  20. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  21. * DEALINGS IN THE SOFTWARE.
  22. */
  23. /** @addtogroup ciniparser
  24. * @{
  25. */
  26. /**
  27. * @file ciniparser.c
  28. * @author N. Devillard
  29. * @date Sep 2007
  30. * @version 3.0
  31. * @brief Parser for ini files.
  32. */
  33. #include <ctype.h>
  34. #include <ccan/ciniparser/ciniparser.h>
  35. #define ASCIILINESZ (1024)
  36. #define INI_INVALID_KEY ((char*) NULL)
  37. /**
  38. * This enum stores the status for each parsed line (internal use only).
  39. */
  40. typedef enum _line_status_ {
  41. LINE_UNPROCESSED,
  42. LINE_ERROR,
  43. LINE_EMPTY,
  44. LINE_COMMENT,
  45. LINE_SECTION,
  46. LINE_VALUE
  47. } line_status;
  48. /**
  49. * @brief Convert a string to lowercase.
  50. * @param s String to convert.
  51. * @return ptr to statically allocated string.
  52. *
  53. * This function returns a pointer to a statically allocated string
  54. * containing a lowercased version of the input string. Do not free
  55. * or modify the returned string! Since the returned string is statically
  56. * allocated, it will be modified at each function call (not re-entrant).
  57. */
  58. static char *strlwc(const char *s)
  59. {
  60. static char l[ASCIILINESZ+1];
  61. int i;
  62. if (s == NULL)
  63. return NULL;
  64. for (i = 0; s[i] && i < ASCIILINESZ; i++)
  65. l[i] = tolower(s[i]);
  66. l[i] = '\0';
  67. return l;
  68. }
  69. /**
  70. * @brief Remove blanks at the beginning and the end of a string.
  71. * @param s String to parse.
  72. * @return ptr to statically allocated string.
  73. *
  74. * This function returns a pointer to a statically allocated string,
  75. * which is identical to the input string, except that all blank
  76. * characters at the end and the beg. of the string have been removed.
  77. * Do not free or modify the returned string! Since the returned string
  78. * is statically allocated, it will be modified at each function call
  79. * (not re-entrant).
  80. */
  81. static char *strstrip(const char *s)
  82. {
  83. static char l[ASCIILINESZ+1];
  84. unsigned int i, numspc;
  85. if (s == NULL)
  86. return NULL;
  87. while (isspace(*s))
  88. s++;
  89. for (i = numspc = 0; s[i] && i < ASCIILINESZ; i++) {
  90. l[i] = s[i];
  91. if (isspace(l[i]))
  92. numspc++;
  93. else
  94. numspc = 0;
  95. }
  96. l[i - numspc] = '\0';
  97. return l;
  98. }
  99. /**
  100. * @brief Load a single line from an INI file
  101. * @param input_line Input line, may be concatenated multi-line input
  102. * @param section Output space to store section
  103. * @param key Output space to store key
  104. * @param value Output space to store value
  105. * @return line_status value
  106. */
  107. static
  108. line_status ciniparser_line(char *input_line, char *section,
  109. char *key, char *value)
  110. {
  111. line_status sta;
  112. char line[ASCIILINESZ+1];
  113. int len;
  114. strcpy(line, strstrip(input_line));
  115. len = (int) strlen(line);
  116. if (len < 1) {
  117. /* Empty line */
  118. sta = LINE_EMPTY;
  119. } else if (line[0] == '#') {
  120. /* Comment line */
  121. sta = LINE_COMMENT;
  122. } else if (line[0] == '[' && line[len-1] == ']') {
  123. /* Section name */
  124. sscanf(line, "[%[^]]", section);
  125. strcpy(section, strstrip(section));
  126. strcpy(section, strlwc(section));
  127. sta = LINE_SECTION;
  128. } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2
  129. || sscanf (line, "%[^=] = '%[^\']'", key, value) == 2
  130. || sscanf (line, "%[^=] = %[^;#]", key, value) == 2) {
  131. /* Usual key=value, with or without comments */
  132. strcpy(key, strstrip(key));
  133. strcpy(key, strlwc(key));
  134. strcpy(value, strstrip(value));
  135. /*
  136. * sscanf cannot handle '' or "" as empty values
  137. * this is done here
  138. */
  139. if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) {
  140. value[0] = 0;
  141. }
  142. sta = LINE_VALUE;
  143. } else if (sscanf(line, "%[^=] = %[;#]", key, value) == 2
  144. || sscanf(line, "%[^=] %[=]", key, value) == 2) {
  145. /*
  146. * Special cases:
  147. * key=
  148. * key=;
  149. * key=#
  150. */
  151. strcpy(key, strstrip(key));
  152. strcpy(key, strlwc(key));
  153. value[0] = 0;
  154. sta = LINE_VALUE;
  155. } else {
  156. /* Generate syntax error */
  157. sta = LINE_ERROR;
  158. }
  159. return sta;
  160. }
  161. /* The remaining public functions are documented in ciniparser.h */
  162. int ciniparser_getnsec(dictionary *d)
  163. {
  164. int i;
  165. int nsec;
  166. if (d == NULL)
  167. return -1;
  168. nsec = 0;
  169. for (i = 0; i < d->size; i++) {
  170. if (d->key[i] == NULL)
  171. continue;
  172. if (strchr(d->key[i], ':') == NULL) {
  173. nsec ++;
  174. }
  175. }
  176. return nsec;
  177. }
  178. char *ciniparser_getsecname(dictionary *d, int n)
  179. {
  180. int i;
  181. int foundsec;
  182. if (d == NULL || n < 0)
  183. return NULL;
  184. if (n == 0)
  185. n ++;
  186. foundsec = 0;
  187. for (i = 0; i < d->size; i++) {
  188. if (d->key[i] == NULL)
  189. continue;
  190. if (! strchr(d->key[i], ':')) {
  191. foundsec++;
  192. if (foundsec >= n)
  193. break;
  194. }
  195. }
  196. if (foundsec == n) {
  197. return d->key[i];
  198. }
  199. return (char *) NULL;
  200. }
  201. void ciniparser_dump(dictionary *d, FILE *f)
  202. {
  203. int i;
  204. if (d == NULL || f == NULL)
  205. return;
  206. for (i = 0; i < d->size; i++) {
  207. if (d->key[i] == NULL)
  208. continue;
  209. if (d->val[i] != NULL) {
  210. fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]);
  211. } else {
  212. fprintf(f, "[%s]=UNDEF\n", d->key[i]);
  213. }
  214. }
  215. return;
  216. }
  217. void ciniparser_dump_ini(dictionary *d, FILE *f)
  218. {
  219. int i, j;
  220. char keym[ASCIILINESZ+1];
  221. int nsec;
  222. char *secname;
  223. int seclen;
  224. if (d == NULL || f == NULL)
  225. return;
  226. memset(keym, 0, ASCIILINESZ + 1);
  227. nsec = ciniparser_getnsec(d);
  228. if (nsec < 1) {
  229. /* No section in file: dump all keys as they are */
  230. for (i = 0; i < d->size; i++) {
  231. if (d->key[i] == NULL)
  232. continue;
  233. fprintf(f, "%s = %s\n", d->key[i], d->val[i]);
  234. }
  235. return;
  236. }
  237. for (i = 0; i < nsec; i++) {
  238. secname = ciniparser_getsecname(d, i);
  239. seclen = (int)strlen(secname);
  240. fprintf(f, "\n[%s]\n", secname);
  241. snprintf(keym, ASCIILINESZ + 1, "%s:", secname);
  242. for (j = 0; j < d->size; j++) {
  243. if (d->key[j] == NULL)
  244. continue;
  245. if (!strncmp(d->key[j], keym, seclen+1)) {
  246. fprintf(f, "%-30s = %s\n",
  247. d->key[j]+seclen+1,
  248. d->val[j] ? d->val[j] : "");
  249. }
  250. }
  251. }
  252. fprintf(f, "\n");
  253. return;
  254. }
  255. char *ciniparser_getstring(dictionary *d, const char *key, char *def)
  256. {
  257. char *lc_key;
  258. char *sval;
  259. if (d == NULL || key == NULL)
  260. return def;
  261. lc_key = strlwc(key);
  262. sval = dictionary_get(d, lc_key, def);
  263. return sval;
  264. }
  265. int ciniparser_getint(dictionary *d, const char *key, int notfound)
  266. {
  267. char *str;
  268. str = ciniparser_getstring(d, key, INI_INVALID_KEY);
  269. if (str == INI_INVALID_KEY)
  270. return notfound;
  271. return (int) strtol(str, NULL, 10);
  272. }
  273. double ciniparser_getdouble(dictionary *d, char *key, double notfound)
  274. {
  275. char *str;
  276. str = ciniparser_getstring(d, key, INI_INVALID_KEY);
  277. if (str == INI_INVALID_KEY)
  278. return notfound;
  279. return atof(str);
  280. }
  281. int ciniparser_getboolean(dictionary *d, const char *key, int notfound)
  282. {
  283. char *c;
  284. int ret;
  285. c = ciniparser_getstring(d, key, INI_INVALID_KEY);
  286. if (c == INI_INVALID_KEY)
  287. return notfound;
  288. switch(c[0]) {
  289. case 'y': case 'Y': case '1': case 't': case 'T':
  290. ret = 1;
  291. break;
  292. case 'n': case 'N': case '0': case 'f': case 'F':
  293. ret = 0;
  294. break;
  295. default:
  296. ret = notfound;
  297. break;
  298. }
  299. return ret;
  300. }
  301. int ciniparser_find_entry(dictionary *ini, char *entry)
  302. {
  303. int found = 0;
  304. if (ciniparser_getstring(ini, entry, INI_INVALID_KEY) != INI_INVALID_KEY) {
  305. found = 1;
  306. }
  307. return found;
  308. }
  309. int ciniparser_set(dictionary *d, char *entry, char *val)
  310. {
  311. return dictionary_set(d, strlwc(entry), val);
  312. }
  313. void ciniparser_unset(dictionary *ini, char *entry)
  314. {
  315. dictionary_unset(ini, strlwc(entry));
  316. }
  317. dictionary *ciniparser_load(const char *ininame)
  318. {
  319. FILE *in;
  320. char line[ASCIILINESZ+1];
  321. char section[ASCIILINESZ+1];
  322. char key[ASCIILINESZ+1];
  323. char tmp[ASCIILINESZ+1];
  324. char val[ASCIILINESZ+1];
  325. int last = 0, len, lineno = 0, errs = 0;
  326. dictionary *dict;
  327. if ((in = fopen(ininame, "r")) == NULL) {
  328. fprintf(stderr, "ciniparser: cannot open %s\n", ininame);
  329. return NULL;
  330. }
  331. dict = dictionary_new(0);
  332. if (!dict) {
  333. fclose(in);
  334. return NULL;
  335. }
  336. memset(line, 0, ASCIILINESZ + 1);
  337. memset(section, 0, ASCIILINESZ + 1);
  338. memset(key, 0, ASCIILINESZ + 1);
  339. memset(val, 0, ASCIILINESZ + 1);
  340. last = 0;
  341. while (fgets(line+last, ASCIILINESZ-last, in)!=NULL) {
  342. lineno++;
  343. len = (int) strlen(line)-1;
  344. /* Safety check against buffer overflows */
  345. if (line[len] != '\n') {
  346. fprintf(stderr,
  347. "ciniparser: input line too long in %s (%d)\n",
  348. ininame,
  349. lineno);
  350. dictionary_del(dict);
  351. fclose(in);
  352. return NULL;
  353. }
  354. /* Get rid of \n and spaces at end of line */
  355. while ((len >= 0) &&
  356. ((line[len] == '\n') || (isspace(line[len])))) {
  357. line[len] = 0;
  358. len--;
  359. }
  360. /* Detect multi-line */
  361. if (len >= 0 && line[len] == '\\') {
  362. /* Multi-line value */
  363. last = len;
  364. continue;
  365. }
  366. switch (ciniparser_line(line, section, key, val)) {
  367. case LINE_EMPTY:
  368. case LINE_COMMENT:
  369. break;
  370. case LINE_SECTION:
  371. errs = dictionary_set(dict, section, NULL);
  372. break;
  373. case LINE_VALUE:
  374. snprintf(tmp, ASCIILINESZ + 1, "%s:%s", section, key);
  375. errs = dictionary_set(dict, tmp, val);
  376. break;
  377. case LINE_ERROR:
  378. fprintf(stderr, "ciniparser: syntax error in %s (%d):\n",
  379. ininame, lineno);
  380. fprintf(stderr, "-> %s\n", line);
  381. errs++;
  382. break;
  383. default:
  384. break;
  385. }
  386. memset(line, 0, ASCIILINESZ);
  387. last = 0;
  388. if (errs < 0) {
  389. fprintf(stderr, "ciniparser: memory allocation failure\n");
  390. break;
  391. }
  392. }
  393. if (errs) {
  394. dictionary_del(dict);
  395. dict = NULL;
  396. }
  397. fclose(in);
  398. return dict;
  399. }
  400. void ciniparser_freedict(dictionary *d)
  401. {
  402. dictionary_del(d);
  403. }
  404. /** @}
  405. */