LCOV - code coverage report
Current view: top level - src/plugins/simpleini - simpleini.c (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 124 151 82.1 %
Date: 2019-09-12 12:28:41 Functions: 7 8 87.5 %

          Line data    Source code
       1             : /**
       2             :  * @file
       3             :  *
       4             :  * @brief
       5             :  *
       6             :  * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
       7             :  */
       8             : 
       9             : #define _GNU_SOURCE
      10             : #ifndef HAVE_KDBCONFIG
      11             : #include "kdbconfig.h"
      12             : #endif
      13             : 
      14             : #include "simpleini.h"
      15             : #include <errno.h>
      16             : 
      17             : #include <kdbassert.h>
      18             : #include <kdbease.h>
      19             : #include <kdberrors.h>
      20             : #include <kdblogger.h>
      21             : #include <kdbutility.h>
      22             : 
      23             : #include <stdio.h>
      24             : #include <stdlib.h>
      25             : 
      26             : 
      27             : struct lineFormat
      28             : {
      29             :         char * format;
      30             :         char * delimiter;
      31             : };
      32             : 
      33             : /**
      34             :  * @brief Builds together a format string by the plugin's configuration
      35             :  *
      36             :  * @param handle to plugin
      37             :  * @param first format string for key
      38             :  * @param second format string for value
      39             :  * @param delimiter pointer to store a newly allocated found delimiter string (string between key and value)
      40             :  *
      41             :  * @return lineFormat struct with two newly allocated strings (if not NULL)
      42             :  */
      43          31 : static struct lineFormat getFormat (Plugin * handle)
      44             : {
      45             :         struct lineFormat ret;
      46             :         // char * format;
      47          31 :         Key * key = ksLookupByName (elektraPluginGetConfig (handle), "/format", 0);
      48          31 :         if (!key)
      49             :         {
      50          17 :                 ret.format = elektraStrDup ("%s = %s\n");
      51          17 :                 ret.delimiter = elektraStrDup (" = ");
      52             :         }
      53             :         else
      54             :         {
      55          14 :                 const size_t maxFactor = 2; // at maximum every char is a %, %% -> %%%%
      56          14 :                 const size_t newLineAtEnd = 2;
      57          14 :                 const size_t userFormatSize = keyGetValueSize (key);
      58          14 :                 ret.format = elektraMalloc (userFormatSize * maxFactor + newLineAtEnd);
      59             : 
      60          14 :                 char * delimiterStart = NULL;
      61          14 :                 char * delimiterEnd = NULL;
      62          14 :                 int numFormatSpecifier = 0;
      63             : 
      64          14 :                 const char * userFormat = keyString (key);
      65          14 :                 int gotPercent = 0;
      66          14 :                 size_t j = 0;
      67          72 :                 for (size_t i = 0; i < userFormatSize; ++i, ++j)
      68             :                 {
      69          58 :                         const char c = userFormat[i];
      70          58 :                         if (gotPercent)
      71             :                         {
      72          22 :                                 if (c == '%')
      73             :                                 {
      74             :                                         // escaped %% -> %%%%
      75           8 :                                         ret.format[j++] = '%';
      76           8 :                                         ret.format[j++] = '%';
      77           8 :                                         ret.format[j] = '%';
      78             :                                 }
      79             :                                 else
      80             :                                 {
      81             :                                         // single % -> %s
      82          14 :                                         numFormatSpecifier++;
      83             :                                         // we only accept 2 format spec
      84             :                                         // (otherwise scanf would access internal mem -> security issue?)
      85             :                                         // use it as '%' so write it twice
      86          14 :                                         ret.format[j++] = numFormatSpecifier <= 2 ? 's' : '%';
      87          14 :                                         ret.format[j] = c;
      88             : 
      89          14 :                                         if (numFormatSpecifier == 1)
      90             :                                         {
      91             :                                                 // first format conversion specifier
      92             :                                                 // position after '%'
      93             :                                                 delimiterStart = (char *) &(userFormat[i]);
      94             :                                         }
      95           4 :                                         else if (numFormatSpecifier == 2)
      96             :                                         {
      97             :                                                 // second format spec.
      98             :                                                 // position of '%'
      99           4 :                                                 delimiterEnd = (char *) &(userFormat[i - 1]);
     100             :                                         }
     101             :                                 }
     102             :                                 gotPercent = 0;
     103             :                         }
     104          36 :                         else if (c == '%')
     105             :                         {
     106          22 :                                 ret.format[j] = c;
     107          22 :                                 gotPercent = 1;
     108             :                         }
     109             :                         else
     110             :                         {
     111          14 :                                 ret.format[j] = c;
     112             :                         }
     113             :                 }
     114          14 :                 --j; // discard null byte that is already there
     115          14 :                 ELEKTRA_ASSERT (ret.format[j] == '\0', "should be null byte at end of string but was %c", ret.format[j]);
     116          14 :                 ret.format[j++] = '\n';
     117          14 :                 ret.format[j] = '\0';
     118             : 
     119             :                 // be more robust, if no delimiter was found
     120          14 :                 if (delimiterStart == NULL || delimiterEnd == NULL)
     121             :                 {
     122             :                         ELEKTRA_LOG_DEBUG ("no delimiter found");
     123             :                         ret.delimiter = NULL;
     124             :                 }
     125             :                 else
     126             :                 {
     127             :                         // copy delimiter
     128           4 :                         const size_t delimiterLen = delimiterEnd - delimiterStart;
     129           4 :                         ret.delimiter = elektraStrNDup (delimiterStart, (delimiterLen + 1));
     130           4 :                         ret.delimiter[delimiterLen] = '\0';
     131             :                         ELEKTRA_LOG_DEBUG ("found delimiter:  '%s'", ret.delimiter);
     132             :                 }
     133             :                 ELEKTRA_LOG_DEBUG ("format: %s", ret.format);
     134             :         }
     135             : 
     136          31 :         return ret;
     137             : }
     138             : 
     139          32 : static char * replaceStringFormatSpec (char * format, const char * replace)
     140             : {
     141          32 :         size_t formatLen = strlen (format);
     142          32 :         size_t replaceLen = strlen (replace);
     143          32 :         size_t needleLen = strlen ("%s");
     144          32 :         size_t offset = replaceLen - needleLen;
     145             : 
     146          32 :         char * posRepl = strstr (format, "%s");
     147          32 :         if (posRepl)
     148             :         {
     149          32 :                 char * result = elektraMalloc (formatLen + offset + 1);
     150             : 
     151          32 :                 size_t preReplLen = posRepl - format;
     152             :                 // copy pre replacement
     153          32 :                 strncpy (result, format, preReplLen);
     154             :                 // copy replacement
     155          32 :                 strcpy (result + preReplLen, replace);
     156             :                 // copy post replacement
     157          32 :                 strncpy (result + preReplLen + replaceLen, posRepl + needleLen, formatLen - needleLen - preReplLen);
     158          32 :                 result[formatLen + offset] = '\0';
     159          32 :                 return result;
     160             :         }
     161             :         else
     162             :         {
     163             :                 return NULL;
     164             :         }
     165             : }
     166             : 
     167          26 : static char * getReadFormat (Plugin * handle)
     168             : {
     169          26 :         struct lineFormat f = getFormat (handle);
     170             : 
     171          26 :         char * keyFormat = NULL;
     172          26 :         if (f.delimiter)
     173             :         {
     174             :                 // scanf key format pattern: read everything until first char of delimiter
     175          16 :                 keyFormat = elektraFormat ("%%m[^%c]", f.delimiter[0]);
     176             :         }
     177             :         else
     178             :         {
     179             :                 // without delimiter, we also do not have two '%s' in the format which would be
     180             :                 // replaced by our key and value scanf format spec.
     181          10 :                 elektraFree (f.format);
     182          10 :                 return 0;
     183             :         }
     184             : 
     185             :         // make scanf format pattern with key format pattern and value format pattern
     186             :         // (we do not use simple printf style here, since it would replace '%%' to '%' but we need those
     187             :         // escaped '%' for a save scanf format pattern
     188          16 :         char * tmp = replaceStringFormatSpec (f.format, keyFormat);
     189          16 :         ELEKTRA_ASSERT (tmp != 0, "format has to have a '%%s' for the key");
     190             : 
     191             :         // replace value format specifier
     192          16 :         char * ret = replaceStringFormatSpec (tmp, "%m[^\n]");
     193          16 :         ELEKTRA_ASSERT (ret != 0, "format has to have a '%%s' for the value");
     194          16 :         elektraFree (tmp);
     195             : 
     196          16 :         elektraFree (keyFormat);
     197          16 :         elektraFree (f.format);
     198          16 :         elektraFree (f.delimiter);
     199             : 
     200          16 :         return ret;
     201             : }
     202             : 
     203           5 : static char * getWriteFormat (Plugin * handle)
     204             : {
     205           5 :         struct lineFormat f = getFormat (handle);
     206             : 
     207           5 :         elektraFree (f.delimiter);
     208           5 :         return f.format;
     209             : }
     210             : 
     211          61 : int elektraSimpleiniGet (Plugin * handle, KeySet * returned, Key * parentKey)
     212             : {
     213             :         /* get all keys */
     214             : 
     215          61 :         if (!strcmp (keyName (parentKey), "system/elektra/modules/simpleini"))
     216             :         {
     217          35 :                 KeySet * moduleConfig = ksNew (
     218             :                         30, keyNew ("system/elektra/modules/simpleini", KEY_VALUE, "simpleini plugin waits for your orders", KEY_END),
     219             :                         keyNew ("system/elektra/modules/simpleini/exports", KEY_END),
     220             :                         keyNew ("system/elektra/modules/simpleini/exports/get", KEY_FUNC, elektraSimpleiniGet, KEY_END),
     221             :                         keyNew ("system/elektra/modules/simpleini/exports/set", KEY_FUNC, elektraSimpleiniSet, KEY_END),
     222             : #include "readme_simpleini.c"
     223             :                         keyNew ("system/elektra/modules/simpleini/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END),
     224             :                         keyNew ("system/elektra/modules/simpleini/config/needs", KEY_VALUE, "the needed configuration to work in a backend",
     225             :                                 KEY_END),
     226             :                         keyNew ("system/elektra/modules/simpleini/config/needs/chars", KEY_VALUE, "Characters needed", KEY_END),
     227             :                         // space in value now works:
     228             :                         // TODO: characters present in format should be escaped
     229             :                         /*
     230             :                         keyNew ("system/elektra/modules/simpleini/config/needs/chars/20", KEY_VALUE, "61", KEY_END), // space -> a
     231             :                         keyNew ("system/elektra/modules/simpleini/config/needs/chars/23", KEY_VALUE, "62", KEY_END), // # -> b
     232             :                         keyNew ("system/elektra/modules/simpleini/config/needs/chars/25", KEY_VALUE, "63",
     233             :                                 KEY_END), // % -> c (escape character)
     234             :                         keyNew ("system/elektra/modules/simpleini/config/needs/chars/3B", KEY_VALUE, "64", KEY_END), // ; -> d
     235             :                         keyNew ("system/elektra/modules/simpleini/config/needs/chars/3D", KEY_VALUE, "65", KEY_END), // = -> e
     236             :                         keyNew ("system/elektra/modules/simpleini/config/needs/chars/5C", KEY_VALUE, "66", KEY_END), // \\ -> f
     237             :                         */
     238             :                         keyNew ("system/elektra/modules/simpleini/config/needs/chars/0A", KEY_VALUE, "67", KEY_END), // enter (NL) -> g
     239             :                         keyNew ("system/elektra/modules/simpleini/config/needs/chars/0D", KEY_VALUE, "68", KEY_END), // CR -> h
     240             :                         keyNew ("system/elektra/modules/simpleini/config/needs/escape", KEY_VALUE, "25", KEY_END), KS_END);
     241          35 :                 ksAppend (returned, moduleConfig);
     242          35 :                 ksDel (moduleConfig);
     243          35 :                 return 1;
     244             :         }
     245             : 
     246          26 :         char * key = 0;
     247          26 :         char * strippedkey = 0;
     248          26 :         char * value = 0;
     249          26 :         int errnosave = errno;
     250             : 
     251          26 :         char * format = getReadFormat (handle);
     252          26 :         if (!format)
     253             :         {
     254          10 :                 ELEKTRA_SET_VALIDATION_SYNTACTIC_ERROR (parentKey, "Invalid 'format' specified");
     255          10 :                 return -1;
     256             :         }
     257             : 
     258             :         ELEKTRA_LOG ("Read from '%s' with format '%s'", keyString (parentKey), format);
     259             : 
     260          16 :         const char * filename = keyString (parentKey);
     261          16 :         FILE * fp = fopen (filename, "r");
     262          16 :         if (!fp)
     263             :         {
     264           0 :                 ELEKTRA_SET_ERROR_GET (parentKey);
     265           0 :                 errno = errnosave;
     266           0 :                 elektraFree (format);
     267           0 :                 return -1;
     268             :         }
     269             : 
     270          16 :         int n = 0;
     271          16 :         size_t size = 0;
     272          16 :         ssize_t ksize = 0;
     273             : #pragma GCC diagnostic ignored "-Wformat"
     274             :         // icc warning #269: invalid format string conversion
     275             :         // key and value will be both newly allocated strings
     276          66 :         while ((n = fscanf (fp, format, &key, &value)) >= 0)
     277             :         {
     278             :                 ELEKTRA_LOG_DEBUG ("Read %d parts: '%s' with value '%s'", n, key, value);
     279          34 :                 if (n == 0)
     280             :                 {
     281             :                         // discard line
     282           0 :                         if (getline (&key, &size, fp) == -1 && !feof (fp))
     283             :                         {
     284           0 :                                 ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (
     285             :                                         parentKey, "Failed discarding rest of line of file %s at position %ld with key %s", filename,
     286             :                                         ftell (fp), key);
     287           0 :                                 elektraFree (key);
     288           0 :                                 fclose (fp);
     289           0 :                                 return -1;
     290             :                         }
     291             :                         ELEKTRA_LOG_DEBUG ("Discard '%s'", key);
     292           0 :                         elektraFree (key);
     293           0 :                         key = 0;
     294           0 :                         continue;
     295             :                 }
     296             : 
     297          34 :                 Key * read = keyNew (keyName (parentKey), KEY_END);
     298          34 :                 strippedkey = elektraStrip (key);
     299             : 
     300          34 :                 if (keyAddName (read, strippedkey) == -1)
     301             :                 {
     302           0 :                         ELEKTRA_ADD_VALIDATION_SYNTACTIC_WARNINGF (parentKey, "Key name '%s' is not valid, discarding key", strippedkey);
     303           0 :                         keyDel (read);
     304           0 :                         elektraFree (key);
     305           0 :                         if (n == 2)
     306             :                         {
     307           0 :                                 elektraFree (value);
     308             :                         }
     309           0 :                         continue;
     310             :                 }
     311             : 
     312          34 :                 if (n == 2)
     313             :                 {
     314          34 :                         keySetString (read, value);
     315          34 :                         elektraFree (value);
     316          34 :                         value = 0;
     317             :                 }
     318             : 
     319          34 :                 elektraFree (key);
     320          34 :                 key = 0;
     321             : 
     322          34 :                 if (ksAppendKey (returned, read) != ksize + 1)
     323             :                 {
     324           0 :                         ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (parentKey, "Duplicated key '%s' at position %ld in file %s",
     325             :                                                                  keyName (read), ftell (fp), filename);
     326           0 :                         elektraFree (format);
     327           0 :                         fclose (fp);
     328           0 :                         return -1;
     329             :                 }
     330             :                 ++ksize;
     331             :         }
     332             : 
     333          16 :         if (feof (fp) == 0)
     334             :         {
     335           0 :                 ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (parentKey, "Not at the end of file at position %ld in file %s", ftell (fp),
     336             :                                                          filename);
     337           0 :                 elektraFree (format);
     338           0 :                 fclose (fp);
     339           0 :                 return -1;
     340             :         }
     341             : 
     342          16 :         elektraFree (format);
     343          16 :         fclose (fp);
     344             : 
     345          16 :         return 1; /* success */
     346             : }
     347             : 
     348           5 : int elektraSimpleiniSet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned, Key * parentKey)
     349             : {
     350             :         /* set all keys */
     351             : 
     352           5 :         FILE * fp = fopen (keyString (parentKey), "w");
     353           5 :         if (!fp)
     354             :         {
     355           0 :                 ELEKTRA_SET_ERROR_SET (parentKey);
     356             :                 return -1;
     357             :         }
     358             : 
     359           5 :         char * format = getWriteFormat (handle);
     360             : 
     361             :         ELEKTRA_LOG ("Write to '%s' with format '%s'", keyString (parentKey), format);
     362             : 
     363             :         Key * cur;
     364           5 :         ksRewind (returned);
     365          15 :         while ((cur = ksNext (returned)) != 0)
     366             :         {
     367           5 :                 const char * name = elektraKeyGetRelativeName (cur, parentKey);
     368           5 :                 fprintf (fp, format, name, keyString (cur));
     369             :         }
     370             : 
     371           5 :         fclose (fp);
     372             : 
     373           5 :         elektraFree (format);
     374           5 :         return 1; /* success */
     375             : }
     376             : 
     377         415 : Plugin * ELEKTRA_PLUGIN_EXPORT
     378             : {
     379             :         // clang-format off
     380         415 :         return elektraPluginExport("simpleini",
     381             :                 ELEKTRA_PLUGIN_GET,     &elektraSimpleiniGet,
     382             :                 ELEKTRA_PLUGIN_SET,     &elektraSimpleiniSet,
     383             :                 ELEKTRA_PLUGIN_END);
     384             : }
     385             : 

Generated by: LCOV version 1.13