-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcfg_parse.c
More file actions
584 lines (487 loc) · 14.3 KB
/
cfg_parse.c
File metadata and controls
584 lines (487 loc) · 14.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
#include "cfg_parse.h"
/* for malloc, EXIT_SUCCESS and _FAILURE, exit */
#include <stdlib.h>
/* for FILE*, fgets, fputs */
#include <stdio.h>
/* for memset, strlen, strchr etc */
#include <string.h>
/* for tolower, isspace */
#include <ctype.h>
/* SIZE_MAX */
#if __STDC_VERSION__ >= 199901L
#include <stdint.h>
#elif !defined(SIZE_MAX)
#define SIZE_MAX ((size_t)-1)
#endif
/* ************************************************************************* */
/* implementation details of (opaque) config structures */
/* ************************************************************************* */
struct cfg_node
{
char* key;
char* value;
struct cfg_node* next;
};
struct cfg_struct
{
struct cfg_node* head;
};
/* ************************************************************************* */
/* Helper functions */
/* ************************************************************************* */
/* A malloc() wrapper which handles null return values */
static void* cfg_malloc(const size_t size)
{
void* ptr =
malloc(size);
if (ptr == NULL)
{
perror("cfg_parse: ERROR: malloc() returned NULL");
exit(EXIT_FAILURE);
}
return ptr;
}
/* Returns a duplicate of input str, without leading / trailing whitespace
Input str *MUST* be null-terminated, or disaster will result */
static char* cfg_trim(const char* str)
{
size_t tlen;
char* tstr;
/* advance start pointer to first non-whitespace char */
while (isspace(*str))
str ++;
/* roll back length until we run out of whitespace */
tlen = strlen(str);
while (tlen > 0 && isspace(str[tlen - 1]))
tlen --;
/* copy portion of string to new string */
tstr = cfg_malloc(tlen + 1);
tstr[tlen] = '\0';
if (tlen > 0) memcpy(tstr, str, tlen);
return tstr;
}
/* Returns a duplicate of input str, without leading / trailing whitespace
Also lowercases the string, AND returns NULL instead of empty str */
static char* cfg_norm_key(const char* key)
{
size_t i, tlen;
char* tkey;
/* advance start pointer to first non-whitespace char */
while (isspace(*key))
key ++;
/* roll back length until we run out of whitespace */
tlen = strlen(key);
while (tlen > 0 && isspace(key[tlen - 1]))
tlen --;
/* Exclude empty key */
if (tlen == 0) return NULL;
/* copy portion of string to new string */
tkey = cfg_malloc(tlen + 1);
tkey[tlen] = '\0';
/* Lowercase key and copy */
for (i = 0; i < tlen; i++)
tkey[i] = tolower(key[i]);
return tkey;
}
/* Creates a node struct with key and value, and returns it */
static struct cfg_node* cfg_create_node(char* key, char* value)
{
struct cfg_node* cur =
cfg_malloc(sizeof(struct cfg_node));
/* assign key, value */
cur->key = key;
cur->value = value;
cur->next = NULL;
return cur;
}
/* ************************************************************************* */
/* Public functions */
/* ************************************************************************* */
/**
* This function initializes a cfg_struct, and must be called before
* performing any further operations.
* @return Pointer to newly initialized cfg_struct object.
*/
struct cfg_struct* cfg_init()
{
struct cfg_struct* cfg =
cfg_malloc(sizeof(struct cfg_struct));
cfg->head = NULL;
return cfg;
}
/**
* This function deletes an entire cfg_struct, clearing any memory
* previously held by the structure.
* @param cfg Pointer to cfg_struct to delete.
*/
void cfg_free(struct cfg_struct* cfg)
{
struct cfg_node* temp;
if (cfg == NULL) return;
while ((temp = cfg->head) != NULL)
{
cfg->head = temp->next;
free(temp->key);
free(temp->value);
free(temp);
}
free(cfg);
}
/**
* This function loads data from a file, and inserts / updates the specified
* cfg_struct. New keys will be inserted. Existing keys will have values
* overwritten by those read from the file.
* The format of config-files is "key=value", with any amount of whitespace.
* Comments can be added, beginning with a # character until end-of-line.
* The maximum line size is CFG_MAX_LINE bytes (see cfg_parse.h)
* @param cfg Pointer to cfg_struct to update.
* @param filename String containing filename to open and parse.
* @return EXIT_SUCCESS (0) on success, or EXIT_FAILURE if file could not be
* opened.
*/
int cfg_load(struct cfg_struct* cfg, const char* filename)
{
FILE* fp;
char buffer[CFG_MAX_LINE + 1];
/* safety check: null input */
if (cfg == NULL || filename == NULL) return EXIT_FAILURE;
/* open file for reading */
fp = fopen(filename, "r");
if (fp == NULL) return EXIT_FAILURE;
while (fgets(buffer, CFG_MAX_LINE + 1, fp) != NULL)
{
/* locate first # sign and terminate string there (comment) */
char* delim = strchr(buffer, '#');
if (delim != NULL) *delim = '\0';
/* locate first = sign and prepare to split */
delim = strchr(buffer, '=');
if (delim != NULL)
{
*delim = '\0';
delim ++;
cfg_set(cfg, buffer, delim);
}
/* else: no '=', so either a blank or invalid line */
}
/* print warning in case the read attempt failed but not because EOF */
if (!feof(fp))
{
perror("cfg_parse: Warning: cfg_load() early termination:");
}
fclose(fp);
return EXIT_SUCCESS;
}
/**
* This function saves a complete cfg_struct to a file.
* Comments are not preserved.
* @param cfg Pointer to cfg_struct to save.
* @param filename String containing filename to open and parse.
* @return EXIT_SUCCESS (0) on success, or EXIT_FAILURE if file could not be
* opened or a write error occurred.
*/
int cfg_save(const struct cfg_struct* cfg, const char* filename)
{
FILE* fp;
struct cfg_node* cur;
/* safety check: null input */
if (cfg == NULL || filename == NULL) return EXIT_FAILURE;
/* open output file for writing */
fp = fopen(filename, "w");
if (fp == NULL) return EXIT_FAILURE;
/* point at first item in list */
cur = cfg->head;
/* step through the list, dumping each key-value pair to disk */
while (cur != NULL)
{
if (fputs(cur->key, fp) == EOF ||
fputc('=', fp) == EOF ||
fputs(cur->value, fp) == EOF ||
fputc('\n', fp) == EOF)
{
fclose(fp);
return EXIT_FAILURE;
}
cur = cur->next;
}
fclose(fp);
return EXIT_SUCCESS;
}
/**
* This function performs a key-lookup on a cfg_struct, and returns the
* associated value.
* @param cfg Pointer to cfg_struct to search.
* @param key String containing key to search for.
* @return String containing associated value, or NULL if key was not found.
*/
void remove_outer_quotes(char* str)
{
size_t len = strlen(str);
if (len >= 2) {
char first = str[0];
char last = str[len - 1];
if ((first == '"' && last == '"') || (first == '\'' && last == '\'')) {
str[len - 1] = '\0';
memmove(str, str + 1, len - 1);
}
}
}
const char* cfg_get(const struct cfg_struct* cfg, const char* key)
{
char* tkey;
struct cfg_node* cur;
/* safety check: null input */
if (cfg == NULL || cfg->head == NULL || key == NULL) return NULL;
/* Trim input search key */
tkey = cfg_norm_key(key);
/* Exclude empty key */
if (tkey == NULL) return NULL;
/* set up pointer to start of list */
cur = cfg->head;
/* loop through linked list looking for match on key
if found, free search key, return the value */
do
{
if (strcmp(tkey, cur->key) == 0)
{
free(tkey);
remove_outer_quotes(cur->value);
return cur->value;
}
} while ((cur = cur->next) != NULL);
free(tkey);
return NULL;
}
/**
* Returns an array of strings, one for each key in the config struct.
* This array and its contents are dynamically allocated: the caller
* should free them when done.
* @param cfg Pointer to cfg_struct to retrieve keys from.
* @param count Output parameter: number of keys in the returned array.
* @return Array of (count) strings, one for each key in the struct, or NULL
* in case of error or empty struct.
*/
char** cfg_get_keys(const struct cfg_struct* cfg, size_t* count)
{
size_t i;
char** keys;
struct cfg_node* cur;
/* safety check: null input */
if (cfg == NULL || cfg->head == NULL) return NULL;
/* walk the list to count how many keys we have available */
i = 0;
for (cur = cfg->head; cur != NULL; cur = cur->next)
i ++;
/* now create the array to hold them all */
if (SIZE_MAX / sizeof(char*) < i) return NULL;
keys = cfg_malloc(i * sizeof(char*));
/* walk the list again, this time allocating and copying each key */
cur = cfg->head;
for (*count = 0; *count < i; (*count) ++)
{
/* create space to hold the key, and copy it over */
keys[*count] = cfg_malloc(strlen(cur->key) + 1);
strcpy(keys[*count], cur->key);
cur = cur->next;
}
return keys;
}
/**
* This function sets a single key-value pair in a cfg_struct.
* If the key already exists, its value will be updated.
* If not, a new item is added to the cfg_struct list.
* For convenience, a NULL value is treated as a call to cfg_delete().
* @param cfg Pointer to cfg_struct to search.
* @param key String containing key to search for.
* @param value String containing new value to assign to key.
*/
void cfg_set(struct cfg_struct* cfg, const char* key, const char* value)
{
char* tkey;
char* tvalue;
struct cfg_node* cur;
/* Treat NULL value as a "delete" operation */
if (value == NULL)
{
cfg_delete(cfg, key);
return;
}
/* safety check: null input */
if (cfg == NULL || key == NULL) return;
/* Trim input search key */
tkey = cfg_norm_key(key);
/* Exclude empty key */
if (tkey == NULL) return;
/* Trim value. */
tvalue = cfg_trim(value);
if (cfg->head == NULL)
{
/* list was empty to begin with */
cfg->head = cfg_create_node(tkey, tvalue);
} else {
struct cfg_node* prev;
/* search list for existing key */
cur = cfg->head;
do
{
if (strcmp(tkey, cur->key) == 0)
{
/* found a match: no longer need cur key */
free(tkey);
/* update value */
free(cur->value);
cur->value = tvalue;
return;
}
prev = cur;
} while ((cur = cur->next) != NULL);
/* not found: create new element and append it */
prev->next = cfg_create_node(tkey, tvalue);
}
}
/**
* This function sets multiple key-value pairs in a cfg_struct.
* @param cfg Pointer to cfg_struct to search.
* @param keys Array of strings containing key to search for.
* @param values Array of strings containing new value to assign to key.
* @param count Length of keys / values arrays
*/
void cfg_set_array(struct cfg_struct* cfg, const char* keys[], const char* values[], const size_t count)
{
size_t i;
/* safety check: null input */
if (cfg == NULL || keys == NULL || values == NULL || count == 0) return;
/* Call cfg_set on every item in the lists */
for (i = 0; i < count; i ++)
cfg_set(cfg, keys[i], values[i]);
}
/**
* This function deletes a key-value pair from a cfg_struct.
* If the key does not exist, the function does nothing.
* @param cfg Pointer to cfg_struct to search.
* @param key String containing key to search for.
*/
void cfg_delete(struct cfg_struct* cfg, const char* key)
{
char* tkey;
struct cfg_node* cur;
struct cfg_node* prev;
/* safety check: null input */
if (cfg == NULL || cfg->head == NULL || key == NULL) return;
/* Trim input search key */
tkey = cfg_norm_key(key);
/* Exclude empty key */
if (tkey == NULL) return;
/* search list for existing key */
cur = cfg->head;
do
{
if (strcmp(tkey, cur->key) == 0)
{
/* found it - cleanup trimmed key */
free(tkey);
if (cur == cfg->head)
{
/* first element */
cfg->head = cur->next;
} else {
/* splice out element */
prev->next = cur->next;
}
/* delete element */
free(cur->value);
free(cur->key);
free(cur);
return;
}
prev = cur;
} while ((cur = cur->next) != NULL);
/* not found */
/* cleanup trimmed key */
free(tkey);
}
/**
* This function deletes multiple key-value pairs from a cfg_struct.
* @param cfg Pointer to cfg_struct to search.
* @param keys Array of strings containing key to search for.
* @param count Length of keys array
*/
void cfg_delete_array(struct cfg_struct* cfg, const char* keys[], const size_t count)
{
size_t i;
/* safety check: null input */
if (cfg == NULL || cfg->head == NULL || keys == NULL || count == 0) return;
/* Call cfg_delete on every item in the list */
for (i = 0; i < count; i ++)
cfg_delete(cfg, keys[i]);
}
/**
* This function performs the inverse of cfg_delete_array().
* Instead of deleting entries from cfg which match keys[],
* this will KEEP only those entries that match keys[].
* It can be used to keep a config file tidy between versions or
* after user edits.
* @param cfg Pointer to cfg_struct to search.
* @param keys Array of strings containing keys to keep
* @param count Length of keys array
*/
void cfg_prune(struct cfg_struct* cfg, const char* keys[], const size_t count)
{
char** tkeys;
size_t i, j;
struct cfg_node* cur;
struct cfg_node* prev;
/* safety check: null input */
if (cfg == NULL || cfg->head == NULL || keys == NULL || count == 0 ||
SIZE_MAX / sizeof(char*) < count) return;
/* First we must prep every key in keys[] using the normalize function. */
tkeys = cfg_malloc(count * sizeof(char*));
j = 0;
for (i = 0; i < count; i ++)
{
char *tkey;
if (keys[i] == NULL) continue;
tkey = cfg_norm_key(keys[i]);
if (tkey == NULL) continue;
tkeys[j] = tkey;
j ++;
}
if (j == 0)
{
free(tkeys);
return;
}
/* Now iterate through the cfg struct and test every entry */
cur = cfg->head;
do
{
for (i = 0; i < j; i ++)
if (strcmp(tkeys[i], cur->key) == 0)
break;
if (i == j)
{
/* Didn't find a key match - delete this */
free(cur->value);
free(cur->key);
if (cur == cfg->head)
{
/* first element */
cfg->head = cur->next;
free(cur);
cur = cfg->head;
} else {
/* splice out element */
prev->next = cur->next;
free(cur);
cur = prev->next;
}
} else {
/* matched, advance list element */
prev = cur;
cur = cur->next;
}
} while (cur != NULL);
/* Cleanup all our trimmed keys */
for (i = 0; i < j; i ++)
free(tkeys[i]);
free(tkeys);
}