/*1* tclLoad.c --2*3* This file provides the generic portion (those that are the same4* on all platforms) of Tcl's dynamic loading facilities.5*6* Copyright (c) 1995 Sun Microsystems, Inc.7*8* See the file "license.terms" for information on usage and redistribution9* of this file, and for a DISCLAIMER OF ALL WARRANTIES.10*11* SCCS: @(#) tclLoad.c 1.15 96/10/12 17:05:5812*/1314#include "tclInt.h"1516/*17* The following structure describes a package that has been loaded18* either dynamically (with the "load" command) or statically (as19* indicated by a call to Tcl_PackageLoaded). All such packages20* are linked together into a single list for the process. Packages21* are never unloaded, so these structures are never freed.22*/2324typedef struct LoadedPackage {25char *fileName; /* Name of the file from which the26* package was loaded. An empty string27* means the package is loaded statically.28* Malloc-ed. */29char *packageName; /* Name of package prefix for the package,30* properly capitalized (first letter UC,31* others LC), no "_", as in "Net".32* Malloc-ed. */33Tcl_PackageInitProc *initProc;34/* Initialization procedure to call to35* incorporate this package into a trusted36* interpreter. */37Tcl_PackageInitProc *safeInitProc;38/* Initialization procedure to call to39* incorporate this package into a safe40* interpreter (one that will execute41* untrusted scripts). NULL means the42* package can't be used in unsafe43* interpreters. */44struct LoadedPackage *nextPtr;45/* Next in list of all packages loaded into46* this application process. NULL means47* end of list. */48} LoadedPackage;4950static LoadedPackage *firstPackagePtr = NULL;51/* First in list of all packages loaded into52* this process. */5354/*55* The following structure represents a particular package that has56* been incorporated into a particular interpreter (by calling its57* initialization procedure). There is a list of these structures for58* each interpreter, with an AssocData value (key "load") for the59* interpreter that points to the first package (if any).60*/6162typedef struct InterpPackage {63LoadedPackage *pkgPtr; /* Points to detailed information about64* package. */65struct InterpPackage *nextPtr;66/* Next package in this interpreter, or67* NULL for end of list. */68} InterpPackage;6970/*71* Prototypes for procedures that are private to this file:72*/7374static void LoadCleanupProc _ANSI_ARGS_((ClientData clientData,75Tcl_Interp *interp));76static void LoadExitProc _ANSI_ARGS_((ClientData clientData));7778/*79*----------------------------------------------------------------------80*81* Tcl_LoadCmd --82*83* This procedure is invoked to process the "load" Tcl command.84* See the user documentation for details on what it does.85*86* Results:87* A standard Tcl result.88*89* Side effects:90* See the user documentation.91*92*----------------------------------------------------------------------93*/9495int96Tcl_LoadCmd(dummy, interp, argc, argv)97ClientData dummy; /* Not used. */98Tcl_Interp *interp; /* Current interpreter. */99int argc; /* Number of arguments. */100char **argv; /* Argument strings. */101{102Tcl_Interp *target;103LoadedPackage *pkgPtr, *defaultPtr;104Tcl_DString pkgName, initName, safeInitName, fileName;105Tcl_PackageInitProc *initProc, *safeInitProc;106InterpPackage *ipFirstPtr, *ipPtr;107int code, c, gotPkgName, namesMatch, filesMatch;108char *p, *fullFileName, *p1, *p2;109110if ((argc < 2) || (argc > 4)) {111Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],112" fileName ?packageName? ?interp?\"", (char *) NULL);113return TCL_ERROR;114}115fullFileName = Tcl_TranslateFileName(interp, argv[1], &fileName);116if (fullFileName == NULL) {117return TCL_ERROR;118}119Tcl_DStringInit(&pkgName);120Tcl_DStringInit(&initName);121Tcl_DStringInit(&safeInitName);122if ((argc >= 3) && (argv[2][0] != 0)) {123gotPkgName = 1;124} else {125gotPkgName = 0;126}127if ((fullFileName[0] == 0) && !gotPkgName) {128interp->result = "must specify either file name or package name";129code = TCL_ERROR;130goto done;131}132133/*134* Figure out which interpreter we're going to load the package into.135*/136137target = interp;138if (argc == 4) {139target = Tcl_GetSlave(interp, argv[3]);140if (target == NULL) {141Tcl_AppendResult(interp, "couldn't find slave interpreter named \"",142argv[3], "\"", (char *) NULL);143return TCL_ERROR;144}145}146147/*148* Scan through the packages that are currently loaded to see if the149* package we want is already loaded. We'll use a loaded package if150* it meets any of the following conditions:151* - Its name and file match the once we're looking for.152* - Its file matches, and we weren't given a name.153* - Its name matches, the file name was specified as empty, and there154* is only no statically loaded package with the same name.155*/156157defaultPtr = NULL;158for (pkgPtr = firstPackagePtr; pkgPtr != NULL; pkgPtr = pkgPtr->nextPtr) {159if (!gotPkgName) {160namesMatch = 0;161} else {162namesMatch = 1;163for (p1 = argv[2], p2 = pkgPtr->packageName; ; p1++, p2++) {164if ((isupper(UCHAR(*p1)) ? tolower(UCHAR(*p1)) : *p1)165!= (isupper(UCHAR(*p2)) ? tolower(UCHAR(*p2)) : *p2)) {166namesMatch = 0;167break;168}169if (*p1 == 0) {170break;171}172}173}174filesMatch = (strcmp(pkgPtr->fileName, fullFileName) == 0);175if (filesMatch && (namesMatch || !gotPkgName)) {176break;177}178if (namesMatch && (fullFileName[0] == 0)) {179defaultPtr = pkgPtr;180}181if (filesMatch && !namesMatch && (fullFileName[0] != 0)) {182/*183* Can't have two different packages loaded from the same184* file.185*/186187Tcl_AppendResult(interp, "file \"", fullFileName,188"\" is already loaded for package \"",189pkgPtr->packageName, "\"", (char *) NULL);190code = TCL_ERROR;191goto done;192}193}194if (pkgPtr == NULL) {195pkgPtr = defaultPtr;196}197198/*199* Scan through the list of packages already loaded in the target200* interpreter. If the package we want is already loaded there,201* then there's nothing for us to to.202*/203204if (pkgPtr != NULL) {205ipFirstPtr = (InterpPackage *) Tcl_GetAssocData(target, "tclLoad",206(Tcl_InterpDeleteProc **) NULL);207for (ipPtr = ipFirstPtr; ipPtr != NULL; ipPtr = ipPtr->nextPtr) {208if (ipPtr->pkgPtr == pkgPtr) {209code = TCL_OK;210goto done;211}212}213}214215if (pkgPtr == NULL) {216/*217* The desired file isn't currently loaded, so load it. It's an218* error if the desired package is a static one.219*/220221if (fullFileName[0] == 0) {222Tcl_AppendResult(interp, "package \"", argv[2],223"\" isn't loaded statically", (char *) NULL);224code = TCL_ERROR;225goto done;226}227228/*229* Figure out the module name if it wasn't provided explicitly.230*/231232if (gotPkgName) {233Tcl_DStringAppend(&pkgName, argv[2], -1);234} else {235if (!TclGuessPackageName(fullFileName, &pkgName)) {236int pargc;237char **pargv, *pkgGuess;238239/*240* The platform-specific code couldn't figure out the241* module name. Make a guess by taking the last element242* of the file name, stripping off any leading "lib",243* and then using all of the alphabetic and underline244* characters that follow that.245*/246247Tcl_SplitPath(fullFileName, &pargc, &pargv);248pkgGuess = pargv[pargc-1];249if ((pkgGuess[0] == 'l') && (pkgGuess[1] == 'i')250&& (pkgGuess[2] == 'b')) {251pkgGuess += 3;252}253for (p = pkgGuess; isalpha(UCHAR(*p)) || (*p == '_'); p++) {254/* Empty loop body. */255}256if (p == pkgGuess) {257ckfree((char *)pargv);258Tcl_AppendResult(interp,259"couldn't figure out package name for ",260fullFileName, (char *) NULL);261code = TCL_ERROR;262goto done;263}264Tcl_DStringAppend(&pkgName, pkgGuess, (p - pkgGuess));265ckfree((char *)pargv);266}267}268269/*270* Fix the capitalization in the package name so that the first271* character is in caps but the others are all lower-case.272*/273274p = Tcl_DStringValue(&pkgName);275c = UCHAR(*p);276if (c != 0) {277if (islower(c)) {278*p = (char) toupper(c);279}280p++;281while (1) {282c = UCHAR(*p);283if (c == 0) {284break;285}286if (isupper(c)) {287*p = (char) tolower(c);288}289p++;290}291}292293/*294* Compute the names of the two initialization procedures,295* based on the package name.296*/297298Tcl_DStringAppend(&initName, Tcl_DStringValue(&pkgName), -1);299Tcl_DStringAppend(&initName, "_Init", 5);300Tcl_DStringAppend(&safeInitName, Tcl_DStringValue(&pkgName), -1);301Tcl_DStringAppend(&safeInitName, "_SafeInit", 9);302303/*304* Call platform-specific code to load the package and find the305* two initialization procedures.306*/307308code = TclLoadFile(interp, fullFileName, Tcl_DStringValue(&initName),309Tcl_DStringValue(&safeInitName), &initProc, &safeInitProc);310if (code != TCL_OK) {311goto done;312}313if (initProc == NULL) {314Tcl_AppendResult(interp, "couldn't find procedure ",315Tcl_DStringValue(&initName), (char *) NULL);316code = TCL_ERROR;317goto done;318}319320/*321* Create a new record to describe this package.322*/323324if (firstPackagePtr == NULL) {325Tcl_CreateExitHandler(LoadExitProc, (ClientData) NULL);326}327pkgPtr = (LoadedPackage *) ckalloc(sizeof(LoadedPackage));328pkgPtr->fileName = (char *) ckalloc((unsigned)329(strlen(fullFileName) + 1));330strcpy(pkgPtr->fileName, fullFileName);331pkgPtr->packageName = (char *) ckalloc((unsigned)332(Tcl_DStringLength(&pkgName) + 1));333strcpy(pkgPtr->packageName, Tcl_DStringValue(&pkgName));334pkgPtr->initProc = initProc;335pkgPtr->safeInitProc = safeInitProc;336pkgPtr->nextPtr = firstPackagePtr;337firstPackagePtr = pkgPtr;338}339340/*341* Invoke the package's initialization procedure (either the342* normal one or the safe one, depending on whether or not the343* interpreter is safe).344*/345346if (Tcl_IsSafe(target)) {347if (pkgPtr->safeInitProc != NULL) {348code = (*pkgPtr->safeInitProc)(target);349} else {350Tcl_AppendResult(interp,351"can't use package in a safe interpreter: ",352"no ", pkgPtr->packageName, "_SafeInit procedure",353(char *) NULL);354code = TCL_ERROR;355goto done;356}357} else {358code = (*pkgPtr->initProc)(target);359}360if ((code == TCL_ERROR) && (target != interp)) {361/*362* An error occurred, so transfer error information from the363* destination interpreter back to our interpreter. Must clear364* interp's result before calling Tcl_AddErrorInfo, since365* Tcl_AddErrorInfo will store the interp's result in errorInfo366* before appending target's $errorInfo; we've already got367* everything we need in target's $errorInfo.368*/369370Tcl_ResetResult(interp);371Tcl_AddErrorInfo(interp, Tcl_GetVar2(target,372"errorInfo", (char *) NULL, TCL_GLOBAL_ONLY));373Tcl_SetVar2(interp, "errorCode", (char *) NULL,374Tcl_GetVar2(target, "errorCode", (char *) NULL,375TCL_GLOBAL_ONLY), TCL_GLOBAL_ONLY);376Tcl_SetResult(interp, target->result, TCL_VOLATILE);377}378379/*380* Record the fact that the package has been loaded in the381* target interpreter.382*/383384if (code == TCL_OK) {385/*386* Refetch ipFirstPtr: loading the package may have introduced387* additional static packages at the head of the linked list!388*/389390ipFirstPtr = (InterpPackage *) Tcl_GetAssocData(target, "tclLoad",391(Tcl_InterpDeleteProc **) NULL);392ipPtr = (InterpPackage *) ckalloc(sizeof(InterpPackage));393ipPtr->pkgPtr = pkgPtr;394ipPtr->nextPtr = ipFirstPtr;395Tcl_SetAssocData(target, "tclLoad", LoadCleanupProc,396(ClientData) ipPtr);397}398399done:400Tcl_DStringFree(&pkgName);401Tcl_DStringFree(&initName);402Tcl_DStringFree(&safeInitName);403Tcl_DStringFree(&fileName);404return code;405}406407/*408*----------------------------------------------------------------------409*410* Tcl_StaticPackage --411*412* This procedure is invoked to indicate that a particular413* package has been linked statically with an application.414*415* Results:416* None.417*418* Side effects:419* Once this procedure completes, the package becomes loadable420* via the "load" command with an empty file name.421*422*----------------------------------------------------------------------423*/424425void426Tcl_StaticPackage(interp, pkgName, initProc, safeInitProc)427Tcl_Interp *interp; /* If not NULL, it means that the428* package has already been loaded429* into the given interpreter by430* calling the appropriate init proc. */431char *pkgName; /* Name of package (must be properly432* capitalized: first letter upper433* case, others lower case). */434Tcl_PackageInitProc *initProc; /* Procedure to call to incorporate435* this package into a trusted436* interpreter. */437Tcl_PackageInitProc *safeInitProc; /* Procedure to call to incorporate438* this package into a safe interpreter439* (one that will execute untrusted440* scripts). NULL means the package441* can't be used in safe442* interpreters. */443{444LoadedPackage *pkgPtr;445InterpPackage *ipPtr, *ipFirstPtr;446447/*448* Check to see if someone else has already reported this package as449* statically loaded. If this call is redundant then just return.450*/451452for (pkgPtr = firstPackagePtr; pkgPtr != NULL; pkgPtr = pkgPtr->nextPtr) {453if ((pkgPtr->initProc == initProc)454&& (pkgPtr->safeInitProc == safeInitProc)455&& (strcmp(pkgPtr->packageName, pkgName) == 0)) {456return;457}458}459460if (firstPackagePtr == NULL) {461Tcl_CreateExitHandler(LoadExitProc, (ClientData) NULL);462}463pkgPtr = (LoadedPackage *) ckalloc(sizeof(LoadedPackage));464pkgPtr->fileName = (char *) ckalloc((unsigned) 1);465pkgPtr->fileName[0] = 0;466pkgPtr->packageName = (char *) ckalloc((unsigned)467(strlen(pkgName) + 1));468strcpy(pkgPtr->packageName, pkgName);469pkgPtr->initProc = initProc;470pkgPtr->safeInitProc = safeInitProc;471pkgPtr->nextPtr = firstPackagePtr;472firstPackagePtr = pkgPtr;473474if (interp != NULL) {475ipFirstPtr = (InterpPackage *) Tcl_GetAssocData(interp, "tclLoad",476(Tcl_InterpDeleteProc **) NULL);477ipPtr = (InterpPackage *) ckalloc(sizeof(InterpPackage));478ipPtr->pkgPtr = pkgPtr;479ipPtr->nextPtr = ipFirstPtr;480Tcl_SetAssocData(interp, "tclLoad", LoadCleanupProc,481(ClientData) ipPtr);482}483}484485/*486*----------------------------------------------------------------------487*488* TclGetLoadedPackages --489*490* This procedure returns information about all of the files491* that are loaded (either in a particular intepreter, or492* for all interpreters).493*494* Results:495* The return value is a standard Tcl completion code. If496* successful, a list of lists is placed in interp->result.497* Each sublist corresponds to one loaded file; its first498* element is the name of the file (or an empty string for499* something that's statically loaded) and the second element500* is the name of the package in that file.501*502* Side effects:503* None.504*505*----------------------------------------------------------------------506*/507508int509TclGetLoadedPackages(interp, targetName)510Tcl_Interp *interp; /* Interpreter in which to return511* information or error message. */512char *targetName; /* Name of target interpreter or NULL.513* If NULL, return info about all interps;514* otherwise, just return info about this515* interpreter. */516{517Tcl_Interp *target;518LoadedPackage *pkgPtr;519InterpPackage *ipPtr;520char *prefix;521522if (targetName == NULL) {523/*524* Return information about all of the available packages.525*/526527prefix = "{";528for (pkgPtr = firstPackagePtr; pkgPtr != NULL;529pkgPtr = pkgPtr->nextPtr) {530Tcl_AppendResult(interp, prefix, (char *) NULL);531Tcl_AppendElement(interp, pkgPtr->fileName);532Tcl_AppendElement(interp, pkgPtr->packageName);533Tcl_AppendResult(interp, "}", (char *) NULL);534prefix = " {";535}536return TCL_OK;537}538539/*540* Return information about only the packages that are loaded in541* a given interpreter.542*/543544target = Tcl_GetSlave(interp, targetName);545if (target == NULL) {546Tcl_AppendResult(interp, "couldn't find slave interpreter named \"",547targetName, "\"", (char *) NULL);548return TCL_ERROR;549}550ipPtr = (InterpPackage *) Tcl_GetAssocData(target, "tclLoad",551(Tcl_InterpDeleteProc **) NULL);552prefix = "{";553for ( ; ipPtr != NULL; ipPtr = ipPtr->nextPtr) {554pkgPtr = ipPtr->pkgPtr;555Tcl_AppendResult(interp, prefix, (char *) NULL);556Tcl_AppendElement(interp, pkgPtr->fileName);557Tcl_AppendElement(interp, pkgPtr->packageName);558Tcl_AppendResult(interp, "}", (char *) NULL);559prefix = " {";560}561return TCL_OK;562}563564/*565*----------------------------------------------------------------------566*567* LoadCleanupProc --568*569* This procedure is called to delete all of the InterpPackage570* structures for an interpreter when the interpreter is deleted.571* It gets invoked via the Tcl AssocData mechanism.572*573* Results:574* None.575*576* Side effects:577* Storage for all of the InterpPackage procedures for interp578* get deleted.579*580*----------------------------------------------------------------------581*/582583static void584LoadCleanupProc(clientData, interp)585ClientData clientData; /* Pointer to first InterpPackage structure586* for interp. */587Tcl_Interp *interp; /* Interpreter that is being deleted. */588{589InterpPackage *ipPtr, *nextPtr;590591ipPtr = (InterpPackage *) clientData;592while (ipPtr != NULL) {593nextPtr = ipPtr->nextPtr;594ckfree((char *) ipPtr);595ipPtr = nextPtr;596}597}598599/*600*----------------------------------------------------------------------601*602* LoadExitProc --603*604* This procedure is invoked just before the application exits.605* It frees all of the LoadedPackage structures.606*607* Results:608* None.609*610* Side effects:611* Memory is freed.612*613*----------------------------------------------------------------------614*/615616static void617LoadExitProc(clientData)618ClientData clientData; /* Not used. */619{620LoadedPackage *pkgPtr;621622while (firstPackagePtr != NULL) {623pkgPtr = firstPackagePtr;624firstPackagePtr = pkgPtr->nextPtr;625ckfree(pkgPtr->fileName);626ckfree(pkgPtr->packageName);627ckfree((char *) pkgPtr);628}629}630631632