LCOV - code coverage report
Current view: top level - src/plugins/mini - mini.c (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 75 87 86.2 %
Date: 2019-09-12 12:28:41 Functions: 11 12 91.7 %

          Line data    Source code
       1             : /**
       2             :  * @file
       3             :  *
       4             :  * @brief Source for mini plugin
       5             :  *
       6             :  * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
       7             :  *
       8             :  */
       9             : 
      10             : /* -- Imports --------------------------------------------------------------------------------------------------------------------------- */
      11             : 
      12             : #include "mini.h"
      13             : 
      14             : #include <kdbassert.h>
      15             : #include <kdbease.h>
      16             : #include <kdberrors.h>
      17             : #include <kdblogger.h>
      18             : #include <kdbutility.h>
      19             : // The definition `_WITH_GETLINE` is required for FreeBSD
      20             : #define _WITH_GETLINE
      21             : #include <stdio.h>
      22             : 
      23             : /* -- Functions ------------------------------------------------------------------------------------------------------------------------- */
      24             : 
      25             : // ===========
      26             : // = Private =
      27             : // ===========
      28             : 
      29             : /**
      30             :  * @brief This function returns a key set containing the contract of this plugin.
      31             :  *
      32             :  * @return A contract describing the functionality of this plugin.
      33             :  */
      34         161 : static inline KeySet * elektraMiniContract (void)
      35             : {
      36         161 :         return ksNew (30, keyNew ("system/elektra/modules/mini", KEY_VALUE, "mini plugin waits for your orders", KEY_END),
      37             :                       keyNew ("system/elektra/modules/mini/exports", KEY_END),
      38             :                       keyNew ("system/elektra/modules/mini/exports/get", KEY_FUNC, elektraMiniGet, KEY_END),
      39             :                       keyNew ("system/elektra/modules/mini/exports/set", KEY_FUNC, elektraMiniSet, KEY_END),
      40             : #include ELEKTRA_README
      41             :                       keyNew ("system/elektra/modules/mini/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END),
      42             :                       keyNew ("system/elektra/modules/mini/config/needs/chars/23", KEY_VALUE, "23", KEY_END), // 23 ↔︎ `#`
      43             :                       keyNew ("system/elektra/modules/mini/config/needs/chars/3B", KEY_VALUE, "3B", KEY_END), // 3B ↔︎ `;`
      44             :                       keyNew ("system/elektra/modules/mini/config/needs/chars/3D", KEY_VALUE, "3D", KEY_END), // 3D ↔︎ `=`
      45             :                       keyNew ("system/elektra/modules/mini/config/needs/chars/5C", KEY_VALUE, "5C", KEY_END), // 5C ↔︎ `\`
      46             :                       KS_END);
      47             : }
      48             : 
      49             : /**
      50             :  * @brief This function removes comments marked with `;` or '#' from a
      51             :  *        given string by locating the first non-escaped comment character.
      52             :  *        It then overwrites this character with `\0`.
      53             :  *
      54             :  * @pre The parameter `line` must not be `NULL`.
      55             :  *
      56             :  * @param line The string from which we want to remove line comments.
      57             :  *
      58             :  * @return A pointer to the first character of the modified version of
      59             :  *         line.
      60             :  */
      61          86 : static inline char * stripComment (char * line)
      62             : {
      63          86 :         ELEKTRA_NOT_NULL (line);
      64             : 
      65             :         char * current = line;
      66             :         char * before = NULL;
      67             : 
      68             :         /* As long as we are not the end of the string and
      69             :            the current character is either not a comment marker or the comment marker was escaped */
      70        1546 :         while (*current != '\0' && ((*current != '#' && *current != ';') || (before && *before == '\\')))
      71             :         {
      72        1460 :                 before = current;
      73        1460 :                 current++;
      74             :         }
      75          86 :         *current = '\0';
      76          86 :         return line;
      77             : }
      78             : 
      79             : /**
      80             :  * @brief This function locates the first non-escaped equals
      81             :  *        character (`=`) in a given string.
      82             :  *
      83             :  * @pre The parameter `text` must not be `NULL`.
      84             :  *
      85             :  * @param text The string in which the equals character should be located
      86             :  *
      87             :  * @return A pointer to the first unescaped `=` in text or a pointer to
      88             :  *         the terminating `\0` of `text` if no such character exists.
      89             :  */
      90          76 : static inline char * findUnescapedEquals (char * text)
      91             : {
      92          76 :         ELEKTRA_NOT_NULL (text);
      93             : 
      94             :         char * equals = text;
      95             :         char * before = NULL;
      96             : 
      97         787 :         while (*equals != '\0' && (*equals != '=' || (before && *before == '\\')))
      98             :         {
      99         711 :                 before = equals++;
     100             :         }
     101          76 :         return equals;
     102             : }
     103             : 
     104             : /**
     105             :  * @brief Parse a single line of a text in INI like format (`key = value`) and
     106             :  *        store the resulting key value pair in the given key set.
     107             :  *
     108             :  * The string stored in `line` can also be empty or contain comments denoted
     109             :  * by `;` or `#`. The function ignores empty lines. If a line contains non-commented
     110             :  * characters that do not follow the pattern `key = value`, then this function will
     111             :  * add a warning about this invalid key value pair to `parentKey`.
     112             :  *
     113             :  * @pre The parameters `line`, `keySet` and `parentKey` must not be `NULL`.
     114             :  *
     115             :  * @param line A single line string that should be parsed by this function
     116             :  * @param lineNumber The lineNumber of the current line of text. This value will
     117             :  *                   be used by this function to generate warning messages about
     118             :  *                   invalid key value pairs.
     119             :  * @param keySet The keyset where the key value pair contained in `line` should
     120             :  *               be saved
     121             :  * @param parentKey This key is used by this function to store warnings about
     122             :  *                  invalid key value pairs
     123             :  */
     124          86 : static inline void parseLine (char * line, size_t lineNumber, KeySet * keySet, Key * parentKey)
     125             : {
     126          86 :         ELEKTRA_NOT_NULL (line);
     127          86 :         ELEKTRA_NOT_NULL (keySet);
     128          86 :         ELEKTRA_NOT_NULL (parentKey);
     129             : 
     130          86 :         char * pair = elektraStrip (stripComment (line));
     131             : 
     132          86 :         if (*pair == '\0')
     133             :         {
     134             :                 return;
     135             :         }
     136             : 
     137          76 :         char * equals = findUnescapedEquals (pair);
     138          76 :         if (*equals == '\0' || equals == pair)
     139             :         {
     140             :                 ELEKTRA_LOG_WARNING ("Ignored line %zu since “%s” does not contain a valid key value pair", lineNumber, pair);
     141          15 :                 ELEKTRA_ADD_VALIDATION_SYNTACTIC_WARNINGF (parentKey, "Line %zu: '%s' is not a valid key value pair", lineNumber, pair);
     142          15 :                 return;
     143             :         }
     144             : 
     145          61 :         *equals = '\0';
     146             : 
     147          61 :         char * name = elektraRstrip (pair, NULL);
     148          61 :         char * value = elektraLskip (equals + 1);
     149             : 
     150          61 :         Key * key = keyNew (keyName (parentKey), KEY_END);
     151          61 :         keyAddName (key, name);
     152          61 :         keySetString (key, value);
     153             :         ELEKTRA_LOG_DEBUG ("Name:  “%s”", keyName (key));
     154             :         ELEKTRA_LOG_DEBUG ("Value: “%s”", keyString (key));
     155             : 
     156          61 :         ksAppendKey (keySet, key);
     157             : }
     158             : 
     159             : /**
     160             :  * @brief Parse a file containing text in INI like format (`key = value`).
     161             :  *
     162             :  * @pre The parameters `file`, `keySet` and `parentKey` must not be `NULL`.
     163             :  *
     164             :  * @param file The file handle that points to the data this function should parse
     165             :  * @param keySet The keyset where the key value pairs saved in `file` should
     166             :  *               be saved
     167             :  * @param parentKey A key that is used by this function to store warning and error
     168             :  *                  information
     169             :  *
     170             :  * @retval ELEKTRA_PLUGIN_STATUS_SUCCESS if the parsing process finished successfully
     171             :  * @retval ELEKTRA_PLUGIN_STATUS_ERROR if the function was unable to parse `file`
     172             :  */
     173          30 : static int parseINI (FILE * file, KeySet * keySet, Key * parentKey)
     174             : {
     175          30 :         ELEKTRA_NOT_NULL (file);
     176          30 :         ELEKTRA_NOT_NULL (keySet);
     177          30 :         ELEKTRA_NOT_NULL (parentKey);
     178             : 
     179          30 :         char * line = NULL;
     180          30 :         size_t capacity = 0;
     181          30 :         int errorNumber = errno;
     182             : 
     183             :         size_t lineNumber;
     184         116 :         for (lineNumber = 1; getline (&line, &capacity, file) != -1; ++lineNumber)
     185             :         {
     186             :                 ELEKTRA_LOG_DEBUG ("Read Line %zu: %s", lineNumber, line);
     187          86 :                 parseLine (line, lineNumber, keySet, parentKey);
     188             :         }
     189             : 
     190          30 :         elektraFree (line);
     191             : 
     192          30 :         if (!feof (file))
     193             :         {
     194             :                 ELEKTRA_LOG_WARNING ("%s:%zu: Unable to read line", keyString (parentKey), lineNumber);
     195           0 :                 ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (parentKey, "Unable to read line %zu: %s", lineNumber, strerror (errno));
     196           0 :                 errno = errorNumber;
     197           0 :                 return ELEKTRA_PLUGIN_STATUS_ERROR;
     198             :         }
     199             : 
     200             :         return ELEKTRA_PLUGIN_STATUS_SUCCESS;
     201             : }
     202             : 
     203             : /**
     204             :  * @brief Parse a file containing key value pairs in an INI like format and
     205             :  *        store the obtained key value pairs in a given key set.
     206             :  *
     207             :  * @pre The parameters `returned`, and `parentKey` must not be `NULL`.
     208             :  *
     209             :  * @param returned A key set used to store the key value pairs contained in the
     210             :  *                 file specified by the value of `parentKey`
     211             :  * @param parentKey The value of this key value pair stores the path to the file
     212             :  *                  this function should parse
     213             :  *
     214             :  * @retval ELEKTRA_PLUGIN_STATUS_SUCCESS if the whole parsing process was successful
     215             :  * @retval ELEKTRA_PLUGIN_STATUS_ERROR if at least one part of the parsing process failed
     216             :  */
     217          30 : static int parseFile (KeySet * returned, Key * parentKey)
     218             : {
     219          30 :         ELEKTRA_NOT_NULL (returned);
     220          30 :         ELEKTRA_NOT_NULL (parentKey);
     221             : 
     222             :         ELEKTRA_LOG ("Read configuration data");
     223          30 :         int errorNumber = errno;
     224          30 :         FILE * source = fopen (keyString (parentKey), "r");
     225             : 
     226          30 :         if (!source || (parseINI (source, returned, parentKey) < 0) | (fclose (source) != 0)) //! OCLint
     227             :         {
     228           0 :                 ELEKTRA_SET_ERROR_GET (parentKey);
     229           0 :                 errno = errorNumber;
     230           0 :                 return ELEKTRA_PLUGIN_STATUS_ERROR;
     231             :         }
     232             : 
     233             :         return ELEKTRA_PLUGIN_STATUS_SUCCESS;
     234             : }
     235             : 
     236             : /**
     237             :  * @brief Add an error to `parentKey` and restore `errno` if `status` contains
     238             :  *        a negative number (write error).
     239             :  *
     240             :  * @pre The parameter `parentKey` must not be `NULL`.
     241             :  *
     242             :  * @param status This value will be checked by this function to determine if
     243             :  *               the write function (`fprintf`) returned unsuccessfully
     244             :  * @param errorNumber A saved value for `errno` this function should restore
     245             :  *                    if `status` indicates an write error
     246             :  * @param parentKey The key to which this function will add error information
     247             :  *                  if `status` indicates an write error
     248             :  *
     249             :  * @retval ELEKTRA_PLUGIN_STATUS_SUCCESS if status indicates success (`status >= 0`)
     250             :  * @retval ELEKTRA_PLUGIN_STATUS_ERROR if there was a write error (`status < 0`)
     251             :  */
     252          10 : static inline int checkWrite (int status, int errorNumber, Key * parentKey)
     253             : {
     254          10 :         ELEKTRA_NOT_NULL (parentKey);
     255             : 
     256          10 :         if (status < 0)
     257             :         {
     258           0 :                 ELEKTRA_SET_ERROR_SET (parentKey);
     259           0 :                 errno = errorNumber;
     260           0 :                 return ELEKTRA_PLUGIN_STATUS_ERROR;
     261             :         }
     262             : 
     263             :         return ELEKTRA_PLUGIN_STATUS_SUCCESS;
     264             : }
     265             : 
     266             : /**
     267             :  * @brief Store the key value pairs of a key set in a file using an INI like format
     268             :  *
     269             :  * @pre The parameters `file`, `keySet`, and `parentKey` must not be `NULL`.
     270             :  *
     271             :  * @param keySet This key set contains the key value pairs that should be stored
     272             :  *               in `file`
     273             :  * @param parentKey The function uses this key to store error and warning information
     274             :  *
     275             :  * @retval ELEKTRA_PLUGIN_STATUS_SUCCESS if the function was able to store all key value
     276             :  *                                       pairs successfully
     277             :  * @retval ELEKTRA_PLUGIN_STATUS_ERROR if the function was unable to store all key value
     278             :  *                                     pairs in `file`
     279             :  */
     280          10 : static inline int writeFile (FILE * file, KeySet * keySet, Key * parentKey)
     281             : {
     282          10 :         ELEKTRA_NOT_NULL (file);
     283          10 :         ELEKTRA_NOT_NULL (keySet);
     284          10 :         ELEKTRA_NOT_NULL (parentKey);
     285             : 
     286          10 :         int status = 0;
     287          10 :         int errorNumber = errno;
     288          10 :         ksRewind (keySet);
     289          41 :         for (Key * key; (key = ksNext (keySet)) != 0 && status >= 0;)
     290             :         {
     291          21 :                 const char * name = elektraKeyGetRelativeName (key, parentKey);
     292             :                 ELEKTRA_LOG_DEBUG ("Write mapping “%s=%s”", name, keyString (key));
     293             : 
     294          21 :                 status = fprintf (file, "%s=%s\n", name, keyString (key));
     295             :         }
     296          10 :         return checkWrite (status, errorNumber, parentKey);
     297             : }
     298             : 
     299             : // ====================
     300             : // = Plugin Interface =
     301             : // ====================
     302             : 
     303             : /** @see elektraDocGet */
     304         191 : int elektraMiniGet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned, Key * parentKey)
     305             : {
     306         191 :         if (!elektraStrCmp (keyName (parentKey), "system/elektra/modules/mini"))
     307             :         {
     308             :                 ELEKTRA_LOG_DEBUG ("Retrieve plugin contract");
     309         161 :                 KeySet * contract = elektraMiniContract ();
     310         161 :                 ksAppend (returned, contract);
     311         161 :                 ksDel (contract);
     312             : 
     313         161 :                 return ELEKTRA_PLUGIN_STATUS_SUCCESS;
     314             :         }
     315             : 
     316          30 :         return parseFile (returned, parentKey);
     317             : }
     318             : 
     319             : /** @see elektraDocSet */
     320          10 : int elektraMiniSet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned, Key * parentKey)
     321             : {
     322             :         ELEKTRA_LOG ("Write configuration data");
     323          10 :         int errorNumber = errno;
     324          10 :         FILE * destination = fopen (keyString (parentKey), "w");
     325             : 
     326          10 :         if (!destination || (writeFile (destination, returned, parentKey) < 0) | (fclose (destination) == EOF)) //! OCLint
     327             :         {
     328           0 :                 ELEKTRA_SET_ERROR_SET (parentKey);
     329           0 :                 errno = errorNumber;
     330           0 :                 return ELEKTRA_PLUGIN_STATUS_ERROR;
     331             :         }
     332             :         return ELEKTRA_PLUGIN_STATUS_SUCCESS;
     333             : }
     334             : 
     335         325 : Plugin * ELEKTRA_PLUGIN_EXPORT
     336             : {
     337             :         // clang-format off
     338         325 :         return elektraPluginExport ("mini",
     339             :                 ELEKTRA_PLUGIN_GET,     &elektraMiniGet,
     340             :                 ELEKTRA_PLUGIN_SET,     &elektraMiniSet,
     341             :                 ELEKTRA_PLUGIN_END);
     342             : }

Generated by: LCOV version 1.13