1 /**
2  * @file
3  * Subset of config items
4  *
5  * @authors
6  * Copyright (C) 2019 Richard Russon <rich@flatcap.org>
7  *
8  * @copyright
9  * This program is free software: you can redistribute it and/or modify it under
10  * the terms of the GNU General Public License as published by the Free Software
11  * Foundation, either version 2 of the License, or (at your option) any later
12  * version.
13  *
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17  * details.
18  *
19  * You should have received a copy of the GNU General Public License along with
20  * this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 /**
24  * @page config_subset Subset of config items
25  *
26  * Subset of config items
27  */
28 
29 #include "config.h"
30 #include <limits.h>
31 #include <stdint.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include "mutt/lib.h"
35 #include "subset.h"
36 #include "set.h"
37 
38 struct Notify;
39 
40 /**
41  * ConfigEventNames - Names for logging
42  */
43 static const struct Mapping ConfigEventNames[] = {
44   // clang-format off
45   { "NT_CONFIG_SET",   NT_CONFIG_SET   },
46   { "NT_CONFIG_RESET", NT_CONFIG_RESET },
47   { NULL, 0 },
48   // clang-format on
49 };
50 
51 /**
52  * elem_list_sort - Sort two HashElem pointers to config
53  * @param a First HashElem
54  * @param b Second HashElem
55  * @retval -1 a precedes b
56  * @retval  0 a and b are identical
57  * @retval  1 b precedes a
58  */
elem_list_sort(const void * a,const void * b)59 int elem_list_sort(const void *a, const void *b)
60 {
61   if (!a || !b)
62     return 0;
63 
64   const struct HashElem *hea = *(struct HashElem const *const *) a;
65   const struct HashElem *heb = *(struct HashElem const *const *) b;
66 
67   return mutt_istr_cmp(hea->key.strkey, heb->key.strkey);
68 }
69 
70 /**
71  * get_elem_list - Create a sorted list of all config items
72  * @param cs ConfigSet to read
73  * @retval ptr Null-terminated array of HashElem
74  */
get_elem_list(struct ConfigSet * cs)75 struct HashElem **get_elem_list(struct ConfigSet *cs)
76 {
77   if (!cs)
78     return NULL;
79 
80   struct HashElem **list = mutt_mem_calloc(1024, sizeof(struct HashElem *));
81   size_t index = 0;
82 
83   struct HashWalkState walk = { 0 };
84   struct HashElem *he = NULL;
85 
86   while ((he = mutt_hash_walk(cs->hash, &walk)))
87   {
88     list[index++] = he;
89     if (index == 1022)
90       break; /* LCOV_EXCL_LINE */
91   }
92 
93   qsort(list, index, sizeof(struct HashElem *), elem_list_sort);
94 
95   return list;
96 }
97 
98 /**
99  * cs_subset_free - Free a Config Subset
100  * @param ptr Subset to free
101  *
102  * @note Config items matching this Subset will be freed
103  */
cs_subset_free(struct ConfigSubset ** ptr)104 void cs_subset_free(struct ConfigSubset **ptr)
105 {
106   if (!ptr || !*ptr)
107     return;
108 
109   struct ConfigSubset *sub = *ptr;
110 
111   if (sub->cs && sub->name)
112   {
113     char scope[256];
114     snprintf(scope, sizeof(scope), "%s:", sub->name);
115 
116     // We don't know if any config items have been set,
117     // so search for anything with a matching scope.
118     struct HashElem **list = get_elem_list(sub->cs);
119     for (size_t i = 0; list[i]; i++)
120     {
121       const char *item = list[i]->key.strkey;
122       if (mutt_str_startswith(item, scope) != 0)
123       {
124         cs_uninherit_variable(sub->cs, item);
125       }
126     }
127     FREE(&list);
128   }
129 
130   notify_free(&sub->notify);
131   FREE(&sub->name);
132   FREE(ptr);
133 }
134 
135 /**
136  * cs_subset_new - Create a new Config Subset
137  * @param name   Name for this Subset
138  * @param sub_parent Parent Subset
139  * @param not_parent Parent Notification
140  * @retval ptr New Subset
141  *
142  * @note The name will be combined with the parents' names
143  */
cs_subset_new(const char * name,struct ConfigSubset * sub_parent,struct Notify * not_parent)144 struct ConfigSubset *cs_subset_new(const char *name, struct ConfigSubset *sub_parent,
145                                    struct Notify *not_parent)
146 {
147   struct ConfigSubset *sub = mutt_mem_calloc(1, sizeof(*sub));
148 
149   if (sub_parent)
150   {
151     sub->parent = sub_parent;
152     sub->cs = sub_parent->cs;
153   }
154 
155   if (name)
156   {
157     char scope[256];
158 
159     if (sub_parent && sub_parent->name)
160       snprintf(scope, sizeof(scope), "%s:%s", sub_parent->name, name);
161     else
162       mutt_str_copy(scope, name, sizeof(scope));
163 
164     sub->name = mutt_str_dup(scope);
165   }
166 
167   sub->notify = notify_new();
168   notify_set_parent(sub->notify, not_parent);
169 
170   return sub;
171 }
172 
173 /**
174  * cs_subset_lookup - Find an inherited config item
175  * @param sub  Subset to search
176  * @param name Name of Config item to find
177  * @retval ptr HashElem of the config item
178  */
cs_subset_lookup(const struct ConfigSubset * sub,const char * name)179 struct HashElem *cs_subset_lookup(const struct ConfigSubset *sub, const char *name)
180 {
181   if (!sub || !name)
182     return NULL;
183 
184   char scope[256];
185   if (sub->name)
186     snprintf(scope, sizeof(scope), "%s:%s", sub->name, name);
187   else
188     mutt_str_copy(scope, name, sizeof(scope));
189 
190   return cs_get_elem(sub->cs, scope);
191 }
192 
193 /**
194  * cs_subset_create_inheritance - Create a Subset config item (inherited)
195  * @param sub  Config Subset
196  * @param name Name of config item
197  * @retval ptr  HashElem of the config item
198  * @retval NULL Error
199  */
cs_subset_create_inheritance(const struct ConfigSubset * sub,const char * name)200 struct HashElem *cs_subset_create_inheritance(const struct ConfigSubset *sub, const char *name)
201 {
202   if (!sub)
203     return NULL;
204 
205   struct HashElem *he = cs_subset_lookup(sub, name);
206   if (he)
207     return he;
208 
209   if (sub->parent)
210   {
211     // Create parent before creating name
212     he = cs_subset_create_inheritance(sub->parent, name);
213   }
214 
215   if (!he)
216     return NULL;
217 
218   char scope[256];
219   snprintf(scope, sizeof(scope), "%s:%s", sub->name, name);
220   return cs_inherit_variable(sub->cs, he, scope);
221 }
222 
223 /**
224  * cs_subset_notify_observers - Notify all observers of an event
225  * @param sub  Config Subset
226  * @param he   HashElem representing config item
227  * @param ev   Type of event
228  */
cs_subset_notify_observers(const struct ConfigSubset * sub,struct HashElem * he,enum NotifyConfig ev)229 void cs_subset_notify_observers(const struct ConfigSubset *sub,
230                                 struct HashElem *he, enum NotifyConfig ev)
231 {
232   if (!sub || !he)
233     return;
234 
235   struct HashElem *he_base = cs_get_base(he);
236   struct EventConfig ev_c = { sub, he_base->key.strkey, he };
237   mutt_debug(LL_NOTIFY, "%s: %s\n",
238              NONULL(mutt_map_get_name(ev, ConfigEventNames)), he_base->key.strkey);
239   notify_send(sub->notify, NT_CONFIG, ev, &ev_c);
240 }
241 
242 /**
243  * cs_subset_he_native_get - Natively get the value of a HashElem config item
244  * @param sub Config Subset
245  * @param he  HashElem representing config item
246  * @param err Buffer for error messages
247  * @retval intptr_t Native pointer/value
248  * @retval INT_MIN  Error
249  */
cs_subset_he_native_get(const struct ConfigSubset * sub,struct HashElem * he,struct Buffer * err)250 intptr_t cs_subset_he_native_get(const struct ConfigSubset *sub,
251                                  struct HashElem *he, struct Buffer *err)
252 {
253   if (!sub)
254     return INT_MIN;
255 
256   return cs_he_native_get(sub->cs, he, err);
257 }
258 
259 /**
260  * cs_subset_str_native_get - Natively get the value of a string config item
261  * @param sub  Config Subset
262  * @param name Name of config item
263  * @param err Buffer for error messages
264  * @retval intptr_t Native pointer/value
265  * @retval INT_MIN  Error
266  */
cs_subset_str_native_get(const struct ConfigSubset * sub,const char * name,struct Buffer * err)267 intptr_t cs_subset_str_native_get(const struct ConfigSubset *sub,
268                                   const char *name, struct Buffer *err)
269 {
270   struct HashElem *he = cs_subset_create_inheritance(sub, name);
271 
272   return cs_subset_he_native_get(sub, he, err);
273 }
274 
275 /**
276  * cs_subset_he_native_set - Natively set the value of a HashElem config item
277  * @param sub   Config Subset
278  * @param he    HashElem representing config item
279  * @param value Native pointer/value to set
280  * @param err   Buffer for error messages
281  * @retval num Result, e.g. #CSR_SUCCESS
282  */
cs_subset_he_native_set(const struct ConfigSubset * sub,struct HashElem * he,intptr_t value,struct Buffer * err)283 int cs_subset_he_native_set(const struct ConfigSubset *sub, struct HashElem *he,
284                             intptr_t value, struct Buffer *err)
285 {
286   if (!sub)
287     return CSR_ERR_CODE;
288 
289   int rc = cs_he_native_set(sub->cs, he, value, err);
290 
291   if ((CSR_RESULT(rc) == CSR_SUCCESS) && !(rc & CSR_SUC_NO_CHANGE))
292     cs_subset_notify_observers(sub, he, NT_CONFIG_SET);
293 
294   return rc;
295 }
296 
297 /**
298  * cs_subset_str_native_set - Natively set the value of a string config item
299  * @param sub   Config Subset
300  * @param name  Name of config item
301  * @param value Native pointer/value to set
302  * @param err   Buffer for error messages
303  * @retval num Result, e.g. #CSR_SUCCESS
304  */
cs_subset_str_native_set(const struct ConfigSubset * sub,const char * name,intptr_t value,struct Buffer * err)305 int cs_subset_str_native_set(const struct ConfigSubset *sub, const char *name,
306                              intptr_t value, struct Buffer *err)
307 {
308   struct HashElem *he = cs_subset_create_inheritance(sub, name);
309 
310   return cs_subset_he_native_set(sub, he, value, err);
311 }
312 
313 /**
314  * cs_subset_he_reset - Reset a config item to its initial value
315  * @param sub  Config Subset
316  * @param he   HashElem representing config item
317  * @param err  Buffer for error messages
318  * @retval num Result, e.g. #CSR_SUCCESS
319  */
cs_subset_he_reset(const struct ConfigSubset * sub,struct HashElem * he,struct Buffer * err)320 int cs_subset_he_reset(const struct ConfigSubset *sub, struct HashElem *he, struct Buffer *err)
321 {
322   if (!sub)
323     return CSR_ERR_CODE;
324 
325   int rc = cs_he_reset(sub->cs, he, err);
326 
327   if ((CSR_RESULT(rc) == CSR_SUCCESS) && !(rc & CSR_SUC_NO_CHANGE))
328     cs_subset_notify_observers(sub, he, NT_CONFIG_RESET);
329 
330   return rc;
331 }
332 
333 /**
334  * cs_subset_str_reset - Reset a config item to its initial value
335  * @param sub  Config Subset
336  * @param name Name of config item
337  * @param err  Buffer for error messages
338  * @retval num Result, e.g. #CSR_SUCCESS
339  */
cs_subset_str_reset(const struct ConfigSubset * sub,const char * name,struct Buffer * err)340 int cs_subset_str_reset(const struct ConfigSubset *sub, const char *name, struct Buffer *err)
341 {
342   struct HashElem *he = cs_subset_create_inheritance(sub, name);
343 
344   return cs_subset_he_reset(sub, he, err);
345 }
346 
347 /**
348  * cs_subset_he_string_get - Get a config item as a string
349  * @param sub    Config Subset
350  * @param he     HashElem representing config item
351  * @param result Buffer for results or error messages
352  * @retval num Result, e.g. #CSR_SUCCESS
353  */
cs_subset_he_string_get(const struct ConfigSubset * sub,struct HashElem * he,struct Buffer * result)354 int cs_subset_he_string_get(const struct ConfigSubset *sub, struct HashElem *he,
355                             struct Buffer *result)
356 {
357   if (!sub)
358     return CSR_ERR_CODE;
359 
360   return cs_he_string_get(sub->cs, he, result);
361 }
362 
363 /**
364  * cs_subset_str_string_get - Get a config item as a string
365  * @param sub    Config Subset
366  * @param name   Name of config item
367  * @param result Buffer for results or error messages
368  * @retval num Result, e.g. #CSR_SUCCESS
369  */
cs_subset_str_string_get(const struct ConfigSubset * sub,const char * name,struct Buffer * result)370 int cs_subset_str_string_get(const struct ConfigSubset *sub, const char *name,
371                              struct Buffer *result)
372 {
373   struct HashElem *he = cs_subset_create_inheritance(sub, name);
374 
375   return cs_subset_he_string_get(sub, he, result);
376 }
377 
378 /**
379  * cs_subset_he_string_set - Set a config item by string
380  * @param sub   Config Subset
381  * @param he    HashElem representing config item
382  * @param value Value to set
383  * @param err   Buffer for error messages
384  * @retval num Result, e.g. #CSR_SUCCESS
385  */
cs_subset_he_string_set(const struct ConfigSubset * sub,struct HashElem * he,const char * value,struct Buffer * err)386 int cs_subset_he_string_set(const struct ConfigSubset *sub, struct HashElem *he,
387                             const char *value, struct Buffer *err)
388 {
389   if (!sub)
390     return CSR_ERR_CODE;
391 
392   int rc = cs_he_string_set(sub->cs, he, value, err);
393 
394   if ((CSR_RESULT(rc) == CSR_SUCCESS) && !(rc & CSR_SUC_NO_CHANGE))
395     cs_subset_notify_observers(sub, he, NT_CONFIG_SET);
396 
397   return rc;
398 }
399 
400 /**
401  * cs_subset_str_string_set - Set a config item by string
402  * @param sub   Config Subset
403  * @param name  Name of config item
404  * @param value Value to set
405  * @param err   Buffer for error messages
406  * @retval num Result, e.g. #CSR_SUCCESS
407  */
cs_subset_str_string_set(const struct ConfigSubset * sub,const char * name,const char * value,struct Buffer * err)408 int cs_subset_str_string_set(const struct ConfigSubset *sub, const char *name,
409                              const char *value, struct Buffer *err)
410 {
411   struct HashElem *he = cs_subset_create_inheritance(sub, name);
412 
413   return cs_subset_he_string_set(sub, he, value, err);
414 }
415 
416 /**
417  * cs_subset_he_string_plus_equals - Add to a config item by string
418  * @param sub   Config Subset
419  * @param he    HashElem representing config item
420  * @param value Value to set
421  * @param err   Buffer for error messages
422  * @retval num Result, e.g. #CSR_SUCCESS
423  */
cs_subset_he_string_plus_equals(const struct ConfigSubset * sub,struct HashElem * he,const char * value,struct Buffer * err)424 int cs_subset_he_string_plus_equals(const struct ConfigSubset *sub, struct HashElem *he,
425                                     const char *value, struct Buffer *err)
426 {
427   if (!sub)
428     return CSR_ERR_CODE;
429 
430   int rc = cs_he_string_plus_equals(sub->cs, he, value, err);
431 
432   if ((CSR_RESULT(rc) == CSR_SUCCESS) && !(rc & CSR_SUC_NO_CHANGE))
433     cs_subset_notify_observers(sub, he, NT_CONFIG_SET);
434 
435   return rc;
436 }
437 
438 /**
439  * cs_subset_str_string_plus_equals - Add to a config item by string
440  * @param sub   Config Subset
441  * @param name  Name of config item
442  * @param value Value to set
443  * @param err   Buffer for error messages
444  * @retval num Result, e.g. #CSR_SUCCESS
445  */
cs_subset_str_string_plus_equals(const struct ConfigSubset * sub,const char * name,const char * value,struct Buffer * err)446 int cs_subset_str_string_plus_equals(const struct ConfigSubset *sub, const char *name,
447                                      const char *value, struct Buffer *err)
448 {
449   struct HashElem *he = cs_subset_create_inheritance(sub, name);
450 
451   return cs_subset_he_string_plus_equals(sub, he, value, err);
452 }
453 
454 /**
455  * cs_subset_he_string_minus_equals - Remove from a config item by string
456  * @param sub   Config Subset
457  * @param he    HashElem representing config item
458  * @param value Value to set
459  * @param err   Buffer for error messages
460  * @retval num Result, e.g. #CSR_SUCCESS
461  */
cs_subset_he_string_minus_equals(const struct ConfigSubset * sub,struct HashElem * he,const char * value,struct Buffer * err)462 int cs_subset_he_string_minus_equals(const struct ConfigSubset *sub, struct HashElem *he,
463                                      const char *value, struct Buffer *err)
464 {
465   if (!sub)
466     return CSR_ERR_CODE;
467 
468   int rc = cs_he_string_minus_equals(sub->cs, he, value, err);
469 
470   if ((CSR_RESULT(rc) == CSR_SUCCESS) && !(rc & CSR_SUC_NO_CHANGE))
471     cs_subset_notify_observers(sub, he, NT_CONFIG_SET);
472 
473   return rc;
474 }
475 
476 /**
477  * cs_subset_str_string_minus_equals - Remove from a config item by string
478  * @param sub   Config Subset
479  * @param name  Name of config item
480  * @param value Value to set
481  * @param err   Buffer for error messages
482  * @retval num Result, e.g. #CSR_SUCCESS
483  */
cs_subset_str_string_minus_equals(const struct ConfigSubset * sub,const char * name,const char * value,struct Buffer * err)484 int cs_subset_str_string_minus_equals(const struct ConfigSubset *sub, const char *name,
485                                       const char *value, struct Buffer *err)
486 {
487   struct HashElem *he = cs_subset_create_inheritance(sub, name);
488 
489   return cs_subset_he_string_minus_equals(sub, he, value, err);
490 }
491