1/* 2 * tclMacOSXFCmd.c 3 * 4 * This file implements the MacOSX specific portion of file manipulation 5 * subcommands of the "file" command. 6 * 7 * Copyright (c) 2003-2007 Daniel A. Steffen <das@users.sourceforge.net> 8 * 9 * See the file "license.terms" for information on usage and redistribution of 10 * this file, and for a DISCLAIMER OF ALL WARRANTIES. 11 * 12 * RCS: @(#) $Id: tclMacOSXFCmd.c,v 1.12 2007/04/23 20:46:14 das Exp $ 13 */ 14 15#include "tclInt.h" 16 17#ifdef HAVE_GETATTRLIST 18#include <sys/attr.h> 19#include <sys/paths.h> 20#include <libkern/OSByteOrder.h> 21#endif 22 23/* Darwin 8 copyfile API. */ 24#ifdef HAVE_COPYFILE 25#ifdef HAVE_COPYFILE_H 26#include <copyfile.h> 27#if defined(HAVE_WEAK_IMPORT) && MAC_OS_X_VERSION_MIN_REQUIRED < 1040 28/* Support for weakly importing copyfile. */ 29#define WEAK_IMPORT_COPYFILE 30extern int copyfile(const char *from, const char *to, copyfile_state_t state, 31 copyfile_flags_t flags) WEAK_IMPORT_ATTRIBUTE; 32#endif /* HAVE_WEAK_IMPORT */ 33#else /* HAVE_COPYFILE_H */ 34int copyfile(const char *from, const char *to, void *state, uint32_t flags); 35#define COPYFILE_ACL (1<<0) 36#define COPYFILE_XATTR (1<<2) 37#define COPYFILE_NOFOLLOW_SRC (1<<18) 38#if defined(HAVE_WEAK_IMPORT) && MAC_OS_X_VERSION_MIN_REQUIRED < 1040 39/* Support for weakly importing copyfile. */ 40#define WEAK_IMPORT_COPYFILE 41extern int copyfile(const char *from, const char *to, void *state, 42 uint32_t flags) WEAK_IMPORT_ATTRIBUTE; 43#endif /* HAVE_WEAK_IMPORT */ 44#endif /* HAVE_COPYFILE_H */ 45#endif /* HAVE_COPYFILE */ 46 47#include <libkern/OSByteOrder.h> 48 49/* 50 * Constants for file attributes subcommand. Need to be kept in sync with 51 * tclUnixFCmd.c ! 52 */ 53 54enum { 55 UNIX_GROUP_ATTRIBUTE, 56 UNIX_OWNER_ATTRIBUTE, 57 UNIX_PERMISSIONS_ATTRIBUTE, 58#ifdef HAVE_CHFLAGS 59 UNIX_READONLY_ATTRIBUTE, 60#endif 61#ifdef MAC_OSX_TCL 62 MACOSX_CREATOR_ATTRIBUTE, 63 MACOSX_TYPE_ATTRIBUTE, 64 MACOSX_HIDDEN_ATTRIBUTE, 65 MACOSX_RSRCLENGTH_ATTRIBUTE, 66#endif 67}; 68 69typedef u_int32_t OSType; 70 71static int GetOSTypeFromObj(Tcl_Interp *interp, 72 Tcl_Obj *objPtr, OSType *osTypePtr); 73static Tcl_Obj * NewOSTypeObj(const OSType newOSType); 74static int SetOSTypeFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr); 75static void UpdateStringOfOSType(Tcl_Obj *objPtr); 76 77static Tcl_ObjType tclOSTypeType = { 78 "osType", /* name */ 79 NULL, /* freeIntRepProc */ 80 NULL, /* dupIntRepProc */ 81 UpdateStringOfOSType, /* updateStringProc */ 82 SetOSTypeFromAny /* setFromAnyProc */ 83}; 84 85enum { 86 kIsInvisible = 0x4000, 87}; 88 89#define kFinfoIsInvisible (OSSwapHostToBigConstInt16(kIsInvisible)) 90 91typedef struct finderinfo { 92 u_int32_t type; 93 u_int32_t creator; 94 u_int16_t fdFlags; 95 u_int32_t location; 96 u_int16_t reserved; 97 u_int32_t extendedFileInfo[4]; 98} __attribute__ ((__packed__)) finderinfo; 99 100typedef struct fileinfobuf { 101 u_int32_t info_length; 102 u_int32_t data[8]; 103} fileinfobuf; 104 105/* 106 *---------------------------------------------------------------------- 107 * 108 * TclMacOSXGetFileAttribute 109 * 110 * Gets a MacOSX attribute of a file. Which attribute is controlled by 111 * objIndex. The object will have ref count 0. 112 * 113 * Results: 114 * Standard TCL result. Returns a new Tcl_Obj in attributePtrPtr if there 115 * is no error. 116 * 117 * Side effects: 118 * A new object is allocated. 119 * 120 *---------------------------------------------------------------------- 121 */ 122 123int 124TclMacOSXGetFileAttribute( 125 Tcl_Interp *interp, /* The interp we are using for errors. */ 126 int objIndex, /* The index of the attribute. */ 127 Tcl_Obj *fileName, /* The name of the file (UTF-8). */ 128 Tcl_Obj **attributePtrPtr) /* A pointer to return the object with. */ 129{ 130#ifdef HAVE_GETATTRLIST 131 int result; 132 Tcl_StatBuf statBuf; 133 struct attrlist alist; 134 fileinfobuf finfo; 135 finderinfo *finder = (finderinfo*)(&finfo.data); 136 off_t *rsrcForkSize = (off_t*)(&finfo.data); 137 const char *native; 138 139 result = TclpObjStat(fileName, &statBuf); 140 141 if (result != 0) { 142 Tcl_AppendResult(interp, "could not read \"", 143 TclGetString(fileName), "\": ", Tcl_PosixError(interp), NULL); 144 return TCL_ERROR; 145 } 146 147 if (S_ISDIR(statBuf.st_mode) && objIndex != MACOSX_HIDDEN_ATTRIBUTE) { 148 /* 149 * Directories only support attribute "-hidden". 150 */ 151 152 errno = EISDIR; 153 Tcl_AppendResult(interp, "invalid attribute: ", 154 Tcl_PosixError(interp), NULL); 155 return TCL_ERROR; 156 } 157 158 bzero(&alist, sizeof(struct attrlist)); 159 alist.bitmapcount = ATTR_BIT_MAP_COUNT; 160 if (objIndex == MACOSX_RSRCLENGTH_ATTRIBUTE) { 161 alist.fileattr = ATTR_FILE_RSRCLENGTH; 162 } else { 163 alist.commonattr = ATTR_CMN_FNDRINFO; 164 } 165 native = Tcl_FSGetNativePath(fileName); 166 result = getattrlist(native, &alist, &finfo, sizeof(fileinfobuf), 0); 167 168 if (result != 0) { 169 Tcl_AppendResult(interp, "could not read attributes of \"", 170 TclGetString(fileName), "\": ", Tcl_PosixError(interp), NULL); 171 return TCL_ERROR; 172 } 173 174 switch (objIndex) { 175 case MACOSX_CREATOR_ATTRIBUTE: 176 *attributePtrPtr = NewOSTypeObj( 177 OSSwapBigToHostInt32(finder->creator)); 178 break; 179 case MACOSX_TYPE_ATTRIBUTE: 180 *attributePtrPtr = NewOSTypeObj( 181 OSSwapBigToHostInt32(finder->type)); 182 break; 183 case MACOSX_HIDDEN_ATTRIBUTE: 184 *attributePtrPtr = Tcl_NewBooleanObj( 185 (finder->fdFlags & kFinfoIsInvisible) != 0); 186 break; 187 case MACOSX_RSRCLENGTH_ATTRIBUTE: 188 *attributePtrPtr = Tcl_NewWideIntObj(*rsrcForkSize); 189 break; 190 } 191 return TCL_OK; 192#else 193 Tcl_AppendResult(interp, "Mac OS X file attributes not supported", NULL); 194 return TCL_ERROR; 195#endif 196} 197 198/* 199 *--------------------------------------------------------------------------- 200 * 201 * TclMacOSXSetFileAttribute -- 202 * 203 * Sets a MacOSX attribute of a file. Which attribute is controlled by 204 * objIndex. 205 * 206 * Results: 207 * Standard TCL result. 208 * 209 * Side effects: 210 * As above. 211 * 212 *--------------------------------------------------------------------------- 213 */ 214 215int 216TclMacOSXSetFileAttribute( 217 Tcl_Interp *interp, /* The interp for error reporting. */ 218 int objIndex, /* The index of the attribute. */ 219 Tcl_Obj *fileName, /* The name of the file (UTF-8). */ 220 Tcl_Obj *attributePtr) /* New owner for file. */ 221{ 222#ifdef HAVE_GETATTRLIST 223 int result; 224 Tcl_StatBuf statBuf; 225 struct attrlist alist; 226 fileinfobuf finfo; 227 finderinfo *finder = (finderinfo*)(&finfo.data); 228 off_t *rsrcForkSize = (off_t*)(&finfo.data); 229 const char *native; 230 231 result = TclpObjStat(fileName, &statBuf); 232 233 if (result != 0) { 234 Tcl_AppendResult(interp, "could not read \"", 235 TclGetString(fileName), "\": ", Tcl_PosixError(interp), NULL); 236 return TCL_ERROR; 237 } 238 239 if (S_ISDIR(statBuf.st_mode) && objIndex != MACOSX_HIDDEN_ATTRIBUTE) { 240 /* 241 * Directories only support attribute "-hidden". 242 */ 243 244 errno = EISDIR; 245 Tcl_AppendResult(interp, "invalid attribute: ", 246 Tcl_PosixError(interp), NULL); 247 return TCL_ERROR; 248 } 249 250 bzero(&alist, sizeof(struct attrlist)); 251 alist.bitmapcount = ATTR_BIT_MAP_COUNT; 252 if (objIndex == MACOSX_RSRCLENGTH_ATTRIBUTE) { 253 alist.fileattr = ATTR_FILE_RSRCLENGTH; 254 } else { 255 alist.commonattr = ATTR_CMN_FNDRINFO; 256 } 257 native = Tcl_FSGetNativePath(fileName); 258 result = getattrlist(native, &alist, &finfo, sizeof(fileinfobuf), 0); 259 260 if (result != 0) { 261 Tcl_AppendResult(interp, "could not read attributes of \"", 262 TclGetString(fileName), "\": ", Tcl_PosixError(interp), NULL); 263 return TCL_ERROR; 264 } 265 266 if (objIndex != MACOSX_RSRCLENGTH_ATTRIBUTE) { 267 OSType t; 268 int h; 269 270 switch (objIndex) { 271 case MACOSX_CREATOR_ATTRIBUTE: 272 if (GetOSTypeFromObj(interp, attributePtr, &t) != TCL_OK) { 273 return TCL_ERROR; 274 } 275 finder->creator = OSSwapHostToBigInt32(t); 276 break; 277 case MACOSX_TYPE_ATTRIBUTE: 278 if (GetOSTypeFromObj(interp, attributePtr, &t) != TCL_OK) { 279 return TCL_ERROR; 280 } 281 finder->type = OSSwapHostToBigInt32(t); 282 break; 283 case MACOSX_HIDDEN_ATTRIBUTE: 284 if (Tcl_GetBooleanFromObj(interp, attributePtr, &h) != TCL_OK) { 285 return TCL_ERROR; 286 } 287 if (h) { 288 finder->fdFlags |= kFinfoIsInvisible; 289 } else { 290 finder->fdFlags &= ~kFinfoIsInvisible; 291 } 292 break; 293 } 294 295 result = setattrlist(native, &alist, 296 &finfo.data, sizeof(finfo.data), 0); 297 298 if (result != 0) { 299 Tcl_AppendResult(interp, "could not set attributes of \"", 300 TclGetString(fileName), "\": ", 301 Tcl_PosixError(interp), NULL); 302 return TCL_ERROR; 303 } 304 } else { 305 Tcl_WideInt newRsrcForkSize; 306 307 if (Tcl_GetWideIntFromObj(interp, attributePtr, 308 &newRsrcForkSize) != TCL_OK) { 309 return TCL_ERROR; 310 } 311 312 if (newRsrcForkSize != *rsrcForkSize) { 313 Tcl_DString ds; 314 315 /* 316 * Only setting rsrclength to 0 to strip a file's resource fork is 317 * supported. 318 */ 319 320 if(newRsrcForkSize != 0) { 321 Tcl_AppendResult(interp, 322 "setting nonzero rsrclength not supported", NULL); 323 return TCL_ERROR; 324 } 325 326 /* 327 * Construct path to resource fork. 328 */ 329 330 Tcl_DStringInit(&ds); 331 Tcl_DStringAppend(&ds, native, -1); 332 Tcl_DStringAppend(&ds, _PATH_RSRCFORKSPEC, -1); 333 334 result = truncate(Tcl_DStringValue(&ds), (off_t)0); 335 if (result != 0) { 336 /* 337 * truncate() on a valid resource fork path may fail with 338 * a permission error in some OS releases, try truncating 339 * with open() instead: 340 */ 341 int fd = open(Tcl_DStringValue(&ds), O_WRONLY | O_TRUNC); 342 if (fd > 0) { 343 result = close(fd); 344 } 345 } 346 347 Tcl_DStringFree(&ds); 348 349 if (result != 0) { 350 Tcl_AppendResult(interp, 351 "could not truncate resource fork of \"", 352 TclGetString(fileName), "\": ", 353 Tcl_PosixError(interp), NULL); 354 return TCL_ERROR; 355 } 356 } 357 } 358 return TCL_OK; 359#else 360 Tcl_AppendResult(interp, "Mac OS X file attributes not supported", NULL); 361 return TCL_ERROR; 362#endif 363} 364 365/* 366 *--------------------------------------------------------------------------- 367 * 368 * TclMacOSXCopyFileAttributes -- 369 * 370 * Copy the MacOSX attributes and resource fork (if present) from one 371 * file to another. 372 * 373 * Results: 374 * Standard Tcl result. 375 * 376 * Side effects: 377 * MacOSX attributes and resource fork are updated in the new file to 378 * reflect the old file. 379 * 380 *--------------------------------------------------------------------------- 381 */ 382 383int 384TclMacOSXCopyFileAttributes( 385 CONST char *src, /* Path name of source file (native). */ 386 CONST char *dst, /* Path name of target file (native). */ 387 CONST Tcl_StatBuf *statBufPtr) 388 /* Stat info for source file */ 389{ 390#ifdef WEAK_IMPORT_COPYFILE 391 if (copyfile != NULL) { 392#endif 393#ifdef HAVE_COPYFILE 394 if (copyfile(src, dst, NULL, COPYFILE_XATTR | 395 (S_ISLNK(statBufPtr->st_mode) ? COPYFILE_NOFOLLOW_SRC : 396 COPYFILE_ACL)) < 0) { 397 return TCL_ERROR; 398 } 399 return TCL_OK; 400#endif /* HAVE_COPYFILE */ 401#ifdef WEAK_IMPORT_COPYFILE 402 } else { 403#endif 404#if !defined(HAVE_COPYFILE) || defined(WEAK_IMPORT_COPYFILE) 405#ifdef HAVE_GETATTRLIST 406 struct attrlist alist; 407 fileinfobuf finfo; 408 off_t *rsrcForkSize = (off_t*)(&finfo.data); 409 410 bzero(&alist, sizeof(struct attrlist)); 411 alist.bitmapcount = ATTR_BIT_MAP_COUNT; 412 alist.commonattr = ATTR_CMN_FNDRINFO; 413 414 if (getattrlist(src, &alist, &finfo, sizeof(fileinfobuf), 0)) { 415 return TCL_ERROR; 416 } 417 418 if (setattrlist(dst, &alist, &finfo.data, sizeof(finfo.data), 0)) { 419 return TCL_ERROR; 420 } 421 422 if (!S_ISDIR(statBufPtr->st_mode)) { 423 /* 424 * Only copy non-empty resource fork. 425 */ 426 427 alist.commonattr = 0; 428 alist.fileattr = ATTR_FILE_RSRCLENGTH; 429 430 if (getattrlist(src, &alist, &finfo, sizeof(fileinfobuf), 0)) { 431 return TCL_ERROR; 432 } 433 434 if(*rsrcForkSize > 0) { 435 int result; 436 Tcl_DString ds_src, ds_dst; 437 438 /* 439 * Construct paths to resource forks. 440 */ 441 442 Tcl_DStringInit(&ds_src); 443 Tcl_DStringAppend(&ds_src, src, -1); 444 Tcl_DStringAppend(&ds_src, _PATH_RSRCFORKSPEC, -1); 445 Tcl_DStringInit(&ds_dst); 446 Tcl_DStringAppend(&ds_dst, dst, -1); 447 Tcl_DStringAppend(&ds_dst, _PATH_RSRCFORKSPEC, -1); 448 449 result = TclUnixCopyFile(Tcl_DStringValue(&ds_src), 450 Tcl_DStringValue(&ds_dst), statBufPtr, 1); 451 452 Tcl_DStringFree(&ds_src); 453 Tcl_DStringFree(&ds_dst); 454 455 if (result != 0) { 456 return TCL_ERROR; 457 } 458 } 459 } 460 return TCL_OK; 461#else 462 return TCL_ERROR; 463#endif /* HAVE_GETATTRLIST */ 464#endif /* !defined(HAVE_COPYFILE) || defined(WEAK_IMPORT_COPYFILE) */ 465#ifdef WEAK_IMPORT_COPYFILE 466 } 467#endif 468} 469 470/* 471 *---------------------------------------------------------------------- 472 * 473 * TclMacOSXMatchType -- 474 * 475 * This routine is used by the globbing code to check if a file 476 * matches a given mac type and/or creator code. 477 * 478 * Results: 479 * The return value is 1, 0 or -1 indicating whether the file 480 * matches the given criteria, does not match them, or an error 481 * occurred (in wich case an error is left in interp). 482 * 483 * Side effects: 484 * None. 485 * 486 *---------------------------------------------------------------------- 487 */ 488 489int 490TclMacOSXMatchType( 491 Tcl_Interp *interp, /* Interpreter to receive errors. */ 492 CONST char *pathName, /* Native path to check. */ 493 CONST char *fileName, /* Native filename to check. */ 494 Tcl_StatBuf *statBufPtr, /* Stat info for file to check */ 495 Tcl_GlobTypeData *types) /* Type description to match against. */ 496{ 497#ifdef HAVE_GETATTRLIST 498 struct attrlist alist; 499 fileinfobuf finfo; 500 finderinfo *finder = (finderinfo*)(&finfo.data); 501 OSType osType; 502 503 bzero(&alist, sizeof(struct attrlist)); 504 alist.bitmapcount = ATTR_BIT_MAP_COUNT; 505 alist.commonattr = ATTR_CMN_FNDRINFO; 506 if (getattrlist(pathName, &alist, &finfo, sizeof(fileinfobuf), 0) != 0) { 507 return 0; 508 } 509 if ((types->perm & TCL_GLOB_PERM_HIDDEN) && 510 !((finder->fdFlags & kFinfoIsInvisible) || (*fileName == '.'))) { 511 return 0; 512 } 513 if (S_ISDIR(statBufPtr->st_mode) && (types->macType || types->macCreator)) { 514 /* Directories don't support types or creators */ 515 return 0; 516 } 517 if (types->macType) { 518 if (GetOSTypeFromObj(interp, types->macType, &osType) != TCL_OK) { 519 return -1; 520 } 521 if (osType != OSSwapBigToHostInt32(finder->type)) { 522 return 0; 523 } 524 } 525 if (types->macCreator) { 526 if (GetOSTypeFromObj(interp, types->macCreator, &osType) != TCL_OK) { 527 return -1; 528 } 529 if (osType != OSSwapBigToHostInt32(finder->creator)) { 530 return 0; 531 } 532 } 533#endif 534 return 1; 535} 536 537/* 538 *---------------------------------------------------------------------- 539 * 540 * GetOSTypeFromObj -- 541 * 542 * Attempt to return an OSType from the Tcl object "objPtr". 543 * 544 * Results: 545 * Standard TCL result. If an error occurs during conversion, an error 546 * message is left in interp->objResult. 547 * 548 * Side effects: 549 * The string representation of objPtr will be updated if necessary. 550 * 551 *---------------------------------------------------------------------- 552 */ 553 554static int 555GetOSTypeFromObj( 556 Tcl_Interp *interp, /* Used for error reporting if not NULL. */ 557 Tcl_Obj *objPtr, /* The object from which to get an OSType. */ 558 OSType *osTypePtr) /* Place to store resulting OSType. */ 559{ 560 int result = TCL_OK; 561 562 if (objPtr->typePtr != &tclOSTypeType) { 563 result = tclOSTypeType.setFromAnyProc(interp, objPtr); 564 }; 565 *osTypePtr = (OSType) objPtr->internalRep.longValue; 566 return result; 567} 568 569/* 570 *---------------------------------------------------------------------- 571 * 572 * NewOSTypeObj -- 573 * 574 * Create a new OSType object. 575 * 576 * Results: 577 * The newly created OSType object is returned, it has ref count 0. 578 * 579 * Side effects: 580 * None. 581 * 582 *---------------------------------------------------------------------- 583 */ 584 585static Tcl_Obj * 586NewOSTypeObj( 587 const OSType osType) /* OSType used to initialize the new object. */ 588{ 589 Tcl_Obj *objPtr; 590 591 TclNewObj(objPtr); 592 Tcl_InvalidateStringRep(objPtr); 593 objPtr->internalRep.longValue = (long) osType; 594 objPtr->typePtr = &tclOSTypeType; 595 return objPtr; 596} 597 598/* 599 *---------------------------------------------------------------------- 600 * 601 * SetOSTypeFromAny -- 602 * 603 * Attempts to force the internal representation for a Tcl object to 604 * tclOSTypeType, specifically. 605 * 606 * Results: 607 * The return value is a standard object Tcl result. If an error occurs 608 * during conversion, an error message is left in the interpreter's 609 * result unless "interp" is NULL. 610 * 611 *---------------------------------------------------------------------- 612 */ 613 614static int 615SetOSTypeFromAny( 616 Tcl_Interp *interp, /* Tcl interpreter */ 617 Tcl_Obj *objPtr) /* Pointer to the object to convert */ 618{ 619 char *string; 620 int length, result = TCL_OK; 621 Tcl_DString ds; 622 Tcl_Encoding encoding = Tcl_GetEncoding(NULL, "macRoman"); 623 624 string = Tcl_GetStringFromObj(objPtr, &length); 625 Tcl_UtfToExternalDString(encoding, string, length, &ds); 626 627 if (Tcl_DStringLength(&ds) > 4) { 628 Tcl_AppendResult(interp, "expected Macintosh OS type but got \"", 629 string, "\": ", NULL); 630 result = TCL_ERROR; 631 } else { 632 OSType osType; 633 char string[4] = {'\0','\0','\0','\0'}; 634 memcpy(string, Tcl_DStringValue(&ds), 635 (size_t) Tcl_DStringLength(&ds)); 636 osType = (OSType) string[0] << 24 | 637 (OSType) string[1] << 16 | 638 (OSType) string[2] << 8 | 639 (OSType) string[3]; 640 TclFreeIntRep(objPtr); 641 objPtr->internalRep.longValue = (long) osType; 642 objPtr->typePtr = &tclOSTypeType; 643 } 644 Tcl_DStringFree(&ds); 645 Tcl_FreeEncoding(encoding); 646 return result; 647} 648 649/* 650 *---------------------------------------------------------------------- 651 * 652 * UpdateStringOfOSType -- 653 * 654 * Update the string representation for an OSType object. Note: This 655 * function does not free an existing old string rep so storage will be 656 * lost if this has not already been done. 657 * 658 * Results: 659 * None. 660 * 661 * Side effects: 662 * The object's string is set to a valid string that results from the 663 * OSType-to-string conversion. 664 * 665 *---------------------------------------------------------------------- 666 */ 667 668static void 669UpdateStringOfOSType( 670 register Tcl_Obj *objPtr) /* OSType object whose string rep to update. */ 671{ 672 char string[5]; 673 OSType osType = (OSType) objPtr->internalRep.longValue; 674 Tcl_DString ds; 675 Tcl_Encoding encoding = Tcl_GetEncoding(NULL, "macRoman"); 676 677 string[0] = (char) (osType >> 24); 678 string[1] = (char) (osType >> 16); 679 string[2] = (char) (osType >> 8); 680 string[3] = (char) (osType); 681 string[4] = '\0'; 682 Tcl_ExternalToUtfDString(encoding, string, -1, &ds); 683 objPtr->bytes = ckalloc((unsigned) Tcl_DStringLength(&ds) + 1); 684 strcpy(objPtr->bytes, Tcl_DStringValue(&ds)); 685 objPtr->length = Tcl_DStringLength(&ds); 686 Tcl_DStringFree(&ds); 687 Tcl_FreeEncoding(encoding); 688} 689 690/* 691 * Local Variables: 692 * mode: c 693 * c-basic-offset: 4 694 * fill-column: 78 695 * End: 696 */ 697