/* * 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" /* The `zsh' 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 boot_zsh(Module m) { return 0; } /* 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. */ /**/ int addbuiltin(Builtin b) { Builtin bn = (Builtin) builtintab->getnode2(builtintab, b->nam); if (bn && (bn->flags & BINF_ADDED)) return 1; if (bn) builtintab->freenode(builtintab->removenode(builtintab, b->nam)); PERMALLOC { builtintab->addnode(builtintab, b->nam, b); } LASTALLOC; return 0; } /* 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 1 if all * * additions succeed, 2 if some succeed and some fail, and 0 if all (and * * at least 1) fail. The usual usage in a boot_*() function would be * * return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); */ /**/ int addbuiltins(char const *nam, Builtin binl, int size) { int hads = 0, hadf = 0, n; for(n = 0; n < size; n++) { Builtin b = &binl[n]; if(b->flags & BINF_ADDED) continue; if(addbuiltin(b)) { zwarnnam(nam, "name clash when adding builtin `%s'", b->nam, 0); hadf = 1; } else { b->flags |= BINF_ADDED; hads = 2; } } return hadf ? hads : 1; } #ifdef DYNAMIC /* $module_path ($MODULE_PATH) */ /**/ char **module_path; /* List of modules */ /**/ LinkList modules; /* 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. */ /**/ int add_autobin(char *nam, char *module) { Builtin bn = zcalloc(sizeof(*bn)); bn->nam = ztrdup(nam); bn->optstr = ztrdup(module); return addbuiltin(bn); } /* Remove the builtin added previously by addbuiltin(). Returns * * zero on succes and -1 if there is no builtin with that name. */ /**/ int deletebuiltin(char *nam) { Builtin bn; bn = (Builtin) builtintab->removenode(builtintab, nam); if (!bn) return -1; builtintab->freenode((HashNode)bn); return 0; } /* Delete multiple builtins. binl points to a table of `size' builtin * * structures. Those for which (.flags & BINF_ADDED) is true are to be * * deleted; that flag is cleared. If any fail, an error message is * * printed, using nam as the leading name. Returns 1 if all deletions * * succeed, 2 if some succeed and some fail, and 0 if all (and at least * * 1) fail. In normal use, from a cleanup_*() function, this return * * value would be ignored -- the only cause of failure would be that a * * wayward module had deleted our builtin without telling us. */ /**/ int deletebuiltins(char const *nam, Builtin binl, int size) { int hads = 0, hadf = 0, n; for(n = 0; n < size; n++) { Builtin b = &binl[n]; if(!(b->flags & BINF_ADDED)) continue; if(deletebuiltin(b->nam)) { zwarnnam(nam, "builtin `%s' already deleted", b->nam, 0); hadf = 1; } else hads = 2; b->flags &= ~BINF_ADDED; } return hadf ? hads : 1; } #ifdef HAVE_DLFCN_H # include #else # include # include # include #endif #ifndef RTLD_LAZY # define RTLD_LAZY 1 #endif #ifndef RTLD_GLOBAL # define RTLD_GLOBAL 0 #endif #ifndef HAVE_DLCLOSE # define dlclose(X) ((X), 0) #endif #ifdef DLSYM_NEEDS_UNDERSCORE # define STR_BOOT "_boot_" # define STR_BOOT_S "_boot_%s" # define STR_CLEANUP "_cleanup_" # define STR_CLEANUP_S "_cleanup_%s" #else /* !DLSYM_NEEDS_UNDERSCORE */ # define STR_BOOT "boot_" # define STR_BOOT_S "boot_%s" # define STR_CLEANUP "cleanup_" # define STR_CLEANUP_S "cleanup_%s" #endif /* !DLSYM_NEEDS_UNDERSCORE */ typedef int (*Module_func) _((Module)); /**/ static void * try_load_module(char const *name) { char buf[PATH_MAX + 1]; char **pp; void *ret = NULL; int l; if (strchr(name, '/')) { ret = dlopen(unmeta(name), RTLD_LAZY | RTLD_GLOBAL); if (ret || unset(PATHDIRS) || (*name == '/') || (*name == '.' && name[1] == '/') || (*name == '.' && name[1] == '.' && name[2] == '/')) return ret; } l = strlen(name) + 1; for (pp = module_path; !ret && *pp; pp++) { if (l + (**pp ? strlen(*pp) : 1) > PATH_MAX) continue; sprintf(buf, "%s/%s", **pp ? *pp : ".", name); ret = dlopen(unmeta(buf), RTLD_LAZY | RTLD_GLOBAL); } return ret; } /**/ static void * do_load_module(char const *name) { void *ret = NULL; char buf[PATH_MAX + 1]; if (strlen(name) + strlen(DL_EXT) < PATH_MAX) { sprintf(buf, "%s.%s", name, DL_EXT); ret = try_load_module(buf); } if (!ret) ret = try_load_module(name); if (!ret) { int waserr = errflag; zerr("failed to load module: %s", name, 0); errflag = waserr; } return ret; } /**/ static LinkNode find_module(const char *name) { Module m; LinkNode node; for (node = firstnode(modules); node; incnode(node)) { m = (Module) getdata(node); if (!strcmp(m->nam, name)) return node; } return NULL; } /**/ static int init_module(Module m) { char *s, *t; #ifndef DYNAMIC_NAME_CLASH_OK char buf[PATH_MAX + 1]; #endif Module_func fn; s = strrchr(m->nam, '/'); if (s) s = dupstring(++s); else s = m->nam; if ((t = strrchr(s, '.'))) *t = '\0'; #ifdef DYNAMIC_NAME_CLASH_OK fn = (Module_func) dlsym(m->handle, STR_BOOT); #else /* !DYNAMIC_NAME_CLASH_OK */ if (strlen(s) + 6 > PATH_MAX) return 1; sprintf(buf, STR_BOOT_S, s); fn = (Module_func) dlsym(m->handle, buf); #endif /* !DYNAMIC_NAME_CLASH_OK */ if(fn) return fn(m); zwarnnam(m->nam, "no boot function", NULL, 0); return 1; } /**/ Module load_module(char const *name) { Module m; void *handle; LinkNode node, n; if (!(node = find_module(name))) { if (!(handle = do_load_module(name))) return NULL; m = zcalloc(sizeof(*m)); m->nam = ztrdup(name); m->handle = handle; if (init_module(m)) { dlclose(handle); zsfree(m->nam); zfree(m, sizeof(*m)); return NULL; } PERMALLOC { addlinknode(modules, m); } LASTALLOC; return m; } m = (Module) getdata(node); if (m->handle) return m; if (m->flags & MOD_BUSY) { zerr("circular dependencies for module %s", name, 0); return NULL; } m->flags |= MOD_BUSY; for (n = firstnode(m->deps); n; incnode(n)) if (!load_module((char *) getdata(n))) { m->flags &= ~MOD_BUSY; return NULL; } m->flags &= ~MOD_BUSY; if (!(m->handle = do_load_module(name))) return NULL; if (init_module(m)) { dlclose(m->handle); m->handle = NULL; return NULL; } return m; } /**/ static int cleanup_module(Module m) { char *s, *t; #ifndef DYNAMIC_NAME_CLASH_OK char buf[PATH_MAX + 1]; #endif Module_func fn; s = strrchr(m->nam, '/'); if (s) s = dupstring(++s); else s = m->nam; if ((t = strrchr(s, '.'))) *t = '\0'; #ifdef DYNAMIC_NAME_CLASH_OK fn = (Module_func) dlsym(m->handle, STR_CLEANUP); #else /* !DYNAMIC_NAME_CLASH_OK */ if (strlen(s) + 9 > PATH_MAX) return 1; sprintf(buf, STR_CLEANUP_S, s); fn = (Module_func) dlsym(m->handle, buf); #endif /* !DYNAMIC_NAME_CLASH_OK */ if(fn) return fn(m); zwarnnam(m->nam, "no cleanup function", NULL, 0); return 1; } /**/ void add_dep(char *name, char *from) { LinkNode node; Module m; PERMALLOC { if (!(node = find_module(name))) { m = zcalloc(sizeof(*m)); m->nam = ztrdup(name); addlinknode(modules, m); } else m = (Module) getdata(node); if (!m->deps) m->deps = newlinklist(); for (node = firstnode(m->deps); node && strcmp((char *) getdata(node), from); incnode(node)); if (!node) addlinknode(m->deps, ztrdup(from)); } LASTALLOC; } /**/ static void autoloadscan(HashNode hn, int printflags) { Builtin bn = (Builtin) hn; if(bn->flags & BINF_ADDED) return; if(printflags & PRINT_LIST) { fputs("zmodload -a ", stdout); if(bn->optstr[0] == '-') fputs("-- ", stdout); quotedzputs(bn->optstr, stdout); if(strcmp(bn->nam, bn->optstr)) { putchar(' '); quotedzputs(bn->nam, stdout); } } else { nicezputs(bn->nam, stdout); if(strcmp(bn->nam, bn->optstr)) { fputs(" (", stdout); nicezputs(bn->optstr, stdout); putchar(')'); } } putchar('\n'); } /**/ int bin_zmodload(char *nam, char **args, char *ops, int func) { if(ops['d'] && ops['a']) { zwarnnam(nam, "-d cannot be combined with -a", NULL, 0); return 1; } if (ops['u'] && !*args) { zwarnnam(nam, "what do you want to unload?", NULL, 0); return 1; } if(ops['d']) return bin_zmodload_dep(nam, args, ops); else if(ops['a']) return bin_zmodload_auto(nam, args, ops); else return bin_zmodload_load(nam, args, ops); } /**/ static int bin_zmodload_dep(char *nam, char **args, char *ops) { LinkNode node; Module m; if(ops['u']) { /* remove dependencies */ char *tnam = *args++; node = find_module(tnam); if (!node) return 0; m = (Module) getdata(node); if(*args && m->deps) { do { for(node = firstnode(m->deps); node; incnode(node)) if(!strcmp(*args, getdata(node))) { zsfree(getdata(node)); remnode(m->deps, node); 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->handle) { remnode(modules, node); zsfree(m->nam); zfree(m, sizeof(*m)); } return 0; } else if(!args[0] || !args[1]) { /* list dependencies */ for (node = firstnode(modules); node; incnode(node)) { m = (Module) getdata(node); if (m->deps && (!args[0] || !strcmp(args[0], m->nam))) { LinkNode n; if(ops['L']) { printf("zmodload -d "); if(m->nam[0] == '-') fputs("-- ", stdout); quotedzputs(m->nam, stdout); } else { nicezputs(m->nam, stdout); putchar(':'); } for (n = firstnode(m->deps); n; incnode(n)) { putchar(' '); if(ops['L']) quotedzputs((char *) getdata(n), stdout); else nicezputs((char *) getdata(n), stdout); } putchar('\n'); } } return 0; } else { /* add dependencies */ int ret = 0; char *tnam = *args++; for(; *args; args++) { if(isset(RESTRICTED) && strchr(*args, '/')) { zwarnnam(nam, "%s: restricted", *args, 0); ret = 1; } else add_dep(tnam, *args); } return ret; } } /**/ static int bin_zmodload_auto(char *nam, char **args, char *ops) { int ret = 0; if(ops['u']) { /* remove autoloaded builtins */ for (; *args; args++) { Builtin bn = (Builtin) builtintab->getnode2(builtintab, *args); if (!bn) { if(!ops['i']) { zwarnnam(nam, "%s: no such builtin", *args, 0); ret = 1; } } else if (bn->flags & BINF_ADDED) { zwarnnam(nam, "%s: builtin is already defined", *args, 0); ret = 1; } else deletebuiltin(*args); } return ret; } else if(!*args) { /* list autoloaded builtins */ scanhashtable(builtintab, 0, 0, 0, autoloadscan, ops['L'] ? PRINT_LIST : 0); return 0; } else { /* add autoloaded builtins */ char *modnam; modnam = *args++; if(isset(RESTRICTED) && strchr(modnam, '/')) { zwarnnam(nam, "%s: restricted", modnam, 0); return 1; } do { char *bnam = *args ? *args++ : modnam; if (strchr(bnam, '/')) { zwarnnam(nam, "%s: `/' is illegal in a builtin", bnam, 0); ret = 1; } else if (add_autobin(bnam, modnam) && !ops['i']) { zwarnnam(nam, "failed to add builtin %s", bnam, 0); ret = 1; } } while(*args); return ret; } } /**/ static int bin_zmodload_load(char *nam, char **args, char *ops) { LinkNode node; Module m; int ret = 0; if(ops['u']) { /* unload modules */ for(; *args; args++) { node = find_module(*args); if (node) { LinkNode mn, dn; for (mn = firstnode(modules); mn; incnode(mn)) { m = (Module) getdata(mn); if (m->deps && m->handle) for (dn = firstnode(m->deps); dn; incnode(dn)) if (!strcmp((char *) getdata(dn), *args)) { zwarnnam(nam, "module %s is in use by another module and cannot be unloaded", *args, 0); ret = 1; goto cont; } } m = (Module) getdata(node); if (m->handle && cleanup_module(m)) ret = 1; else { if (m->handle) dlclose(m->handle); m->handle = NULL; if(!m->deps) { remnode(modules, node); zsfree(m->nam); zfree(m, sizeof(*m)); } } } else if (!ops['i']) { zwarnnam(nam, "no such module %s", *args, 0); ret = 1; } cont: ; } return ret; } else if(!*args) { /* list modules */ for (node = firstnode(modules); node; incnode(node)) { m = (Module) getdata(node); if (m->handle) { if(ops['L']) { printf("zmodload "); if(m->nam[0] == '-') fputs("-- ", stdout); quotedzputs(m->nam, stdout); } else nicezputs(m->nam, stdout); putchar('\n'); } } return 0; } else { /* load modules */ for (; *args; args++) { node = find_module(*args); if (node && ((Module) getdata(node))->handle) { if (!ops['i']) { zwarnnam(nam, "module %s already loaded.", *args, 0); ret = 1; } } else if (isset(RESTRICTED) && strchr(*args, '/')) { zwarnnam(nam, "%s: restricted", *args, 0); ret = 1; } else if (!load_module(*args)) ret = 1; } return ret; } } #endif /* DYNAMIC */