Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief Source for camel plugin
5 : *
6 : * @copyright BSD License (see doc/LICENSE.md or https://www.libelektra.org)
7 : *
8 : */
9 :
10 : /* -- Imports --------------------------------------------------------------------------------------------------------------------------- */
11 :
12 : #include "camel.h"
13 :
14 : #include <assert.h>
15 : #include <stdbool.h>
16 : // The definition `_WITH_GETLINE` is required for FreeBSD
17 : #define _WITH_GETLINE
18 : #include <stdio.h>
19 : #include <stdlib.h>
20 :
21 : #include <kdbassert.h>
22 : #include <kdbease.h>
23 : #include <kdberrors.h>
24 : #include <kdbhelper.h>
25 : #include <kdblogger.h>
26 :
27 : #ifdef HAVE_LOGGER
28 : #include <libgen.h>
29 : #include <sys/param.h>
30 : #endif
31 :
32 : /* -- Data Structures ------------------------------------------------------------------------------------------------------------------- */
33 :
34 : /** This enum specifies the possible states of the recursive descent parser. */
35 : typedef enum
36 : {
37 : /** Unable to open file */
38 : ERROR_FILE_OPEN,
39 : /** Unable to close file */
40 : ERROR_FILE_CLOSE,
41 : /** Error while parsing file */
42 : ERROR_PARSE,
43 : /** Everything is okay */
44 : OK
45 : } statusType;
46 :
47 : /** This structure saves various data for the recursive descent parser used in this plugin. */
48 : typedef struct
49 : {
50 : /** The current state of the parsing engine */
51 : statusType status;
52 :
53 : /** The handle of the opened file */
54 : FILE * file;
55 : /** Current line inside `file` */
56 : size_t line;
57 : /** Current column inside `line` */
58 : size_t column;
59 : /** Start of last text matched by parser */
60 : char * match;
61 : /** End of last text matched by parser */
62 : char * end;
63 : /** Last key read by parser */
64 : char * key;
65 : /** Last value read by parser */
66 : char * value;
67 :
68 : /** Text buffer for the content saved in `file` */
69 : char * bufferBase;
70 : /** Current location in the text buffer */
71 : char * buffer;
72 : /** Length of characters still available in the text buffer */
73 : size_t bufferCharsAvailable;
74 :
75 : /** Saves filename and allows us to emit error information */
76 : Key * parentKey;
77 : /** Contains key values pairs we parsed (get direction) or data we need to write back (set direction) */
78 : KeySet * keySet;
79 :
80 : /** Stores previous value of `errno` */
81 : int errorNumber;
82 : } parserType;
83 :
84 : /* -- Macros ---------------------------------------------------------------------------------------------------------------------------- */
85 :
86 : #define LOG_PARSE(data, message, ...) \
87 : ELEKTRA_LOG_DEBUG ("%s:%zu:%zu: " message, strrchr (keyString (data->parentKey), '/') + 1, data->line, data->column, __VA_ARGS__);
88 :
89 : #define SET_ERROR_PARSE(data, message, ...) \
90 : ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (data->parentKey, "General parse error: %s:%zu:%zu: " message, \
91 : keyString (data->parentKey), data->line, data->column, __VA_ARGS__);
92 :
93 : #define RET_NOK(function) \
94 : if (function->status != OK) \
95 : { \
96 : return parser; /* Requires that the name of the parsing structure is `parser`! */ \
97 : }
98 :
99 : /* -- Functions ------------------------------------------------------------------------------------------------------------------------- */
100 :
101 : // ========
102 : // = Misc =
103 : // ========
104 :
105 : /**
106 : * @brief Set an error specified via the global variable `errno`
107 : *
108 : * @pre The parameter `parser` must not be `NULL`.
109 : *
110 : * @param parser Saves the parent key this function uses to emit error information
111 : * @param status Specifies the type of error that this function should set
112 : *
113 : * @return An updated version of the variable `parser`
114 : */
115 0 : static parserType * setErrorErrno (parserType * const parser, statusType status)
116 : {
117 0 : ELEKTRA_NOT_NULL (parser);
118 :
119 0 : SET_ERROR_PARSE (parser, "%s", strerror (errno));
120 0 : errno = parser->errorNumber;
121 0 : parser->status = status;
122 0 : return parser;
123 : }
124 :
125 : /**
126 : * @brief Set an allocation error
127 : *
128 : * @pre The parameter `parser` must not be `NULL`.
129 : *
130 : * @param parser Saves the parent key this function uses to emit error information
131 : * @param status Specifies the size of the last allocation attempt, that caused the error
132 : *
133 : * @return An updated version of the variable `parser`
134 : */
135 0 : static parserType * setErrorMalloc (parserType * const parser, size_t size)
136 : {
137 0 : ELEKTRA_NOT_NULL (parser);
138 :
139 0 : ELEKTRA_MALLOC_ERROR (parser->parentKey, size);
140 0 : parser->status = ERROR_PARSE;
141 0 : return parser;
142 : }
143 :
144 : // ===========
145 : // = Parsing =
146 : // ===========
147 :
148 : /**
149 : * @brief Extend the text buffer with at least one byte if possible
150 : *
151 : * If there was an error, then the status of the given parser structure will be updated accordingly.
152 : *
153 : * @pre The variables `parser` and `parser->file` must not be `NULL`
154 : *
155 : * @param parser Saves the pointer to the buffer location this function tries to fill with at least one character
156 : *
157 : * @return An updated version of the variable `parser`
158 : */
159 472 : static parserType * bufferChar (parserType * const parser)
160 : {
161 472 : ELEKTRA_NOT_NULL (parser);
162 472 : ELEKTRA_NOT_NULL (parser->file);
163 :
164 472 : char * line = NULL;
165 : size_t capacity;
166 : ssize_t numberCharsRead;
167 :
168 974 : while (parser->bufferCharsAvailable < 1 && (numberCharsRead = getline (&line, &capacity, parser->file)) != -1)
169 : {
170 30 : size_t bufferOffset = parser->buffer - parser->bufferBase;
171 30 : size_t bufferCharsAvailable = parser->bufferCharsAvailable + numberCharsRead;
172 30 : size_t bufferSize = bufferOffset + bufferCharsAvailable + 1;
173 60 : if ((parser->bufferBase == 0 && (parser->bufferBase = elektraMalloc (bufferSize)) == NULL) ||
174 30 : ((elektraRealloc ((void **) &parser->bufferBase, bufferSize) < 0)))
175 : {
176 0 : return setErrorMalloc (parser, bufferSize);
177 : }
178 30 : strncpy (parser->bufferBase + bufferOffset, line, numberCharsRead + 1); //! OCLint (constant conditional operator)
179 30 : parser->buffer = parser->bufferBase + bufferOffset;
180 30 : free (line);
181 :
182 30 : parser->bufferCharsAvailable = bufferCharsAvailable;
183 : }
184 :
185 472 : if (ferror (parser->file)) return setErrorErrno (parser, ERROR_PARSE);
186 : return parser;
187 : }
188 :
189 : /**
190 : * @brief Read one character from the text buffer if possible
191 : *
192 : * If there was an error, then the status of the given parser structure will be updated accordingly.
193 : *
194 : * @pre The variables `parser` and `parser->file` must not be `NULL`
195 : *
196 : * @param parser Saves the parsing information this function operates on
197 : *
198 : * @return An updated version of the variable `parser`
199 : */
200 472 : static parserType * getNextChar (parserType * parser)
201 : {
202 472 : ELEKTRA_NOT_NULL (parser);
203 472 : ELEKTRA_NOT_NULL (parser->file);
204 :
205 472 : RET_NOK (bufferChar (parser));
206 :
207 472 : if (parser->bufferCharsAvailable < 1)
208 : {
209 0 : parser->match = NULL;
210 0 : return parser;
211 : }
212 :
213 472 : if (*parser->buffer == '\n')
214 : {
215 22 : parser->line++;
216 22 : parser->column = 1;
217 : }
218 : else
219 : {
220 450 : parser->column++;
221 : }
222 :
223 472 : parser->bufferCharsAvailable--;
224 472 : parser->match = parser->buffer;
225 472 : parser->buffer++;
226 :
227 472 : return parser;
228 : }
229 :
230 : /**
231 : * @brief Put back one character into the buffer
232 : *
233 : * @pre The variables `parser` and `parser->buffer` must not be `NULL`
234 : *
235 : * @param parser Saves the parsing information this function operates on
236 : *
237 : * @return An updated version of the variable `parser`
238 : */
239 112 : static parserType * putBackChar (parserType * parser)
240 : {
241 112 : ELEKTRA_NOT_NULL (parser);
242 112 : ELEKTRA_NOT_NULL (parser->buffer);
243 112 : ELEKTRA_ASSERT (parser->buffer - 1 >= parser->bufferBase, "Can not put back more characters than available");
244 :
245 : // We assume that we never put back the newline character
246 112 : ELEKTRA_ASSERT (*(parser->buffer - 1) != '\n', "Tried to put back newline character");
247 112 : parser->column--;
248 112 : parser->bufferCharsAvailable++;
249 112 : parser->buffer--;
250 :
251 112 : return parser;
252 : }
253 :
254 : /**
255 : * @brief Accept one of the characters specified via the string `characters`
256 : *
257 : * - If there was an error, then the status of the given parser structure will be updated accordingly.
258 : * - If one of the characters in `characters` matched the character at the current buffer position, then the function returns a pointer to
259 : * the matched character inside the variable `parser->match`. Otherwise `parser->match` will be NULL.
260 : * - On a match this function increments the current buffer positions.
261 : *
262 : * @pre The variables `parser`, `parser->file` and `characters` must not be `NULL`
263 : *
264 : * @param parser Saves the parsing information this function operates on
265 : * @param characters Saves a list of characters this function compares with the character at the current buffer position
266 : *
267 : * @return An updated version of the variable `parser`
268 : */
269 270 : static parserType * acceptChars (parserType * const parser, char const * const characters)
270 : {
271 270 : ELEKTRA_NOT_NULL (parser);
272 270 : ELEKTRA_NOT_NULL (parser->file);
273 270 : ELEKTRA_NOT_NULL (characters);
274 :
275 270 : if (getNextChar (parser)->status != OK || !parser->match) return parser;
276 :
277 270 : char * lastCharacter = parser->match;
278 270 : parser->match = NULL;
279 :
280 270 : if (strchr (characters, *lastCharacter))
281 : {
282 : LOG_PARSE (parser, "Accepted character “%c”", *lastCharacter);
283 190 : parser->match = lastCharacter;
284 190 : return parser;
285 : }
286 : LOG_PARSE (parser, "Put back character “%c”", *lastCharacter);
287 80 : return putBackChar (parser);
288 : }
289 :
290 : /**
291 : * @brief Assert that the buffer contains one of the characters specified via the string `characters`
292 : *
293 : * - If there was an error, then the status of the given parser structure will be updated accordingly.
294 : * - If none of the characters inside the variable `characters` matched the character at the current buffer position, then this function
295 : * will also set an error.
296 : * - If there was a match, then the function increments the current buffer positions and return the match in the variable `parser->match`.
297 : *
298 : * @pre The variables `parser`, `parser->file` and `characters` must not be `NULL`
299 : *
300 : * @param parser Saves the parsing information this function operates on
301 : * @param characters Saves a list of characters this function compares with the character at the current buffer position
302 : *
303 : * @return An updated version of the variable `parser`
304 : */
305 96 : static parserType * expect (parserType * const parser, char const * const characters)
306 : {
307 96 : ELEKTRA_NOT_NULL (parser);
308 96 : ELEKTRA_NOT_NULL (parser->file);
309 96 : ELEKTRA_NOT_NULL (characters);
310 :
311 96 : RET_NOK (acceptChars (parser, characters));
312 :
313 96 : if (!parser->match)
314 : {
315 0 : if (parser->bufferCharsAvailable > 0)
316 : {
317 0 : SET_ERROR_PARSE (parser, "Expected '%s' but found '%c'", characters, *parser->buffer);
318 : }
319 : else
320 : {
321 :
322 0 : SET_ERROR_PARSE (parser, "Expected '%s' but found end of file instead", characters);
323 : }
324 0 : parser->status = ERROR_PARSE;
325 : }
326 :
327 : return parser;
328 : }
329 :
330 : /**
331 : * @brief Consume all white space at the current buffer position and increment the buffer positions to account for the read whitespace
332 : *
333 : * If there was an error, then the status of the given parser structure will be updated accordingly.
334 : *
335 : * @pre The variables `parser`, `parser->file` and `characters` must not be `NULL`
336 : *
337 : * @param parser Saves the parsing information this function operates on
338 : *
339 : * @return An updated version of the variable `parser`
340 : */
341 72 : static parserType * whitespace (parserType * const parser)
342 : {
343 72 : ELEKTRA_NOT_NULL (parser);
344 72 : ELEKTRA_NOT_NULL (parser->file);
345 :
346 158 : while (acceptChars (parser, " \t\n")->status == OK && parser->match)
347 : ; //! OCLINT
348 :
349 72 : return parser;
350 : }
351 :
352 : /**
353 : * @brief Read a value that starts at the current buffer position and ends with a non-escaped double quote sign
354 : *
355 : * If there was an error, then the status of the given parser structure will be updated accordingly.
356 : *
357 : * @pre The variables `parser`, `parser->buffer` and `parser->file` must not be `NULL`
358 : *
359 : * @param parser Saves the parsing information this function operates on
360 : *
361 : * @return An updated version of the variable `parser`
362 : */
363 32 : static parserType * content (parserType * const parser)
364 : {
365 32 : ELEKTRA_NOT_NULL (parser);
366 32 : ELEKTRA_NOT_NULL (parser->file);
367 32 : ELEKTRA_NOT_NULL (parser->buffer);
368 :
369 : char * previous = parser->buffer;
370 : size_t numberCharsRead = 0;
371 : char * text = parser->buffer;
372 :
373 202 : while (getNextChar (parser)->status == OK && parser->match && (*parser->match != '"' || *previous == '\\'))
374 : {
375 170 : numberCharsRead++;
376 : LOG_PARSE (parser, "Read character “%c”", *parser->match);
377 170 : previous = parser->match;
378 : }
379 32 : RET_NOK (parser);
380 32 : if (*(parser->buffer - 1) == '"')
381 : {
382 32 : putBackChar (parser);
383 32 : numberCharsRead--;
384 : }
385 :
386 32 : parser->end = text + numberCharsRead;
387 : LOG_PARSE (parser, "End: “%c”", *parser->end);
388 32 : parser->match = text;
389 :
390 32 : return parser;
391 : }
392 :
393 : /**
394 : * @brief Read a value that starts and ends with a non-escaped double quote sign.
395 : *
396 : * - If there was an error, then the status of the given parser structure will be updated accordingly.
397 : * - If the value was read successfully, then the start (position right after the first `"`) and end (position right before the ending
398 : * `"`) of the content will be saved in the variables `parser->match` and `parser->end`.
399 : *
400 : * @pre The variables `parser` and `parser->file` must not be `NULL`
401 : *
402 : * @param parser Saves the parsing information this function operates on
403 : *
404 : * @return An updated version of the variable `parser`
405 : */
406 32 : static parserType * doubleQuoted (parserType * const parser)
407 : {
408 32 : ELEKTRA_NOT_NULL (parser);
409 32 : ELEKTRA_NOT_NULL (parser->file);
410 :
411 32 : RET_NOK (expect (parser, "\""));
412 32 : RET_NOK (content (parser));
413 32 : char * text = parser->match;
414 32 : RET_NOK (expect (parser, "\""));
415 32 : parser->match = text;
416 :
417 32 : return parser;
418 : }
419 :
420 : /**
421 : * @brief Save a copy of the string specified via the variables `parser->match` and `parser->end` in `location`
422 : *
423 : * If there was an error, then the status of the given parser structure will be updated accordingly.
424 : *
425 : * @pre The variables `parser` and `parser->file` must not be `NULL`
426 : *
427 : * @param parser Saves the parsing information this function operates on
428 : * @param location The location where this function should save the copy of the string
429 : *
430 : * @return An updated version of the variable `parser`
431 : */
432 32 : static parserType * saveText (parserType * const parser, char ** location)
433 : {
434 32 : ELEKTRA_NOT_NULL (parser);
435 32 : ELEKTRA_NOT_NULL (parser->match);
436 32 : ELEKTRA_NOT_NULL (parser->end);
437 32 : ELEKTRA_ASSERT (parser->end - parser->match >= -1, "The string specified via parser->match and parser->end has negative length");
438 32 : ELEKTRA_NOT_NULL (location);
439 :
440 32 : size_t length = parser->end - parser->match + 1;
441 32 : if (*location) elektraFree (*location);
442 32 : *location = elektraMalloc (length + 1);
443 32 : if (!*location) return setErrorMalloc (parser, length + 1);
444 :
445 32 : strncpy (*location, parser->match, length); //! OCLint (constant conditional operator)
446 32 : (*location)[length] = '\0';
447 :
448 32 : return parser;
449 : }
450 :
451 : /**
452 : * @brief Read a double quoted value that starts and ends with optional whitespace characters
453 : *
454 : * - The content of the double quoted value will be stored at the address specified via the variable `location`.
455 : * - If there was an error, then the status of the given parser structure will be updated accordingly.
456 : *
457 : * @pre The variables `parser` and `parser->file` must not be `NULL`
458 : *
459 : * @param parser Saves the parsing information this function operates on
460 : *
461 : * @return An updated version of the variable `parser`
462 : */
463 32 : static parserType * doubleQuotedSpace (parserType * const parser, char ** location)
464 : {
465 32 : ELEKTRA_NOT_NULL (parser);
466 32 : ELEKTRA_NOT_NULL (parser->file);
467 :
468 32 : RET_NOK (whitespace (parser));
469 32 : RET_NOK (doubleQuoted (parser));
470 32 : RET_NOK (saveText (parser, location));
471 32 : RET_NOK (whitespace (parser));
472 :
473 : return parser;
474 : }
475 :
476 : /**
477 : * @brief Read a key (including leading and trailing whitespace) and save the result in the variable `parser->key`.
478 : *
479 : * If there was an error, then the status of the given parser structure will be updated accordingly.
480 : *
481 : * @pre The variables `parser` and `parser->file` must not be `NULL`
482 : *
483 : * @param parser Saves the parsing information this function operates on
484 : *
485 : * @return An updated version of the variable `parser`
486 : */
487 16 : static parserType * key (parserType * const parser)
488 : {
489 16 : ELEKTRA_NOT_NULL (parser);
490 16 : ELEKTRA_NOT_NULL (parser->file);
491 :
492 16 : return doubleQuotedSpace (parser, &parser->key);
493 : }
494 :
495 : /**
496 : * @brief Read a value (including leading and trailing whitespace) and save the result in the variable `parser->value`.
497 : *
498 : * If there was an error, then the status of the given parser structure will be updated accordingly.
499 : *
500 : * @pre The variables `parser` and `parser->file` must not be `NULL`
501 : *
502 : * @param parser Saves the parsing information this function operates on
503 : *
504 : * @return An updated version of the variable `parser`
505 : */
506 16 : static parserType * value (parserType * const parser)
507 : {
508 16 : ELEKTRA_NOT_NULL (parser);
509 16 : ELEKTRA_NOT_NULL (parser->file);
510 :
511 16 : return doubleQuotedSpace (parser, &parser->value);
512 : }
513 :
514 : /**
515 : * @brief Read a key value pair and save them in the key set `parser->keySet`
516 : *
517 : * If there was an error, then the status of the given parser structure will be updated accordingly.
518 : *
519 : * @pre The variables `parser` and `parser->file` must not be `NULL`
520 : *
521 : * @param parser Saves the parsing information this function operates on
522 : *
523 : * @return An updated version of the variable `parser`
524 : */
525 16 : static parserType * pair (parserType * const parser)
526 : {
527 16 : ELEKTRA_NOT_NULL (parser);
528 16 : ELEKTRA_NOT_NULL (parser->file);
529 :
530 16 : RET_NOK (key (parser));
531 : LOG_PARSE (parser, "Read key “%s”", parser->key);
532 :
533 16 : RET_NOK (expect (parser, ":"));
534 16 : RET_NOK (value (parser));
535 : LOG_PARSE (parser, "Read value “%s”", parser->value);
536 :
537 16 : Key * key = keyNew (keyName (parser->parentKey), KEY_END);
538 16 : keyAddName (key, parser->key);
539 16 : keySetString (key, parser->value);
540 : ELEKTRA_LOG_DEBUG ("Name: “%s”", keyName (key));
541 : ELEKTRA_LOG_DEBUG ("Value: “%s”", keyString (key));
542 16 : ksAppendKey (parser->keySet, key);
543 :
544 16 : return parser;
545 : }
546 :
547 : /**
548 : * @brief Read optional key value pairs and save them in the key set `parser->keySet`
549 : *
550 : * If there was an error, then the status of the given parser structure will be updated accordingly.
551 : *
552 : * @pre The variables `parser` and `parser->file` must not be `NULL`
553 : *
554 : * @param parser Saves the parsing information this function operates on
555 : *
556 : * @return An updated version of the variable `parser`
557 : */
558 8 : static parserType * optionalAdditionalPairs (parserType * const parser)
559 : {
560 8 : ELEKTRA_NOT_NULL (parser);
561 8 : ELEKTRA_NOT_NULL (parser->file);
562 :
563 16 : while (acceptChars (parser, ",")->status == OK && parser->match)
564 : {
565 8 : RET_NOK (pair (parser));
566 : }
567 : return parser;
568 : }
569 :
570 : /**
571 : * @brief Read a list of key value pairs and save them in the key set `parser->keySet`
572 : *
573 : * If there was an error, then the status of the given parser structure will be updated accordingly.
574 : *
575 : * @pre The variables `parser` and `parser->file` must not be `NULL`
576 : *
577 : * @param parser Saves the parsing information this function operates on
578 : *
579 : * @return An updated version of the variable `parser`
580 : */
581 8 : static parserType * pairs (parserType * const parser)
582 : {
583 8 : ELEKTRA_NOT_NULL (parser);
584 8 : ELEKTRA_NOT_NULL (parser->file);
585 :
586 8 : RET_NOK (whitespace (parser));
587 8 : RET_NOK (expect (parser, "{"));
588 :
589 8 : RET_NOK (pair (parser));
590 :
591 8 : RET_NOK (optionalAdditionalPairs (parser));
592 :
593 8 : RET_NOK (expect (parser, "}"));
594 : LOG_PARSE (parser, "“%s: %s”", parser->key, parser->value);
595 :
596 : return parser;
597 : }
598 :
599 : // =====================
600 : // = Resource Handling =
601 : // =====================
602 :
603 : /**
604 : * @brief Open a file for reading
605 : *
606 : * @pre The variables `parser` and `parser->parentKey` must not be `NULL`
607 : *
608 : * @param parser Saves the filename of the file this function opens
609 : *
610 : * @return The updated parsing structure. If there were any errors opening the file, then this function sets the type of the parsing
611 : * structure to `ERROR_FILE_OPEN`.
612 : */
613 8 : static parserType * openFile (parserType * const parser)
614 : {
615 8 : ELEKTRA_NOT_NULL (parser);
616 8 : ELEKTRA_NOT_NULL (parser->parentKey);
617 :
618 8 : parser->file = fopen (keyString (parser->parentKey), "r");
619 :
620 8 : if (!parser->file) setErrorErrno (parser, ERROR_FILE_OPEN);
621 :
622 8 : return parser;
623 : }
624 :
625 : /**
626 : * @brief Free allocated resources
627 : *
628 : * @pre The parameter `parser` must not be `NULL`
629 : *
630 : * @param parser Contains resources this function frees
631 : *
632 : * @return The updated parsing structure. If there were any errors closing the file specified via `parser`, then this function sets
633 : * the type of the parsing structure to `ERROR_FILE_CLOSE`.
634 : */
635 8 : static parserType * cleanup (parserType * const parser)
636 : {
637 8 : ELEKTRA_NOT_NULL (parser);
638 :
639 8 : if (parser->file && fclose (parser->file) != 0) setErrorErrno (parser, ERROR_FILE_CLOSE);
640 8 : if (parser->bufferBase) elektraFree (parser->bufferBase);
641 8 : if (parser->key) elektraFree (parser->key);
642 8 : if (parser->value) elektraFree (parser->value);
643 :
644 8 : return parser;
645 : }
646 :
647 : /**
648 : * @brief Parse a file containing data specified in a very basic subset of YAML and store the obtained data in a given key set.
649 : *
650 : * @pre The parameters `returned`, and `parentKey` must not be `NULL`.
651 : *
652 : * @param returned A key set used to store the data contained in the file specified by the value of `parentKey`
653 : * @param parentKey The value of this key value pair stores the path to the file this function should parse
654 : *
655 : * @retval ELEKTRA_PLUGIN_STATUS_SUCCESS if the whole parsing process was successful
656 : * @retval ELEKTRA_PLUGIN_STATUS_ERROR if at least one part of the parsing process failed
657 : */
658 8 : static int parseFile (KeySet * returned ELEKTRA_UNUSED, Key * parentKey)
659 : {
660 8 : ELEKTRA_NOT_NULL (returned);
661 8 : ELEKTRA_NOT_NULL (parentKey);
662 :
663 : ELEKTRA_LOG ("Read configuration data");
664 :
665 16 : parserType * parser = &(parserType){ .status = OK,
666 : .line = 1,
667 : .column = 1,
668 : .file = NULL,
669 : .match = NULL,
670 : .bufferBase = NULL,
671 : .buffer = NULL,
672 : .bufferCharsAvailable = 0,
673 : .parentKey = parentKey,
674 : .keySet = returned,
675 8 : .errorNumber = errno };
676 :
677 8 : if (openFile (parser)->status == OK) pairs (parser);
678 8 : cleanup (parser);
679 :
680 8 : return parser->status == OK ? ELEKTRA_PLUGIN_STATUS_SUCCESS : ELEKTRA_PLUGIN_STATUS_ERROR;
681 : }
682 :
683 : /**
684 : * @brief This function returns a key set containing the contract of this plugin.
685 : *
686 : * @return A contract describing the functionality of this plugin.
687 : */
688 34 : static KeySet * contractCamel (void)
689 : {
690 34 : return ksNew (30, keyNew ("system/elektra/modules/camel", KEY_VALUE, "camel plugin waits for your orders", KEY_END),
691 : keyNew ("system/elektra/modules/camel/exports", KEY_END),
692 : keyNew ("system/elektra/modules/camel/exports/get", KEY_FUNC, elektraCamelGet, KEY_END),
693 : keyNew ("system/elektra/modules/camel/exports/set", KEY_FUNC, elektraCamelSet, KEY_END),
694 : #include ELEKTRA_README
695 : keyNew ("system/elektra/modules/camel/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END), KS_END);
696 : }
697 :
698 : /**
699 : * @brief Store the key value pairs of a key set in a file using a YAML sequence
700 : *
701 : * @pre The parameters `file`, `keySet`, and `parentKey` must not be `NULL`.
702 : *
703 : * @param keySet This key set contains the key value pairs that should be stored in `file`
704 : * @param parentKey The function uses this key to determine the relative name of a key in `keySet`
705 : *
706 : * @return The function returns a positive number (including 0) on success or a negative number if there was a problem writing the file.
707 : */
708 4 : static int writeFile (FILE * file, KeySet * keySet, Key * parentKey)
709 : {
710 4 : ELEKTRA_NOT_NULL (file);
711 4 : ELEKTRA_NOT_NULL (keySet);
712 4 : ELEKTRA_NOT_NULL (parentKey);
713 :
714 4 : ksRewind (keySet);
715 :
716 4 : int status = fprintf (file, "{\n");
717 4 : bool first = true;
718 17 : for (Key * key; status >= 0 && (key = ksNext (keySet)) != 0;)
719 : {
720 9 : const char * name = elektraKeyGetRelativeName (key, parentKey);
721 : ELEKTRA_LOG_DEBUG ("Write mapping “\"%s\" : \"%s\"”", name, keyString (key));
722 9 : status = fprintf (file, "%s \"%s\" : \"%s\"\n", first ? " " : ",", name, keyString (key));
723 9 : first = false;
724 : }
725 4 : return status < 0 ? status : fprintf (file, "}");
726 : }
727 :
728 : // ====================
729 : // = Plugin Interface =
730 : // ====================
731 :
732 : /** @see elektraDocGet */
733 42 : int elektraCamelGet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned, Key * parentKey)
734 : {
735 42 : if (!elektraStrCmp (keyName (parentKey), "system/elektra/modules/camel"))
736 : {
737 : ELEKTRA_LOG_DEBUG ("Retrieve plugin contract");
738 34 : KeySet * contract = contractCamel ();
739 34 : ksAppend (returned, contract);
740 34 : ksDel (contract);
741 :
742 34 : return ELEKTRA_PLUGIN_STATUS_SUCCESS;
743 : }
744 :
745 8 : return parseFile (returned, parentKey);
746 : }
747 :
748 : /** @see elektraDocSet */
749 4 : int elektraCamelSet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned, Key * parentKey)
750 : {
751 : ELEKTRA_LOG ("Write configuration data");
752 4 : int errorNumber = errno;
753 4 : FILE * destination = fopen (keyString (parentKey), "w");
754 :
755 : // The bitwise or in the next line is correct, since we need to close the file even if writing fails
756 4 : if (!destination || (writeFile (destination, returned, parentKey) < 0) | (fclose (destination) == EOF)) //! OCLint
757 : {
758 0 : ELEKTRA_SET_ERROR_SET (parentKey);
759 0 : errno = errorNumber;
760 0 : return ELEKTRA_PLUGIN_STATUS_ERROR;
761 : }
762 :
763 : return ELEKTRA_PLUGIN_STATUS_SUCCESS;
764 : }
765 :
766 68 : Plugin * ELEKTRA_PLUGIN_EXPORT
767 : {
768 68 : return elektraPluginExport ("camel", ELEKTRA_PLUGIN_GET, &elektraCamelGet, ELEKTRA_PLUGIN_SET, &elektraCamelSet,
769 : ELEKTRA_PLUGIN_END);
770 : }
|