Lely core libraries  2.3.4
config_ini.c
Go to the documentation of this file.
1 
24 #include "util.h"
25 
26 #if !LELY_NO_STDIO
27 
28 #include <lely/util/config.h>
29 #include <lely/util/diag.h>
30 #include <lely/util/frbuf.h>
31 #include <lely/util/fwbuf.h>
32 #include <lely/util/lex.h>
33 #include <lely/util/membuf.h>
34 #include <lely/util/print.h>
35 
36 #include <assert.h>
37 
38 static int issection(int c);
39 static int iskey(int c);
40 static int isvalue(int c);
41 
42 static size_t skip(const char *begin, const char *end, struct floc *at);
43 
44 static void membuf_print_chars(struct membuf *buf, const char *s, size_t n);
45 
46 static void config_print_ini_func(const char *section, const char *key,
47  const char *value, void *data);
48 
49 size_t
50 config_parse_ini_file(config_t *config, const char *filename)
51 {
52  frbuf_t *buf = frbuf_create(filename);
53  if (!buf) {
54  diag(DIAG_ERROR, get_errc(), "%s", filename);
55  return 0;
56  }
57 
58  size_t size = 0;
59  const void *map = frbuf_map(buf, 0, &size);
60  if (!map) {
61  diag(DIAG_ERROR, get_errc(), "%s: unable to map file",
62  filename);
63  frbuf_destroy(buf);
64  return 0;
65  }
66 
67  const char *begin = map;
68  const char *end = begin + size;
69  struct floc at = { filename, 1, 1 };
70  size_t chars = config_parse_ini_text(config, begin, end, &at);
71 
72  frbuf_destroy(buf);
73 
74  return chars;
75 }
76 
77 size_t
78 config_parse_ini_text(config_t *config, const char *begin, const char *end,
79  struct floc *at)
80 {
81  assert(config);
82  assert(begin);
83 
84  struct membuf section = MEMBUF_INIT;
85  struct membuf key = MEMBUF_INIT;
86  struct membuf value = MEMBUF_INIT;
87 
88  const char *cp = begin;
89  size_t chars = 0;
90 
91  for (;;) {
92  // Skip comments and empty lines.
93  for (;;) {
94  cp += skip(cp, end, at);
95  if ((chars = lex_break(cp, end, at)) > 0)
96  cp += chars;
97  else
98  break;
99  }
100  if ((end && cp >= end) || !*cp)
101  break;
102 
103  if ((chars = lex_char('[', cp, end, at)) > 0) {
104  cp += chars;
105  cp += skip(cp, end, at);
106  if ((chars = lex_ctype(&issection, cp, end, at)) > 0) {
107  membuf_print_chars(&section, cp, chars);
108  cp += chars;
109  cp += skip(cp, end, at);
110  if ((chars = lex_char(']', cp, end, at)) > 0)
111  cp += chars;
112  else
113  diag_if(DIAG_ERROR, 0, at,
114  "expected ']' after section name");
115  } else {
116  diag_if(DIAG_ERROR, 0, at,
117  "expected section name after '['");
118  }
119  cp += lex_line_comment(NULL, cp, end, at);
120  } else if ((chars = lex_ctype(&iskey, cp, end, at)) > 0) {
121  membuf_print_chars(&key, cp, chars);
122  cp += chars;
123  cp += skip(cp, end, at);
124  if ((chars = lex_char('=', cp, end, at)) > 0) {
125  cp += chars;
126  cp += skip(cp, end, at);
127  if ((chars = lex_char('\"', cp, end, at)) > 0) {
128  cp += chars;
129  lex_c99_str(cp, end, at, NULL, &chars);
130  chars++;
131  membuf_clear(&value);
132  membuf_reserve(&value, chars);
133  char *s = membuf_alloc(&value, &chars);
134  if (s && chars)
135  s[--chars] = '\0';
136  cp += lex_c99_str(cp, end, NULL, s,
137  &chars);
138  // clang-format off
139  if ((chars = lex_char('\"', cp, end,
140  at)) > 0)
141  // clang-format on
142  cp += chars;
143  else
144  diag_if(DIAG_ERROR, 0, at,
145  "expected '\"' after string");
146  } else {
147  chars = lex_ctype(
148  &isvalue, cp, end, at);
149  membuf_print_chars(&value, cp, chars);
150  cp += chars;
151  }
152  config_set(config, membuf_begin(&section),
153  membuf_begin(&key),
154  membuf_begin(&value));
155  membuf_clear(&value);
156  } else {
157  diag_if(DIAG_ERROR, 0, at,
158  "expected '=' after key");
159  }
160  cp += lex_line_comment(NULL, cp, end, at);
161  } else {
162  if (isgraph((unsigned char)*cp))
163  diag_if(DIAG_ERROR, 0, at,
164  "unknown character '%c'", *cp);
165  else
166  diag_if(DIAG_ERROR, 0, at,
167  "unknown character '\\%o'",
168  *cp);
169  // Skip the offending character.
170  cp += lex_char(*cp, cp, end, at);
171  }
172  }
173 
174  membuf_fini(&value);
175  membuf_fini(&key);
176  membuf_fini(&section);
177 
178  return cp - begin;
179 }
180 
181 size_t
182 config_print_ini_file(const config_t *config, const char *filename)
183 {
184  fwbuf_t *buf = fwbuf_create(filename);
185  if (!buf) {
186  diag(DIAG_ERROR, get_errc(), "%s", filename);
187  return 0;
188  }
189 
190  size_t size = config_print_ini_text(config, NULL, NULL);
191  void *map = fwbuf_map(buf, 0, &size);
192  if (!map) {
193  diag(DIAG_ERROR, get_errc(), "%s: unable to map file",
194  filename);
195  fwbuf_destroy(buf);
196  return 0;
197  }
198 
199  char *begin = map;
200  char *end = begin + size;
201  size_t chars = config_print_ini_text(config, &begin, end);
202 
203  if (fwbuf_commit(buf) == -1) {
204  diag(DIAG_ERROR, get_errc(), "%s: unable to commit file",
205  filename);
206  fwbuf_destroy(buf);
207  return 0;
208  }
209 
210  fwbuf_destroy(buf);
211 
212  return chars;
213 }
214 
215 size_t
216 config_print_ini_text(const config_t *config, char **pbegin, char *end)
217 {
218  assert(config);
219 
220  struct {
221  char **pbegin;
222  char *end;
223  const char *section;
224  size_t chars;
225  } ctx = { .pbegin = pbegin, .end = end, .section = NULL, .chars = 0 };
226 
227  config_foreach(config, &config_print_ini_func, &ctx);
228 
229  return ctx.chars;
230 }
231 
232 static int
233 issection(int c)
234 {
235  return isgraph(c) && c != '#' && c != ';' && c != '[' && c != ']';
236 }
237 
238 static int
239 iskey(int c)
240 {
241  return isgraph(c) && c != '#' && c != ';' && c != '=';
242 }
243 
244 static int
245 isvalue(int c)
246 {
247  return isprint(c) && c != '#' && c != ';';
248 }
249 
250 static size_t
251 skip(const char *begin, const char *end, struct floc *at)
252 {
253  assert(begin);
254 
255  const char *cp = begin;
256 
257  cp += lex_ctype(&isblank, cp, end, at);
258  cp += lex_line_comment("#", cp, end, at);
259  cp += lex_line_comment(";", cp, end, at);
260 
261  return cp - begin;
262 }
263 
264 static void
265 membuf_print_chars(struct membuf *buf, const char *s, size_t n)
266 {
267  assert(buf);
268 
269  // Remove trailing whitespace.
270  while (n && isspace((unsigned char)s[n - 1]))
271  n--;
272 
273  membuf_clear(buf);
274  if (!membuf_reserve(buf, n + 1))
275  return;
276  membuf_write(buf, s, n);
277  membuf_write(buf, "", 1);
278 }
279 
280 static void
281 config_print_ini_func(const char *section, const char *key, const char *value,
282  void *data)
283 {
284  assert(section);
285  assert(key);
286  assert(value);
287  struct {
288  char **pbegin;
289  char *end;
290  const char *section;
291  size_t chars;
292  } *ctx = data;
293  assert(ctx);
294  char **pbegin = ctx->pbegin;
295  char *end = ctx->end;
296  size_t chars = ctx->chars;
297 
298  if (ctx->section != section) {
299  ctx->section = section;
300  // Prepend the section with a newline, if necessary.
301  if (chars)
302  chars += print_char(pbegin, end, '\n');
303  if (*section) {
304  chars += print_char(pbegin, end, '[');
305  while (*section)
306  chars += print_char(pbegin, end, *section++);
307  chars += print_char(pbegin, end, ']');
308  chars += print_char(pbegin, end, '\n');
309  }
310  }
311 
312  while (*key)
313  chars += print_char(pbegin, end, *key++);
314 
315  chars += print_char(pbegin, end, ' ');
316  chars += print_char(pbegin, end, '=');
317 
318  if (*value) {
319  chars += print_char(pbegin, end, ' ');
320  size_t n = strlen(value);
321  // Check for leading or trailing whitespace.
322  int esc = isspace((unsigned char)value[0])
323  || isspace((unsigned char)value[n - 1]);
324  // Check for non-printable ASCII characters or comments.
325  for (size_t i = 0; !esc && i < n; i++)
326  esc = !isvalue((unsigned char)value[i]);
327  if (esc) {
328  chars += print_char(pbegin, end, '"');
329  chars += print_c99_str(pbegin, end, value, n);
330  chars += print_char(pbegin, end, '"');
331  } else {
332  while (*value)
333  chars += print_char(pbegin, end, *value++);
334  }
335  }
336 
337  chars += print_char(pbegin, end, '\n');
338 
339  ctx->chars = chars;
340 }
341 
342 #endif // !LELY_NO_STDIO
This header file is part of the utilities library; it contains the configuration functions.
void config_foreach(const config_t *config, config_foreach_func_t *func, void *data)
Invokes a function for each key in a configuration struct.
Definition: config.c:238
const char * config_set(config_t *config, const char *section, const char *key, const char *value)
Sets a key in or removes a key from a configuration struct.
Definition: config.c:218
size_t config_parse_ini_file(config_t *config, const char *filename)
Parses an INI file and adds the keys to a configuration struct.
Definition: config_ini.c:50
size_t config_print_ini_file(const config_t *config, const char *filename)
Prints a configuration struct to an INI file.
Definition: config_ini.c:182
size_t config_parse_ini_text(config_t *config, const char *begin, const char *end, struct floc *at)
Parses a string in INI-format and adds the keys to a configuration struct.
Definition: config_ini.c:78
size_t config_print_ini_text(const config_t *config, char **pbegin, char *end)
Prints a configuration struct in INI-format to a memory buffer.
Definition: config_ini.c:216
This header file is part of the utilities library; it contains the diagnostic declarations.
@ DIAG_ERROR
An error.
Definition: diag.h:57
void diag_if(enum diag_severity severity, int errc, const struct floc *at, const char *format,...)
Emits a diagnostic message occurring at a location in a text file.
Definition: diag.c:205
void diag(enum diag_severity severity, int errc, const char *format,...)
Emits a diagnostic message.
Definition: diag.c:171
int get_errc(void)
Returns the last (thread-specific) native error code set by a system call or library function.
Definition: errnum.c:932
This header file is part of the utilities library; it contains the read file buffer declarations.
frbuf_t * frbuf_create(const char *filename)
Creates a new read file buffer.
Definition: frbuf.c:138
void frbuf_destroy(frbuf_t *buf)
Destroys a read file buffer.
Definition: frbuf.c:163
const void * frbuf_map(frbuf_t *buf, intmax_t pos, size_t *psize)
Maps (part of) the contents of a read file buffer to memory.
Definition: frbuf.c:394
This header file is part of the utilities library; it contains the (atomic) write file buffer declara...
void * fwbuf_map(fwbuf_t *buf, intmax_t pos, size_t *psize)
Maps (part of) the contents of a write file buffer to memory.
Definition: fwbuf.c:658
void fwbuf_destroy(fwbuf_t *buf)
Destroys a write file buffer.
Definition: fwbuf.c:330
int fwbuf_commit(fwbuf_t *buf)
Commits all changes to a write file buffer to disk if all previous file operations were successful,...
Definition: fwbuf.c:979
fwbuf_t * fwbuf_create(const char *filename)
Creates a new (atomic) write file buffer.
Definition: fwbuf.c:305
This header file is part of the utilities library; it contains the lexer function declarations.
size_t lex_char(int c, const char *begin, const char *end, struct floc *at)
Lexes the specified character from a memory buffer.
Definition: lex.c:41
size_t lex_ctype(int(*ctype)(int), const char *begin, const char *end, struct floc *at)
Greedily lexes a sequence of characters of the specified class from a memory buffer.
Definition: lex.c:54
size_t lex_line_comment(const char *delim, const char *begin, const char *end, struct floc *at)
Lexes a single line-comment (excluding the line break) starting with the specified delimiter from a m...
Definition: lex.c:633
size_t lex_c99_str(const char *begin, const char *end, struct floc *at, char *s, size_t *pn)
Lexes a UTF-8 encoded Unicode string from a memory buffer.
Definition: lex.c:250
size_t lex_break(const char *begin, const char *end, struct floc *at)
Lexes a single line break from a memory buffer.
Definition: lex.c:69
This header file is part of the utilities library; it contains the memory buffer declarations.
void * membuf_begin(const struct membuf *buf)
Returns a pointer to the first byte in a memory buffer.
Definition: membuf.h:161
size_t membuf_reserve(struct membuf *buf, size_t size)
Resizes a memory buffer, if necessary, to make room for at least an additional size bytes.
Definition: membuf.c:52
void membuf_fini(struct membuf *buf)
Finalizes a memory buffer.
Definition: membuf.c:40
size_t membuf_write(struct membuf *buf, const void *ptr, size_t size)
Writes data to a memory buffer.
Definition: membuf.h:222
void * membuf_alloc(struct membuf *buf, size_t *size)
Creates region of *size bytes in a memory buffer, starting at the current position indicator given by...
Definition: membuf.h:211
void membuf_clear(struct membuf *buf)
Clears a memory buffer.
Definition: membuf.h:169
#define MEMBUF_INIT
The static initializer for struct membuf.
Definition: membuf.h:46
This header file is part of the utilities library; it contains the printing function declarations.
size_t print_c99_str(char **pbegin, char *end, const char *s, size_t n)
Prints a UTF-8 encoded Unicode string to a memory buffer.
Definition: print.c:193
size_t print_char(char **pbegin, char *end, int c)
Prints a single character to a memory buffer.
Definition: print.h:286
This is the internal header file of the utilities library.
A configuration struct.
Definition: config.c:38
An read file buffer struct.
Definition: frbuf.c:52
An (atomic) write file buffer struct.
Definition: fwbuf.c:59
A location in a text file.
Definition: diag.h:39
const char * filename
The name of the file.
Definition: diag.h:41
A memory buffer.
Definition: membuf.h:36
char * end
A pointer to one past the last byte in the buffer.
Definition: membuf.h:42
char * begin
A pointer to the first byte in the buffer.
Definition: membuf.h:40