Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief Source for specload plugin
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : *
8 : */
9 :
10 : #include "specload.h"
11 :
12 : #include <kdberrors.h>
13 : #include <kdbhelper.h>
14 :
15 : #include <kdbease.h>
16 : #include <kdbinvoke.h>
17 : #include <kdbmodule.h>
18 : #include <kdbproposal.h>
19 : #include <stdio.h>
20 : #include <stdlib.h>
21 : #include <unistd.h>
22 : #include <utime.h>
23 :
24 : // keep #ifdef in sync with kdb export
25 : #ifdef _WIN32
26 : #define STDIN_FILENAME ("CON")
27 : #define STDOUT_FILENAME ("CON")
28 : #else
29 : #define STDIN_FILENAME ("/dev/stdin")
30 : #define STDOUT_FILENAME ("/dev/stdout")
31 : #endif
32 :
33 : struct change
34 : {
35 : const char * meta;
36 : bool add;
37 : bool edit;
38 : bool remove;
39 : };
40 :
41 : // TODO: allow more changes
42 : static struct change allowedChanges[] = { { "description", true, true, true },
43 : { "opt/help", true, true, true },
44 : { "default", true, true, false },
45 : { "type", true, false, false },
46 : { NULL, false, false, false } };
47 :
48 : static bool readConfig (KeySet * conf, char ** directFilePtr, char ** appPtr, char *** argvPtr, Key * errorKey);
49 : static bool loadSpec (KeySet * returned, const char * directFile, const char * app, char * argv[], Key * parentKey,
50 : ElektraInvokeHandle * quickDump);
51 : static int isChangeAllowed (Key * oldKey, Key * newKey);
52 : static KeySet * calculateMetaDiff (Key * oldKey, Key * newKey);
53 :
54 57 : static inline void freeArgv (char ** argv)
55 : {
56 57 : if (argv != NULL)
57 : {
58 : size_t index = 0;
59 60 : while (argv[index] != NULL)
60 : {
61 40 : elektraFree (argv[index]);
62 40 : ++index;
63 : }
64 20 : elektraFree (argv);
65 : }
66 57 : }
67 :
68 0 : static int copyError (Key * dest, Key * src)
69 : {
70 0 : keyRewindMeta (src);
71 0 : const Key * metaKey = keyGetMeta (src, "error");
72 0 : if (!metaKey) return 0;
73 0 : keySetMeta (dest, keyName (metaKey), keyString (metaKey));
74 0 : while ((metaKey = keyNextMeta (src)) != NULL)
75 : {
76 0 : if (strncmp (keyName (metaKey), "error/", 6) != 0) break;
77 0 : keySetMeta (dest, keyName (metaKey), keyString (metaKey));
78 : }
79 : return 1;
80 : }
81 :
82 108 : int elektraSpecloadOpen (Plugin * handle, Key * errorKey)
83 : {
84 108 : Specload * specload = elektraMalloc (sizeof (Specload));
85 :
86 108 : KeySet * conf = elektraPluginGetConfig (handle);
87 108 : if (ksLookupByName (conf, "system/module", 0) != NULL || ksLookupByName (conf, "system/sendspec", 0) != NULL)
88 : {
89 70 : elektraFree (specload);
90 70 : return ELEKTRA_PLUGIN_STATUS_SUCCESS;
91 : }
92 :
93 38 : if (!readConfig (conf, &specload->directFile, &specload->app, &specload->argv, errorKey))
94 : {
95 2 : elektraFree (specload);
96 2 : return ELEKTRA_PLUGIN_STATUS_ERROR;
97 : }
98 :
99 36 : specload->quickDumpConfig = ksNew (0, KS_END);
100 36 : specload->quickDump = elektraInvokeOpen ("quickdump", specload->quickDumpConfig, errorKey);
101 :
102 36 : if (!specload->quickDump)
103 : {
104 0 : elektraFree (specload);
105 0 : return ELEKTRA_PLUGIN_STATUS_ERROR;
106 : }
107 :
108 36 : elektraPluginSetData (handle, specload);
109 :
110 36 : return ELEKTRA_PLUGIN_STATUS_SUCCESS;
111 : }
112 :
113 108 : int elektraSpecloadClose (Plugin * handle, Key * errorKey)
114 : {
115 108 : Specload * specload = elektraPluginGetData (handle);
116 :
117 108 : if (specload != NULL)
118 : {
119 36 : elektraInvokeClose (specload->quickDump, errorKey);
120 :
121 36 : ksDel (specload->quickDumpConfig);
122 :
123 36 : if (specload->directFile != NULL)
124 : {
125 26 : elektraFree (specload->directFile);
126 : }
127 :
128 36 : if (specload->app != NULL)
129 : {
130 10 : elektraFree (specload->app);
131 : }
132 :
133 36 : freeArgv (specload->argv);
134 :
135 36 : elektraFree (specload);
136 36 : elektraPluginSetData (handle, NULL);
137 : }
138 :
139 108 : return ELEKTRA_PLUGIN_STATUS_SUCCESS;
140 : }
141 :
142 : /**
143 : * Sends the given specification (@p spec) over stdout, to be received by the process using specload.
144 : *
145 : * Note: To use this function with elektraInvoke2Args, call elektraInvokeOpen with a config containing
146 : * the key 'system/sendspec'. This postpones the check for an existent app until elektraSpecloadGet is called.
147 : *
148 : * @param handle A specload plugin handle.
149 : * @param spec The specification to send.
150 : * @param parentKey The parent key under which the target specload instance was mounted. Value unused.
151 : *
152 : * @retval #ELEKTRA_PLUGIN_STATUS_SUCCESS on success
153 : * @retval #ELEKTRA_PLUGIN_STATUS_ERROR on error
154 : */
155 46 : int elektraSpecloadSendSpec (Plugin * handle ELEKTRA_UNUSED, KeySet * spec, Key * parentKey)
156 : {
157 46 : Key * errorKey = keyNew (0, KEY_END);
158 :
159 46 : KeySet * quickDumpConf = ksNew (0, KS_END);
160 :
161 46 : if (keyGetMeta (parentKey, "system/elektra/quickdump/noparent") != NULL)
162 : {
163 2 : ksAppendKey (quickDumpConf, keyNew ("system/noparent", KEY_END));
164 : }
165 :
166 46 : ElektraInvokeHandle * quickDump = elektraInvokeOpen ("quickdump", quickDumpConf, errorKey);
167 :
168 46 : Key * quickDumpParent = keyNew (keyName (parentKey), KEY_VALUE, STDOUT_FILENAME, KEY_END);
169 :
170 46 : int result = elektraInvoke2Args (quickDump, "set", spec, quickDumpParent);
171 :
172 46 : elektraInvokeClose (quickDump, errorKey);
173 46 : keyDel (errorKey);
174 46 : keyDel (quickDumpParent);
175 46 : ksDel (quickDumpConf);
176 :
177 46 : return result == ELEKTRA_PLUGIN_STATUS_SUCCESS ? ELEKTRA_PLUGIN_STATUS_SUCCESS : ELEKTRA_PLUGIN_STATUS_ERROR;
178 : }
179 :
180 84 : int elektraSpecloadGet (Plugin * handle, KeySet * returned, Key * parentKey)
181 : {
182 84 : if (!elektraStrCmp (keyName (parentKey), "system/elektra/modules/specload"))
183 : {
184 74 : KeySet * contract =
185 74 : ksNew (30, keyNew ("system/elektra/modules/specload", KEY_VALUE, "specload plugin waits for your orders", KEY_END),
186 : keyNew ("system/elektra/modules/specload/exports", KEY_END),
187 : keyNew ("system/elektra/modules/specload/exports/open", KEY_FUNC, elektraSpecloadOpen, KEY_END),
188 : keyNew ("system/elektra/modules/specload/exports/close", KEY_FUNC, elektraSpecloadClose, KEY_END),
189 : keyNew ("system/elektra/modules/specload/exports/get", KEY_FUNC, elektraSpecloadGet, KEY_END),
190 : keyNew ("system/elektra/modules/specload/exports/set", KEY_FUNC, elektraSpecloadSet, KEY_END),
191 : keyNew ("system/elektra/modules/specload/exports/checkconf", KEY_FUNC, elektraSpecloadCheckConfig, KEY_END),
192 : keyNew ("system/elektra/modules/specload/exports/sendspec", KEY_FUNC, elektraSpecloadSendSpec, KEY_END),
193 : #include ELEKTRA_README
194 : keyNew ("system/elektra/modules/specload/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END), KS_END);
195 74 : ksAppend (returned, contract);
196 74 : ksDel (contract);
197 :
198 74 : return ELEKTRA_PLUGIN_STATUS_SUCCESS;
199 : }
200 :
201 10 : if (keyGetNamespace (parentKey) != KEY_NS_SPEC)
202 : {
203 0 : ELEKTRA_SET_INTERFACE_ERROR (parentKey, "This plugin can only be used for the spec namespace");
204 0 : return ELEKTRA_PLUGIN_STATUS_ERROR;
205 : }
206 :
207 10 : Specload * specload = elektraPluginGetData (handle);
208 :
209 10 : KeySet * spec = ksNew (0, KS_END);
210 :
211 10 : if (!loadSpec (spec, specload->directFile, specload->app, specload->argv, parentKey, specload->quickDump))
212 : {
213 0 : ksDel (spec);
214 0 : ELEKTRA_SET_INSTALLATION_ERROR (
215 : parentKey, "Couldn't load the base specification. Make sure the app is available and the arguments are correct");
216 0 : return ELEKTRA_PLUGIN_STATUS_ERROR;
217 : }
218 :
219 10 : const char * overlayFile = keyString (parentKey);
220 10 : if (overlayFile[0] != '/')
221 : {
222 2 : char * path = elektraFormat ("%s/%s", KDB_DB_SPEC, overlayFile);
223 2 : keySetString (parentKey, path);
224 2 : elektraFree (path);
225 : }
226 :
227 10 : if (access (keyString (parentKey), F_OK) != -1)
228 : {
229 4 : if (elektraInvoke2Args (specload->quickDump, "get", spec, parentKey) == ELEKTRA_PLUGIN_STATUS_ERROR)
230 : {
231 0 : ksDel (spec);
232 0 : ELEKTRA_SET_INSTALLATION_ERROR (parentKey, "Couldn't load the overlay specification");
233 0 : return ELEKTRA_PLUGIN_STATUS_ERROR;
234 : }
235 : }
236 :
237 10 : ksAppend (returned, spec);
238 10 : ksDel (spec);
239 :
240 10 : return ELEKTRA_PLUGIN_STATUS_SUCCESS;
241 : }
242 :
243 56 : int elektraSpecloadSet (Plugin * handle, KeySet * returned, Key * parentKey)
244 : {
245 56 : if (keyGetNamespace (parentKey) != KEY_NS_SPEC)
246 : {
247 0 : ELEKTRA_SET_INTERFACE_ERROR (parentKey, "This plugin can only be used for the spec namespace");
248 0 : return ELEKTRA_PLUGIN_STATUS_ERROR;
249 : }
250 :
251 56 : Specload * specload = elektraPluginGetData (handle);
252 :
253 56 : KeySet * spec = ksNew (0, KS_END);
254 56 : if (!loadSpec (spec, specload->directFile, specload->app, specload->argv, parentKey, specload->quickDump))
255 : {
256 0 : ksDel (spec);
257 0 : ELEKTRA_SET_INSTALLATION_ERROR (
258 : parentKey, "Couldn't load the base specification. Make sure the app is available and the arguments are correct");
259 0 : return ELEKTRA_PLUGIN_STATUS_ERROR;
260 : }
261 :
262 56 : const char * overlayFile = keyString (parentKey);
263 56 : if (overlayFile[0] != '/')
264 : {
265 0 : char * path = elektraFormat ("%s/%s", KDB_DB_SPEC, overlayFile);
266 0 : keySetString (parentKey, path);
267 0 : elektraFree (path);
268 : }
269 :
270 56 : KeySet * oldData = ksNew (ksGetSize (returned), KS_END);
271 56 : if (access (keyString (parentKey), F_OK) != -1)
272 : {
273 52 : if (elektraInvoke2Args (specload->quickDump, "get", oldData, parentKey) == ELEKTRA_PLUGIN_STATUS_ERROR)
274 : {
275 0 : ksDel (oldData);
276 0 : ELEKTRA_SET_INSTALLATION_ERROR (parentKey, "Couldn't load the overlay specification");
277 0 : return ELEKTRA_PLUGIN_STATUS_ERROR;
278 : }
279 : }
280 :
281 56 : KeySet * overrides = ksNew (0, KS_END);
282 :
283 56 : cursor_t cursor = ksGetCursor (returned);
284 56 : ksRewind (returned);
285 : Key * new;
286 : Key * old;
287 156 : while ((new = ksNext (returned)) != NULL)
288 : {
289 56 : old = ksLookup (oldData, new, KDB_O_POP);
290 56 : if (old == NULL)
291 : {
292 28 : old = ksLookup (spec, new, 0);
293 : }
294 :
295 56 : int changeAllowed = isChangeAllowed (old, new);
296 56 : keyDel (old);
297 :
298 56 : if (changeAllowed < 0)
299 : {
300 12 : ELEKTRA_SET_RESOURCE_ERROR (parentKey, "This kind of change is not allowed");
301 12 : ksSetCursor (returned, cursor);
302 12 : ksDel (overrides);
303 12 : ksDel (oldData);
304 12 : ksDel (spec);
305 12 : return ELEKTRA_PLUGIN_STATUS_ERROR;
306 : }
307 :
308 44 : if (changeAllowed > 0)
309 : {
310 36 : ksAppendKey (overrides, new);
311 : }
312 : }
313 44 : ksDel (spec);
314 :
315 : // check if remaining old keys can be removed
316 88 : while ((old = ksNext (oldData)) != NULL)
317 : {
318 0 : if (isChangeAllowed (old, NULL) > 0)
319 : {
320 0 : ksSetCursor (returned, cursor);
321 0 : ksDel (overrides);
322 0 : ksDel (oldData);
323 0 : return ELEKTRA_PLUGIN_STATUS_ERROR;
324 : }
325 : }
326 44 : ksDel (oldData);
327 :
328 44 : ksSetCursor (returned, cursor);
329 :
330 44 : int result = elektraInvoke2Args (specload->quickDump, "set", overrides, parentKey);
331 44 : ksDel (overrides);
332 44 : return result;
333 : }
334 :
335 21 : int elektraSpecloadCheckConfig (Key * errorKey, KeySet * conf)
336 : {
337 : char * directFile;
338 : char * app;
339 : char ** argv;
340 :
341 21 : if (!readConfig (conf, &directFile, &app, &argv, errorKey))
342 : {
343 : return ELEKTRA_PLUGIN_STATUS_ERROR;
344 : }
345 :
346 21 : bool directFileMode = directFile != NULL;
347 :
348 21 : KeySet * quickDumpConfig = ksNew (0, KS_END);
349 21 : ElektraInvokeHandle * quickDump = elektraInvokeOpen ("quickdump", quickDumpConfig, errorKey);
350 :
351 21 : KeySet * spec = ksNew (0, KS_END);
352 :
353 21 : bool result = loadSpec (spec, directFile, app, argv, errorKey, quickDump);
354 :
355 21 : elektraInvokeClose (quickDump, errorKey);
356 21 : ksDel (quickDumpConfig);
357 21 : elektraFree (directFile);
358 21 : elektraFree (app);
359 21 : freeArgv (argv);
360 21 : ksDel (spec);
361 :
362 21 : if (!result)
363 : {
364 0 : if (directFileMode)
365 : {
366 0 : ELEKTRA_SET_INSTALLATION_ERROR (
367 : errorKey, "Couldn't load the specification. Make sure the specified file is a valid quickdump file");
368 : }
369 : else
370 : {
371 0 : ELEKTRA_SET_INSTALLATION_ERROR (
372 : errorKey, "Couldn't load the specification. Make sure the app is available and the arguments are correct");
373 : }
374 : return ELEKTRA_PLUGIN_STATUS_ERROR;
375 : }
376 :
377 : return ELEKTRA_PLUGIN_STATUS_NO_UPDATE;
378 : }
379 :
380 59 : bool readConfig (KeySet * conf, char ** directFilePtr, char ** appPtr, char *** argvPtr, Key * errorKey)
381 : {
382 59 : Key * fileKey = ksLookupByName (conf, "/file", 0);
383 :
384 59 : if (fileKey != NULL)
385 : {
386 37 : const char * directFile = keyString (fileKey);
387 :
388 37 : if (directFile[0] != '/')
389 : {
390 0 : ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (errorKey, "The value of the file config key '%s' is not an absolute path",
391 : directFile);
392 0 : return false;
393 : }
394 :
395 37 : if (access (directFile, R_OK) != 0)
396 : {
397 0 : ELEKTRA_SET_RESOURCE_ERRORF (errorKey, "File '%s' doesn't exist or cannot be read", directFile);
398 0 : return false;
399 : }
400 :
401 37 : *directFilePtr = elektraStrDup (directFile);
402 37 : *appPtr = NULL;
403 37 : *argvPtr = NULL;
404 :
405 37 : return true;
406 : }
407 :
408 22 : Key * appKey = ksLookupByName (conf, "/app", 0);
409 :
410 22 : if (appKey == NULL)
411 : {
412 2 : ELEKTRA_SET_INSTALLATION_ERROR (errorKey, "You need to set an application using the app config key");
413 2 : return false;
414 : }
415 :
416 20 : const char * app = keyString (appKey);
417 :
418 20 : if (app[0] != '/')
419 : {
420 0 : ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (errorKey, "The value of the app config key '%s' is not an absolute path", app);
421 0 : return false;
422 : }
423 :
424 20 : if (access (app, X_OK) != 0)
425 : {
426 0 : ELEKTRA_SET_RESOURCE_ERRORF (errorKey, "App '%s' doesn't exist or is not executable", app);
427 0 : return false;
428 : }
429 :
430 : KeySet * args;
431 20 : if (ksLookupByName (conf, "/app/args", 0) == NULL)
432 : {
433 20 : args = ksNew (1, keyNew ("user/app/args/#0", KEY_VALUE, "--elektra-spec", KEY_END), KS_END);
434 : }
435 : else
436 : {
437 0 : Key * parentKey = keyNew ("/app/args", KEY_END);
438 0 : args = elektraArrayGet (parentKey, conf);
439 0 : keyDel (parentKey);
440 : }
441 :
442 20 : ssize_t size = ksGetSize (args);
443 20 : char ** argv = elektraMalloc ((size + 2) * sizeof (char *));
444 20 : argv[0] = elektraStrDup (app);
445 :
446 20 : size_t index = 1;
447 20 : ksRewind (args);
448 : Key * cur;
449 60 : while ((cur = ksNext (args)) != NULL)
450 : {
451 20 : argv[index] = elektraStrDup (keyString (cur));
452 20 : ++index;
453 : }
454 20 : argv[index] = NULL;
455 20 : ksDel (args);
456 :
457 20 : *directFilePtr = NULL;
458 20 : *appPtr = elektraStrDup (app);
459 20 : *argvPtr = argv;
460 :
461 20 : return true;
462 : }
463 :
464 87 : bool loadSpec (KeySet * returned, const char * directFile, const char * app, char * argv[], Key * parentKey,
465 : ElektraInvokeHandle * quickDump)
466 : {
467 87 : if (directFile != NULL)
468 : {
469 45 : Key * quickDumpParent = keyNew (keyName (parentKey), KEY_VALUE, directFile, KEY_END);
470 45 : int result = elektraInvoke2Args (quickDump, "get", returned, quickDumpParent);
471 :
472 45 : if (result != ELEKTRA_PLUGIN_STATUS_SUCCESS)
473 : {
474 0 : copyError (parentKey, quickDumpParent);
475 : }
476 45 : keyDel (quickDumpParent);
477 :
478 45 : return result == ELEKTRA_PLUGIN_STATUS_SUCCESS;
479 : }
480 :
481 : pid_t pid;
482 : int fd[2];
483 :
484 42 : if (pipe (fd) != 0)
485 : {
486 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Could not execute app. Reason: %s", strerror (errno));
487 0 : return false;
488 : }
489 :
490 42 : pid = fork ();
491 :
492 84 : if (pid == -1)
493 : {
494 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Could not execute app. Reason: %s", strerror (errno));
495 0 : return false;
496 : }
497 :
498 84 : if (pid == 0)
499 : {
500 : // child
501 42 : if (dup2 (fd[1], STDOUT_FILENO) == -1)
502 : {
503 0 : exit (EXIT_FAILURE);
504 : }
505 :
506 42 : close (fd[0]);
507 42 : close (fd[1]);
508 :
509 42 : execv (app, argv);
510 :
511 42 : exit (EXIT_FAILURE);
512 : }
513 :
514 : // parent
515 42 : close (fd[1]);
516 :
517 42 : int stdin_copy = dup (STDIN_FILENO);
518 :
519 42 : if (dup2 (fd[0], STDIN_FILENO) == -1)
520 : {
521 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Could not execute app. Reason: %s", strerror (errno));
522 0 : return false;
523 : }
524 :
525 42 : close (fd[0]);
526 :
527 42 : Key * quickDumpParent = keyNew (keyName (parentKey), KEY_VALUE, STDIN_FILENAME, KEY_END);
528 :
529 42 : int result = elektraInvoke2Args (quickDump, "get", returned, quickDumpParent);
530 :
531 42 : if (result != ELEKTRA_PLUGIN_STATUS_SUCCESS)
532 : {
533 0 : copyError (parentKey, quickDumpParent);
534 : }
535 42 : keyDel (quickDumpParent);
536 :
537 42 : if (dup2 (stdin_copy, STDIN_FILENO) == -1)
538 : {
539 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Could not execute app. Reason: %s", strerror (errno));
540 0 : return false;
541 : }
542 42 : close (stdin_copy);
543 :
544 42 : return result == ELEKTRA_PLUGIN_STATUS_SUCCESS;
545 : }
546 :
547 : /**
548 : * Checks whether the @p oldKey can be changed into @p newKey safely.
549 : *
550 : * Both @p oldKey and @p newKey may be NULL, to represent adding and
551 : * removing Keys respectively.
552 : *
553 : * @param oldKey old Key, or NULL for added Key
554 : * @param newKey new Key, or NULL for removed Key
555 : *
556 : * @retval 0 no change detected
557 : * @retval 1 change allowed
558 : * @retval -1 change forbidden
559 : * @retval -2 error, e.g. different keynames
560 : */
561 56 : int isChangeAllowed (Key * oldKey, Key * newKey)
562 : {
563 56 : if (oldKey == newKey)
564 : {
565 : // same key (pointer)
566 : return 0;
567 : }
568 :
569 56 : keyswitch_t changes = keyCompare (oldKey, newKey);
570 56 : if (changes == 0)
571 : {
572 : // equal keys
573 : return 0;
574 : }
575 :
576 48 : if (changes != KEY_NULL && changes != KEY_META)
577 : {
578 : // only metadata changes allowed
579 : return -1;
580 : }
581 :
582 40 : if ((changes & KEY_NAME) != 0)
583 : {
584 : // different key names
585 : return -2;
586 : }
587 :
588 40 : if (oldKey == NULL)
589 : {
590 20 : if (keyIsBinary (newKey) ? keyValue (newKey) != NULL : strlen (keyString (newKey)) > 0)
591 : {
592 : // adding values not allowed
593 : return -1;
594 : }
595 :
596 16 : oldKey = keyNew (keyName (newKey), KEY_END);
597 : }
598 : else
599 : {
600 20 : oldKey = keyDup (oldKey);
601 : }
602 :
603 36 : if (newKey == NULL)
604 : {
605 0 : if (keyIsBinary (oldKey) ? keyValue (oldKey) != NULL : strlen (keyString (oldKey)) > 0)
606 : {
607 : // removing values not allowed
608 : return -1;
609 : }
610 :
611 0 : newKey = keyNew (keyName (oldKey), KEY_END);
612 : }
613 : else
614 : {
615 36 : newKey = keyDup (newKey);
616 : }
617 :
618 36 : KeySet * metaDiff = calculateMetaDiff (oldKey, newKey);
619 :
620 36 : keyDel (oldKey);
621 36 : keyDel (newKey);
622 :
623 180 : for (int i = 0; allowedChanges[i].meta != NULL; ++i)
624 : {
625 144 : struct change cur = allowedChanges[i];
626 144 : Key * diff = ksLookupByName (metaDiff, cur.meta, KDB_O_POP);
627 :
628 144 : if (diff == NULL)
629 : {
630 96 : continue;
631 : }
632 :
633 48 : if (strcmp (keyString (diff), "add") == 0 && !cur.add)
634 : {
635 0 : keyDel (diff);
636 0 : ksDel (metaDiff);
637 : // add not allowed
638 0 : return -1;
639 : }
640 :
641 48 : if (strcmp (keyString (diff), "edit") == 0 && !cur.edit)
642 : {
643 0 : keyDel (diff);
644 0 : ksDel (metaDiff);
645 : // edit not allowed
646 0 : return -1;
647 : }
648 :
649 48 : if (strcmp (keyString (diff), "remove") == 0 && !cur.remove)
650 : {
651 0 : keyDel (diff);
652 0 : ksDel (metaDiff);
653 : // remove not allowed
654 0 : return -1;
655 : }
656 :
657 48 : keyDel (diff);
658 : }
659 :
660 36 : size_t size = ksGetSize (metaDiff);
661 :
662 36 : ksDel (metaDiff);
663 :
664 36 : return size == 0 ? 1 : -1;
665 : }
666 :
667 : /**
668 : * Calculate a diff for the metadata of two keys.
669 : *
670 : * For each meta key that is different between @p oldKey and @p newKey,
671 : * a key will be created in the resulting KeySet. The name of this key
672 : * is the name of the metakey. The value of the key is determined as follows:
673 : * <ul>
674 : * <li>If @p oldKey has a meta key not present in @p newKey: value = "remove"</li>
675 : * <li>If @p newKey has a meta key not present in @p oldKey: value = "add"</li>
676 : * <li>If metakey is present in both @p oldKey and @p newKey, but its value changed: value = "edit"</li>
677 : * </ul>
678 : * Additionally the old and new values are stored in the metakeys `old` and `new` respectively.
679 : *
680 : * @param oldKey the old key
681 : * @param newKey the new key
682 : * @return a KeySet (has to be `ksDel`ed) containing the diff
683 : */
684 36 : KeySet * calculateMetaDiff (Key * oldKey, Key * newKey)
685 : {
686 36 : KeySet * result = ksNew (0, KS_END);
687 :
688 36 : keyRewindMeta (oldKey);
689 36 : keyRewindMeta (newKey);
690 :
691 36 : const Key * oldMeta = keyNextMeta (oldKey);
692 36 : const Key * newMeta = keyNextMeta (newKey);
693 :
694 120 : while (oldMeta != NULL && newMeta != NULL)
695 : {
696 48 : const char * oldName = keyName (oldMeta);
697 48 : const char * newName = keyName (newMeta);
698 :
699 48 : int cmp = elektraStrCmp (oldName, newName);
700 48 : if (cmp < 0)
701 : {
702 : // oldKey has to "catch up"
703 4 : ksAppendKey (result,
704 : keyNew (oldName, KEY_META_NAME, KEY_VALUE, "remove", KEY_META, "old", keyString (oldMeta), KEY_END));
705 4 : oldMeta = keyNextMeta (oldKey);
706 : }
707 44 : else if (cmp > 0)
708 : {
709 : // newKey has to "catch up"
710 12 : ksAppendKey (result,
711 : keyNew (newName, KEY_META_NAME, KEY_VALUE, "add", KEY_META, "new", keyString (newMeta), KEY_END));
712 12 : newMeta = keyNextMeta (newKey);
713 : }
714 : else
715 : {
716 : // same name
717 32 : ksAppendKey (result, keyNew (oldName, KEY_META_NAME, KEY_VALUE, "edit", KEY_META, "old", keyString (oldMeta),
718 : KEY_META, "new", keyString (newMeta), KEY_END));
719 32 : oldMeta = keyNextMeta (oldKey);
720 32 : newMeta = keyNextMeta (newKey);
721 : }
722 : }
723 :
724 : // remaining metadata in oldKey was removed
725 36 : while ((oldMeta = keyNextMeta (oldKey)) != NULL)
726 : {
727 0 : ksAppendKey (result,
728 : keyNew (keyName (oldMeta), KEY_META_NAME, KEY_VALUE, "remove", KEY_META, "old", keyString (oldMeta), KEY_END));
729 : }
730 :
731 : // remaining metadata in newKey was added
732 36 : while ((newMeta = keyNextMeta (newKey)) != NULL)
733 : {
734 0 : ksAppendKey (result,
735 : keyNew (keyName (newMeta), KEY_META_NAME, KEY_VALUE, "add", KEY_META, "new", keyString (newMeta), KEY_END));
736 : }
737 :
738 36 : return result;
739 : }
740 :
741 108 : Plugin * ELEKTRA_PLUGIN_EXPORT
742 : {
743 : // clang-format off
744 108 : return elektraPluginExport ("specload",
745 : ELEKTRA_PLUGIN_OPEN, &elektraSpecloadOpen,
746 : ELEKTRA_PLUGIN_CLOSE, &elektraSpecloadClose,
747 : ELEKTRA_PLUGIN_GET, &elektraSpecloadGet,
748 : ELEKTRA_PLUGIN_SET, &elektraSpecloadSet,
749 : ELEKTRA_PLUGIN_END);
750 : // clang-format on
751 : }
|