1 /*
2 * libini - Library to read INI configuration files
3 *
4 * Copyright (C) 2014 Paul Cercueil <paul@crapouillou.net>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 */
16
17 #include <errno.h>
18 #include <stdbool.h>
19 #include <stdint.h>
20 #include <stdio.h>
21
22 #include "ini.h"
23
24 struct INI {
25 const char *buf, *end, *curr;
26 bool free_buf_on_exit;
27 };
28
_ini_open_mem(const char * buf,size_t len,bool free_buf_on_exit)29 static struct INI *_ini_open_mem(const char *buf,
30 size_t len, bool free_buf_on_exit)
31 {
32 struct INI *ini = malloc(sizeof(*ini));
33 if (!ini) {
34 errno = ENOMEM;
35 return NULL;
36 }
37
38 ini->buf = ini->curr = buf;
39 ini->end = buf + len;
40 ini->free_buf_on_exit = free_buf_on_exit;
41 return ini;
42 }
43
ini_open_mem(const char * buf,size_t len)44 struct INI *ini_open_mem(const char *buf, size_t len)
45 {
46 return _ini_open_mem(buf, len, false);
47 }
48
ini_open(const char * file)49 struct INI *ini_open(const char *file)
50 {
51 FILE *f;
52 char *buf, *ptr;
53 size_t len, left;
54 struct INI *ini = NULL;
55 int ret = 0;
56
57 f = fopen(file, "r");
58 if (!f) {
59 ret = -errno;
60 goto err_set_errno;
61 }
62
63 fseek(f, 0, SEEK_END);
64 ret = ftell(f);
65
66 if (ret <= 0) {
67 ret = -EINVAL;
68 goto error_fclose;
69 }
70
71 len = (size_t) ret;
72 buf = malloc(len);
73 if (!buf) {
74 ret = -ENOMEM;
75 goto error_fclose;
76 }
77
78 rewind(f);
79
80 for (left = len, ptr = buf; left; ) {
81 size_t tmp = fread(ptr, 1, left, f);
82 if (tmp == 0) {
83 if (feof(f))
84 break;
85
86 ret = -ferror(f);
87 free(buf);
88 goto error_fclose;
89 }
90
91 left -= tmp;
92 ptr += tmp;
93 }
94
95 ini = _ini_open_mem(buf, len - left, true);
96 if (!ini)
97 ret = -errno;
98
99 error_fclose:
100 fclose(f);
101 err_set_errno:
102 errno = -ret;
103 return ini;
104 }
105
ini_close(struct INI * ini)106 void ini_close(struct INI *ini)
107 {
108 if (ini->free_buf_on_exit)
109 free((char *) ini->buf);
110 free(ini);
111 }
112
skip_comments(struct INI * ini)113 static bool skip_comments(struct INI *ini)
114 {
115 const char *curr = ini->curr;
116 const char *end = ini->end;
117
118 while (curr != end) {
119 if (*curr == '\r' || *curr == '\n')
120 curr++;
121 else if (*curr == '#')
122 do { curr++; } while (curr != end && *curr != '\n');
123 else
124 break;
125 }
126
127 ini->curr = curr;
128 return curr == end;
129 }
130
skip_line(struct INI * ini)131 static bool skip_line(struct INI *ini)
132 {
133 const char *curr = ini->curr;
134 const char *end = ini->end;
135
136 for (; curr != end && *curr != '\n'; curr++);
137 if (curr == end) {
138 ini->curr = end;
139 return true;
140 } else {
141 ini->curr = curr + 1;
142 return false;
143 }
144 }
145
ini_next_section(struct INI * ini,const char ** name,size_t * name_len)146 int ini_next_section(struct INI *ini, const char **name, size_t *name_len)
147 {
148 const char *_name;
149 if (ini->curr == ini->end)
150 return 0; /* EOF: no more sections */
151
152 if (ini->curr == ini->buf) {
153 if (skip_comments(ini) || *ini->curr != '[')
154 return -EIO;
155 } else while (*ini->curr != '[' && !skip_line(ini));
156
157 if (ini->curr == ini->end)
158 return 0; /* EOF: no more sections */
159
160 _name = ++ini->curr;
161 do {
162 ini->curr++;
163 if (ini->curr == ini->end || *ini->curr == '\n')
164 return -EIO;
165 } while (*ini->curr != ']');
166
167
168 if (name && name_len) {
169 *name = _name;
170 *name_len = ini->curr - _name;
171 }
172
173 ini->curr++;
174 return 1;
175 }
176
ini_read_pair(struct INI * ini,const char ** key,size_t * key_len,const char ** value,size_t * value_len)177 int ini_read_pair(struct INI *ini,
178 const char **key, size_t *key_len,
179 const char **value, size_t *value_len)
180 {
181 size_t _key_len = 0;
182 const char *_key, *_value, *curr, *end = ini->end;
183
184 if (skip_comments(ini))
185 return 0;
186 curr = _key = ini->curr;
187
188 if (*curr == '[')
189 return 0;
190
191 while (true) {
192 curr++;
193
194 if (curr == end || *curr == '\n') {
195 return -EIO;
196
197 } else if (*curr == '=') {
198 const char *tmp = curr;
199 _key_len = curr - _key;
200 for (tmp = curr - 1; tmp > ini->curr &&
201 (*tmp == ' ' || *tmp == '\t'); tmp--)
202 _key_len--;
203 curr++;
204 break;
205 }
206 }
207
208 /* Skip whitespaces. */
209 while (curr != end && (*curr == ' ' || *curr == '\t')) curr++;
210 if (curr == end)
211 return -EIO;
212
213 _value = curr;
214
215 while (curr != end && *curr != '\n') curr++;
216 if (curr == end)
217 return -EIO;
218
219 *value = _value;
220 *value_len = curr - _value - (*(curr - 1) == '\r');
221 *key = _key;
222 *key_len = _key_len;
223
224 ini->curr = ++curr;
225 return 1;
226 }
227
ini_set_read_pointer(struct INI * ini,const char * pointer)228 void ini_set_read_pointer(struct INI *ini, const char *pointer)
229 {
230 if ((uintptr_t) pointer < (uintptr_t) ini->buf)
231 ini->curr = ini->buf;
232 else if ((uintptr_t) pointer > (uintptr_t) ini->end)
233 ini->curr = ini->end;
234 else
235 ini->curr = pointer;
236 }
237
ini_get_line_number(struct INI * ini,const char * pointer)238 int ini_get_line_number(struct INI *ini, const char *pointer)
239 {
240 int line = 1;
241 const char *it;
242
243 if ((uintptr_t) pointer < (uintptr_t) ini->buf)
244 return -EINVAL;
245 if ((uintptr_t) pointer > (uintptr_t) ini->end)
246 return -EINVAL;
247
248 for (it = ini->buf; (uintptr_t) it < (uintptr_t) pointer; it++)
249 line += (*it == '\n');
250
251 return line;
252 }
253