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