Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
n_config_file.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
26#include <stdio.h>
27#include <errno.h>
28
29#include "nilorea/n_common.h"
31#include "nilorea/n_pcre.h"
32
37void free_no_null(void* ptr) {
38 char* strptr = ptr;
39 if (strptr) {
40 free(strptr);
41 }
42 return;
43}
44
51 if (!section)
52 return;
53 Free(section->section_name);
54 destroy_ht(&section->entries);
55 Free(section);
56} /* destroy_config_file_section(...) */
57
64CONFIG_FILE* load_config_file(char* filename, int* errors) {
65 CONFIG_FILE* cfg_file = NULL;
66 CONFIG_FILE_SECTION* section = NULL;
67 char buffer[MAX_CONFIG_LINE_LEN] = "";
68 char** split_result = NULL;
69 N_PCRE* npcre = npcre_new("(.*?=)(.*)", 0);
70 int error = 0;
71 FILE* in = NULL;
72
73 __n_assert(filename, return NULL);
74 int fd = open(filename, O_RDONLY);
75 error = errno;
76
77 if (fd == -1) {
78 n_log(LOG_ERR, "Unable to open %s: %s", _str(filename), strerror(error));
79 (*errors)++;
80 npcre_delete(&npcre);
81 return NULL;
82 }
83
84 in = fdopen(fd, "r");
85 error = errno;
86 if (!in) {
87 n_log(LOG_ERR, "fdopen failed for %s (fd=%d): %s", _str(filename), fd, strerror(errno));
88 close(fd);
89 (*errors)++;
90 npcre_delete(&npcre);
91 return NULL;
92 }
93
94 Malloc(cfg_file, CONFIG_FILE, 1);
95 if (!cfg_file) {
96 fclose(in);
97 npcre_delete(&npcre);
98 return NULL;
99 }
101 if (!cfg_file->sections) {
102 Free(cfg_file);
103 fclose(in);
104 npcre_delete(&npcre);
105 return NULL;
106 }
107 cfg_file->filename = strdup(filename);
108 if (!cfg_file->filename) {
109 n_log(LOG_ERR, "couldn't strdup( %s )", _str(filename));
110 (*errors)++;
111 list_destroy(&cfg_file->sections);
112 Free(cfg_file);
113 fclose(in);
114 npcre_delete(&npcre);
115 return NULL;
116 }
117 /* adding default section */
118 CONFIG_FILE_SECTION* default_section = NULL;
119 Malloc(default_section, CONFIG_FILE_SECTION, 1);
120 if (!default_section) {
121 n_log(LOG_ERR, "couldn't allocate default_section");
122 (*errors)++;
123 list_destroy(&cfg_file->sections);
124 Free(cfg_file->filename);
125 Free(cfg_file);
126 fclose(in);
127 npcre_delete(&npcre);
128 return NULL;
129 }
130
131 default_section->section_name = strdup("__DEFAULT__");
132 if (!default_section->section_name) {
133 n_log(LOG_ERR, "couldn't allocate default_section name");
134 (*errors)++;
135 list_destroy(&cfg_file->sections);
136 Free(cfg_file->filename);
137 Free(default_section);
138 Free(cfg_file);
139 fclose(in);
140 npcre_delete(&npcre);
141 return NULL;
142 }
143
144 default_section->entries = new_ht(CONFIG_SECTION_HASH_TABLE_LEN);
145 if (!default_section->entries) {
146 n_log(LOG_ERR, "error creating hash table of size %d for default_section", CONFIG_SECTION_HASH_TABLE_LEN);
147 (*errors)++;
148 list_destroy(&cfg_file->sections);
149 Free(cfg_file->filename);
150 Free(default_section->section_name);
151 Free(default_section);
152 Free(cfg_file);
153 fclose(in);
154 npcre_delete(&npcre);
155 return NULL;
156 }
157 list_push(cfg_file->sections, default_section, &destroy_config_file_section);
158
159 size_t line_number = 0;
160 while (fgets(buffer, MAX_CONFIG_LINE_LEN, in)) {
161 NSTRBYTE end = 0;
162
163 char* bufptr = (char*)&buffer;
164 line_number++;
165
166 bufptr = trim_nocopy(buffer);
167 if (strlen(bufptr) == 0) {
168 continue;
169 }
170
171 switch (bufptr[0]) {
172 /* comment */
173 case '#':
174 case ';':
175 continue;
176 /* new section */
177 case '[':
178 end = 0;
179 /* go to begin of section name */
180 if (skipu(bufptr, ']', &end, 1) != TRUE) {
181 n_log(LOG_ERR, "coulnd't find end of section at line %zu of %s (string:%s)", line_number, filename, buffer);
182 (*errors)++;
183 continue;
184 }
185 /* keep only the name */
186 bufptr++;
187 bufptr[end - 1] = '\0';
188 bufptr = trim_nocopy(bufptr);
189 if (strlen(bufptr) == 0) {
190 n_log(LOG_ERR, "section without name at line %zu", line_number);
191 (*errors)++;
192 continue;
193 }
194
195 Malloc(section, CONFIG_FILE_SECTION, 1);
196 __n_assert(section, break);
197
198 section->section_name = strdup(bufptr);
199 if (!section->section_name) {
200 n_log(LOG_ERR, "couldn't duplicate %s", bufptr);
201 (*errors)++;
202 Free(section);
203 continue;
204 }
205
207 if (!section->entries) {
208 n_log(LOG_ERR, "error creating [%s] hash table of size %d", bufptr, CONFIG_SECTION_HASH_TABLE_LEN);
209 (*errors)++;
210 Free(section->section_name);
211 Free(section);
212 continue;
213 }
214 list_push(cfg_file->sections, section, &destroy_config_file_section);
215 continue;
216
217 /* if it's not a comment, not a section, not empty, then it's an entry ! */
218 default:
219 if (!section) {
220 section = default_section;
221 n_log(LOG_DEBUG, "No section, setting __DEFAULT__ starting line %zu of %s (string:%s)", line_number, filename, bufptr);
222 }
223
224 if (npcre_match_capture(bufptr, npcre) == TRUE) {
225 Malloc(split_result, char*, 3);
226 if (!split_result) {
227 n_log(LOG_ERR, "allocation failed at line %zu of %s", line_number, filename);
228 (*errors)++;
229 continue;
230 }
231 split_result[2] = NULL;
232 split_result[0] = str_replace((char*)npcre->match_list[1], "=", " ");
233 split_result[1] = strdup((char*)npcre->match_list[2]);
234 } else {
235 split_result = NULL;
236 }
237
238 if (!split_result) {
239 n_log(LOG_ERR, "couldn't find entry separator '=' at line %zu of %s (string:%s)", line_number, filename, bufptr);
240 (*errors)++;
241 continue;
242 }
243 if (split_count(split_result) < 2) {
244 n_log(LOG_ERR, "Invalid split count at line %zu of %s (string:%s, count:%d)", line_number, filename, bufptr, split_count(split_result));
245 (*errors)++;
246 free_split_result(&split_result);
247 continue;
248 }
249 if (strlen(trim_nocopy(split_result[0])) == 0) {
250 free_split_result(&split_result);
251 n_log(LOG_ERR, "couldn't find entry name at line %zu of %s (string:%s)", line_number, filename, buffer);
252 (*errors)++;
253 continue;
254 }
255
256 /* value pointer */
257 const char* valptr = NULL;
258 /* flags */
259 int closing_delimiter_found = 0;
260 int opening_delimiter_found = 0;
261 /* initialize with empty delimiter */
262 char delimiter = '\0';
263
264 /* If the trimmed value have a zero length it means we only had spaces or tabs
265 In that particular case we set the val to NULL */
266 if (strlen(trim_nocopy(split_result[1])) == 0) {
267 Free(split_result[1]);
268 valptr = NULL;
269 delimiter = '\0';
270 } else /* we have a value, or something between delimiters */
271 {
272 valptr = trim_nocopy(split_result[1]);
273 size_t it = strlen(valptr);
274
275 /* get value right boundary */
276 while (1) {
277 /* if comments tags replace them with end of string */
278 if (split_result[1][it] == ';' || split_result[1][it] == '#') {
279 split_result[1][it] = '\0';
280 } else {
281 /* we found a delimiter on the right */
282 if (split_result[1][it] == '"' || split_result[1][it] == '\'') {
283 delimiter = split_result[1][it];
284 split_result[1][it] = '\0';
285 closing_delimiter_found = 1;
286 break;
287 }
288 }
289 if (it == 0)
290 break;
291 else
292 it--;
293 }
294
295 if (strlen(trim_nocopy(split_result[1])) == 0) {
296 Free(split_result[1]);
297 valptr = NULL;
298 delimiter = '\0';
299 }
300
301 if (split_result[1]) {
302 /* get value left boundary */
303 it = 0;
304 while (split_result[1][it] != '\0') {
305 if (split_result[1][it] == ';' || split_result[1][it] == '#') {
306 split_result[1][it] = '\0';
307 } else {
308 if (delimiter != '\0' && split_result[1][it] == delimiter) {
309 opening_delimiter_found = 1;
310 it++;
311 valptr = split_result[1] + it;
312 break;
313 }
314 it++;
315 }
316 }
317 if (strlen(trim_nocopy(split_result[1])) == 0) {
318 Free(split_result[1]);
319 valptr = NULL;
320 delimiter = '\0';
321 }
322 }
323 }
324 if (delimiter != '\0' && ((opening_delimiter_found && !closing_delimiter_found) || (!opening_delimiter_found && closing_delimiter_found))) {
325 free_split_result(&split_result);
326 n_log(LOG_ERR, "Only one delimiter found at line %zu of %s (string:%s)", line_number, filename, buffer);
327 (*errors)++;
328 continue;
329 }
330
331 char* result = NULL;
332 N_STR* entry_key = NULL;
333 size_t nb_entry_in_sections = 0;
334 while (TRUE) {
335 nstrprintf(entry_key, "%s%zu", trim_nocopy(split_result[0]), nb_entry_in_sections);
336 if (ht_get_ptr(section->entries, _nstr(entry_key), (void**)&result) == FALSE) {
337 break;
338 }
339 nb_entry_in_sections++;
340 free_nstr(&entry_key);
341 }
342 char* strptr = NULL;
343 if (valptr) {
344 strptr = strdup(valptr);
345 }
346 ht_put_ptr(section->entries, _nstr(entry_key), strptr, &free_no_null, NULL);
347 free_nstr(&entry_key);
348 free_split_result(&split_result);
349 continue;
350 }
351 }
352 npcre_delete(&npcre);
353 if (!feof(in)) {
354 n_log(LOG_ERR, "couldn't read EOF for %s", filename);
355 (*errors)++;
356 }
357 fclose(in);
358
359 return cfg_file;
360
361} /*load_config_file */
362
369int write_config_file(CONFIG_FILE* cfg_file, char* filename) {
370 __n_assert(cfg_file, return FALSE);
371 __n_assert(filename, return FALSE);
372
373 int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0600); // Secure permissions
374 if (fd == -1) {
375 int error = errno;
376 n_log(LOG_ERR, "Unable to open '%s' for writing (0600): %s", _str(filename), strerror(error));
377 return FALSE;
378 }
379
380 FILE* out = NULL;
381 out = fdopen(fd, "w");
382 if (!out) {
383 int error = errno;
384 n_log(LOG_ERR, "fdopen failed for '%s' (fd=%d): %s", _str(filename), fd, strerror(error));
385 close(fd);
386 return FALSE;
387 }
388
389 char* last_section = NULL;
390 // cppcheck-suppress variableScope
391 const char *section = NULL, *key = NULL, *val = NULL;
392
393 config_foreach(cfg_file, section, key, val) {
394 /* write section name */
395 if (!last_section || strcmp(last_section, section) != 0) {
396 fprintf(out, "[%s]\n", section);
397 FreeNoLog(last_section);
398 last_section = strdup(section);
399 }
400 fprintf(out, "%s=%s\n", key, val);
401 }
403 fclose(out);
404 FreeNoLog(last_section);
405
406 return TRUE;
407} /* write_config_file */
408
415size_t get_nb_config_file_sections(CONFIG_FILE* cfg_file, const char* section_name) {
416 __n_assert(cfg_file, return 0);
417 __n_assert(cfg_file->sections, return 0);
418
419 size_t nb_sections = 0;
420 list_foreach(listnode, cfg_file->sections) {
421 const CONFIG_FILE_SECTION* section = (const CONFIG_FILE_SECTION*)listnode->ptr;
422 if (section_name) {
423 if (!strcmp(section->section_name, section_name)) {
424 nb_sections++;
425 }
426 } else {
427 nb_sections++;
428 }
429 }
430 return nb_sections;
431} /* get_nb_config_file_sections(...) */
432
441size_t get_nb_config_file_sections_entries(CONFIG_FILE* cfg_file, const char* section_name, size_t section_position, char* entry) {
442 __n_assert(cfg_file, return 0);
443 __n_assert(cfg_file->sections, return 0);
444 __n_assert(section_name, return 0);
445 __n_assert(entry, return 0);
446
447 size_t nb_sections = 0;
448 size_t nb_entry_in_sections = 0;
449 list_foreach(listnode, cfg_file->sections) {
450 CONFIG_FILE_SECTION* section = (CONFIG_FILE_SECTION*)listnode->ptr;
451 if (!strcmp(section->section_name, section_name)) {
452 if (nb_sections == section_position) {
453 char* result = NULL;
454 N_STR* entry_key = NULL;
455 while (TRUE) {
456 nstrprintf(entry_key, "%s%zu", entry, nb_entry_in_sections);
457 if (ht_get_ptr(section->entries, _nstr(entry_key), (void**)&result) == FALSE) {
458 free_nstr(&entry_key);
459 break;
460 }
461 nb_entry_in_sections++;
462 free_nstr(&entry_key);
463 }
464 break;
465 }
466 nb_sections++;
467 }
468 }
469 return nb_entry_in_sections;
470} /* get_nb_config_file_sections_entries(...) */
471
481char* get_config_section_value(CONFIG_FILE* cfg_file, char* section_name, size_t section_position, char* entry, size_t entry_position) {
482 __n_assert(cfg_file, return NULL);
483 __n_assert(cfg_file->sections, return NULL);
484 __n_assert(section_name, return NULL);
485 __n_assert(entry, return NULL);
486
487 if (section_position >= cfg_file->sections->nb_items) {
488 n_log(LOG_DEBUG, "section_position (%d) is higher than the number of item in list (%d)", section_position, cfg_file->sections->nb_items);
489 return NULL;
490 }
491
492 size_t section_position_it = 0;
493 HASH_TABLE* entries = NULL;
494 list_foreach(listnode, cfg_file->sections) {
495 CONFIG_FILE_SECTION* section = (CONFIG_FILE_SECTION*)listnode->ptr;
496 if (!strcmp(section->section_name, section_name)) {
497 if (section_position_it == section_position) {
498 entries = section->entries;
499 break;
500 }
501 section_position_it++;
502 }
503 }
504
505 if (!entries) {
506 n_log(LOG_ERR, "section %s not found", section_name);
507 return NULL;
508 }
509
510 char* result = NULL;
511 N_STR* entry_key = NULL;
512 nstrprintf(entry_key, "%s%zu", entry, entry_position);
513 if (ht_get_ptr(entries, _nstr(entry_key), (void**)&result) == FALSE) {
514 /* n_log( LOG_DEBUG , "ht_get_ptr( %p , trim_nocopy( %s ) , (void **)&result ) returned FALSE !" , entries , _nstr( entry_key ) ); */
515 free_nstr(&entry_key);
516 return NULL;
517 }
518 free_nstr(&entry_key);
519
520 return result;
521} /* get_config_section_value */
522
529 __n_assert(cfg_file && (*cfg_file), return FALSE);
530
531 Free((*cfg_file)->filename);
532
533 if ((*cfg_file)->sections) {
534 list_destroy(&(*cfg_file)->sections);
535 }
536
537 Free((*cfg_file));
538
539 return TRUE;
540} /* destroy_config_file */
char * key
#define FreeNoLog(__ptr)
Free Handler without log.
Definition n_common.h:271
#define Malloc(__ptr, __struct, __size)
Malloc Handler to get errors and set to 0.
Definition n_common.h:203
#define __n_assert(__ptr, __ret)
macro to assert things
Definition n_common.h:278
#define _str(__PTR)
define true
Definition n_common.h:192
#define Free(__ptr)
Free Handler to get errors.
Definition n_common.h:262
#define _nstr(__PTR)
N_STR or "NULL" string for logging purposes.
Definition n_common.h:198
HASH_TABLE * entries
hash table of key-value entries
char * section_name
name of the config section
char * filename
path and name of the config file
LIST * sections
list of CONFIG_FILE_SECTION
CONFIG_FILE * load_config_file(char *filename, int *errors)
load a config file
int write_config_file(CONFIG_FILE *cfg_file, char *filename)
write a config file
#define config_endfor
Foreach elements of CONFIG_FILE macro END.
#define CONFIG_SECTION_HASH_TABLE_LEN
size of the hash table of config sections entries
int destroy_config_file(CONFIG_FILE **cfg_file)
Destroy a loaded config file.
#define MAX_CONFIG_LINE_LEN
maximum length of a single config line
char * get_config_section_value(CONFIG_FILE *cfg_file, char *section_name, size_t section_position, char *entry, size_t entry_position)
Function to parse sections and get entries values.
#define config_foreach(__config, __section_name, __key, __val)
Foreach elements of CONFIG_FILE macro, i.e config_foreach( config , section , key ,...
size_t get_nb_config_file_sections(CONFIG_FILE *cfg_file, const char *section_name)
Get the number of config file with section_name.
size_t get_nb_config_file_sections_entries(CONFIG_FILE *cfg_file, const char *section_name, size_t section_position, char *entry)
Get the number of config file with section_name.
Structure of a config file.
Structure of a config section.
int ht_get_ptr(HASH_TABLE *table, const char *key, void **val)
get pointer at 'key' from 'table'
Definition n_hash.c:2100
int destroy_ht(HASH_TABLE **table)
empty a table and destroy it
Definition n_hash.c:2234
HASH_TABLE * new_ht(size_t size)
Create a hash table with the given size.
Definition n_hash.c:2001
int ht_put_ptr(HASH_TABLE *table, const char *key, void *ptr, void(*destructor)(void *ptr), void *(*duplicator)(void *ptr))
put an arbitrary pointer value with given key in the targeted hash table
Definition n_hash.c:2154
structure of a hash table
Definition n_hash.h:137
size_t nb_items
number of item currently in the list
Definition n_list.h:60
int list_push(LIST *list, void *ptr, void(*destructor)(void *ptr))
Add a pointer to the end of the list.
Definition n_list.c:227
#define list_foreach(__ITEM_, __LIST_)
ForEach macro helper, safe for node removal during iteration.
Definition n_list.h:88
int list_destroy(LIST **list)
Empty and Free a list container.
Definition n_list.c:547
LIST * new_generic_list(size_t max_items)
Initialiaze a generic list container to max_items pointers.
Definition n_list.c:36
#define MAX_LIST_ITEMS
flag to pass to new_generic_list for the maximum possible number of item in a list
Definition n_list.h:74
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:88
#define LOG_DEBUG
debug-level messages
Definition n_log.h:83
#define LOG_ERR
error conditions
Definition n_log.h:75
char * trim_nocopy(char *s)
trim and zero end the string, WARNING: keep and original pointer to delete the string correctly
Definition n_str.c:122
size_t NSTRBYTE
N_STR base unit.
Definition n_str.h:57
#define free_nstr(__ptr)
free a N_STR structure and set the pointer to NULL
Definition n_str.h:201
int split_count(char **split_result)
Count split elements.
Definition n_str.c:992
int skipu(const char *string, char toskip, NSTRBYTE *iterator, int inc)
skip until 'toskip' occurence is found from 'iterator' to the next 'toskip' value.
Definition n_str.c:786
char * str_replace(const char *string, const char *substr, const char *replacement)
Replace "substr" by "replacement" inside string taken from http://coding.debuntu.org/c-implementing-s...
Definition n_str.c:1419
#define nstrprintf(__nstr_var, __format,...)
Macro to quickly allocate and sprintf to N_STR.
Definition n_str.h:115
int free_split_result(char ***tab)
Free a split result allocated array.
Definition n_str.c:1008
A box including a string and his lenght.
Definition n_str.h:60
PCRE2_UCHAR8 ** match_list
populated match list (NULL-terminated) after npcre_match_capture() Allocated by PCRE2 via pcre2_subst...
Definition n_pcre.h:104
N_PCRE * npcre_new(char *str, int flags)
From pcre doc, the flag bits are: PCRE_ANCHORED Force pattern anchoring PCRE_AUTO_CALLOUT Compile aut...
Definition n_pcre.c:80
int npcre_match_capture(char *str, N_PCRE *pcre)
Return TRUE if str matches regexp, and make captures.
Definition n_pcre.c:261
int npcre_delete(N_PCRE **pcre)
Free a N_PCRE pointer.
Definition n_pcre.c:134
N_PCRE structure.
Definition n_pcre.h:93
Common headers and low-level functions & define.
void free_no_null(void *ptr)
local free for config entries
void destroy_config_file_section(void *ptr)
Destroy a config file section.
Config file reading and writing.
PCRE helpers for regex matching.