Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief Array methods.
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : */
8 :
9 : #define __STDC_FORMAT_MACROS
10 :
11 : #include <kdb.h>
12 : #include <kdbease.h>
13 : #include <kdbhelper.h>
14 : #include <kdbtypes.h>
15 :
16 : #include <ctype.h>
17 : #include <errno.h>
18 : #include <limits.h>
19 : #include <stdio.h>
20 : #include <stdlib.h>
21 : #include <string.h>
22 :
23 : /**
24 : * @brief validate array syntax
25 : *
26 : * @param key an element of an array
27 : *
28 : * @retval -1 if no array element/syntax error/no key
29 : * @retval 0 if start
30 : * @retval 1 if array element
31 : */
32 8638 : int elektraArrayValidateName (const Key * key)
33 : {
34 8638 : if (!key) return -1;
35 8638 : int offsetIndex = elektraArrayValidateBaseNameString (keyBaseName (key));
36 8638 : return offsetIndex >= 1 ? 1 : offsetIndex;
37 : }
38 :
39 : /**
40 : * @brief validate array syntax
41 : *
42 : * @param baseName the supposed array element basename
43 : *
44 : * @retval -1 if no array element/syntax error/no key
45 : * @retval 0 if start
46 : * @retval offsetIndex otherwise, where `offsetIndex` stores the offset
47 : * to the first digit of the array index of `baseName`
48 : */
49 176591 : int elektraArrayValidateBaseNameString (const char * baseName)
50 : {
51 176591 : const char * current = baseName;
52 176591 : if (!current || *current != '#') return -1;
53 171879 : if (!strcmp (current, "#")) return 0;
54 :
55 168564 : current++;
56 168564 : int underscores = 0;
57 168564 : int digits = 0;
58 :
59 416331 : while (*current == '_')
60 : {
61 79203 : current++;
62 79203 : underscores++;
63 : }
64 :
65 416360 : while (isdigit ((unsigned char) *current))
66 : {
67 247796 : current++;
68 247796 : digits++;
69 : }
70 :
71 168564 : if (underscores != digits - 1) return -1;
72 168553 : if (underscores + digits > ELEKTRA_MAX_ARRAY_SIZE - 2)
73 : {
74 : return -1;
75 : }
76 :
77 168553 : return underscores + 1;
78 : }
79 :
80 185697 : int elektraReadArrayNumber (const char * baseName, kdb_long_long_t * oldIndex)
81 : {
82 :
83 185697 : int errnosave = errno;
84 185697 : errno = 0;
85 185697 : if (sscanf (baseName, ELEKTRA_LONG_LONG_F, oldIndex) != 1)
86 : {
87 0 : errno = errnosave;
88 0 : return -1;
89 : }
90 :
91 185697 : if (errno != 0) // any error
92 : {
93 0 : errno = errnosave;
94 0 : return -1;
95 : }
96 :
97 185697 : if (*oldIndex < 0) // underflow
98 : {
99 : return -1;
100 : }
101 :
102 : /*
103 : overflow not possible, cannot be larger than largest number
104 : if (*oldIndex >= INT64_MAX) // overflow
105 : {
106 : return -1;
107 : }
108 : */
109 185697 : return 0;
110 : }
111 :
112 :
113 : /**
114 : * @brief Increment the name of the key by one
115 : *
116 : * Alphabetical order will remain
117 : *
118 : * e.g. user/abc/\#9 will be changed to
119 : * user/abc/\#_10
120 : *
121 : * For the start:
122 : * user/abc/\#
123 : * will be changed to
124 : * user/abc/\#0
125 : *
126 : * @param key which base name will be incremented
127 : *
128 : * @retval -1 on error (e.g. array too large, non-valid array)
129 : * @retval 0 on success
130 : */
131 166041 : int elektraArrayIncName (Key * key)
132 : {
133 166041 : const char * baseName = keyBaseName (key);
134 :
135 166041 : int offsetIndex = elektraArrayValidateBaseNameString (baseName);
136 166041 : if (offsetIndex == -1) return -1;
137 :
138 : // Jump to array index
139 166023 : baseName += offsetIndex;
140 :
141 166023 : kdb_long_long_t oldIndex = 0;
142 166023 : if (offsetIndex && elektraReadArrayNumber (baseName, &oldIndex) == -1) return -1;
143 166023 : kdb_long_long_t newIndex = offsetIndex ? oldIndex + 1 : 0; // we increment by one or use 0 if the name contains no index yet
144 :
145 : char newName[ELEKTRA_MAX_ARRAY_SIZE];
146 :
147 166023 : elektraWriteArrayNumber (newName, newIndex);
148 166023 : keySetBaseName (key, newName);
149 :
150 166023 : return 0;
151 : }
152 :
153 : /**
154 : * @brief Decrement the name of an array key by one.
155 : *
156 : * The alphabetical order will remain intact. For example,
157 : * `user/abc/\#_10` will be changed to `user/abc/\#9`.
158 : *
159 : * @param This parameter determines the key name this function decrements.
160 : *
161 : * @retval -1 on error (e.g. new array index too small, non-valid array)
162 : * @retval 0 on success
163 : */
164 126 : int elektraArrayDecName (Key * key)
165 : {
166 126 : const char * baseName = keyBaseName (key);
167 :
168 126 : int offsetIndex = elektraArrayValidateBaseNameString (baseName);
169 126 : if (offsetIndex == -1) return -1;
170 :
171 : // Jump to array index
172 126 : baseName += offsetIndex;
173 :
174 126 : kdb_long_long_t oldIndex = 0;
175 126 : if (elektraReadArrayNumber (baseName, &oldIndex) == -1 || oldIndex == 0) return -1;
176 :
177 : char newName[ELEKTRA_MAX_ARRAY_SIZE];
178 124 : elektraWriteArrayNumber (newName, oldIndex - 1);
179 124 : keySetBaseName (key, newName);
180 :
181 124 : return 0;
182 : }
183 :
184 : /**
185 : * @internal
186 : *
187 : * Returns true (1) for all keys that are part of the array
188 : * identified by the supplied array parent. Only the array
189 : * elements themselves, but no subkeys of them will be filtered
190 : *
191 : * @pre The supplied argument has to be of type (const Key *)
192 : * and is the parent of the array to be extracted. For example
193 : * if the keys of the array comment/# are to be extracted, a key
194 : * with the name "comment" has to be supplied
195 : *
196 : * @param key the key to be checked against the array
197 : * @param argument the array parent
198 : * @return 1 if the key is part of the array identified by the
199 : * array parent, 0 otherwise
200 : *
201 : */
202 16349 : static int arrayFilter (const Key * key, void * argument)
203 : {
204 16349 : const Key * arrayParent = (const Key *) argument;
205 16349 : return keyIsDirectBelow (arrayParent, key) && elektraArrayValidateName (key) > 0;
206 : }
207 :
208 :
209 : /**
210 : * @brief Return all the array keys below the given array parent
211 : *
212 : * The array parent itself is not returned.
213 : * For example, if `user/config/#` is an array,
214 : * `user/config` is the array parent.
215 : * Only the direct array keys will be returned. This means
216 : * that for example `user/config/#1/key` will not be included,
217 : * but only `user/config/#1`.
218 : *
219 : * A new keyset will be allocated for the resulting keys.
220 : * This means that the caller must `ksDel` the resulting keyset.
221 : *
222 : * @param arrayParent the parent of the array to be returned
223 : * @param keys the keyset containing the array keys
224 : *
225 : * @return a keyset containing the array keys (if any)
226 : * @retval NULL on `NULL` pointers
227 : */
228 757 : KeySet * elektraArrayGet (const Key * arrayParent, KeySet * keys)
229 : {
230 757 : if (!arrayParent) return 0;
231 :
232 757 : if (!keys) return 0;
233 :
234 730 : KeySet * arrayKeys = ksNew (ksGetSize (keys), KS_END);
235 730 : elektraKsFilter (arrayKeys, keys, &arrayFilter, (void *) arrayParent);
236 730 : return arrayKeys;
237 : }
238 :
239 : /**
240 : *
241 : * Return the next key in the given array.
242 : * The function will automatically allocate memory
243 : * for a new key and name it accordingly.
244 : *
245 : * @pre The supplied keyset must contain only valid array keys.
246 : *
247 : * The caller has to keyDel the resulting key.
248 : *
249 : * @param arrayKeys the array where the new key will belong to
250 : *
251 : * @return the new array key on success
252 : * @retval NULL if the passed array is empty
253 : * @retval NULL on NULL pointers or if an error occurs
254 : */
255 117 : Key * elektraArrayGetNextKey (KeySet * arrayKeys)
256 : {
257 117 : if (!arrayKeys) return 0;
258 :
259 117 : Key * last = ksPop (arrayKeys);
260 :
261 117 : if (!last) return 0;
262 :
263 113 : ksAppendKey (arrayKeys, last);
264 113 : Key * newKey = keyDup (last);
265 113 : keySetBinary (newKey, 0, 0);
266 113 : int ret = elektraArrayIncName (newKey);
267 :
268 113 : if (ret == -1)
269 : {
270 0 : keyDel (newKey);
271 0 : return 0;
272 : }
273 :
274 : return newKey;
275 : }
|