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