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