1/*
2 * tclLoad.c --
3 *
4 *	This file provides the generic portion (those that are the same
5 *	on all platforms) of Tcl's dynamic loading facilities.
6 *
7 * Copyright (c) 1995-1997 Sun Microsystems, Inc.
8 *
9 * See the file "license.terms" for information on usage and redistribution
10 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 *
12 * RCS: @(#) $Id: tclLoad.c,v 1.9 2003/02/01 23:37:29 kennykb Exp $
13 */
14
15#include "tclInt.h"
16
17/*
18 * The following structure describes a package that has been loaded
19 * either dynamically (with the "load" command) or statically (as
20 * indicated by a call to TclGetLoadedPackages).  All such packages
21 * are linked together into a single list for the process.  Packages
22 * are never unloaded, until the application exits, when
23 * TclFinalizeLoad is called, and these structures are freed.
24 */
25
26typedef struct LoadedPackage {
27    char *fileName;		/* Name of the file from which the
28				 * package was loaded.  An empty string
29				 * means the package is loaded statically.
30				 * Malloc-ed. */
31    char *packageName;		/* Name of package prefix for the package,
32				 * properly capitalized (first letter UC,
33				 * others LC), no "_", as in "Net".
34				 * Malloc-ed. */
35    Tcl_LoadHandle loadHandle;	/* Token for the loaded file which should be
36				 * passed to (*unLoadProcPtr)() when the file
37				 * is no longer needed.  If fileName is NULL,
38				 * then this field is irrelevant. */
39    Tcl_PackageInitProc *initProc;
40				/* Initialization procedure to call to
41				 * incorporate this package into a trusted
42				 * interpreter. */
43    Tcl_PackageInitProc *safeInitProc;
44				/* Initialization procedure to call to
45				 * incorporate this package into a safe
46				 * interpreter (one that will execute
47				 * untrusted scripts).   NULL means the
48				 * package can't be used in unsafe
49				 * interpreters. */
50    Tcl_FSUnloadFileProc *unLoadProcPtr;
51				/* Procedure to use to unload this package.
52				 * If NULL, then we do not attempt to unload
53				 * the package.  If fileName is NULL, then
54				 * this field is irrelevant. */
55    struct LoadedPackage *nextPtr;
56				/* Next in list of all packages loaded into
57				 * this application process.  NULL means
58				 * end of list. */
59} LoadedPackage;
60
61/*
62 * TCL_THREADS
63 * There is a global list of packages that is anchored at firstPackagePtr.
64 * Access to this list is governed by a mutex.
65 */
66
67static LoadedPackage *firstPackagePtr = NULL;
68				/* First in list of all packages loaded into
69				 * this process. */
70
71TCL_DECLARE_MUTEX(packageMutex)
72
73/*
74 * The following structure represents a particular package that has
75 * been incorporated into a particular interpreter (by calling its
76 * initialization procedure).  There is a list of these structures for
77 * each interpreter, with an AssocData value (key "load") for the
78 * interpreter that points to the first package (if any).
79 */
80
81typedef struct InterpPackage {
82    LoadedPackage *pkgPtr;	/* Points to detailed information about
83				 * package. */
84    struct InterpPackage *nextPtr;
85				/* Next package in this interpreter, or
86				 * NULL for end of list. */
87} InterpPackage;
88
89/*
90 * Prototypes for procedures that are private to this file:
91 */
92
93static void		LoadCleanupProc _ANSI_ARGS_((ClientData clientData,
94			    Tcl_Interp *interp));
95
96/*
97 *----------------------------------------------------------------------
98 *
99 * Tcl_LoadObjCmd --
100 *
101 *	This procedure is invoked to process the "load" Tcl command.
102 *	See the user documentation for details on what it does.
103 *
104 * Results:
105 *	A standard Tcl result.
106 *
107 * Side effects:
108 *	See the user documentation.
109 *
110 *----------------------------------------------------------------------
111 */
112
113int
114Tcl_LoadObjCmd(dummy, interp, objc, objv)
115    ClientData dummy;		/* Not used. */
116    Tcl_Interp *interp;		/* Current interpreter. */
117    int objc;			/* Number of arguments. */
118    Tcl_Obj *CONST objv[];	/* Argument objects. */
119{
120    Tcl_Interp *target;
121    LoadedPackage *pkgPtr, *defaultPtr;
122    Tcl_DString pkgName, tmp, initName, safeInitName;
123    Tcl_PackageInitProc *initProc, *safeInitProc;
124    InterpPackage *ipFirstPtr, *ipPtr;
125    int code, namesMatch, filesMatch;
126    char *p, *fullFileName, *packageName;
127    Tcl_LoadHandle loadHandle;
128    Tcl_FSUnloadFileProc *unLoadProcPtr = NULL;
129    Tcl_UniChar ch;
130    int offset;
131
132    if ((objc < 2) || (objc > 4)) {
133        Tcl_WrongNumArgs(interp, 1, objv, "fileName ?packageName? ?interp?");
134	return TCL_ERROR;
135    }
136    if (Tcl_FSConvertToPathType(interp, objv[1]) != TCL_OK) {
137	return TCL_ERROR;
138    }
139    fullFileName = Tcl_GetString(objv[1]);
140
141    Tcl_DStringInit(&pkgName);
142    Tcl_DStringInit(&initName);
143    Tcl_DStringInit(&safeInitName);
144    Tcl_DStringInit(&tmp);
145
146    packageName = NULL;
147    if (objc >= 3) {
148	packageName = Tcl_GetString(objv[2]);
149	if (packageName[0] == '\0') {
150	    packageName = NULL;
151	}
152    }
153    if ((fullFileName[0] == 0) && (packageName == NULL)) {
154	Tcl_SetResult(interp,
155		"must specify either file name or package name",
156		TCL_STATIC);
157	code = TCL_ERROR;
158	goto done;
159    }
160
161    /*
162     * Figure out which interpreter we're going to load the package into.
163     */
164
165    target = interp;
166    if (objc == 4) {
167	char *slaveIntName;
168	slaveIntName = Tcl_GetString(objv[3]);
169	target = Tcl_GetSlave(interp, slaveIntName);
170	if (target == NULL) {
171	    return TCL_ERROR;
172	}
173    }
174
175    /*
176     * Scan through the packages that are currently loaded to see if the
177     * package we want is already loaded.  We'll use a loaded package if
178     * it meets any of the following conditions:
179     *  - Its name and file match the once we're looking for.
180     *  - Its file matches, and we weren't given a name.
181     *  - Its name matches, the file name was specified as empty, and there
182     *    is only no statically loaded package with the same name.
183     */
184    Tcl_MutexLock(&packageMutex);
185
186    defaultPtr = NULL;
187    for (pkgPtr = firstPackagePtr; pkgPtr != NULL; pkgPtr = pkgPtr->nextPtr) {
188	if (packageName == NULL) {
189	    namesMatch = 0;
190	} else {
191	    Tcl_DStringSetLength(&pkgName, 0);
192	    Tcl_DStringAppend(&pkgName, packageName, -1);
193	    Tcl_DStringSetLength(&tmp, 0);
194	    Tcl_DStringAppend(&tmp, pkgPtr->packageName, -1);
195	    Tcl_UtfToLower(Tcl_DStringValue(&pkgName));
196	    Tcl_UtfToLower(Tcl_DStringValue(&tmp));
197	    if (strcmp(Tcl_DStringValue(&tmp),
198		    Tcl_DStringValue(&pkgName)) == 0) {
199		namesMatch = 1;
200	    } else {
201		namesMatch = 0;
202	    }
203	}
204	Tcl_DStringSetLength(&pkgName, 0);
205
206	filesMatch = (strcmp(pkgPtr->fileName, fullFileName) == 0);
207	if (filesMatch && (namesMatch || (packageName == NULL))) {
208	    break;
209	}
210	if (namesMatch && (fullFileName[0] == 0)) {
211	    defaultPtr = pkgPtr;
212	}
213	if (filesMatch && !namesMatch && (fullFileName[0] != 0)) {
214	    /*
215	     * Can't have two different packages loaded from the same
216	     * file.
217	     */
218
219	    Tcl_AppendResult(interp, "file \"", fullFileName,
220		    "\" is already loaded for package \"",
221		    pkgPtr->packageName, "\"", (char *) NULL);
222	    code = TCL_ERROR;
223	    Tcl_MutexUnlock(&packageMutex);
224	    goto done;
225	}
226    }
227    Tcl_MutexUnlock(&packageMutex);
228    if (pkgPtr == NULL) {
229	pkgPtr = defaultPtr;
230    }
231
232    /*
233     * Scan through the list of packages already loaded in the target
234     * interpreter.  If the package we want is already loaded there,
235     * then there's nothing for us to to.
236     */
237
238    if (pkgPtr != NULL) {
239	ipFirstPtr = (InterpPackage *) Tcl_GetAssocData(target, "tclLoad",
240		(Tcl_InterpDeleteProc **) NULL);
241	for (ipPtr = ipFirstPtr; ipPtr != NULL; ipPtr = ipPtr->nextPtr) {
242	    if (ipPtr->pkgPtr == pkgPtr) {
243		code = TCL_OK;
244		goto done;
245	    }
246	}
247    }
248
249    if (pkgPtr == NULL) {
250	/*
251	 * The desired file isn't currently loaded, so load it.  It's an
252	 * error if the desired package is a static one.
253	 */
254
255	if (fullFileName[0] == 0) {
256	    Tcl_AppendResult(interp, "package \"", packageName,
257		    "\" isn't loaded statically", (char *) NULL);
258	    code = TCL_ERROR;
259	    goto done;
260	}
261
262	/*
263	 * Figure out the module name if it wasn't provided explicitly.
264	 */
265
266	if (packageName != NULL) {
267	    Tcl_DStringAppend(&pkgName, packageName, -1);
268	} else {
269	    int retc;
270	    /*
271	     * Threading note - this call used to be protected by a mutex.
272	     */
273	    retc = TclGuessPackageName(fullFileName, &pkgName);
274	    if (!retc) {
275		Tcl_Obj *splitPtr;
276		Tcl_Obj *pkgGuessPtr;
277		int pElements;
278		char *pkgGuess;
279
280		/*
281		 * The platform-specific code couldn't figure out the
282		 * module name.  Make a guess by taking the last element
283		 * of the file name, stripping off any leading "lib",
284		 * and then using all of the alphabetic and underline
285		 * characters that follow that.
286		 */
287
288		splitPtr = Tcl_FSSplitPath(objv[1], &pElements);
289		Tcl_ListObjIndex(NULL, splitPtr, pElements -1, &pkgGuessPtr);
290		pkgGuess = Tcl_GetString(pkgGuessPtr);
291		if ((pkgGuess[0] == 'l') && (pkgGuess[1] == 'i')
292			&& (pkgGuess[2] == 'b')) {
293		    pkgGuess += 3;
294		}
295		for (p = pkgGuess; *p != 0; p += offset) {
296		    offset = Tcl_UtfToUniChar(p, &ch);
297		    if ((ch > 0x100)
298			    || !(isalpha(UCHAR(ch)) /* INTL: ISO only */
299				    || (UCHAR(ch) == '_'))) {
300			break;
301		    }
302		}
303		if (p == pkgGuess) {
304		    Tcl_DecrRefCount(splitPtr);
305		    Tcl_AppendResult(interp,
306			    "couldn't figure out package name for ",
307			    fullFileName, (char *) NULL);
308		    code = TCL_ERROR;
309		    goto done;
310		}
311		Tcl_DStringAppend(&pkgName, pkgGuess, (p - pkgGuess));
312		Tcl_DecrRefCount(splitPtr);
313	    }
314	}
315
316	/*
317	 * Fix the capitalization in the package name so that the first
318	 * character is in caps (or title case) but the others are all
319	 * lower-case.
320	 */
321
322	Tcl_DStringSetLength(&pkgName,
323		Tcl_UtfToTitle(Tcl_DStringValue(&pkgName)));
324
325	/*
326	 * Compute the names of the two initialization procedures,
327	 * based on the package name.
328	 */
329
330	Tcl_DStringAppend(&initName, Tcl_DStringValue(&pkgName), -1);
331	Tcl_DStringAppend(&initName, "_Init", 5);
332	Tcl_DStringAppend(&safeInitName, Tcl_DStringValue(&pkgName), -1);
333	Tcl_DStringAppend(&safeInitName, "_SafeInit", 9);
334
335	/*
336	 * Call platform-specific code to load the package and find the
337	 * two initialization procedures.
338	 */
339
340	Tcl_MutexLock(&packageMutex);
341	code = Tcl_FSLoadFile(interp, objv[1], Tcl_DStringValue(&initName),
342		Tcl_DStringValue(&safeInitName), &initProc, &safeInitProc,
343		&loadHandle,&unLoadProcPtr);
344	Tcl_MutexUnlock(&packageMutex);
345	if (code != TCL_OK) {
346	    goto done;
347	}
348	if (initProc == NULL) {
349	    Tcl_AppendResult(interp, "couldn't find procedure ",
350		    Tcl_DStringValue(&initName), (char *) NULL);
351	    if (unLoadProcPtr != NULL) {
352		(*unLoadProcPtr)(loadHandle);
353	    }
354	    code = TCL_ERROR;
355	    goto done;
356	}
357
358	/*
359	 * Create a new record to describe this package.
360	 */
361
362	pkgPtr = (LoadedPackage *) ckalloc(sizeof(LoadedPackage));
363	pkgPtr->fileName	= (char *) ckalloc((unsigned)
364		(strlen(fullFileName) + 1));
365	strcpy(pkgPtr->fileName, fullFileName);
366	pkgPtr->packageName	= (char *) ckalloc((unsigned)
367		(Tcl_DStringLength(&pkgName) + 1));
368	strcpy(pkgPtr->packageName, Tcl_DStringValue(&pkgName));
369	pkgPtr->loadHandle	= loadHandle;
370	pkgPtr->unLoadProcPtr	= unLoadProcPtr;
371	pkgPtr->initProc	= initProc;
372	pkgPtr->safeInitProc	= safeInitProc;
373	Tcl_MutexLock(&packageMutex);
374	pkgPtr->nextPtr		= firstPackagePtr;
375	firstPackagePtr		= pkgPtr;
376	Tcl_MutexUnlock(&packageMutex);
377    }
378
379    /*
380     * Invoke the package's initialization procedure (either the
381     * normal one or the safe one, depending on whether or not the
382     * interpreter is safe).
383     */
384
385    if (Tcl_IsSafe(target)) {
386	if (pkgPtr->safeInitProc != NULL) {
387	    code = (*pkgPtr->safeInitProc)(target);
388	} else {
389	    Tcl_AppendResult(interp,
390		    "can't use package in a safe interpreter: ",
391		    "no ", pkgPtr->packageName, "_SafeInit procedure",
392		    (char *) NULL);
393	    code = TCL_ERROR;
394	    goto done;
395	}
396    } else {
397	code = (*pkgPtr->initProc)(target);
398    }
399
400    /*
401     * Record the fact that the package has been loaded in the
402     * target interpreter.
403     */
404
405    if (code == TCL_OK) {
406	/*
407	 * Refetch ipFirstPtr: loading the package may have introduced
408	 * additional static packages at the head of the linked list!
409	 */
410
411	ipFirstPtr = (InterpPackage *) Tcl_GetAssocData(target, "tclLoad",
412		(Tcl_InterpDeleteProc **) NULL);
413	ipPtr = (InterpPackage *) ckalloc(sizeof(InterpPackage));
414	ipPtr->pkgPtr = pkgPtr;
415	ipPtr->nextPtr = ipFirstPtr;
416	Tcl_SetAssocData(target, "tclLoad", LoadCleanupProc,
417		(ClientData) ipPtr);
418    } else {
419	TclTransferResult(target, code, interp);
420    }
421
422    done:
423    Tcl_DStringFree(&pkgName);
424    Tcl_DStringFree(&initName);
425    Tcl_DStringFree(&safeInitName);
426    Tcl_DStringFree(&tmp);
427    return code;
428}
429
430/*
431 *----------------------------------------------------------------------
432 *
433 * Tcl_StaticPackage --
434 *
435 *	This procedure is invoked to indicate that a particular
436 *	package has been linked statically with an application.
437 *
438 * Results:
439 *	None.
440 *
441 * Side effects:
442 *	Once this procedure completes, the package becomes loadable
443 *	via the "load" command with an empty file name.
444 *
445 *----------------------------------------------------------------------
446 */
447
448void
449Tcl_StaticPackage(interp, pkgName, initProc, safeInitProc)
450    Tcl_Interp *interp;			/* If not NULL, it means that the
451					 * package has already been loaded
452					 * into the given interpreter by
453					 * calling the appropriate init proc. */
454    CONST char *pkgName;		/* Name of package (must be properly
455					 * capitalized: first letter upper
456					 * case, others lower case). */
457    Tcl_PackageInitProc *initProc;	/* Procedure to call to incorporate
458					 * this package into a trusted
459					 * interpreter. */
460    Tcl_PackageInitProc *safeInitProc;	/* Procedure to call to incorporate
461					 * this package into a safe interpreter
462					 * (one that will execute untrusted
463					 * scripts).   NULL means the package
464					 * can't be used in safe
465					 * interpreters. */
466{
467    LoadedPackage *pkgPtr;
468    InterpPackage *ipPtr, *ipFirstPtr;
469
470    /*
471     * Check to see if someone else has already reported this package as
472     * statically loaded in the process.
473     */
474
475    Tcl_MutexLock(&packageMutex);
476    for (pkgPtr = firstPackagePtr; pkgPtr != NULL; pkgPtr = pkgPtr->nextPtr) {
477	if ((pkgPtr->initProc == initProc)
478		&& (pkgPtr->safeInitProc == safeInitProc)
479		&& (strcmp(pkgPtr->packageName, pkgName) == 0)) {
480	    break;
481	}
482    }
483    Tcl_MutexUnlock(&packageMutex);
484
485    /*
486     * If the package is not yet recorded as being loaded statically,
487     * add it to the list now.
488     */
489
490    if ( pkgPtr == NULL ) {
491	pkgPtr = (LoadedPackage *) ckalloc(sizeof(LoadedPackage));
492	pkgPtr->fileName	= (char *) ckalloc((unsigned) 1);
493	pkgPtr->fileName[0]	= 0;
494	pkgPtr->packageName	= (char *) ckalloc((unsigned)
495						   (strlen(pkgName) + 1));
496	strcpy(pkgPtr->packageName, pkgName);
497	pkgPtr->loadHandle	= NULL;
498	pkgPtr->initProc	= initProc;
499	pkgPtr->safeInitProc	= safeInitProc;
500	Tcl_MutexLock(&packageMutex);
501	pkgPtr->nextPtr		= firstPackagePtr;
502	firstPackagePtr		= pkgPtr;
503	Tcl_MutexUnlock(&packageMutex);
504    }
505
506    if (interp != NULL) {
507
508	/*
509	 * If we're loading the package into an interpreter,
510	 * determine whether it's already loaded.
511	 */
512
513	ipFirstPtr = (InterpPackage *) Tcl_GetAssocData(interp, "tclLoad",
514		(Tcl_InterpDeleteProc **) NULL);
515	for ( ipPtr = ipFirstPtr; ipPtr != NULL; ipPtr = ipPtr->nextPtr ) {
516	    if ( ipPtr->pkgPtr == pkgPtr ) {
517		return;
518	    }
519	}
520
521	/*
522	 * Package isn't loade in the current interp yet. Mark it as
523	 * now being loaded.
524	 */
525
526	ipPtr = (InterpPackage *) ckalloc(sizeof(InterpPackage));
527	ipPtr->pkgPtr = pkgPtr;
528	ipPtr->nextPtr = ipFirstPtr;
529	Tcl_SetAssocData(interp, "tclLoad", LoadCleanupProc,
530		(ClientData) ipPtr);
531    }
532}
533
534/*
535 *----------------------------------------------------------------------
536 *
537 * TclGetLoadedPackages --
538 *
539 *	This procedure returns information about all of the files
540 *	that are loaded (either in a particular intepreter, or
541 *	for all interpreters).
542 *
543 * Results:
544 *	The return value is a standard Tcl completion code.  If
545 *	successful, a list of lists is placed in the interp's result.
546 *	Each sublist corresponds to one loaded file;  its first
547 *	element is the name of the file (or an empty string for
548 *	something that's statically loaded) and the second element
549 *	is the name of the package in that file.
550 *
551 * Side effects:
552 *	None.
553 *
554 *----------------------------------------------------------------------
555 */
556
557int
558TclGetLoadedPackages(interp, targetName)
559    Tcl_Interp *interp;		/* Interpreter in which to return
560				 * information or error message. */
561    char *targetName;		/* Name of target interpreter or NULL.
562				 * If NULL, return info about all interps;
563				 * otherwise, just return info about this
564				 * interpreter. */
565{
566    Tcl_Interp *target;
567    LoadedPackage *pkgPtr;
568    InterpPackage *ipPtr;
569    char *prefix;
570
571    if (targetName == NULL) {
572	/*
573	 * Return information about all of the available packages.
574	 */
575
576	prefix = "{";
577	Tcl_MutexLock(&packageMutex);
578	for (pkgPtr = firstPackagePtr; pkgPtr != NULL;
579		pkgPtr = pkgPtr->nextPtr) {
580	    Tcl_AppendResult(interp, prefix, (char *) NULL);
581	    Tcl_AppendElement(interp, pkgPtr->fileName);
582	    Tcl_AppendElement(interp, pkgPtr->packageName);
583	    Tcl_AppendResult(interp, "}", (char *) NULL);
584	    prefix = " {";
585	}
586	Tcl_MutexUnlock(&packageMutex);
587	return TCL_OK;
588    }
589
590    /*
591     * Return information about only the packages that are loaded in
592     * a given interpreter.
593     */
594
595    target = Tcl_GetSlave(interp, targetName);
596    if (target == NULL) {
597	return TCL_ERROR;
598    }
599    ipPtr = (InterpPackage *) Tcl_GetAssocData(target, "tclLoad",
600	    (Tcl_InterpDeleteProc **) NULL);
601    prefix = "{";
602    for ( ; ipPtr != NULL; ipPtr = ipPtr->nextPtr) {
603	pkgPtr = ipPtr->pkgPtr;
604	Tcl_AppendResult(interp, prefix, (char *) NULL);
605	Tcl_AppendElement(interp, pkgPtr->fileName);
606	Tcl_AppendElement(interp, pkgPtr->packageName);
607	Tcl_AppendResult(interp, "}", (char *) NULL);
608	prefix = " {";
609    }
610    return TCL_OK;
611}
612
613/*
614 *----------------------------------------------------------------------
615 *
616 * LoadCleanupProc --
617 *
618 *	This procedure is called to delete all of the InterpPackage
619 *	structures for an interpreter when the interpreter is deleted.
620 *	It gets invoked via the Tcl AssocData mechanism.
621 *
622 * Results:
623 *	None.
624 *
625 * Side effects:
626 *	Storage for all of the InterpPackage procedures for interp
627 *	get deleted.
628 *
629 *----------------------------------------------------------------------
630 */
631
632static void
633LoadCleanupProc(clientData, interp)
634    ClientData clientData;	/* Pointer to first InterpPackage structure
635				 * for interp. */
636    Tcl_Interp *interp;		/* Interpreter that is being deleted. */
637{
638    InterpPackage *ipPtr, *nextPtr;
639
640    ipPtr = (InterpPackage *) clientData;
641    while (ipPtr != NULL) {
642	nextPtr = ipPtr->nextPtr;
643	ckfree((char *) ipPtr);
644	ipPtr = nextPtr;
645    }
646}
647
648/*
649 *----------------------------------------------------------------------
650 *
651 * TclFinalizeLoad --
652 *
653 *	This procedure is invoked just before the application exits.
654 *	It frees all of the LoadedPackage structures.
655 *
656 * Results:
657 *	None.
658 *
659 * Side effects:
660 *	Memory is freed.
661 *
662 *----------------------------------------------------------------------
663 */
664
665void
666TclFinalizeLoad()
667{
668    LoadedPackage *pkgPtr;
669
670    /*
671     * No synchronization here because there should just be
672     * one thread alive at this point.  Logically,
673     * packageMutex should be grabbed at this point, but
674     * the Mutexes get finalized before the call to this routine.
675     * The only subsystem left alive at this point is the
676     * memory allocator.
677     */
678
679    while (firstPackagePtr != NULL) {
680	pkgPtr = firstPackagePtr;
681	firstPackagePtr = pkgPtr->nextPtr;
682#if defined(TCL_UNLOAD_DLLS) || defined(__WIN32__)
683	/*
684	 * Some Unix dlls are poorly behaved - registering things like
685	 * atexit calls that can't be unregistered.  If you unload
686	 * such dlls, you get a core on exit because it wants to
687	 * call a function in the dll after it's been unloaded.
688	 */
689	if (pkgPtr->fileName[0] != '\0') {
690	    Tcl_FSUnloadFileProc *unLoadProcPtr = pkgPtr->unLoadProcPtr;
691	    if (unLoadProcPtr != NULL) {
692	        (*unLoadProcPtr)(pkgPtr->loadHandle);
693	    }
694	}
695#endif
696	ckfree(pkgPtr->fileName);
697	ckfree(pkgPtr->packageName);
698	ckfree((char *) pkgPtr);
699    }
700}
701