Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief Source for mini plugin
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : *
8 : */
9 :
10 : /* -- Imports --------------------------------------------------------------------------------------------------------------------------- */
11 :
12 : #include "mini.h"
13 :
14 : #include <kdbassert.h>
15 : #include <kdbease.h>
16 : #include <kdberrors.h>
17 : #include <kdblogger.h>
18 : #include <kdbutility.h>
19 : // The definition `_WITH_GETLINE` is required for FreeBSD
20 : #define _WITH_GETLINE
21 : #include <stdio.h>
22 :
23 : /* -- Functions ------------------------------------------------------------------------------------------------------------------------- */
24 :
25 : // ===========
26 : // = Private =
27 : // ===========
28 :
29 : /**
30 : * @brief This function returns a key set containing the contract of this plugin.
31 : *
32 : * @return A contract describing the functionality of this plugin.
33 : */
34 161 : static inline KeySet * elektraMiniContract (void)
35 : {
36 161 : return ksNew (30, keyNew ("system/elektra/modules/mini", KEY_VALUE, "mini plugin waits for your orders", KEY_END),
37 : keyNew ("system/elektra/modules/mini/exports", KEY_END),
38 : keyNew ("system/elektra/modules/mini/exports/get", KEY_FUNC, elektraMiniGet, KEY_END),
39 : keyNew ("system/elektra/modules/mini/exports/set", KEY_FUNC, elektraMiniSet, KEY_END),
40 : #include ELEKTRA_README
41 : keyNew ("system/elektra/modules/mini/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END),
42 : keyNew ("system/elektra/modules/mini/config/needs/chars/23", KEY_VALUE, "23", KEY_END), // 23 ↔︎ `#`
43 : keyNew ("system/elektra/modules/mini/config/needs/chars/3B", KEY_VALUE, "3B", KEY_END), // 3B ↔︎ `;`
44 : keyNew ("system/elektra/modules/mini/config/needs/chars/3D", KEY_VALUE, "3D", KEY_END), // 3D ↔︎ `=`
45 : keyNew ("system/elektra/modules/mini/config/needs/chars/5C", KEY_VALUE, "5C", KEY_END), // 5C ↔︎ `\`
46 : KS_END);
47 : }
48 :
49 : /**
50 : * @brief This function removes comments marked with `;` or '#' from a
51 : * given string by locating the first non-escaped comment character.
52 : * It then overwrites this character with `\0`.
53 : *
54 : * @pre The parameter `line` must not be `NULL`.
55 : *
56 : * @param line The string from which we want to remove line comments.
57 : *
58 : * @return A pointer to the first character of the modified version of
59 : * line.
60 : */
61 86 : static inline char * stripComment (char * line)
62 : {
63 86 : ELEKTRA_NOT_NULL (line);
64 :
65 : char * current = line;
66 : char * before = NULL;
67 :
68 : /* As long as we are not the end of the string and
69 : the current character is either not a comment marker or the comment marker was escaped */
70 1546 : while (*current != '\0' && ((*current != '#' && *current != ';') || (before && *before == '\\')))
71 : {
72 1460 : before = current;
73 1460 : current++;
74 : }
75 86 : *current = '\0';
76 86 : return line;
77 : }
78 :
79 : /**
80 : * @brief This function locates the first non-escaped equals
81 : * character (`=`) in a given string.
82 : *
83 : * @pre The parameter `text` must not be `NULL`.
84 : *
85 : * @param text The string in which the equals character should be located
86 : *
87 : * @return A pointer to the first unescaped `=` in text or a pointer to
88 : * the terminating `\0` of `text` if no such character exists.
89 : */
90 76 : static inline char * findUnescapedEquals (char * text)
91 : {
92 76 : ELEKTRA_NOT_NULL (text);
93 :
94 : char * equals = text;
95 : char * before = NULL;
96 :
97 787 : while (*equals != '\0' && (*equals != '=' || (before && *before == '\\')))
98 : {
99 711 : before = equals++;
100 : }
101 76 : return equals;
102 : }
103 :
104 : /**
105 : * @brief Parse a single line of a text in INI like format (`key = value`) and
106 : * store the resulting key value pair in the given key set.
107 : *
108 : * The string stored in `line` can also be empty or contain comments denoted
109 : * by `;` or `#`. The function ignores empty lines. If a line contains non-commented
110 : * characters that do not follow the pattern `key = value`, then this function will
111 : * add a warning about this invalid key value pair to `parentKey`.
112 : *
113 : * @pre The parameters `line`, `keySet` and `parentKey` must not be `NULL`.
114 : *
115 : * @param line A single line string that should be parsed by this function
116 : * @param lineNumber The lineNumber of the current line of text. This value will
117 : * be used by this function to generate warning messages about
118 : * invalid key value pairs.
119 : * @param keySet The keyset where the key value pair contained in `line` should
120 : * be saved
121 : * @param parentKey This key is used by this function to store warnings about
122 : * invalid key value pairs
123 : */
124 86 : static inline void parseLine (char * line, size_t lineNumber, KeySet * keySet, Key * parentKey)
125 : {
126 86 : ELEKTRA_NOT_NULL (line);
127 86 : ELEKTRA_NOT_NULL (keySet);
128 86 : ELEKTRA_NOT_NULL (parentKey);
129 :
130 86 : char * pair = elektraStrip (stripComment (line));
131 :
132 86 : if (*pair == '\0')
133 : {
134 : return;
135 : }
136 :
137 76 : char * equals = findUnescapedEquals (pair);
138 76 : if (*equals == '\0' || equals == pair)
139 : {
140 : ELEKTRA_LOG_WARNING ("Ignored line %zu since “%s” does not contain a valid key value pair", lineNumber, pair);
141 15 : ELEKTRA_ADD_VALIDATION_SYNTACTIC_WARNINGF (parentKey, "Line %zu: '%s' is not a valid key value pair", lineNumber, pair);
142 15 : return;
143 : }
144 :
145 61 : *equals = '\0';
146 :
147 61 : char * name = elektraRstrip (pair, NULL);
148 61 : char * value = elektraLskip (equals + 1);
149 :
150 61 : Key * key = keyNew (keyName (parentKey), KEY_END);
151 61 : keyAddName (key, name);
152 61 : keySetString (key, value);
153 : ELEKTRA_LOG_DEBUG ("Name: “%s”", keyName (key));
154 : ELEKTRA_LOG_DEBUG ("Value: “%s”", keyString (key));
155 :
156 61 : ksAppendKey (keySet, key);
157 : }
158 :
159 : /**
160 : * @brief Parse a file containing text in INI like format (`key = value`).
161 : *
162 : * @pre The parameters `file`, `keySet` and `parentKey` must not be `NULL`.
163 : *
164 : * @param file The file handle that points to the data this function should parse
165 : * @param keySet The keyset where the key value pairs saved in `file` should
166 : * be saved
167 : * @param parentKey A key that is used by this function to store warning and error
168 : * information
169 : *
170 : * @retval ELEKTRA_PLUGIN_STATUS_SUCCESS if the parsing process finished successfully
171 : * @retval ELEKTRA_PLUGIN_STATUS_ERROR if the function was unable to parse `file`
172 : */
173 30 : static int parseINI (FILE * file, KeySet * keySet, Key * parentKey)
174 : {
175 30 : ELEKTRA_NOT_NULL (file);
176 30 : ELEKTRA_NOT_NULL (keySet);
177 30 : ELEKTRA_NOT_NULL (parentKey);
178 :
179 30 : char * line = NULL;
180 30 : size_t capacity = 0;
181 30 : int errorNumber = errno;
182 :
183 : size_t lineNumber;
184 116 : for (lineNumber = 1; getline (&line, &capacity, file) != -1; ++lineNumber)
185 : {
186 : ELEKTRA_LOG_DEBUG ("Read Line %zu: %s", lineNumber, line);
187 86 : parseLine (line, lineNumber, keySet, parentKey);
188 : }
189 :
190 30 : elektraFree (line);
191 :
192 30 : if (!feof (file))
193 : {
194 : ELEKTRA_LOG_WARNING ("%s:%zu: Unable to read line", keyString (parentKey), lineNumber);
195 0 : ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (parentKey, "Unable to read line %zu: %s", lineNumber, strerror (errno));
196 0 : errno = errorNumber;
197 0 : return ELEKTRA_PLUGIN_STATUS_ERROR;
198 : }
199 :
200 : return ELEKTRA_PLUGIN_STATUS_SUCCESS;
201 : }
202 :
203 : /**
204 : * @brief Parse a file containing key value pairs in an INI like format and
205 : * store the obtained key value pairs in a given key set.
206 : *
207 : * @pre The parameters `returned`, and `parentKey` must not be `NULL`.
208 : *
209 : * @param returned A key set used to store the key value pairs contained in the
210 : * file specified by the value of `parentKey`
211 : * @param parentKey The value of this key value pair stores the path to the file
212 : * this function should parse
213 : *
214 : * @retval ELEKTRA_PLUGIN_STATUS_SUCCESS if the whole parsing process was successful
215 : * @retval ELEKTRA_PLUGIN_STATUS_ERROR if at least one part of the parsing process failed
216 : */
217 30 : static int parseFile (KeySet * returned, Key * parentKey)
218 : {
219 30 : ELEKTRA_NOT_NULL (returned);
220 30 : ELEKTRA_NOT_NULL (parentKey);
221 :
222 : ELEKTRA_LOG ("Read configuration data");
223 30 : int errorNumber = errno;
224 30 : FILE * source = fopen (keyString (parentKey), "r");
225 :
226 30 : if (!source || (parseINI (source, returned, parentKey) < 0) | (fclose (source) != 0)) //! OCLint
227 : {
228 0 : ELEKTRA_SET_ERROR_GET (parentKey);
229 0 : errno = errorNumber;
230 0 : return ELEKTRA_PLUGIN_STATUS_ERROR;
231 : }
232 :
233 : return ELEKTRA_PLUGIN_STATUS_SUCCESS;
234 : }
235 :
236 : /**
237 : * @brief Add an error to `parentKey` and restore `errno` if `status` contains
238 : * a negative number (write error).
239 : *
240 : * @pre The parameter `parentKey` must not be `NULL`.
241 : *
242 : * @param status This value will be checked by this function to determine if
243 : * the write function (`fprintf`) returned unsuccessfully
244 : * @param errorNumber A saved value for `errno` this function should restore
245 : * if `status` indicates an write error
246 : * @param parentKey The key to which this function will add error information
247 : * if `status` indicates an write error
248 : *
249 : * @retval ELEKTRA_PLUGIN_STATUS_SUCCESS if status indicates success (`status >= 0`)
250 : * @retval ELEKTRA_PLUGIN_STATUS_ERROR if there was a write error (`status < 0`)
251 : */
252 10 : static inline int checkWrite (int status, int errorNumber, Key * parentKey)
253 : {
254 10 : ELEKTRA_NOT_NULL (parentKey);
255 :
256 10 : if (status < 0)
257 : {
258 0 : ELEKTRA_SET_ERROR_SET (parentKey);
259 0 : errno = errorNumber;
260 0 : return ELEKTRA_PLUGIN_STATUS_ERROR;
261 : }
262 :
263 : return ELEKTRA_PLUGIN_STATUS_SUCCESS;
264 : }
265 :
266 : /**
267 : * @brief Store the key value pairs of a key set in a file using an INI like format
268 : *
269 : * @pre The parameters `file`, `keySet`, and `parentKey` must not be `NULL`.
270 : *
271 : * @param keySet This key set contains the key value pairs that should be stored
272 : * in `file`
273 : * @param parentKey The function uses this key to store error and warning information
274 : *
275 : * @retval ELEKTRA_PLUGIN_STATUS_SUCCESS if the function was able to store all key value
276 : * pairs successfully
277 : * @retval ELEKTRA_PLUGIN_STATUS_ERROR if the function was unable to store all key value
278 : * pairs in `file`
279 : */
280 10 : static inline int writeFile (FILE * file, KeySet * keySet, Key * parentKey)
281 : {
282 10 : ELEKTRA_NOT_NULL (file);
283 10 : ELEKTRA_NOT_NULL (keySet);
284 10 : ELEKTRA_NOT_NULL (parentKey);
285 :
286 10 : int status = 0;
287 10 : int errorNumber = errno;
288 10 : ksRewind (keySet);
289 41 : for (Key * key; (key = ksNext (keySet)) != 0 && status >= 0;)
290 : {
291 21 : const char * name = elektraKeyGetRelativeName (key, parentKey);
292 : ELEKTRA_LOG_DEBUG ("Write mapping “%s=%s”", name, keyString (key));
293 :
294 21 : status = fprintf (file, "%s=%s\n", name, keyString (key));
295 : }
296 10 : return checkWrite (status, errorNumber, parentKey);
297 : }
298 :
299 : // ====================
300 : // = Plugin Interface =
301 : // ====================
302 :
303 : /** @see elektraDocGet */
304 191 : int elektraMiniGet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned, Key * parentKey)
305 : {
306 191 : if (!elektraStrCmp (keyName (parentKey), "system/elektra/modules/mini"))
307 : {
308 : ELEKTRA_LOG_DEBUG ("Retrieve plugin contract");
309 161 : KeySet * contract = elektraMiniContract ();
310 161 : ksAppend (returned, contract);
311 161 : ksDel (contract);
312 :
313 161 : return ELEKTRA_PLUGIN_STATUS_SUCCESS;
314 : }
315 :
316 30 : return parseFile (returned, parentKey);
317 : }
318 :
319 : /** @see elektraDocSet */
320 10 : int elektraMiniSet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned, Key * parentKey)
321 : {
322 : ELEKTRA_LOG ("Write configuration data");
323 10 : int errorNumber = errno;
324 10 : FILE * destination = fopen (keyString (parentKey), "w");
325 :
326 10 : if (!destination || (writeFile (destination, returned, parentKey) < 0) | (fclose (destination) == EOF)) //! OCLint
327 : {
328 0 : ELEKTRA_SET_ERROR_SET (parentKey);
329 0 : errno = errorNumber;
330 0 : return ELEKTRA_PLUGIN_STATUS_ERROR;
331 : }
332 : return ELEKTRA_PLUGIN_STATUS_SUCCESS;
333 : }
334 :
335 325 : Plugin * ELEKTRA_PLUGIN_EXPORT
336 : {
337 : // clang-format off
338 325 : return elektraPluginExport ("mini",
339 : ELEKTRA_PLUGIN_GET, &elektraMiniGet,
340 : ELEKTRA_PLUGIN_SET, &elektraMiniSet,
341 : ELEKTRA_PLUGIN_END);
342 : }
|