Tcl Source Code

Check-in [c2f3dbddd9]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:TIP 701 - Tcl_FSTildeExpand C API
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | core-8-branch
Files: files | file ages | folders
SHA3-256: c2f3dbddd9af563f536e5506558b75dd19a94f0906812e886a2bba70136c2792
User & Date: apnadkarni 2024-10-30 11:38:45.459
Context
2024-10-31
13:30
merge 8.6 (clock: load TZ failure normalization and test coverage) check-in: f788320fc9 user: sebres tags: core-8-branch
2024-10-30
12:24
Merge 8.7 - TIP 701 - Tcl_FSTildeExpand C API check-in: ecb2b9d3b5 user: apnadkarni tags: trunk, main
11:38
TIP 701 - Tcl_FSTildeExpand C API check-in: c2f3dbddd9 user: apnadkarni tags: core-8-branch
11:24
Add Tcl_FSTildeExpand manpage. Do not depend on caller to clean up output DString on error Closed-Leaf check-in: 321a559f8c user: apnadkarni tags: tip-701
2024-10-29
18:37
merge 8.6: fixes regression and further bug [2c237beffbace823] check-in: 5f73876fe7 user: sebres tags: core-8-branch
Changes
Unified Diff Ignore Whitespace Patch
Changes to doc/FileSystem.3.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
'\"
'\" Copyright (c) 2001 Vincent Darley
'\" Copyright (c) 2008-2010 Donal K. Fellows
'\"
'\" See the file "license.terms" for information on usage and redistribution
'\" of this file, and for a DISCLAIMER OF ALL WARRANTIES.
'\"
.TH Filesystem 3 8.4 Tcl "Tcl Library Procedures"
.so man.macros
.BS
.SH NAME
Tcl_FSRegister, Tcl_FSUnregister, Tcl_FSData, Tcl_FSMountsChanged, Tcl_FSGetFileSystemForPath, Tcl_FSGetPathType, Tcl_FSCopyFile, Tcl_FSCopyDirectory, Tcl_FSCreateDirectory, Tcl_FSDeleteFile, Tcl_FSRemoveDirectory, Tcl_FSRenameFile, Tcl_FSListVolumes, Tcl_FSEvalFile, Tcl_FSEvalFileEx, Tcl_FSLoadFile, Tcl_FSUnloadFile, Tcl_FSMatchInDirectory, Tcl_FSLink, Tcl_FSLstat, Tcl_FSUtime, Tcl_FSFileAttrsGet, Tcl_FSFileAttrsSet, Tcl_FSFileAttrStrings, Tcl_FSStat, Tcl_FSAccess, Tcl_FSOpenFileChannel, Tcl_FSGetCwd, Tcl_FSChdir, Tcl_FSPathSeparator, Tcl_FSJoinPath, Tcl_FSSplitPath, Tcl_FSEqualPaths, Tcl_FSGetNormalizedPath, Tcl_FSJoinToPath, Tcl_FSConvertToPathType, Tcl_FSGetInternalRep, Tcl_FSGetTranslatedPath, Tcl_FSGetTranslatedStringPath, Tcl_FSNewNativePath, Tcl_FSGetNativePath, Tcl_FSFileSystemInfo, Tcl_GetAccessTimeFromStat, Tcl_GetBlockSizeFromStat, Tcl_GetBlocksFromStat, Tcl_GetChangeTimeFromStat, Tcl_GetDeviceTypeFromStat, Tcl_GetFSDeviceFromStat, Tcl_GetFSInodeFromStat, Tcl_GetGroupIdFromStat, Tcl_GetLinkCountFromStat, Tcl_GetModeFromStat, Tcl_GetModificationTimeFromStat, Tcl_GetSizeFromStat, Tcl_GetUserIdFromStat, Tcl_AllocStatBuf \- procedures to interact with any filesystem
.SH SYNOPSIS
.nf
\fB#include <tcl.h>\fR
.sp
int
\fBTcl_FSRegister\fR(\fIclientData, fsPtr\fR)
.sp











|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
'\"
'\" Copyright (c) 2001 Vincent Darley
'\" Copyright (c) 2008-2010 Donal K. Fellows
'\"
'\" See the file "license.terms" for information on usage and redistribution
'\" of this file, and for a DISCLAIMER OF ALL WARRANTIES.
'\"
.TH Filesystem 3 8.4 Tcl "Tcl Library Procedures"
.so man.macros
.BS
.SH NAME
Tcl_FSRegister, Tcl_FSUnregister, Tcl_FSData, Tcl_FSMountsChanged, Tcl_FSGetFileSystemForPath, Tcl_FSGetPathType, Tcl_FSCopyFile, Tcl_FSCopyDirectory, Tcl_FSCreateDirectory, Tcl_FSDeleteFile, Tcl_FSRemoveDirectory, Tcl_FSRenameFile, Tcl_FSListVolumes, Tcl_FSEvalFile, Tcl_FSEvalFileEx, Tcl_FSLoadFile, Tcl_FSUnloadFile, Tcl_FSMatchInDirectory, Tcl_FSLink, Tcl_FSLstat, Tcl_FSUtime, Tcl_FSFileAttrsGet, Tcl_FSFileAttrsSet, Tcl_FSFileAttrStrings, Tcl_FSStat, Tcl_FSAccess, Tcl_FSOpenFileChannel, Tcl_FSGetCwd, Tcl_FSChdir, Tcl_FSPathSeparator, Tcl_FSJoinPath, Tcl_FSSplitPath, Tcl_FSEqualPaths, Tcl_FSGetNormalizedPath, Tcl_FSJoinToPath, Tcl_FSConvertToPathType, Tcl_FSGetInternalRep, Tcl_FSGetTranslatedPath, Tcl_FSGetTranslatedStringPath, Tcl_FSNewNativePath, Tcl_FSGetNativePath, Tcl_FSFileSystemInfo, Tcl_GetAccessTimeFromStat, Tcl_GetBlockSizeFromStat, Tcl_GetBlocksFromStat, Tcl_GetChangeTimeFromStat, Tcl_GetDeviceTypeFromStat, Tcl_GetFSDeviceFromStat, Tcl_GetFSInodeFromStat, Tcl_GetGroupIdFromStat, Tcl_GetLinkCountFromStat, Tcl_GetModeFromStat, Tcl_GetModificationTimeFromStat, Tcl_GetSizeFromStat, Tcl_GetUserIdFromStat, Tcl_AllocStatBuf, Tcl_FSTildeExpand \- procedures to interact with any filesystem
.SH SYNOPSIS
.nf
\fB#include <tcl.h>\fR
.sp
int
\fBTcl_FSRegister\fR(\fIclientData, fsPtr\fR)
.sp
136
137
138
139
140
141
142



