mirror of
git://git.code.sf.net/p/zsh/code
synced 2025-01-01 05:16:05 +01:00
3639 lines
82 KiB
C
3639 lines
82 KiB
C
/*
|
|
* 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;
|
|
|
|
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;
|
|
}
|
|
|
|
pm = setsparam(dupstring(pnam), ztrdup(module));
|
|
|
|
pm->node.flags |= PM_AUTOLOAD;
|
|
if (flags & FEAT_AUTOALL)
|
|
pm->node.flags |= PM_AUTOALL;
|
|
unqueue_signals();
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* 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 <sys/ldr.h>
|
|
|
|
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 <dl.h>
|
|
# else
|
|
# include <dlfcn.h>
|
|
# endif
|
|
#else
|
|
# ifdef HAVE_DL_H
|
|
# include <dl.h>
|
|
# define RTLD_LAZY BIND_DEFERRED
|
|
# define RTLD_GLOBAL DYNAMIC_PATH
|
|
# else
|
|
# include <sys/types.h>
|
|
# include <nlist.h>
|
|
# include <link.h>
|
|
# 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,STOUC(*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;
|
|
}
|