diff options
Diffstat (limited to 'generic/tclFileName.c')
| -rw-r--r-- | generic/tclFileName.c | 3067 | 
1 files changed, 2002 insertions, 1065 deletions
| diff --git a/generic/tclFileName.c b/generic/tclFileName.c index 06e83a3..5d4702b 100644 --- a/generic/tclFileName.c +++ b/generic/tclFileName.c @@ -1,214 +1,372 @@ -/*  +/*   * tclFileName.c --   * - *	This file contains routines for converting file names betwen - *	native and network form. + *	This file contains routines for converting file names betwen native + *	and network form.   *   * Copyright (c) 1995-1998 Sun Microsystems, Inc.   * Copyright (c) 1998-1999 by Scriptics Corporation.   * - * See the file "license.terms" for information on usage and redistribution - * of this file, and for a DISCLAIMER OF ALL WARRANTIES. - * - * RCS: @(#) $Id: tclFileName.c,v 1.6 1999/04/16 00:46:46 stanton Exp $ + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES.   */  #include "tclInt.h" -#include "tclPort.h"  #include "tclRegexp.h" +#include "tclFileSystem.h" /* For TclGetPathType() */  /* - * The following regular expression matches the root portion of a Windows - * absolute or volume relative path.  It will match both UNC and drive relative - * paths. - */ - -#define WIN_ROOT_PATTERN "^(([a-zA-Z]:)|[/\\\\][/\\\\]+([^/\\\\]+)[/\\\\]+([^/\\\\]+)|([/\\\\]))([/\\\\])*" - -/* - * The following regular expression matches the root portion of a Macintosh - * absolute path.  It will match degenerate Unix-style paths, tilde paths, - * Unix-style paths, and Mac paths. + * The following variable is set in the TclPlatformInit call to one of: + * TCL_PLATFORM_UNIX or TCL_PLATFORM_WINDOWS.   */ -#define MAC_ROOT_PATTERN "^((/+([.][.]?/+)*([.][.]?)?)|(~[^:/]*)(/[^:]*)?|(~[^:]*)(:.*)?|/+([.][.]?/+)*([^:/]+)(/[^:]*)?|([^:]+):.*)$" - -/* - * The following variables are used to hold precompiled regular expressions - * for use in filename matching. - */ - -typedef struct ThreadSpecificData { -    int initialized; -    Tcl_Obj *winRootPatternPtr; -    Tcl_Obj *macRootPatternPtr; -} ThreadSpecificData; - -static Tcl_ThreadDataKey dataKey; +TclPlatformType tclPlatform = TCL_PLATFORM_UNIX;  /* - * The following variable is set in the TclPlatformInit call to one - * of: TCL_PLATFORM_UNIX, TCL_PLATFORM_MAC, or TCL_PLATFORM_WINDOWS. + * Prototypes for local procedures defined in this file:   */ -TclPlatformType tclPlatform = TCL_PLATFORM_UNIX; +static const char *	DoTildeSubst(Tcl_Interp *interp, +			    const char *user, Tcl_DString *resultPtr); +static const char *	ExtractWinRoot(const char *path, +			    Tcl_DString *resultPtr, int offset, +			    Tcl_PathType *typePtr); +static int		SkipToChar(char **stringPtr, int match); +static Tcl_Obj *	SplitWinPath(const char *path); +static Tcl_Obj *	SplitUnixPath(const char *path); +static int		DoGlob(Tcl_Interp *interp, Tcl_Obj *resultPtr, +			    const char *separators, Tcl_Obj *pathPtr, int flags, +			    char *pattern, Tcl_GlobTypeData *types);  /* - * Prototypes for local procedures defined in this file: + * When there is no support for getting the block size of a file in a stat() + * call, use this as a guess. Allow it to be overridden in the platform- + * specific files.   */ -static char *		DoTildeSubst _ANSI_ARGS_((Tcl_Interp *interp, -			    CONST char *user, Tcl_DString *resultPtr)); -static CONST char *	ExtractWinRoot _ANSI_ARGS_((CONST char *path, -			    Tcl_DString *resultPtr, int offset)); -static void		FileNameCleanup _ANSI_ARGS_((ClientData clientData)); -static void		FileNameInit _ANSI_ARGS_((void)); -static int		SkipToChar _ANSI_ARGS_((char **stringPtr, -			    char *match)); -static char *		SplitMacPath _ANSI_ARGS_((CONST char *path, -			    Tcl_DString *bufPtr)); -static char *		SplitWinPath _ANSI_ARGS_((CONST char *path, -			    Tcl_DString *bufPtr)); -static char *		SplitUnixPath _ANSI_ARGS_((CONST char *path, -			    Tcl_DString *bufPtr)); +#if (!defined(HAVE_STRUCT_STAT_ST_BLKSIZE) && !defined(GUESSED_BLOCK_SIZE)) +#define GUESSED_BLOCK_SIZE	1024 +#endif  /*   *----------------------------------------------------------------------   * - * FileNameInit -- + * SetResultLength --   * - *	This procedure initializes the patterns used by this module. + *	Resets the result DString for ExtractWinRoot to accommodate + *	any NT extended path prefixes.   *   * Results:   *	None.   *   * Side effects: - *	Compiles the regular expressions. - * + *	May modify the Tcl_DString.   *----------------------------------------------------------------------   */  static void -FileNameInit() +SetResultLength( +    Tcl_DString *resultPtr, +    int offset, +    int extended)  { -    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); -    if (!tsdPtr->initialized) { -	tsdPtr->initialized = 1; -	tsdPtr->winRootPatternPtr = Tcl_NewStringObj(WIN_ROOT_PATTERN, -1); -	tsdPtr->macRootPatternPtr = Tcl_NewStringObj(MAC_ROOT_PATTERN, -1); -	Tcl_CreateThreadExitHandler(FileNameCleanup, NULL); +    Tcl_DStringSetLength(resultPtr, offset); +    if (extended == 2) { +	TclDStringAppendLiteral(resultPtr, "//?/UNC/"); +    } else if (extended == 1) { +	TclDStringAppendLiteral(resultPtr, "//?/");      }  }  /*   *----------------------------------------------------------------------   * - * FileNameCleanup -- + * ExtractWinRoot --   * - *	This procedure is a Tcl_ExitProc used to clean up the static - *	data structures used in this file. + *	Matches the root portion of a Windows path and appends it to the + *	specified Tcl_DString.   *   * Results: - *	None. + *	Returns the position in the path immediately after the root including + *	any trailing slashes. Appends a cleaned up version of the root to the + *	Tcl_DString at the specified offest.   *   * Side effects: - *	Deallocates storage used by the procedures in this file. + *	Modifies the specified Tcl_DString.   *   *----------------------------------------------------------------------   */ -static void -FileNameCleanup(clientData) -    ClientData clientData;	/* Not used. */ +static const char * +ExtractWinRoot( +    const char *path,		/* Path to parse. */ +    Tcl_DString *resultPtr,	/* Buffer to hold result. */ +    int offset,			/* Offset in buffer where result should be +				 * stored. */ +    Tcl_PathType *typePtr)	/* Where to store pathType result */  { -    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); -    Tcl_DecrRefCount(tsdPtr->winRootPatternPtr); -    Tcl_DecrRefCount(tsdPtr->macRootPatternPtr); -    tsdPtr->initialized = 0; +    int extended = 0; + +    if (   (path[0] == '/' || path[0] == '\\') +	&& (path[1] == '/' || path[1] == '\\') +	&& (path[2] == '?') +	&& (path[3] == '/' || path[3] == '\\')) { +	extended = 1; +	path = path + 4; +	if (path[0] == 'U' && path[1] == 'N' && path[2] == 'C' +	    && (path[3] == '/' || path[3] == '\\')) { +	    extended = 2; +	    path = path + 4; +	} +    } + +    if (path[0] == '/' || path[0] == '\\') { +	/* +	 * Might be a UNC or Vol-Relative path. +	 */ + +	const char *host, *share, *tail; +	int hlen, slen; + +	if (path[1] != '/' && path[1] != '\\') { +	    SetResultLength(resultPtr, offset, extended); +	    *typePtr = TCL_PATH_VOLUME_RELATIVE; +	    TclDStringAppendLiteral(resultPtr, "/"); +	    return &path[1]; +	} +	host = &path[2]; + +	/* +	 * Skip separators. +	 */ + +	while (host[0] == '/' || host[0] == '\\') { +	    host++; +	} + +	for (hlen = 0; host[hlen];hlen++) { +	    if (host[hlen] == '/' || host[hlen] == '\\') { +		break; +	    } +	} +	if (host[hlen] == 0 || host[hlen+1] == 0) { +	    /* +	     * The path given is simply of the form '/foo', '//foo', +	     * '/////foo' or the same with backslashes. If there is exactly +	     * one leading '/' the path is volume relative (see filename man +	     * page). If there are more than one, we are simply assuming they +	     * are superfluous and we trim them away. (An alternative +	     * interpretation would be that it is a host name, but we have +	     * been documented that that is not the case). +	     */ + +	    *typePtr = TCL_PATH_VOLUME_RELATIVE; +	    TclDStringAppendLiteral(resultPtr, "/"); +	    return &path[2]; +	} +	SetResultLength(resultPtr, offset, extended); +	share = &host[hlen]; + +	/* +	 * Skip separators. +	 */ + +	while (share[0] == '/' || share[0] == '\\') { +	    share++; +	} + +	for (slen=0; share[slen]; slen++) { +	    if (share[slen] == '/' || share[slen] == '\\') { +		break; +	    } +	} +	TclDStringAppendLiteral(resultPtr, "//"); +	Tcl_DStringAppend(resultPtr, host, hlen); +	TclDStringAppendLiteral(resultPtr, "/"); +	Tcl_DStringAppend(resultPtr, share, slen); + +	tail = &share[slen]; + +	/* +	 * Skip separators. +	 */ + +	while (tail[0] == '/' || tail[0] == '\\') { +	    tail++; +	} + +	*typePtr = TCL_PATH_ABSOLUTE; +	return tail; +    } else if (*path && path[1] == ':') { +	/* +	 * Might be a drive separator. +	 */ + +	SetResultLength(resultPtr, offset, extended); + +	if (path[2] != '/' && path[2] != '\\') { +	    *typePtr = TCL_PATH_VOLUME_RELATIVE; +	    Tcl_DStringAppend(resultPtr, path, 2); +	    return &path[2]; +	} else { +	    const char *tail = &path[3]; + +	    /* +	     * Skip separators. +	     */ + +	    while (*tail && (tail[0] == '/' || tail[0] == '\\')) { +		tail++; +	    } + +	    *typePtr = TCL_PATH_ABSOLUTE; +	    Tcl_DStringAppend(resultPtr, path, 2); +	    TclDStringAppendLiteral(resultPtr, "/"); + +	    return tail; +	} +    } else { +	int abs = 0; + +	/* +	 * Check for Windows devices. +	 */ + +	if ((path[0] == 'c' || path[0] == 'C') +		&& (path[1] == 'o' || path[1] == 'O')) { +	    if ((path[2] == 'm' || path[2] == 'M') +		    && path[3] >= '1' && path[3] <= '4') { +		/* +		 * May have match for 'com[1-4]:?', which is a serial port. +		 */ + +		if (path[4] == '\0') { +		    abs = 4; +		} else if (path [4] == ':' && path[5] == '\0') { +		    abs = 5; +		} + +	    } else if ((path[2] == 'n' || path[2] == 'N') && path[3] == '\0') { +		/* +		 * Have match for 'con'. +		 */ + +		abs = 3; +	    } + +	} else if ((path[0] == 'l' || path[0] == 'L') +		&& (path[1] == 'p' || path[1] == 'P') +		&& (path[2] == 't' || path[2] == 'T')) { +	    if (path[3] >= '1' && path[3] <= '3') { +		/* +		 * May have match for 'lpt[1-3]:?' +		 */ + +		if (path[4] == '\0') { +		    abs = 4; +		} else if (path [4] == ':' && path[5] == '\0') { +		    abs = 5; +		} +	    } + +	} else if ((path[0] == 'p' || path[0] == 'P') +		&& (path[1] == 'r' || path[1] == 'R') +		&& (path[2] == 'n' || path[2] == 'N') +		&& path[3] == '\0') { +	    /* +	     * Have match for 'prn'. +	     */ +	    abs = 3; + +	} else if ((path[0] == 'n' || path[0] == 'N') +		&& (path[1] == 'u' || path[1] == 'U') +		&& (path[2] == 'l' || path[2] == 'L') +		&& path[3] == '\0') { +	    /* +	     * Have match for 'nul'. +	     */ + +	    abs = 3; + +	} else if ((path[0] == 'a' || path[0] == 'A') +		&& (path[1] == 'u' || path[1] == 'U') +		&& (path[2] == 'x' || path[2] == 'X') +		&& path[3] == '\0') { +	    /* +	     * Have match for 'aux'. +	     */ + +	    abs = 3; +	} + +	if (abs != 0) { +	    *typePtr = TCL_PATH_ABSOLUTE; +	    SetResultLength(resultPtr, offset, extended); +	    Tcl_DStringAppend(resultPtr, path, abs); +	    return path + abs; +	} +    } + +    /* +     * Anything else is treated as relative. +     */ + +    *typePtr = TCL_PATH_RELATIVE; +    return path;  }  /*   *----------------------------------------------------------------------   * - * ExtractWinRoot -- + * Tcl_GetPathType -- + * + *	Determines whether a given path is relative to the current directory, + *	relative to the current volume, or absolute. + * + *	The objectified Tcl_FSGetPathType should be used in preference to this + *	function (as you can see below, this is just a wrapper around that + *	other function).   * - *	Matches the root portion of a Windows path and appends it - *	to the specified Tcl_DString. - *	   * Results: - *	Returns the position in the path immediately after the root - *	including any trailing slashes. - *	Appends a cleaned up version of the root to the Tcl_DString - *	at the specified offest. + *	Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or + *	TCL_PATH_VOLUME_RELATIVE.   *   * Side effects: - *	Modifies the specified Tcl_DString. + *	None.   *   *----------------------------------------------------------------------   */ -static CONST char * -ExtractWinRoot(path, resultPtr, offset) -    CONST char *path;		/* Path to parse. */ -    Tcl_DString *resultPtr;	/* Buffer to hold result. */ -    int offset;			/* Offset in buffer where result should be -				 * stored. */ +Tcl_PathType +Tcl_GetPathType( +    const char *path)  { -    int length; -    Tcl_RegExp re; -    char *dummy, *tail, *drive, *hostStart, *hostEnd, *shareStart, -	*shareEnd, *lastSlash; -    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); - -    /* -     * Initialize the path name parser for Windows path names. -     */ - -    FileNameInit(); - -    re = Tcl_GetRegExpFromObj(NULL, tsdPtr->winRootPatternPtr, REG_ADVANCED); +    Tcl_PathType type; +    Tcl_Obj *tempObj = Tcl_NewStringObj(path,-1); -    /* -     * Match the root portion of a Windows path name. -     */ - -    if (!Tcl_RegExpExec(NULL, re, path, path)) { -	return path; -    } - -    Tcl_DStringSetLength(resultPtr, offset); - -    Tcl_RegExpRange(re, 0, &dummy, &tail); -    Tcl_RegExpRange(re, 2, &drive, &dummy); -    Tcl_RegExpRange(re, 3, &hostStart, &hostEnd); -    Tcl_RegExpRange(re, 4, &shareStart, &shareEnd); -    Tcl_RegExpRange(re, 6, &lastSlash, &dummy); - -    if (drive != NULL) { -	Tcl_DStringAppend(resultPtr, drive, 2); -	if (lastSlash != NULL) { -	    Tcl_DStringAppend(resultPtr, "/", 1); -	} -    } else if (shareStart != NULL) { -	Tcl_DStringAppend(resultPtr, "//", 2); -	length = hostEnd - hostStart; -	Tcl_DStringAppend(resultPtr, hostStart, length); -	Tcl_DStringAppend(resultPtr, "/", 1); -	length = shareEnd - shareStart; -	Tcl_DStringAppend(resultPtr, shareStart, length); -    } else { -	Tcl_DStringAppend(resultPtr, "/", 1); -    } -    return tail; +    Tcl_IncrRefCount(tempObj); +    type = Tcl_FSGetPathType(tempObj); +    Tcl_DecrRefCount(tempObj); +    return type;  }  /*   *----------------------------------------------------------------------   * - * Tcl_GetPathType -- + * TclpGetNativePathType --   * - *	Determines whether a given path is relative to the current - *	directory, relative to the current volume, or absolute. + *	Determines whether a given path is relative to the current directory, + *	relative to the current volume, or absolute, but ONLY FOR THE NATIVE + *	FILESYSTEM. This function is called from tclIOUtil.c (but needs to be + *	here due to its dependence on static variables/functions in this + *	file). The exported function Tcl_FSGetPathType should be used by + *	extensions. + * + *	Note that '~' paths are always considered TCL_PATH_ABSOLUTE, even + *	though expanding the '~' could lead to any possible path type. This + *	function should therefore be considered a low-level, string + *	manipulation function only -- it doesn't actually do any expansion in + *	making its determination.   *   * Results:   *	Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or @@ -221,103 +379,167 @@ ExtractWinRoot(path, resultPtr, offset)   */  Tcl_PathType -Tcl_GetPathType(path) -    char *path; +TclpGetNativePathType( +    Tcl_Obj *pathPtr,		/* Native path of interest */ +    int *driveNameLengthPtr,	/* Returns length of drive, if non-NULL and +				 * path was absolute */ +    Tcl_Obj **driveNameRef)  { -    ThreadSpecificData *tsdPtr;      Tcl_PathType type = TCL_PATH_ABSOLUTE; -    Tcl_RegExp re; +    int pathLen; +    const char *path = Tcl_GetStringFromObj(pathPtr, &pathLen); -    switch (tclPlatform) { -   	case TCL_PLATFORM_UNIX: -	    /* -	     * Paths that begin with / or ~ are absolute. -	     */ +    if (path[0] == '~') { +	/* +	 * This case is common to all platforms. Paths that begin with ~ are +	 * absolute. +	 */ -	    if ((path[0] != '/') && (path[0] != '~')) { -		type = TCL_PATH_RELATIVE; +	if (driveNameLengthPtr != NULL) { +	    const char *end = path + 1; +	    while ((*end != '\0') && (*end != '/')) { +		end++;  	    } -	    break; +	    *driveNameLengthPtr = end - path; +	} +    } else { +	switch (tclPlatform) { +	case TCL_PLATFORM_UNIX: { +	    const char *origPath = path; -	case TCL_PLATFORM_MAC: -	    if (path[0] == ':') { -		type = TCL_PATH_RELATIVE; -	    } else if (path[0] != '~') { -		tsdPtr = TCL_TSD_INIT(&dataKey); +	    /* +	     * Paths that begin with / are absolute. +	     */ +	    if (path[0] == '/') { +		++path; +#if defined(__CYGWIN__) || defined(__QNX__)  		/* -		 * Since we have eliminated the easy cases, use the -		 * root pattern to look for the other types. +		 * Check for "//" network path prefix  		 */ - -		FileNameInit(); -		re = Tcl_GetRegExpFromObj(NULL, tsdPtr->macRootPatternPtr, -			REG_ADVANCED); - -		if (!Tcl_RegExpExec(NULL, re, path, path)) { -		    type = TCL_PATH_RELATIVE; -		} else { -		    char *unixRoot, *dummy; - -		    Tcl_RegExpRange(re, 2, &unixRoot, &dummy); -		    if (unixRoot) { -			type = TCL_PATH_RELATIVE; +		if ((*path == '/') && path[1] && (path[1] != '/')) { +		    path += 2; +		    while (*path && *path != '/') { +			++path; +		    } +#if defined(__CYGWIN__) +		    /* UNC paths need to be followed by a share name */ +		    if (*path++ && (*path && *path != '/')) { +			++path; +			while (*path && *path != '/') { +			    ++path; +			} +		    } else { +			path = origPath + 1;  		    } +#endif +		} +#endif +		if (driveNameLengthPtr != NULL) { +		    /* +		     * We need this addition in case the QNX or Cygwin code was used. +		     */ + +		    *driveNameLengthPtr = (path - origPath);  		} +	    } else { +		type = TCL_PATH_RELATIVE;  	    }  	    break; -	 -	case TCL_PLATFORM_WINDOWS: -	    if (path[0] != '~') { -		tsdPtr = TCL_TSD_INIT(&dataKey); - -		/* -		 * Since we have eliminated the easy cases, check for -		 * drive relative paths using the regular expression. -		 */ - -		FileNameInit(); -		re = Tcl_GetRegExpFromObj(NULL, tsdPtr->winRootPatternPtr, -			REG_ADVANCED); - -		if (Tcl_RegExpExec(NULL, re, path, path)) { -		    char *drive, *dummy, *unixRoot, *lastSlash; - -		    Tcl_RegExpRange(re, 2, &drive, &dummy); -		    Tcl_RegExpRange(re, 5, &unixRoot, &dummy); -		    Tcl_RegExpRange(re, 6, &lastSlash, &dummy); -		     -		    if (unixRoot || (drive && !lastSlash)) { -			type = TCL_PATH_VOLUME_RELATIVE; -		    } -		} else { -		    type = TCL_PATH_RELATIVE; +	} +	case TCL_PLATFORM_WINDOWS: { +	    Tcl_DString ds; +	    const char *rootEnd; + +	    Tcl_DStringInit(&ds); +	    rootEnd = ExtractWinRoot(path, &ds, 0, &type); +	    if ((rootEnd != path) && (driveNameLengthPtr != NULL)) { +		*driveNameLengthPtr = rootEnd - path; +		if (driveNameRef != NULL) { +		    *driveNameRef = TclDStringToObj(&ds); +		    Tcl_IncrRefCount(*driveNameRef);  		}  	    } +	    Tcl_DStringFree(&ds);  	    break; +	} +	}      }      return type;  }  /* + *--------------------------------------------------------------------------- + * + * TclpNativeSplitPath -- + * + *	This function takes the given Tcl_Obj, which should be a valid path, + *	and returns a Tcl List object containing each segment of that path as + *	an element. + * + *	Note this function currently calls the older Split(Plat)Path + *	functions, which require more memory allocation than is desirable. + * + * Results: + *	Returns list object with refCount of zero. If the passed in lenPtr is + *	non-NULL, we use it to return the number of elements in the returned + *	list. + * + * Side effects: + *	None. + * + *--------------------------------------------------------------------------- + */ + +Tcl_Obj * +TclpNativeSplitPath( +    Tcl_Obj *pathPtr,		/* Path to split. */ +    int *lenPtr)		/* int to store number of path elements. */ +{ +    Tcl_Obj *resultPtr = NULL;	/* Needed only to prevent gcc warnings. */ + +    /* +     * Perform platform specific splitting. +     */ + +    switch (tclPlatform) { +    case TCL_PLATFORM_UNIX: +	resultPtr = SplitUnixPath(Tcl_GetString(pathPtr)); +	break; + +    case TCL_PLATFORM_WINDOWS: +	resultPtr = SplitWinPath(Tcl_GetString(pathPtr)); +	break; +    } + +    /* +     * Compute the number of elements in the result. +     */ + +    if (lenPtr != NULL) { +	Tcl_ListObjLength(NULL, resultPtr, lenPtr); +    } +    return resultPtr; +} + +/*   *----------------------------------------------------------------------   *   * Tcl_SplitPath --   * - *	Split a path into a list of path components.  The first element - *	of the list will have the same path type as the original path. + *	Split a path into a list of path components. The first element of the + *	list will have the same path type as the original path.   *   * Results: - *	Returns a standard Tcl result.  The interpreter result contains - *	a list of path components. - *	*argvPtr will be filled in with the address of an array - *	whose elements point to the elements of path, in order. - *	*argcPtr will get filled in with the number of valid elements - *	in the array.  A single block of memory is dynamically allocated - *	to hold both the argv array and a copy of the path elements. - *	The caller must eventually free this memory by calling ckfree() - *	on *argvPtr.  Note:  *argvPtr and *argcPtr are only modified - *	if the procedure returns normally. + *	Returns a standard Tcl result. The interpreter result contains a list + *	of path components. *argvPtr will be filled in with the address of an + *	array whose elements point to the elements of path, in order. + *	*argcPtr will get filled in with the number of valid elements in the + *	array. A single block of memory is dynamically allocated to hold both + *	the argv array and a copy of the path elements. The caller must + *	eventually free this memory by calling ckfree() on *argvPtr. Note: + *	*argvPtr and *argcPtr are only modified if the procedure returns + *	normally.   *   * Side effects:   *	Allocates memory. @@ -326,79 +548,77 @@ Tcl_GetPathType(path)   */  void -Tcl_SplitPath(path, argcPtr, argvPtr) -    CONST char *path;		/* Pointer to string containing a path. */ -    int *argcPtr;		/* Pointer to location to fill in with -				 * the number of elements in the path. */ -    char ***argvPtr;		/* Pointer to place to store pointer to array +Tcl_SplitPath( +    const char *path,		/* Pointer to string containing a path. */ +    int *argcPtr,		/* Pointer to location to fill in with the +				 * number of elements in the path. */ +    const char ***argvPtr)	/* Pointer to place to store pointer to array  				 * of pointers to path elements. */  { -    int i, size; +    Tcl_Obj *resultPtr = NULL;	/* Needed only to prevent gcc warnings. */ +    Tcl_Obj *tmpPtr, *eltPtr; +    int i, size, len;      char *p; -    Tcl_DString buffer; - -    Tcl_DStringInit(&buffer); +    const char *str;      /* -     * Perform platform specific splitting.  These routines will leave the -     * result in the specified buffer.  Individual elements are terminated -     * with a null character. +     * Perform the splitting, using objectified, vfs-aware code.       */ -    p = NULL;			/* Needed only to prevent gcc warnings. */ -    switch (tclPlatform) { -   	case TCL_PLATFORM_UNIX: -	    p = SplitUnixPath(path, &buffer); -	    break; - -	case TCL_PLATFORM_WINDOWS: -	    p = SplitWinPath(path, &buffer); -	    break; -	     -	case TCL_PLATFORM_MAC: -	    p = SplitMacPath(path, &buffer); -	    break; -    } +    tmpPtr = Tcl_NewStringObj(path, -1); +    Tcl_IncrRefCount(tmpPtr); +    resultPtr = Tcl_FSSplitPath(tmpPtr, argcPtr); +    Tcl_IncrRefCount(resultPtr); +    Tcl_DecrRefCount(tmpPtr);      /* -     * Compute the number of elements in the result. +     * Calculate space required for the result.       */ -    size = Tcl_DStringLength(&buffer); -    *argcPtr = 0; -    for (i = 0; i < size; i++) { -	if (p[i] == '\0') { -	    (*argcPtr)++; -	} +    size = 1; +    for (i = 0; i < *argcPtr; i++) { +	Tcl_ListObjIndex(NULL, resultPtr, i, &eltPtr); +	Tcl_GetStringFromObj(eltPtr, &len); +	size += len + 1;      } -     +      /* -     * Allocate a buffer large enough to hold the contents of the -     * DString plus the argv pointers and the terminating NULL pointer. +     * Allocate a buffer large enough to hold the contents of all of the list +     * plus the argv pointers and the terminating NULL pointer.       */ -    *argvPtr = (char **) ckalloc((unsigned) -	    ((((*argcPtr) + 1) * sizeof(char *)) + size)); +    *argvPtr = ckalloc((((*argcPtr) + 1) * sizeof(char *)) + size);      /* -     * Position p after the last argv pointer and copy the contents of -     * the DString. +     * Position p after the last argv pointer and copy the contents of the +     * list in, piece by piece.       */      p = (char *) &(*argvPtr)[(*argcPtr) + 1]; -    memcpy((VOID *) p, (VOID *) Tcl_DStringValue(&buffer), (size_t) size); +    for (i = 0; i < *argcPtr; i++) { +	Tcl_ListObjIndex(NULL, resultPtr, i, &eltPtr); +	str = Tcl_GetStringFromObj(eltPtr, &len); +	memcpy(p, str, (size_t) len+1); +	p += len+1; +    }      /*       * Now set up the argv pointers.       */ +    p = (char *) &(*argvPtr)[(*argcPtr) + 1]; +      for (i = 0; i < *argcPtr; i++) {  	(*argvPtr)[i] = p; -	while ((*p++) != '\0') {} +	for (; *(p++)!='\0'; );      }      (*argvPtr)[i] = NULL; -    Tcl_DStringFree(&buffer); +    /* +     * Free the result ptr given to us by Tcl_FSSplitPath +     */ + +    Tcl_DecrRefCount(resultPtr);  }  /* @@ -406,12 +626,11 @@ Tcl_SplitPath(path, argcPtr, argvPtr)   *   * SplitUnixPath --   * - *	This routine is used by Tcl_SplitPath to handle splitting - *	Unix paths. + *	This routine is used by Tcl_(FS)SplitPath to handle splitting Unix + *	paths.   *   * Results: - *	Stores a null separated array of strings in the specified - *	Tcl_DString. + *	Returns a newly allocated Tcl list object.   *   * Side effects:   *	None. @@ -419,48 +638,76 @@ Tcl_SplitPath(path, argcPtr, argvPtr)   *----------------------------------------------------------------------   */ -static char * -SplitUnixPath(path, bufPtr) -    CONST char *path;		/* Pointer to string containing a path. */ -    Tcl_DString *bufPtr;	/* Pointer to DString to use for the result. */ +static Tcl_Obj * +SplitUnixPath( +    const char *path)		/* Pointer to string containing a path. */  {      int length; -    CONST char *p, *elementStart; +    const char *origPath = path, *elementStart; +    Tcl_Obj *result = Tcl_NewObj();      /*       * Deal with the root directory as a special case.       */ -    if (path[0] == '/') { -	Tcl_DStringAppend(bufPtr, "/", 2); -	p = path+1; -    } else { -	p = path; +    if (*path == '/') { +	Tcl_Obj *rootElt; +	++path; +#if defined(__CYGWIN__) || defined(__QNX__) +	/* +	 * Check for "//" network path prefix +	 */ +	if ((*path == '/') && path[1] && (path[1] != '/')) { +	    path += 2; +	    while (*path && *path != '/') { +		++path; +	    } +#if defined(__CYGWIN__) +	    /* UNC paths need to be followed by a share name */ +	    if (*path++ && (*path && *path != '/')) { +		++path; +		while (*path && *path != '/') { +		    ++path; +		} +	    } else { +		path = origPath + 1; +	    } +#endif +	} +#endif +	rootElt = Tcl_NewStringObj(origPath, path - origPath); +	Tcl_ListObjAppendElement(NULL, result, rootElt); +	while (*path == '/') { +	    ++path; +	}      }      /* -     * Split on slashes.  Embedded elements that start with tilde will be +     * Split on slashes. Embedded elements that start with tilde will be       * prefixed with "./" so they are not affected by tilde substitution.       */      for (;;) { -	elementStart = p; -	while ((*p != '\0') && (*p != '/')) { -	    p++; +	elementStart = path; +	while ((*path != '\0') && (*path != '/')) { +	    path++;  	} -	length = p - elementStart; +	length = path - elementStart;  	if (length > 0) { -	    if ((elementStart[0] == '~') && (elementStart != path)) { -		Tcl_DStringAppend(bufPtr, "./", 2); +	    Tcl_Obj *nextElt; +	    if ((elementStart[0] == '~') && (elementStart != origPath)) { +		TclNewLiteralStringObj(nextElt, "./"); +		Tcl_AppendToObj(nextElt, elementStart, length); +	    } else { +		nextElt = Tcl_NewStringObj(elementStart, length);  	    } -	    Tcl_DStringAppend(bufPtr, elementStart, length); -	    Tcl_DStringAppend(bufPtr, "", 1); +	    Tcl_ListObjAppendElement(NULL, result, nextElt);  	} -	if (*p++ == '\0') { +	if (*path++ == '\0') {  	    break;  	}      } -    return Tcl_DStringValue(bufPtr); +    return result;  }  /* @@ -468,12 +715,11 @@ SplitUnixPath(path, bufPtr)   *   * SplitWinPath --   * - *	This routine is used by Tcl_SplitPath to handle splitting - *	Windows paths. + *	This routine is used by Tcl_(FS)SplitPath to handle splitting Windows + *	paths.   *   * Results: - *	Stores a null separated array of strings in the specified - *	Tcl_DString. + *	Returns a newly allocated Tcl list object.   *   * Side effects:   *	None. @@ -481,27 +727,32 @@ SplitUnixPath(path, bufPtr)   *----------------------------------------------------------------------   */ -static char * -SplitWinPath(path, bufPtr) -    CONST char *path;		/* Pointer to string containing a path. */ -    Tcl_DString *bufPtr;	/* Pointer to DString to use for the result. */ +static Tcl_Obj * +SplitWinPath( +    const char *path)		/* Pointer to string containing a path. */  {      int length; -    CONST char *p, *elementStart; +    const char *p, *elementStart; +    Tcl_PathType type = TCL_PATH_ABSOLUTE; +    Tcl_DString buf; +    Tcl_Obj *result = Tcl_NewObj(); +    Tcl_DStringInit(&buf); -    p = ExtractWinRoot(path, bufPtr, 0); +    p = ExtractWinRoot(path, &buf, 0, &type);      /*       * Terminate the root portion, if we matched something.       */      if (p != path) { -	Tcl_DStringAppend(bufPtr, "", 1); +	Tcl_ListObjAppendElement(NULL, result, TclDStringToObj(&buf));      } +    Tcl_DStringFree(&buf);      /* -     * Split on slashes.  Embedded elements that start with tilde will be -     * prefixed with "./" so they are not affected by tilde substitution. +     * Split on slashes. Embedded elements that start with tilde or a drive +     * letter will be prefixed with "./" so they are not affected by tilde +     * substitution.       */      do { @@ -511,203 +762,193 @@ SplitWinPath(path, bufPtr)  	}  	length = p - elementStart;  	if (length > 0) { -	    if ((elementStart[0] == '~') && (elementStart != path)) { -		Tcl_DStringAppend(bufPtr, "./", 2); +	    Tcl_Obj *nextElt; +	    if ((elementStart != path) && ((elementStart[0] == '~') +		    || (isalpha(UCHAR(elementStart[0])) +			&& elementStart[1] == ':'))) { +		TclNewLiteralStringObj(nextElt, "./"); +		Tcl_AppendToObj(nextElt, elementStart, length); +	    } else { +		nextElt = Tcl_NewStringObj(elementStart, length);  	    } -	    Tcl_DStringAppend(bufPtr, elementStart, length); -	    Tcl_DStringAppend(bufPtr, "", 1); +	    Tcl_ListObjAppendElement(NULL, result, nextElt);  	}      } while (*p++ != '\0'); -    return Tcl_DStringValue(bufPtr); +    return result;  }  /* - *---------------------------------------------------------------------- + *---------------------------------------------------------------------------   * - * SplitMacPath -- + * Tcl_FSJoinToPath --   * - *	This routine is used by Tcl_SplitPath to handle splitting - *	Macintosh paths. + *	This function takes the given object, which should usually be a valid + *	path or NULL, and joins onto it the array of paths segments given. + * + *	The objects in the array given will temporarily have their refCount + *	increased by one, and then decreased by one when this function exits + *	(which means if they had zero refCount when we were called, they will + *	be freed).   *   * Results: - *	Returns a newly allocated argv array. + *	Returns object owned by the caller (which should increment its + *	refCount) - typically an object with refCount of zero.   *   * Side effects:   *	None.   * - *---------------------------------------------------------------------- + *---------------------------------------------------------------------------   */ -static char * -SplitMacPath(path, bufPtr) -    CONST char *path;		/* Pointer to string containing a path. */ -    Tcl_DString *bufPtr;	/* Pointer to DString to use for the result. */ +Tcl_Obj * +Tcl_FSJoinToPath( +    Tcl_Obj *pathPtr,		/* Valid path or NULL. */ +    int objc,			/* Number of array elements to join */ +    Tcl_Obj *const objv[])	/* Path elements to join. */  { -    int isMac = 0;		/* 1 if is Mac-style, 0 if Unix-style path. */ -    int i, length; -    CONST char *p, *elementStart; -    Tcl_RegExp re; -    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); +    if (pathPtr == NULL) { +	return TclJoinPath(objc, objv); +    } +    if (objc == 0) { +	return TclJoinPath(1, &pathPtr); +    } +    if (objc == 1) { +	Tcl_Obj *pair[2]; -    /* -     * Initialize the path name parser for Macintosh path names. -     */ +	pair[0] = pathPtr; +	pair[1] = objv[0]; +	return TclJoinPath(2, pair); +    } else { +	int elemc = objc + 1; +	Tcl_Obj *ret, **elemv = ckalloc(elemc*sizeof(Tcl_Obj **)); + +	elemv[0] = pathPtr; +	memcpy(elemv+1, objv, objc*sizeof(Tcl_Obj **)); +	ret = TclJoinPath(elemc, elemv); +	ckfree(elemv); +	return ret; +    } +} + +/* + *--------------------------------------------------------------------------- + * + * TclpNativeJoinPath -- + * + *	'prefix' is absolute, 'joining' is relative to prefix. + * + * Results: + *	modifies prefix + * + * Side effects: + *	None. + * + *--------------------------------------------------------------------------- + */ -    FileNameInit(); +void +TclpNativeJoinPath( +    Tcl_Obj *prefix, +    const char *joining) +{ +    int length, needsSep; +    char *dest; +    const char *p; +    const char *start; + +    start = Tcl_GetStringFromObj(prefix, &length);      /* -     * Match the root portion of a Mac path name. +     * Remove the ./ from tilde prefixed elements, and drive-letter prefixed +     * elements on Windows, unless it is the first component.       */ -    i = 0;			/* Needed only to prevent gcc warnings. */ - -    re = Tcl_GetRegExpFromObj(NULL, tsdPtr->macRootPatternPtr, REG_ADVANCED); +    p = joining; -    if (Tcl_RegExpExec(NULL, re, path, path) == 1) { -	char *start, *end; +    if (length != 0) { +	if ((p[0] == '.') && (p[1] == '/') && ((p[2] == '~') +		|| (tclPlatform==TCL_PLATFORM_WINDOWS && isalpha(UCHAR(p[2])) +		&& (p[3] == ':')))) { +	    p += 2; +	} +    } +    if (*p == '\0') { +	return; +    } +    switch (tclPlatform) { +    case TCL_PLATFORM_UNIX:  	/* -	 * Treat degenerate absolute paths like / and /../.. as -	 * Mac relative file names for lack of anything else to do. +	 * Append a separator if needed.  	 */ -	Tcl_RegExpRange(re, 2, &start, &end); -	if (start) { -	    Tcl_DStringAppend(bufPtr, ":", 1); -	    Tcl_RegExpRange(re, 0, &start, &end); -	    Tcl_DStringAppend(bufPtr, path, end - start + 1); -	    return Tcl_DStringValue(bufPtr); +	if (length > 0 && (start[length-1] != '/')) { +	    Tcl_AppendToObj(prefix, "/", 1); +	    Tcl_GetStringFromObj(prefix, &length);  	} +	needsSep = 0; -	Tcl_RegExpRange(re, 5, &start, &end); -	if (start) { -	    /* -	     * Unix-style tilde prefixed paths. -	     */ - -	    isMac = 0; -	    i = 5; -	} else { -	    Tcl_RegExpRange(re, 7, &start, &end); -	    if (start) { -		/* -		 * Mac-style tilde prefixed paths. -		 */ - -		isMac = 1; -		i = 7; -	    } else { -		Tcl_RegExpRange(re, 10, &start, &end); -		if (start) { - -		    /* -		     * Normal Unix style paths. -		     */ - -		    isMac = 0; -		    i = 10; -		} else { -		    Tcl_RegExpRange(re, 12, &start, &end); -		    if (start) { +	/* +	 * Append the element, eliminating duplicate and trailing slashes. +	 */ -			/* -			 * Normal Mac style paths. -			 */ +	Tcl_SetObjLength(prefix, length + (int) strlen(p)); -			isMac = 1; -			i = 12; -		    } +	dest = Tcl_GetString(prefix) + length; +	for (; *p != '\0'; p++) { +	    if (*p == '/') { +		while (p[1] == '/') { +		    p++; +		} +		if (p[1] != '\0' && needsSep) { +		    *dest++ = '/';  		} +	    } else { +		*dest++ = *p; +		needsSep = 1;  	    }  	} +	length = dest - Tcl_GetString(prefix); +	Tcl_SetObjLength(prefix, length); +	break; -	Tcl_RegExpRange(re, i, &start, &end); -	length = end - start; - -	/* -	 * Append the element and terminate it with a : and a null.  Note that -	 * we are forcing the DString to contain an extra null at the end. -	 */ - -	Tcl_DStringAppend(bufPtr, start, length); -	Tcl_DStringAppend(bufPtr, ":", 2); -	p = end; -    } else { -	isMac = (strchr(path, ':') != NULL); -	p = path; -    } -     -    if (isMac) { - +    case TCL_PLATFORM_WINDOWS:  	/* -	 * p is pointing at the first colon in the path.  There -	 * will always be one, since this is a Mac-style path. +	 * Check to see if we need to append a separator.  	 */ -	elementStart = p++; -	while ((p = strchr(p, ':')) != NULL) { -	    length = p - elementStart; -	    if (length == 1) { -		while (*p == ':') { -		    Tcl_DStringAppend(bufPtr, "::", 3); -		    elementStart = p++; -		} -	    } else { -		/* -		 * If this is a simple component, drop the leading colon. -		 */ - -		if ((elementStart[1] != '~') -			&& (strchr(elementStart+1, '/') == NULL)) { -		    elementStart++; -		    length--; -		} -		Tcl_DStringAppend(bufPtr, elementStart, length); -		Tcl_DStringAppend(bufPtr, "", 1); -		elementStart = p++; -	    } -	} -	if (elementStart[1] != '\0' || elementStart == path) { -	    if ((elementStart[1] != '~') && (elementStart[1] != '\0') -			&& (strchr(elementStart+1, '/') == NULL)) { -		    elementStart++; -	    } -	    Tcl_DStringAppend(bufPtr, elementStart, -1); -	    Tcl_DStringAppend(bufPtr, "", 1); +	if ((length > 0) && +		(start[length-1] != '/') && (start[length-1] != ':')) { +	    Tcl_AppendToObj(prefix, "/", 1); +	    Tcl_GetStringFromObj(prefix, &length);  	} -    } else { +	needsSep = 0;  	/* -	 * Split on slashes, suppress extra /'s, and convert .. to ::.  +	 * Append the element, eliminating duplicate and trailing slashes.  	 */ -	for (;;) { -	    elementStart = p; -	    while ((*p != '\0') && (*p != '/')) { -		p++; -	    } -	    length = p - elementStart; -	    if (length > 0) { -		if ((length == 1) && (elementStart[0] == '.')) { -		    Tcl_DStringAppend(bufPtr, ":", 2); -		} else if ((length == 2) && (elementStart[0] == '.') -			&& (elementStart[1] == '.')) { -		    Tcl_DStringAppend(bufPtr, "::", 3); -		} else { -		    if (*elementStart == '~') { -			Tcl_DStringAppend(bufPtr, ":", 1); -		    } -		    Tcl_DStringAppend(bufPtr, elementStart, length); -		    Tcl_DStringAppend(bufPtr, "", 1); +	Tcl_SetObjLength(prefix, length + (int) strlen(p)); +	dest = Tcl_GetString(prefix) + length; +	for (; *p != '\0'; p++) { +	    if ((*p == '/') || (*p == '\\')) { +		while ((p[1] == '/') || (p[1] == '\\')) { +		    p++;  		} -	    } -	    if (*p++ == '\0') { -		break; +		if ((p[1] != '\0') && needsSep) { +		    *dest++ = '/'; +		} +	    } else { +		*dest++ = *p; +		needsSep = 1;  	    }  	} +	length = dest - Tcl_GetString(prefix); +	Tcl_SetObjLength(prefix, length); +	break;      } -    return Tcl_DStringValue(bufPtr); +    return;  }  /* @@ -715,12 +956,13 @@ SplitMacPath(path, bufPtr)   *   * Tcl_JoinPath --   * - *	Combine a list of paths in a platform specific manner. + *	Combine a list of paths in a platform specific manner. The function + *	'Tcl_FSJoinPath' should be used in preference where possible.   *   * Results: - *	Appends the joined path to the end of the specified - *	returning a pointer to the resulting string.  Note that - *	the Tcl_DString must already be initialized. + *	Appends the joined path to the end of the specified Tcl_DString + *	returning a pointer to the resulting string. Note that the + *	Tcl_DString must already be initialized.   *   * Side effects:   *	Modifies the Tcl_DString. @@ -729,204 +971,46 @@ SplitMacPath(path, bufPtr)   */  char * -Tcl_JoinPath(argc, argv, resultPtr) -    int argc; -    char **argv; -    Tcl_DString *resultPtr;	/* Pointer to previously initialized DString. */ +Tcl_JoinPath( +    int argc, +    const char *const *argv, +    Tcl_DString *resultPtr)	/* Pointer to previously initialized DString */  { -    int oldLength, length, i, needsSep; -    Tcl_DString buffer; -    char c, *dest; -    CONST char *p; - -    Tcl_DStringInit(&buffer); -    oldLength = Tcl_DStringLength(resultPtr); - -    switch (tclPlatform) { -   	case TCL_PLATFORM_UNIX: -	    for (i = 0; i < argc; i++) { -		p = argv[i]; -		/* -		 * If the path is absolute, reset the result buffer. -		 * Consume any duplicate leading slashes or a ./ in -		 * front of a tilde prefixed path that isn't at the -		 * beginning of the path. -		 */ - -		if (*p == '/') { -		    Tcl_DStringSetLength(resultPtr, oldLength); -		    Tcl_DStringAppend(resultPtr, "/", 1); -		    while (*p == '/') { -			p++; -		    } -		} else if (*p == '~') { -		    Tcl_DStringSetLength(resultPtr, oldLength); -		} else if ((Tcl_DStringLength(resultPtr) != oldLength) -			&& (p[0] == '.') && (p[1] == '/') -			&& (p[2] == '~')) { -		    p += 2; -		} - -		if (*p == '\0') { -		    continue; -		} - -		/* -		 * Append a separator if needed. -		 */ - -		length = Tcl_DStringLength(resultPtr); -		if ((length != oldLength) -			&& (Tcl_DStringValue(resultPtr)[length-1] != '/')) { -		    Tcl_DStringAppend(resultPtr, "/", 1); -		    length++; -		} +    int i, len; +    Tcl_Obj *listObj = Tcl_NewObj(); +    Tcl_Obj *resultObj; +    const char *resultStr; -		/* -		 * Append the element, eliminating duplicate and trailing -		 * slashes. -		 */ - -		Tcl_DStringSetLength(resultPtr, (int) (length + strlen(p))); -		dest = Tcl_DStringValue(resultPtr) + length; -		for (; *p != '\0'; p++) { -		    if (*p == '/') { -			while (p[1] == '/') { -			    p++; -			} -			if (p[1] != '\0') { -			    *dest++ = '/'; -			} -		    } else { -			*dest++ = *p; -		    } -		} -		length = dest - Tcl_DStringValue(resultPtr); -		Tcl_DStringSetLength(resultPtr, length); -	    } -	    break; - -	case TCL_PLATFORM_WINDOWS: -	    /* -	     * Iterate over all of the components.  If a component is -	     * absolute, then reset the result and start building the -	     * path from the current component on. -	     */ - -	    for (i = 0; i < argc; i++) { -		p = ExtractWinRoot(argv[i], resultPtr, oldLength); -		length = Tcl_DStringLength(resultPtr); -		 -		/* -		 * If the pointer didn't move, then this is a relative path -		 * or a tilde prefixed path. -		 */ - -		if (p == argv[i]) { -		    /* -		     * Remove the ./ from tilde prefixed elements unless -		     * it is the first component. -		     */ - -		    if ((length != oldLength) -			    && (p[0] == '.') -			    && ((p[1] == '/') || (p[1] == '\\')) -			    && (p[2] == '~')) { -			p += 2; -		    } else if (*p == '~') { -			Tcl_DStringSetLength(resultPtr, oldLength); -			length = oldLength; -		    } -		} +    /* +     * Build the list of paths. +     */ -		if (*p != '\0') { -		    /* -		     * Check to see if we need to append a separator. -		     */ +    for (i = 0; i < argc; i++) { +	Tcl_ListObjAppendElement(NULL, listObj, +		Tcl_NewStringObj(argv[i], -1)); +    } -		     -		    if (length != oldLength) { -			c = Tcl_DStringValue(resultPtr)[length-1]; -			if ((c != '/') && (c != ':')) { -			    Tcl_DStringAppend(resultPtr, "/", 1); -			} -		    } +    /* +     * Ask the objectified code to join the paths. +     */ -		    /* -		     * Append the element, eliminating duplicate and -		     * trailing slashes. -		     */ +    Tcl_IncrRefCount(listObj); +    resultObj = Tcl_FSJoinPath(listObj, argc); +    Tcl_IncrRefCount(resultObj); +    Tcl_DecrRefCount(listObj); -		    length = Tcl_DStringLength(resultPtr); -		    Tcl_DStringSetLength(resultPtr, (int) (length + strlen(p))); -		    dest = Tcl_DStringValue(resultPtr) + length; -		    for (; *p != '\0'; p++) { -			if ((*p == '/') || (*p == '\\')) { -			    while ((p[1] == '/') || (p[1] == '\\')) { -				p++; -			    } -			    if (p[1] != '\0') { -				*dest++ = '/'; -			    } -			} else { -			    *dest++ = *p; -			} -		    } -		    length = dest - Tcl_DStringValue(resultPtr); -		    Tcl_DStringSetLength(resultPtr, length); -		} -	    } -	    break; +    /* +     * Store the result. +     */ -	case TCL_PLATFORM_MAC: -	    needsSep = 1; -	    for (i = 0; i < argc; i++) { -		Tcl_DStringSetLength(&buffer, 0); -		p = SplitMacPath(argv[i], &buffer); -		if ((*p != ':') && (*p != '\0') -			&& (strchr(p, ':') != NULL)) { -		    Tcl_DStringSetLength(resultPtr, oldLength); -		    length = strlen(p); -		    Tcl_DStringAppend(resultPtr, p, length); -		    needsSep = 0; -		    p += length+1; -		} +    resultStr = Tcl_GetStringFromObj(resultObj, &len); +    Tcl_DStringAppend(resultPtr, resultStr, len); +    Tcl_DecrRefCount(resultObj); -		/* -		 * Now append the rest of the path elements, skipping -		 * : unless it is the first element of the path, and -		 * watching out for :: et al. so we don't end up with -		 * too many colons in the result. -		 */ +    /* +     * Return a pointer to the result. +     */ -		for (; *p != '\0'; p += length+1) { -		    if (p[0] == ':' && p[1] == '\0') { -			if (Tcl_DStringLength(resultPtr) != oldLength) { -			    p++; -			} else { -			    needsSep = 0; -			} -		    } else { -			c = p[1]; -			if (*p == ':') { -			    if (!needsSep) { -				p++; -			    } -			} else { -			    if (needsSep) { -				Tcl_DStringAppend(resultPtr, ":", 1); -			    } -			} -			needsSep = (c == ':') ? 0 : 1; -		    } -		    length = strlen(p); -		    Tcl_DStringAppend(resultPtr, p, length); -		} -	    } -	    break; -			        -    } -    Tcl_DStringFree(&buffer);      return Tcl_DStringValue(resultPtr);  } @@ -936,19 +1020,19 @@ Tcl_JoinPath(argc, argv, resultPtr)   * Tcl_TranslateFileName --   *   *	Converts a file name into a form usable by the native system - *	interfaces.  If the name starts with a tilde, it will produce a - *	name where the tilde and following characters have been replaced - *	by the home directory location for the named user. + *	interfaces. If the name starts with a tilde, it will produce a name + *	where the tilde and following characters have been replaced by the + *	home directory location for the named user.   *   * Results: - *	The return value is a pointer to a string containing the name - *	after tilde substitution.  If there was no tilde substitution, - *	the return value is a pointer to a copy of the original string. - *	If there was an error in processing the name, then an error - *	message is left in the interp's result (if interp was not NULL) - *	and the return value is NULL.  Space for the return value is - *	allocated in bufferPtr; the caller must call Tcl_DStringFree() - *	to free the space if the return value was not NULL. + *	The return value is a pointer to a string containing the name after + *	tilde substitution. If there was no tilde substitution, the return + *	value is a pointer to a copy of the original string. If there was an + *	error in processing the name, then an error message is left in the + *	interp's result (if interp was not NULL) and the return value is NULL. + *	Space for the return value is allocated in bufferPtr; the caller must + *	call Tcl_DStringFree() to free the space if the return value was not + *	NULL.   *   * Side effects:   *	None. @@ -957,67 +1041,45 @@ Tcl_JoinPath(argc, argv, resultPtr)   */  char * -Tcl_TranslateFileName(interp, name, bufferPtr) -    Tcl_Interp *interp;		/* Interpreter in which to store error -				 * message (if necessary). */ -    char *name;			/* File name, which may begin with "~" (to +Tcl_TranslateFileName( +    Tcl_Interp *interp,		/* Interpreter in which to store error message +				 * (if necessary). */ +    const char *name,		/* File name, which may begin with "~" (to  				 * indicate current user's home directory) or  				 * "~<user>" (to indicate any user's home  				 * directory). */ -    Tcl_DString *bufferPtr;	/* Uninitialized or free DString filled -				 * with name after tilde substitution. */ +    Tcl_DString *bufferPtr)	/* Uninitialized or free DString filled with +				 * name after tilde substitution. */  { -    register char *p; - -    /* -     * Handle tilde substitutions, if needed. -     */ - -    if (name[0] == '~') { -	int argc, length; -	char **argv; -	Tcl_DString temp; - -	Tcl_SplitPath(name, &argc, &argv); -	 -	/* -	 * Strip the trailing ':' off of a Mac path before passing the user -	 * name to DoTildeSubst. -	 */ - -	if (tclPlatform == TCL_PLATFORM_MAC) { -	    length = strlen(argv[0]); -	    argv[0][length-1] = '\0'; -	} -	 -	Tcl_DStringInit(&temp); -	argv[0] = DoTildeSubst(interp, argv[0]+1, &temp); -	if (argv[0] == NULL) { -	    Tcl_DStringFree(&temp); -	    ckfree((char *)argv); -	    return NULL; -	} -	Tcl_DStringInit(bufferPtr); -	Tcl_JoinPath(argc, argv, bufferPtr); -	Tcl_DStringFree(&temp); -	ckfree((char*)argv); -    } else { -	Tcl_DStringInit(bufferPtr); -	Tcl_JoinPath(1, &name, bufferPtr); +    Tcl_Obj *path = Tcl_NewStringObj(name, -1); +    Tcl_Obj *transPtr; + +    Tcl_IncrRefCount(path); +    transPtr = Tcl_FSGetTranslatedPath(interp, path); +    if (transPtr == NULL) { +	Tcl_DecrRefCount(path); +	return NULL;      } +    Tcl_DStringInit(bufferPtr); +    TclDStringAppendObj(bufferPtr, transPtr); +    Tcl_DecrRefCount(path); +    Tcl_DecrRefCount(transPtr); +      /* -     * Convert forward slashes to backslashes in Windows paths because -     * some system interfaces don't accept forward slashes. +     * Convert forward slashes to backslashes in Windows paths because some +     * system interfaces don't accept forward slashes.       */      if (tclPlatform == TCL_PLATFORM_WINDOWS) { +	register char *p;  	for (p = Tcl_DStringValue(bufferPtr); *p != '\0'; p++) {  	    if (*p == '/') {  		*p = '\\';  	    }  	}      } +      return Tcl_DStringValue(bufferPtr);  } @@ -1026,12 +1088,12 @@ Tcl_TranslateFileName(interp, name, bufferPtr)   *   * TclGetExtension --   * - *	This function returns a pointer to the beginning of the - *	extension part of a file name. + *	This function returns a pointer to the beginning of the extension part + *	of a file name.   *   * Results:   *	Returns a pointer into name which indicates where the extension - *	starts.  If there is no extension, returns NULL. + *	starts. If there is no extension, returns NULL.   *   * Side effects:   *	None. @@ -1039,11 +1101,11 @@ Tcl_TranslateFileName(interp, name, bufferPtr)   *----------------------------------------------------------------------   */ -char * -TclGetExtension(name) -    char *name;			/* File name to parse. */ +const char * +TclGetExtension( +    const char *name)		/* File name to parse. */  { -    char *p, *lastSep; +    const char *p, *lastSep;      /*       * First find the last directory separator. @@ -1051,36 +1113,27 @@ TclGetExtension(name)      lastSep = NULL;		/* Needed only to prevent gcc warnings. */      switch (tclPlatform) { -	case TCL_PLATFORM_UNIX: -	    lastSep = strrchr(name, '/'); -	    break; - -	case TCL_PLATFORM_MAC: -	    if (strchr(name, ':') == NULL) { -		lastSep = strrchr(name, '/'); -	    } else { -		lastSep = strrchr(name, ':'); +    case TCL_PLATFORM_UNIX: +	lastSep = strrchr(name, '/'); +	break; + +    case TCL_PLATFORM_WINDOWS: +	lastSep = NULL; +	for (p = name; *p != '\0'; p++) { +	    if (strchr("/\\:", *p) != NULL) { +		lastSep = p;  	    } -	    break; - -	case TCL_PLATFORM_WINDOWS: -	    lastSep = NULL; -	    for (p = name; *p != '\0'; p++) { -		if (strchr("/\\:", *p) != NULL) { -		    lastSep = p; -		} -	    } -	    break; +	} +	break;      }      p = strrchr(name, '.'); -    if ((p != NULL) && (lastSep != NULL) -	    && (lastSep > p)) { +    if ((p != NULL) && (lastSep != NULL) && (lastSep > p)) {  	p = NULL;      }      /*       * In earlier versions, we used to back up to the first period in a series -     * so that "foo..o" would be split into "foo" and "..o".  This is a +     * so that "foo..o" would be split into "foo" and "..o". This is a       * confusing and usually incorrect behavior, so now we split at the last       * period in the name.       */ @@ -1098,11 +1151,10 @@ TclGetExtension(name)   *   * Results:   *	The result is a pointer to a static string containing the home - *	directory in native format.  If there was an error in processing - *	the substitution, then an error message is left in the interp's - *	result and the return value is NULL.  On success, the results - *	are appended to resultPtr, and the contents of resultPtr are - *	returned. + *	directory in native format. If there was an error in processing the + *	substitution, then an error message is left in the interp's result and + *	the return value is NULL. On success, the results are appended to + *	resultPtr, and the contents of resultPtr are returned.   *   * Side effects:   *	Information may be left in resultPtr. @@ -1110,42 +1162,42 @@ TclGetExtension(name)   *----------------------------------------------------------------------   */ -static char * -DoTildeSubst(interp, user, resultPtr) -    Tcl_Interp *interp;		/* Interpreter in which to store error -				 * message (if necessary). */ -    CONST char *user;		/* Name of user whose home directory should be +static const char * +DoTildeSubst( +    Tcl_Interp *interp,		/* Interpreter in which to store error message +				 * (if necessary). */ +    const char *user,		/* Name of user whose home directory should be  				 * substituted, or "" for current user. */ -    Tcl_DString *resultPtr;	/* Initialized DString filled with name -				 * after tilde substitution. */ +    Tcl_DString *resultPtr)	/* Initialized DString filled with name after +				 * tilde substitution. */  { -    char *dir; +    const char *dir;      if (*user == '\0') {  	Tcl_DString dirString; -	 +  	dir = TclGetEnv("HOME", &dirString);  	if (dir == NULL) {  	    if (interp) { -		Tcl_ResetResult(interp); -		Tcl_AppendResult(interp, "couldn't find HOME environment ", -			"variable to expand path", (char *) NULL); +		Tcl_SetObjResult(interp, Tcl_NewStringObj( +			"couldn't find HOME environment " +			"variable to expand path", -1)); +		Tcl_SetErrorCode(interp, "TCL", "FILENAME", "NO_HOME", NULL);  	    }  	    return NULL;  	}  	Tcl_JoinPath(1, &dir, resultPtr);  	Tcl_DStringFree(&dirString); -    } else { -	if (TclpGetUserHome(user, resultPtr) == NULL) {	 -	    if (interp) { -		Tcl_ResetResult(interp); -		Tcl_AppendResult(interp, "user \"", user, "\" doesn't exist", -			(char *) NULL); -	    } -	    return NULL; +    } else if (TclpGetUserHome(user, resultPtr) == NULL) { +	if (interp) { +	    Tcl_ResetResult(interp); +	    Tcl_SetObjResult(interp, Tcl_ObjPrintf( +		    "user \"%s\" doesn't exist", user)); +	    Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "USER", user, NULL);  	} +	return NULL;      } -    return resultPtr->string; +    return Tcl_DStringValue(resultPtr);  }  /* @@ -1153,8 +1205,8 @@ DoTildeSubst(interp, user, resultPtr)   *   * Tcl_GlobObjCmd --   * - *	This procedure is invoked to process the "glob" Tcl command. - *	See the user documentation for details on what it does. + *	This procedure is invoked to process the "glob" Tcl command. See the + *	user documentation for details on what it does.   *   * Results:   *	A standard Tcl result. @@ -1167,62 +1219,459 @@ DoTildeSubst(interp, user, resultPtr)  	/* ARGSUSED */  int -Tcl_GlobObjCmd(dummy, interp, objc, objv) -    ClientData dummy;			/* Not used. */ -    Tcl_Interp *interp;			/* Current interpreter. */ -    int objc;				/* Number of arguments. */ -    Tcl_Obj *CONST objv[];		/* Argument objects. */ +Tcl_GlobObjCmd( +    ClientData dummy,		/* Not used. */ +    Tcl_Interp *interp,		/* Current interpreter. */ +    int objc,			/* Number of arguments. */ +    Tcl_Obj *const objv[])	/* Argument objects. */  { -    int index, i, noComplain, skip, length; +    int index, i, globFlags, length, join, dir, result;      char *string; -    static char *options[] = {"-nocomplain", "--", NULL}; -    enum options {GLOB_NOCOMPLAIN, GLOB_LAST}; +    const char *separators; +    Tcl_Obj *typePtr, *look; +    Tcl_Obj *pathOrDir = NULL; +    Tcl_DString prefix; +    static const char *const options[] = { +	"-directory", "-join", "-nocomplain", "-path", "-tails", +	"-types", "--", NULL +    }; +    enum options { +	GLOB_DIR, GLOB_JOIN, GLOB_NOCOMPLAIN, GLOB_PATH, GLOB_TAILS, +	GLOB_TYPE, GLOB_LAST +    }; +    enum pathDirOptions {PATH_NONE = -1 , PATH_GENERAL = 0, PATH_DIR = 1}; +    Tcl_GlobTypeData *globTypes = NULL; + +    globFlags = 0; +    join = 0; +    dir = PATH_NONE; +    typePtr = NULL; +    for (i = 1; i < objc; i++) { +	if (Tcl_GetIndexFromObj(interp, objv[i], options, "option", 0, +		&index) != TCL_OK) { +	    string = Tcl_GetStringFromObj(objv[i], &length); +	    if (string[0] == '-') { +		/* +		 * It looks like the command contains an option so signal an +		 * error. +		 */ + +		return TCL_ERROR; +	    } else { +		/* +		 * This clearly isn't an option; assume it's the first glob +		 * pattern. We must clear the error. +		 */ + +		Tcl_ResetResult(interp); +		break; +	    } +	} -    noComplain = 0; -    for (skip = 1; skip < objc; skip++) { -	string = Tcl_GetString(objv[skip]); -	if (string[0] != '-') { +	switch (index) { +	case GLOB_NOCOMPLAIN:			/* -nocomplain */ +	    globFlags |= TCL_GLOBMODE_NO_COMPLAIN; +	    break; +	case GLOB_DIR:				/* -dir */ +	    if (i == (objc-1)) { +		Tcl_SetObjResult(interp, Tcl_NewStringObj( +			"missing argument to \"-directory\"", -1)); +		Tcl_SetErrorCode(interp, "TCL", "ARGUMENT", "MISSING", NULL); +		return TCL_ERROR; +	    } +	    if (dir != PATH_NONE) { +		Tcl_SetObjResult(interp, Tcl_NewStringObj( +			"\"-directory\" cannot be used with \"-path\"", -1)); +		Tcl_SetErrorCode(interp, "TCL", "OPERATION", "GLOB", +			"BADOPTIONCOMBINATION", NULL); +		return TCL_ERROR; +	    } +	    dir = PATH_DIR; +	    globFlags |= TCL_GLOBMODE_DIR; +	    pathOrDir = objv[i+1]; +	    i++; +	    break; +	case GLOB_JOIN:				/* -join */ +	    join = 1;  	    break; +	case GLOB_TAILS:				/* -tails */ +	    globFlags |= TCL_GLOBMODE_TAILS; +	    break; +	case GLOB_PATH:				/* -path */ +	    if (i == (objc-1)) { +		Tcl_SetObjResult(interp, Tcl_NewStringObj( +			"missing argument to \"-path\"", -1)); +		Tcl_SetErrorCode(interp, "TCL", "ARGUMENT", "MISSING", NULL); +		return TCL_ERROR; +	    } +	    if (dir != PATH_NONE) { +		Tcl_SetObjResult(interp, Tcl_NewStringObj( +			"\"-path\" cannot be used with \"-directory\"", -1)); +		Tcl_SetErrorCode(interp, "TCL", "OPERATION", "GLOB", +			"BADOPTIONCOMBINATION", NULL); +		return TCL_ERROR; +	    } +	    dir = PATH_GENERAL; +	    pathOrDir = objv[i+1]; +	    i++; +	    break; +	case GLOB_TYPE:				/* -types */ +	    if (i == (objc-1)) { +		Tcl_SetObjResult(interp, Tcl_NewStringObj( +			"missing argument to \"-types\"", -1)); +		Tcl_SetErrorCode(interp, "TCL", "ARGUMENT", "MISSING", NULL); +		return TCL_ERROR; +	    } +	    typePtr = objv[i+1]; +	    if (Tcl_ListObjLength(interp, typePtr, &length) != TCL_OK) { +		return TCL_ERROR; +	    } +	    i++; +	    break; +	case GLOB_LAST:				/* -- */ +	    i++; +	    goto endOfForLoop;  	} -	if (Tcl_GetIndexFromObj(interp, objv[skip], options, "switch", -		TCL_EXACT, &index) != TCL_OK) { -	    return TCL_ERROR; +    } + +  endOfForLoop: +    if ((globFlags & TCL_GLOBMODE_TAILS) && (pathOrDir == NULL)) { +	Tcl_SetObjResult(interp, Tcl_NewStringObj( +		"\"-tails\" must be used with either " +		"\"-directory\" or \"-path\"", -1)); +	Tcl_SetErrorCode(interp, "TCL", "OPERATION", "GLOB", +		"BADOPTIONCOMBINATION", NULL); +	return TCL_ERROR; +    } + +    separators = NULL;		/* lint. */ +    switch (tclPlatform) { +    case TCL_PLATFORM_UNIX: +	separators = "/"; +	break; +    case TCL_PLATFORM_WINDOWS: +	separators = "/\\:"; +	break; +    } + +    if (dir == PATH_GENERAL) { +	int pathlength; +	const char *last; +	const char *first = Tcl_GetStringFromObj(pathOrDir,&pathlength); + +	/* +	 * Find the last path separator in the path +	 */ + +	last = first + pathlength; +	for (; last != first; last--) { +	    if (strchr(separators, *(last-1)) != NULL) { +		break; +	    }  	} -	if (index == GLOB_NOCOMPLAIN) { -	    noComplain = 1; + +	if (last == first + pathlength) { +	    /* +	     * It's really a directory. +	     */ + +	    dir = PATH_DIR; +  	} else { -	    skip++; -	    break; +	    Tcl_DString pref; +	    char *search, *find; +	    Tcl_DStringInit(&pref); +	    if (last == first) { +		/* +		 * The whole thing is a prefix. This means we must remove any +		 * 'tails' flag too, since it is irrelevant now (the same +		 * effect will happen without it), but in particular its use +		 * in TclGlob requires a non-NULL pathOrDir. +		 */ + +		Tcl_DStringAppend(&pref, first, -1); +		globFlags &= ~TCL_GLOBMODE_TAILS; +		pathOrDir = NULL; +	    } else { +		/* +		 * Have to split off the end. +		 */ + +		Tcl_DStringAppend(&pref, last, first+pathlength-last); +		pathOrDir = Tcl_NewStringObj(first, last-first-1); + +		/* +		 * We must ensure that we haven't cut off too much, and turned +		 * a valid path like '/' or 'C:/' into an incorrect path like +		 * '' or 'C:'. The way we do this is to add a separator if +		 * there are none presently in the prefix. +		 */ + +		if (strpbrk(Tcl_GetString(pathOrDir), "\\/") == NULL) { +		    Tcl_AppendToObj(pathOrDir, last-1, 1); +		} +	    } + +	    /* +	     * Need to quote 'prefix'. +	     */ + +	    Tcl_DStringInit(&prefix); +	    search = Tcl_DStringValue(&pref); +	    while ((find = (strpbrk(search, "\\[]*?{}"))) != NULL) { +		Tcl_DStringAppend(&prefix, search, find-search); +		TclDStringAppendLiteral(&prefix, "\\"); +		Tcl_DStringAppend(&prefix, find, 1); +		search = find+1; +		if (*search == '\0') { +		    break; +		} +	    } +	    if (*search != '\0') { +		Tcl_DStringAppend(&prefix, search, -1); +	    } +	    Tcl_DStringFree(&pref);  	}      } -    if (skip >= objc) { -        Tcl_WrongNumArgs(interp, 1, objv, "?switches? name ?name ...?"); -	return TCL_ERROR; + +    if (pathOrDir != NULL) { +	Tcl_IncrRefCount(pathOrDir);      } -    for (i = skip; i < objc; i++) { -	string = Tcl_GetString(objv[i]); -	if (TclGlob(interp, string, noComplain) != TCL_OK) { -	    return TCL_ERROR; +    if (typePtr != NULL) { +	/* +	 * The rest of the possible type arguments (except 'd') are platform +	 * specific. We don't complain when they are used on an incompatible +	 * platform. +	 */ + +	Tcl_ListObjLength(interp, typePtr, &length); +	if (length <= 0) { +	    goto skipTypes; +	} +	globTypes = TclStackAlloc(interp, sizeof(Tcl_GlobTypeData)); +	globTypes->type = 0; +	globTypes->perm = 0; +	globTypes->macType = NULL; +	globTypes->macCreator = NULL; + +	while (--length >= 0) { +	    int len; +	    const char *str; + +	    Tcl_ListObjIndex(interp, typePtr, length, &look); +	    str = Tcl_GetStringFromObj(look, &len); +	    if (strcmp("readonly", str) == 0) { +		globTypes->perm |= TCL_GLOB_PERM_RONLY; +	    } else if (strcmp("hidden", str) == 0) { +		globTypes->perm |= TCL_GLOB_PERM_HIDDEN; +	    } else if (len == 1) { +		switch (str[0]) { +		case 'r': +		    globTypes->perm |= TCL_GLOB_PERM_R; +		    break; +		case 'w': +		    globTypes->perm |= TCL_GLOB_PERM_W; +		    break; +		case 'x': +		    globTypes->perm |= TCL_GLOB_PERM_X; +		    break; +		case 'b': +		    globTypes->type |= TCL_GLOB_TYPE_BLOCK; +		    break; +		case 'c': +		    globTypes->type |= TCL_GLOB_TYPE_CHAR; +		    break; +		case 'd': +		    globTypes->type |= TCL_GLOB_TYPE_DIR; +		    break; +		case 'p': +		    globTypes->type |= TCL_GLOB_TYPE_PIPE; +		    break; +		case 'f': +		    globTypes->type |= TCL_GLOB_TYPE_FILE; +		    break; +		case 'l': +		    globTypes->type |= TCL_GLOB_TYPE_LINK; +		    break; +		case 's': +		    globTypes->type |= TCL_GLOB_TYPE_SOCK; +		    break; +		default: +		    goto badTypesArg; +		} + +	    } else if (len == 4) { +		/* +		 * This is assumed to be a MacOS file type. +		 */ + +		if (globTypes->macType != NULL) { +		    goto badMacTypesArg; +		} +		globTypes->macType = look; +		Tcl_IncrRefCount(look); + +	    } else { +		Tcl_Obj *item; + +		if ((Tcl_ListObjLength(NULL, look, &len) == TCL_OK) +			&& (len == 3)) { +		    Tcl_ListObjIndex(interp, look, 0, &item); +		    if (!strcmp("macintosh", Tcl_GetString(item))) { +			Tcl_ListObjIndex(interp, look, 1, &item); +			if (!strcmp("type", Tcl_GetString(item))) { +			    Tcl_ListObjIndex(interp, look, 2, &item); +			    if (globTypes->macType != NULL) { +				goto badMacTypesArg; +			    } +			    globTypes->macType = item; +			    Tcl_IncrRefCount(item); +			    continue; +			} else if (!strcmp("creator", Tcl_GetString(item))) { +			    Tcl_ListObjIndex(interp, look, 2, &item); +			    if (globTypes->macCreator != NULL) { +				goto badMacTypesArg; +			    } +			    globTypes->macCreator = item; +			    Tcl_IncrRefCount(item); +			    continue; +			} +		    } +		} + +		/* +		 * Error cases. We reset the 'join' flag to zero, since we +		 * haven't yet made use of it. +		 */ + +	    badTypesArg: +		Tcl_SetObjResult(interp, Tcl_ObjPrintf( +			"bad argument to \"-types\": %s", +			Tcl_GetString(look))); +		Tcl_SetErrorCode(interp, "TCL", "ARGUMENT", "BAD", NULL); +		result = TCL_ERROR; +		join = 0; +		goto endOfGlob; + +	    badMacTypesArg: +		Tcl_SetObjResult(interp, Tcl_NewStringObj( +			"only one MacOS type or creator argument" +			" to \"-types\" allowed", -1)); +		result = TCL_ERROR; +		Tcl_SetErrorCode(interp, "TCL", "ARGUMENT", "BAD", NULL); +		join = 0; +		goto endOfGlob; +	    } +	} +    } + +  skipTypes: +    /* +     * Now we perform the actual glob below. This may involve joining together +     * the pattern arguments, dealing with particular file types etc. We use a +     * 'goto' to ensure we free any memory allocated along the way. +     */ + +    objc -= i; +    objv += i; +    result = TCL_OK; + +    if (join) { +	if (dir != PATH_GENERAL) { +	    Tcl_DStringInit(&prefix); +	} +	for (i = 0; i < objc; i++) { +	    TclDStringAppendObj(&prefix, objv[i]); +	    if (i != objc -1) { +		Tcl_DStringAppend(&prefix, separators, 1); +	    } +	} +	if (TclGlob(interp, Tcl_DStringValue(&prefix), pathOrDir, globFlags, +		globTypes) != TCL_OK) { +	    result = TCL_ERROR; +	    goto endOfGlob; +	} +    } else if (dir == PATH_GENERAL) { +	Tcl_DString str; + +	for (i = 0; i < objc; i++) { +	    Tcl_DStringInit(&str); +	    if (dir == PATH_GENERAL) { +		TclDStringAppendDString(&str, &prefix); +	    } +	    TclDStringAppendObj(&str, objv[i]); +	    if (TclGlob(interp, Tcl_DStringValue(&str), pathOrDir, globFlags, +		    globTypes) != TCL_OK) { +		result = TCL_ERROR; +		Tcl_DStringFree(&str); +		goto endOfGlob; +	    } +	} +	Tcl_DStringFree(&str); +    } else { +	for (i = 0; i < objc; i++) { +	    string = Tcl_GetString(objv[i]); +	    if (TclGlob(interp, string, pathOrDir, globFlags, +		    globTypes) != TCL_OK) { +		result = TCL_ERROR; +		goto endOfGlob; +	    }  	}      } -    if (noComplain == 0) { -	Tcl_GetStringFromObj(Tcl_GetObjResult(interp), &length); + +    if ((globFlags & TCL_GLOBMODE_NO_COMPLAIN) == 0) { +	if (Tcl_ListObjLength(interp, Tcl_GetObjResult(interp), +		&length) != TCL_OK) { +	    /* +	     * This should never happen. Maybe we should be more dramatic. +	     */ + +	    result = TCL_ERROR; +	    goto endOfGlob; +	} +  	if (length == 0) { -	    char *sep = ""; - -	    Tcl_AppendResult(interp, "no files matched glob pattern", -		    (objc == 2) ? " \"" : "s \"", (char *) NULL); -	    for (i = skip; i < objc; i++) { -		string = Tcl_GetString(objv[i]); -		Tcl_AppendResult(interp, sep, string, (char *) NULL); -		sep = " "; +	    Tcl_Obj *errorMsg = +		    Tcl_ObjPrintf("no files matched glob pattern%s \"", +			    (join || (objc == 1)) ? "" : "s"); + +	    if (join) { +		Tcl_AppendToObj(errorMsg, Tcl_DStringValue(&prefix), -1); +	    } else { +		const char *sep = ""; + +		for (i = 0; i < objc; i++) { +		    Tcl_AppendPrintfToObj(errorMsg, "%s%s", +			    sep, Tcl_GetString(objv[i])); +		    sep = " "; +		}  	    } -	    Tcl_AppendResult(interp, "\"", (char *) NULL); -	    return TCL_ERROR; +	    Tcl_AppendToObj(errorMsg, "\"", -1); +	    Tcl_SetObjResult(interp, errorMsg); +	    Tcl_SetErrorCode(interp, "TCL", "OPERATION", "GLOB", "NOMATCH", +		    NULL); +	    result = TCL_ERROR; +	} +    } + +  endOfGlob: +    if (join || (dir == PATH_GENERAL)) { +	Tcl_DStringFree(&prefix); +    } +    if (pathOrDir != NULL) { +	Tcl_DecrRefCount(pathOrDir); +    } +    if (globTypes != NULL) { +	if (globTypes->macType != NULL) { +	    Tcl_DecrRefCount(globTypes->macType);  	} +	if (globTypes->macCreator != NULL) { +	    Tcl_DecrRefCount(globTypes->macCreator); +	} +	TclStackFree(interp, globTypes);      } -    return TCL_OK; +    return result;  }  /* @@ -1230,126 +1679,375 @@ Tcl_GlobObjCmd(dummy, interp, objc, objv)   *   * TclGlob --   * - *	This procedure prepares arguments for the TclDoGlob call. - *	It sets the separator string based on the platform, performs - *      tilde substitution, and calls TclDoGlob. + *	This procedure prepares arguments for the DoGlob call. It sets the + *	separator string based on the platform, performs * tilde substitution, + *	and calls DoGlob. + * + *	The interpreter's result, on entry to this function, must be a valid + *	Tcl list (e.g. it could be empty), since we will lappend any new + *	results to that list. If it is not a valid list, this function will + *	fail to do anything very meaningful. + * + *	Note that if globFlags contains 'TCL_GLOBMODE_TAILS' then pathPrefix + *	cannot be NULL (it is only allowed with -dir or -path).   *   * Results: - *	The return value is a standard Tcl result indicating whether - *	an error occurred in globbing.  After a normal return the - *	result in interp (set by TclDoGlob) holds all of the file names - *	given by the dir and rem arguments.  After an error the - *	result in interp will hold an error message. + *	The return value is a standard Tcl result indicating whether an error + *	occurred in globbing. After a normal return the result in interp (set + *	by DoGlob) holds all of the file names given by the pattern and + *	pathPrefix arguments. After an error the result in interp will hold + *	an error message.   *   * Side effects: - *	The currentArgString is written to. + *	The 'pattern' is written to.   *   *----------------------------------------------------------------------   */  	/* ARGSUSED */  int -TclGlob(interp, pattern, noComplain) -    Tcl_Interp *interp;		/* Interpreter for returning error message -				 * or appending list of matching file names. */ -    char *pattern;		/* Glob pattern to match. Must not refer -				 * to a static string. */ -    int noComplain;		/* Flag to turn off storing error messages -				 * in interp. */ +TclGlob( +    Tcl_Interp *interp,		/* Interpreter for returning error message or +				 * appending list of matching file names. */ +    char *pattern,		/* Glob pattern to match. Must not refer to a +				 * static string. */ +    Tcl_Obj *pathPrefix,	/* Path prefix to glob pattern, if non-null, +				 * which is considered literally. */ +    int globFlags,		/* Stores or'ed combination of flags */ +    Tcl_GlobTypeData *types)	/* Struct containing acceptable types. May be +				 * NULL. */  { -    char *separators; -    char *head, *tail; -    char c; +    const char *separators; +    const char *head; +    char *tail, *start;      int result; -    Tcl_DString buffer; +    Tcl_Obj *filenamesObj, *savedResultObj;      separators = NULL;		/* lint. */      switch (tclPlatform) { -	case TCL_PLATFORM_UNIX: -	    separators = "/"; -	    break; -	case TCL_PLATFORM_WINDOWS: -	    separators = "/\\:"; -	    break; -	case TCL_PLATFORM_MAC: -	    separators = (strchr(pattern, ':') == NULL) -		? "/" : ":"; -	    break; +    case TCL_PLATFORM_UNIX: +	separators = "/"; +	break; +    case TCL_PLATFORM_WINDOWS: +	separators = "/\\:"; +	break;      } -    Tcl_DStringInit(&buffer); - -    /* -     * Perform tilde substitution, if needed. -     */ +    if (pathPrefix == NULL) { +	char c; +	Tcl_DString buffer; +	Tcl_DStringInit(&buffer); -    if (pattern[0] == '~') { -	char *p; +	start = pattern;  	/* -	 * Find the first path separator after the tilde. +	 * Perform tilde substitution, if needed.  	 */ -	for (tail = pattern; *tail != '\0'; tail++) { -	    if (*tail == '\\') { -		if (strchr(separators, tail[1]) != NULL) { +	if (start[0] == '~') { +	    /* +	     * Find the first path separator after the tilde. +	     */ + +	    for (tail = start; *tail != '\0'; tail++) { +		if (*tail == '\\') { +		    if (strchr(separators, tail[1]) != NULL) { +			break; +		    } +		} else if (strchr(separators, *tail) != NULL) {  		    break;  		} -	    } else if (strchr(separators, *tail) != NULL) { -		break;  	    } -	} -	/* -	 * Determine the home directory for the specified user.  Note that -	 * we don't allow special characters in the user name. -	 */ -	 -	c = *tail; -	*tail = '\0'; -	p = strpbrk(pattern+1, "\\[]*?{}"); -	if (p == NULL) { -	    head = DoTildeSubst(interp, pattern+1, &buffer); -	} else { -	    if (!noComplain) { -		Tcl_ResetResult(interp); -		Tcl_AppendResult(interp, "globbing characters not ", -			"supported in user names", (char *) NULL); +	    /* +	     * Determine the home directory for the specified user. +	     */ + +	    c = *tail; +	    *tail = '\0'; +	    head = DoTildeSubst(interp, start+1, &buffer); +	    *tail = c; +	    if (head == NULL) { +		return TCL_ERROR;  	    } -	    head = NULL; +	    if (head != Tcl_DStringValue(&buffer)) { +		Tcl_DStringAppend(&buffer, head, -1); +	    } +	    pathPrefix = TclDStringToObj(&buffer); +	    Tcl_IncrRefCount(pathPrefix); +	    globFlags |= TCL_GLOBMODE_DIR; +	    if (c != '\0') { +		tail++; +	    } +	    Tcl_DStringFree(&buffer); +	} else { +	    tail = pattern;  	} -	*tail = c; -	if (head == NULL) { -	    if (noComplain) { +    } else { +	Tcl_IncrRefCount(pathPrefix); +	tail = pattern; +    } + +    /* +     * Handling empty path prefixes with glob patterns like 'C:' or +     * 'c:////////' is a pain on Windows if we leave it too late, since these +     * aren't really patterns at all! We therefore check the head of the +     * pattern now for such cases, if we don't have an unquoted prefix yet. +     * +     * Similarly on Unix with '/' at the head of the pattern -- it just +     * indicates the root volume, so we treat it as such. +     */ + +    if (tclPlatform == TCL_PLATFORM_WINDOWS) { +	if (pathPrefix == NULL && tail[0] != '\0' && tail[1] == ':') { +	    char *p = tail + 1; +	    pathPrefix = Tcl_NewStringObj(tail, 1); +	    while (*p != '\0') { +		char c = p[1]; +		if (*p == '\\') { +		    if (strchr(separators, c) != NULL) { +			if (c == '\\') { +			    c = '/'; +			} +			Tcl_AppendToObj(pathPrefix, &c, 1); +			p++; +		    } else { +			break; +		    } +		} else if (strchr(separators, *p) != NULL) { +		    Tcl_AppendToObj(pathPrefix, p, 1); +		} else { +		    break; +		} +		p++; +	    } +	    tail = p; +	    Tcl_IncrRefCount(pathPrefix); +	} else if (pathPrefix == NULL && (tail[0] == '/' +		|| (tail[0] == '\\' && tail[1] == '\\'))) { +	    int driveNameLen; +	    Tcl_Obj *driveName; +	    Tcl_Obj *temp = Tcl_NewStringObj(tail, -1); +	    Tcl_IncrRefCount(temp); + +	    switch (TclGetPathType(temp, NULL, &driveNameLen, &driveName)) { +	    case TCL_PATH_VOLUME_RELATIVE: {  		/* -		 * We should in fact pass down the nocomplain flag  -		 * or save the interp result or use another mechanism -		 * so the interp result is not mangled on errors in that case. -		 * but that would a bigger change than reasonable for a patch -		 * release. -		 * (see fileName.test 15.2-15.4 for expected behaviour) +		 * Volume relative path which is equivalent to a path in the +		 * root of the cwd's volume. We will actually return +		 * non-volume-relative paths here. i.e. 'glob /foo*' will +		 * return 'C:/foobar'. This is much the same as globbing for a +		 * path with '\\' will return one with '/' on Windows.  		 */ -		Tcl_ResetResult(interp); -		return TCL_OK; -	    } else { -		return TCL_ERROR; + +		Tcl_Obj *cwd = Tcl_FSGetCwd(interp); + +		if (cwd == NULL) { +		    Tcl_DecrRefCount(temp); +		    return TCL_ERROR; +		} +		pathPrefix = Tcl_NewStringObj(Tcl_GetString(cwd), 3); +		Tcl_DecrRefCount(cwd); +		if (tail[0] == '/') { +		    tail++; +		} else { +		    tail += 2; +		} +		Tcl_IncrRefCount(pathPrefix); +		break; +	    } +	    case TCL_PATH_ABSOLUTE: +		/* +		 * Absolute, possibly network path //Machine/Share. Use that +		 * as the path prefix (it already has a refCount). +		 */ + +		pathPrefix = driveName; +		tail += driveNameLen; +		break; +	    case TCL_PATH_RELATIVE: +		/* Do nothing */ +		break;  	    } +	    Tcl_DecrRefCount(temp); +	} + +	/* +	 * ':' no longer needed as a separator. It is only relevant to the +	 * beginning of the path. +	 */ + +	separators = "/\\"; + +    } else if (tclPlatform == TCL_PLATFORM_UNIX) { +	if (pathPrefix == NULL && tail[0] == '/') { +	    pathPrefix = Tcl_NewStringObj(tail, 1); +	    tail++; +	    Tcl_IncrRefCount(pathPrefix); +	} +    } + +    /* +     * Finally if we still haven't managed to generate a path prefix, check if +     * the path starts with a current volume. +     */ + +    if (pathPrefix == NULL) { +	int driveNameLen; +	Tcl_Obj *driveName; +	if (TclFSNonnativePathType(tail, (int) strlen(tail), NULL, +		&driveNameLen, &driveName) == TCL_PATH_ABSOLUTE) { +	    pathPrefix = driveName; +	    tail += driveNameLen;  	} -	if (head != Tcl_DStringValue(&buffer)) { -	    Tcl_DStringAppend(&buffer, head, -1); +    } + +    /* +     * To process a [glob] invokation, this function may be called multiple +     * times. Each time, the previously discovered filenames are in the +     * interpreter result. We stash that away here so the result is free for +     * error messsages. +     */ + +    savedResultObj = Tcl_GetObjResult(interp); +    Tcl_IncrRefCount(savedResultObj); +    Tcl_ResetResult(interp); +    TclNewObj(filenamesObj); +    Tcl_IncrRefCount(filenamesObj); + +    /* +     * Now we do the actual globbing, adding filenames as we go to buffer in +     * filenamesObj +     */ + +    if (*tail == '\0' && pathPrefix != NULL) { +	/* +	 * An empty pattern. This means 'pathPrefix' is actually a full path +	 * of a file/directory we want to simply check for existence and type. +	 */ + +	if (types == NULL) { +	    /* +	     * We just want to check for existence. In this case we make it +	     * easy on Tcl_FSMatchInDirectory and its sub-implementations by +	     * not bothering them (even though they should support this +	     * situation) and we just use the simple existence check with +	     * Tcl_FSAccess. +	     */ + +	    if (Tcl_FSAccess(pathPrefix, F_OK) == 0) { +		Tcl_ListObjAppendElement(interp, filenamesObj, pathPrefix); +	    } +	    result = TCL_OK; +	} else { +	    /* +	     * We want to check for the correct type. Tcl_FSMatchInDirectory +	     * is documented to do this for us, if we give it a NULL pattern. +	     */ + +	    result = Tcl_FSMatchInDirectory(interp, filenamesObj, pathPrefix, +		    NULL, types);  	}      } else { -	tail = pattern; +	result = DoGlob(interp, filenamesObj, separators, pathPrefix, +		globFlags & TCL_GLOBMODE_DIR, tail, types);      } -    result = TclDoGlob(interp, separators, &buffer, tail); -    Tcl_DStringFree(&buffer); +    /* +     * Check for errors... +     */ +      if (result != TCL_OK) { -	if (noComplain) { -	    Tcl_ResetResult(interp); -	    return TCL_OK; +	TclDecrRefCount(filenamesObj); +	TclDecrRefCount(savedResultObj); +	if (pathPrefix != NULL) { +	    Tcl_DecrRefCount(pathPrefix); +	} +	return result; +    } + +    /* +     * If we only want the tails, we must strip off the prefix now. It may +     * seem more efficient to pass the tails flag down into DoGlob, +     * Tcl_FSMatchInDirectory, but those functions are continually adjusting +     * the prefix as the various pieces of the pattern are assimilated, so +     * that would add a lot of complexity to the code. This way is a little +     * slower (when the -tails flag is given), but much simpler to code. +     * +     * We do it by rewriting the result list in-place. +     */ + +    if (globFlags & TCL_GLOBMODE_TAILS) { +	int objc, i; +	Tcl_Obj **objv; +	int prefixLen; +	const char *pre; + +	/* +	 * If this length has never been set, set it here. +	 */ + +	if (pathPrefix == NULL) { +	    Tcl_Panic("Called TclGlob with TCL_GLOBMODE_TAILS and pathPrefix==NULL");  	} + +	pre = Tcl_GetStringFromObj(pathPrefix, &prefixLen); +	if (prefixLen > 0 +		&& (strchr(separators, pre[prefixLen-1]) == NULL)) { +	    /* +	     * If we're on Windows and the prefix is a volume relative one +	     * like 'C:', then there won't be a path separator in between, so +	     * no need to skip it here. +	     */ + +	    if ((tclPlatform != TCL_PLATFORM_WINDOWS) || (prefixLen != 2) +		    || (pre[1] != ':')) { +		prefixLen++; +	    } +	} + +	Tcl_ListObjGetElements(NULL, filenamesObj, &objc, &objv); +	for (i = 0; i< objc; i++) { +	    int len; +	    const char *oldStr = Tcl_GetStringFromObj(objv[i], &len); +	    Tcl_Obj *elem; + +	    if (len == prefixLen) { +		if ((pattern[0] == '\0') +			|| (strchr(separators, pattern[0]) == NULL)) { +		    TclNewLiteralStringObj(elem, "."); +		} else { +		    TclNewLiteralStringObj(elem, "/"); +		} +	    } else { +		elem = Tcl_NewStringObj(oldStr+prefixLen, len-prefixLen); +	    } +	    Tcl_ListObjReplace(interp, filenamesObj, i, 1, 1, &elem); +	} +    } + +    /* +     * Now we have a list of discovered filenames in filenamesObj and a list +     * of previously discovered (saved earlier from the interpreter result) in +     * savedResultObj. Merge them and put them back in the interpreter result. +     */ + +    if (Tcl_IsShared(savedResultObj)) { +	TclDecrRefCount(savedResultObj); +	savedResultObj = Tcl_DuplicateObj(savedResultObj); +	Tcl_IncrRefCount(savedResultObj); +    } +    if (Tcl_ListObjAppendList(interp, savedResultObj, filenamesObj) != TCL_OK){ +	result = TCL_ERROR; +    } else { +	Tcl_SetObjResult(interp, savedResultObj); +    } +    TclDecrRefCount(savedResultObj); +    TclDecrRefCount(filenamesObj); +    if (pathPrefix != NULL) { +	Tcl_DecrRefCount(pathPrefix);      } +      return result;  } @@ -1358,14 +2056,13 @@ TclGlob(interp, pattern, noComplain)   *   * SkipToChar --   * - *	This function traverses a glob pattern looking for the next - *	unquoted occurance of the specified character at the same braces - *	nesting level. + *	This function traverses a glob pattern looking for the next unquoted + *	occurance of the specified character at the same braces nesting level.   *   * Results: - *	Updates stringPtr to point to the matching character, or to - *	the end of the string if nothing matched.  The return value - *	is 1 if a match was found at the top level, otherwise it is 0. + *	Updates stringPtr to point to the matching character, or to the end of + *	the string if nothing matched. The return value is 1 if a match was + *	found at the top level, otherwise it is 0.   *   * Side effects:   *	None. @@ -1374,9 +2071,9 @@ TclGlob(interp, pattern, noComplain)   */  static int -SkipToChar(stringPtr, match) -    char **stringPtr;			/* Pointer string to check. */ -    char *match;			/* Pointer to character to find. */ +SkipToChar( +    char **stringPtr,		/* Pointer string to check. */ +    int match)			/* Character to find. */  {      int quoted, level;      register char *p; @@ -1389,7 +2086,7 @@ SkipToChar(stringPtr, match)  	    quoted = 0;  	    continue;  	} -	if ((level == 0) && (*p == *match)) { +	if ((level == 0) && (*p == match)) {  	    *stringPtr = p;  	    return 1;  	} @@ -1408,19 +2105,23 @@ SkipToChar(stringPtr, match)  /*   *----------------------------------------------------------------------   * - * TclDoGlob -- + * DoGlob --   * - *	This recursive procedure forms the heart of the globbing - *	code.  It performs a depth-first traversal of the tree - *	given by the path name to be globbed.  The directory and - *	remainder are assumed to be native format paths. + *	This recursive procedure forms the heart of the globbing code. It + *	performs a depth-first traversal of the tree given by the path name to + *	be globbed and the pattern. The directory and remainder are assumed to + *	be native format paths. The prefix contained in 'pathPtr' is either a + *	directory or path from which to start the search (or NULL). If pathPtr + *	is NULL, then the pattern must not start with an absolute path + *	specification (that case should be handled by moving the absolute path + *	prefix into pathPtr before calling DoGlob).   *   * Results: - *	The return value is a standard Tcl result indicating whether - *	an error occurred in globbing.  After a normal return the - *	result in interp will be set to hold all of the file names - *	given by the dir and rem arguments.  After an error the - *	result in interp will hold an error message. + *	The return value is a standard Tcl result indicating whether an error + *	occurred in globbing. After a normal return the result in interp will + *	be set to hold all of the file names given by the dir and remaining + *	arguments. After an error the result in interp will hold an error + *	message.   *   * Side effects:   *	None. @@ -1428,140 +2129,105 @@ SkipToChar(stringPtr, match)   *----------------------------------------------------------------------   */ -int -TclDoGlob(interp, separators, headPtr, tail) -    Tcl_Interp *interp;		/* Interpreter to use for error reporting +static int +DoGlob( +    Tcl_Interp *interp,		/* Interpreter to use for error reporting  				 * (e.g. unmatched brace). */ -    char *separators;		/* String containing separator characters -				 * that should be used to identify globbing +    Tcl_Obj *matchesObj,	/* Unshared list object in which to place all +				 * resulting filenames. Caller allocates and +				 * deallocates; DoGlob must not touch the +				 * refCount of this object. */ +    const char *separators,	/* String containing separator characters that +				 * should be used to identify globbing  				 * boundaries. */ -    Tcl_DString *headPtr;	/* Completely expanded prefix. */ -    char *tail;			/* The unexpanded remainder of the path. -				 * Must not be a pointer to a static string. */ +    Tcl_Obj *pathPtr,		/* Completely expanded prefix. */ +    int flags,			/* If non-zero then pathPtr is a directory */ +    char *pattern,		/* The pattern to match against. Must not be a +				 * pointer to a static string. */ +    Tcl_GlobTypeData *types)	/* List object containing list of acceptable +				 * types. May be NULL. */  {      int baseLength, quoted, count;      int result = TCL_OK; -    char *name, *p, *openBrace, *closeBrace, *firstSpecialChar, savedChar; -    char lastChar = 0; -    int length = Tcl_DStringLength(headPtr); - -    if (length > 0) { -	lastChar = Tcl_DStringValue(headPtr)[length-1]; -    } +    char *name, *p, *openBrace, *closeBrace, *firstSpecialChar; +    Tcl_Obj *joinedPtr;      /* -     * Consume any leading directory separators, leaving tail pointing -     * just past the last initial separator. +     * Consume any leading directory separators, leaving pattern pointing just +     * past the last initial separator.       */      count = 0; -    name = tail; -    for (; *tail != '\0'; tail++) { -	if ((*tail == '\\') && (strchr(separators, tail[1]) != NULL)) { -	    tail++; -	} else if (strchr(separators, *tail) == NULL) { -	    break; -	} -	count++; -    } - -    /* -     * Deal with path separators.  On the Mac, we have to watch out -     * for multiple separators, since they are special in Mac-style -     * paths. -     */ - -    switch (tclPlatform) { -	case TCL_PLATFORM_MAC: -	    if (*separators == '/') { -		if (((length == 0) && (count == 0)) -			|| ((length > 0) && (lastChar != ':'))) { -		    Tcl_DStringAppend(headPtr, ":", 1); -		} -	    } else { -		if (count == 0) { -		    if ((length > 0) && (lastChar != ':')) { -			Tcl_DStringAppend(headPtr, ":", 1); -		    } -		} else { -		    if (lastChar == ':') { -			count--; -		    } -		    while (count-- > 0) { -			Tcl_DStringAppend(headPtr, ":", 1); -		    } -		} -	    } -	    break; -	case TCL_PLATFORM_WINDOWS: +    name = pattern; +    for (; *pattern != '\0'; pattern++) { +	if (*pattern == '\\') {  	    /* -	     * If this is a drive relative path, add the colon and the -	     * trailing slash if needed.  Otherwise add the slash if -	     * this is the first absolute element, or a later relative -	     * element.  Add an extra slash if this is a UNC path. +	     * If the first character is escaped, either we have a directory +	     * separator, or we have any other character. In the latter case +	     * the rest is a pattern, and we must break from the loop. This +	     * is particularly important on Windows where '\' is both the +	     * escaping character and a directory separator.  	     */ -	    if (*name == ':') { -		Tcl_DStringAppend(headPtr, ":", 1); -		if (count > 1) { -		    Tcl_DStringAppend(headPtr, "/", 1); -		} -	    } else if ((*tail != '\0') -		    && (((length > 0) -			    && (strchr(separators, lastChar) == NULL)) -			    || ((length == 0) && (count > 0)))) { -		Tcl_DStringAppend(headPtr, "/", 1); -		if ((length == 0) && (count > 1)) { -		    Tcl_DStringAppend(headPtr, "/", 1); -		} -	    } -	     -	    break; -	case TCL_PLATFORM_UNIX: -	    /* -	     * Add a separator if this is the first absolute element, or -	     * a later relative element. -	     */ - -	    if ((*tail != '\0') -		    && (((length > 0) -			    && (strchr(separators, lastChar) == NULL)) -			    || ((length == 0) && (count > 0)))) { -		Tcl_DStringAppend(headPtr, "/", 1); +	    if (strchr(separators, pattern[1]) != NULL) { +		pattern++; +	    } else { +		break;  	    } +	} else if (strchr(separators, *pattern) == NULL) {  	    break; +	} +	count++;      }      /* -     * Look for the first matching pair of braces or the first -     * directory separator that is not inside a pair of braces. +     * Look for the first matching pair of braces or the first directory +     * separator that is not inside a pair of braces.       */      openBrace = closeBrace = NULL;      quoted = 0; -    for (p = tail; *p != '\0'; p++) { +    for (p = pattern; *p != '\0'; p++) {  	if (quoted) {  	    quoted = 0; +  	} else if (*p == '\\') {  	    quoted = 1;  	    if (strchr(separators, p[1]) != NULL) { -		break;			/* Quoted directory separator. */ +		/* +		 * Quoted directory separator. +		 */ +		break;  	    } +  	} else if (strchr(separators, *p) != NULL) { -	    break;			/* Unquoted directory separator. */ +	    /* +	     * Unquoted directory separator. +	     */ +	    break; +  	} else if (*p == '{') {  	    openBrace = p;  	    p++; -	    if (SkipToChar(&p, "}")) { -		closeBrace = p;		/* Balanced braces. */ +	    if (SkipToChar(&p, '}')) { +		/* +		 * Balanced braces. +		 */ + +		closeBrace = p;  		break;  	    } -	    Tcl_SetResult(interp, "unmatched open-brace in file name", -		    TCL_STATIC); +	    Tcl_SetObjResult(interp, Tcl_NewStringObj( +		    "unmatched open-brace in file name", -1)); +	    Tcl_SetErrorCode(interp, "TCL", "OPERATION", "GLOB", "BALANCE", +		    NULL);  	    return TCL_ERROR; +  	} else if (*p == '}') { -	    Tcl_SetResult(interp, "unmatched close-brace in file name", -		    TCL_STATIC); +	    Tcl_SetObjResult(interp, Tcl_NewStringObj( +		    "unmatched close-brace in file name", -1)); +	    Tcl_SetErrorCode(interp, "TCL", "OPERATION", "GLOB", "BALANCE", +		    NULL);  	    return TCL_ERROR;  	}      } @@ -1573,28 +2239,27 @@ TclDoGlob(interp, separators, headPtr, tail)      if (openBrace != NULL) {  	char *element;  	Tcl_DString newName; +  	Tcl_DStringInit(&newName);  	/* -	 * For each element within in the outermost pair of braces, -	 * append the element and the remainder to the fixed portion -	 * before the first brace and recursively call TclDoGlob. +	 * For each element within in the outermost pair of braces, append the +	 * element and the remainder to the fixed portion before the first +	 * brace and recursively call DoGlob.  	 */ -	Tcl_DStringAppend(&newName, tail, openBrace-tail); +	Tcl_DStringAppend(&newName, pattern, openBrace-pattern);  	baseLength = Tcl_DStringLength(&newName); -	length = Tcl_DStringLength(headPtr);  	*closeBrace = '\0';  	for (p = openBrace; p != closeBrace; ) {  	    p++;  	    element = p; -	    SkipToChar(&p, ","); -	    Tcl_DStringSetLength(headPtr, length); +	    SkipToChar(&p, ',');  	    Tcl_DStringSetLength(&newName, baseLength);  	    Tcl_DStringAppend(&newName, element, p-element);  	    Tcl_DStringAppend(&newName, closeBrace+1, -1); -	    result = TclDoGlob(interp, separators, -		    headPtr, Tcl_DStringValue(&newName)); +	    result = DoGlob(interp, matchesObj, separators, pathPtr, flags, +		    Tcl_DStringValue(&newName), types);  	    if (result != TCL_OK) {  		break;  	    } @@ -1605,116 +2270,388 @@ TclDoGlob(interp, separators, headPtr, tail)      }      /* -     * At this point, there are no more brace substitutions to perform on -     * this path component.  The variable p is pointing at a quoted or -     * unquoted directory separator or the end of the string.  So we need -     * to check for special globbing characters in the current pattern. -     * We avoid modifying tail if p is pointing at the end of the string. +     * At this point, there are no more brace substitutions to perform on this +     * path component. The variable p is pointing at a quoted or unquoted +     * directory separator or the end of the string. So we need to check for +     * special globbing characters in the current pattern. We avoid modifying +     * pattern if p is pointing at the end of the string. +     * +     * If we find any globbing characters, then we must call +     * Tcl_FSMatchInDirectory. If we're at the end of the string, then that's +     * all we need to do. If we're not at the end of the string, then we must +     * recurse, so we do that below. +     * +     * Alternatively, if there are no globbing characters then again there are +     * two cases. If we're at the end of the string, we just need to check for +     * the given path's existence and type. If we're not at the end of the +     * string, we recurse.       */      if (*p != '\0') { +	char savedChar = *p;  	/* -	 * Note that we are modifying the string in place.  This won't work -	 * if the string is a static. +	 * Note that we are modifying the string in place. This won't work if +	 * the string is a static.  	 */ -	 savedChar = *p; -	 *p = '\0'; -	 firstSpecialChar = strpbrk(tail, "*[]?\\"); -	 *p = savedChar; +	*p = '\0'; +	firstSpecialChar = strpbrk(pattern, "*[]?\\"); +	*p = savedChar;      } else { -	firstSpecialChar = strpbrk(tail, "*[]?\\"); +	firstSpecialChar = strpbrk(pattern, "*[]?\\");      }      if (firstSpecialChar != NULL) {  	/* -	 * Look for matching files in the current directory.  The -	 * implementation of this function is platform specific, but may -	 * recursively call TclDoGlob.  For each file that matches, it will -	 * add the match onto the interp's result, or call TclDoGlob if there -	 * are more characters to be processed. +	 * Look for matching files in the given directory. The implementation +	 * of this function is filesystem specific. For each file that +	 * matches, it will add the match onto the resultPtr given.  	 */ -	return TclpMatchFiles(interp, separators, headPtr, tail, p); -    } -    Tcl_DStringAppend(headPtr, tail, p-tail); -    if (*p != '\0') { -	return TclDoGlob(interp, separators, headPtr, p); +	static Tcl_GlobTypeData dirOnly = { +	    TCL_GLOB_TYPE_DIR, 0, NULL, NULL +	}; +	char save = *p; +	Tcl_Obj *subdirsPtr; + +	if (*p == '\0') { +	    return Tcl_FSMatchInDirectory(interp, matchesObj, pathPtr, +		    pattern, types); +	} + +	/* +	 * We do the recursion ourselves. This makes implementing +	 * Tcl_FSMatchInDirectory for each filesystem much easier. +	 */ + +	*p = '\0'; +	TclNewObj(subdirsPtr); +	Tcl_IncrRefCount(subdirsPtr); +	result = Tcl_FSMatchInDirectory(interp, subdirsPtr, pathPtr, +		pattern, &dirOnly); +	*p = save; +	if (result == TCL_OK) { +	    int subdirc, i, repair = -1; +	    Tcl_Obj **subdirv; + +	    result = Tcl_ListObjGetElements(interp, subdirsPtr, +		    &subdirc, &subdirv); +	    for (i=0; result==TCL_OK && i<subdirc; i++) { +		Tcl_Obj *copy = NULL; + +		if (pathPtr == NULL && Tcl_GetString(subdirv[i])[0] == '~') { +		    Tcl_ListObjLength(NULL, matchesObj, &repair); +		    copy = subdirv[i]; +		    subdirv[i] = Tcl_NewStringObj("./", 2); +		    Tcl_AppendObjToObj(subdirv[i], copy); +		    Tcl_IncrRefCount(subdirv[i]); +		} +		result = DoGlob(interp, matchesObj, separators, subdirv[i], +			1, p+1, types); +		if (copy) { +		    int end; + +		    Tcl_DecrRefCount(subdirv[i]); +		    subdirv[i] = copy; +		    Tcl_ListObjLength(NULL, matchesObj, &end); +		    while (repair < end) { +			const char *bytes; +			int numBytes; +			Tcl_Obj *fixme, *newObj; + +			Tcl_ListObjIndex(NULL, matchesObj, repair, &fixme); +			bytes = Tcl_GetStringFromObj(fixme, &numBytes); +			newObj = Tcl_NewStringObj(bytes+2, numBytes-2); +			Tcl_ListObjReplace(NULL, matchesObj, repair, 1, +				1, &newObj); +			repair++; +		    } +		    repair = -1; +		} +	    } +	} +	TclDecrRefCount(subdirsPtr); +	return result;      }      /* -     * There are no more wildcards in the pattern and no more unprocessed -     * characters in the tail, so now we can construct the path and verify -     * the existence of the file. +     * We reach here with no pattern char in current section       */ -    switch (tclPlatform) { -	case TCL_PLATFORM_MAC: { -	    if (strchr(Tcl_DStringValue(headPtr), ':') == NULL) { -		Tcl_DStringAppend(headPtr, ":", 1); -	    } -	    name = Tcl_DStringValue(headPtr); -	    if (TclpAccess(name, F_OK) == 0) { -		if ((name[1] != '\0') && (strchr(name+1, ':') == NULL)) { -		    Tcl_AppendElement(interp, name + 1); +    if (*p == '\0') { +	int length; +	Tcl_DString append; + +	/* +	 * This is the code path reached by a command like 'glob foo'. +	 * +	 * There are no more wildcards in the pattern and no more unprocessed +	 * characters in the pattern, so now we can construct the path, and +	 * pass it to Tcl_FSMatchInDirectory with an empty pattern to verify +	 * the existence of the file and check it is of the correct type (if a +	 * 'types' flag it given -- if no such flag was given, we could just +	 * use 'Tcl_FSLStat', but for simplicity we keep to a common +	 * approach). +	 */ + +	Tcl_DStringInit(&append); +	Tcl_DStringAppend(&append, pattern, p-pattern); + +	if (pathPtr != NULL) { +	    (void) Tcl_GetStringFromObj(pathPtr, &length); +	} else { +	    length = 0; +	} + +	switch (tclPlatform) { +	case TCL_PLATFORM_WINDOWS: +	    if (length == 0 && (Tcl_DStringLength(&append) == 0)) { +		if (((*name == '\\') && (name[1] == '/' || +			name[1] == '\\')) || (*name == '/')) { +		    TclDStringAppendLiteral(&append, "/");  		} else { -		    Tcl_AppendElement(interp, name); +		    TclDStringAppendLiteral(&append, ".");  		}  	    } +  	    break; -	} -	case TCL_PLATFORM_WINDOWS: { -	    int exists; -	     -	    /* -	     * We need to convert slashes to backslashes before checking -	     * for the existence of the file.  Once we are done, we need -	     * to convert the slashes back. -	     */ -	    if (Tcl_DStringLength(headPtr) == 0) { -		if (((*name == '\\') && (name[1] == '/' || name[1] == '\\')) -			|| (*name == '/')) { -		    Tcl_DStringAppend(headPtr, "\\", 1); +	case TCL_PLATFORM_UNIX: +	    if (length == 0 && (Tcl_DStringLength(&append) == 0)) { +		if ((*name == '\\' && name[1] == '/') || (*name == '/')) { +		    TclDStringAppendLiteral(&append, "/");  		} else { -		    Tcl_DStringAppend(headPtr, ".", 1); -		} -	    } else { -		for (p = Tcl_DStringValue(headPtr); *p != '\0'; p++) { -		    if (*p == '/') { -			*p = '\\'; -		    } +		    TclDStringAppendLiteral(&append, ".");  		}  	    } -	    name = Tcl_DStringValue(headPtr); -	    exists = (TclpAccess(name, F_OK) == 0); +	    break; +	} -	    for (p = name; *p != '\0'; p++) { -		if (*p == '\\') { -		    *p = '/'; +	/* +	 * Common for all platforms. +	 */ + +	if (pathPtr == NULL) { +	    joinedPtr = TclDStringToObj(&append); +	} else if (flags) { +	    joinedPtr = TclNewFSPathObj(pathPtr, Tcl_DStringValue(&append), +		    Tcl_DStringLength(&append)); +	} else { +	    joinedPtr = Tcl_DuplicateObj(pathPtr); +	    if (strchr(separators, Tcl_DStringValue(&append)[0]) == NULL) { +		/* +		 * The current prefix must end in a separator. +		 */ + +		int len; +		const char *joined = Tcl_GetStringFromObj(joinedPtr,&len); + +		if (strchr(separators, joined[len-1]) == NULL) { +		    Tcl_AppendToObj(joinedPtr, "/", 1);  		}  	    } -	    if (exists) { -		Tcl_AppendElement(interp, name); -	    } -	    break; +	    Tcl_AppendToObj(joinedPtr, Tcl_DStringValue(&append), +		    Tcl_DStringLength(&append));  	} -	case TCL_PLATFORM_UNIX: { -	    if (Tcl_DStringLength(headPtr) == 0) { -		if ((*name == '\\' && name[1] == '/') || (*name == '/')) { -		    Tcl_DStringAppend(headPtr, "/", 1); -		} else { -		    Tcl_DStringAppend(headPtr, ".", 1); +	Tcl_IncrRefCount(joinedPtr); +	Tcl_DStringFree(&append); +	result = Tcl_FSMatchInDirectory(interp, matchesObj, joinedPtr, NULL, +		types); +	Tcl_DecrRefCount(joinedPtr); +	return result; +    } + +    /* +     * If it's not the end of the string, we must recurse +     */ + +    if (pathPtr == NULL) { +	joinedPtr = Tcl_NewStringObj(pattern, p-pattern); +    } else if (flags) { +	joinedPtr = TclNewFSPathObj(pathPtr, pattern, p-pattern); +    } else { +	joinedPtr = Tcl_DuplicateObj(pathPtr); +	if (strchr(separators, pattern[0]) == NULL) { +	    /* +	     * The current prefix must end in a separator, unless this is a +	     * volume-relative path. In particular globbing in Windows shares, +	     * when not using -dir or -path, e.g. 'glob [file join +	     * //machine/share/subdir *]' requires adding a separator here. +	     * This behaviour is not currently tested for in the test suite. +	     */ + +	    int len; +	    const char *joined = Tcl_GetStringFromObj(joinedPtr,&len); + +	    if (strchr(separators, joined[len-1]) == NULL) { +		if (Tcl_FSGetPathType(pathPtr) != TCL_PATH_VOLUME_RELATIVE) { +		    Tcl_AppendToObj(joinedPtr, "/", 1);  		}  	    } -	    name = Tcl_DStringValue(headPtr); -	    if (TclpAccess(name, F_OK) == 0) { -		Tcl_AppendElement(interp, name); -	    } -	    break;  	} +	Tcl_AppendToObj(joinedPtr, pattern, p-pattern);      } -    return TCL_OK; +    Tcl_IncrRefCount(joinedPtr); +    result = DoGlob(interp, matchesObj, separators, joinedPtr, 1, p, types); +    Tcl_DecrRefCount(joinedPtr); + +    return result; +} + +/* + *--------------------------------------------------------------------------- + * + * Tcl_AllocStatBuf -- + * + *	This procedure allocates a Tcl_StatBuf on the heap. It exists so that + *	extensions may be used unchanged on systems where largefile support is + *	optional. + * + * Results: + *	A pointer to a Tcl_StatBuf which may be deallocated by being passed to + *	ckfree(). + * + * Side effects: + *	None. + * + *--------------------------------------------------------------------------- + */ + +Tcl_StatBuf * +Tcl_AllocStatBuf(void) +{ +    return ckalloc(sizeof(Tcl_StatBuf)); +} + +/* + *--------------------------------------------------------------------------- + * + * Access functions for Tcl_StatBuf -- + * + *	These functions provide portable read-only access to the portable + *	fields of the Tcl_StatBuf structure (really a 'struct stat', 'struct + *	stat64' or something else related). [TIP #316] + * + * Results: + *	The value from the field being retrieved. + * + * Side effects: + *	None. + * + *--------------------------------------------------------------------------- + */ + +unsigned +Tcl_GetFSDeviceFromStat( +    const Tcl_StatBuf *statPtr) +{ +    return (unsigned) statPtr->st_dev; +} + +unsigned +Tcl_GetFSInodeFromStat( +    const Tcl_StatBuf *statPtr) +{ +    return (unsigned) statPtr->st_ino; +} + +unsigned +Tcl_GetModeFromStat( +    const Tcl_StatBuf *statPtr) +{ +    return (unsigned) statPtr->st_mode; +} + +int +Tcl_GetLinkCountFromStat( +    const Tcl_StatBuf *statPtr) +{ +    return (int)statPtr->st_nlink; +} + +int +Tcl_GetUserIdFromStat( +    const Tcl_StatBuf *statPtr) +{ +    return (int) statPtr->st_uid; +} + +int +Tcl_GetGroupIdFromStat( +    const Tcl_StatBuf *statPtr) +{ +    return (int) statPtr->st_gid; +} + +int +Tcl_GetDeviceTypeFromStat( +    const Tcl_StatBuf *statPtr) +{ +    return (int) statPtr->st_rdev; +} + +Tcl_WideInt +Tcl_GetAccessTimeFromStat( +    const Tcl_StatBuf *statPtr) +{ +    return (Tcl_WideInt) statPtr->st_atime; +} + +Tcl_WideInt +Tcl_GetModificationTimeFromStat( +    const Tcl_StatBuf *statPtr) +{ +    return (Tcl_WideInt) statPtr->st_mtime; +} + +Tcl_WideInt +Tcl_GetChangeTimeFromStat( +    const Tcl_StatBuf *statPtr) +{ +    return (Tcl_WideInt) statPtr->st_ctime; +} + +Tcl_WideUInt +Tcl_GetSizeFromStat( +    const Tcl_StatBuf *statPtr) +{ +    return (Tcl_WideUInt) statPtr->st_size; +} + +Tcl_WideUInt +Tcl_GetBlocksFromStat( +    const Tcl_StatBuf *statPtr) +{ +#ifdef HAVE_STRUCT_STAT_ST_BLOCKS +    return (Tcl_WideUInt) statPtr->st_blocks; +#else +    register unsigned blksize = Tcl_GetBlockSizeFromStat(statPtr); + +    return ((Tcl_WideUInt) statPtr->st_size + blksize - 1) / blksize; +#endif +} + +unsigned +Tcl_GetBlockSizeFromStat( +    const Tcl_StatBuf *statPtr) +{ +#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE +    return (unsigned) statPtr->st_blksize; +#else +    /* +     * Not a great guess, but will do... +     */ + +    return GUESSED_BLOCK_SIZE; +#endif  } + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ | 
