LCOV - code coverage report
Current view: top level - src/plugins/camel - camel.c (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 182 203 89.7 %
Date: 2019-09-12 12:28:41 Functions: 23 26 88.5 %

          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             : }

Generated by: LCOV version 1.13