143
144
145
146
147
148
149
.sp
const void *
\fBTcl_FSGetNativePath\fR(\fIpathPtr\fR)
.sp
Tcl_Obj *
\fBTcl_FSFileSystemInfo\fR(\fIpathPtr\fR)
.sp



Tcl_StatBuf *
\fBTcl_AllocStatBuf\fR()
.sp
long long
\fBTcl_GetAccessTimeFromStat\fR(\fIstatPtr\fR)
.sp
unsigned







>
>
>







136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
.sp
const void *
\fBTcl_FSGetNativePath\fR(\fIpathPtr\fR)
.sp
Tcl_Obj *
\fBTcl_FSFileSystemInfo\fR(\fIpathPtr\fR)
.sp
int
\fBTcl_FSTildeExpand\fR(\fIinterp, pathStr, dsPtr\fR)
.sp
Tcl_StatBuf *
\fBTcl_AllocStatBuf\fR()
.sp
long long
\fBTcl_GetAccessTimeFromStat\fR(\fIstatPtr\fR)
.sp
unsigned
183
184
185
186
187
188
189


190
191
192
193
194
195
196
\fBTcl_GetUserIdFromStat\fR(\fIstatPtr\fR)
.fi
.SH ARGUMENTS
.AS Tcl_GlobTypeData **srcPathPtr out
.AP "const Tcl_Filesystem" *fsPtr in
Points to a structure containing the addresses of procedures that
can be called to perform the various filesystem operations.


.AP Tcl_Obj *pathPtr in
The path represented by this value is used for the operation in
question. If the value does not already have an internal \fBpath\fR
representation, it will be converted to have one.
.AP Tcl_Obj *srcPathPtr in
As for \fIpathPtr\fR, but used for the source file for a copy or
rename operation.







>
>







186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
\fBTcl_GetUserIdFromStat\fR(\fIstatPtr\fR)
.fi
.SH ARGUMENTS
.AS Tcl_GlobTypeData **srcPathPtr out
.AP "const Tcl_Filesystem" *fsPtr in
Points to a structure containing the addresses of procedures that
can be called to perform the various filesystem operations.
.AP "const char" *pathStr in
Pointer to a NUL terminated string representing a file system path.
.AP Tcl_Obj *pathPtr in
The path represented by this value is used for the operation in
question. If the value does not already have an internal \fBpath\fR
representation, it will be converted to have one.
.AP Tcl_Obj *srcPathPtr in
As for \fIpathPtr\fR, but used for the source file for a copy or
rename operation.
285
286
287
288
289
290
291


292
293
294
295
296
297
298
the symbolic link specified by \fIlinkNamePtr\fR is to be read.
.AP int linkAction in
OR-ed combination of flags indicating what kind of link should be
created (will be ignored if \fItoPtr\fR is NULL). Valid bits to set
are \fBTCL_CREATE_SYMBOLIC_LINK\fR and \fBTCL_CREATE_HARD_LINK\fR.
When both flags are set and the underlying filesystem can do either,
symbolic links are preferred.


.BE
.SH DESCRIPTION
.PP
There are several reasons for calling the \fBTcl_FS\fR API functions
(e.g.\ \fBTcl_FSAccess\fR and \fBTcl_FSStat\fR)
rather than calling system level functions like \fBaccess\fR and
\fBstat\fR directly. First, they will work cross-platform, so an







>
>







290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
the symbolic link specified by \fIlinkNamePtr\fR is to be read.
.AP int linkAction in
OR-ed combination of flags indicating what kind of link should be
created (will be ignored if \fItoPtr\fR is NULL). Valid bits to set
are \fBTCL_CREATE_SYMBOLIC_LINK\fR and \fBTCL_CREATE_HARD_LINK\fR.
When both flags are set and the underlying filesystem can do either,
symbolic links are preferred.
.AP Tcl_DString *dsPtr out
Pointer to a \fBTcl_DString\fR to hold an output string result.
.BE
.SH DESCRIPTION
.PP
There are several reasons for calling the \fBTcl_FS\fR API functions
(e.g.\ \fBTcl_FSAccess\fR and \fBTcl_FSStat\fR)
rather than calling system level functions like \fBaccess\fR and
\fBstat\fR directly. First, they will work cross-platform, so an
783
784
785
786
787
788
789











790
791
792
793
794
795
796
.PP
\fBTcl_FSGetPathType\fR determines whether the given path is relative
to the current directory, relative to the current volume, or
absolute.
.PP
It returns one of \fBTCL_PATH_ABSOLUTE\fR, \fBTCL_PATH_RELATIVE\fR, or
\fBTCL_PATH_VOLUME_RELATIVE\fR











.SS "PORTABLE STAT RESULT API"
.PP
\fBTcl_AllocStatBuf\fR allocates a \fITcl_StatBuf\fR on the system heap (which
may be deallocated by being passed to \fBckfree\fR). This allows extensions to
invoke \fBTcl_FSStat\fR and \fBTcl_FSLstat\fR without being dependent on the
size of the buffer. That in turn depends on the flags used to build Tcl.
.PP







>
>
>
>
>
>
>
>
>
>
>







