/* * module.c - deal with dynamic modules * * This file is part of zsh, the Z shell. * * Copyright (c) 1996-1997 Zoltán Hidvégi * All rights reserved. * * Permission is hereby granted, without written agreement and without * license or royalty fees, to use, copy, modify, and distribute this * software and to distribute modified versions of this software for any * purpose, provided that the above copyright notice and the following * two paragraphs appear in all copies of this software. * * In no event shall Zoltán Hidvégi or the Zsh Development Group be liable * to any party for direct, indirect, special, incidental, or consequential * damages arising out of the use of this software and its documentation, * even if Zoltán Hidvégi and the Zsh Development Group have been advised of * the possibility of such damage. * * Zoltán Hidvégi and the Zsh Development Group specifically disclaim any * warranties, including, but not limited to, the implied warranties of * merchantability and fitness for a particular purpose. The software * provided hereunder is on an "as is" basis, and Zoltán Hidvégi and the * Zsh Development Group have no obligation to provide maintenance, * support, updates, enhancements, or modifications. */ #include "zsh.mdh" #include "module.pro" /* * List of linked-in modules. * This is set up at boot and remains for the life of the shell; * entries do not appear in "zmodload" listings. */ /**/ LinkList linkedmodules; /* $module_path ($MODULE_PATH) */ /**/ char **module_path; /* Hash of modules */ /**/ mod_export HashTable modulestab; /* * Bit flags passed as the "flags" argument of a autofeaturefn_t. * Used in other places, such as the final argument to * do_module_features(). */ enum { /* * `-i' option: ignore errors pertaining to redefinitions, * or indicate to do_module_features() that it should be * silent. */ FEAT_IGNORE = 0x0001, /* If a condition, condition is infix rather than prefix */ FEAT_INFIX = 0x0002, /* * Enable all features in the module when autoloading. * This is the traditional zmodload -a behaviour; * zmodload -Fa only enables features explicitly marked for * autoloading. */ FEAT_AUTOALL = 0x0004, /* * Remove feature: alternative to "-X:NAME" used if * X is passed separately from NAME. */ FEAT_REMOVE = 0x0008, /* * For do_module_features(). Check that any autoloads * for the module are actually provided. */ FEAT_CHECKAUTO = 0x0010 }; /* * All functions to add or remove autoloadable features fit * the following prototype. * * "module" is the name of the module. * * "feature" is the name of the feature, minus any type prefix. * * "flags" is a set of the bits above. * * The return value is 0 for success, -1 for failure with no * message needed, and one of the following to indicate the calling * function should print a message: * * 1: failed to add [type] `[feature]' * 2: [feature]: no such [type] * 3: [feature]: [type] is already defined */ typedef int (*autofeaturefn_t)(const char *module, const char *feature, int flags); /* Bits in the second argument to find_module. */ enum { /* * Resolve any aliases to the underlying module. */ FINDMOD_ALIASP = 0x0001, /* * Create an element for the module in the list if * it is not found. */ FINDMOD_CREATE = 0x0002, }; static void freemodulenode(HashNode hn) { Module m = (Module) hn; if (m->node.flags & MOD_ALIAS) zsfree(m->u.alias); zsfree(m->node.nam); if (m->autoloads) freelinklist(m->autoloads, freestr); if (m->deps) freelinklist(m->deps, freestr); zfree(m, sizeof(*m)); } /* flags argument to printmodulenode */ enum { /* -L flag, output zmodload commands */ PRINTMOD_LIST = 0x0001, /* -e flag */ PRINTMOD_EXIST = 0x0002, /* -A flag */ PRINTMOD_ALIAS = 0x0004, /* -d flag */ PRINTMOD_DEPS = 0x0008, /* -F flag */ PRINTMOD_FEATURES = 0x0010, /* -l flag in combination with -L flag */ PRINTMOD_LISTALL = 0x0020, /* -a flag */ PRINTMOD_AUTO = 0x0040 }; /* Scan function for printing module details */ static void printmodulenode(HashNode hn, int flags) { Module m = (Module)hn; /* * If we check for a module loaded under an alias, we * need the name of the alias. We can use it in other * cases, too. */ const char *modname = m->node.nam; if (flags & PRINTMOD_DEPS) { /* * Print the module's dependencies. */ LinkNode n; if (!m->deps) return; if (flags & PRINTMOD_LIST) { printf("zmodload -d "); if (modname[0] == '-') fputs("-- ", stdout); quotedzputs(modname, stdout); } else { nicezputs(modname, stdout); putchar(':'); } for (n = firstnode(m->deps); n; incnode(n)) { putchar(' '); if (flags & PRINTMOD_LIST) quotedzputs((char *) getdata(n), stdout); else nicezputs((char *) getdata(n), stdout); } } else if (flags & PRINTMOD_EXIST) { /* * Just print the module name, provided the module is * present under an alias or otherwise. */ if (m->node.flags & MOD_ALIAS) { if (!(flags & PRINTMOD_ALIAS) || !(m = find_module(m->u.alias, FINDMOD_ALIASP, NULL))) return; } if (!m->u.handle || (m->node.flags & MOD_UNLOAD)) return; nicezputs(modname, stdout); } else if (m->node.flags & MOD_ALIAS) { /* * Normal listing, but for aliases. */ if (flags & PRINTMOD_LIST) { printf("zmodload -A "); if (modname[0] == '-') fputs("-- ", stdout); quotedzputs(modname, stdout); putchar('='); quotedzputs(m->u.alias, stdout); } else { nicezputs(modname, stdout); fputs(" -> ", stdout); nicezputs(m->u.alias, stdout); } } else if (m->u.handle || (flags & PRINTMOD_AUTO)) { /* * Loaded module. */ if (flags & PRINTMOD_LIST) { /* * List with -L format. Possibly we are printing * features, either enables or autoloads. */ char **features = NULL; int *enables = NULL; if (flags & PRINTMOD_AUTO) { if (!m->autoloads || !firstnode(m->autoloads)) return; } else if (flags & PRINTMOD_FEATURES) { if (features_module(m, &features) || enables_module(m, &enables) || !*features) return; } printf("zmodload "); if (flags & PRINTMOD_AUTO) { fputs("-Fa ", stdout); } else if (features) fputs("-F ", stdout); if(modname[0] == '-') fputs("-- ", stdout); quotedzputs(modname, stdout); if (flags & PRINTMOD_AUTO) { LinkNode an; for (an = firstnode(m->autoloads); an; incnode(an)) { putchar(' '); quotedzputs((char *)getdata(an), stdout); } } else if (features) { const char *f; while ((f = *features++)) { int on = *enables++; if (flags & PRINTMOD_LISTALL) printf(" %s", on ? "+" : "-"); else if (!on) continue; else putchar(' '); quotedzputs(f, stdout); } } } else /* -l */ nicezputs(modname, stdout); } else return; putchar('\n'); } /**/ HashTable newmoduletable(int size, char const *name) { HashTable ht; ht = newhashtable(size, name, NULL); ht->hash = hasher; ht->emptytable = emptyhashtable; ht->filltable = NULL; ht->cmpnodes = strcmp; ht->addnode = addhashnode; /* DISABLED is not supported */ ht->getnode = gethashnode2; ht->getnode2 = gethashnode2; ht->removenode = removehashnode; ht->disablenode = NULL; ht->enablenode = NULL; ht->freenode = freemodulenode; ht->printnode = printmodulenode; return ht; } /************************************************************************ * zsh/main standard module functions ************************************************************************/ /* The `zsh/main' module contains all the base code that can't actually be * * built as a separate module. It is initialised by main(), so there's * * nothing for the boot function to do. */ /**/ int setup_(UNUSED(Module m)) { return 0; } /**/ int features_(UNUSED(Module m), UNUSED(char ***features)) { /* * There are lots and lots of features, but they're not * handled here. */ return 1; } /**/ int enables_(UNUSED(Module m), UNUSED(int **enables)) { return 1; } /**/ int boot_(UNUSED(Module m)) { return 0; } /**/ int cleanup_(UNUSED(Module m)) { return 0; } /**/ int finish_(UNUSED(Module m)) { return 0; } /************************************************************************ * Module utility functions ************************************************************************/ /* This registers a builtin module. */ /**/ void register_module(char *n, Module_void_func setup, Module_features_func features, Module_enables_func enables, Module_void_func boot, Module_void_func cleanup, Module_void_func finish) { Linkedmod m; m = (Linkedmod) zalloc(sizeof(*m)); m->name = ztrdup(n); m->setup = setup; m->features = features; m->enables = enables; m->boot = boot; m->cleanup = cleanup; m->finish = finish; zaddlinknode(linkedmodules, m); } /* Check if a module is linked in. */ /**/ Linkedmod module_linked(char const *name) { LinkNode node; for (node = firstnode(linkedmodules); node; incnode(node)) if (!strcmp(((Linkedmod) getdata(node))->name, name)) return (Linkedmod) getdata(node); return NULL; } /************************************************************************ * Support for the various feature types. * First, builtins. ************************************************************************/ /* addbuiltin() can be used to add a new builtin. It returns zero on * * success, 1 on failure. The only possible type of failure is that * * a builtin with the specified name already exists. An autoloaded * * builtin can be replaced using this function. */ /**/ static int addbuiltin(Builtin b) { Builtin bn = (Builtin) builtintab->getnode2(builtintab, b->node.nam); if (bn && (bn->node.flags & BINF_ADDED)) return 1; if (bn) builtintab->freenode(builtintab->removenode(builtintab, b->node.nam)); builtintab->addnode(builtintab, b->node.nam, b); return 0; } /* Define an autoloadable builtin. It returns 0 on success, or 1 on * * failure. The only possible cause of failure is that a builtin * * with the specified name already exists. */ /**/ static int add_autobin(const char *module, const char *bnam, int flags) { Builtin bn; int ret; bn = zshcalloc(sizeof(*bn)); bn->node.nam = ztrdup(bnam); bn->optstr = ztrdup(module); if (flags & FEAT_AUTOALL) bn->node.flags |= BINF_AUTOALL; if ((ret = addbuiltin(bn))) { builtintab->freenode(&bn->node); if (!(flags & FEAT_IGNORE)) return 1; } return 0; } /* Remove the builtin added previously by addbuiltin(). Returns * * zero on success and -1 if there is no builtin with that name. */ /**/ int deletebuiltin(const char *nam) { Builtin bn; bn = (Builtin) builtintab->removenode(builtintab, nam); if (!bn) return -1; builtintab->freenode(&bn->node); return 0; } /* Remove an autoloaded added by add_autobin */ /**/ static int del_autobin(UNUSED(const char *module), const char *bnam, int flags) { Builtin bn = (Builtin) builtintab->getnode2(builtintab, bnam); if (!bn) { if(!(flags & FEAT_IGNORE)) return 2; } else if (bn->node.flags & BINF_ADDED) { if (!(flags & FEAT_IGNORE)) return 3; } else deletebuiltin(bnam); return 0; } /* * Manipulate a set of builtins. This should be called * via setfeatureenables() (or, usually, via the next level up, * handlefeatures()). * * "nam" is the name of the calling code builtin, probably "zmodload". * * "binl" is the builtin table containing an array of "size" builtins. * * "e" is either NULL, in which case all builtins in the * table are removed, or else an array corresponding to "binl" * with a 1 for builtins that are to be added and a 0 for builtins * that are to be removed. Any builtin already in the appropriate * state is left alone. * * Returns 1 on any error, 0 for success. The recommended way * of handling errors is to compare the enables passed down * with the set retrieved after the error to find what failed. */ /**/ static int setbuiltins(char const *nam, Builtin binl, int size, int *e) { int ret = 0, n; for(n = 0; n < size; n++) { Builtin b = &binl[n]; if (e && *e++) { if (b->node.flags & BINF_ADDED) continue; if (addbuiltin(b)) { zwarnnam(nam, "name clash when adding builtin `%s'", b->node.nam); ret = 1; } else { b->node.flags |= BINF_ADDED; } } else { if (!(b->node.flags & BINF_ADDED)) continue; if (deletebuiltin(b->node.nam)) { zwarnnam(nam, "builtin `%s' already deleted", b->node.nam); ret = 1; } else { b->node.flags &= ~BINF_ADDED; } } } return ret; } /* * Add multiple builtins. binl points to a table of `size' builtin * structures. Those for which (.flags & BINF_ADDED) is false are to be * added; that flag is set if they succeed. * * If any fail, an error message is printed, using nam as the leading name. * Returns 0 on success, 1 for any failure. * * This should not be used from a module; instead, use handlefeatures(). */ /**/ mod_export int addbuiltins(char const *nam, Builtin binl, int size) { int ret = 0, n; for(n = 0; n < size; n++) { Builtin b = &binl[n]; if(b->node.flags & BINF_ADDED) continue; if(addbuiltin(b)) { zwarnnam(nam, "name clash when adding builtin `%s'", b->node.nam); ret = 1; } else { b->node.flags |= BINF_ADDED; } } return ret; } /************************************************************************ * Function wrappers. ************************************************************************/ /* The list of function wrappers defined. */ /**/ FuncWrap wrappers; /* This adds a definition for a wrapper. Return value is one in case of * * error and zero if all went fine. */ /**/ mod_export int addwrapper(Module m, FuncWrap w) { FuncWrap p, q; /* * We can't add a wrapper to an alias, since it's supposed * to behave identically to the resolved module. This shouldn't * happen since we usually add wrappers when a real module is * loaded. */ if (m->node.flags & MOD_ALIAS) return 1; if (w->flags & WRAPF_ADDED) return 1; for (p = wrappers, q = NULL; p; q = p, p = p->next); if (q) q->next = w; else wrappers = w; w->next = NULL; w->flags |= WRAPF_ADDED; w->module = m; return 0; } /* This removes the given wrapper definition from the list. Returned is * * one in case of error and zero otherwise. */ /**/ mod_export int deletewrapper(Module m, FuncWrap w) { FuncWrap p, q; if (m->node.flags & MOD_ALIAS) return 1; if (w->flags & WRAPF_ADDED) { for (p = wrappers, q = NULL; p && p != w; q = p, p = p->next); if (p) { if (q) q->next = p->next; else wrappers = p->next; p->flags &= ~WRAPF_ADDED; return 0; } } return 1; } /************************************************************************ * Conditions. ************************************************************************/ /* The list of module-defined conditions. */ /**/ mod_export Conddef condtab; /* This gets a condition definition with the given name. The first * * argument says if we have to look for an infix condition. The last * * argument is non-zero if we should autoload modules if needed. */ /**/ Conddef getconddef(int inf, const char *name, int autol) { Conddef p; int f = 1; char *lookup, *s; /* detokenize the Dash to the form encoded in lookup tables */ lookup = dupstring(name); if (!lookup) return NULL; for (s = lookup; *s != '\0'; s++) { if (*s == Dash) *s = '-'; } do { for (p = condtab; p; p = p->next) { if ((!!inf == !!(p->flags & CONDF_INFIX)) && !strcmp(lookup, p->name)) break; } if (autol && p && p->module) { /* * This is a definition for an autoloaded condition; load the * module if we haven't tried that already. */ if (f) { (void)ensurefeature(p->module, (p->flags & CONDF_INFIX) ? "C:" : "c:", (p->flags & CONDF_AUTOALL) ? NULL : lookup); f = 0; p = NULL; } else { deleteconddef(p); return NULL; } } else break; } while (!p); return p; } /* * This adds the given condition definition. The return value is zero on * * success and 1 on failure. If there is a matching definition for an * * autoloaded condition, it is removed. * * This is used for adding both an autoload definition or * a real condition. In the latter case the caller is responsible * for setting the CONDF_ADDED flag. */ /**/ static int addconddef(Conddef c) { Conddef p = getconddef((c->flags & CONDF_INFIX), c->name, 0); if (p) { if (!p->module || (p->flags & CONDF_ADDED)) return 1; /* There is an autoload definition. */ deleteconddef(p); } c->next = condtab; condtab = c; return 0; } /* This removes the given condition definition from the list(s). If this * * is a definition for a autoloaded condition, the memory is freed. */ /**/ int deleteconddef(Conddef c) { Conddef p, q; for (p = condtab, q = NULL; p && p != c; q = p, p = p->next); if (p) { if (q) q->next = p->next; else condtab = p->next; if (p->module) { /* autoloaded, free it */ zsfree(p->name); zsfree(p->module); zfree(p, sizeof(*p)); } return 0; } return -1; } /* * Add or remove sets of conditions. The interface is * identical to setbuiltins(). */ /**/ static int setconddefs(char const *nam, Conddef c, int size, int *e) { int ret = 0; while (size--) { if (e && *e++) { if (c->flags & CONDF_ADDED) { c++; continue; } if (addconddef(c)) { zwarnnam(nam, "name clash when adding condition `%s'", c->name); ret = 1; } else { c->flags |= CONDF_ADDED; } } else { if (!(c->flags & CONDF_ADDED)) { c++; continue; } if (deleteconddef(c)) { zwarnnam(nam, "condition `%s' already deleted", c->name); ret = 1; } else { c->flags &= ~CONDF_ADDED; } } c++; } return ret; } /* This adds a definition for autoloading a module for a condition. */ /**/ static int add_autocond(const char *module, const char *cnam, int flags) { Conddef c; c = (Conddef) zalloc(sizeof(*c)); c->name = ztrdup(cnam); c->flags = ((flags & FEAT_INFIX) ? CONDF_INFIX : 0); if (flags & FEAT_AUTOALL) c->flags |= CONDF_AUTOALL; c->module = ztrdup(module); if (addconddef(c)) { zsfree(c->name); zsfree(c->module); zfree(c, sizeof(*c)); if (!(flags & FEAT_IGNORE)) return 1; } return 0; } /* Remove a condition added with add_autocond */ /**/ static int del_autocond(UNUSED(const char *modnam), const char *cnam, int flags) { Conddef cd = getconddef((flags & FEAT_INFIX) ? 1 : 0, cnam, 0); if (!cd) { if (!(flags & FEAT_IGNORE)) { return 2; } } else if (cd->flags & CONDF_ADDED) { if (!(flags & FEAT_IGNORE)) return 3; } else deleteconddef(cd); return 0; } /************************************************************************ * Hook functions. ************************************************************************/ /* This list of hook functions defined. */ /**/ Hookdef hooktab; /* Find a hook definition given the name. */ /**/ Hookdef gethookdef(char *n) { Hookdef p; for (p = hooktab; p; p = p->next) if (!strcmp(n, p->name)) return p; return NULL; } /* This adds the given hook definition. The return value is zero on * * success and 1 on failure. */ /**/ int addhookdef(Hookdef h) { if (gethookdef(h->name)) return 1; h->next = hooktab; hooktab = h; h->funcs = znewlinklist(); return 0; } /* * This adds multiple hook definitions. This is like addbuiltins(). * This allows a NULL module because we call it from init.c. */ /**/ mod_export int addhookdefs(Module m, Hookdef h, int size) { int ret = 0; while (size--) { if (addhookdef(h)) { zwarnnam(m ? m->node.nam : NULL, "name clash when adding hook `%s'", h->name); ret = 1; } h++; } return ret; } /* Delete hook definitions. */ /**/ int deletehookdef(Hookdef h) { Hookdef p, q; for (p = hooktab, q = NULL; p && p != h; q = p, p = p->next); if (!p) return 1; if (q) q->next = p->next; else hooktab = p->next; freelinklist(p->funcs, NULL); return 0; } /* Remove multiple hook definitions. */ /**/ mod_export int deletehookdefs(UNUSED(Module m), Hookdef h, int size) { int ret = 0; while (size--) { if (deletehookdef(h)) ret = 1; h++; } return ret; } /* Add a function to a hook. */ /**/ int addhookdeffunc(Hookdef h, Hookfn f) { zaddlinknode(h->funcs, (void *) f); return 0; } /**/ mod_export int addhookfunc(char *n, Hookfn f) { Hookdef h = gethookdef(n); if (h) return addhookdeffunc(h, f); return 1; } /* Delete a function from a hook. */ /**/ int deletehookdeffunc(Hookdef h, Hookfn f) { LinkNode p; for (p = firstnode(h->funcs); p; incnode(p)) if (f == (Hookfn) getdata(p)) { remnode(h->funcs, p); return 0; } return 1; } /* Delete a hook. */ /**/ mod_export int deletehookfunc(char *n, Hookfn f) { Hookdef h = gethookdef(n); if (h) return deletehookdeffunc(h, f); return 1; } /* Run the function(s) for a hook. */ /**/ mod_export int runhookdef(Hookdef h, void *d) { if (empty(h->funcs)) { if (h->def) return h->def(h, d); return 0; } else if (h->flags & HOOKF_ALL) { LinkNode p; int r; for (p = firstnode(h->funcs); p; incnode(p)) if ((r = ((Hookfn) getdata(p))(h, d))) return r; if (h->def) return h->def(h, d); return 0; } else return ((Hookfn) getdata(lastnode(h->funcs)))(h, d); } /************************************************************************ * Shell parameters. ************************************************************************/ /* * Check that it's possible to add a parameter. This * requires that either there's no parameter already present, * or it's a global parameter marked for autoloading. * * The special status 2 is to indicate it didn't work but * -i was in use so we didn't print a warning. */ static int checkaddparam(const char *nam, int opt_i) { Param pm; if (!(pm = (Param) gethashnode2(paramtab, nam))) return 0; if (pm->level || !(pm->node.flags & PM_AUTOLOAD)) { /* * -i suppresses "it's already that way" warnings, * but not "this can't possibly work" warnings, so we print * the message anyway if there's a local parameter blocking * the parameter we want to add, not if there's a * non-autoloadable parameter already there. This * is consistent with the way add_auto* functions work. */ if (!opt_i || pm->level) { zwarn("Can't add module parameter `%s': %s", nam, pm->level ? "local parameter exists" : "parameter already exists"); return 1; } return 2; } unsetparam_pm(pm, 0, 1); return 0; } /* This adds the given parameter definition. The return value is zero on * * success and 1 on failure. */ /**/ int addparamdef(Paramdef d) { Param pm; if (checkaddparam(d->name, 0)) return 1; if (d->getnfn) { if (!(pm = createspecialhash(d->name, d->getnfn, d->scantfn, d->flags))) return 1; } else if (!(pm = createparam(d->name, d->flags)) && !(pm = (Param) paramtab->getnode(paramtab, d->name))) return 1; d->pm = pm; pm->level = 0; if (d->var) pm->u.data = d->var; if (d->var || d->gsu) { /* * If no get/set/unset class, use the appropriate * variable type, else use the one supplied. */ switch (PM_TYPE(pm->node.flags)) { case PM_SCALAR: pm->gsu.s = d->gsu ? (GsuScalar)d->gsu : &varscalar_gsu; break; case PM_INTEGER: pm->gsu.i = d->gsu ? (GsuInteger)d->gsu : &varinteger_gsu; break; case PM_FFLOAT: case PM_EFLOAT: pm->gsu.f = d->gsu; break; case PM_ARRAY: pm->gsu.a = d->gsu ? (GsuArray)d->gsu : &vararray_gsu; break; case PM_HASHED: /* hashes may behave like standard hashes */ if (d->gsu) pm->gsu.h = (GsuHash)d->gsu; break; default: unsetparam_pm(pm, 0, 1); return 1; } } return 0; } /* Delete parameters defined. No error checking yet. */ /**/ int deleteparamdef(Paramdef d) { Param pm = (Param) paramtab->getnode(paramtab, d->name); if (!pm) return 1; if (pm != d->pm) { /* * See if the parameter has been hidden. If so, * bring it to the front to unset it. */ Param prevpm, searchpm; for (prevpm = pm, searchpm = pm->old; searchpm; prevpm = searchpm, searchpm = searchpm->old) if (searchpm == d->pm) break; if (!searchpm) return 1; paramtab->removenode(paramtab, pm->node.nam); prevpm->old = searchpm->old; searchpm->old = pm; paramtab->addnode(paramtab, searchpm->node.nam, searchpm); pm = searchpm; } pm->node.flags = (pm->node.flags & ~PM_READONLY) | PM_REMOVABLE; unsetparam_pm(pm, 0, 1); d->pm = NULL; return 0; } /* * Add or remove sets of parameters. The interface is * identical to setbuiltins(). */ /**/ static int setparamdefs(char const *nam, Paramdef d, int size, int *e) { int ret = 0; while (size--) { if (e && *e++) { if (d->pm) { d++; continue; } if (addparamdef(d)) { zwarnnam(nam, "error when adding parameter `%s'", d->name); ret = 1; } } else { if (!d->pm) { d++; continue; } if (deleteparamdef(d)) { zwarnnam(nam, "parameter `%s' already deleted", d->name); ret = 1; } } d++; } return ret; } /* This adds a definition for autoloading a module for a parameter. */ /**/ static int add_autoparam(const char *module, const char *pnam, int flags) { Param pm; int ret; int ne = noerrs; queue_signals(); if ((ret = checkaddparam(pnam, (flags & FEAT_IGNORE)))) { unqueue_signals(); /* * checkaddparam() has already printed a message if one was * needed. If it wasn't owing to the presence of -i, ret is 2; * for consistency with other add_auto* functions we return * status 0 to indicate there's already such a parameter and * we've been told not to worry if so. */ return ret == 2 ? 0 : -1; } noerrs = 2; if ((pm = setsparam(dupstring(pnam), ztrdup(module)))) { pm->node.flags |= PM_AUTOLOAD; if (flags & FEAT_AUTOALL) pm->node.flags |= PM_AUTOALL; ret = 0; } else ret = -1; noerrs = ne; unqueue_signals(); return ret; } /* Remove a parameter added with add_autoparam() */ /**/ static int del_autoparam(UNUSED(const char *modnam), const char *pnam, int flags) { Param pm = (Param) gethashnode2(paramtab, pnam); if (!pm) { if (!(flags & FEAT_IGNORE)) return 2; } else if (!(pm->node.flags & PM_AUTOLOAD)) { if (!(flags & FEAT_IGNORE)) return 3; } else unsetparam_pm(pm, 0, 1); return 0; } /************************************************************************ * Math functions. ************************************************************************/ /* List of math functions. */ /**/ MathFunc mathfuncs; /* * Remove a single math function form the list (utility function). * This does not delete a module math function, that's deletemathfunc(). */ /**/ void removemathfunc(MathFunc previous, MathFunc current) { if (previous) previous->next = current->next; else mathfuncs = current->next; zsfree(current->name); zsfree(current->module); zfree(current, sizeof(*current)); } /* Find a math function in the list, handling autoload if necessary. */ /**/ MathFunc getmathfunc(const char *name, int autol) { MathFunc p, q = NULL; for (p = mathfuncs; p; q = p, p = p->next) if (!strcmp(name, p->name)) { if (autol && p->module && !(p->flags & MFF_USERFUNC)) { char *n = dupstring(p->module); int flags = p->flags; removemathfunc(q, p); (void)ensurefeature(n, "f:", (flags & MFF_AUTOALL) ? NULL : name); p = getmathfunc(name, 0); if (!p) { zerr("autoloading module %s failed to define math function: %s", n, name); } } return p; } return NULL; } /* Add a single math function */ /**/ static int addmathfunc(MathFunc f) { MathFunc p, q = NULL; if (f->flags & MFF_ADDED) return 1; for (p = mathfuncs; p; q = p, p = p->next) if (!strcmp(f->name, p->name)) { if (p->module && !(p->flags & MFF_USERFUNC)) { /* * Autoloadable, replace. */ removemathfunc(q, p); break; } return 1; } f->next = mathfuncs; mathfuncs = f; return 0; } /* Delete a single math function */ /**/ mod_export int deletemathfunc(MathFunc f) { MathFunc p, q; for (p = mathfuncs, q = NULL; p && p != f; q = p, p = p->next); if (p) { if (q) q->next = f->next; else mathfuncs = f->next; /* the following applies to both unloaded and user-defined functions */ if (f->module) { zsfree(f->name); zsfree(f->module); zfree(f, sizeof(*f)); } else f->flags &= ~MFF_ADDED; return 0; } return -1; } /* * Add or remove sets of math functions. The interface is * identical to setbuiltins(). */ /**/ static int setmathfuncs(char const *nam, MathFunc f, int size, int *e) { int ret = 0; while (size--) { if (e && *e++) { if (f->flags & MFF_ADDED) { f++; continue; } if (addmathfunc(f)) { zwarnnam(nam, "name clash when adding math function `%s'", f->name); ret = 1; } else { f->flags |= MFF_ADDED; } } else { if (!(f->flags & MFF_ADDED)) { f++; continue; } if (deletemathfunc(f)) { zwarnnam(nam, "math function `%s' already deleted", f->name); ret = 1; } } f++; } return ret; } /* Add an autoload definition for a math function. */ /**/ static int add_automathfunc(const char *module, const char *fnam, int flags) { MathFunc f; f = (MathFunc) zalloc(sizeof(*f)); f->name = ztrdup(fnam); f->module = ztrdup(module); f->flags = 0; if (addmathfunc(f)) { zsfree(f->name); zsfree(f->module); zfree(f, sizeof(*f)); if (!(flags & FEAT_IGNORE)) return 1; } return 0; } /* Remove a math function added with add_automathfunc() */ /**/ static int del_automathfunc(UNUSED(const char *modnam), const char *fnam, int flags) { MathFunc f = getmathfunc(fnam, 0); if (!f) { if (!(flags & FEAT_IGNORE)) return 2; } else if (f->flags & MFF_ADDED) { if (!(flags & FEAT_IGNORE)) return 3; } else deletemathfunc(f); return 0; } /************************************************************************ * Now support for dynamical loading and the fallback functions * we use for loading if dynamical loading is not available. ************************************************************************/ /**/ #ifdef DYNAMIC /**/ #ifdef AIXDYNAMIC #include static char *dlerrstr[256]; static void * load_and_bind(const char *fn) { void *ret = (void *) load((char *) fn, L_NOAUTODEFER, NULL); if (ret) { Module m; int i, err = loadbind(0, (void *) addbuiltin, ret); for (i = 0; i < modulestab->hsize && !err; i++) { for (m = (Module)modulestab->nodes[i]; m && !err; m = (Module)m->node.next) { if (!(m->node.flags & MOD_ALIAS) && m->u.handle && !(m->node.flags & MOD_LINKED)) err |= loadbind(0, m->u.handle, ret); } } if (err) { loadquery(L_GETMESSAGES, dlerrstr, sizeof(dlerrstr)); unload(ret); ret = NULL; } } else loadquery(L_GETMESSAGES, dlerrstr, sizeof(dlerrstr)); return ret; } #define dlopen(X,Y) load_and_bind(X) #define dlclose(X) unload(X) #define dlerror() (dlerrstr[0]) #ifndef HAVE_DLERROR # define HAVE_DLERROR 1 #endif /**/ #else #ifdef HAVE_DLFCN_H # if defined(HAVE_DL_H) && defined(HPUX10DYNAMIC) # include # else # include # endif #else # ifdef HAVE_DL_H # include # define RTLD_LAZY BIND_DEFERRED # define RTLD_GLOBAL DYNAMIC_PATH # else # include # include # include # endif #endif /**/ #ifdef HPUX10DYNAMIC # define dlopen(file,mode) (void *)shl_load((file), (mode), (long) 0) # define dlclose(handle) shl_unload((shl_t)(handle)) static void * hpux_dlsym(void *handle, char *name) { void *sym_addr; if (!shl_findsym((shl_t *)&handle, name, TYPE_UNDEFINED, &sym_addr)) return sym_addr; return NULL; } # define dlsym(handle,name) hpux_dlsym(handle,name) # ifdef HAVE_DLERROR /* paranoia */ # undef HAVE_DLERROR # endif #else # ifndef HAVE_DLCLOSE # define dlclose(X) ((X), 0) # endif /**/ #endif #ifdef DLSYM_NEEDS_UNDERSCORE # define STR_SETUP "_setup_" # define STR_FEATURES "_features_" # define STR_ENABLES "_enables_" # define STR_BOOT "_boot_" # define STR_CLEANUP "_cleanup_" # define STR_FINISH "_finish_" #else /* !DLSYM_NEEDS_UNDERSCORE */ # define STR_SETUP "setup_" # define STR_FEATURES "features_" # define STR_ENABLES "enables_" # define STR_BOOT "boot_" # define STR_CLEANUP "cleanup_" # define STR_FINISH "finish_" #endif /* !DLSYM_NEEDS_UNDERSCORE */ /**/ #endif /* !AIXDYNAMIC */ #ifndef RTLD_LAZY # define RTLD_LAZY 1 #endif #ifndef RTLD_GLOBAL # define RTLD_GLOBAL 0 #endif /* * Attempt to load a module. This is the lowest level of * zsh function for dynamical modules. Returns the handle * from the dynamic loader. */ /**/ static void * try_load_module(char const *name) { char buf[PATH_MAX + 1]; char **pp; void *ret = NULL; int l; l = 1 + strlen(name) + 1 + strlen(DL_EXT); for (pp = module_path; !ret && *pp; pp++) { if (l + (**pp ? strlen(*pp) : 1) > PATH_MAX) continue; sprintf(buf, "%s/%s.%s", **pp ? *pp : ".", name, DL_EXT); unmetafy(buf, NULL); if (*buf) /* dlopen(NULL) returns a handle to the main binary */ ret = dlopen(buf, RTLD_LAZY | RTLD_GLOBAL); } return ret; } /* * Load a module, with option to complain or not. * Returns the handle from the dynamic loader. */ /**/ static void * do_load_module(char const *name, int silent) { void *ret; ret = try_load_module(name); if (!ret && !silent) { #ifdef HAVE_DLERROR char *errstr = dlerror(); zwarn("failed to load module `%s': %s", name, errstr ? metafy(errstr, -1, META_HEAPDUP) : "empty module path"); #else zwarn("failed to load module: %s", name); #endif } return ret; } /**/ #else /* !DYNAMIC */ /* * Dummy loader when no dynamic loading available; always fails. */ /**/ static void * do_load_module(char const *name, int silent) { if (!silent) zwarn("failed to load module: %s", name); return NULL; } /**/ #endif /* !DYNAMIC */ /* * Find a module in the list. * flags is a set of bits defined in the enum above. * If namep is set, this is set to point to the last alias value resolved, * even if that module was not loaded. or the module name if no aliases. * Hence this is always the physical module to load in a chain of aliases. * Return NULL if the module named is not stored as a structure, or if we were * resolving aliases and the final module named is not stored as a * structure. */ /**/ static Module find_module(const char *name, int flags, const char **namep) { Module m; m = (Module)modulestab->getnode2(modulestab, name); if (m) { if ((flags & FINDMOD_ALIASP) && (m->node.flags & MOD_ALIAS)) { if (namep) *namep = m->u.alias; return find_module(m->u.alias, flags, namep); } if (namep) *namep = m->node.nam; return m; } if (!(flags & FINDMOD_CREATE)) return NULL; m = zshcalloc(sizeof(*m)); modulestab->addnode(modulestab, ztrdup(name), m); return m; } /* * Unlink and free a module node from the linked list. */ /**/ static void delete_module(Module m) { modulestab->removenode(modulestab, m->node.nam); modulestab->freenode(&m->node); } /* * Return 1 if a module is fully loaded else zero. * A linked module may be marked as unloaded even though * we can't fully unload it; this returns 0 to try to * make that state transparently like an unloaded module. */ /**/ mod_export int module_loaded(const char *name) { Module m; return ((m = find_module(name, FINDMOD_ALIASP, NULL)) && m->u.handle && !(m->node.flags & MOD_UNLOAD)); } /* * Setup and cleanup functions: we don't search for aliases here, * since they should have been resolved before we try to load or unload * the module. */ /**/ #ifdef DYNAMIC /**/ #ifdef AIXDYNAMIC /**/ static int dyn_setup_module(Module m) { return ((int (*)_((int,Module, void*))) m->u.handle)(0, m, NULL); } /**/ static int dyn_features_module(Module m, char ***features) { return ((int (*)_((int,Module, void*))) m->u.handle)(4, m, features); } /**/ static int dyn_enables_module(Module m, int **enables) { return ((int (*)_((int,Module, void*))) m->u.handle)(5, m, enables); } /**/ static int dyn_boot_module(Module m) { return ((int (*)_((int,Module, void*))) m->u.handle)(1, m, NULL); } /**/ static int dyn_cleanup_module(Module m) { return ((int (*)_((int,Module, void*))) m->u.handle)(2, m, NULL); } /**/ static int dyn_finish_module(Module m) { return ((int (*)_((int,Module,void *))) m->u.handle)(3, m, NULL); } /**/ #else static Module_generic_func module_func(Module m, char *name) { #ifdef DYNAMIC_NAME_CLASH_OK return (Module_generic_func) dlsym(m->u.handle, name); #else /* !DYNAMIC_NAME_CLASH_OK */ VARARR(char, buf, strlen(name) + strlen(m->node.nam)*2 + 1); char const *p; char *q; strcpy(buf, name); q = strchr(buf, 0); for(p = m->node.nam; *p; p++) { if(*p == '/') { *q++ = 'Q'; *q++ = 's'; } else if(*p == '_') { *q++ = 'Q'; *q++ = 'u'; } else if(*p == 'Q') { *q++ = 'Q'; *q++ = 'q'; } else *q++ = *p; } *q = 0; return (Module_generic_func) dlsym(m->u.handle, buf); #endif /* !DYNAMIC_NAME_CLASH_OK */ } /**/ static int dyn_setup_module(Module m) { Module_void_func fn = (Module_void_func)module_func(m, STR_SETUP); if (fn) return fn(m); zwarnnam(m->node.nam, "no setup function"); return 1; } /**/ static int dyn_features_module(Module m, char ***features) { Module_features_func fn = (Module_features_func)module_func(m, STR_FEATURES); if (fn) return fn(m, features); /* not a user-visible error if no features function */ return 1; } /**/ static int dyn_enables_module(Module m, int **enables) { Module_enables_func fn = (Module_enables_func)module_func(m, STR_ENABLES); if (fn) return fn(m, enables); /* not a user-visible error if no enables function */ return 1; } /**/ static int dyn_boot_module(Module m) { Module_void_func fn = (Module_void_func)module_func(m, STR_BOOT); if(fn) return fn(m); zwarnnam(m->node.nam, "no boot function"); return 1; } /**/ static int dyn_cleanup_module(Module m) { Module_void_func fn = (Module_void_func)module_func(m, STR_CLEANUP); if(fn) return fn(m); zwarnnam(m->node.nam, "no cleanup function"); return 1; } /* Note that this function does more than just calling finish_foo(), * * it really unloads the module. */ /**/ static int dyn_finish_module(Module m) { Module_void_func fn = (Module_void_func)module_func(m, STR_FINISH); int r; if (fn) r = fn(m); else { zwarnnam(m->node.nam, "no finish function"); r = 1; } dlclose(m->u.handle); return r; } /**/ #endif /* !AIXDYNAMIC */ /**/ static int setup_module(Module m) { return ((m->node.flags & MOD_LINKED) ? (m->u.linked->setup)(m) : dyn_setup_module(m)); } /**/ static int features_module(Module m, char ***features) { return ((m->node.flags & MOD_LINKED) ? (m->u.linked->features)(m, features) : dyn_features_module(m, features)); } /**/ static int enables_module(Module m, int **enables) { return ((m->node.flags & MOD_LINKED) ? (m->u.linked->enables)(m, enables) : dyn_enables_module(m, enables)); } /**/ static int boot_module(Module m) { return ((m->node.flags & MOD_LINKED) ? (m->u.linked->boot)(m) : dyn_boot_module(m)); } /**/ static int cleanup_module(Module m) { return ((m->node.flags & MOD_LINKED) ? (m->u.linked->cleanup)(m) : dyn_cleanup_module(m)); } /**/ static int finish_module(Module m) { return ((m->node.flags & MOD_LINKED) ? (m->u.linked->finish)(m) : dyn_finish_module(m)); } /**/ #else /* !DYNAMIC */ /**/ static int setup_module(Module m) { return ((m->node.flags & MOD_LINKED) ? (m->u.linked->setup)(m) : 1); } /**/ static int features_module(Module m, char ***features) { return ((m->node.flags & MOD_LINKED) ? (m->u.linked->features)(m, features) : 1); } /**/ static int enables_module(Module m, int **enables) { return ((m->node.flags & MOD_LINKED) ? (m->u.linked->enables)(m, enables) : 1); } /**/ static int boot_module(Module m) { return ((m->node.flags & MOD_LINKED) ? (m->u.linked->boot)(m) : 1); } /**/ static int cleanup_module(Module m) { return ((m->node.flags & MOD_LINKED) ? (m->u.linked->cleanup)(m) : 1); } /**/ static int finish_module(Module m) { return ((m->node.flags & MOD_LINKED) ? (m->u.linked->finish)(m) : 1); } /**/ #endif /* !DYNAMIC */ /************************************************************************ * Functions called when manipulating modules ************************************************************************/ /* * Set the features for the module, which must be loaded * by now (though may not be fully set up). * * Return 0 for success, 1 for failure, 2 if some features * couldn't be set by the module itself (non-existent features * are tested here and cause 1 to be returned). */ /**/ static int do_module_features(Module m, Feature_enables enablesarr, int flags) { char **features; int ret = 0; if (features_module(m, &features) == 0) { /* * Features are supported. If we were passed * a NULL array, enable all features, else * enable only the features listed. * (This may in principle be an empty array, * although that's not very pointful.) */ int *enables = NULL; if (enables_module(m, &enables)) { /* If features are supported, enables should be, too */ if (!(flags & FEAT_IGNORE)) zwarn("error getting enabled features for module `%s'", m->node.nam); return 1; } if ((flags & FEAT_CHECKAUTO) && m->autoloads) { /* * Check autoloads are available. Since these * have been requested at some other point, they * don't affect the return status unless something * in enablesstr doesn't work. */ LinkNode an, nextn; for (an = firstnode(m->autoloads); an; an = nextn) { char *al = (char *)getdata(an), **ptr; /* careful, we can delete the current node */ nextn = nextnode(an); for (ptr = features; *ptr; ptr++) if (!strcmp(al, *ptr)) break; if (!*ptr) { char *arg[2]; if (!(flags & FEAT_IGNORE)) zwarn( "module `%s' has no such feature: `%s': autoload cancelled", m->node.nam, al); /* * This shouldn't happen, so it's not worth optimising * the call to autofeatures... */ arg[0] = al = dupstring(al); arg[1] = NULL; (void)autofeatures(NULL, m->node.nam, arg, 0, FEAT_IGNORE|FEAT_REMOVE); /* * don't want to try to enable *that*... * expunge it from the enable string. */ if (enablesarr) { Feature_enables fep; for (fep = enablesarr; fep->str; fep++) { char *str = fep->str; if (*str == '+' || *str == '-') str++; if (fep->pat ? pattry(fep->pat, al) : !strcmp(al, str)) { /* can't enable it after all, so return 1 */ ret = 1; while (fep->str) { fep->str = fep[1].str; fep->pat = fep[1].pat; fep++; } if (!fep->pat) break; } } } } } } if (enablesarr) { Feature_enables fep; for (fep = enablesarr; fep->str; fep++) { char **fp, *esp = fep->str; int on = 1, found = 0; if (*esp == '+') esp++; else if (*esp == '-') { on = 0; esp++; } for (fp = features; *fp; fp++) if (fep->pat ? pattry(fep->pat, *fp) : !strcmp(*fp, esp)) { enables[fp - features] = on; found++; if (!fep->pat) break; } if (!found) { if (!(flags & FEAT_IGNORE)) zwarn(fep->pat ? "module `%s' has no feature matching: `%s'" : "module `%s' has no such feature: `%s'", m->node.nam, esp); return 1; } } } else { /* * Enable all features. This is used when loading * without using zmodload -F. */ int n_features = arrlen(features); int *ep; for (ep = enables; n_features--; ep++) *ep = 1; } if (enables_module(m, &enables)) return 2; } else if (enablesarr) { if (!(flags & FEAT_IGNORE)) zwarn("module `%s' does not support features", m->node.nam); return 1; } /* Else it doesn't support features but we don't care. */ return ret; } /* * Boot the module, including setting up features. * As we've only just loaded the module, we don't yet * know what features it supports, so we get them passed * as a string. * * Returns 0 if OK, 1 if completely failed, 2 if some features * couldn't be set up. */ /**/ static int do_boot_module(Module m, Feature_enables enablesarr, int silent) { int ret = do_module_features(m, enablesarr, silent ? FEAT_IGNORE|FEAT_CHECKAUTO : FEAT_CHECKAUTO); if (ret == 1) return 1; if (boot_module(m)) return 1; return ret; } /* * Cleanup the module. */ /**/ static int do_cleanup_module(Module m) { return (m->node.flags & MOD_LINKED) ? (m->u.linked && m->u.linked->cleanup(m)) : (m->u.handle && cleanup_module(m)); } /* * Test a module name contains only valid characters: those * allowed in a shell identifier plus slash. Return 1 if so. */ /**/ static int modname_ok(char const *p) { do { p = itype_end(p, IIDENT, 0); if (!*p) return 1; } while(*p++ == '/'); return 0; } /* * High level function to load a module, encapsulating * all the handling of module functions. * * "*enablesstr" is NULL if the caller is not feature-aware; * then the module should turn on all features. If it * is not NULL it points to an array of features to be * turned on. This function is responsible for testing whether * the module supports those features. * * If "silent" is 1, don't issue warnings for errors. * * Now returns 0 for success (changed post-4.3.4), * 1 for complete failure, 2 if some features couldn't be set. */ /**/ mod_export int load_module(char const *name, Feature_enables enablesarr, int silent) { Module m; void *handle = NULL; Linkedmod linked; int set, bootret; if (!modname_ok(name)) { if (!silent) zerr("invalid module name `%s'", name); return 1; } /* * The following function call may alter name to the final name in a * chain of aliases. This makes sure the actual module loaded * is the right one. */ queue_signals(); if (!(m = find_module(name, FINDMOD_ALIASP, &name))) { if (!(linked = module_linked(name)) && !(handle = do_load_module(name, silent))) { unqueue_signals(); return 1; } m = zshcalloc(sizeof(*m)); if (handle) { m->u.handle = handle; m->node.flags |= MOD_SETUP; } else { m->u.linked = linked; m->node.flags |= MOD_SETUP | MOD_LINKED; } modulestab->addnode(modulestab, ztrdup(name), m); if ((set = setup_module(m)) || (bootret = do_boot_module(m, enablesarr, silent)) == 1) { if (!set) do_cleanup_module(m); finish_module(m); delete_module(m); unqueue_signals(); return 1; } m->node.flags |= MOD_INIT_S | MOD_INIT_B; m->node.flags &= ~MOD_SETUP; unqueue_signals(); return bootret; } if (m->node.flags & MOD_SETUP) { unqueue_signals(); return 0; } if (m->node.flags & MOD_UNLOAD) m->node.flags &= ~MOD_UNLOAD; else if ((m->node.flags & MOD_LINKED) ? m->u.linked : m->u.handle) { unqueue_signals(); return 0; } if (m->node.flags & MOD_BUSY) { unqueue_signals(); zerr("circular dependencies for module ;%s", name); return 1; } m->node.flags |= MOD_BUSY; /* * TODO: shouldn't we unload the module if one of * its dependencies fails? */ if (m->deps) { LinkNode n; for (n = firstnode(m->deps); n; incnode(n)) if (load_module((char *) getdata(n), NULL, silent) == 1) { m->node.flags &= ~MOD_BUSY; unqueue_signals(); return 1; } } m->node.flags &= ~MOD_BUSY; if (!m->u.handle) { handle = NULL; if (!(linked = module_linked(name)) && !(handle = do_load_module(name, silent))) { unqueue_signals(); return 1; } if (handle) { m->u.handle = handle; m->node.flags |= MOD_SETUP; } else { m->u.linked = linked; m->node.flags |= MOD_SETUP | MOD_LINKED; } if (setup_module(m)) { finish_module(m); if (handle) m->u.handle = NULL; else m->u.linked = NULL; m->node.flags &= ~MOD_SETUP; unqueue_signals(); return 1; } m->node.flags |= MOD_INIT_S; } m->node.flags |= MOD_SETUP; if ((bootret = do_boot_module(m, enablesarr, silent)) == 1) { do_cleanup_module(m); finish_module(m); if (m->node.flags & MOD_LINKED) m->u.linked = NULL; else m->u.handle = NULL; m->node.flags &= ~MOD_SETUP; unqueue_signals(); return 1; } m->node.flags |= MOD_INIT_B; m->node.flags &= ~MOD_SETUP; unqueue_signals(); return bootret; } /* This ensures that the module with the name given as the first argument * is loaded. * The other argument is the array of features to set. If this is NULL * all features are enabled (even if the module was already loaded). * * If this is non-NULL the module features are set accordingly * whether or not the module is loaded; it is an error if the * module does not support the features passed (even if the feature * is to be turned off) or if the module does not support features * at all. * The return value is 0 if the module was found or loaded * (this changed post-4.3.4, because I got so confused---pws), * 1 if loading failed completely, 2 if some features couldn't be set. * * This function behaves like load_module() except that it * handles the case where the module was already loaded, and * sets features accordingly. */ /**/ mod_export int require_module(const char *module, Feature_enables features, int silent) { Module m = NULL; int ret = 0; /* Resolve aliases and actual loadable module as for load_module */ queue_signals(); m = find_module(module, FINDMOD_ALIASP, &module); if (!m || !m->u.handle || (m->node.flags & MOD_UNLOAD)) ret = load_module(module, features, silent); else ret = do_module_features(m, features, 0); unqueue_signals(); return ret; } /* * Indicate that the module named "name" depends on the module * named "from". */ /**/ void add_dep(const char *name, char *from) { LinkNode node; Module m; /* * If we were passed an alias, we must resolve it to a final * module name (and maybe add the corresponding struct), since otherwise * we would need to check all modules to see if they happen * to be aliased to the same thing to implement dependencies properly. * * This should mean that an attempt to add an alias which would * have the same name as a module which has dependencies is correctly * rejected, because then the module named already exists as a non-alias. * Better make sure. (There's no problem making a an alias which * *points* to a module with dependencies, of course.) */ m = find_module(name, FINDMOD_ALIASP|FINDMOD_CREATE, &name); if (!m->deps) m->deps = znewlinklist(); for (node = firstnode(m->deps); node && strcmp((char *) getdata(node), from); incnode(node)); if (!node) zaddlinknode(m->deps, ztrdup(from)); } /* * Function to be used when scanning the builtins table to * find and print autoloadable builtins. */ /**/ static void autoloadscan(HashNode hn, int printflags) { Builtin bn = (Builtin) hn; if(bn->node.flags & BINF_ADDED) return; if(printflags & PRINT_LIST) { fputs("zmodload -ab ", stdout); if(bn->optstr[0] == '-') fputs("-- ", stdout); quotedzputs(bn->optstr, stdout); if(strcmp(bn->node.nam, bn->optstr)) { putchar(' '); quotedzputs(bn->node.nam, stdout); } } else { nicezputs(bn->node.nam, stdout); if(strcmp(bn->node.nam, bn->optstr)) { fputs(" (", stdout); nicezputs(bn->optstr, stdout); putchar(')'); } } putchar('\n'); } /************************************************************************ * Handling for the zmodload builtin and its various options. ************************************************************************/ /* * Main builtin entry point for zmodload. */ /**/ int bin_zmodload(char *nam, char **args, Options ops, UNUSED(int func)) { int ops_bcpf = OPT_ISSET(ops,'b') || OPT_ISSET(ops,'c') || OPT_ISSET(ops,'p') || OPT_ISSET(ops,'f'); int ops_au = OPT_ISSET(ops,'a') || OPT_ISSET(ops,'u'); int ret = 1, autoopts; /* options only allowed with -F */ char *fonly = "lP", *fp; if (ops_bcpf && !ops_au) { zwarnnam(nam, "-b, -c, -f, and -p must be combined with -a or -u"); return 1; } if (OPT_ISSET(ops,'F') && (ops_bcpf || OPT_ISSET(ops,'u'))) { zwarnnam(nam, "-b, -c, -f, -p and -u cannot be combined with -F"); return 1; } if (OPT_ISSET(ops,'A') || OPT_ISSET(ops,'R')) { if (ops_bcpf || ops_au || OPT_ISSET(ops,'d') || (OPT_ISSET(ops,'R') && OPT_ISSET(ops,'e'))) { zwarnnam(nam, "illegal flags combined with -A or -R"); return 1; } if (!OPT_ISSET(ops,'e')) return bin_zmodload_alias(nam, args, ops); } if (OPT_ISSET(ops,'d') && OPT_ISSET(ops,'a')) { zwarnnam(nam, "-d cannot be combined with -a"); return 1; } if (OPT_ISSET(ops,'u') && !*args) { zwarnnam(nam, "what do you want to unload?"); return 1; } if (OPT_ISSET(ops,'e') && (OPT_ISSET(ops,'I') || OPT_ISSET(ops,'L') || (OPT_ISSET(ops,'a') && !OPT_ISSET(ops,'F')) || OPT_ISSET(ops,'d') || OPT_ISSET(ops,'i') || OPT_ISSET(ops,'u'))) { zwarnnam(nam, "-e cannot be combined with other options"); /* except -F ... */ return 1; } for (fp = fonly; *fp; fp++) { if (OPT_ISSET(ops,(unsigned char) *fp) && !OPT_ISSET(ops,'F')) { zwarnnam(nam, "-%c is only allowed with -F", *fp); return 1; } } queue_signals(); if (OPT_ISSET(ops, 'F')) ret = bin_zmodload_features(nam, args, ops); else if (OPT_ISSET(ops,'e')) ret = bin_zmodload_exist(nam, args, ops); else if (OPT_ISSET(ops,'d')) ret = bin_zmodload_dep(nam, args, ops); else if ((autoopts = OPT_ISSET(ops, 'b') + OPT_ISSET(ops, 'c') + OPT_ISSET(ops, 'p') + OPT_ISSET(ops, 'f')) || /* zmodload -a is equivalent to zmodload -ab, annoyingly */ OPT_ISSET(ops, 'a')) { if (autoopts > 1) { zwarnnam(nam, "use only one of -b, -c, or -p"); ret = 1; } else ret = bin_zmodload_auto(nam, args, ops); } else ret = bin_zmodload_load(nam, args, ops); unqueue_signals(); return ret; } /* zmodload -A */ /**/ static int bin_zmodload_alias(char *nam, char **args, Options ops) { /* * TODO: while it would be too nasty to have aliases, as opposed * to real loadable modules, with dependencies --- just what would * we need to load when, exactly? --- there is in principle no objection * to making it possible to force an alias onto an existing unloaded * module which has dependencies. This would simply transfer * the dependencies down the line to the aliased-to module name. * This is actually useful, since then you can alias zsh/zle=mytestzle * to load another version of zle. But then what happens when the * alias is removed? Do you transfer the dependencies back? And * suppose other names are aliased to the same file? It might be * kettle of fish best left unwormed. */ Module m; if (!*args) { if (OPT_ISSET(ops,'R')) { zwarnnam(nam, "no module alias to remove"); return 1; } scanhashtable(modulestab, 1, MOD_ALIAS, 0, modulestab->printnode, OPT_ISSET(ops,'L') ? PRINTMOD_LIST : 0); return 0; } for (; *args; args++) { char *eqpos = strchr(*args, '='); char *aliasname = eqpos ? eqpos+1 : NULL; if (eqpos) *eqpos = '\0'; if (!modname_ok(*args)) { zwarnnam(nam, "invalid module name `%s'", *args); return 1; } if (OPT_ISSET(ops,'R')) { if (aliasname) { zwarnnam(nam, "bad syntax for removing module alias: %s", *args); return 1; } m = find_module(*args, 0, NULL); if (m) { if (!(m->node.flags & MOD_ALIAS)) { zwarnnam(nam, "module is not an alias: %s", *args); return 1; } delete_module(m); } else { zwarnnam(nam, "no such module alias: %s", *args); return 1; } } else { if (aliasname) { const char *mname = aliasname; if (!modname_ok(aliasname)) { zwarnnam(nam, "invalid module name `%s'", aliasname); return 1; } do { if (!strcmp(mname, *args)) { zwarnnam(nam, "module alias would refer to itself: %s", *args); return 1; } } while ((m = find_module(mname, 0, NULL)) && (m->node.flags & MOD_ALIAS) && (mname = m->u.alias)); m = find_module(*args, 0, NULL); if (m) { if (!(m->node.flags & MOD_ALIAS)) { zwarnnam(nam, "module is not an alias: %s", *args); return 1; } zsfree(m->u.alias); } else { m = (Module) zshcalloc(sizeof(*m)); m->node.flags = MOD_ALIAS; modulestab->addnode(modulestab, ztrdup(*args), m); } m->u.alias = ztrdup(aliasname); } else { if ((m = find_module(*args, 0, NULL))) { if (m->node.flags & MOD_ALIAS) modulestab->printnode(&m->node, OPT_ISSET(ops,'L') ? PRINTMOD_LIST : 0); else { zwarnnam(nam, "module is not an alias: %s", *args); return 1; } } else { zwarnnam(nam, "no such module alias: %s", *args); return 1; } } } } return 0; } /* zmodload -e (without -F) */ /**/ static int bin_zmodload_exist(UNUSED(char *nam), char **args, Options ops) { Module m; if (!*args) { scanhashtable(modulestab, 1, 0, 0, modulestab->printnode, OPT_ISSET(ops,'A') ? PRINTMOD_EXIST|PRINTMOD_ALIAS : PRINTMOD_EXIST); return 0; } else { int ret = 0; for (; !ret && *args; args++) { if (!(m = find_module(*args, FINDMOD_ALIASP, NULL)) || !m->u.handle || (m->node.flags & MOD_UNLOAD)) ret = 1; } return ret; } } /* zmodload -d */ /**/ static int bin_zmodload_dep(UNUSED(char *nam), char **args, Options ops) { Module m; if (OPT_ISSET(ops,'u')) { /* remove dependencies, which can't pertain to aliases */ const char *tnam = *args++; m = find_module(tnam, FINDMOD_ALIASP, &tnam); if (!m) return 0; if (*args && m->deps) { do { LinkNode dnode; for (dnode = firstnode(m->deps); dnode; incnode(dnode)) if (!strcmp(*args, getdata(dnode))) { zsfree(getdata(dnode)); remnode(m->deps, dnode); break; } } while(*++args); if (empty(m->deps)) { freelinklist(m->deps, freestr); m->deps = NULL; } } else { if (m->deps) { freelinklist(m->deps, freestr); m->deps = NULL; } } if (!m->deps && !m->u.handle) delete_module(m); return 0; } else if (!args[0] || !args[1]) { /* list dependencies */ int depflags = OPT_ISSET(ops,'L') ? PRINTMOD_DEPS|PRINTMOD_LIST : PRINTMOD_DEPS; if (args[0]) { if ((m = (Module)modulestab->getnode2(modulestab, args[0]))) modulestab->printnode(&m->node, depflags); } else { scanhashtable(modulestab, 1, 0, 0, modulestab->printnode, depflags); } return 0; } else { /* add dependencies */ int ret = 0; char *tnam = *args++; for (; *args; args++) add_dep(tnam, *args); return ret; } } /* * Function for scanning the parameter table to find and print * out autoloadable parameters. */ static void printautoparams(HashNode hn, int lon) { Param pm = (Param) hn; if (pm->node.flags & PM_AUTOLOAD) { if (lon) printf("zmodload -ap %s %s\n", pm->u.str, pm->node.nam); else printf("%s (%s)\n", pm->node.nam, pm->u.str); } } /* zmodload -a/u [bcpf] */ /**/ static int bin_zmodload_auto(char *nam, char **args, Options ops) { int fchar, flags; char *modnam; if (OPT_ISSET(ops,'c')) { if (!*args) { /* list autoloaded conditions */ Conddef p; for (p = condtab; p; p = p->next) { if (p->module) { if (OPT_ISSET(ops,'L')) { fputs("zmodload -ac", stdout); if (p->flags & CONDF_INFIX) putchar('I'); printf(" %s %s\n", p->module, p->name); } else { if (p->flags & CONDF_INFIX) fputs("infix ", stdout); else fputs("post ", stdout); printf("%s (%s)\n",p->name, p->module); } } } return 0; } fchar = OPT_ISSET(ops,'I') ? 'C' : 'c'; } else if (OPT_ISSET(ops,'p')) { if (!*args) { /* list autoloaded parameters */ scanhashtable(paramtab, 1, 0, 0, printautoparams, OPT_ISSET(ops,'L')); return 0; } fchar = 'p'; } else if (OPT_ISSET(ops,'f')) { if (!*args) { /* list autoloaded math functions */ MathFunc p; for (p = mathfuncs; p; p = p->next) { if (!(p->flags & MFF_USERFUNC) && p->module) { if (OPT_ISSET(ops,'L')) { fputs("zmodload -af", stdout); printf(" %s %s\n", p->module, p->name); } else printf("%s (%s)\n",p->name, p->module); } } return 0; } fchar = 'f'; } else { /* builtins are the default; zmodload -ab or just zmodload -a */ if (!*args) { /* list autoloaded builtins */ scanhashtable(builtintab, 1, 0, 0, autoloadscan, OPT_ISSET(ops,'L') ? PRINT_LIST : 0); return 0; } fchar = 'b'; } flags = FEAT_AUTOALL; if (OPT_ISSET(ops,'i')) flags |= FEAT_IGNORE; if (OPT_ISSET(ops,'u')) { /* remove autoload */ flags |= FEAT_REMOVE; modnam = NULL; } else { /* add autoload */ modnam = *args; if (args[1]) args++; } return autofeatures(nam, modnam, args, fchar, flags); } /* Backend handler for zmodload -u */ /**/ int unload_module(Module m) { int del; /* * Only unload the real module, so resolve aliases. */ if (m->node.flags & MOD_ALIAS) { m = find_module(m->u.alias, FINDMOD_ALIASP, NULL); if (!m) return 1; } /* * We may need to clean up the module any time setup_ has been * called. After cleanup_ is successful we are no longer in the * booted state (because features etc. are deregistered), so remove * MOD_INIT_B, and also MOD_INIT_S since we won't need to cleanup * again if this succeeded. */ if ((m->node.flags & MOD_INIT_S) && !(m->node.flags & MOD_UNLOAD) && do_cleanup_module(m)) return 1; m->node.flags &= ~(MOD_INIT_B|MOD_INIT_S); del = (m->node.flags & MOD_UNLOAD); if (m->wrapper) { m->node.flags |= MOD_UNLOAD; return 0; } m->node.flags &= ~MOD_UNLOAD; /* * We always need to finish the module (and unload it) * if it is present. */ if (m->node.flags & MOD_LINKED) { if (m->u.linked) { m->u.linked->finish(m); m->u.linked = NULL; } } else { if (m->u.handle) { finish_module(m); m->u.handle = NULL; } } if (del && m->deps) { /* The module was unloaded delayed, unload all modules * * on which it depended. */ LinkNode n; for (n = firstnode(m->deps); n; incnode(n)) { Module dm = find_module((char *) getdata(n), FINDMOD_ALIASP, NULL); if (dm && (dm->node.flags & MOD_UNLOAD)) { /* See if this is the only module depending on it. */ Module am; int du = 1, i; /* Scan hash table the hard way */ for (i = 0; du && i < modulestab->hsize; i++) { for (am = (Module)modulestab->nodes[i]; du && am; am = (Module)am->node.next) { LinkNode sn; /* * Don't scan the module we're unloading; * ignore if no dependencies. */ if (am == m || !am->deps) continue; /* Don't scan if not loaded nor linked */ if ((am->node.flags & MOD_LINKED) ? !am->u.linked : !am->u.handle) continue; for (sn = firstnode(am->deps); du && sn; incnode(sn)) { if (!strcmp((char *) getdata(sn), dm->node.nam)) du = 0; } } } if (du) unload_module(dm); } } } if (m->autoloads && firstnode(m->autoloads)) { /* * Module has autoloadable features. Restore them * so that the module will be reloaded when needed. */ autofeatures("zsh", m->node.nam, hlinklist2array(m->autoloads, 0), 0, FEAT_IGNORE); } else if (!m->deps) { delete_module(m); } return 0; } /* * Unload a module by name (modname); nam is the command name. * Optionally don't print some error messages (always print * dependency errors). */ /**/ int unload_named_module(char *modname, char *nam, int silent) { const char *mname; Module m; int ret = 0; m = find_module(modname, FINDMOD_ALIASP, &mname); if (m) { int i, del = 0; Module dm; for (i = 0; i < modulestab->hsize; i++) { for (dm = (Module)modulestab->nodes[i]; dm; dm = (Module)dm->node.next) { LinkNode dn; if (!dm->deps || !dm->u.handle) continue; for (dn = firstnode(dm->deps); dn; incnode(dn)) { if (!strcmp((char *) getdata(dn), mname)) { if (dm->node.flags & MOD_UNLOAD) del = 1; else { zwarnnam(nam, "module %s is in use by another module and cannot be unloaded", mname); return 1; } } } } } if (del) m->wrapper++; if (unload_module(m)) ret = 1; if (del) m->wrapper--; } else if (!silent) { zwarnnam(nam, "no such module %s", modname); ret = 1; } return ret; } /* zmodload -u without -d */ /**/ static int bin_zmodload_load(char *nam, char **args, Options ops) { int ret = 0; if(OPT_ISSET(ops,'u')) { /* unload modules */ for(; *args; args++) { if (unload_named_module(*args, nam, OPT_ISSET(ops,'i'))) ret = 1; } return ret; } else if(!*args) { /* list modules */ scanhashtable(modulestab, 1, 0, MOD_UNLOAD|MOD_ALIAS, modulestab->printnode, OPT_ISSET(ops,'L') ? PRINTMOD_LIST : 0); return 0; } else { /* load modules */ for (; *args; args++) { int tmpret = require_module(*args, NULL, OPT_ISSET(ops,'s')); if (tmpret && ret != 1) ret = tmpret; } return ret; } } /* zmodload -F */ /**/ static int bin_zmodload_features(const char *nam, char **args, Options ops) { int iarg; char *modname = *args; Patprog *patprogs; Feature_enables features, fep; if (modname) args++; else if (OPT_ISSET(ops,'L')) { int printflags = PRINTMOD_LIST|PRINTMOD_FEATURES; if (OPT_ISSET(ops,'P')) { zwarnnam(nam, "-P is only allowed with a module name"); return 1; } if (OPT_ISSET(ops,'l')) printflags |= PRINTMOD_LISTALL; if (OPT_ISSET(ops,'a')) printflags |= PRINTMOD_AUTO; scanhashtable(modulestab, 1, 0, MOD_ALIAS, modulestab->printnode, printflags); return 0; } if (!modname) { zwarnnam(nam, "-F requires a module name"); return 1; } if (OPT_ISSET(ops,'m')) { char **argp; Patprog *patprogp; /* not NULL terminated */ patprogp = patprogs = (Patprog *)zhalloc(arrlen(args)*sizeof(Patprog)); for (argp = args; *argp; argp++, patprogp++) { char *arg = *argp; if (*arg == '+' || *arg == '-') arg++; tokenize(arg); *patprogp = patcompile(arg, 0, 0); } } else patprogs = NULL; if (OPT_ISSET(ops,'l') || OPT_ISSET(ops,'L') || OPT_ISSET(ops,'e')) { /* * With option 'l', list all features one per line with + or -. * With option 'L', list as zmodload statement showing * only options turned on. * With both options, list as zmodload showing options * to be turned both on and off. */ Module m; char **features, **fp, **arrset = NULL, **arrp = NULL; int *enables = NULL, *ep; char *param = OPT_ARG_SAFE(ops,'P'); m = find_module(modname, FINDMOD_ALIASP, NULL); if (OPT_ISSET(ops,'a')) { LinkNode ln; /* * If there are no autoloads defined, return status 1. */ if (!m || !m->autoloads) return 1; if (OPT_ISSET(ops,'e')) { for (fp = args; *fp; fp++) { char *fstr = *fp; int sense = 1; if (*fstr == '+') fstr++; else if (*fstr == '-') { fstr++; sense = 0; } if ((linknodebystring(m->autoloads, fstr) != NULL) != sense) return 1; } return 0; } if (param) { arrp = arrset = (char **)zalloc(sizeof(char*) * (countlinknodes(m->autoloads)+1)); } else if (OPT_ISSET(ops,'L')) { printf("zmodload -aF %s%c", m->node.nam, m->autoloads && firstnode(m->autoloads) ? ' ' : '\n'); arrp = NULL; } for (ln = firstnode(m->autoloads); ln; incnode(ln)) { char *al = (char *)getdata(ln); if (param) *arrp++ = ztrdup(al); else printf("%s%c", al, OPT_ISSET(ops,'L') && nextnode(ln) ? ' ' : '\n'); } if (param) { *arrp = NULL; if (!setaparam(param, arrset)) return 1; } return 0; } if (!m || !m->u.handle || (m->node.flags & MOD_UNLOAD)) { if (!OPT_ISSET(ops,'e')) zwarnnam(nam, "module `%s' is not yet loaded", modname); return 1; } if (features_module(m, &features)) { if (!OPT_ISSET(ops,'e')) zwarnnam(nam, "module `%s' does not support features", m->node.nam); return 1; } if (enables_module(m, &enables)) { /* this shouldn't ever happen, so don't silence this error */ zwarnnam(nam, "error getting enabled features for module `%s'", m->node.nam); return 1; } for (arrp = args, iarg = 0; *arrp; arrp++, iarg++) { char *arg = *arrp; int on, found = 0; if (*arg == '-') { on = 0; arg++; } else if (*arg == '+') { on = 1; arg++; } else on = -1; for (fp = features, ep = enables; *fp; fp++, ep++) { if (patprogs ? pattry(patprogs[iarg], *fp) : !strcmp(arg, *fp)) { /* for -e, check given state, if any */ if (OPT_ISSET(ops,'e') && on != -1 && on != (*ep & 1)) return 1; found++; if (!patprogs) break; } } if (!found) { if (!OPT_ISSET(ops,'e')) zwarnnam(nam, patprogs ? "module `%s' has no feature matching: `%s'" : "module `%s' has no such feature: `%s'", modname, *arrp); return 1; } } if (OPT_ISSET(ops,'e')) /* yep, everything we want exists */ return 0; if (param) { int arrlen = 0; for (fp = features, ep = enables; *fp; fp++, ep++) { if (OPT_ISSET(ops, 'L') && !OPT_ISSET(ops, 'l') && !*ep) continue; if (*args) { char **argp; for (argp = args, iarg = 0; *argp; argp++, iarg++) { char *arg = *argp; /* ignore +/- for consistency */ if (*arg == '+' || *arg == '-') arg++; if (patprogs ? pattry(patprogs[iarg], *fp) : !strcmp(*fp, arg)) break; } if (!*argp) continue; } arrlen++; } arrp = arrset = zalloc(sizeof(char *) * (arrlen+1)); } else if (OPT_ISSET(ops, 'L')) printf("zmodload -F %s ", m->node.nam); for (fp = features, ep = enables; *fp; fp++, ep++) { char *onoff; int term; if (*args) { char **argp; for (argp = args, iarg = 0; *argp; argp++, iarg++) { char *arg = *argp; if (*arg == '+' || *arg == '-') arg++; if (patprogs ? pattry(patprogs[iarg], *fp) : !strcmp(*fp, *argp)) break; } if (!*argp) continue; } if (OPT_ISSET(ops, 'L') && !OPT_ISSET(ops, 'l')) { if (!*ep) continue; onoff = ""; } else if (*ep) { onoff = "+"; } else { onoff = "-"; } if (param) { *arrp++ = bicat(onoff, *fp); } else { if (OPT_ISSET(ops, 'L') && fp[1]) { term = ' '; } else { term = '\n'; } printf("%s%s%c", onoff, *fp, term); } } if (param) { *arrp = NULL; if (!setaparam(param, arrset)) return 1; } return 0; } else if (OPT_ISSET(ops,'P')) { zwarnnam(nam, "-P can only be used with -l or -L"); return 1; } else if (OPT_ISSET(ops,'a')) { if (OPT_ISSET(ops,'m')) { zwarnnam(nam, "-m cannot be used with -a"); return 1; } /* * With zmodload -aF, we always use the effect of -i. * The thinking is that marking a feature for * autoload is separate from enabling or disabling it. * Arguably we could do this with the zmodload -ab method * but I've kept it there for old time's sake. * The decoupling has meant FEAT_IGNORE/-i also * suppresses an error for attempting to remove an * autoload when the feature is enabled, which used * to be a hard error before. */ return autofeatures(nam, modname, args, 0, FEAT_IGNORE); } fep = features = (Feature_enables)zhalloc((arrlen(args)+1)*sizeof(*fep)); while (*args) { fep->str = *args++; fep->pat = patprogs ? *patprogs++ : NULL; fep++; } fep->str = NULL; fep->pat = NULL; return require_module(modname, features, OPT_ISSET(ops,'s')); } /************************************************************************ * Generic feature support. * These functions are designed to be called by modules. ************************************************************************/ /* * Construct a features array out of the list of concrete * features given, leaving space for any abstract features * to be added by the module itself. * * Note the memory is from the heap. */ /**/ mod_export char ** featuresarray(UNUSED(Module m), Features f) { int bn_size = f->bn_size, cd_size = f->cd_size; int mf_size = f->mf_size, pd_size = f->pd_size; int features_size = bn_size + cd_size + pd_size + mf_size + f->n_abstract; Builtin bnp = f->bn_list; Conddef cdp = f->cd_list; MathFunc mfp = f->mf_list; Paramdef pdp = f->pd_list; char **features = (char **)zhalloc((features_size + 1) * sizeof(char *)); char **featurep = features; while (bn_size--) *featurep++ = dyncat("b:", (bnp++)->node.nam); while (cd_size--) { *featurep++ = dyncat((cdp->flags & CONDF_INFIX) ? "C:" : "c:", cdp->name); cdp++; } while (mf_size--) *featurep++ = dyncat("f:", (mfp++)->name); while (pd_size--) *featurep++ = dyncat("p:", (pdp++)->name); features[features_size] = NULL; return features; } /* * Return the current set of enables for the features in a * module using heap memory. Leave space for abstract * features. The array is not zero terminated. */ /**/ mod_export int * getfeatureenables(UNUSED(Module m), Features f) { int bn_size = f->bn_size, cd_size = f->cd_size; int mf_size = f->mf_size, pd_size = f->pd_size; int features_size = bn_size + cd_size + mf_size + pd_size + f->n_abstract; Builtin bnp = f->bn_list; Conddef cdp = f->cd_list; MathFunc mfp = f->mf_list; Paramdef pdp = f->pd_list; int *enables = zhalloc(sizeof(int) * features_size); int *enablep = enables; while (bn_size--) *enablep++ = ((bnp++)->node.flags & BINF_ADDED) ? 1 : 0; while (cd_size--) *enablep++ = ((cdp++)->flags & CONDF_ADDED) ? 1 : 0; while (mf_size--) *enablep++ = ((mfp++)->flags & MFF_ADDED) ? 1 : 0; while (pd_size--) *enablep++ = (pdp++)->pm ? 1 : 0; return enables; } /* * Add or remove the concrete features passed in arguments, * depending on the corresponding element of the array e. * If e is NULL, disable everything. * Return 0 for success, 1 for failure; does not attempt * to imitate the return values of addbuiltins() etc. * Any failure in adding a requested feature is an * error. */ /**/ mod_export int setfeatureenables(Module m, Features f, int *e) { int ret = 0; if (f->bn_size) { if (setbuiltins(m->node.nam, f->bn_list, f->bn_size, e)) ret = 1; if (e) e += f->bn_size; } if (f->cd_size) { if (setconddefs(m->node.nam, f->cd_list, f->cd_size, e)) ret = 1; if (e) e += f->cd_size; } if (f->mf_size) { if (setmathfuncs(m->node.nam, f->mf_list, f->mf_size, e)) ret = 1; if (e) e += f->mf_size; } if (f->pd_size) { if (setparamdefs(m->node.nam, f->pd_list, f->pd_size, e)) ret = 1; if (e) e += f->pd_size; } return ret; } /* * Convenient front-end to get or set features which * can be used in a module enables_() function. */ /**/ mod_export int handlefeatures(Module m, Features f, int **enables) { if (!enables || *enables) return setfeatureenables(m, f, enables ? *enables : NULL); *enables = getfeatureenables(m, f); return 0; } /* * Ensure module "modname" is providing feature with "prefix" * and "feature" (e.g. "b:", "limit"). If feature is NULL, * ensure all features are loaded (used for compatibility * with the pre-feature autoloading behaviour). * * This will usually be called from the main shell to handle * loading of an autoloadable feature. * * Returns 0 on success, 1 for error in module, 2 for error * setting the feature. However, this isn't actually all * that useful for testing immediately on an autoload since * it could be a failure to autoload a different feature * from the one we want. We could fix this but it's * possible to test other ways. */ /**/ mod_export int ensurefeature(const char *modname, const char *prefix, const char *feature) { char *f; struct feature_enables features[2]; if (!feature) return require_module(modname, NULL, 0); f = dyncat(prefix, feature); features[0].str = f; features[0].pat = NULL; features[1].str = NULL; features[1].pat = NULL; return require_module(modname, features, 0); } /* * Add autoloadable features for a given module. */ /**/ int autofeatures(const char *cmdnam, const char *module, char **features, int prefchar, int defflags) { int ret = 0, subret; Module defm, m; char **modfeatures = NULL; int *modenables = NULL; if (module) { defm = (Module)find_module(module, FINDMOD_ALIASP|FINDMOD_CREATE, NULL); if ((defm->node.flags & MOD_LINKED) ? defm->u.linked : defm->u.handle) { (void)features_module(defm, &modfeatures); (void)enables_module(defm, &modenables); } } else defm = NULL; for (; *features; features++) { char *fnam, *typnam, *feature; int add, fchar, flags = defflags; autofeaturefn_t fn; if (prefchar) { /* * "features" is list of bare features with no * type prefix; prefchar gives type character. */ add = 1; /* unless overridden by flag */ fchar = prefchar; fnam = *features; feature = zhalloc(strlen(fnam) + 3); sprintf(feature, "%c:%s", fchar, fnam); } else { feature = *features; if (*feature == '-') { add = 0; feature++; } else { add = 1; if (*feature == '+') feature++; } if (!*feature || feature[1] != ':') { zwarnnam(cmdnam, "bad format for autoloadable feature: `%s'", feature); ret = 1; continue; } fnam = feature + 2; fchar = feature[0]; } if (flags & FEAT_REMOVE) add = 0; switch (fchar) { case 'b': fn = add ? add_autobin : del_autobin; typnam = "builtin"; break; case 'C': flags |= FEAT_INFIX; /* FALLTHROUGH */ case 'c': fn = add ? add_autocond : del_autocond; typnam = "condition"; break; case 'f': fn = add ? add_automathfunc : del_automathfunc; typnam = "math function"; break; case 'p': fn = add ? add_autoparam : del_autoparam; typnam = "parameter"; break; default: zwarnnam(cmdnam, "bad autoloadable feature type: `%c'", fchar); ret = 1; continue; } if (strchr(fnam, '/')) { zwarnnam(cmdnam, "%s: `/' is illegal in a %s", fnam, typnam); ret = 1; continue; } if (!module) { /* * Traditional un-autoload syntax doesn't tell us * which module this came from. */ int i; for (i = 0, m = NULL; !m && i < modulestab->hsize; i++) { for (m = (Module)modulestab->nodes[i]; m; m = (Module)m->node.next) { if (m->autoloads && linknodebystring(m->autoloads, feature)) break; } } if (!m) { if (!(flags & FEAT_IGNORE)) { ret = 1; zwarnnam(cmdnam, "%s: no such %s", fnam, typnam); } continue; } } else m = defm; subret = 0; if (add) { char **ptr; if (modfeatures) { /* * If the module is already available, check that * it does in fact provide the necessary feature. */ for (ptr = modfeatures; *ptr; ptr++) if (!strcmp(*ptr, feature)) break; if (!*ptr) { zwarnnam(cmdnam, "module `%s' has no such feature: `%s'", m->node.nam, feature); ret = 1; continue; } /* * If the feature is already provided by the module, there's * nothing more to do. */ if (modenables[ptr-modfeatures]) continue; /* * Otherwise, marking it for autoload will do the * right thing when the feature is eventually used. */ } if (!m->autoloads) { m->autoloads = znewlinklist(); zaddlinknode(m->autoloads, ztrdup(feature)); } else { /* Insert in lexical order */ LinkNode ln, prev = (LinkNode)m->autoloads; while ((ln = nextnode(prev))) { int cmp = strcmp(feature, (char *)getdata(ln)); if (cmp == 0) { /* Already there. Never an error. */ break; } if (cmp < 0) { zinsertlinknode(m->autoloads, prev, ztrdup(feature)); break; } prev = ln; } if (!ln) zaddlinknode(m->autoloads, ztrdup(feature)); } } else if (m->autoloads) { LinkNode ln; if ((ln = linknodebystring(m->autoloads, feature))) zsfree((char *)remnode(m->autoloads, ln)); else { /* * With -i (or zmodload -Fa), removing an autoload * that's not there is not an error. */ subret = (flags & FEAT_IGNORE) ? -2 : 2; } } if (subret == 0) subret = fn(module, fnam, flags); if (subret != 0) { /* -2 indicates not an error, just skip running fn() */ if (subret != -2) ret = 1; switch (subret) { case 1: zwarnnam(cmdnam, "failed to add %s `%s'", typnam, fnam); break; case 2: zwarnnam(cmdnam, "%s: no such %s", fnam, typnam); break; case 3: zwarnnam(cmdnam, "%s: %s is already defined", fnam, typnam); break; default: /* no (further) message needed */ break; } } } return ret; }