/* 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);
}