790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
.PP
\fBTcl_FSGetPathType\fR determines whether the given path is relative
to the current directory, relative to the current volume, or
absolute.
.PP
It returns one of \fBTCL_PATH_ABSOLUTE\fR, \fBTCL_PATH_RELATIVE\fR, or
\fBTCL_PATH_VOLUME_RELATIVE\fR
.PP
\fBTcl_FSTildeExpand\fR performs tilde substitution on the input path passed via
\fBpathStr\fR as described in the documentation for the \fBfile tildeexpand\fR
Tcl command. On success, the function returns \fBTCL_OK\fR with the result of
the substitution in \fBdsPtr\fR which must be subsequently freed by the caller.
The \fBdsPtr\fR structure is initialized by the function. No guarantees are made
about the form of the returned path such as the path separators used. The
returned result should be passed to other Tcl C API functions such as
\fBTcl_FSGetNormalizedPath\fR or \fBTcl_FSGetNativePath\fR if necessary. On
error, the function returns \fBTCL_ERROR\fR with an error message in
\fBinterp\fR which may be passed as NULL if error messages are not of interest.
.SS "PORTABLE STAT RESULT API"
.PP
\fBTcl_AllocStatBuf\fR allocates a \fITcl_StatBuf\fR on the system heap (which
may be deallocated by being passed to \fBckfree\fR). This allows extensions to
invoke \fBTcl_FSStat\fR and \fBTcl_FSLstat\fR without being dependent on the
size of the buffer. That in turn depends on the flags used to build Tcl.
.PP
Changes to generic/tcl.decls.
2418
2419
2420
2421
2422
2423
2424






2425
2426
2427
2428
2429
2430
2431
}
declare 655 {
    const char *Tcl_UtfNext(const char *src)
}
declare 656 {
    const char *Tcl_UtfPrev(const char *src, const char *start)
}







# TIP 656
declare 658 {
    int Tcl_ExternalToUtfDStringEx(Tcl_Interp *interp, Tcl_Encoding encoding,
	    const char *src, Tcl_Size srcLen, int flags, Tcl_DString *dsPtr,
	    Tcl_Size *errorLocationPtr)
}







>
>
>
>
>
>







2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
}
declare 655 {
    const char *Tcl_UtfNext(const char *src)
}
declare 656 {
    const char *Tcl_UtfPrev(const char *src, const char *start)
}

# TIP 701
declare 657 {
    int Tcl_FSTildeExpand(Tcl_Interp *interp, const char *path,
        Tcl_DString *dsPtr)
}

# TIP 656
declare 658 {
    int Tcl_ExternalToUtfDStringEx(Tcl_Interp *interp, Tcl_Encoding encoding,
	    const char *src, Tcl_Size srcLen, int flags, Tcl_DString *dsPtr,
	    Tcl_Size *errorLocationPtr)
}
Changes to generic/tclDecls.h.
1944
1945
1946
1947
1948
1949
1950
1951


1952
1953
1954
1955
1956
1957
1958
/* Slot 653 is reserved */
/* 654 */
EXTERN int		Tcl_UtfCharComplete(const char *src, Tcl_Size length);
/* 655 */
EXTERN const char *	Tcl_UtfNext(const char *src);
/* 656 */
EXTERN const char *	Tcl_UtfPrev(const char *src, const char *start);
/* Slot 657 is reserved */


/* 658 */
EXTERN int		Tcl_ExternalToUtfDStringEx(Tcl_Interp *interp,
				Tcl_Encoding encoding, const char *src,
				Tcl_Size srcLen, int flags,
				Tcl_DString *dsPtr,
				Tcl_Size *errorLocationPtr);
/* 659 */







|
>
>







1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
/* Slot 653 is reserved */
/* 654 */
EXTERN int		Tcl_UtfCharComplete(const char *src, Tcl_Size length);
/* 655 */
EXTERN const char *	Tcl_UtfNext(const char *src);
/* 656 */
EXTERN const char *	Tcl_UtfPrev(const char *src, const char *start);
/* 657 */
EXTERN int		Tcl_FSTildeExpand(Tcl_Interp *interp,
				const char *path, Tcl_DString *dsPtr);
/* 658 */
EXTERN int		Tcl_ExternalToUtfDStringEx(Tcl_Interp *interp,
				Tcl_Encoding encoding, const char *src,
				Tcl_Size srcLen, int flags,
				Tcl_DString *dsPtr,
				Tcl_Size *errorLocationPtr);
/* 659 */
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
    void (*reserved650)(void);
    void (*reserved651)(void);
    void (*reserved652)(void);
    void (*reserved653)(void);
    int (*tcl_UtfCharComplete) (const char *src, Tcl_Size length); /* 654 */
    const char * (*tcl_UtfNext) (const char *src); /* 655 */
    const char * (*tcl_UtfPrev) (const char *src, const char *start); /* 656 */
    void (*reserved657)(void);
    int (*tcl_ExternalToUtfDStringEx) (Tcl_Interp *interp, Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, int flags, Tcl_DString *dsPtr, Tcl_Size *errorLocationPtr); /* 658 */
    int (*tcl_UtfToExternalDStringEx) (Tcl_Interp *interp, Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, int flags, Tcl_DString *dsPtr, Tcl_Size *errorLocationPtr); /* 659 */
    int (*tcl_AsyncMarkFromSignal) (Tcl_AsyncHandler async, int sigNumber); /* 660 */
    void (*reserved661)(void);
    void (*reserved662)(void);
    void (*reserved663)(void);
    void (*reserved664)(void);







|







2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
    void (*reserved650)(void);
    void (*reserved651)(void);
    void (*reserved652)(void);
    void (*reserved653)(void);
    int (*tcl_UtfCharComplete) (const char *src, Tcl_Size length); /* 654 */
    const char * (*tcl_UtfNext) (const char *src); /* 655 */
    const char * (*tcl_UtfPrev) (const char *src, const char *start); /* 656 */
    int (*tcl_FSTildeExpand) (Tcl_Interp *interp, const char *path, Tcl_DString *dsPtr); /* 657 */
    int (*tcl_ExternalToUtfDStringEx) (Tcl_Interp *interp, Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, int flags, Tcl_DString *dsPtr, Tcl_Size *errorLocationPtr); /* 658 */
    int (*tcl_UtfToExternalDStringEx) (Tcl_Interp *interp, Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, int flags, Tcl_DString *dsPtr, Tcl_Size *errorLocationPtr); /* 659 */
    int (*tcl_AsyncMarkFromSignal) (Tcl_AsyncHandler async, int sigNumber); /* 660 */
    void (*reserved661)(void);
    void (*reserved662)(void);
    void (*reserved663)(void);
    void (*reserved664)(void);
4086
4087
4088
4089
4090
4091
4092

4093
4094
4095
4096
4097
4098
4099
4100
/* Slot 653 is reserved */
#define Tcl_UtfCharComplete \
	(tclStubsPtr->tcl_UtfCharComplete) /* 654 */
