LCOV - code coverage report
Current view: top level - src/plugins/blockresolver - blockresolver.c (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 166 199 83.4 %
Date: 2019-09-12 12:28:41 Functions: 12 14 85.7 %

          Line data    Source code
       1             : /**
       2             :  * @file
       3             :  *
       4             :  * @brief Source for blockresolver plugin
       5             :  *
       6             :  * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
       7             :  *
       8             :  */
       9             : 
      10             : #include "blockresolver.h"
      11             : 
      12             : #include <kdberrors.h>
      13             : #include <kdbhelper.h>
      14             : #include <kdbproposal.h>
      15             : #include <stdio.h>
      16             : #include <stdlib.h>
      17             : #include <string.h>
      18             : #include <sys/stat.h>
      19             : #include <sys/time.h>
      20             : #include <time.h>
      21             : #include <unistd.h>
      22             : 
      23             : 
      24             : #include "../resolver/shared.h"
      25             : #include <kdbinvoke.h>
      26             : 
      27             : 
      28             : #define TV_MAX_DIGITS 26
      29             : #define BUFSIZE_MAX 1024
      30             : 
      31             : typedef struct
      32             : {
      33             :         char * tmpFile;
      34             :         char * realFile;
      35             :         char * identifier;
      36             :         time_t mtime;
      37             :         long startPos;
      38             :         long endPos;
      39             :         unsigned short getPass;
      40             :         unsigned short setPass;
      41             : } BlockData;
      42             : 
      43           7 : static int elektraResolveFilename (Key * parentKey, ElektraResolveTempfile tmpFile)
      44             : {
      45           7 :         int rc = 0;
      46           7 :         ElektraInvokeHandle * handle = elektraInvokeOpen ("resolver", 0, 0);
      47           7 :         if (!handle)
      48             :         {
      49             :                 rc = -1;
      50             :                 goto RESOLVE_FAILED;
      51             :         }
      52             : 
      53           7 :         ElektraResolved * resolved = NULL;
      54             :         typedef ElektraResolved * (*resolveFileFunc) (elektraNamespace, const char *, ElektraResolveTempfile, Key *);
      55           7 :         resolveFileFunc resolveFunc = *(resolveFileFunc *) elektraInvokeGetFunction (handle, "filename");
      56             : 
      57           7 :         if (!resolveFunc)
      58             :         {
      59             :                 rc = -1;
      60             :                 goto RESOLVE_FAILED;
      61             :         }
      62             : 
      63             :         typedef void (*freeHandleFunc) (ElektraResolved *);
      64           7 :         freeHandleFunc freeHandle = *(freeHandleFunc *) elektraInvokeGetFunction (handle, "freeHandle");
      65             : 
      66           7 :         if (!freeHandle)
      67             :         {
      68             :                 rc = -1;
      69             :                 goto RESOLVE_FAILED;
      70             :         }
      71           7 :         resolved = resolveFunc (keyGetNamespace (parentKey), keyString (parentKey), tmpFile, parentKey);
      72             : 
      73           7 :         if (!resolved)
      74             :         {
      75             :                 rc = -1;
      76             :                 goto RESOLVE_FAILED;
      77             :         }
      78             :         else
      79             :         {
      80           7 :                 keySetString (parentKey, resolved->fullPath);
      81           7 :                 freeHandle (resolved);
      82             :         }
      83             : 
      84             : RESOLVE_FAILED:
      85           7 :         elektraInvokeClose (handle, 0);
      86           7 :         return rc;
      87             : }
      88             : 
      89           2 : int elektraBlockresolverCheckFile (const char * filename ELEKTRA_UNUSED)
      90             : {
      91           2 :         return 1;
      92             : }
      93             : 
      94          11 : static const char * genTempFilename (void)
      95             : {
      96             :         struct timeval tv;
      97          11 :         gettimeofday (&tv, 0);
      98          11 :         const char * tmpFilePrefix = "/tmp/elektra_blockresolver_";
      99          11 :         char * tmpFile = elektraMalloc (strlen (tmpFilePrefix) + TV_MAX_DIGITS + 2);
     100          11 :         snprintf (tmpFile, strlen (tmpFilePrefix) + TV_MAX_DIGITS + 2, "%s%lu:" ELEKTRA_TIME_USEC_F, tmpFilePrefix, tv.tv_sec, tv.tv_usec);
     101          11 :         return tmpFile;
     102             : }
     103             : 
     104           7 : static int initData (Plugin * handle, Key * parentKey)
     105             : {
     106           7 :         BlockData * data = elektraPluginGetData (handle);
     107           7 :         if (!data)
     108             :         {
     109           7 :                 data = elektraCalloc (sizeof (BlockData));
     110           7 :                 elektraPluginSetData (handle, data);
     111           7 :                 KeySet * config = elektraPluginGetConfig (handle);
     112           7 :                 ksRewind (config);
     113           7 :                 Key * key = ksLookupByName (config, "/identifier", KDB_O_NONE);
     114           7 :                 if (!key) return -1;
     115           7 :                 data->identifier = (char *) keyString (key);
     116           7 :                 key = ksLookupByName (config, "/path", KDB_O_NONE);
     117           7 :                 if (!key) return -1;
     118           7 :                 keySetString (parentKey, keyString (key));
     119           7 :                 if (elektraResolveFilename (parentKey, ELEKTRA_RESOLVER_TEMPFILE_NONE) == -1)
     120             :                 {
     121             :                         return -1;
     122             :                 }
     123           7 :                 data->realFile = elektraStrDup (keyString (parentKey));
     124             :                 struct stat buf;
     125          14 :                 if (stat (data->realFile, &buf))
     126             :                 {
     127             :                         return -1;
     128             :                 }
     129           7 :                 data->mtime = buf.st_mtime;
     130           7 :                 data->tmpFile = (char *) genTempFilename ();
     131           7 :                 data->startPos = -1;
     132           7 :                 data->endPos = -1;
     133           7 :                 data->getPass = 0;
     134           7 :                 data->setPass = 0;
     135             :         }
     136             :         return 0;
     137             : }
     138             : 
     139          70 : int elektraBlockresolverClose (Plugin * handle ELEKTRA_UNUSED, Key * errorKey ELEKTRA_UNUSED)
     140             : {
     141             :         // free all plugin resources and shut it down
     142             :         // this function is optional
     143          70 :         BlockData * data = elektraPluginGetData (handle);
     144          70 :         if (data)
     145             :         {
     146           7 :                 if (data->tmpFile)
     147             :                 {
     148           7 :                         unlink (data->tmpFile);
     149           7 :                         elektraFree (data->tmpFile);
     150             :                 }
     151           7 :                 if (data->realFile) elektraFree (data->realFile);
     152           7 :                 elektraFree (data);
     153             :         }
     154          70 :         elektraPluginSetData (handle, 0);
     155          70 :         return 1; // success
     156             : }
     157             : 
     158           7 : static long getBlockStart (FILE * fp, const char * identifier)
     159             : {
     160           7 :         long position = -1;
     161             :         char buffer[BUFSIZE_MAX];
     162           7 :         fseek (fp, 0, SEEK_SET);
     163           7 :         while (fgets (buffer, sizeof (buffer), fp))
     164             :         {
     165          24 :                 if (!strncmp (buffer, identifier, strlen (identifier)))
     166             :                 {
     167           7 :                         if (!strcmp (buffer + strlen (identifier) + 1, "start\n"))
     168             :                         {
     169           7 :                                 position = ftell (fp);
     170           7 :                                 break;
     171             :                         }
     172             :                         else
     173             :                         {
     174             :                                 break;
     175             :                         }
     176             :                 }
     177             :         }
     178           7 :         return position;
     179             : }
     180             : 
     181           7 : static long getBlockEnd (FILE * fp, const char * identifier, long offset)
     182             : {
     183           7 :         if (offset < 0) return -1;
     184           7 :         long position = -1;
     185             :         char buffer[BUFSIZE_MAX];
     186           7 :         fseek (fp, offset, 0);
     187           7 :         while (fgets (buffer, sizeof (buffer), fp))
     188             :         {
     189          28 :                 if (!strncmp (buffer, identifier, strlen (identifier)))
     190             :                 {
     191           7 :                         if (!strcmp (buffer + strlen (identifier) + 1, "stop\n"))
     192             :                         {
     193           7 :                                 position = ftell (fp) - strlen (buffer);
     194           7 :                                 break;
     195             :                         }
     196             :                         else
     197             :                         {
     198             :                                 break;
     199             :                         }
     200             :                 }
     201             :         }
     202             :         return position;
     203             : }
     204             : 
     205          15 : static const char * getBlock (FILE * fp, const long startPos, const long endPos)
     206             : {
     207          15 :         fseek (fp, startPos, SEEK_SET);
     208          15 :         if (endPos <= startPos) return NULL;
     209          15 :         size_t blockSize = endPos - startPos;
     210          15 :         if (blockSize <= 0) return NULL;
     211          15 :         char * block = elektraMalloc (blockSize + 1);
     212          15 :         if (!block) return NULL;
     213          15 :         size_t read = fread (block, 1, blockSize, fp);
     214          15 :         if (read != blockSize)
     215             :         {
     216           0 :                 elektraFree (block);
     217           0 :                 return NULL;
     218             :         }
     219          15 :         block[read] = '\0';
     220          15 :         return block;
     221             : }
     222             : 
     223          41 : int elektraBlockresolverGet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned ELEKTRA_UNUSED, Key * parentKey ELEKTRA_UNUSED)
     224             : {
     225          41 :         if (!elektraStrCmp (keyName (parentKey), "system/elektra/modules/blockresolver"))
     226             :         {
     227          34 :                 KeySet * contract = ksNew (
     228             :                         30,
     229             :                         keyNew ("system/elektra/modules/blockresolver", KEY_VALUE, "blockresolver plugin waits for your orders", KEY_END),
     230             :                         keyNew ("system/elektra/modules/blockresolver/exports", KEY_END),
     231             :                         keyNew ("system/elektra/modules/blockresolver/exports/close", KEY_FUNC, elektraBlockresolverClose, KEY_END),
     232             :                         keyNew ("system/elektra/modules/blockresolver/exports/error", KEY_FUNC, elektraBlockresolverError, KEY_END),
     233             :                         keyNew ("system/elektra/modules/blockresolver/exports/get", KEY_FUNC, elektraBlockresolverGet, KEY_END),
     234             :                         keyNew ("system/elektra/modules/blockresolver/exports/set", KEY_FUNC, elektraBlockresolverSet, KEY_END),
     235             :                         keyNew ("system/elektra/modules/blockresolver/exports/commit", KEY_FUNC, elektraBlockresolverCommit, KEY_END),
     236             :                         keyNew ("system/elektra/modules/blockresolver/exports/checkfile", KEY_FUNC, elektraBlockresolverCheckFile, KEY_END),
     237             : #include ELEKTRA_README
     238             :                         keyNew ("system/elektra/modules/blockresolver/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END), KS_END);
     239          34 :                 ksAppend (returned, contract);
     240          34 :                 ksDel (contract);
     241             : 
     242          34 :                 return 1; // success
     243             :         }
     244           7 :         int rc = initData (handle, parentKey);
     245           7 :         if (rc) return -1;
     246             : 
     247           7 :         int retVal = 0;
     248           7 :         BlockData * data = elektraPluginGetData (handle);
     249           7 :         keySetString (parentKey, data->tmpFile);
     250           7 :         FILE * fin = NULL;
     251           7 :         FILE * fout = NULL;
     252           7 :         char * block = NULL;
     253             : 
     254           7 :         if (data->getPass > 0)
     255             :         {
     256             :                 struct stat buf;
     257           0 :                 if (stat (data->realFile, &buf))
     258             :                 {
     259           0 :                         ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey, "Failed to stat file %s\n. Reason: %s", data->realFile, strerror (errno));
     260           0 :                         return -1;
     261             :                 }
     262           0 :                 if (buf.st_mtime == data->mtime) return 0;
     263             :         }
     264             : 
     265           7 :         fin = fopen (data->realFile, "r");
     266           7 :         if (!fin)
     267             :         {
     268           0 :                 ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Couldn't open %s for reading. Reason: %s", data->realFile, strerror (errno));
     269           0 :                 goto GET_CLEANUP;
     270             :         }
     271             : 
     272           7 :         data->startPos = getBlockStart (fin, data->identifier);
     273           7 :         if (data->startPos == -1) goto GET_CLEANUP;
     274           7 :         data->endPos = getBlockEnd (fin, data->identifier, data->startPos);
     275           7 :         if (data->endPos == -1)
     276             :         {
     277           0 :                 ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (parentKey, "Couldn't find end of block %s", data->identifier);
     278           0 :                 retVal = -1;
     279           0 :                 goto GET_CLEANUP;
     280             :         }
     281           7 :         block = (char *) getBlock (fin, data->startPos, data->endPos);
     282           7 :         if (!block)
     283             :         {
     284           0 :                 ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (parentKey, "Failed to extract block %s\n", data->identifier);
     285           0 :                 retVal = -1;
     286           0 :                 goto GET_CLEANUP;
     287             :         }
     288           7 :         fclose (fin);
     289           7 :         fin = NULL;
     290           7 :         fout = fopen (data->tmpFile, "w");
     291           7 :         if (!fout)
     292             :         {
     293           0 :                 ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Couldn't open %s for writing. Reason: %s", data->tmpFile, strerror (errno));
     294           0 :                 retVal = -1;
     295           0 :                 goto GET_CLEANUP;
     296             :         }
     297           7 :         size_t blockSize = data->endPos - data->startPos;
     298           7 :         fwrite (block, 1, blockSize, fout);
     299           7 :         retVal = 1;
     300           7 :         ++(data->getPass);
     301             : GET_CLEANUP:
     302           7 :         if (fin) fclose (fin);
     303           7 :         if (fout) fclose (fout);
     304           7 :         if (block) elektraFree (block);
     305             :         return retVal; // success
     306             : }
     307             : 
     308           8 : int elektraBlockresolverSet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned ELEKTRA_UNUSED, Key * parentKey ELEKTRA_UNUSED)
     309             : {
     310           8 :         BlockData * data = elektraPluginGetData (handle);
     311           8 :         if (!data) return -1;
     312           8 :         keySetString (parentKey, data->tmpFile);
     313             :         struct stat buf;
     314          16 :         if (stat (data->realFile, &buf))
     315             :         {
     316           0 :                 ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey, "Failed to stat file %s\n. Reason: %s", data->realFile, strerror (errno));
     317           0 :                 return -1;
     318             :         }
     319           8 :         if (buf.st_mtime > data->mtime)
     320             :         {
     321           0 :                 ELEKTRA_SET_CONFLICTING_STATE_ERRORF (parentKey, "File '%s' has been modified", data->realFile);
     322           0 :                 return -1;
     323             :         }
     324           8 :         FILE * fout = NULL;
     325           8 :         FILE * fin = NULL;
     326           8 :         char * block = NULL;
     327           8 :         char * mergeFile = NULL;
     328           8 :         int retVal = -1;
     329           8 :         if (!data->setPass)
     330             :         {
     331           4 :                 ++(data->setPass);
     332           4 :                 return 1;
     333             :         }
     334           4 :         else if (data->setPass == 1)
     335             :         {
     336             :                 // commit phase
     337           4 :                 mergeFile = (char *) genTempFilename ();
     338           4 :                 fout = fopen (mergeFile, "w");
     339           4 :                 if (!fout)
     340             :                 {
     341           0 :                         ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Couldn't open %s for writing. Reason: %s", data->realFile,
     342             :                                                      strerror (errno));
     343           0 :                         goto SET_CLEANUP;
     344             :                 }
     345           4 :                 fin = fopen (data->realFile, "r");
     346           4 :                 if (!fin)
     347             :                 {
     348           0 :                         ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Couldn't open %s for reading. Reason: %s", data->realFile,
     349             :                                                      strerror (errno));
     350           0 :                         goto SET_CLEANUP;
     351             :                 }
     352           4 :                 block = (char *) getBlock (fin, 0, data->startPos);
     353           4 :                 if (!block)
     354             :                 {
     355           0 :                         ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (parentKey, "Failed to extract block before %s\n", data->identifier);
     356           0 :                         goto SET_CLEANUP;
     357             :                 }
     358           4 :                 fwrite (block, 1, data->startPos, fout);
     359           4 :                 fseek (fin, 0, SEEK_END);
     360           4 :                 elektraFree (block);
     361           4 :                 block = NULL;
     362           4 :                 size_t blockSize = ftell (fin) - data->endPos;
     363           4 :                 block = (char *) getBlock (fin, data->endPos, ftell (fin));
     364           4 :                 if (!block)
     365             :                 {
     366           0 :                         ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (parentKey, "Failed to extract block after %s\n", data->identifier);
     367           0 :                         goto SET_CLEANUP;
     368             :                 }
     369           4 :                 fclose (fin);
     370           4 :                 fin = fopen (data->tmpFile, "r");
     371           4 :                 if (!fin)
     372             :                 {
     373           0 :                         ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Couldn't open %s for reading Reason: %s", data->tmpFile, strerror (errno));
     374           0 :                         goto SET_CLEANUP;
     375             :                 }
     376             :                 char buffer[BUFSIZE_MAX];
     377             :                 size_t read = 0;
     378           7 :                 while ((read = fread (buffer, 1, sizeof (buffer), fin)) > 0)
     379             :                 {
     380           3 :                         fwrite (buffer, 1, read, fout);
     381             :                 }
     382           4 :                 fwrite (block, 1, blockSize, fout);
     383           4 :                 retVal = 1;
     384             :         }
     385             : 
     386             : SET_CLEANUP:
     387           4 :         if (fin) fclose (fin);
     388           4 :         if (fout) fclose (fout);
     389           4 :         if (block) elektraFree (block);
     390             : 
     391           4 :         if (retVal == 1)
     392             :         {
     393           4 :                 if (rename (mergeFile, data->realFile) == -1) retVal = -1;
     394           4 :                 elektraFree (mergeFile);
     395           4 :                 mergeFile = NULL;
     396             :         }
     397           4 :         if (mergeFile) elektraFree (mergeFile);
     398             :         return retVal; // success
     399             : }
     400             : 
     401           0 : int elektraBlockresolverError (Plugin * handle ELEKTRA_UNUSED, KeySet * returned ELEKTRA_UNUSED, Key * parentKey ELEKTRA_UNUSED)
     402             : {
     403             :         // set all keys
     404             :         // this function is optional
     405             : 
     406           0 :         return 1; // success
     407             : }
     408             : 
     409           2 : int elektraBlockresolverCommit (Plugin * handle ELEKTRA_UNUSED, KeySet * returned ELEKTRA_UNUSED, Key * parentKey ELEKTRA_UNUSED)
     410             : {
     411           2 :         return elektraBlockresolverSet (handle, returned, parentKey);
     412             : }
     413             : 
     414          70 : Plugin * ELEKTRA_PLUGIN_EXPORT
     415             : {
     416             :         // clang-format off
     417          70 :     return elektraPluginExport ("blockresolver",
     418             :             ELEKTRA_PLUGIN_CLOSE,       &elektraBlockresolverClose,
     419             :             ELEKTRA_PLUGIN_ERROR, &elektraBlockresolverError,
     420             :             ELEKTRA_PLUGIN_GET, &elektraBlockresolverGet,
     421             :             ELEKTRA_PLUGIN_SET, &elektraBlockresolverSet,
     422             :             ELEKTRA_PLUGIN_COMMIT, &elektraBlockresolverCommit,
     423             :             ELEKTRA_PLUGIN_END);
     424             : }
     425             : 

Generated by: LCOV version 1.13