1 /**
2  * @file
3  * A collection of config items
4  *
5  * @authors
6  * Copyright (C) 2017-2018 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_set Config Set
25  *
26  * A collection of config items.
27  */
28 
29 #include "config.h"
30 #include <limits.h>
31 #include <stdio.h>
32 #include "mutt/lib.h"
33 #include "set.h"
34 #include "inheritance.h"
35 #include "types.h"
36 
37 struct ConfigSetType RegisteredTypes[18] = {
38   { 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
39 };
40 
41 /**
42  * cs_hashelem_free - Callback function for the Hash Table - Implements ::hash_hdata_free_t - @ingroup hash_hdata_free_api
43  * @param type Object type, e.g. #DT_STRING
44  * @param obj  Object to destroy
45  * @param data ConfigSet associated with the object
46  */
cs_hashelem_free(int type,void * obj,intptr_t data)47 static void cs_hashelem_free(int type, void *obj, intptr_t data)
48 {
49   if (data == 0)
50     return; /* LCOV_EXCL_LINE */
51 
52   struct ConfigSet *cs = (struct ConfigSet *) data;
53 
54   const struct ConfigSetType *cst = NULL;
55 
56   if (type & DT_INHERITED)
57   {
58     struct Inheritance *i = obj;
59 
60     struct HashElem *he_base = cs_get_base(i->parent);
61     struct ConfigDef *cdef = he_base->data;
62 
63     if (!cdef)
64       return; // LCOV_EXCL_LINE
65 
66     cst = cs_get_type_def(cs, he_base->type);
67     if (cst && cst->destroy)
68       cst->destroy(cs, (void **) &i->var, cdef);
69 
70     FREE(&i->name);
71     FREE(&i);
72   }
73   else
74   {
75     struct ConfigDef *cdef = obj;
76 
77     cst = cs_get_type_def(cs, type);
78     if (cst && cst->destroy)
79       cst->destroy(cs, &cdef->var, cdef);
80 
81     /* If we allocated the initial value, clean it up */
82     if (cdef->type & DT_INITIAL_SET)
83       FREE(&cdef->initial);
84   }
85 }
86 
87 /**
88  * create_synonym - Create an alternative name for a config item
89  * @param cs   Config items
90  * @param cdef Variable definition
91  * @param err  Buffer for error messages
92  * @retval ptr New HashElem representing the config item synonym
93  */
create_synonym(const struct ConfigSet * cs,struct ConfigDef * cdef,struct Buffer * err)94 static struct HashElem *create_synonym(const struct ConfigSet *cs,
95                                        struct ConfigDef *cdef, struct Buffer *err)
96 {
97   if (!cs || !cdef)
98     return NULL; /* LCOV_EXCL_LINE */
99 
100   const char *name = (const char *) cdef->initial;
101   struct HashElem *parent = cs_get_elem(cs, name);
102   if (!parent)
103   {
104     mutt_buffer_printf(err, _("No such variable: %s"), name);
105     return NULL;
106   }
107 
108   struct HashElem *child =
109       mutt_hash_typed_insert(cs->hash, cdef->name, cdef->type, (void *) cdef);
110   if (!child)
111     return NULL; /* LCOV_EXCL_LINE */
112 
113   cdef->var = (intptr_t) parent;
114   return child;
115 }
116 
117 /**
118  * reg_one_var - Register one config item
119  * @param cs   Config items
120  * @param cdef Variable definition
121  * @param err  Buffer for error messages
122  * @retval ptr New HashElem representing the config item
123  */
reg_one_var(const struct ConfigSet * cs,struct ConfigDef * cdef,struct Buffer * err)124 static struct HashElem *reg_one_var(const struct ConfigSet *cs,
125                                     struct ConfigDef *cdef, struct Buffer *err)
126 {
127   if (!cs || !cdef)
128     return NULL; /* LCOV_EXCL_LINE */
129 
130   if (DTYPE(cdef->type) == DT_SYNONYM)
131     return create_synonym(cs, cdef, err);
132 
133   const struct ConfigSetType *cst = cs_get_type_def(cs, cdef->type);
134   if (!cst)
135   {
136     mutt_buffer_printf(err, _("Variable '%s' has an invalid type %d"),
137                        cdef->name, cdef->type);
138     return NULL;
139   }
140 
141   struct HashElem *he = mutt_hash_typed_insert(cs->hash, cdef->name, cdef->type, cdef);
142   if (!he)
143     return NULL; /* LCOV_EXCL_LINE */
144 
145   if (cst && cst->reset)
146     cst->reset(cs, &cdef->var, cdef, err);
147 
148   return he;
149 }
150 
151 /**
152  * cs_new - Create a new Config Set
153  * @param size Number of expected config items
154  * @retval ptr New ConfigSet object
155  */
cs_new(size_t size)156 struct ConfigSet *cs_new(size_t size)
157 {
158   struct ConfigSet *cs = mutt_mem_calloc(1, sizeof(*cs));
159 
160   cs->hash = mutt_hash_new(size, MUTT_HASH_NO_FLAGS);
161   mutt_hash_set_destructor(cs->hash, cs_hashelem_free, (intptr_t) cs);
162 
163   return cs;
164 }
165 
166 /**
167  * cs_free - Free a Config Set
168  * @param[out] ptr Config items
169  */
cs_free(struct ConfigSet ** ptr)170 void cs_free(struct ConfigSet **ptr)
171 {
172   if (!ptr || !*ptr)
173     return;
174 
175   struct ConfigSet *cs = *ptr;
176 
177   mutt_hash_free(&cs->hash);
178   FREE(ptr);
179 }
180 
181 /**
182  * cs_get_base - Find the root Config Item
183  * @param he Config Item to examine
184  * @retval ptr Root Config Item
185  *
186  * Given an inherited HashElem, find the HashElem representing the original
187  * Config Item.
188  */
cs_get_base(struct HashElem * he)189 struct HashElem *cs_get_base(struct HashElem *he)
190 {
191   if (!(he->type & DT_INHERITED))
192     return he;
193 
194   struct Inheritance *i = he->data;
195   return cs_get_base(i->parent);
196 }
197 
198 /**
199  * cs_get_elem - Get the HashElem representing a config item
200  * @param cs   Config items
201  * @param name Name of config item
202  * @retval ptr HashElem representing the config item
203  */
cs_get_elem(const struct ConfigSet * cs,const char * name)204 struct HashElem *cs_get_elem(const struct ConfigSet *cs, const char *name)
205 {
206   if (!cs || !name)
207     return NULL;
208 
209   struct HashElem *he = mutt_hash_find_elem(cs->hash, name);
210   if (!he)
211     return NULL;
212 
213   if (DTYPE(he->type) != DT_SYNONYM)
214     return he;
215 
216   const struct ConfigDef *cdef = he->data;
217 
218   return (struct HashElem *) cdef->var;
219 }
220 
221 /**
222  * cs_get_type_def - Get the definition for a type
223  * @param cs   Config items
224  * @param type Type to lookup, e.g. #DT_NUMBER
225  * @retval ptr ConfigSetType representing the type
226  */
cs_get_type_def(const struct ConfigSet * cs,unsigned int type)227 const struct ConfigSetType *cs_get_type_def(const struct ConfigSet *cs, unsigned int type)
228 {
229   if (!cs)
230     return NULL;
231 
232   type = DTYPE(type);
233   if ((type < 1) || (type >= mutt_array_size(cs->types)))
234     return NULL;
235 
236   if (!cs->types[type].name)
237     return NULL;
238 
239   return &cs->types[type];
240 }
241 
242 /**
243  * cs_register_type - Register a type of config item
244  * @param cs   Config items
245  * @param cst  Structure defining the type
246  * @retval true Type was registered successfully
247  */
cs_register_type(struct ConfigSet * cs,const struct ConfigSetType * cst)248 bool cs_register_type(struct ConfigSet *cs, const struct ConfigSetType *cst)
249 {
250   if (!cs || !cst)
251     return false;
252 
253   if (!cst->name || !cst->string_set || !cst->string_get || !cst->reset ||
254       !cst->native_set || !cst->native_get)
255   {
256     return false;
257   }
258 
259   if (cst->type >= mutt_array_size(cs->types))
260     return false;
261 
262   if (cs->types[cst->type].name)
263     return false; /* already registered */
264 
265   cs->types[cst->type] = *cst;
266   return true;
267 }
268 
269 /**
270  * cs_register_variables - Register a set of config items
271  * @param cs    Config items
272  * @param vars  Variable definition
273  * @param flags Flags, e.g. #DT_DEPRECATED
274  * @retval true All variables were registered successfully
275  */
cs_register_variables(const struct ConfigSet * cs,struct ConfigDef vars[],uint32_t flags)276 bool cs_register_variables(const struct ConfigSet *cs, struct ConfigDef vars[], uint32_t flags)
277 {
278   if (!cs || !vars)
279     return false;
280 
281   struct Buffer err = mutt_buffer_make(0);
282 
283   bool rc = true;
284 
285   for (size_t i = 0; vars[i].name; i++)
286   {
287     vars[i].type |= flags;
288     if (!reg_one_var(cs, &vars[i], &err))
289     {
290       mutt_debug(LL_DEBUG1, "%s\n", mutt_buffer_string(&err));
291       rc = false;
292     }
293   }
294 
295   mutt_buffer_dealloc(&err);
296   return rc;
297 }
298 
299 /**
300  * cs_inherit_variable - Create in inherited config item
301  * @param cs     Config items
302  * @param parent HashElem of parent config item
303  * @param name   Name of account-specific config item
304  * @retval ptr New HashElem representing the inherited config item
305  */
cs_inherit_variable(const struct ConfigSet * cs,struct HashElem * parent,const char * name)306 struct HashElem *cs_inherit_variable(const struct ConfigSet *cs,
307                                      struct HashElem *parent, const char *name)
308 {
309   if (!cs || !parent)
310     return NULL;
311 
312   struct Inheritance *i = mutt_mem_calloc(1, sizeof(*i));
313   i->parent = parent;
314   i->name = mutt_str_dup(name);
315 
316   struct HashElem *he = mutt_hash_typed_insert(cs->hash, i->name, DT_INHERITED, i);
317   if (!he)
318   {
319     FREE(&i->name);
320     FREE(&i);
321   }
322 
323   return he;
324 }
325 
326 /**
327  * cs_uninherit_variable - Remove an inherited config item
328  * @param cs   Config items
329  * @param name Name of config item to remove
330  */
cs_uninherit_variable(const struct ConfigSet * cs,const char * name)331 void cs_uninherit_variable(const struct ConfigSet *cs, const char *name)
332 {
333   if (!cs || !name)
334     return;
335 
336   mutt_hash_delete(cs->hash, name, NULL);
337 }
338 
339 /**
340  * cs_he_reset - Reset a config item to its initial value
341  * @param cs   Config items
342  * @param he   HashElem representing config item
343  * @param err  Buffer for error messages
344  * @retval num Result, e.g. #CSR_SUCCESS
345  */
cs_he_reset(const struct ConfigSet * cs,struct HashElem * he,struct Buffer * err)346 int cs_he_reset(const struct ConfigSet *cs, struct HashElem *he, struct Buffer *err)
347 {
348   if (!cs || !he)
349     return CSR_ERR_CODE;
350 
351   /* An inherited var that's already pointing to its parent.
352    * Return 'success', but don't send a notification. */
353   if ((he->type & DT_INHERITED) && (DTYPE(he->type) == 0))
354     return CSR_SUCCESS;
355 
356   int rc = CSR_SUCCESS;
357 
358   if (he->type & DT_INHERITED)
359   {
360     struct Inheritance *i = he->data;
361     struct HashElem *he_base = cs_get_base(he);
362     struct ConfigDef *cdef = he_base->data;
363     if (!cdef)
364       return CSR_ERR_CODE; // LCOV_EXCL_LINE
365 
366     const struct ConfigSetType *cst = cs_get_type_def(cs, he_base->type);
367     if (cst && cst->destroy)
368       cst->destroy(cs, (void **) &i->var, cdef);
369 
370     he->type = DT_INHERITED;
371   }
372   else
373   {
374     struct ConfigDef *cdef = he->data;
375     if (!cdef)
376       return CSR_ERR_CODE; // LCOV_EXCL_LINE
377 
378     const struct ConfigSetType *cst = cs_get_type_def(cs, he->type);
379     if (cst)
380       rc = cst->reset(cs, &cdef->var, cdef, err);
381   }
382 
383   return rc;
384 }
385 
386 /**
387  * cs_str_reset - Reset a config item to its initial value
388  * @param cs   Config items
389  * @param name Name of config item
390  * @param err  Buffer for error messages
391  * @retval num Result, e.g. #CSR_SUCCESS
392  */
cs_str_reset(const struct ConfigSet * cs,const char * name,struct Buffer * err)393 int cs_str_reset(const struct ConfigSet *cs, const char *name, struct Buffer *err)
394 {
395   if (!cs || !name)
396     return CSR_ERR_CODE;
397 
398   struct HashElem *he = cs_get_elem(cs, name);
399   if (!he)
400   {
401     mutt_buffer_printf(err, _("Unknown variable '%s'"), name);
402     return CSR_ERR_UNKNOWN;
403   }
404 
405   return cs_he_reset(cs, he, err);
406 }
407 
408 /**
409  * cs_he_initial_set - Set the initial value of a config item
410  * @param cs    Config items
411  * @param he    HashElem representing config item
412  * @param value Value to set
413  * @param err   Buffer for error messages
414  * @retval num Result, e.g. #CSR_SUCCESS
415  */
cs_he_initial_set(const struct ConfigSet * cs,struct HashElem * he,const char * value,struct Buffer * err)416 int cs_he_initial_set(const struct ConfigSet *cs, struct HashElem *he,
417                       const char *value, struct Buffer *err)
418 {
419   if (!cs || !he)
420     return CSR_ERR_CODE;
421 
422   struct ConfigDef *cdef = NULL;
423 
424   if (he->type & DT_INHERITED)
425   {
426     struct HashElem *he_base = cs_get_base(he);
427     cdef = he_base->data;
428     mutt_debug(LL_DEBUG1, "Variable '%s' is inherited type\n", cdef->name);
429     return CSR_ERR_CODE;
430   }
431 
432   cdef = he->data;
433   if (!cdef)
434     return CSR_ERR_CODE; // LCOV_EXCL_LINE
435 
436   const struct ConfigSetType *cst = cs_get_type_def(cs, he->type);
437   if (!cst)
438   {
439     mutt_debug(LL_DEBUG1, "Variable '%s' has an invalid type %d\n", cdef->name, he->type);
440     return CSR_ERR_CODE;
441   }
442 
443   int rc = cst->string_set(cs, NULL, cdef, value, err);
444   if (CSR_RESULT(rc) != CSR_SUCCESS)
445     return rc;
446 
447   return CSR_SUCCESS;
448 }
449 
450 /**
451  * cs_str_initial_set - Set the initial value of a config item
452  * @param cs    Config items
453  * @param name  Name of config item
454  * @param value Value to set
455  * @param err   Buffer for error messages
456  * @retval num Result, e.g. #CSR_SUCCESS
457  */
cs_str_initial_set(const struct ConfigSet * cs,const char * name,const char * value,struct Buffer * err)458 int cs_str_initial_set(const struct ConfigSet *cs, const char *name,
459                        const char *value, struct Buffer *err)
460 {
461   if (!cs || !name)
462     return CSR_ERR_CODE;
463 
464   struct HashElem *he = cs_get_elem(cs, name);
465   if (!he)
466   {
467     mutt_buffer_printf(err, _("Unknown variable '%s'"), name);
468     return CSR_ERR_UNKNOWN;
469   }
470 
471   return cs_he_initial_set(cs, he, value, err);
472 }
473 
474 /**
475  * cs_he_initial_get - Get the initial, or parent, value of a config item
476  * @param cs     Config items
477  * @param he     HashElem representing config item
478  * @param result Buffer for results or error messages
479  * @retval num Result, e.g. #CSR_SUCCESS
480  *
481  * If a config item is inherited from another, then this will get the parent's
482  * value.  Otherwise, it will get the config item's initial value.
483  */
cs_he_initial_get(const struct ConfigSet * cs,struct HashElem * he,struct Buffer * result)484 int cs_he_initial_get(const struct ConfigSet *cs, struct HashElem *he, struct Buffer *result)
485 {
486   if (!cs || !he || !result)
487     return CSR_ERR_CODE;
488 
489   const struct ConfigDef *cdef = NULL;
490   const struct ConfigSetType *cst = NULL;
491 
492   if (he->type & DT_INHERITED)
493   {
494     struct HashElem *he_base = cs_get_base(he);
495     cdef = he_base->data;
496     cst = cs_get_type_def(cs, he_base->type);
497   }
498   else
499   {
500     cdef = he->data;
501     cst = cs_get_type_def(cs, he->type);
502   }
503 
504   if (!cst)
505     return CSR_ERR_CODE; // LCOV_EXCL_LINE
506 
507   return cst->string_get(cs, NULL, cdef, result);
508 }
509 
510 /**
511  * cs_str_initial_get - Get the initial, or parent, value of a config item
512  * @param cs     Config items
513  * @param name   Name of config item
514  * @param result Buffer for results or error messages
515  * @retval num Result, e.g. #CSR_SUCCESS
516  *
517  * If a config item is inherited from another, then this will get the parent's
518  * value.  Otherwise, it will get the config item's initial value.
519  */
cs_str_initial_get(const struct ConfigSet * cs,const char * name,struct Buffer * result)520 int cs_str_initial_get(const struct ConfigSet *cs, const char *name, struct Buffer *result)
521 {
522   if (!cs || !name)
523     return CSR_ERR_CODE;
524 
525   struct HashElem *he = cs_get_elem(cs, name);
526   if (!he)
527   {
528     mutt_buffer_printf(result, _("Unknown variable '%s'"), name);
529     return CSR_ERR_UNKNOWN;
530   }
531 
532   return cs_he_initial_get(cs, he, result);
533 }
534 
535 /**
536  * cs_he_string_set - Set a config item by string
537  * @param cs    Config items
538  * @param he    HashElem representing config item
539  * @param value Value to set
540  * @param err   Buffer for error messages
541  * @retval num Result, e.g. #CSR_SUCCESS
542  */
cs_he_string_set(const struct ConfigSet * cs,struct HashElem * he,const char * value,struct Buffer * err)543 int cs_he_string_set(const struct ConfigSet *cs, struct HashElem *he,
544                      const char *value, struct Buffer *err)
545 {
546   if (!cs || !he)
547     return CSR_ERR_CODE;
548 
549   struct ConfigDef *cdef = NULL;
550   const struct ConfigSetType *cst = NULL;
551   void *var = NULL;
552 
553   if (he->type & DT_INHERITED)
554   {
555     struct Inheritance *i = he->data;
556     struct HashElem *he_base = cs_get_base(he);
557     cdef = he_base->data;
558     cst = cs_get_type_def(cs, he_base->type);
559     var = &i->var;
560   }
561   else
562   {
563     cdef = he->data;
564     cst = cs_get_type_def(cs, he->type);
565     var = &cdef->var;
566   }
567 
568   if (!cdef)
569     return CSR_ERR_CODE; // LCOV_EXCL_LINE
570 
571   if (!cst)
572   {
573     mutt_debug(LL_DEBUG1, "Variable '%s' has an invalid type %d\n", cdef->name, he->type);
574     return CSR_ERR_CODE;
575   }
576 
577   int rc = cst->string_set(cs, var, cdef, value, err);
578   if (CSR_RESULT(rc) != CSR_SUCCESS)
579     return rc;
580 
581   if (he->type & DT_INHERITED)
582     he->type = cdef->type | DT_INHERITED;
583 
584   return rc;
585 }
586 
587 /**
588  * cs_str_string_set - Set a config item by string
589  * @param cs    Config items
590  * @param name  Name of config item
591  * @param value Value to set
592  * @param err   Buffer for error messages
593  * @retval num Result, e.g. #CSR_SUCCESS
594  */
cs_str_string_set(const struct ConfigSet * cs,const char * name,const char * value,struct Buffer * err)595 int cs_str_string_set(const struct ConfigSet *cs, const char *name,
596                       const char *value, struct Buffer *err)
597 {
598   if (!cs || !name)
599     return CSR_ERR_CODE;
600 
601   struct HashElem *he = cs_get_elem(cs, name);
602   if (!he)
603   {
604     mutt_buffer_printf(err, _("Unknown variable '%s'"), name);
605     return CSR_ERR_UNKNOWN;
606   }
607 
608   return cs_he_string_set(cs, he, value, err);
609 }
610 
611 /**
612  * cs_he_string_get - Get a config item as a string
613  * @param cs     Config items
614  * @param he     HashElem representing config item
615  * @param result Buffer for results or error messages
616  * @retval num Result, e.g. #CSR_SUCCESS
617  */
cs_he_string_get(const struct ConfigSet * cs,struct HashElem * he,struct Buffer * result)618 int cs_he_string_get(const struct ConfigSet *cs, struct HashElem *he, struct Buffer *result)
619 {
620   if (!cs || !he || !result)
621     return CSR_ERR_CODE;
622 
623   struct ConfigDef *cdef = NULL;
624   const struct ConfigSetType *cst = NULL;
625   void *var = NULL;
626 
627   if (he->type & DT_INHERITED)
628   {
629     struct Inheritance *i = he->data;
630 
631     // inherited, value not set
632     if (DTYPE(he->type) == 0)
633       return cs_he_string_get(cs, i->parent, result);
634 
635     // inherited, value set
636     struct HashElem *he_base = cs_get_base(he);
637     cdef = he_base->data;
638     cst = cs_get_type_def(cs, he_base->type);
639     var = &i->var;
640   }
641   else
642   {
643     // not inherited
644     cdef = he->data;
645     cst = cs_get_type_def(cs, he->type);
646     var = &cdef->var;
647   }
648 
649   if (!cdef || !cst)
650     return CSR_ERR_CODE; // LCOV_EXCL_LINE
651 
652   return cst->string_get(cs, var, cdef, result);
653 }
654 
655 /**
656  * cs_str_string_get - Get a config item as a string
657  * @param cs     Config items
658  * @param name   Name of config item
659  * @param result Buffer for results or error messages
660  * @retval num Result, e.g. #CSR_SUCCESS
661  */
cs_str_string_get(const struct ConfigSet * cs,const char * name,struct Buffer * result)662 int cs_str_string_get(const struct ConfigSet *cs, const char *name, struct Buffer *result)
663 {
664   if (!cs || !name)
665     return CSR_ERR_CODE;
666 
667   struct HashElem *he = cs_get_elem(cs, name);
668   if (!he)
669   {
670     mutt_buffer_printf(result, _("Unknown variable '%s'"), name);
671     return CSR_ERR_UNKNOWN;
672   }
673 
674   return cs_he_string_get(cs, he, result);
675 }
676 
677 /**
678  * cs_he_native_set - Natively set the value of a HashElem config item
679  * @param cs    Config items
680  * @param he    HashElem representing config item
681  * @param value Native pointer/value to set
682  * @param err   Buffer for error messages
683  * @retval num Result, e.g. #CSR_SUCCESS
684  */
cs_he_native_set(const struct ConfigSet * cs,struct HashElem * he,intptr_t value,struct Buffer * err)685 int cs_he_native_set(const struct ConfigSet *cs, struct HashElem *he,
686                      intptr_t value, struct Buffer *err)
687 {
688   if (!cs || !he)
689     return CSR_ERR_CODE;
690 
691   struct ConfigDef *cdef = NULL;
692   const struct ConfigSetType *cst = NULL;
693   void *var = NULL;
694 
695   if (he->type & DT_INHERITED)
696   {
697     struct Inheritance *i = he->data;
698     struct HashElem *he_base = cs_get_base(he);
699     cdef = he_base->data;
700     cst = cs_get_type_def(cs, he_base->type);
701     var = &i->var;
702   }
703   else
704   {
705     cdef = he->data;
706     cst = cs_get_type_def(cs, he->type);
707     var = &cdef->var;
708   }
709 
710   if (!cst)
711   {
712     mutt_debug(LL_DEBUG1, "Variable '%s' has an invalid type %d\n", cdef->name, he->type);
713     return CSR_ERR_CODE;
714   }
715 
716   if (!var || !cdef)
717     return CSR_ERR_CODE; // LCOV_EXCL_LINE
718 
719   int rc = cst->native_set(cs, var, cdef, value, err);
720   if (CSR_RESULT(rc) != CSR_SUCCESS)
721     return rc;
722 
723   if (he->type & DT_INHERITED)
724     he->type = cdef->type | DT_INHERITED;
725 
726   return rc;
727 }
728 
729 /**
730  * cs_str_native_set - Natively set the value of a string config item
731  * @param cs    Config items
732  * @param name  Name of config item
733  * @param value Native pointer/value to set
734  * @param err   Buffer for error messages
735  * @retval num Result, e.g. #CSR_SUCCESS
736  */
cs_str_native_set(const struct ConfigSet * cs,const char * name,intptr_t value,struct Buffer * err)737 int cs_str_native_set(const struct ConfigSet *cs, const char *name,
738                       intptr_t value, struct Buffer *err)
739 {
740   if (!cs || !name)
741     return CSR_ERR_CODE;
742 
743   struct HashElem *he = cs_get_elem(cs, name);
744   if (!he)
745   {
746     mutt_buffer_printf(err, _("Unknown variable '%s'"), name);
747     return CSR_ERR_UNKNOWN;
748   }
749 
750   struct ConfigDef *cdef = NULL;
751   const struct ConfigSetType *cst = NULL;
752   void *var = NULL;
753 
754   if (he->type & DT_INHERITED)
755   {
756     struct Inheritance *i = he->data;
757     struct HashElem *he_base = cs_get_base(he);
758     cdef = he_base->data;
759     cst = cs_get_type_def(cs, he_base->type);
760     var = &i->var;
761   }
762   else
763   {
764     cdef = he->data;
765     cst = cs_get_type_def(cs, he->type);
766     var = &cdef->var;
767   }
768 
769   if (!cst || !var || !cdef)
770     return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
771 
772   int rc = cst->native_set(cs, var, cdef, value, err);
773   if (CSR_RESULT(rc) != CSR_SUCCESS)
774     return rc;
775 
776   if (he->type & DT_INHERITED)
777     he->type = cdef->type | DT_INHERITED;
778 
779   return rc;
780 }
781 
782 /**
783  * cs_he_native_get - Natively get the value of a HashElem config item
784  * @param cs  Config items
785  * @param he  HashElem representing config item
786  * @param err Buffer for results or error messages
787  * @retval intptr_t Native pointer/value
788  * @retval INT_MIN  Error
789  */
cs_he_native_get(const struct ConfigSet * cs,struct HashElem * he,struct Buffer * err)790 intptr_t cs_he_native_get(const struct ConfigSet *cs, struct HashElem *he, struct Buffer *err)
791 {
792   if (!cs || !he)
793     return INT_MIN;
794 
795   struct ConfigDef *cdef = NULL;
796   const struct ConfigSetType *cst = NULL;
797   void *var = NULL;
798 
799   if (he->type & DT_INHERITED)
800   {
801     struct Inheritance *i = he->data;
802 
803     // inherited, value not set
804     if (DTYPE(he->type) == 0)
805       return cs_he_native_get(cs, i->parent, err);
806 
807     // inherited, value set
808     struct HashElem *he_base = cs_get_base(he);
809     cdef = he_base->data;
810     cst = cs_get_type_def(cs, he_base->type);
811     var = &i->var;
812   }
813   else
814   {
815     // not inherited
816     cdef = he->data;
817     cst = cs_get_type_def(cs, he->type);
818     var = &cdef->var;
819   }
820 
821   if (!var || !cdef)
822     return INT_MIN; // LCOV_EXCL_LINE
823 
824   if (!cst)
825   {
826     mutt_buffer_printf(err, _("Variable '%s' has an invalid type %d"), cdef->name, he->type);
827     return INT_MIN;
828   }
829 
830   return cst->native_get(cs, var, cdef, err);
831 }
832 
833 /**
834  * cs_str_native_get - Natively get the value of a string config item
835  * @param cs   Config items
836  * @param name Name of config item
837  * @param err  Buffer for error messages
838  * @retval intptr_t Native pointer/value
839  * @retval INT_MIN  Error
840  */
cs_str_native_get(const struct ConfigSet * cs,const char * name,struct Buffer * err)841 intptr_t cs_str_native_get(const struct ConfigSet *cs, const char *name, struct Buffer *err)
842 {
843   if (!cs || !name)
844     return INT_MIN;
845 
846   struct HashElem *he = cs_get_elem(cs, name);
847   return cs_he_native_get(cs, he, err);
848 }
849 
850 /**
851  * cs_he_string_plus_equals - Add to a config item by string
852  * @param cs    Config items
853  * @param he    HashElem representing config item
854  * @param value Value to set
855  * @param err   Buffer for error messages
856  * @retval num Result, e.g. #CSR_SUCCESS
857  */
cs_he_string_plus_equals(const struct ConfigSet * cs,struct HashElem * he,const char * value,struct Buffer * err)858 int cs_he_string_plus_equals(const struct ConfigSet *cs, struct HashElem *he,
859                              const char *value, struct Buffer *err)
860 {
861   if (!cs || !he)
862     return CSR_ERR_CODE;
863 
864   struct ConfigDef *cdef = NULL;
865   const struct ConfigSetType *cst = NULL;
866   void *var = NULL;
867 
868   if (he->type & DT_INHERITED)
869   {
870     struct Inheritance *i = he->data;
871     struct HashElem *he_base = cs_get_base(he);
872     cdef = he_base->data;
873     cst = cs_get_type_def(cs, he_base->type);
874     var = &i->var;
875   }
876   else
877   {
878     cdef = he->data;
879     cst = cs_get_type_def(cs, he->type);
880     var = &cdef->var;
881   }
882 
883   if (!var || !cdef)
884     return INT_MIN; // LCOV_EXCL_LINE
885 
886   if (!cst)
887   {
888     mutt_debug(LL_DEBUG1, "Variable '%s' has an invalid type %d\n", cdef->name, he->type);
889     return CSR_ERR_CODE;
890   }
891 
892   if (!cst->string_plus_equals)
893   {
894     // L10N: e.g. Type 'boolean' doesn't support operation '+='
895     mutt_buffer_printf(err, _("Type '%s' doesn't support operation '%s'"), cst->name, "+=");
896     return CSR_ERR_INVALID | CSV_INV_NOT_IMPL;
897   }
898 
899   int rc = cst->string_plus_equals(cs, var, cdef, value, err);
900   if (CSR_RESULT(rc) != CSR_SUCCESS)
901     return rc;
902 
903   if (he->type & DT_INHERITED)
904     he->type = cdef->type | DT_INHERITED;
905 
906   return rc;
907 }
908 
909 /**
910  * cs_str_string_plus_equals - Add to a config item by string
911  * @param cs    Config items
912  * @param name  Name of config item
913  * @param value Value to set
914  * @param err   Buffer for error messages
915  * @retval num Result, e.g. #CSR_SUCCESS
916  */
cs_str_string_plus_equals(const struct ConfigSet * cs,const char * name,const char * value,struct Buffer * err)917 int cs_str_string_plus_equals(const struct ConfigSet *cs, const char *name,
918                               const char *value, struct Buffer *err)
919 {
920   if (!cs || !name)
921     return CSR_ERR_CODE;
922 
923   struct HashElem *he = cs_get_elem(cs, name);
924   if (!he)
925   {
926     mutt_buffer_printf(err, _("Unknown variable '%s'"), name);
927     return CSR_ERR_UNKNOWN;
928   }
929 
930   return cs_he_string_plus_equals(cs, he, value, err);
931 }
932 
933 /**
934  * cs_he_string_minus_equals - Remove from a config item by string
935  * @param cs    Config items
936  * @param he    HashElem representing config item
937  * @param value Value to set
938  * @param err   Buffer for error messages
939  * @retval num Result, e.g. #CSR_SUCCESS
940  */
cs_he_string_minus_equals(const struct ConfigSet * cs,struct HashElem * he,const char * value,struct Buffer * err)941 int cs_he_string_minus_equals(const struct ConfigSet *cs, struct HashElem *he,
942                               const char *value, struct Buffer *err)
943 {
944   if (!cs || !he)
945     return CSR_ERR_CODE;
946 
947   struct ConfigDef *cdef = NULL;
948   const struct ConfigSetType *cst = NULL;
949   void *var = NULL;
950 
951   if (he->type & DT_INHERITED)
952   {
953     struct Inheritance *i = he->data;
954     struct HashElem *he_base = cs_get_base(he);
955     cdef = he_base->data;
956     cst = cs_get_type_def(cs, he_base->type);
957     var = &i->var;
958   }
959   else
960   {
961     cdef = he->data;
962     cst = cs_get_type_def(cs, he->type);
963     var = &cdef->var;
964   }
965 
966   if (!var || !cdef)
967     return INT_MIN; // LCOV_EXCL_LINE
968 
969   if (!cst)
970   {
971     mutt_debug(LL_DEBUG1, "Variable '%s' has an invalid type %d\n", cdef->name, he->type);
972     return CSR_ERR_CODE;
973   }
974 
975   if (!cst->string_minus_equals)
976   {
977     // L10N: e.g. Type 'boolean' doesn't support operation '+='
978     mutt_buffer_printf(err, _("Type '%s' doesn't support operation '%s'"), cst->name, "-=");
979     return CSR_ERR_INVALID | CSV_INV_NOT_IMPL;
980   }
981 
982   int rc = cst->string_minus_equals(cs, var, cdef, value, err);
983   if (CSR_RESULT(rc) != CSR_SUCCESS)
984     return rc;
985 
986   if (he->type & DT_INHERITED)
987     he->type = cdef->type | DT_INHERITED;
988 
989   return rc;
990 }
991 
992 /**
993  * cs_str_string_minus_equals - Remove from a config item by string
994  * @param cs    Config items
995  * @param name  Name of config item
996  * @param value Value to set
997  * @param err   Buffer for error messages
998  * @retval num Result, e.g. #CSR_SUCCESS
999  */
cs_str_string_minus_equals(const struct ConfigSet * cs,const char * name,const char * value,struct Buffer * err)1000 int cs_str_string_minus_equals(const struct ConfigSet *cs, const char *name,
1001                                const char *value, struct Buffer *err)
1002 {
1003   if (!cs || !name)
1004     return CSR_ERR_CODE;
1005 
1006   struct HashElem *he = cs_get_elem(cs, name);
1007   if (!he)
1008   {
1009     mutt_buffer_printf(err, _("Unknown variable '%s'"), name);
1010     return CSR_ERR_UNKNOWN;
1011   }
1012 
1013   return cs_he_string_minus_equals(cs, he, value, err);
1014 }
1015