#define Tcl_UtfNext \
	(tclStubsPtr->tcl_UtfNext) /* 655 */
#define Tcl_UtfPrev \
	(tclStubsPtr->tcl_UtfPrev) /* 656 */

/* Slot 657 is reserved */
#define Tcl_ExternalToUtfDStringEx \
	(tclStubsPtr->tcl_ExternalToUtfDStringEx) /* 658 */
#define Tcl_UtfToExternalDStringEx \
	(tclStubsPtr->tcl_UtfToExternalDStringEx) /* 659 */
#define Tcl_AsyncMarkFromSignal \
	(tclStubsPtr->tcl_AsyncMarkFromSignal) /* 660 */
/* Slot 661 is reserved */







>
|







4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
/* Slot 653 is reserved */
#define Tcl_UtfCharComplete \
	(tclStubsPtr->tcl_UtfCharComplete) /* 654 */
#define Tcl_UtfNext \
	(tclStubsPtr->tcl_UtfNext) /* 655 */
#define Tcl_UtfPrev \
	(tclStubsPtr->tcl_UtfPrev) /* 656 */
#define Tcl_FSTildeExpand \
	(tclStubsPtr->tcl_FSTildeExpand) /* 657 */
#define Tcl_ExternalToUtfDStringEx \
	(tclStubsPtr->tcl_ExternalToUtfDStringEx) /* 658 */
#define Tcl_UtfToExternalDStringEx \
	(tclStubsPtr->tcl_UtfToExternalDStringEx) /* 659 */
#define Tcl_AsyncMarkFromSignal \
	(tclStubsPtr->tcl_AsyncMarkFromSignal) /* 660 */
/* Slot 661 is reserved */
Changes to generic/tclPathObj.c.
2686
2687
2688
2689
2690
2691
2692




































































2693
2694
2695
2696
2697
2698
2699
    }
    return Tcl_DStringToObj(&dirString);
}

/*
 *----------------------------------------------------------------------
 *




































































 * TclResolveTildePath --
 *
 *	If the passed path is begins with a tilde, does tilde resolution
 *	and returns a Tcl_Obj containing the resolved path. If the tilde
 *	component cannot be resolved, returns NULL. If the path does not
 *	begin with a tilde, returns as is.
 *







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
    }
    return Tcl_DStringToObj(&dirString);
}

/*
 *----------------------------------------------------------------------
 *
 * Tcl_FSTildeExpand --
 *
 *	Copies the path passed in to the output Tcl_DString dsPtr,
 *	resolving leading ~ and ~user components in the path if present.
 *      An error is returned if such a component IS present AND cannot
 *      be resolved.
 *
 *	The output dsPtr must be cleared by caller on success.
 *
 * Results:
 *      TCL_OK - path did not contain leading ~ or it was successful resolved
 *      TCL_ERROR - ~ component could not be resolved.
 *
 *----------------------------------------------------------------------
 */
int Tcl_FSTildeExpand(
    Tcl_Interp *interp,	/* May be NULL. Only used for error messages */
    const char *path,	/* Path to resolve tilde */
    Tcl_DString *dsPtr) /* Output DString for resolved path. */

