/* cfg_driver.c -- Main driver for Mailutils configuration files Copyright (C) 2007-2021 Free Software Foundation, Inc. GNU Mailutils is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Mailutils is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Mailutils. If not, see . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static mu_assoc_t section_tab; static void alloc_section_tab () { if (!section_tab) mu_assoc_create (§ion_tab, MU_ASSOC_COPY_KEY); } int mu_create_canned_section (char *name, struct mu_cfg_section **psection) { int rc; struct mu_cfg_cont **pcont; alloc_section_tab (); rc = mu_assoc_install_ref (section_tab, name, &pcont); if (rc == 0) { mu_config_create_container (pcont, mu_cfg_cont_section); *psection = &(*pcont)->v.section; (*psection)->ident = name; } else if (rc == MU_ERR_EXISTS) *psection = &(*pcont)->v.section; return rc; } int mu_create_canned_param (char *name, struct mu_cfg_param **pparam) { int rc; struct mu_cfg_cont **pcont; alloc_section_tab (); rc = mu_assoc_install_ref (section_tab, name, &pcont); if (rc == 0) { mu_config_create_container (pcont, mu_cfg_cont_param); *pparam = &(*pcont)->v.param; (*pparam)->ident = name; } else if (rc == MU_ERR_EXISTS) *pparam = &(*pcont)->v.param; return rc; } struct mu_cfg_cont * mu_get_canned_container (const char *name) { return mu_assoc_get (section_tab, name); } static struct mu_cfg_cont *root_container; int mu_config_create_container (struct mu_cfg_cont **pcont, enum mu_cfg_cont_type type) { struct mu_cfg_cont *cont; int rc; cont = calloc (1, sizeof (*cont)); if (!cont) return ENOMEM; rc = mu_refcount_create (&cont->refcount); if (rc) free (cont); else { cont->type = type; *pcont = cont; } return rc; } struct dup_data { struct mu_cfg_cont *cont; }; static int dup_container (struct mu_cfg_cont **pcont); static int _dup_cont_action (void *item, void *cbdata) { int rc; struct mu_cfg_cont *cont = item; struct dup_data *pdd = cbdata; rc = dup_container (&cont); if (rc) return rc; if (!pdd->cont->v.section.children) { int rc = mu_list_create (&pdd->cont->v.section.children); if (rc) return rc; } return mu_list_append (pdd->cont->v.section.children, cont); } static int dup_container (struct mu_cfg_cont **pcont) { int rc; struct mu_cfg_cont *newcont, *oldcont = *pcont; struct dup_data dd; rc = mu_config_create_container (&newcont, oldcont->type); if (rc) return rc; dd.cont = newcont; switch (oldcont->type) { case mu_cfg_cont_section: newcont->v.section.ident = oldcont->v.section.ident; newcont->v.section.label = oldcont->v.section.label; newcont->v.section.parser = oldcont->v.section.parser; newcont->v.section.data = oldcont->v.section.data; newcont->v.section.offset = oldcont->v.section.offset; newcont->v.section.docstring = oldcont->v.section.docstring; newcont->v.section.children = NULL; rc = mu_list_foreach (oldcont->v.section.children, _dup_cont_action, &dd); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "_dup_cont_action", oldcont->v.section.ident, rc); abort (); } break; case mu_cfg_cont_param: newcont->v.param = oldcont->v.param; break; } *pcont = newcont; return 0; } static void destroy_list (mu_list_t *plist) { mu_list_t list = *plist; mu_iterator_t itr = NULL; if (!list) return; mu_list_get_iterator (list, &itr); for (mu_iterator_first (itr); !mu_iterator_is_done (itr); mu_iterator_next (itr)) { struct mu_cfg_cont *cont, *p; mu_iterator_current (itr, (void**)&cont); p = cont; mu_config_destroy_container (&p); if (!p) mu_list_remove (list, cont); } mu_iterator_destroy (&itr); if (mu_list_is_empty (list)) mu_list_destroy (plist); } void mu_config_destroy_container (struct mu_cfg_cont **pcont) { struct mu_cfg_cont *cont = *pcont; unsigned refcount = mu_refcount_dec (cont->refcount); /* printf ("destr %p-%s: %d\n", cont, cont->v.section.ident, refcount); */ // FIXME: Shouldn't it be done inside the conditional below? switch (cont->type) { case mu_cfg_cont_section: destroy_list (&cont->v.section.children); break; case mu_cfg_cont_param: break; } if (refcount == 0) { mu_refcount_destroy (&cont->refcount); free (cont); *pcont = 0; } } int mu_cfg_section_add_container (struct mu_cfg_section *sect, struct mu_cfg_cont *cont) { if (!cont) return 0; if (!sect->children) mu_list_create (§->children); return mu_list_append (sect->children, cont); } int mu_cfg_section_add_params (struct mu_cfg_section *sect, struct mu_cfg_param *param) { if (!param) return 0; for (; param->ident; param++) { int rc; struct mu_cfg_cont *container; if (param->type == mu_cfg_section) { container = mu_get_canned_container (param->ident); if (!container) { mu_error (_("INTERNAL ERROR: Requested unknown canned " "section %s"), param->ident); abort (); } if (param->ident[0] == '.') { mu_iterator_t itr; mu_list_get_iterator (container->v.section.children, &itr); for (mu_iterator_first (itr); !mu_iterator_is_done (itr); mu_iterator_next (itr)) { struct mu_cfg_cont *c; mu_iterator_current (itr, (void**)&c); mu_config_clone_container (c); if (mu_refcount_value (c->refcount) > 1) dup_container (&c); switch (c->type) { case mu_cfg_cont_section: if (param->data) { c->v.section.data = param->data; c->v.section.offset = param->offset; } else if (c->v.section.data) /* Keep data & offset */; else c->v.section.offset += param->offset; break; case mu_cfg_cont_param: if (param->data) { container->v.param.data = param->data; container->v.param.offset = param->offset; } else if (container->v.param.data) /* Keep data & offset */; else container->v.param.offset += param->offset; break; } mu_cfg_section_add_container (sect, c); } mu_iterator_destroy (&itr); continue; } else { mu_config_clone_container (container); if (mu_refcount_value (container->refcount) > 1) dup_container (&container); container->v.section.data = param->data; container->v.section.offset = param->offset; } } else { rc = mu_config_create_container (&container, mu_cfg_cont_param); if (rc) return rc; container->v.param = *param; } mu_cfg_section_add_container (sect, container); } return 0; } static int _clone_action (void *item, void *cbdata) { struct mu_cfg_cont *cont = item; return mu_config_clone_container (cont); } int mu_config_clone_container (struct mu_cfg_cont *cont) { if (!cont) return 0; mu_refcount_inc (cont->refcount); /* printf("clone %p-%s: %d\n", cont, cont->v.section.ident, n); */ switch (cont->type) { case mu_cfg_cont_section: return mu_list_foreach (cont->v.section.children, _clone_action, NULL); case mu_cfg_cont_param: break; } return 0; } int mu_config_container_register_section (struct mu_cfg_cont **proot, const char *parent_path, const char *ident, const char *label, mu_cfg_section_fp parser, struct mu_cfg_param *param, struct mu_cfg_section **psection) { int rc; struct mu_cfg_section *root_section; struct mu_cfg_section *parent; if (!*proot) { rc = mu_config_create_container (proot, mu_cfg_cont_section); if (rc) return rc; memset (&(*proot)->v.section, 0, sizeof (*proot)->v.section); } root_section = &(*proot)->v.section; if (parent_path) { if (mu_cfg_find_section (root_section, parent_path, &parent)) return MU_ERR_NOENT; } else parent = root_section; if (mu_refcount_value ((*proot)->refcount) > 1) { /* It is a clone, do copy-on-write */ rc = dup_container (proot); if (rc) return rc; root_section = &(*proot)->v.section; if (parent_path) { if (mu_cfg_find_section (root_section, parent_path, &parent)) return MU_ERR_NOENT; } else parent = root_section; } if (ident) { struct mu_cfg_cont *container; struct mu_cfg_section *s; if (!parent->children) mu_list_create (&parent->children); mu_config_create_container (&container, mu_cfg_cont_section); mu_list_append (parent->children, container); s = &container->v.section; s->ident = strdup (ident); s->label = label ? strdup (label) : NULL; s->parser = parser; s->children = NULL; mu_cfg_section_add_params (s, param); if (psection) *psection = s; } else { mu_cfg_section_add_params (parent, param); /* FIXME: */ if (!parent->parser) parent->parser = parser; if (psection) *psection = parent; } return 0; } int mu_config_root_register_section (const char *parent_path, const char *ident, const char *label, mu_cfg_section_fp parser, struct mu_cfg_param *param) { return mu_config_container_register_section (&root_container, parent_path, ident, label, parser, param, NULL); } int mu_config_register_plain_section (const char *parent_path, const char *ident, struct mu_cfg_param *params) { return mu_config_root_register_section (parent_path, ident, NULL, NULL, params); } struct mu_cfg_cont * mu_config_clone_root_container (void) { struct mu_cfg_cont *cont = root_container; mu_config_clone_container (cont); return cont; } static struct mu_cfg_cont * mu_build_container (struct mu_cfg_param *param) { struct mu_cfg_cont *cont = mu_config_clone_root_container (); mu_config_container_register_section (&cont, NULL, NULL, NULL, NULL, param, NULL); return cont; } int mu_cfg_tree_reduce (mu_cfg_tree_t *parse_tree, struct mu_cfg_parse_hints *hints, struct mu_cfg_param *progparam, void *target_ptr) { int rc = 0; struct mu_cfg_cont *cont; if (!parse_tree) return 0; if (hints && (hints->flags & MU_CF_DUMP)) { int yes = 1; mu_stream_t stream; mu_stdio_stream_create (&stream, MU_STDERR_FD, 0); mu_stream_ioctl (stream, MU_IOCTL_FD, MU_IOCTL_FD_SET_BORROW, &yes); mu_cfg_format_parse_tree (stream, parse_tree, MU_CF_FMT_LOCUS); mu_stream_destroy (&stream); } cont = mu_build_container (progparam); rc = mu_cfg_scan_tree (parse_tree, &cont->v.section, target_ptr, NULL); mu_config_destroy_container (&cont); return rc; } void mu_format_config_tree (mu_stream_t stream, struct mu_cfg_param *progparam) { struct mu_cfg_cont *cont = mu_build_container (progparam); mu_cfg_format_container (stream, cont); mu_config_destroy_container (&cont); } static const char * _first_value_ptr (mu_config_value_t *val) { switch (val->type) { case MU_CFG_STRING: return val->v.string; case MU_CFG_ARRAY: return _first_value_ptr (val->v.arg.v); case MU_CFG_LIST: mu_list_get (val->v.list, 0, (void**) &val); return _first_value_ptr (val); } return ""; } int mu_cfg_assert_value_type (mu_config_value_t *val, int type) { if (!val) { mu_error (_("required argument missing")); return 1; } if (type == MU_CFG_ARRAY) { if (val->type == MU_CFG_STRING) { mu_config_value_t *arr = mu_calloc (1, sizeof arr[0]); arr[0] = *val; val->v.arg.c = 1; val->v.arg.v = arr; val->type = MU_CFG_ARRAY; } } if (val->type != type) { /* FIXME */ mu_error (_("unexpected value: %s"), _first_value_ptr (val)); return 1; } return 0; } int mu_cfg_string_value_cb (mu_config_value_t *val, int (*fun) (const char *, void *), void *data) { int rc = 0; switch (val->type) { case MU_CFG_STRING: return fun (val->v.string, data); break; case MU_CFG_ARRAY: { int i; for (i = 0; i < val->v.arg.c; i++) { if (mu_cfg_assert_value_type (&val->v.arg.v[i], MU_CFG_STRING)) return 1; fun (val->v.arg.v[i].v.string, data); } } break; case MU_CFG_LIST: { mu_iterator_t itr; mu_list_get_iterator (val->v.list, &itr); for (mu_iterator_first (itr); !mu_iterator_is_done (itr); mu_iterator_next (itr)) { mu_config_value_t *pval; mu_iterator_current (itr, (void*) &pval); if (mu_cfg_assert_value_type (pval, MU_CFG_STRING)) { rc = 1; break; } fun (pval->v.string, data); } mu_iterator_destroy (&itr); } } return rc; } struct mapping_closure { mu_assoc_t assoc; char *err_term; }; static int parse_mapping_str (void *item, void *data) { struct mapping_closure *clos = data; char const *str = item; size_t len; char *key, *val; int rc; len = strcspn (str, "="); if (str[len] == 0) { clos->err_term = mu_strdup (str); return MU_ERR_PARSE; } key = mu_alloc (len + 1); memcpy (key, str, len); key[len] = 0; val = mu_strdup (str + len + 1); if (!val) return ENOMEM; rc = mu_assoc_install (clos->assoc, key, val); free (key); return rc; } static int parse_mapping_val (void *item, void *data) { struct mu_config_value *cval = item; if (mu_cfg_assert_value_type (cval, MU_CFG_STRING)) return MU_ERR_PARSE; return parse_mapping_str ((void*) cval->v.string, data); } int mu_cfg_field_map (struct mu_config_value const *val, mu_assoc_t *passoc, char **err_term) { int rc; struct mapping_closure clos; mu_list_t list = NULL; rc = mu_assoc_create (&clos.assoc, 0); if (rc) return rc; mu_assoc_set_destroy_item (clos.assoc, mu_list_free_item); clos.err_term = NULL; switch (val->type) { case MU_CFG_STRING: mu_list_create (&list); mu_list_set_destroy_item (list, mu_list_free_item); rc = mu_string_split (val->v.string, ":", list); if (rc == 0) rc = mu_list_foreach (list, parse_mapping_str, &clos); mu_list_destroy (&list); break; case MU_CFG_LIST: rc = mu_list_foreach (val->v.list, parse_mapping_val, &clos); break; case MU_CFG_ARRAY: rc = EINVAL; } if (rc) { if (rc == MU_ERR_PARSE) { if (err_term) *err_term = clos.err_term; else free (clos.err_term); } else mu_error ("%s:%d: %s", __FILE__, __LINE__, mu_strerror (rc)); mu_assoc_destroy (&clos.assoc); } else *passoc = clos.assoc; return rc; }