Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief A plugin that converts keys to metakeys and vice versa
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : *
8 : */
9 :
10 : #include "keytometa.h"
11 :
12 : #ifndef HAVE_KDBCONFIG
13 : #include "kdbconfig.h"
14 : #endif
15 :
16 :
17 : #include <errno.h>
18 : #include <stdbool.h>
19 : #include <stdlib.h>
20 :
21 : static const char * CONVERT_METANAME = "convert/metaname";
22 : static const char * CONVERT_TARGET = "convert/to";
23 : static const char * CONVERT_APPEND_SAMELEVEL = "convert/append/samelevel";
24 : static const char * CONVERT_APPENDMODE = "convert/append";
25 :
26 : /*
27 : * Wrapper for the function comparing by order metadata. As
28 : * qsort is not stable returning 0 on missing order may
29 : * mess up the original order.
30 : */
31 156 : int elektraKeyCmpOrderWrapper (const void * a, const void * b)
32 : {
33 156 : const Key ** ka = (const Key **) a;
34 156 : const Key ** kb = (const Key **) b;
35 :
36 156 : int orderResult = elektraKeyCmpOrder (*ka, *kb);
37 :
38 : /* comparing the order meta could not order the keys
39 : * revert to comparing the names instead
40 : */
41 156 : if (orderResult == 0) return keyCmp (*ka, *kb);
42 :
43 : return orderResult;
44 : }
45 :
46 : /* The KeySet MUST be sorted alphabetically (or at least ascending
47 : * by the length of keynames) for this function to work
48 : */
49 14 : static Key * findNearestParent (Key * key, KeySet * ks)
50 : {
51 : Key * current;
52 14 : ksSetCursor (ks, ksGetSize (ks) - 1);
53 64 : while ((current = ksPrev (ks)) != 0)
54 : {
55 50 : if (keyIsBelow (current, key))
56 : {
57 : return current;
58 : }
59 : }
60 :
61 : return 0;
62 : }
63 :
64 : /*
65 : * Appends a line to the MetaKey of the supplied Key
66 : * If no MetaKey with the given name exists yet, a new
67 : * one is created containing the supplied line. If
68 : * the MetaKey exists, the supplied line is added as
69 : * a new line to the value of the MetaKey (i.e. a newline
70 : * followed by the given line is appended to the metadata)
71 : *
72 : * @param target the Key whose MetaKey is to be modified
73 : * @param metaName the name of the MetaKey which is to be modified
74 : * @param line the line to be appended to the matadata
75 : * @return the new value size of the modified MetaKey
76 : * @retval -1 on NULL pointers or if a memory allocation error occurs
77 : *
78 : * @see keyGetValueSize(Key *key)
79 : *
80 : */
81 50 : int elektraKeyAppendMetaLine (Key * target, const char * metaName, const char * line)
82 : {
83 50 : if (!target) return 0;
84 50 : if (!metaName) return 0;
85 50 : if (!line) return 0;
86 :
87 50 : if (!keyGetMeta (target, metaName))
88 : {
89 38 : keySetMeta (target, metaName, line);
90 38 : return keyGetValueSize (keyGetMeta (target, metaName));
91 : }
92 :
93 12 : const Key * existingMeta = keyGetMeta (target, metaName);
94 12 : char * buffer = elektraMalloc (keyGetValueSize (existingMeta) + strlen (line) + 1);
95 12 : if (!buffer) return 0;
96 :
97 12 : keyGetString (existingMeta, buffer, keyGetValueSize (existingMeta));
98 12 : strcat (buffer, "\n");
99 12 : strncat (buffer, line, elektraStrLen (line));
100 :
101 12 : keySetMeta (target, metaName, buffer);
102 12 : elektraFree (buffer);
103 12 : return keyGetValueSize (keyGetMeta (target, metaName));
104 : }
105 :
106 50 : static const char * getAppendMode (Key * key)
107 : {
108 50 : const Key * appendModeKey = keyGetMeta (key, CONVERT_APPENDMODE);
109 : const char * appendMode;
110 :
111 : /* append to the next key is the default */
112 50 : appendMode = appendModeKey != 0 ? keyString (appendModeKey) : "next";
113 50 : return appendMode;
114 : }
115 :
116 50 : void removeKeyFromResult (Key * convertKey, Key * target, KeySet * orig)
117 : {
118 : /* remember which key this key was converted to
119 : * before removing it from the result
120 : */
121 50 : keySetMeta (convertKey, CONVERT_TARGET, keyName (target));
122 50 : Key * key = ksLookup (orig, convertKey, KDB_O_POP);
123 50 : keyDel (key);
124 50 : }
125 :
126 128 : static void flushConvertedKeys (Key * target, KeySet * converted, KeySet * orig)
127 : {
128 128 : if (ksGetSize (converted) == 0) return;
129 :
130 28 : ksRewind (converted);
131 : Key * current;
132 :
133 98 : while ((current = ksNext (converted)))
134 : {
135 42 : Key * appendTarget = target;
136 42 : const char * metaName = keyString (keyGetMeta (current, CONVERT_METANAME));
137 :
138 42 : Key * currentDup = keyDup (current);
139 42 : Key * targetDup = keyDup (appendTarget);
140 42 : keySetBaseName (currentDup, 0);
141 42 : keySetBaseName (targetDup, 0);
142 :
143 : /* the convert key request to be converted to a key
144 : * on the same level, but the target is below or above
145 : */
146 42 : if (keyGetMeta (current, CONVERT_APPEND_SAMELEVEL) && keyCmp (currentDup, targetDup))
147 : {
148 6 : appendTarget = 0;
149 : }
150 :
151 42 : keyDel (currentDup);
152 42 : keyDel (targetDup);
153 :
154 : /* no target key was found of the target
155 : * was discarded for some reason. Revert to the parent
156 : */
157 42 : if (!appendTarget)
158 : {
159 6 : appendTarget = findNearestParent (current, orig);
160 : }
161 :
162 42 : elektraKeyAppendMetaLine (appendTarget, metaName, keyString (current));
163 42 : removeKeyFromResult (current, target, orig);
164 : }
165 :
166 28 : ksClear (converted);
167 : }
168 :
169 16 : static KeySet * convertKeys (Key ** keyArray, size_t numKeys, KeySet * orig)
170 : {
171 16 : Key * current = 0;
172 16 : Key * prevAppendTarget = 0;
173 16 : KeySet * prevConverted = ksNew (0, KS_END);
174 16 : KeySet * nextConverted = ksNew (0, KS_END);
175 16 : KeySet * result = ksNew (0, KS_END);
176 :
177 114 : for (size_t index = 0; index < numKeys; index++)
178 : {
179 98 : current = keyArray[index];
180 :
181 98 : if (!keyGetMeta (current, CONVERT_METANAME))
182 : {
183 : /* flush out "previous" and "next" keys which may have been collected
184 : * because the current key serves as a new border
185 : */
186 48 : ksAppend (result, prevConverted);
187 48 : flushConvertedKeys (prevAppendTarget, prevConverted, orig);
188 48 : prevAppendTarget = current;
189 :
190 48 : ksAppend (result, nextConverted);
191 48 : flushConvertedKeys (current, nextConverted, orig);
192 48 : continue;
193 : }
194 :
195 50 : const char * appendMode = getAppendMode (current);
196 50 : const char * metaName = keyString (keyGetMeta (current, CONVERT_METANAME));
197 :
198 50 : Key * bufferKey = 0;
199 50 : if (!strcmp (appendMode, "previous"))
200 : {
201 18 : ksAppendKey (prevConverted, current);
202 : }
203 :
204 50 : if (!strcmp (appendMode, "next"))
205 : {
206 24 : ksAppendKey (nextConverted, current);
207 : }
208 :
209 50 : if (!strcmp (appendMode, "parent"))
210 : {
211 8 : Key * parent = findNearestParent (current, orig);
212 8 : elektraKeyAppendMetaLine (parent, metaName, keyString (current));
213 8 : ksAppendKey (result, current);
214 8 : removeKeyFromResult (current, parent, orig);
215 : }
216 :
217 : if (bufferKey)
218 : {
219 : keySetString (bufferKey, keyName (current));
220 : }
221 : }
222 :
223 16 : ksAppend (result, prevConverted);
224 16 : flushConvertedKeys (prevAppendTarget, prevConverted, orig);
225 :
226 16 : ksAppend (result, nextConverted);
227 16 : flushConvertedKeys (0, nextConverted, orig);
228 :
229 16 : ksDel (nextConverted);
230 16 : ksDel (prevConverted);
231 :
232 16 : return result;
233 : }
234 :
235 320 : int elektraKeyToMetaGet (Plugin * handle, KeySet * returned, Key * parentKey ELEKTRA_UNUSED)
236 : {
237 320 : int errnosave = errno;
238 :
239 : /* configuration only */
240 320 : if (!strcmp (keyName (parentKey), "system/elektra/modules/keytometa"))
241 : {
242 304 : KeySet * info =
243 : #include "contract.h"
244 :
245 304 : ksAppend (returned, info);
246 304 : ksDel (info);
247 304 : return 1;
248 : }
249 :
250 16 : Key ** keyArray = calloc (ksGetSize (returned), sizeof (Key *));
251 16 : int ret = elektraKsToMemArray (returned, keyArray);
252 :
253 16 : if (ret < 0)
254 : {
255 0 : elektraFree (keyArray);
256 0 : ELEKTRA_SET_OUT_OF_MEMORY_ERROR (parentKey, "Memory allocation failed");
257 0 : errno = errnosave;
258 0 : return 0;
259 : }
260 :
261 16 : size_t numKeys = ksGetSize (returned);
262 16 : qsort (keyArray, numKeys, sizeof (Key *), elektraKeyCmpOrderWrapper);
263 :
264 16 : KeySet * convertedKeys = convertKeys (keyArray, numKeys, returned);
265 :
266 16 : elektraFree (keyArray);
267 :
268 : /* cleanup what might have been left from a previous call */
269 16 : KeySet * old = elektraPluginGetData (handle);
270 16 : if (old)
271 : {
272 0 : ksDel (old);
273 : }
274 :
275 16 : elektraPluginSetData (handle, convertedKeys);
276 :
277 16 : errno = errnosave;
278 16 : return 1; /* success */
279 : }
280 :
281 :
282 6 : int elektraKeyToMetaSet (Plugin * handle, KeySet * returned, Key * parentKey ELEKTRA_UNUSED)
283 : {
284 6 : KeySet * converted = elektraPluginGetData (handle);
285 :
286 : /* nothing to do */
287 6 : if (converted == 0) return 1;
288 :
289 6 : ksRewind (converted);
290 :
291 6 : char * saveptr = 0;
292 6 : char * value = 0;
293 : Key * current;
294 6 : Key * previous = 0;
295 32 : while ((current = ksNext (converted)) != 0)
296 : {
297 20 : const Key * targetName = keyGetMeta (current, CONVERT_TARGET);
298 20 : const Key * metaName = keyGetMeta (current, CONVERT_METANAME);
299 :
300 : /* they should always exist, just to be sure */
301 20 : if (targetName && metaName)
302 : {
303 20 : Key * target = ksLookupByName (returned, keyString (targetName), KDB_O_NONE);
304 :
305 : /* this might be NULL as the key might have been deleted */
306 20 : if (target)
307 : {
308 :
309 20 : char * result = 0;
310 20 : if (target != previous)
311 : {
312 : /* handle the first meta line this means initializing strtok and related buffers */
313 12 : elektraFree (value);
314 12 : const Key * valueKey = keyGetMeta (target, keyString (metaName));
315 12 : size_t valueSize = keyGetValueSize (valueKey);
316 12 : value = elektraMalloc (valueSize);
317 12 : keyGetString (valueKey, value, valueSize);
318 12 : keySetMeta (target, keyString (metaName), 0);
319 12 : result = strtok_r (value, "\n", &saveptr);
320 : }
321 : else
322 : {
323 : /* just continue splitting the metadata */
324 : result = strtok_r (NULL, "\n", &saveptr);
325 : }
326 :
327 20 : keySetString (current, result);
328 :
329 20 : previous = target;
330 : }
331 : }
332 :
333 20 : keySetMeta (current, CONVERT_TARGET, 0);
334 20 : keySetMeta (current, CONVERT_METANAME, 0);
335 :
336 20 : ksAppendKey (returned, current);
337 : }
338 :
339 6 : elektraFree (value);
340 :
341 6 : ksDel (converted);
342 6 : elektraPluginSetData (handle, 0);
343 :
344 6 : return 1; /* success */
345 : }
346 :
347 322 : int elektraKeyToMetaClose (Plugin * handle, Key * errorKey ELEKTRA_UNUSED)
348 : {
349 322 : KeySet * old = elektraPluginGetData (handle);
350 :
351 322 : if (old)
352 : {
353 10 : ksDel (old);
354 : }
355 :
356 322 : return 1;
357 : }
358 :
359 322 : Plugin * ELEKTRA_PLUGIN_EXPORT
360 : {
361 : // clang-format off
362 322 : return elektraPluginExport("keytometa",
363 : ELEKTRA_PLUGIN_GET, &elektraKeyToMetaGet,
364 : ELEKTRA_PLUGIN_SET, &elektraKeyToMetaSet,
365 : ELEKTRA_PLUGIN_CLOSE, &elektraKeyToMetaClose,
366 : ELEKTRA_PLUGIN_END);
367 : }
368 :
|