{
    Tcl_Size split;
    int result;

    assert(path);
    assert(dsPtr);

    Tcl_DStringInit(dsPtr);
    if (path[0] != '~') {
	Tcl_DStringAppend(dsPtr, path, -1);
	return TCL_OK;
    }

    /*
     * We have multiple cases '~', '~user', '~/foo/bar...', '~user/foo...'
     * FindSplitPos returns 1 for '~/...' as well as for '~'. Note on
     * Windows FindSplitPos implicitly checks for '\' as separator
     * in addition to what is passed.
     */
    split = FindSplitPos(path, '/');

    if (split == 1) {
        /* No user name specified '~' or '~/...' -> current user */
	result = MakeTildeRelativePath(interp, NULL, path[1] ? 2 + path : NULL, dsPtr);
    } else {
        /* User name specified - ~user, ~user/... */
        const char *user;
        Tcl_DString dsUser;

	Tcl_DStringInit(&dsUser);
        Tcl_DStringAppend(&dsUser, path+1, split-1);
        user = Tcl_DStringValue(&dsUser);

	/* path[split] is / for ~user/... or \0 for ~user */
	result = MakeTildeRelativePath(interp, user,
		  path[split] ? &path[split + 1] : NULL, dsPtr);
	Tcl_DStringFree(&dsUser);
    }
    if (result != TCL_OK) {
        /* Do not rely on caller to free in case of errors */
        Tcl_DStringFree(dsPtr);
    }
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * TclResolveTildePath --
 *
 *	If the passed path is begins with a tilde, does tilde resolution
 *	and returns a Tcl_Obj containing the resolved path. If the tilde
 *	component cannot be resolved, returns NULL. If the path does not
 *	begin with a tilde, returns as is.
 *
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719

2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
 */
Tcl_Obj *
TclResolveTildePath(
    Tcl_Interp *interp, /* May be NULL. Only used for error messages */
    Tcl_Obj *pathObj)
{
    const char *path;
    int len;
    int split;
    Tcl_DString resolvedPath;

    path = TclGetStringFromObj(pathObj, &len);

    if (path[0] != '~') {
	return pathObj;
    }

    /*
     * We have multiple cases '~/foo/bar...', '~user/foo/bar...', etc.
     * split becomes value 1 for '~/...' as well as for '~'. Note on
     * Windows FindSplitPos will implicitly check for '\' as separator
     * in addition to what is passed.
     */
    split = FindSplitPos(path, '/');

    if (split == 1) {
	/* No user name specified -> current user */
	if (MakeTildeRelativePath(
		interp, NULL, path[1] ? 2 + path : NULL, &resolvedPath)
	    != TCL_OK) {
	    return NULL;
	}
    } else {
	/* User name specified - ~user */
	const char *expandedUser;
	Tcl_DString userName;

	Tcl_DStringInit(&userName);
	Tcl_DStringAppend(&userName, path+1, split-1);
	expandedUser = Tcl_DStringValue(&userName);

	/* path[split] is / or \0 */
	if (MakeTildeRelativePath(interp,
				  expandedUser,
				  path[split] ? &path[split+1] : NULL,
				  &resolvedPath)
	    != TCL_OK) {
	    Tcl_DStringFree(&userName);
	    return NULL;
	}
	Tcl_DStringFree(&userName);
    }
    return Tcl_DStringToObj(&resolvedPath);
}

/*
 *----------------------------------------------------------------------
 *
 * TclResolveTildePathList --







|
<



>




<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<
|
|
<
|







2776
2777
2778
2779
2780
2781
2782
2783

2784
2785
2786
2787
2788
2789
2790
2791




























2792


2793
2794

2795
2796
2797
2798
2799
2800
2801
2802
 */
Tcl_Obj *
TclResolveTildePath(
    Tcl_Interp *interp, /* May be NULL. Only used for error messages */
    Tcl_Obj *pathObj)
{
    const char *path;
    Tcl_Size len;

    Tcl_DString resolvedPath;

    path = TclGetStringFromObj(pathObj, &len);
    /* Optimize to skip unnecessary calls below */
    if (path[0] != '~') {
	return pathObj;
    }





























    if (Tcl_FSTildeExpand(interp, path, &resolvedPath) != TCL_OK) {


	return NULL;
    }


    return Tcl_DStringToObj(&resolvedPath);
}

/*
 *----------------------------------------------------------------------
 *
 * TclResolveTildePathList --
Changes to generic/tclStubInit.c.
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
    0, /* 650 */
    0, /* 651 */
    0, /* 652 */
    0, /* 653 */
    Tcl_UtfCharComplete, /* 654 */
    Tcl_UtfNext, /* 655 */
    Tcl_UtfPrev, /* 656 */
    0, /* 657 */
    Tcl_ExternalToUtfDStringEx, /* 658 */
    Tcl_UtfToExternalDStringEx, /* 659 */
    Tcl_AsyncMarkFromSignal, /* 660 */
    0, /* 661 */
    0, /* 662 */
    0, /* 663 */
    0, /* 664 */







|







1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
    0, /* 650 */
    0, /* 651 */
    0, /* 652 */
    0, /* 653 */
    Tcl_UtfCharComplete, /* 654 */
    Tcl_UtfNext, /* 655 */
    Tcl_UtfPrev, /* 656 */
    Tcl_FSTildeExpand, /* 657 */
    Tcl_ExternalToUtfDStringEx, /* 658 */
    Tcl_UtfToExternalDStringEx, /* 659 */
    Tcl_AsyncMarkFromSignal, /* 660 */
    0, /* 661 */
    0, /* 662 */
    0, /* 663 */
    0, /* 664 */
Changes to generic/tclTest.c.
304
305
306
307
308
309
310

311
312
313
314
315
316
317
static Tcl_CmdProc	Testset2Cmd;
static Tcl_CmdProc	TestseterrorcodeCmd;
static Tcl_ObjCmdProc	TestsetobjerrorcodeCmd;
static Tcl_CmdProc	TestsetplatformCmd;
static Tcl_ObjCmdProc	TestSizeCmd;
static Tcl_CmdProc	TeststaticlibraryCmd;
static Tcl_CmdProc	TesttranslatefilenameCmd;

static Tcl_CmdProc	TestupvarCmd;
static Tcl_ObjCmdProc2	TestWrongNumArgsObjCmd;
static Tcl_ObjCmdProc	TestGetIndexFromObjStructObjCmd;
static Tcl_CmdProc	TestChannelCmd;
static Tcl_CmdProc	TestChannelEventCmd;
static Tcl_CmdProc	TestSocketCmd;
static Tcl_ObjCmdProc	TestFilesystemObjCmd;







>







304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
static Tcl_CmdProc	Testset2Cmd;
static Tcl_CmdProc	TestseterrorcodeCmd;
static Tcl_ObjCmdProc	TestsetobjerrorcodeCmd;
static Tcl_CmdProc	TestsetplatformCmd;
static Tcl_ObjCmdProc	TestSizeCmd;
static Tcl_CmdProc	TeststaticlibraryCmd;
static Tcl_CmdProc	TesttranslatefilenameCmd;
static Tcl_ObjCmdProc	TestfstildeexpandCmd;
static Tcl_CmdProc	TestupvarCmd;
static Tcl_ObjCmdProc2	TestWrongNumArgsObjCmd;
static Tcl_ObjCmdProc	TestGetIndexFromObjStructObjCmd;
static Tcl_CmdProc	TestChannelCmd;
static Tcl_CmdProc	TestChannelEventCmd;
static Tcl_CmdProc	TestSocketCmd;
static Tcl_ObjCmdProc	TestFilesystemObjCmd;
722
723
724
725
726
727
728


729
730
731
732
733
734
735
    Tcl_CreateObjCommand(interp, "testsize", TestSizeCmd, NULL, NULL);
    Tcl_CreateCommand(interp, "testsocket", TestSocketCmd,
	    NULL, NULL);
    Tcl_CreateCommand(interp, "teststaticlibrary", TeststaticlibraryCmd,
	    NULL, NULL);
    Tcl_CreateCommand(interp, "testtranslatefilename",
	    TesttranslatefilenameCmd, NULL, NULL);


    Tcl_CreateCommand(interp, "testupvar", TestupvarCmd, NULL, NULL);
    Tcl_CreateCommand(interp, "testmainthread", TestmainthreadCmd, NULL,
	    NULL);
    Tcl_CreateCommand(interp, "testsetmainloop", TestsetmainloopCmd,
	    NULL, NULL);
    Tcl_CreateCommand(interp, "testexitmainloop", TestexitmainloopCmd,
	    NULL, NULL);







>
>







723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
    Tcl_CreateObjCommand(interp, "testsize", TestSizeCmd, NULL, NULL);
    Tcl_CreateCommand(interp, "testsocket", TestSocketCmd,
	    NULL, NULL);
    Tcl_CreateCommand(interp, "teststaticlibrary", TeststaticlibraryCmd,
	    NULL, NULL);
    Tcl_CreateCommand(interp, "testtranslatefilename",
	    TesttranslatefilenameCmd, NULL, NULL);
    Tcl_CreateObjCommand(interp, "testfstildeexpand",
	    TestfstildeexpandCmd, NULL, NULL);
    Tcl_CreateCommand(interp, "testupvar", TestupvarCmd, NULL, NULL);
    Tcl_CreateCommand(interp, "testmainthread", TestmainthreadCmd, NULL,
	    NULL);
    Tcl_CreateCommand(interp, "testsetmainloop", TestsetmainloopCmd,
	    NULL, NULL);
    Tcl_CreateCommand(interp, "testexitmainloop", TestexitmainloopCmd,
	    NULL, NULL);
4935
4936
4937
4938
4939
4940
4941







































4942
4943
4944
4945
4946
4947
4948
    if (result == NULL) {
	return TCL_ERROR;
    }
    Tcl_AppendResult(interp, result, (char *)NULL);
    Tcl_DStringFree(&buffer);
    return TCL_OK;
}








































