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