/* This file is part of Mailfromd. Copyright (C) 2006-2020 Sergey Poznyakoff This program 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. This program 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 this program. If not, see . */ #ifdef HAVE_CONFIG_H # include #endif #include #include "mailfromd.h" #define BUCKETSIZE 16 struct symbucket { struct symbucket *next; size_t elsize; size_t index; char *buf; }; struct _syment_align { char c; struct syment x; }; #define __SYMENT_ALIGNMENT (offsetof(struct _syment_align, x)) #define __SYMENT_ALIGN(a, b) (((a) + (b) - 1) & ~((b) - 1)) #define SYMENT_SIZE(a) \ __SYMENT_ALIGN((a)->elsize,__SYMENT_ALIGNMENT) #define __SYMENT(a,p,n) \ ((struct syment*) ((char*) (p) + SYMENT_SIZE(a) * (n))) #define SYMENT(a,n) \ __SYMENT(a,(a)->buf,n) static struct symbucket *symbucket; /* Bucket handling */ static struct symbucket * alloc_bucket(size_t elsize) { size_t x; size_t size; struct symbucket *p; x = __SYMENT_ALIGN(elsize,__SYMENT_ALIGNMENT); size = ((sizeof(*p) + x - 1) / x) * x; p = mu_alloc(size + x * BUCKETSIZE); p->index = 0; p->elsize = elsize; p->buf = (char*)p + x; p->next = symbucket; return symbucket = p; } static struct symbucket * find_bucket(size_t elsize) { struct symbucket *sp; for (sp = symbucket; sp; sp = sp->next) if (sp->elsize == elsize && sp->index < BUCKETSIZE) return sp; return alloc_bucket(elsize); } static void * alloc_entry(size_t size) { struct symbucket *bp = find_bucket(size); size_t n = bp->index++; struct syment *sym = SYMENT(bp, n); memset(sym, 0, size); sym->refcnt = 1; return sym; } static void free_entry(void *ptr) { /* nothing */ } static void free_buckets() { struct symbucket *p = symbucket; while (p) { struct symbucket *next = p->next; free(p); p = next; } symbucket = NULL; } /* Import rules */ static int make_transform_rule(struct import_rule *rule, const char *expr) { transform_t xform = transform_compile(expr, regex_flags); if (!xform) { parse_error(_("invalid transform string: %s"), transform_error_string()); return 1; } rule->xform = xform; rule->type = import_transform; return 0; } static int make_regex_rule(struct import_rule *rule) { char *start; char *end; int rc; size_t len; char *expr; int neg = 0; start = rule->literal->text; if (start[0] == '!') { neg = 1; start++; } else neg = 0; end = strchr(start + 1, start[0]); start++; if (!end) return 1; len = end - start; expr = mu_alloc(len + 1); memcpy(expr, start, len); expr[len] = 0; rc = regcomp(&rule->re, expr, regex_flags); if (rc) { char errbuf[512]; regerror(rc, &rule->re, errbuf, sizeof(errbuf)); parse_error(_("cannot compile regex (%s): %s"), expr, errbuf); free(expr); return 1; } free(expr); rule->neg = neg; if (end[1] && make_transform_rule (rule, end + 1) == 0) return 0; rule->type = import_regex; return 0; } struct import_rule * import_rule_create(struct literal *lit) { struct import_rule *rule = mu_alloc(sizeof(*rule)); rule->literal = lit; rule->next = NULL; if (mu_ispunct(lit->text[0]) && make_regex_rule(rule) == 0) return rule; rule->type = import_literal; return rule; } void import_rules_free(struct import_rule *rules) { while (rules) { struct import_rule *next = rules->next; switch (rules->type) { case import_literal: break; case import_transform: transform_free(rules->xform); /* fall through */ case import_regex: regfree(&rules->re); } rules = next; } } int _append_pool(void *ptr, const char *s, size_t len) { mu_opool_t op = ptr; mu_opool_append(op, s, len); return 0; } char * _reduce_pool(void *ptr) { mu_opool_t op = ptr; return mu_opool_finish(op, NULL); } int import_rules_eval(struct import_rule *rule, const char *name, char **newname) { int res; regmatch_t *matches = NULL; size_t matchsize = 0; size_t matchcount; if (!rule) return 1; res = 0; *newname = NULL; for (; res == 0 && rule; rule = rule->next) { switch (rule->type) { case import_literal: res = strcmp(rule->literal->text, name) == 0; break; case import_regex: case import_transform: /* FIXME */ matchcount = rule->re.re_nsub + 1; if (matchsize < matchcount) matches = mu_realloc(matches, sizeof(matches[0]) * matchcount); res = regexec(&rule->re, name, matchcount, matches, 0) == 0; if (rule->neg) res = !res; if (res && rule->type == import_transform) { char *str; mu_opool_t op; mu_opool_create(&op, MU_OPOOL_ENOMEMABRT); str = transform_string(rule->xform, name, op, _append_pool, _reduce_pool); mu_opool_destroy(&op); *newname = str; } break; } } free(matches); return res; } struct symtab *stab_module; struct symtab *stab_builtin; struct symtab *stab_pragma; struct symtab *stab_literal; struct variable *builtin_variable_list; struct symtab * xsymtab_create(size_t elsize, int flags, void *(*alloc_fun)(size_t), void (*free_fun)(void *)) { struct symtab *s = symtab_create(elsize, flags, alloc_fun, free_fun); if (!s) mu_alloc_die(); return s; } struct syment * xsymtab_lookup(struct symtab *st, const char *name) { struct syment *ep; int rc = symtab_lookup_or_install(&ep, st, name, NULL); switch (rc) { case ENOMEM: case E2BIG: mu_error("%s", symtab_strerror(rc)); abort(); } return rc == 0 ? ep : NULL; } struct builtin * xsymtab_install_builtin(const char *name) { struct syment *ep; int install = 1; int rc = symtab_lookup_or_install(&ep, stab_builtin, name, &install); if (rc) { parse_error(_("cannot install builtin function %s: %s"), name, symtab_strerror(rc)); abort(); } return (struct builtin*) ep; } void import_builtin_variables(struct symtab *st) { struct variable *var; for (var = builtin_variable_list; var; var = var->next) { int install; struct syment *ent; int rc = symtab_lookup_or_install_entry(&ent, st, (struct syment *) var, &install); if (rc == 0 && !install) { parse_error("INTERNAL ERROR at %s:%d: " "cannot import builtin variable %s: %s", __FILE__, __LINE__, ((struct syment *) var)->name, "variable already imported"); abort(); } if (rc) { parse_error("INTERNAL ERROR at %s:%d: " "cannot import builtin variable %s: %s", __FILE__, __LINE__, ((struct syment *) var)->name, symtab_strerror(rc)); abort(); } } } struct symimport_closure { struct module *module; struct mu_locus_range locus; struct import_rule *rules; char *newname; }; struct mf_symbol * symbol_resolve_alias(struct mf_symbol *sp) { if (sp) { while (sp->alias) sp = (struct mf_symbol *) sp->alias; } return sp; } int import_selfun(const struct syment *ent, void *closure) { struct symimport_closure *sip = closure; struct mf_symbol *sym = (struct mf_symbol*) ent; return sym->module == sip->module && (sym->module->flags == 0 || (sym->module->flags & SYM_PUBLIC) || ((sym->module->flags & SYM_STATIC) && (sym->flags & SYM_PUBLIC))) && !(sym->flags & SYM_STATIC) && import_rules_eval(sip->rules, ent->name, &sip->newname); } int import_errfun(int rc, struct symtab *tab, const char *name, void *closure) { struct symimport_closure *sip = closure; parse_error_locus(&sip->locus, _("cannot import symbol `%s': %s"), name, symtab_strerror(rc)); return rc; } int import_confun(struct symtab *tab, struct syment *dstent, struct syment *srcent, void *closure) { struct symimport_closure *sip = closure; struct mf_symbol *src = (struct mf_symbol*)srcent; struct mf_symbol *dst = (struct mf_symbol*)dstent; if (mu_locus_point_same_file (&src->locus.beg, &dst->locus.beg)) return 0; parse_error_locus(&sip->locus, _("symbol import conflict: %s"), srcent->name); parse_error_locus(&src->locus, _("imported symbol location")); parse_error_locus(&dst->locus, _("conflicting symbol location")); return EBUSY; } int import_cpyfun(struct syment **pdst, struct syment *src, size_t symsize, void *closure) { struct symimport_closure *sip = closure; struct syment *dst; if (sip->newname) { struct mf_symbol *mdst = alloc_entry(sizeof(*mdst)); struct mf_symbol *s = symbol_resolve_alias((struct mf_symbol*)src); mdst->name = mf_strdup(sip->newname); mdst->refcnt = 1; mdst->alias = (struct mf_symbol*)src; mu_locus_range_copy(&mdst->locus, &s->locus); dst = (struct syment*)mdst; free(sip->newname); sip->newname = NULL; } else { src->refcnt++; dst = src; } *pdst = dst; return 0; } int module_import_symbols(struct module *dst, struct module *src, struct mu_locus_range const *loc) { int i; struct symimport_closure clos; clos.module = src; clos.locus = *loc; clos.rules = dst->import_rules; clos.newname = NULL; int res = 0; for (i = 0; i < MODULE_NAMESPACE_COUNT; i++) { if (symtab_import(dst->symtab[i], src->symtab[i], import_selfun, import_errfun, import_confun, import_cpyfun, &clos)) { res = 1; break; } } import_rules_free(dst->import_rules); dst->import_rules = NULL; return res; } struct module *top_module; struct module_list *module_stack; struct module_list *module_head, *module_tail; static void module_register(struct module *mod) { struct module_list *p = alloc_entry(sizeof(*p)); p->module = mod; p->next = NULL; if (module_tail) module_tail->next = p; else module_head = p; module_tail = p; } int module_symtab_enumerate(enum module_namespace ns, symtab_enumerator_t fun, void *data) { struct module_list *p; for (p = module_head; p; p = p->next) { int rc = symtab_enumerate(p->module->symtab[ns], fun, data); if (rc) return rc; } return 0; } static void module_init(struct module *mod, const char *name, const char *file) { mod->name = mf_strdup(name); mod->file = file ? mf_strdup(file) : NULL; mod->dclname = NULL; mod->flags = 0; mod->symtab[namespace_function] = xsymtab_create(sizeof(struct function), SYMTAB_COPY_KEY, alloc_entry, free_entry); mod->symtab[namespace_variable] = xsymtab_create(sizeof(struct variable), SYMTAB_COPY_KEY, alloc_entry, free_entry); mod->symtab[namespace_constant] = xsymtab_create(sizeof(struct constant), SYMTAB_COPY_KEY, alloc_entry, free_entry); mod->import_rules = NULL; mod->submodule_head = mod->submodule_tail = NULL; mod->incl_sources = NULL; module_register(mod); } static struct module * module_create(const char *name, const char *file) { struct module *mod = alloc_entry(sizeof(*mod)); module_init(mod, name, file); return mod; } static void module_free(struct module *mod) { int i; if (!mod) return; for (i = 0; i < MODULE_NAMESPACE_COUNT; i++) symtab_destroy(&mod->symtab[i]); mu_list_destroy(&mod->incl_sources); } static void module_add_submodule(struct module *mod, struct module *submod) { struct module_list *p = alloc_entry(sizeof(*p)); p->module = submod; p->next = NULL; if (mod->submodule_tail) mod->submodule_tail->next = p; else mod->submodule_head = p; mod->submodule_tail = p; } static int module_has_submodule(struct module *mod, struct module *submod) { struct module_list *p; for (p = mod->submodule_head; p; p = p->next) if (p->module == submod) return 1; return 0; } static void module_push(struct module *mod) { struct module_list *p = alloc_entry(sizeof(*p)); p->module = mod; p->next = module_stack; module_stack = p; } static struct module * module_pop() { struct module_list *p = module_stack; struct module *mod; if (p) { mod = p->module; module_stack = p->next; } else mod = NULL; return mod; } int set_top_module(const char *name, const char *file, struct import_rule *import_rules, struct mu_locus_range const *locus) { struct module *mod; struct syment *ent; int install = 1; int rc = symtab_lookup_or_install(&ent, stab_module, name, &install); if (rc) { parse_error_locus(locus, _("cannot install module %s: %s"), name, symtab_strerror(rc)); abort(); } mod = (struct module *) ent; if (!install) { if (!module_has_submodule(top_module, mod)) /* Import symbols from mod to top_module */ module_import_symbols(top_module, mod, locus); return 1; } module_init(mod, name, file); import_builtin_variables(mod->symtab[namespace_variable]); top_module->import_rules = import_rules; module_add_submodule(top_module, mod); module_push(top_module); top_module = mod; return 0; } void pop_top_module() { if (module_stack) { struct module *mod = top_module; top_module = module_pop(); /* Import symbols from mod to top_module */ if (mod != top_module) { struct mu_locus_range lr = MU_LOCUS_RANGE_INITIALIZER; module_import_symbols(top_module, mod, &lr); /*FIXME: locus?*/ } } } static int _collect_modules(void *ep, void *data) { struct module ***pmod = data; *(*pmod)++ = ep; return 0; } static int _module_comp(const void *a, const void *b) { const struct module *am = a; const struct module *bm = b; return strcmp(am->name, bm->name); } void collect_modules(struct module ***pmodv, size_t *pmodc) { size_t modc = symtab_count_entries(stab_module); struct module **modv = mu_calloc(modc + 1, sizeof(*modv)); struct module **tmp = modv + 1; modv[0] = top_module; symtab_enumerate(stab_module, _collect_modules, &tmp); qsort(modv + 1, modc, sizeof(modv[0]), _module_comp); modc++; *pmodv = modv; *pmodc = modc; } static void free_module_entry(void *ptr) { module_free((struct module *)ptr); } void init_symbols() { stab_module = xsymtab_create(sizeof(struct module), SYMTAB_COPY_KEY, alloc_entry, free_module_entry); stab_builtin = xsymtab_create(sizeof(struct builtin), SYMTAB_COPY_KEY, alloc_entry, free_entry); stab_pragma = xsymtab_create(sizeof(struct pragma), SYMTAB_COPY_KEY, alloc_entry, free_entry); stab_literal = xsymtab_create(sizeof(struct literal), SYMTAB_COPY_KEY, alloc_entry, free_entry); top_module = module_create("top", script_file); } void free_symbols() { symtab_destroy(&stab_module); symtab_destroy(&stab_builtin); symtab_destroy(&stab_pragma); symtab_destroy(&stab_literal); module_free(top_module); top_module = NULL; free_buckets(); } void va_builtin_install(char *name, void (*handler) (eval_environ_t), data_type_t rettype, size_t argcount, ...) { struct builtin *builtin; va_list ap; size_t i; data_type_t *argtype = mu_alloc (argcount * sizeof *argtype); va_start(ap, argcount); for (i = 0; i < argcount; i++) argtype[i] = va_arg(ap, data_type_t); va_end(ap); builtin = xsymtab_install_builtin(name); /* new entry */ builtin->name = mf_strdup(name); builtin->handler = handler; builtin->parmcount = argcount; builtin->parmtype = argtype; builtin->rettype = rettype; builtin->statemask = 0; builtin->flags = 0; } void va_builtin_install_ex (char *name, void (*handler) (eval_environ_t), unsigned statemsk, data_type_t rettype, size_t argcount, size_t optcount, int flags, ...) { struct builtin *builtin; va_list ap; size_t i; data_type_t *argtype = mu_alloc (argcount * sizeof *argtype); va_start(ap, flags); for (i = 0; i < argcount; i++) argtype[i] = va_arg(ap, data_type_t); va_end(ap); builtin = xsymtab_install_builtin(name); /* new entry */ builtin->name = mf_strdup(name); builtin->handler = handler; builtin->parmcount = argcount; builtin->optcount = optcount; builtin->parmtype = argtype; builtin->rettype = rettype; builtin->statemask = statemsk; builtin->flags = flags; } const struct builtin * builtin_lookup(const char *name) { return (struct builtin *)xsymtab_lookup(stab_builtin, name); } static void init_variable(struct variable *var) { mu_locus_range_init (&var->sym.locus); var->sym.module = top_module; var->sym.flags = 0; var->type = dtype_unspecified; var->storage_class = storage_extern; var->off = 0; var->addrptr = NULL; var->shadowed = NULL; var->xref = NULL; var->next = NULL; } struct variable * variable_install(const char *name) { struct variable *vp; struct syment *ent; int install = 1; int rc = symtab_lookup_or_install(&ent, TOP_MODULE_SYMTAB(namespace_variable), name, &install); if (rc) { parse_error(_("cannot install variable %s: %s"), name, symtab_strerror(rc)); abort(); } vp = (struct variable *) ent; if (install) { init_variable(vp); vp->sym.name = mf_strdup(name); } return vp; } struct variable * variable_replace(const char *name, struct variable *var) { if (!var) { var = alloc_entry(sizeof(*var)); init_variable(var); var->sym.name = mf_strdup(name); } if (symtab_replace(TOP_MODULE_SYMTAB(namespace_variable), (struct syment*)var, NULL)) { parse_error(_("INTERNAL ERROR: variable %s not found"), name); abort(); } return var; } struct variable * variable_lookup(const char *name) { struct variable *var = (struct variable *) xsymtab_lookup(TOP_MODULE_SYMTAB(namespace_variable), name); return (struct variable *)symbol_resolve_alias((struct mf_symbol*)var); } struct variable * builtin_variable_install(const char *name, data_type_t type, unsigned flags, size_t *addrptr) { struct variable *var = variable_install(name); var->sym.flags = flags; var->sym.module = NULL; var->type = type; var->addrptr = addrptr; var->next = builtin_variable_list; builtin_variable_list = var; return var; } static struct function * function_lookup_or_install(const char *name, struct mu_locus_range const *locus) { struct function *fp; struct syment *ent; int install = 1; int rc = symtab_lookup_or_install(&ent, TOP_MODULE_SYMTAB(namespace_function), name, &install); if (rc) { parse_error(_("cannot install function %s: %s"), name, symtab_strerror(rc)); abort(); } fp = (struct function*) ent; if (!install) { if (fp->sym.alias) { fp = (struct function *) symbol_resolve_alias((struct mf_symbol*)fp); parse_error_locus(locus, _("redefinition of function %s by alias %s"), fp->sym.name, name); parse_error_locus(&fp->sym.locus, _("previously defined here")); } else { parse_error_locus(locus, _("redefinition of function %s"), name); parse_error_locus(&fp->sym.locus, _("previously defined here")); } } return fp; } struct function * function_install(const char *name, size_t parmcnt, size_t optcnt, int varargs, data_type_t *parmtypes, data_type_t rettype, struct mu_locus_range const *locus) { struct function *fp = function_lookup_or_install(name, locus); fp->sym.name = mf_strdup(name); fp->sym.module = top_module; mu_locus_range_copy(&fp->sym.locus, locus); fp->node = NULL; fp->entry = 0; fp->parmcount = parmcnt; fp->optcount = optcnt; fp->varargs = varargs; fp->parmtype = parmtypes; fp->rettype = rettype; fp->exmask = exmask_create(); fp->statemask = 0; return fp; } struct function * function_lookup(const char *name) { struct function *fp = (struct function *) xsymtab_lookup(TOP_MODULE_SYMTAB(namespace_function), name); return (struct function*) symbol_resolve_alias((struct mf_symbol*)fp); } void install_alias(const char *name, struct function *fun, struct mu_locus_range const *locus) { struct function *fp = function_lookup_or_install(name, locus); fp->sym.name = mf_strdup(name); fp->sym.module = top_module; mu_locus_range_copy(&fp->sym.locus, locus); fp->sym.alias = (struct mf_symbol*) fun; } struct literal * literal_lookup(const char *text) { struct literal *lit; struct syment *ent; int install = 1; int rc = symtab_lookup_or_install(&ent, stab_literal, text, &install); if (rc) { parse_error(_("cannot install literal %s: %s"), text, symtab_strerror(rc)); abort(); } lit = (struct literal *)ent; if (install) { lit->flags = 0; lit->regex = NULL; } return lit; } struct constant * define_constant(const char *name, struct value *value, unsigned flags, struct mu_locus_range const *locus) { struct constant *cp; struct syment *ent; int install = 1; int rc = symtab_lookup_or_install(&ent, TOP_MODULE_SYMTAB(namespace_constant), name, &install); if (rc) { parse_error(_("cannot install constant %s: %s"), name, symtab_strerror(rc)); abort(); } cp = (struct constant *) ent; if (install) { cp->sym.name = mf_strdup(name); } else { parse_warning_locus(locus, "%s: redefinition of constant", name); parse_warning_locus(&cp->sym.locus, "this is the location of the " "previous definition"); } cp->sym.module = top_module; mu_locus_range_copy(&cp->sym.locus, locus); cp->sym.flags = flags; cp->value = *value; return cp; } const struct constant * constant_lookup(const char *name) { const struct constant *cp = (const struct constant *) xsymtab_lookup(TOP_MODULE_SYMTAB(namespace_constant), name); return (const struct constant *) symbol_resolve_alias((struct mf_symbol*)cp); } const struct value * constant_lookup_value(const char *name) { const struct constant *cptr = constant_lookup(name); return cptr ? &cptr->value : NULL; } struct sym_regex * install_regex(struct literal *lit, unsigned regflags) { struct sym_regex *regex; for (regex = lit->regex; regex; regex = regex->next) if (regex->regflags == regflags) return regex; /* FIXME: don't use alloc_entry here ... or change sym_regex to match struct syment structure */ regex = alloc_entry(sizeof(*regex)); regex->lit = lit; regex->regflags = regflags; regex->flags = 0; regex->index = 0; regex->next = lit->regex; lit->regex = regex; return regex; } void install_pragma(const char *name, int minargs, int maxargs, void (*handler) (int, char **, const char *)) { struct pragma *pragma; struct syment *ent; int install = 1; int rc = symtab_lookup_or_install(&ent, stab_pragma, name, &install); if (rc) { parse_error(_("cannot install pragma %s: %s"), name, symtab_strerror(rc)); abort(); } if (!install) { parse_error(_("INTERNAL ERROR: pragma %s already defined"), name); abort(); } pragma = (struct pragma *)ent; pragma->name = mf_strdup(name); pragma->minargs = minargs; pragma->maxargs = maxargs; pragma->handler = handler; } const struct pragma * lookup_pragma(const char *name) { return (const struct pragma *)xsymtab_lookup(stab_pragma, name); }