/*
 *----------------------------------------------------------------------
 *
 * TestupvarCmd --
 *
 *	This procedure implements the "testupvar" command.  It is used







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







4938
4939
4940
4941
4942
4943
4944
4945
4946
4947
4948
4949
4950
4951
4952
4953
4954
4955
4956
4957
4958
4959
4960
4961
4962
4963
4964
4965
4966
4967
4968
4969
4970
4971
4972
4973
4974
4975
4976
4977
4978
4979
4980
4981
4982
4983
4984
4985
4986
4987
4988
4989
4990
    if (result == NULL) {
	return TCL_ERROR;
    }
    Tcl_AppendResult(interp, result, (char *)NULL);
    Tcl_DStringFree(&buffer);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TestfstildeexpandCmd --
 *
 *	This procedure implements the "testfstildeexpand" command.
 *	It is used to test the Tcl_FSTildeExpand command. It differs
 *      from the script level "file tildeexpand" tests because of a
 *	slightly different code path.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
TestfstildeexpandCmd(
    TCL_UNUSED(void *),
    Tcl_Interp *interp,		/* Current interpreter. */
    int objc,			/* Number of arguments. */
    Tcl_Obj *const objv[])	/* The argument objects. */
{
    Tcl_DString buffer;

    if (objc != 2) {
	Tcl_WrongNumArgs(interp, 1, objv, "PATH");
	return TCL_ERROR;
    }
    if (Tcl_FSTildeExpand(interp, Tcl_GetString(objv[1]), &buffer) != TCL_OK) {
	return TCL_ERROR;
    }
    Tcl_SetObjResult(interp, Tcl_DStringToObj(&buffer));
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TestupvarCmd --
 *
 *	This procedure implements the "testupvar" command.  It is used
Changes to tests/fCmd.test.
82
83
84
85
86
87
88

89
90
91
92
93
94
95
testConstraint notDarwin9 [expr {![testConstraint darwin9]}]
testConstraint notContinuousIntegration [expr {![info exists ::env(CI)]}]

testConstraint fileSharing 0
testConstraint notFileSharing 1
testConstraint linkFile 1
testConstraint linkDirectory 1


# Several tests require need to match results against the Unix username
set user {}
if {[testConstraint unix]} {
    catch {
	set user [exec whoami]
    }







>







82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
testConstraint notDarwin9 [expr {![testConstraint darwin9]}]
testConstraint notContinuousIntegration [expr {![info exists ::env(CI)]}]

testConstraint fileSharing 0
testConstraint notFileSharing 1
testConstraint linkFile 1
testConstraint linkDirectory 1
testConstraint testfstildeexpand [llength [info commands testfstildeexpand]]

# Several tests require need to match results against the Unix username
set user {}
if {[testConstraint unix]} {
    catch {
	set user [exec whoami]
    }
2665
2666
2667
2668
2669
2670
2671



2672
2673



2674
2675
2676
2677
2678
2679
2680







2681
2682
2683
2684
2685
2686
2687
2688








2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700











2701
2702
2703
2704
2705
2706






2707
2708



2709
2710
2711
2712



2713
2714



2715
2716
2717
2718
2719
2720
2721






2722
2723



2724
2725
2726



2727
2728
2729



2730
2731
2732



2733
2734
2735



2736
2737
2738



2739
2740
2741
2742
2743
2744






2745
2746
2747
2748
2749
2750
2751







2752
2753
2754
2755
2756
2757
2758
    set ::env(HOME) [file join $::env(HOME) foo]
} -cleanup {
    set ::env(HOME) [file dirname $::env(HOME)]
} -body {
    string tolower [file home $::tcl_platform(user)]
} -match glob -result [gethomedirglob $::tcl_platform(user)]




test fCmd-32.1 {file tildeexpand ~} -body {
    file tildeexpand ~



} -result [file join $::env(HOME)]
test fCmd-32.2 {file tildeexpand ~ - obeys env} -setup {
    set ::env(HOME) $::env(HOME)/xxx
} -cleanup {
    set ::env(HOME) [file dirname $::env(HOME)]
} -body {
    file tildeexpand ~







} -result [file join $::env(HOME) xxx]
test fCmd-32.3 {file tildeexpand ~ - error} -setup {
    set saved $::env(HOME)
    unset ::env(HOME)
} -cleanup {
    set ::env(HOME) $saved
} -body {
    file tildeexpand ~








} -returnCodes error -result {couldn't find HOME environment variable to expand path}
test fCmd-32.4 {
    file tildeexpand ~ - relative path. Following 8.x ~ expansion behavior, relative
    paths are not made absolute
} -setup {
    set saved $::env(HOME)
    set ::env(HOME) relative/path
} -cleanup {
    set ::env(HOME) $saved
} -body {
    file tildeexpand ~
} -result relative/path











test fCmd-32.5 {file tildeexpand ~USER} -body {
    # Note - as in 8.x this form does NOT necessarily give same result as
    # env(HOME) even when user is current user. Assume result contains user
    # name, else not sure how to check
    string tolower [file tildeexpand ~$::tcl_platform(user)]
} -match glob -result [gethomedirglob $::tcl_platform(user)]






test fCmd-32.6 {file tildeexpand ~UNKNOWNUSER} -body {
    file tildeexpand ~nosuchuser



} -returnCodes error -result {user "nosuchuser" doesn't exist}
test fCmd-32.7 {file tildeexpand ~extra arg} -body {
    file tildeexpand ~ arg
} -returnCodes error -result {wrong # args: should be "file tildeexpand path"}



test fCmd-32.8 {file tildeexpand ~/path} -body {
    file tildeexpand ~/foo



} -result [file join $::env(HOME)/foo]
test fCmd-32.9 {file tildeexpand ~USER/bar} -body {
    # Note - as in 8.x this form does NOT necessarily give same result as
    # env(HOME) even when user is current user. Assume result contains user
    # name, else not sure how to check
    string tolower [file tildeexpand ~$::tcl_platform(user)/bar]
} -match glob -result [file join [gethomedirglob $::tcl_platform(user)] bar]






test fCmd-32.10 {file tildeexpand ~UNKNOWNUSER} -body {
    file tildeexpand ~nosuchuser/foo



} -returnCodes error -result {user "nosuchuser" doesn't exist}
test fCmd-32.11 {file tildeexpand /~/path} -body {
    file tildeexpand /~/foo



} -result /~/foo
test fCmd-32.12 {file tildeexpand /~user/path} -body {
    file tildeexpand /~$::tcl_platform(user)/foo



} -result /~$::tcl_platform(user)/foo
test fCmd-32.13 {file tildeexpand ./~} -body {
    file tildeexpand ./~



} -result ./~
test fCmd-32.14 {file tildeexpand relative/path} -body {
    file tildeexpand relative/path



} -result relative/path
test fCmd-32.15 {file tildeexpand ~\\path} -body {
    file tildeexpand ~\\foo



} -constraints win -result [file join $::env(HOME)/foo]
test fCmd-32.16 {file tildeexpand ~USER\\bar} -body {
    # Note - as in 8.x this form does NOT necessarily give same result as
    # env(HOME) even when user is current user. Assume result contains user
    # name, else not sure how to check
    string tolower [file tildeexpand ~$::tcl_platform(user)\\bar]






} -constraints win -match glob -result [file join [gethomedirglob $::tcl_platform(user)] bar]
test fCmd-32.17 {file tildeexpand ~USER does not mirror HOME} -setup {
    set ::env(HOME) [file join $::env(HOME) foo]
} -cleanup {
    set ::env(HOME) [file dirname $::env(HOME)]
} -body {
    string tolower [file tildeexpand ~$::tcl_platform(user)]







} -match glob -result [gethomedirglob $::tcl_platform(user)]


# cleanup
cleanup
if {[testConstraint unix]} {
    removeDirectory tcl[pid] /tmp







>
>
>


>
>
>







>
>
>
>
>
>
>








>
>
>
>
>
>
>
>












>
>
>
>
>
>
>
>
>
>
>






>
>
>
>
>
>


>
>
>




>
>
>


>
>
>







>
>
>
>
>
>


>
>
>



>
>
>



>
>
>



>
>
>



>
>
>



>
>
>






>
>
>
>
>
>







>
>
>
>
>
>
>







2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
    set ::env(HOME) [file join $::env(HOME) foo]
} -cleanup {
    set ::env(HOME) [file dirname $::env(HOME)]
} -body {
    string tolower [file home $::tcl_platform(user)]
} -match glob -result [gethomedirglob $::tcl_platform(user)]

# file tildeexpand and testfstildexpand are identical in behavior
# but tested separately as the former is a script wrapper that does some
# sanitization/optimization while the latter is a raw call to Tcl_FSTildeExpand.
test fCmd-32.1 {file tildeexpand ~} -body {
    file tildeexpand ~
} -result [file join $::env(HOME)]
test fCmd-32.1.1 {Tcl_FSTildeExpand ~} -constraints testfstildeexpand -body {
    testfstildeexpand ~
} -result [file join $::env(HOME)]
test fCmd-32.2 {file tildeexpand ~ - obeys env} -setup {
    set ::env(HOME) $::env(HOME)/xxx
} -cleanup {
    set ::env(HOME) [file dirname $::env(HOME)]
} -body {
    file tildeexpand ~
} -result [file join $::env(HOME) xxx]
test fCmd-32.2.1 {Tcl_FSTildeExpand ~ - obeys env} -setup {
    set ::env(HOME) $::env(HOME)/xxx
} -cleanup {
    set ::env(HOME) [file dirname $::env(HOME)]
} -constraints testfstildeexpand -body {
    testfstildeexpand ~
} -result [file join $::env(HOME) xxx]
test fCmd-32.3 {file tildeexpand ~ - error} -setup {
    set saved $::env(HOME)
    unset ::env(HOME)
} -cleanup {
    set ::env(HOME) $saved
} -body {
    file tildeexpand ~
} -returnCodes error -result {couldn't find HOME environment variable to expand path}
test fCmd-32.3.1 {Tcl_FSTildeExpand ~ - error} -setup {
    set saved $::env(HOME)
    unset ::env(HOME)
} -cleanup {
    set ::env(HOME) $saved
} -constraints testfstildeexpand -body {
    testfstildeexpand ~
} -returnCodes error -result {couldn't find HOME environment variable to expand path}
test fCmd-32.4 {
    file tildeexpand ~ - relative path. Following 8.x ~ expansion behavior, relative
    paths are not made absolute
} -setup {
    set saved $::env(HOME)
    set ::env(HOME) relative/path
} -cleanup {
    set ::env(HOME) $saved
} -body {
    file tildeexpand ~
} -result relative/path
test fCmd-32.4.1 {
    Tcl_FSTildeExpand ~ - relative path. Following 8.x ~ expansion behavior, relative
    paths are not made absolute
} -setup {
    set saved $::env(HOME)
    set ::env(HOME) relative/path
} -cleanup {
    set ::env(HOME) $saved
} -constraints testfstildeexpand -body {
    testfstildeexpand ~
} -result relative/path
test fCmd-32.5 {file tildeexpand ~USER} -body {
    # Note - as in 8.x this form does NOT necessarily give same result as
    # env(HOME) even when user is current user. Assume result contains user
    # name, else not sure how to check
    string tolower [file tildeexpand ~$::tcl_platform(user)]
} -match glob -result [gethomedirglob $::tcl_platform(user)]
test fCmd-32.5.1 {Tcl_FSTildeExpand ~USER} -constraints testfstildeexpand -body {
    # Note - as in 8.x this form does NOT necessarily give same result as
    # env(HOME) even when user is current user. Assume result contains user
    # name, else not sure how to check
    string tolower [testfstildeexpand ~$::tcl_platform(user)]
} -match glob -result [gethomedirglob $::tcl_platform(user)]
test fCmd-32.6 {file tildeexpand ~UNKNOWNUSER} -body {
    file tildeexpand ~nosuchuser
} -returnCodes error -result {user "nosuchuser" doesn't exist}
test fCmd-32.6.1 {Tcl_FSTildeExpand ~UNKNOWNUSER} -constraints testfstildeexpand -body {
    testfstildeexpand ~nosuchuser
} -returnCodes error -result {user "nosuchuser" doesn't exist}
test fCmd-32.7 {file tildeexpand ~extra arg} -body {
    file tildeexpand ~ arg
} -returnCodes error -result {wrong # args: should be "file tildeexpand path"}
test fCmd-32.7.1 {Tcl_FSTildeExpand ~extra arg} -constraints testfstildeexpand -body {
    testfstildeexpand ~ arg
} -returnCodes error -result {wrong # args: should be "testfstildeexpand PATH"}
test fCmd-32.8 {file tildeexpand ~/path} -body {
    file tildeexpand ~/foo
} -result [file join $::env(HOME)/foo]
test fCmd-32.8.1 {Tcl_FSTildeExpand ~/path} -constraints testfstildeexpand -body {
    testfstildeexpand ~/foo
} -result [file join $::env(HOME)/foo]
test fCmd-32.9 {file tildeexpand ~USER/bar} -body {
    # Note - as in 8.x this form does NOT necessarily give same result as
    # env(HOME) even when user is current user. Assume result contains user
    # name, else not sure how to check
    string tolower [file tildeexpand ~$::tcl_platform(user)/bar]
} -match glob -result [file join [gethomedirglob $::tcl_platform(user)] bar]
test fCmd-32.9.1 {Tcl_FSTildeExpand ~USER/bar} -constraints testfstildeexpand -body {
    # Note - as in 8.x this form does NOT necessarily give same result as
    # env(HOME) even when user is current user. Assume result contains user
    # name, else not sure how to check
    string tolower [testfstildeexpand ~$::tcl_platform(user)/bar]
} -match glob -result [file join [gethomedirglob $::tcl_platform(user)] bar]
test fCmd-32.10 {file tildeexpand ~UNKNOWNUSER} -body {
    file tildeexpand ~nosuchuser/foo
} -returnCodes error -result {user "nosuchuser" doesn't exist}
test fCmd-32.10.1 {Tcl_FSTildeExpand ~UNKNOWNUSER} -constraints testfstildeexpand -body {
    testfstildeexpand ~nosuchuser/foo
} -returnCodes error -result {user "nosuchuser" doesn't exist}
test fCmd-32.11 {file tildeexpand /~/path} -body {
    file tildeexpand /~/foo
} -result /~/foo
test fCmd-32.11.1 {Tcl_FSTildeExpand /~/path} -constraints testfstildeexpand -body {
    testfstildeexpand /~/foo
} -result /~/foo
test fCmd-32.12 {file tildeexpand /~user/path} -body {
    file tildeexpand /~$::tcl_platform(user)/foo
} -result /~$::tcl_platform(user)/foo
test fCmd-32.12.1 {Tcl_FSTildeExpand /~user/path} -constraints testfstildeexpand -body {
    testfstildeexpand /~$::tcl_platform(user)/foo
} -result /~$::tcl_platform(user)/foo
test fCmd-32.13 {file tildeexpand ./~} -body {
    file tildeexpand ./~
} -result ./~
test fCmd-32.13.1 {Tcl_FSTildeExpand ./~} -constraints testfstildeexpand -body {
    testfstildeexpand ./~
} -result ./~
test fCmd-32.14 {file tildeexpand relative/path} -body {
    file tildeexpand relative/path
} -result relative/path
test fCmd-32.14.1 {Tcl_FSTildeExpand relative/path} -constraints testfstildeexpand -body {
    testfstildeexpand relative/path
} -result relative/path
test fCmd-32.15 {file tildeexpand ~\\path} -body {
    file tildeexpand ~\\foo
} -constraints win -result [file join $::env(HOME)/foo]
test fCmd-32.15.1 {Tcl_FSTildeExpand ~\\path} -constraints testfstildeexpand -body {
    testfstildeexpand ~\\foo
} -constraints win -result [file join $::env(HOME)/foo]
test fCmd-32.16 {file tildeexpand ~USER\\bar} -body {
    # Note - as in 8.x this form does NOT necessarily give same result as
    # env(HOME) even when user is current user. Assume result contains user
    # name, else not sure how to check
    string tolower [file tildeexpand ~$::tcl_platform(user)\\bar]
} -constraints win -match glob -result [file join [gethomedirglob $::tcl_platform(user)] bar]
test fCmd-32.16.1 {Tcl_FSTildeExpand ~USER\\bar} -constraints testfstildeexpand -body {
    # Note - as in 8.x this form does NOT necessarily give same result as
    # env(HOME) even when user is current user. Assume result contains user
    # name, else not sure how to check
    string tolower [testfstildeexpand ~$::tcl_platform(user)\\bar]
} -constraints win -match glob -result [file join [gethomedirglob $::tcl_platform(user)] bar]
test fCmd-32.17 {file tildeexpand ~USER does not mirror HOME} -setup {
    set ::env(HOME) [file join $::env(HOME) foo]
} -cleanup {
    set ::env(HOME) [file dirname $::env(HOME)]
} -body {
    string tolower [file tildeexpand ~$::tcl_platform(user)]
} -match glob -result [gethomedirglob $::tcl_platform(user)]
test fCmd-32.17.1 {Tcl_FSTildeExpand ~USER does not mirror HOME} -setup {
    set ::env(HOME) [file join $::env(HOME) foo]
} -cleanup {
    set ::env(HOME) [file dirname $::env(HOME)]
} -constraints testfstildeexpand -body {
    string tolower [testfstildeexpand ~$::tcl_platform(user)]
} -match glob -result [gethomedirglob $::tcl_platform(user)]


# cleanup
cleanup
if {[testConstraint unix]} {
    removeDirectory tcl[pid] /tmp