1 /*
2  * tclFileName.c --
3  *
4  *	This file contains routines for converting file names betwen native
5  *	and network form.
6  *
7  * Copyright (c) 1995-1998 Sun Microsystems, Inc.
8  * Copyright (c) 1998-1999 by Scriptics Corporation.
9  *
10  * See the file "license.terms" for information on usage and redistribution of
11  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
12  */
13 
14 #include "tclInt.h"
15 #include "tclRegexp.h"
16 #include "tclFileSystem.h" /* For TclGetPathType() */
17 
18 /*
19  * The following variable is set in the TclPlatformInit call to one of:
20  * TCL_PLATFORM_UNIX or TCL_PLATFORM_WINDOWS.
21  */
22 
23 TclPlatformType tclPlatform = TCL_PLATFORM_UNIX;
24 
25 /*
26  * Prototypes for local procedures defined in this file:
27  */
28 
29 static const char *	DoTildeSubst(Tcl_Interp *interp,
30 			    const char *user, Tcl_DString *resultPtr);
31 static const char *	ExtractWinRoot(const char *path,
32 			    Tcl_DString *resultPtr, int offset,
33 			    Tcl_PathType *typePtr);
34 static int		SkipToChar(char **stringPtr, int match);
35 static Tcl_Obj *	SplitWinPath(const char *path);
36 static Tcl_Obj *	SplitUnixPath(const char *path);
37 static int		DoGlob(Tcl_Interp *interp, Tcl_Obj *resultPtr,
38 			    const char *separators, Tcl_Obj *pathPtr, int flags,
39 			    char *pattern, Tcl_GlobTypeData *types);
40 
41 /*
42  *----------------------------------------------------------------------
43  *
44  * SetResultLength --
45  *
46  *	Resets the result DString for ExtractWinRoot to accommodate
47  *	any NT extended path prefixes.
48  *
49  * Results:
50  *	None.
51  *
52  * Side effects:
53  *	May modify the Tcl_DString.
54  *----------------------------------------------------------------------
55  */
56 
57 static void
SetResultLength(Tcl_DString * resultPtr,int offset,int extended)58 SetResultLength(
59     Tcl_DString *resultPtr,
60     int offset,
61     int extended)
62 {
63     Tcl_DStringSetLength(resultPtr, offset);
64     if (extended == 2) {
65 	Tcl_DStringAppend(resultPtr, "//?/UNC/", 8);
66     } else if (extended == 1) {
67 	Tcl_DStringAppend(resultPtr, "//?/", 4);
68     }
69 }
70 
71 /*
72  *----------------------------------------------------------------------
73  *
74  * ExtractWinRoot --
75  *
76  *	Matches the root portion of a Windows path and appends it to the
77  *	specified Tcl_DString.
78  *
79  * Results:
80  *	Returns the position in the path immediately after the root including
81  *	any trailing slashes. Appends a cleaned up version of the root to the
82  *	Tcl_DString at the specified offest.
83  *
84  * Side effects:
85  *	Modifies the specified Tcl_DString.
86  *
87  *----------------------------------------------------------------------
88  */
89 
90 static const char *
ExtractWinRoot(const char * path,Tcl_DString * resultPtr,int offset,Tcl_PathType * typePtr)91 ExtractWinRoot(
92     const char *path,		/* Path to parse. */
93     Tcl_DString *resultPtr,	/* Buffer to hold result. */
94     int offset,			/* Offset in buffer where result should be
95 				 * stored. */
96     Tcl_PathType *typePtr)	/* Where to store pathType result */
97 {
98     int extended = 0;
99 
100     if (   (path[0] == '/' || path[0] == '\\')
101 	&& (path[1] == '/' || path[1] == '\\')
102 	&& (path[2] == '?')
103 	&& (path[3] == '/' || path[3] == '\\')) {
104 	extended = 1;
105 	path = path + 4;
106 	if (path[0] == 'U' && path[1] == 'N' && path[2] == 'C'
107 	    && (path[3] == '/' || path[3] == '\\')) {
108 	    extended = 2;
109 	    path = path + 4;
110 	}
111     }
112 
113     if (path[0] == '/' || path[0] == '\\') {
114 	/*
115 	 * Might be a UNC or Vol-Relative path.
116 	 */
117 
118 	const char *host, *share, *tail;
119 	int hlen, slen;
120 
121 	if (path[1] != '/' && path[1] != '\\') {
122 	    SetResultLength(resultPtr, offset, extended);
123 	    *typePtr = TCL_PATH_VOLUME_RELATIVE;
124 	    Tcl_DStringAppend(resultPtr, "/", 1);
125 	    return &path[1];
126 	}
127 	host = &path[2];
128 
129 	/*
130 	 * Skip separators.
131 	 */
132 
133 	while (host[0] == '/' || host[0] == '\\') {
134 	    host++;
135 	}
136 
137 	for (hlen = 0; host[hlen];hlen++) {
138 	    if (host[hlen] == '/' || host[hlen] == '\\') {
139 		break;
140 	    }
141 	}
142 	if (host[hlen] == 0 || host[hlen+1] == 0) {
143 	    /*
144 	     * The path given is simply of the form '/foo', '//foo',
145 	     * '/////foo' or the same with backslashes. If there is exactly
146 	     * one leading '/' the path is volume relative (see filename man
147 	     * page). If there are more than one, we are simply assuming they
148 	     * are superfluous and we trim them away. (An alternative
149 	     * interpretation would be that it is a host name, but we have
150 	     * been documented that that is not the case).
151 	     */
152 
153 	    *typePtr = TCL_PATH_VOLUME_RELATIVE;
154 	    Tcl_DStringAppend(resultPtr, "/", 1);
155 	    return &path[2];
156 	}
157 	SetResultLength(resultPtr, offset, extended);
158 	share = &host[hlen];
159 
160 	/*
161 	 * Skip separators.
162 	 */
163 
164 	while (share[0] == '/' || share[0] == '\\') {
165 	    share++;
166 	}
167 
168 	for (slen=0; share[slen]; slen++) {
169 	    if (share[slen] == '/' || share[slen] == '\\') {
170 		break;
171 	    }
172 	}
173 	Tcl_DStringAppend(resultPtr, "//", 2);
174 	Tcl_DStringAppend(resultPtr, host, hlen);
175 	Tcl_DStringAppend(resultPtr, "/", 1);
176 	Tcl_DStringAppend(resultPtr, share, slen);
177 
178 	tail = &share[slen];
179 
180 	/*
181 	 * Skip separators.
182 	 */
183 
184 	while (tail[0] == '/' || tail[0] == '\\') {
185 	    tail++;
186 	}
187 
188 	*typePtr = TCL_PATH_ABSOLUTE;
189 	return tail;
190     } else if (*path && path[1] == ':') {
191 	/*
192 	 * Might be a drive separator.
193 	 */
194 
195 	SetResultLength(resultPtr, offset, extended);
196 
197 	if (path[2] != '/' && path[2] != '\\') {
198 	    *typePtr = TCL_PATH_VOLUME_RELATIVE;
199 	    Tcl_DStringAppend(resultPtr, path, 2);
200 	    return &path[2];
201 	} else {
202 	    const char *tail = &path[3];
203 
204 	    /*
205 	     * Skip separators.
206 	     */
207 
208 	    while (*tail && (tail[0] == '/' || tail[0] == '\\')) {
209 		tail++;
210 	    }
211 
212 	    *typePtr = TCL_PATH_ABSOLUTE;
213 	    Tcl_DStringAppend(resultPtr, path, 2);
214 	    Tcl_DStringAppend(resultPtr, "/", 1);
215 
216 	    return tail;
217 	}
218     } else {
219 	int abs = 0;
220 
221 	/*
222 	 * Check for Windows devices.
223 	 */
224 
225 	if ((path[0] == 'c' || path[0] == 'C')
226 		&& (path[1] == 'o' || path[1] == 'O')) {
227 	    if ((path[2] == 'm' || path[2] == 'M')
228 		    && path[3] >= '1' && path[3] <= '9') {
229 		/*
230 		 * May have match for 'com[1-9]:?', which is a serial port.
231 		 */
232 
233 		if (path[4] == '\0') {
234 		    abs = 4;
235 		} else if (path [4] == ':' && path[5] == '\0') {
236 		    abs = 5;
237 		}
238 
239 	    } else if ((path[2] == 'n' || path[2] == 'N') && path[3] == '\0') {
240 		/*
241 		 * Have match for 'con'.
242 		 */
243 
244 		abs = 3;
245 	    }
246 
247 	} else if ((path[0] == 'l' || path[0] == 'L')
248 		&& (path[1] == 'p' || path[1] == 'P')
249 		&& (path[2] == 't' || path[2] == 'T')) {
250 	    if (path[3] >= '1' && path[3] <= '9') {
251 		/*
252 		 * May have match for 'lpt[1-9]:?'
253 		 */
254 
255 		if (path[4] == '\0') {
256 		    abs = 4;
257 		} else if (path [4] == ':' && path[5] == '\0') {
258 		    abs = 5;
259 		}
260 	    }
261 
262 	} else if ((path[0] == 'p' || path[0] == 'P')
263 		&& (path[1] == 'r' || path[1] == 'R')
264 		&& (path[2] == 'n' || path[2] == 'N')
265 		&& path[3] == '\0') {
266 	    /*
267 	     * Have match for 'prn'.
268 	     */
269 	    abs = 3;
270 
271 	} else if ((path[0] == 'n' || path[0] == 'N')
272 		&& (path[1] == 'u' || path[1] == 'U')
273 		&& (path[2] == 'l' || path[2] == 'L')
274 		&& path[3] == '\0') {
275 	    /*
276 	     * Have match for 'nul'.
277 	     */
278 
279 	    abs = 3;
280 
281 	} else if ((path[0] == 'a' || path[0] == 'A')
282 		&& (path[1] == 'u' || path[1] == 'U')
283 		&& (path[2] == 'x' || path[2] == 'X')
284 		&& path[3] == '\0') {
285 	    /*
286 	     * Have match for 'aux'.
287 	     */
288 
289 	    abs = 3;
290 	}
291 
292 	if (abs != 0) {
293 	    *typePtr = TCL_PATH_ABSOLUTE;
294 	    SetResultLength(resultPtr, offset, extended);
295 	    Tcl_DStringAppend(resultPtr, path, abs);
296 	    return path + abs;
297 	}
298     }
299 
300     /*
301      * Anything else is treated as relative.
302      */
303 
304     *typePtr = TCL_PATH_RELATIVE;
305     return path;
306 }
307 
308 /*
309  *----------------------------------------------------------------------
310  *
311  * Tcl_GetPathType --
312  *
313  *	Determines whether a given path is relative to the current directory,
314  *	relative to the current volume, or absolute.
315  *
316  *	The objectified Tcl_FSGetPathType should be used in preference to this
317  *	function (as you can see below, this is just a wrapper around that
318  *	other function).
319  *
320  * Results:
321  *	Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or
322  *	TCL_PATH_VOLUME_RELATIVE.
323  *
324  * Side effects:
325  *	None.
326  *
327  *----------------------------------------------------------------------
328  */
329 
330 Tcl_PathType
Tcl_GetPathType(const char * path)331 Tcl_GetPathType(
332     const char *path)
333 {
334     Tcl_PathType type;
335     Tcl_Obj *tempObj = Tcl_NewStringObj(path,-1);
336 
337     Tcl_IncrRefCount(tempObj);
338     type = Tcl_FSGetPathType(tempObj);
339     Tcl_DecrRefCount(tempObj);
340     return type;
341 }
342 
343 /*
344  *----------------------------------------------------------------------
345  *
346  * TclpGetNativePathType --
347  *
348  *	Determines whether a given path is relative to the current directory,
349  *	relative to the current volume, or absolute, but ONLY FOR THE NATIVE
350  *	FILESYSTEM. This function is called from tclIOUtil.c (but needs to be
351  *	here due to its dependence on static variables/functions in this
352  *	file). The exported function Tcl_FSGetPathType should be used by
353  *	extensions.
354  *
355  *	Note that '~' paths are always considered TCL_PATH_ABSOLUTE, even
356  *	though expanding the '~' could lead to any possible path type. This
357  *	function should therefore be considered a low-level, string
358  *	manipulation function only -- it doesn't actually do any expansion in
359  *	making its determination.
360  *
361  * Results:
362  *	Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or
363  *	TCL_PATH_VOLUME_RELATIVE.
364  *
365  * Side effects:
366  *	None.
367  *
368  *----------------------------------------------------------------------
369  */
370 
371 Tcl_PathType
TclpGetNativePathType(Tcl_Obj * pathPtr,int * driveNameLengthPtr,Tcl_Obj ** driveNameRef)372 TclpGetNativePathType(
373     Tcl_Obj *pathPtr,		/* Native path of interest */
374     int *driveNameLengthPtr,	/* Returns length of drive, if non-NULL and
375 				 * path was absolute */
376     Tcl_Obj **driveNameRef)
377 {
378     Tcl_PathType type = TCL_PATH_ABSOLUTE;
379     int pathLen;
380     const char *path = Tcl_GetStringFromObj(pathPtr, &pathLen);
381 
382     if (path[0] == '~') {
383 	/*
384 	 * This case is common to all platforms. Paths that begin with ~ are
385 	 * absolute.
386 	 */
387 
388 	if (driveNameLengthPtr != NULL) {
389 	    const char *end = path + 1;
390 	    while ((*end != '\0') && (*end != '/')) {
391 		end++;
392 	    }
393 	    *driveNameLengthPtr = end - path;
394 	}
395     } else {
396 	switch (tclPlatform) {
397 	case TCL_PLATFORM_UNIX: {
398 	    const char *origPath = path;
399 
400 	    /*
401 	     * Paths that begin with / are absolute.
402 	     */
403 
404 	    if (path[0] == '/') {
405 		++path;
406 #if defined(__CYGWIN__) || defined(__QNX__)
407 		/*
408 		 * Check for "//" network path prefix
409 		 */
410 		if ((*path == '/') && path[1] && (path[1] != '/')) {
411 		    path += 2;
412 		    while (*path && *path != '/') {
413 			++path;
414 		    }
415 #if defined(__CYGWIN__)
416 		    /* UNC paths need to be followed by a share name */
417 		    if (*path++ && (*path && *path != '/')) {
418 			++path;
419 			while (*path && *path != '/') {
420 			    ++path;
421 			}
422 		    } else {
423 			path = origPath + 1;
424 		    }
425 #endif
426 		}
427 #endif
428 		if (driveNameLengthPtr != NULL) {
429 		    /*
430 		     * We need this addition in case the QNX or Cygwin code was used.
431 		     */
432 
433 		    *driveNameLengthPtr = (path - origPath);
434 		}
435 	    } else {
436 		type = TCL_PATH_RELATIVE;
437 	    }
438 	    break;
439 	}
440 	case TCL_PLATFORM_WINDOWS: {
441 	    Tcl_DString ds;
442 	    const char *rootEnd;
443 
444 	    Tcl_DStringInit(&ds);
445 	    rootEnd = ExtractWinRoot(path, &ds, 0, &type);
446 	    if ((rootEnd != path) && (driveNameLengthPtr != NULL)) {
447 		*driveNameLengthPtr = rootEnd - path;
448 		if (driveNameRef != NULL) {
449 		    *driveNameRef = Tcl_NewStringObj(Tcl_DStringValue(&ds),
450 			    Tcl_DStringLength(&ds));
451 		    Tcl_IncrRefCount(*driveNameRef);
452 		}
453 	    }
454 	    Tcl_DStringFree(&ds);
455 	    break;
456 	}
457 	}
458     }
459     return type;
460 }
461 
462 /*
463  *---------------------------------------------------------------------------
464  *
465  * TclpNativeSplitPath --
466  *
467  *	This function takes the given Tcl_Obj, which should be a valid path,
468  *	and returns a Tcl List object containing each segment of that path as
469  *	an element.
470  *
471  *	Note this function currently calls the older Split(Plat)Path
472  *	functions, which require more memory allocation than is desirable.
473  *
474  * Results:
475  *	Returns list object with refCount of zero. If the passed in lenPtr is
476  *	non-NULL, we use it to return the number of elements in the returned
477  *	list.
478  *
479  * Side effects:
480  *	None.
481  *
482  *---------------------------------------------------------------------------
483  */
484 
485 Tcl_Obj *
TclpNativeSplitPath(Tcl_Obj * pathPtr,int * lenPtr)486 TclpNativeSplitPath(
487     Tcl_Obj *pathPtr,		/* Path to split. */
488     int *lenPtr)		/* int to store number of path elements. */
489 {
490     Tcl_Obj *resultPtr = NULL;	/* Needed only to prevent gcc warnings. */
491 
492     /*
493      * Perform platform specific splitting.
494      */
495 
496     switch (tclPlatform) {
497     case TCL_PLATFORM_UNIX:
498 	resultPtr = SplitUnixPath(Tcl_GetString(pathPtr));
499 	break;
500 
501     case TCL_PLATFORM_WINDOWS:
502 	resultPtr = SplitWinPath(Tcl_GetString(pathPtr));
503 	break;
504     }
505 
506     /*
507      * Compute the number of elements in the result.
508      */
509 
510     if (lenPtr != NULL) {
511 	Tcl_ListObjLength(NULL, resultPtr, lenPtr);
512     }
513     return resultPtr;
514 }
515 
516 /*
517  *----------------------------------------------------------------------
518  *
519  * Tcl_SplitPath --
520  *
521  *	Split a path into a list of path components. The first element of the
522  *	list will have the same path type as the original path.
523  *
524  * Results:
525  *	Returns a standard Tcl result. The interpreter result contains a list
526  *	of path components. *argvPtr will be filled in with the address of an
527  *	array whose elements point to the elements of path, in order.
528  *	*argcPtr will get filled in with the number of valid elements in the
529  *	array. A single block of memory is dynamically allocated to hold both
530  *	the argv array and a copy of the path elements. The caller must
531  *	eventually free this memory by calling ckfree() on *argvPtr. Note:
532  *	*argvPtr and *argcPtr are only modified if the procedure returns
533  *	normally.
534  *
535  * Side effects:
536  *	Allocates memory.
537  *
538  *----------------------------------------------------------------------
539  */
540 
541 void
Tcl_SplitPath(const char * path,int * argcPtr,const char *** argvPtr)542 Tcl_SplitPath(
543     const char *path,		/* Pointer to string containing a path. */
544     int *argcPtr,		/* Pointer to location to fill in with the
545 				 * number of elements in the path. */
546     const char ***argvPtr)	/* Pointer to place to store pointer to array
547 				 * of pointers to path elements. */
548 {
549     Tcl_Obj *resultPtr = NULL;	/* Needed only to prevent gcc warnings. */
550     Tcl_Obj *tmpPtr, *eltPtr;
551     int i, size, len;
552     char *p;
553     const char *str;
554 
555     /*
556      * Perform the splitting, using objectified, vfs-aware code.
557      */
558 
559     tmpPtr = Tcl_NewStringObj(path, -1);
560     Tcl_IncrRefCount(tmpPtr);
561     resultPtr = Tcl_FSSplitPath(tmpPtr, argcPtr);
562     Tcl_IncrRefCount(resultPtr);
563     Tcl_DecrRefCount(tmpPtr);
564 
565     /*
566      * Calculate space required for the result.
567      */
568 
569     size = 1;
570     for (i = 0; i < *argcPtr; i++) {
571 	Tcl_ListObjIndex(NULL, resultPtr, i, &eltPtr);
572 	Tcl_GetStringFromObj(eltPtr, &len);
573 	size += len + 1;
574     }
575 
576     /*
577      * Allocate a buffer large enough to hold the contents of all of the list
578      * plus the argv pointers and the terminating NULL pointer.
579      */
580 
581     *argvPtr = (const char **) ckalloc((unsigned)
582 	    ((((*argcPtr) + 1) * sizeof(char *)) + size));
583 
584     /*
585      * Position p after the last argv pointer and copy the contents of the
586      * list in, piece by piece.
587      */
588 
589     p = (char *) &(*argvPtr)[(*argcPtr) + 1];
590     for (i = 0; i < *argcPtr; i++) {
591 	Tcl_ListObjIndex(NULL, resultPtr, i, &eltPtr);
592 	str = Tcl_GetStringFromObj(eltPtr, &len);
593 	memcpy(p, str, (size_t) len+1);
594 	p += len+1;
595     }
596 
597     /*
598      * Now set up the argv pointers.
599      */
600 
601     p = (char *) &(*argvPtr)[(*argcPtr) + 1];
602 
603     for (i = 0; i < *argcPtr; i++) {
604 	(*argvPtr)[i] = p;
605 	for (; *(p++)!='\0'; );
606     }
607     (*argvPtr)[i] = NULL;
608 
609     /*
610      * Free the result ptr given to us by Tcl_FSSplitPath
611      */
612 
613     Tcl_DecrRefCount(resultPtr);
614 }
615 
616 /*
617  *----------------------------------------------------------------------
618  *
619  * SplitUnixPath --
620  *
621  *	This routine is used by Tcl_(FS)SplitPath to handle splitting Unix
622  *	paths.
623  *
624  * Results:
625  *	Returns a newly allocated Tcl list object.
626  *
627  * Side effects:
628  *	None.
629  *
630  *----------------------------------------------------------------------
631  */
632 
633 static Tcl_Obj *
SplitUnixPath(const char * path)634 SplitUnixPath(
635     const char *path)		/* Pointer to string containing a path. */
636 {
637     int length;
638     const char *origPath = path, *elementStart;
639     Tcl_Obj *result = Tcl_NewObj();
640 
641     /*
642      * Deal with the root directory as a special case.
643      */
644 
645     if (*path == '/') {
646 	Tcl_Obj *rootElt;
647 	++path;
648 #if defined(__CYGWIN__) || defined(__QNX__)
649 	/*
650 	 * Check for "//" network path prefix
651 	 */
652 	if ((*path == '/') && path[1] && (path[1] != '/')) {
653 	    path += 2;
654 	    while (*path && *path != '/') {
655 		++path;
656 	    }
657 #if defined(__CYGWIN__)
658 	    /* UNC paths need to be followed by a share name */
659 	    if (*path++ && (*path && *path != '/')) {
660 		++path;
661 		while (*path && *path != '/') {
662 		    ++path;
663 		}
664 	    } else {
665 		path = origPath + 1;
666 	    }
667 #endif
668 	}
669 #endif
670 	rootElt = Tcl_NewStringObj(origPath, path - origPath);
671 	Tcl_ListObjAppendElement(NULL, result, rootElt);
672 	while (*path == '/') {
673 	    ++path;
674 	}
675     }
676 
677     /*
678      * Split on slashes. Embedded elements that start with tilde will be
679      * prefixed with "./" so they are not affected by tilde substitution.
680      */
681 
682     for (;;) {
683 	elementStart = path;
684 	while ((*path != '\0') && (*path != '/')) {
685 	    path++;
686 	}
687 	length = path - elementStart;
688 	if (length > 0) {
689 	    Tcl_Obj *nextElt;
690 	    if ((elementStart[0] == '~') && (elementStart != origPath)) {
691 		TclNewLiteralStringObj(nextElt, "./");
692 		Tcl_AppendToObj(nextElt, elementStart, length);
693 	    } else {
694 		nextElt = Tcl_NewStringObj(elementStart, length);
695 	    }
696 	    Tcl_ListObjAppendElement(NULL, result, nextElt);
697 	}
698 	if (*path++ == '\0') {
699 	    break;
700 	}
701     }
702     return result;
703 }
704 
705 /*
706  *----------------------------------------------------------------------
707  *
708  * SplitWinPath --
709  *
710  *	This routine is used by Tcl_(FS)SplitPath to handle splitting Windows
711  *	paths.
712  *
713  * Results:
714  *	Returns a newly allocated Tcl list object.
715  *
716  * Side effects:
717  *	None.
718  *
719  *----------------------------------------------------------------------
720  */
721 
722 static Tcl_Obj *
SplitWinPath(const char * path)723 SplitWinPath(
724     const char *path)		/* Pointer to string containing a path. */
725 {
726     int length;
727     const char *p, *elementStart;
728     Tcl_PathType type = TCL_PATH_ABSOLUTE;
729     Tcl_DString buf;
730     Tcl_Obj *result = Tcl_NewObj();
731     Tcl_DStringInit(&buf);
732 
733     p = ExtractWinRoot(path, &buf, 0, &type);
734 
735     /*
736      * Terminate the root portion, if we matched something.
737      */
738 
739     if (p != path) {
740 	Tcl_ListObjAppendElement(NULL, result, Tcl_NewStringObj(
741 		Tcl_DStringValue(&buf), Tcl_DStringLength(&buf)));
742     }
743     Tcl_DStringFree(&buf);
744 
745     /*
746      * Split on slashes. Embedded elements that start with tilde or a drive
747      * letter will be prefixed with "./" so they are not affected by tilde
748      * substitution.
749      */
750 
751     do {
752 	elementStart = p;
753 	while ((*p != '\0') && (*p != '/') && (*p != '\\')) {
754 	    p++;
755 	}
756 	length = p - elementStart;
757 	if (length > 0) {
758 	    Tcl_Obj *nextElt;
759 	    if ((elementStart != path) && ((elementStart[0] == '~')
760 		    || (isalpha(UCHAR(elementStart[0]))
761 			&& elementStart[1] == ':'))) {
762 		TclNewLiteralStringObj(nextElt, "./");
763 		Tcl_AppendToObj(nextElt, elementStart, length);
764 	    } else {
765 		nextElt = Tcl_NewStringObj(elementStart, length);
766 	    }
767 	    Tcl_ListObjAppendElement(NULL, result, nextElt);
768 	}
769     } while (*p++ != '\0');
770 
771     return result;
772 }
773 
774 /*
775  *---------------------------------------------------------------------------
776  *
777  * Tcl_FSJoinToPath --
778  *
779  *	This function takes the given object, which should usually be a valid
780  *	path or NULL, and joins onto it the array of paths segments given.
781  *
782  *	The objects in the array given will temporarily have their refCount
783  *	increased by one, and then decreased by one when this function exits
784  *	(which means if they had zero refCount when we were called, they will
785  *	be freed).
786  *
787  * Results:
788  *	Returns object owned by the caller (which should increment its
789  *	refCount) - typically an object with refCount of zero.
790  *
791  * Side effects:
792  *	None.
793  *
794  *---------------------------------------------------------------------------
795  */
796 
797 Tcl_Obj *
Tcl_FSJoinToPath(Tcl_Obj * pathPtr,int objc,Tcl_Obj * const objv[])798 Tcl_FSJoinToPath(
799     Tcl_Obj *pathPtr,		/* Valid path or NULL. */
800     int objc,			/* Number of array elements to join */
801     Tcl_Obj *const objv[])	/* Path elements to join. */
802 {
803     int i;
804     Tcl_Obj *lobj, *ret;
805 
806     if (pathPtr == NULL) {
807 	lobj = Tcl_NewListObj(0, NULL);
808     } else {
809 	lobj = Tcl_NewListObj(1, &pathPtr);
810     }
811 
812     for (i = 0; i<objc;i++) {
813 	Tcl_ListObjAppendElement(NULL, lobj, objv[i]);
814     }
815     ret = Tcl_FSJoinPath(lobj, -1);
816 
817     /*
818      * It is possible that 'ret' is just a member of the list and is therefore
819      * going to be freed here. Therefore we must adjust the refCount manually.
820      * (It would be better if we changed the documentation of this function
821      * and Tcl_FSJoinPath so that the returned object already has a refCount
822      * for the caller, hence avoiding these subtleties (and code ugliness)).
823      */
824 
825     Tcl_IncrRefCount(ret);
826     Tcl_DecrRefCount(lobj);
827     ret->refCount--;
828     return ret;
829 }
830 
831 /*
832  *---------------------------------------------------------------------------
833  *
834  * TclpNativeJoinPath --
835  *
836  *	'prefix' is absolute, 'joining' is relative to prefix.
837  *
838  * Results:
839  *	modifies prefix
840  *
841  * Side effects:
842  *	None.
843  *
844  *---------------------------------------------------------------------------
845  */
846 
847 void
TclpNativeJoinPath(Tcl_Obj * prefix,const char * joining)848 TclpNativeJoinPath(
849     Tcl_Obj *prefix,
850     const char *joining)
851 {
852     int length, needsSep;
853     char *dest;
854     const char *p;
855     const char *start;
856 
857     start = Tcl_GetStringFromObj(prefix, &length);
858 
859     /*
860      * Remove the ./ from tilde prefixed elements, and drive-letter prefixed
861      * elements on Windows, unless it is the first component.
862      */
863 
864     p = joining;
865 
866     if (length != 0) {
867 	if ((p[0] == '.') && (p[1] == '/') && ((p[2] == '~')
868 		|| (tclPlatform==TCL_PLATFORM_WINDOWS && isalpha(UCHAR(p[2]))
869 		&& (p[3] == ':')))) {
870 	    p += 2;
871 	}
872     }
873     if (*p == '\0') {
874 	return;
875     }
876 
877     switch (tclPlatform) {
878     case TCL_PLATFORM_UNIX:
879 	/*
880 	 * Append a separator if needed.
881 	 */
882 
883 	if (length > 0 && (start[length-1] != '/')) {
884 	    Tcl_AppendToObj(prefix, "/", 1);
885 	    Tcl_GetStringFromObj(prefix, &length);
886 	}
887 	needsSep = 0;
888 
889 	/*
890 	 * Append the element, eliminating duplicate and trailing slashes.
891 	 */
892 
893 	Tcl_SetObjLength(prefix, length + (int) strlen(p));
894 
895 	dest = Tcl_GetString(prefix) + length;
896 	for (; *p != '\0'; p++) {
897 	    if (*p == '/') {
898 		while (p[1] == '/') {
899 		    p++;
900 		}
901 		if (p[1] != '\0' && needsSep) {
902 		    *dest++ = '/';
903 		}
904 	    } else {
905 		*dest++ = *p;
906 		needsSep = 1;
907 	    }
908 	}
909 	length = dest - Tcl_GetString(prefix);
910 	Tcl_SetObjLength(prefix, length);
911 	break;
912 
913     case TCL_PLATFORM_WINDOWS:
914 	/*
915 	 * Check to see if we need to append a separator.
916 	 */
917 
918 	if ((length > 0) &&
919 		(start[length-1] != '/') && (start[length-1] != ':')) {
920 	    Tcl_AppendToObj(prefix, "/", 1);
921 	    Tcl_GetStringFromObj(prefix, &length);
922 	}
923 	needsSep = 0;
924 
925 	/*
926 	 * Append the element, eliminating duplicate and trailing slashes.
927 	 */
928 
929 	Tcl_SetObjLength(prefix, length + (int) strlen(p));
930 	dest = Tcl_GetString(prefix) + length;
931 	for (; *p != '\0'; p++) {
932 	    if ((*p == '/') || (*p == '\\')) {
933 		while ((p[1] == '/') || (p[1] == '\\')) {
934 		    p++;
935 		}
936 		if ((p[1] != '\0') && needsSep) {
937 		    *dest++ = '/';
938 		}
939 	    } else {
940 		*dest++ = *p;
941 		needsSep = 1;
942 	    }
943 	}
944 	length = dest - Tcl_GetString(prefix);
945 	Tcl_SetObjLength(prefix, length);
946 	break;
947     }
948     return;
949 }
950 
951 /*
952  *----------------------------------------------------------------------
953  *
954  * Tcl_JoinPath --
955  *
956  *	Combine a list of paths in a platform specific manner. The function
957  *	'Tcl_FSJoinPath' should be used in preference where possible.
958  *
959  * Results:
960  *	Appends the joined path to the end of the specified Tcl_DString
961  *	returning a pointer to the resulting string. Note that the
962  *	Tcl_DString must already be initialized.
963  *
964  * Side effects:
965  *	Modifies the Tcl_DString.
966  *
967  *----------------------------------------------------------------------
968  */
969 
970 char *
Tcl_JoinPath(int argc,const char * const * argv,Tcl_DString * resultPtr)971 Tcl_JoinPath(
972     int argc,
973     const char *const *argv,
974     Tcl_DString *resultPtr)	/* Pointer to previously initialized DString */
975 {
976     int i, len;
977     Tcl_Obj *listObj = Tcl_NewObj();
978     Tcl_Obj *resultObj;
979     const char *resultStr;
980 
981     /*
982      * Build the list of paths.
983      */
984 
985     for (i = 0; i < argc; i++) {
986 	Tcl_ListObjAppendElement(NULL, listObj,
987 		Tcl_NewStringObj(argv[i], -1));
988     }
989 
990     /*
991      * Ask the objectified code to join the paths.
992      */
993 
994     Tcl_IncrRefCount(listObj);
995     resultObj = Tcl_FSJoinPath(listObj, argc);
996     Tcl_IncrRefCount(resultObj);
997     Tcl_DecrRefCount(listObj);
998 
999     /*
1000      * Store the result.
1001      */
1002 
1003     resultStr = Tcl_GetStringFromObj(resultObj, &len);
1004     Tcl_DStringAppend(resultPtr, resultStr, len);
1005     Tcl_DecrRefCount(resultObj);
1006 
1007     /*
1008      * Return a pointer to the result.
1009      */
1010 
1011     return Tcl_DStringValue(resultPtr);
1012 }
1013 
1014 /*
1015  *---------------------------------------------------------------------------
1016  *
1017  * Tcl_TranslateFileName --
1018  *
1019  *	Converts a file name into a form usable by the native system
1020  *	interfaces. If the name starts with a tilde, it will produce a name
1021  *	where the tilde and following characters have been replaced by the
1022  *	home directory location for the named user.
1023  *
1024  * Results:
1025  *	The return value is a pointer to a string containing the name after
1026  *	tilde substitution. If there was no tilde substitution, the return
1027  *	value is a pointer to a copy of the original string. If there was an
1028  *	error in processing the name, then an error message is left in the
1029  *	interp's result (if interp was not NULL) and the return value is NULL.
1030  *	Space for the return value is allocated in bufferPtr; the caller must
1031  *	call Tcl_DStringFree() to free the space if the return value was not
1032  *	NULL.
1033  *
1034  * Side effects:
1035  *	None.
1036  *
1037  *----------------------------------------------------------------------
1038  */
1039 
1040 char *
Tcl_TranslateFileName(Tcl_Interp * interp,const char * name,Tcl_DString * bufferPtr)1041 Tcl_TranslateFileName(
1042     Tcl_Interp *interp,		/* Interpreter in which to store error message
1043 				 * (if necessary). */
1044     const char *name,		/* File name, which may begin with "~" (to
1045 				 * indicate current user's home directory) or
1046 				 * "~<user>" (to indicate any user's home
1047 				 * directory). */
1048     Tcl_DString *bufferPtr)	/* Uninitialized or free DString filled with
1049 				 * name after tilde substitution. */
1050 {
1051     Tcl_Obj *path = Tcl_NewStringObj(name, -1);
1052     Tcl_Obj *transPtr;
1053 
1054     Tcl_IncrRefCount(path);
1055     transPtr = Tcl_FSGetTranslatedPath(interp, path);
1056     if (transPtr == NULL) {
1057 	Tcl_DecrRefCount(path);
1058 	return NULL;
1059     }
1060 
1061     Tcl_DStringInit(bufferPtr);
1062     Tcl_DStringAppend(bufferPtr, Tcl_GetString(transPtr), -1);
1063     Tcl_DecrRefCount(path);
1064     Tcl_DecrRefCount(transPtr);
1065 
1066     /*
1067      * Convert forward slashes to backslashes in Windows paths because some
1068      * system interfaces don't accept forward slashes.
1069      */
1070 
1071     if (tclPlatform == TCL_PLATFORM_WINDOWS) {
1072 	register char *p;
1073 	for (p = Tcl_DStringValue(bufferPtr); *p != '\0'; p++) {
1074 	    if (*p == '/') {
1075 		*p = '\\';
1076 	    }
1077 	}
1078     }
1079 
1080     return Tcl_DStringValue(bufferPtr);
1081 }
1082 
1083 /*
1084  *----------------------------------------------------------------------
1085  *
1086  * TclGetExtension --
1087  *
1088  *	This function returns a pointer to the beginning of the extension part
1089  *	of a file name.
1090  *
1091  * Results:
1092  *	Returns a pointer into name which indicates where the extension
1093  *	starts. If there is no extension, returns NULL.
1094  *
1095  * Side effects:
1096  *	None.
1097  *
1098  *----------------------------------------------------------------------
1099  */
1100 
1101 const char *
TclGetExtension(const char * name)1102 TclGetExtension(
1103     const char *name)		/* File name to parse. */
1104 {
1105     const char *p, *lastSep;
1106 
1107     /*
1108      * First find the last directory separator.
1109      */
1110 
1111     lastSep = NULL;		/* Needed only to prevent gcc warnings. */
1112     switch (tclPlatform) {
1113     case TCL_PLATFORM_UNIX:
1114 	lastSep = strrchr(name, '/');
1115 	break;
1116 
1117     case TCL_PLATFORM_WINDOWS:
1118 	lastSep = NULL;
1119 	for (p = name; *p != '\0'; p++) {
1120 	    if (strchr("/\\:", *p) != NULL) {
1121 		lastSep = p;
1122 	    }
1123 	}
1124 	break;
1125     }
1126     p = strrchr(name, '.');
1127     if ((p != NULL) && (lastSep != NULL) && (lastSep > p)) {
1128 	p = NULL;
1129     }
1130 
1131     /*
1132      * In earlier versions, we used to back up to the first period in a series
1133      * so that "foo..o" would be split into "foo" and "..o". This is a
1134      * confusing and usually incorrect behavior, so now we split at the last
1135      * period in the name.
1136      */
1137 
1138     return p;
1139 }
1140 
1141 /*
1142  *----------------------------------------------------------------------
1143  *
1144  * DoTildeSubst --
1145  *
1146  *	Given a string following a tilde, this routine returns the
1147  *	corresponding home directory.
1148  *
1149  * Results:
1150  *	The result is a pointer to a static string containing the home
1151  *	directory in native format. If there was an error in processing the
1152  *	substitution, then an error message is left in the interp's result and
1153  *	the return value is NULL. On success, the results are appended to
1154  *	resultPtr, and the contents of resultPtr are returned.
1155  *
1156  * Side effects:
1157  *	Information may be left in resultPtr.
1158  *
1159  *----------------------------------------------------------------------
1160  */
1161 
1162 static const char *
DoTildeSubst(Tcl_Interp * interp,const char * user,Tcl_DString * resultPtr)1163 DoTildeSubst(
1164     Tcl_Interp *interp,		/* Interpreter in which to store error message
1165 				 * (if necessary). */
1166     const char *user,		/* Name of user whose home directory should be
1167 				 * substituted, or "" for current user. */
1168     Tcl_DString *resultPtr)	/* Initialized DString filled with name after
1169 				 * tilde substitution. */
1170 {
1171     const char *dir;
1172 
1173     if (*user == '\0') {
1174 	Tcl_DString dirString;
1175 
1176 	dir = TclGetEnv("HOME", &dirString);
1177 	if (dir == NULL) {
1178 	    if (interp) {
1179 		Tcl_ResetResult(interp);
1180 		Tcl_AppendResult(interp, "couldn't find HOME environment "
1181 			"variable to expand path", NULL);
1182 	    }
1183 	    return NULL;
1184 	}
1185 	Tcl_JoinPath(1, &dir, resultPtr);
1186 	Tcl_DStringFree(&dirString);
1187     } else if (TclpGetUserHome(user, resultPtr) == NULL) {
1188 	if (interp) {
1189 	    Tcl_ResetResult(interp);
1190 	    Tcl_AppendResult(interp, "user \"", user, "\" doesn't exist",
1191 		    NULL);
1192 	}
1193 	return NULL;
1194     }
1195     return Tcl_DStringValue(resultPtr);
1196 }
1197 
1198 /*
1199  *----------------------------------------------------------------------
1200  *
1201  * Tcl_GlobObjCmd --
1202  *
1203  *	This procedure is invoked to process the "glob" Tcl command. See the
1204  *	user documentation for details on what it does.
1205  *
1206  * Results:
1207  *	A standard Tcl result.
1208  *
1209  * Side effects:
1210  *	See the user documentation.
1211  *
1212  *----------------------------------------------------------------------
1213  */
1214 
1215 	/* ARGSUSED */
1216 int
Tcl_GlobObjCmd(ClientData dummy,Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])1217 Tcl_GlobObjCmd(
1218     ClientData dummy,		/* Not used. */
1219     Tcl_Interp *interp,		/* Current interpreter. */
1220     int objc,			/* Number of arguments. */
1221     Tcl_Obj *const objv[])	/* Argument objects. */
1222 {
1223     int index, i, globFlags, length, join, dir, result;
1224     char *string;
1225     const char *separators;
1226     Tcl_Obj *typePtr, *resultPtr, *look;
1227     Tcl_Obj *pathOrDir = NULL;
1228     Tcl_DString prefix;
1229     static const char *options[] = {
1230 	"-directory", "-join", "-nocomplain", "-path", "-tails",
1231 	"-types", "--", NULL
1232     };
1233     enum options {
1234 	GLOB_DIR, GLOB_JOIN, GLOB_NOCOMPLAIN, GLOB_PATH, GLOB_TAILS,
1235 	GLOB_TYPE, GLOB_LAST
1236     };
1237     enum pathDirOptions {PATH_NONE = -1 , PATH_GENERAL = 0, PATH_DIR = 1};
1238     Tcl_GlobTypeData *globTypes = NULL;
1239 
1240     globFlags = 0;
1241     join = 0;
1242     dir = PATH_NONE;
1243     typePtr = NULL;
1244     for (i = 1; i < objc; i++) {
1245 	if (Tcl_GetIndexFromObj(interp, objv[i], options, "option", 0,
1246 		&index) != TCL_OK) {
1247 	    string = Tcl_GetStringFromObj(objv[i], &length);
1248 	    if (string[0] == '-') {
1249 		/*
1250 		 * It looks like the command contains an option so signal an
1251 		 * error.
1252 		 */
1253 
1254 		return TCL_ERROR;
1255 	    } else {
1256 		/*
1257 		 * This clearly isn't an option; assume it's the first glob
1258 		 * pattern. We must clear the error.
1259 		 */
1260 
1261 		Tcl_ResetResult(interp);
1262 		break;
1263 	    }
1264 	}
1265 
1266 	switch (index) {
1267 	case GLOB_NOCOMPLAIN:			/* -nocomplain */
1268 	    globFlags |= TCL_GLOBMODE_NO_COMPLAIN;
1269 	    break;
1270 	case GLOB_DIR:				/* -dir */
1271 	    if (i == (objc-1)) {
1272 		Tcl_SetObjResult(interp, Tcl_NewStringObj(
1273 			"missing argument to \"-directory\"", -1));
1274 		return TCL_ERROR;
1275 	    }
1276 	    if (dir != PATH_NONE) {
1277 		Tcl_SetObjResult(interp, Tcl_NewStringObj(
1278 			"\"-directory\" cannot be used with \"-path\"", -1));
1279 		return TCL_ERROR;
1280 	    }
1281 	    dir = PATH_DIR;
1282 	    globFlags |= TCL_GLOBMODE_DIR;
1283 	    pathOrDir = objv[i+1];
1284 	    i++;
1285 	    break;
1286 	case GLOB_JOIN:				/* -join */
1287 	    join = 1;
1288 	    break;
1289 	case GLOB_TAILS:				/* -tails */
1290 	    globFlags |= TCL_GLOBMODE_TAILS;
1291 	    break;
1292 	case GLOB_PATH:				/* -path */
1293 	    if (i == (objc-1)) {
1294 		Tcl_SetObjResult(interp, Tcl_NewStringObj(
1295 			"missing argument to \"-path\"", -1));
1296 		return TCL_ERROR;
1297 	    }
1298 	    if (dir != PATH_NONE) {
1299 		Tcl_SetObjResult(interp, Tcl_NewStringObj(
1300 			"\"-path\" cannot be used with \"-directory\"", -1));
1301 		return TCL_ERROR;
1302 	    }
1303 	    dir = PATH_GENERAL;
1304 	    pathOrDir = objv[i+1];
1305 	    i++;
1306 	    break;
1307 	case GLOB_TYPE:				/* -types */
1308 	    if (i == (objc-1)) {
1309 		Tcl_SetObjResult(interp, Tcl_NewStringObj(
1310 			"missing argument to \"-types\"", -1));
1311 		return TCL_ERROR;
1312 	    }
1313 	    typePtr = objv[i+1];
1314 	    if (Tcl_ListObjLength(interp, typePtr, &length) != TCL_OK) {
1315 		return TCL_ERROR;
1316 	    }
1317 	    i++;
1318 	    break;
1319 	case GLOB_LAST:				/* -- */
1320 	    i++;
1321 	    goto endOfForLoop;
1322 	}
1323     }
1324 
1325   endOfForLoop:
1326     if (objc - i < 1) {
1327 	Tcl_WrongNumArgs(interp, 1, objv, "?switches? name ?name ...?");
1328 	return TCL_ERROR;
1329     }
1330     if ((globFlags & TCL_GLOBMODE_TAILS) && (pathOrDir == NULL)) {
1331 	Tcl_AppendResult(interp,
1332 		"\"-tails\" must be used with either "
1333 		"\"-directory\" or \"-path\"", NULL);
1334 	return TCL_ERROR;
1335     }
1336 
1337     separators = NULL;		/* lint. */
1338     switch (tclPlatform) {
1339     case TCL_PLATFORM_UNIX:
1340 	separators = "/";
1341 	break;
1342     case TCL_PLATFORM_WINDOWS:
1343 	separators = "/\\:";
1344 	break;
1345     }
1346 
1347     if (dir == PATH_GENERAL) {
1348 	int pathlength;
1349 	const char *last;
1350 	const char *first = Tcl_GetStringFromObj(pathOrDir,&pathlength);
1351 
1352 	/*
1353 	 * Find the last path separator in the path
1354 	 */
1355 
1356 	last = first + pathlength;
1357 	for (; last != first; last--) {
1358 	    if (strchr(separators, *(last-1)) != NULL) {
1359 		break;
1360 	    }
1361 	}
1362 
1363 	if (last == first + pathlength) {
1364 	    /*
1365 	     * It's really a directory.
1366 	     */
1367 
1368 	    dir = PATH_DIR;
1369 
1370 	} else {
1371 	    Tcl_DString pref;
1372 	    char *search, *find;
1373 	    Tcl_DStringInit(&pref);
1374 	    if (last == first) {
1375 		/*
1376 		 * The whole thing is a prefix. This means we must remove any
1377 		 * 'tails' flag too, since it is irrelevant now (the same
1378 		 * effect will happen without it), but in particular its use
1379 		 * in TclGlob requires a non-NULL pathOrDir.
1380 		 */
1381 
1382 		Tcl_DStringAppend(&pref, first, -1);
1383 		globFlags &= ~TCL_GLOBMODE_TAILS;
1384 		pathOrDir = NULL;
1385 	    } else {
1386 		/*
1387 		 * Have to split off the end.
1388 		 */
1389 
1390 		Tcl_DStringAppend(&pref, last, first+pathlength-last);
1391 		pathOrDir = Tcl_NewStringObj(first, last-first-1);
1392 
1393 		/*
1394 		 * We must ensure that we haven't cut off too much, and turned
1395 		 * a valid path like '/' or 'C:/' into an incorrect path like
1396 		 * '' or 'C:'. The way we do this is to add a separator if
1397 		 * there are none presently in the prefix.
1398 		 */
1399 
1400 		if (strpbrk(Tcl_GetString(pathOrDir), "\\/") == NULL) {
1401 		    Tcl_AppendToObj(pathOrDir, last-1, 1);
1402 		}
1403 	    }
1404 
1405 	    /*
1406 	     * Need to quote 'prefix'.
1407 	     */
1408 
1409 	    Tcl_DStringInit(&prefix);
1410 	    search = Tcl_DStringValue(&pref);
1411 	    while ((find = (strpbrk(search, "\\[]*?{}"))) != NULL) {
1412 		Tcl_DStringAppend(&prefix, search, find-search);
1413 		Tcl_DStringAppend(&prefix, "\\", 1);
1414 		Tcl_DStringAppend(&prefix, find, 1);
1415 		search = find+1;
1416 		if (*search == '\0') {
1417 		    break;
1418 		}
1419 	    }
1420 	    if (*search != '\0') {
1421 		Tcl_DStringAppend(&prefix, search, -1);
1422 	    }
1423 	    Tcl_DStringFree(&pref);
1424 	}
1425     }
1426 
1427     if (pathOrDir != NULL) {
1428 	Tcl_IncrRefCount(pathOrDir);
1429     }
1430 
1431     if (typePtr != NULL) {
1432 	/*
1433 	 * The rest of the possible type arguments (except 'd') are platform
1434 	 * specific. We don't complain when they are used on an incompatible
1435 	 * platform.
1436 	 */
1437 
1438 	Tcl_ListObjLength(interp, typePtr, &length);
1439 	if (length <= 0) {
1440 	    goto skipTypes;
1441 	}
1442 	globTypes = (Tcl_GlobTypeData*)
1443 		TclStackAlloc(interp,sizeof(Tcl_GlobTypeData));
1444 	globTypes->type = 0;
1445 	globTypes->perm = 0;
1446 	globTypes->macType = NULL;
1447 	globTypes->macCreator = NULL;
1448 
1449 	while (--length >= 0) {
1450 	    int len;
1451 	    const char *str;
1452 
1453 	    Tcl_ListObjIndex(interp, typePtr, length, &look);
1454 	    str = Tcl_GetStringFromObj(look, &len);
1455 	    if (strcmp("readonly", str) == 0) {
1456 		globTypes->perm |= TCL_GLOB_PERM_RONLY;
1457 	    } else if (strcmp("hidden", str) == 0) {
1458 		globTypes->perm |= TCL_GLOB_PERM_HIDDEN;
1459 	    } else if (len == 1) {
1460 		switch (str[0]) {
1461 		case 'r':
1462 		    globTypes->perm |= TCL_GLOB_PERM_R;
1463 		    break;
1464 		case 'w':
1465 		    globTypes->perm |= TCL_GLOB_PERM_W;
1466 		    break;
1467 		case 'x':
1468 		    globTypes->perm |= TCL_GLOB_PERM_X;
1469 		    break;
1470 		case 'b':
1471 		    globTypes->type |= TCL_GLOB_TYPE_BLOCK;
1472 		    break;
1473 		case 'c':
1474 		    globTypes->type |= TCL_GLOB_TYPE_CHAR;
1475 		    break;
1476 		case 'd':
1477 		    globTypes->type |= TCL_GLOB_TYPE_DIR;
1478 		    break;
1479 		case 'p':
1480 		    globTypes->type |= TCL_GLOB_TYPE_PIPE;
1481 		    break;
1482 		case 'f':
1483 		    globTypes->type |= TCL_GLOB_TYPE_FILE;
1484 		    break;
1485 		case 'l':
1486 		    globTypes->type |= TCL_GLOB_TYPE_LINK;
1487 		    break;
1488 		case 's':
1489 		    globTypes->type |= TCL_GLOB_TYPE_SOCK;
1490 		    break;
1491 		default:
1492 		    goto badTypesArg;
1493 		}
1494 
1495 	    } else if (len == 4) {
1496 		/*
1497 		 * This is assumed to be a MacOS file type.
1498 		 */
1499 
1500 		if (globTypes->macType != NULL) {
1501 		    goto badMacTypesArg;
1502 		}
1503 		globTypes->macType = look;
1504 		Tcl_IncrRefCount(look);
1505 
1506 	    } else {
1507 		Tcl_Obj *item;
1508 
1509 		if ((Tcl_ListObjLength(NULL, look, &len) == TCL_OK)
1510 			&& (len == 3)) {
1511 		    Tcl_ListObjIndex(interp, look, 0, &item);
1512 		    if (!strcmp("macintosh", Tcl_GetString(item))) {
1513 			Tcl_ListObjIndex(interp, look, 1, &item);
1514 			if (!strcmp("type", Tcl_GetString(item))) {
1515 			    Tcl_ListObjIndex(interp, look, 2, &item);
1516 			    if (globTypes->macType != NULL) {
1517 				goto badMacTypesArg;
1518 			    }
1519 			    globTypes->macType = item;
1520 			    Tcl_IncrRefCount(item);
1521 			    continue;
1522 			} else if (!strcmp("creator", Tcl_GetString(item))) {
1523 			    Tcl_ListObjIndex(interp, look, 2, &item);
1524 			    if (globTypes->macCreator != NULL) {
1525 				goto badMacTypesArg;
1526 			    }
1527 			    globTypes->macCreator = item;
1528 			    Tcl_IncrRefCount(item);
1529 			    continue;
1530 			}
1531 		    }
1532 		}
1533 
1534 		/*
1535 		 * Error cases. We reset the 'join' flag to zero, since we
1536 		 * haven't yet made use of it.
1537 		 */
1538 
1539 	    badTypesArg:
1540 		TclNewObj(resultPtr);
1541 		Tcl_AppendToObj(resultPtr, "bad argument to \"-types\": ", -1);
1542 		Tcl_AppendObjToObj(resultPtr, look);
1543 		Tcl_SetObjResult(interp, resultPtr);
1544 		result = TCL_ERROR;
1545 		join = 0;
1546 		goto endOfGlob;
1547 
1548 	    badMacTypesArg:
1549 		Tcl_SetObjResult(interp, Tcl_NewStringObj(
1550 			"only one MacOS type or creator argument"
1551 			" to \"-types\" allowed", -1));
1552 		result = TCL_ERROR;
1553 		join = 0;
1554 		goto endOfGlob;
1555 	    }
1556 	}
1557     }
1558 
1559   skipTypes:
1560     /*
1561      * Now we perform the actual glob below. This may involve joining together
1562      * the pattern arguments, dealing with particular file types etc. We use a
1563      * 'goto' to ensure we free any memory allocated along the way.
1564      */
1565 
1566     objc -= i;
1567     objv += i;
1568     result = TCL_OK;
1569 
1570     if (join) {
1571 	if (dir != PATH_GENERAL) {
1572 	    Tcl_DStringInit(&prefix);
1573 	}
1574 	for (i = 0; i < objc; i++) {
1575 	    string = Tcl_GetStringFromObj(objv[i], &length);
1576 	    Tcl_DStringAppend(&prefix, string, length);
1577 	    if (i != objc -1) {
1578 		Tcl_DStringAppend(&prefix, separators, 1);
1579 	    }
1580 	}
1581 	if (TclGlob(interp, Tcl_DStringValue(&prefix), pathOrDir, globFlags,
1582 		globTypes) != TCL_OK) {
1583 	    result = TCL_ERROR;
1584 	    goto endOfGlob;
1585 	}
1586     } else if (dir == PATH_GENERAL) {
1587 	Tcl_DString str;
1588 
1589 	for (i = 0; i < objc; i++) {
1590 	    Tcl_DStringInit(&str);
1591 	    if (dir == PATH_GENERAL) {
1592 		Tcl_DStringAppend(&str, Tcl_DStringValue(&prefix),
1593 			Tcl_DStringLength(&prefix));
1594 	    }
1595 	    string = Tcl_GetStringFromObj(objv[i], &length);
1596 	    Tcl_DStringAppend(&str, string, length);
1597 	    if (TclGlob(interp, Tcl_DStringValue(&str), pathOrDir, globFlags,
1598 		    globTypes) != TCL_OK) {
1599 		result = TCL_ERROR;
1600 		Tcl_DStringFree(&str);
1601 		goto endOfGlob;
1602 	    }
1603 	}
1604 	Tcl_DStringFree(&str);
1605     } else {
1606 	for (i = 0; i < objc; i++) {
1607 	    string = Tcl_GetString(objv[i]);
1608 	    if (TclGlob(interp, string, pathOrDir, globFlags,
1609 		    globTypes) != TCL_OK) {
1610 		result = TCL_ERROR;
1611 		goto endOfGlob;
1612 	    }
1613 	}
1614     }
1615 
1616     if ((globFlags & TCL_GLOBMODE_NO_COMPLAIN) == 0) {
1617 	if (Tcl_ListObjLength(interp, Tcl_GetObjResult(interp),
1618 		&length) != TCL_OK) {
1619 	    /*
1620 	     * This should never happen. Maybe we should be more dramatic.
1621 	     */
1622 
1623 	    result = TCL_ERROR;
1624 	    goto endOfGlob;
1625 	}
1626 
1627 	if (length == 0) {
1628 	    Tcl_AppendResult(interp, "no files matched glob pattern",
1629 		    (join || (objc == 1)) ? " \"" : "s \"", NULL);
1630 	    if (join) {
1631 		Tcl_AppendResult(interp, Tcl_DStringValue(&prefix), NULL);
1632 	    } else {
1633 		const char *sep = "";
1634 		for (i = 0; i < objc; i++) {
1635 		    string = Tcl_GetString(objv[i]);
1636 		    Tcl_AppendResult(interp, sep, string, NULL);
1637 		    sep = " ";
1638 		}
1639 	    }
1640 	    Tcl_AppendResult(interp, "\"", NULL);
1641 	    result = TCL_ERROR;
1642 	}
1643     }
1644 
1645   endOfGlob:
1646     if (join || (dir == PATH_GENERAL)) {
1647 	Tcl_DStringFree(&prefix);
1648     }
1649     if (pathOrDir != NULL) {
1650 	Tcl_DecrRefCount(pathOrDir);
1651     }
1652     if (globTypes != NULL) {
1653 	if (globTypes->macType != NULL) {
1654 	    Tcl_DecrRefCount(globTypes->macType);
1655 	}
1656 	if (globTypes->macCreator != NULL) {
1657 	    Tcl_DecrRefCount(globTypes->macCreator);
1658 	}
1659 	TclStackFree(interp, globTypes);
1660     }
1661     return result;
1662 }
1663 
1664 /*
1665  *----------------------------------------------------------------------
1666  *
1667  * TclGlob --
1668  *
1669  *	This procedure prepares arguments for the DoGlob call. It sets the
1670  *	separator string based on the platform, performs * tilde substitution,
1671  *	and calls DoGlob.
1672  *
1673  *	The interpreter's result, on entry to this function, must be a valid
1674  *	Tcl list (e.g. it could be empty), since we will lappend any new
1675  *	results to that list. If it is not a valid list, this function will
1676  *	fail to do anything very meaningful.
1677  *
1678  *	Note that if globFlags contains 'TCL_GLOBMODE_TAILS' then pathPrefix
1679  *	cannot be NULL (it is only allowed with -dir or -path).
1680  *
1681  * Results:
1682  *	The return value is a standard Tcl result indicating whether an error
1683  *	occurred in globbing. After a normal return the result in interp (set
1684  *	by DoGlob) holds all of the file names given by the pattern and
1685  *	pathPrefix arguments. After an error the result in interp will hold
1686  *	an error message.
1687  *
1688  * Side effects:
1689  *	The 'pattern' is written to.
1690  *
1691  *----------------------------------------------------------------------
1692  */
1693 
1694 	/* ARGSUSED */
1695 int
TclGlob(Tcl_Interp * interp,char * pattern,Tcl_Obj * pathPrefix,int globFlags,Tcl_GlobTypeData * types)1696 TclGlob(
1697     Tcl_Interp *interp,		/* Interpreter for returning error message or
1698 				 * appending list of matching file names. */
1699     char *pattern,		/* Glob pattern to match. Must not refer to a
1700 				 * static string. */
1701     Tcl_Obj *pathPrefix,	/* Path prefix to glob pattern, if non-null,
1702 				 * which is considered literally. */
1703     int globFlags,		/* Stores or'ed combination of flags */
1704     Tcl_GlobTypeData *types)	/* Struct containing acceptable types. May be
1705 				 * NULL. */
1706 {
1707     const char *separators;
1708     const char *head;
1709     char *tail, *start;
1710     int result;
1711     Tcl_Obj *filenamesObj, *savedResultObj;
1712 
1713     separators = NULL;		/* lint. */
1714     switch (tclPlatform) {
1715     case TCL_PLATFORM_UNIX:
1716 	separators = "/";
1717 	break;
1718     case TCL_PLATFORM_WINDOWS:
1719 	separators = "/\\:";
1720 	break;
1721     }
1722 
1723     if (pathPrefix == NULL) {
1724 	char c;
1725 	Tcl_DString buffer;
1726 	Tcl_DStringInit(&buffer);
1727 
1728 	start = pattern;
1729 
1730 	/*
1731 	 * Perform tilde substitution, if needed.
1732 	 */
1733 
1734 	if (start[0] == '~') {
1735 	    /*
1736 	     * Find the first path separator after the tilde.
1737 	     */
1738 
1739 	    for (tail = start; *tail != '\0'; tail++) {
1740 		if (*tail == '\\') {
1741 		    if (strchr(separators, tail[1]) != NULL) {
1742 			break;
1743 		    }
1744 		} else if (strchr(separators, *tail) != NULL) {
1745 		    break;
1746 		}
1747 	    }
1748 
1749 	    /*
1750 	     * Determine the home directory for the specified user.
1751 	     */
1752 
1753 	    c = *tail;
1754 	    *tail = '\0';
1755 	    head = DoTildeSubst(interp, start+1, &buffer);
1756 	    *tail = c;
1757 	    if (head == NULL) {
1758 		return TCL_ERROR;
1759 	    }
1760 	    if (head != Tcl_DStringValue(&buffer)) {
1761 		Tcl_DStringAppend(&buffer, head, -1);
1762 	    }
1763 	    pathPrefix = Tcl_NewStringObj(Tcl_DStringValue(&buffer),
1764 		    Tcl_DStringLength(&buffer));
1765 	    Tcl_IncrRefCount(pathPrefix);
1766 	    globFlags |= TCL_GLOBMODE_DIR;
1767 	    if (c != '\0') {
1768 		tail++;
1769 	    }
1770 	    Tcl_DStringFree(&buffer);
1771 	} else {
1772 	    tail = pattern;
1773 	}
1774     } else {
1775 	Tcl_IncrRefCount(pathPrefix);
1776 	tail = pattern;
1777     }
1778 
1779     /*
1780      * Handling empty path prefixes with glob patterns like 'C:' or
1781      * 'c:////////' is a pain on Windows if we leave it too late, since these
1782      * aren't really patterns at all! We therefore check the head of the
1783      * pattern now for such cases, if we don't have an unquoted prefix yet.
1784      *
1785      * Similarly on Unix with '/' at the head of the pattern -- it just
1786      * indicates the root volume, so we treat it as such.
1787      */
1788 
1789     if (tclPlatform == TCL_PLATFORM_WINDOWS) {
1790 	if (pathPrefix == NULL && tail[0] != '\0' && tail[1] == ':') {
1791 	    char *p = tail + 1;
1792 	    pathPrefix = Tcl_NewStringObj(tail, 1);
1793 	    while (*p != '\0') {
1794 		char c = p[1];
1795 		if (*p == '\\') {
1796 		    if (strchr(separators, c) != NULL) {
1797 			if (c == '\\') {
1798 			    c = '/';
1799 			}
1800 			Tcl_AppendToObj(pathPrefix, &c, 1);
1801 			p++;
1802 		    } else {
1803 			break;
1804 		    }
1805 		} else if (strchr(separators, *p) != NULL) {
1806 		    Tcl_AppendToObj(pathPrefix, p, 1);
1807 		} else {
1808 		    break;
1809 		}
1810 		p++;
1811 	    }
1812 	    tail = p;
1813 	    Tcl_IncrRefCount(pathPrefix);
1814 	} else if (pathPrefix == NULL && (tail[0] == '/'
1815 		|| (tail[0] == '\\' && tail[1] == '\\'))) {
1816 	    int driveNameLen;
1817 	    Tcl_Obj *driveName;
1818 	    Tcl_Obj *temp = Tcl_NewStringObj(tail, -1);
1819 	    Tcl_IncrRefCount(temp);
1820 
1821 	    switch (TclGetPathType(temp, NULL, &driveNameLen, &driveName)) {
1822 	    case TCL_PATH_VOLUME_RELATIVE: {
1823 		/*
1824 		 * Volume relative path which is equivalent to a path in the
1825 		 * root of the cwd's volume. We will actually return
1826 		 * non-volume-relative paths here. i.e. 'glob /foo*' will
1827 		 * return 'C:/foobar'. This is much the same as globbing for a
1828 		 * path with '\\' will return one with '/' on Windows.
1829 		 */
1830 
1831 		Tcl_Obj *cwd = Tcl_FSGetCwd(interp);
1832 
1833 		if (cwd == NULL) {
1834 		    Tcl_DecrRefCount(temp);
1835 		    return TCL_ERROR;
1836 		}
1837 		pathPrefix = Tcl_NewStringObj(Tcl_GetString(cwd), 3);
1838 		Tcl_DecrRefCount(cwd);
1839 		if (tail[0] == '/') {
1840 		    tail++;
1841 		} else {
1842 		    tail += 2;
1843 		}
1844 		Tcl_IncrRefCount(pathPrefix);
1845 		break;
1846 	    }
1847 	    case TCL_PATH_ABSOLUTE:
1848 		/*
1849 		 * Absolute, possibly network path //Machine/Share. Use that
1850 		 * as the path prefix (it already has a refCount).
1851 		 */
1852 
1853 		pathPrefix = driveName;
1854 		tail += driveNameLen;
1855 		break;
1856 	    case TCL_PATH_RELATIVE:
1857 		/* Do nothing */
1858 		break;
1859 	    }
1860 	    Tcl_DecrRefCount(temp);
1861 	}
1862 
1863 	/*
1864 	 * ':' no longer needed as a separator. It is only relevant to the
1865 	 * beginning of the path.
1866 	 */
1867 
1868 	separators = "/\\";
1869 
1870     } else if (tclPlatform == TCL_PLATFORM_UNIX) {
1871 	if (pathPrefix == NULL && tail[0] == '/') {
1872 	    pathPrefix = Tcl_NewStringObj(tail, 1);
1873 	    tail++;
1874 	    Tcl_IncrRefCount(pathPrefix);
1875 	}
1876     }
1877 
1878     /*
1879      * Finally if we still haven't managed to generate a path prefix, check if
1880      * the path starts with a current volume.
1881      */
1882 
1883     if (pathPrefix == NULL) {
1884 	int driveNameLen;
1885 	Tcl_Obj *driveName;
1886 	if (TclFSNonnativePathType(tail, (int) strlen(tail), NULL,
1887 		&driveNameLen, &driveName) == TCL_PATH_ABSOLUTE) {
1888 	    pathPrefix = driveName;
1889 	    tail += driveNameLen;
1890 	}
1891     }
1892 
1893     /*
1894      * To process a [glob] invokation, this function may be called multiple
1895      * times. Each time, the previously discovered filenames are in the
1896      * interpreter result. We stash that away here so the result is free for
1897      * error messsages.
1898      */
1899 
1900     savedResultObj = Tcl_GetObjResult(interp);
1901     Tcl_IncrRefCount(savedResultObj);
1902     Tcl_ResetResult(interp);
1903     TclNewObj(filenamesObj);
1904     Tcl_IncrRefCount(filenamesObj);
1905 
1906     /*
1907      * Now we do the actual globbing, adding filenames as we go to buffer in
1908      * filenamesObj
1909      */
1910 
1911     if (*tail == '\0' && pathPrefix != NULL) {
1912 	/*
1913 	 * An empty pattern. This means 'pathPrefix' is actually a full path
1914 	 * of a file/directory we want to simply check for existence and type.
1915 	 */
1916 
1917 	if (types == NULL) {
1918 	    /*
1919 	     * We just want to check for existence. In this case we make it
1920 	     * easy on Tcl_FSMatchInDirectory and its sub-implementations by
1921 	     * not bothering them (even though they should support this
1922 	     * situation) and we just use the simple existence check with
1923 	     * Tcl_FSAccess.
1924 	     */
1925 
1926 	    if (Tcl_FSAccess(pathPrefix, F_OK) == 0) {
1927 		Tcl_ListObjAppendElement(interp, filenamesObj, pathPrefix);
1928 	    }
1929 	    result = TCL_OK;
1930 	} else {
1931 	    /*
1932 	     * We want to check for the correct type. Tcl_FSMatchInDirectory
1933 	     * is documented to do this for us, if we give it a NULL pattern.
1934 	     */
1935 
1936 	    result = Tcl_FSMatchInDirectory(interp, filenamesObj, pathPrefix,
1937 		    NULL, types);
1938 	}
1939     } else {
1940 	result = DoGlob(interp, filenamesObj, separators, pathPrefix,
1941 		globFlags & TCL_GLOBMODE_DIR, tail, types);
1942     }
1943 
1944     /*
1945      * Check for errors...
1946      */
1947 
1948     if (result != TCL_OK) {
1949 	TclDecrRefCount(filenamesObj);
1950 	TclDecrRefCount(savedResultObj);
1951 	if (pathPrefix != NULL) {
1952 	    Tcl_DecrRefCount(pathPrefix);
1953 	}
1954 	return result;
1955     }
1956 
1957     /*
1958      * If we only want the tails, we must strip off the prefix now. It may
1959      * seem more efficient to pass the tails flag down into DoGlob,
1960      * Tcl_FSMatchInDirectory, but those functions are continually adjusting
1961      * the prefix as the various pieces of the pattern are assimilated, so
1962      * that would add a lot of complexity to the code. This way is a little
1963      * slower (when the -tails flag is given), but much simpler to code.
1964      *
1965      * We do it by rewriting the result list in-place.
1966      */
1967 
1968     if (globFlags & TCL_GLOBMODE_TAILS) {
1969 	int objc, i;
1970 	Tcl_Obj **objv;
1971 	int prefixLen;
1972 	const char *pre;
1973 
1974 	/*
1975 	 * If this length has never been set, set it here.
1976 	 */
1977 
1978 	if (pathPrefix == NULL) {
1979 	    Tcl_Panic("Called TclGlob with TCL_GLOBMODE_TAILS and pathPrefix==NULL");
1980 	}
1981 
1982 	pre = Tcl_GetStringFromObj(pathPrefix, &prefixLen);
1983 	if (prefixLen > 0
1984 		&& (strchr(separators, pre[prefixLen-1]) == NULL)) {
1985 	    /*
1986 	     * If we're on Windows and the prefix is a volume relative one
1987 	     * like 'C:', then there won't be a path separator in between, so
1988 	     * no need to skip it here.
1989 	     */
1990 
1991 	    if ((tclPlatform != TCL_PLATFORM_WINDOWS) || (prefixLen != 2)
1992 		    || (pre[1] != ':')) {
1993 		prefixLen++;
1994 	    }
1995 	}
1996 
1997 	Tcl_ListObjGetElements(NULL, filenamesObj, &objc, &objv);
1998 	for (i = 0; i< objc; i++) {
1999 	    int len;
2000 	    const char *oldStr = Tcl_GetStringFromObj(objv[i], &len);
2001 	    Tcl_Obj *elem;
2002 
2003 	    if (len == prefixLen) {
2004 		if ((pattern[0] == '\0')
2005 			|| (strchr(separators, pattern[0]) == NULL)) {
2006 		    TclNewLiteralStringObj(elem, ".");
2007 		} else {
2008 		    TclNewLiteralStringObj(elem, "/");
2009 		}
2010 	    } else {
2011 		elem = Tcl_NewStringObj(oldStr+prefixLen, len-prefixLen);
2012 	    }
2013 	    Tcl_ListObjReplace(interp, filenamesObj, i, 1, 1, &elem);
2014 	}
2015     }
2016 
2017     /*
2018      * Now we have a list of discovered filenames in filenamesObj and a list
2019      * of previously discovered (saved earlier from the interpreter result) in
2020      * savedResultObj. Merge them and put them back in the interpreter result.
2021      */
2022 
2023     if (Tcl_IsShared(savedResultObj)) {
2024 	TclDecrRefCount(savedResultObj);
2025 	savedResultObj = Tcl_DuplicateObj(savedResultObj);
2026 	Tcl_IncrRefCount(savedResultObj);
2027     }
2028     if (Tcl_ListObjAppendList(interp, savedResultObj, filenamesObj) != TCL_OK){
2029 	result = TCL_ERROR;
2030     } else {
2031 	Tcl_SetObjResult(interp, savedResultObj);
2032     }
2033     TclDecrRefCount(savedResultObj);
2034     TclDecrRefCount(filenamesObj);
2035     if (pathPrefix != NULL) {
2036 	Tcl_DecrRefCount(pathPrefix);
2037     }
2038 
2039     return result;
2040 }
2041 
2042 /*
2043  *----------------------------------------------------------------------
2044  *
2045  * SkipToChar --
2046  *
2047  *	This function traverses a glob pattern looking for the next unquoted
2048  *	occurance of the specified character at the same braces nesting level.
2049  *
2050  * Results:
2051  *	Updates stringPtr to point to the matching character, or to the end of
2052  *	the string if nothing matched. The return value is 1 if a match was
2053  *	found at the top level, otherwise it is 0.
2054  *
2055  * Side effects:
2056  *	None.
2057  *
2058  *----------------------------------------------------------------------
2059  */
2060 
2061 static int
SkipToChar(char ** stringPtr,int match)2062 SkipToChar(
2063     char **stringPtr,		/* Pointer string to check. */
2064     int match)			/* Character to find. */
2065 {
2066     int quoted, level;
2067     register char *p;
2068 
2069     quoted = 0;
2070     level = 0;
2071 
2072     for (p = *stringPtr; *p != '\0'; p++) {
2073 	if (quoted) {
2074 	    quoted = 0;
2075 	    continue;
2076 	}
2077 	if ((level == 0) && (*p == match)) {
2078 	    *stringPtr = p;
2079 	    return 1;
2080 	}
2081 	if (*p == '{') {
2082 	    level++;
2083 	} else if (*p == '}') {
2084 	    level--;
2085 	} else if (*p == '\\') {
2086 	    quoted = 1;
2087 	}
2088     }
2089     *stringPtr = p;
2090     return 0;
2091 }
2092 
2093 /*
2094  *----------------------------------------------------------------------
2095  *
2096  * DoGlob --
2097  *
2098  *	This recursive procedure forms the heart of the globbing code. It
2099  *	performs a depth-first traversal of the tree given by the path name to
2100  *	be globbed and the pattern. The directory and remainder are assumed to
2101  *	be native format paths. The prefix contained in 'pathPtr' is either a
2102  *	directory or path from which to start the search (or NULL). If pathPtr
2103  *	is NULL, then the pattern must not start with an absolute path
2104  *	specification (that case should be handled by moving the absolute path
2105  *	prefix into pathPtr before calling DoGlob).
2106  *
2107  * Results:
2108  *	The return value is a standard Tcl result indicating whether an error
2109  *	occurred in globbing. After a normal return the result in interp will
2110  *	be set to hold all of the file names given by the dir and remaining
2111  *	arguments. After an error the result in interp will hold an error
2112  *	message.
2113  *
2114  * Side effects:
2115  *	None.
2116  *
2117  *----------------------------------------------------------------------
2118  */
2119 
2120 static int
DoGlob(Tcl_Interp * interp,Tcl_Obj * matchesObj,const char * separators,Tcl_Obj * pathPtr,int flags,char * pattern,Tcl_GlobTypeData * types)2121 DoGlob(
2122     Tcl_Interp *interp,		/* Interpreter to use for error reporting
2123 				 * (e.g. unmatched brace). */
2124     Tcl_Obj *matchesObj,	/* Unshared list object in which to place all
2125 				 * resulting filenames. Caller allocates and
2126 				 * deallocates; DoGlob must not touch the
2127 				 * refCount of this object. */
2128     const char *separators,	/* String containing separator characters that
2129 				 * should be used to identify globbing
2130 				 * boundaries. */
2131     Tcl_Obj *pathPtr,		/* Completely expanded prefix. */
2132     int flags,			/* If non-zero then pathPtr is a directory */
2133     char *pattern,		/* The pattern to match against. Must not be a
2134 				 * pointer to a static string. */
2135     Tcl_GlobTypeData *types)	/* List object containing list of acceptable
2136 				 * types. May be NULL. */
2137 {
2138     int baseLength, quoted, count;
2139     int result = TCL_OK;
2140     char *name, *p, *openBrace, *closeBrace, *firstSpecialChar;
2141     Tcl_Obj *joinedPtr;
2142 
2143     /*
2144      * Consume any leading directory separators, leaving pattern pointing just
2145      * past the last initial separator.
2146      */
2147 
2148     count = 0;
2149     name = pattern;
2150     for (; *pattern != '\0'; pattern++) {
2151 	if (*pattern == '\\') {
2152 	    /*
2153 	     * If the first character is escaped, either we have a directory
2154 	     * separator, or we have any other character. In the latter case
2155 	     * the rest is a pattern, and we must break from the loop. This
2156 	     * is particularly important on Windows where '\' is both the
2157 	     * escaping character and a directory separator.
2158 	     */
2159 
2160 	    if (strchr(separators, pattern[1]) != NULL) {
2161 		pattern++;
2162 	    } else {
2163 		break;
2164 	    }
2165 	} else if (strchr(separators, *pattern) == NULL) {
2166 	    break;
2167 	}
2168 	count++;
2169     }
2170 
2171     /*
2172      * Look for the first matching pair of braces or the first directory
2173      * separator that is not inside a pair of braces.
2174      */
2175 
2176     openBrace = closeBrace = NULL;
2177     quoted = 0;
2178     for (p = pattern; *p != '\0'; p++) {
2179 	if (quoted) {
2180 	    quoted = 0;
2181 
2182 	} else if (*p == '\\') {
2183 	    quoted = 1;
2184 	    if (strchr(separators, p[1]) != NULL) {
2185 		/*
2186 		 * Quoted directory separator.
2187 		 */
2188 		break;
2189 	    }
2190 
2191 	} else if (strchr(separators, *p) != NULL) {
2192 	    /*
2193 	     * Unquoted directory separator.
2194 	     */
2195 	    break;
2196 
2197 	} else if (*p == '{') {
2198 	    openBrace = p;
2199 	    p++;
2200 	    if (SkipToChar(&p, '}')) {
2201 		/*
2202 		 * Balanced braces.
2203 		 */
2204 
2205 		closeBrace = p;
2206 		break;
2207 	    }
2208 	    Tcl_SetResult(interp, "unmatched open-brace in file name",
2209 		    TCL_STATIC);
2210 	    return TCL_ERROR;
2211 
2212 	} else if (*p == '}') {
2213 	    Tcl_SetResult(interp, "unmatched close-brace in file name",
2214 		    TCL_STATIC);
2215 	    return TCL_ERROR;
2216 	}
2217     }
2218 
2219     /*
2220      * Substitute the alternate patterns from the braces and recurse.
2221      */
2222 
2223     if (openBrace != NULL) {
2224 	char *element;
2225 	Tcl_DString newName;
2226 
2227 	Tcl_DStringInit(&newName);
2228 
2229 	/*
2230 	 * For each element within in the outermost pair of braces, append the
2231 	 * element and the remainder to the fixed portion before the first
2232 	 * brace and recursively call DoGlob.
2233 	 */
2234 
2235 	Tcl_DStringAppend(&newName, pattern, openBrace-pattern);
2236 	baseLength = Tcl_DStringLength(&newName);
2237 	*closeBrace = '\0';
2238 	for (p = openBrace; p != closeBrace; ) {
2239 	    p++;
2240 	    element = p;
2241 	    SkipToChar(&p, ',');
2242 	    Tcl_DStringSetLength(&newName, baseLength);
2243 	    Tcl_DStringAppend(&newName, element, p-element);
2244 	    Tcl_DStringAppend(&newName, closeBrace+1, -1);
2245 	    result = DoGlob(interp, matchesObj, separators, pathPtr, flags,
2246 		    Tcl_DStringValue(&newName), types);
2247 	    if (result != TCL_OK) {
2248 		break;
2249 	    }
2250 	}
2251 	*closeBrace = '}';
2252 	Tcl_DStringFree(&newName);
2253 	return result;
2254     }
2255 
2256     /*
2257      * At this point, there are no more brace substitutions to perform on this
2258      * path component. The variable p is pointing at a quoted or unquoted
2259      * directory separator or the end of the string. So we need to check for
2260      * special globbing characters in the current pattern. We avoid modifying
2261      * pattern if p is pointing at the end of the string.
2262      *
2263      * If we find any globbing characters, then we must call
2264      * Tcl_FSMatchInDirectory. If we're at the end of the string, then that's
2265      * all we need to do. If we're not at the end of the string, then we must
2266      * recurse, so we do that below.
2267      *
2268      * Alternatively, if there are no globbing characters then again there are
2269      * two cases. If we're at the end of the string, we just need to check for
2270      * the given path's existence and type. If we're not at the end of the
2271      * string, we recurse.
2272      */
2273 
2274     if (*p != '\0') {
2275 	char savedChar = *p;
2276 
2277 	/*
2278 	 * Note that we are modifying the string in place. This won't work if
2279 	 * the string is a static.
2280 	 */
2281 
2282 	*p = '\0';
2283 	firstSpecialChar = strpbrk(pattern, "*[]?\\");
2284 	*p = savedChar;
2285     } else {
2286 	firstSpecialChar = strpbrk(pattern, "*[]?\\");
2287     }
2288 
2289     if (firstSpecialChar != NULL) {
2290 	/*
2291 	 * Look for matching files in the given directory. The implementation
2292 	 * of this function is filesystem specific. For each file that
2293 	 * matches, it will add the match onto the resultPtr given.
2294 	 */
2295 
2296 	static Tcl_GlobTypeData dirOnly = {
2297 	    TCL_GLOB_TYPE_DIR, 0, NULL, NULL
2298 	};
2299 	char save = *p;
2300 	Tcl_Obj *subdirsPtr;
2301 
2302 	if (*p == '\0') {
2303 	    return Tcl_FSMatchInDirectory(interp, matchesObj, pathPtr,
2304 		    pattern, types);
2305 	}
2306 
2307 	/*
2308 	 * We do the recursion ourselves. This makes implementing
2309 	 * Tcl_FSMatchInDirectory for each filesystem much easier.
2310 	 */
2311 
2312 	*p = '\0';
2313 	TclNewObj(subdirsPtr);
2314 	Tcl_IncrRefCount(subdirsPtr);
2315 	result = Tcl_FSMatchInDirectory(interp, subdirsPtr, pathPtr,
2316 		pattern, &dirOnly);
2317 	*p = save;
2318 	if (result == TCL_OK) {
2319 	    int subdirc, i, repair = -1;
2320 	    Tcl_Obj **subdirv;
2321 
2322 	    result = Tcl_ListObjGetElements(interp, subdirsPtr,
2323 		    &subdirc, &subdirv);
2324 	    for (i=0; result==TCL_OK && i<subdirc; i++) {
2325 		Tcl_Obj *copy = NULL;
2326 
2327 		if (pathPtr == NULL && Tcl_GetString(subdirv[i])[0] == '~') {
2328 		    Tcl_ListObjLength(NULL, matchesObj, &repair);
2329 		    copy = subdirv[i];
2330 		    subdirv[i] = Tcl_NewStringObj("./", 2);
2331 		    Tcl_AppendObjToObj(subdirv[i], copy);
2332 		    Tcl_IncrRefCount(subdirv[i]);
2333 		}
2334 		result = DoGlob(interp, matchesObj, separators, subdirv[i],
2335 			1, p+1, types);
2336 		if (copy) {
2337 		    int end;
2338 
2339 		    Tcl_DecrRefCount(subdirv[i]);
2340 		    subdirv[i] = copy;
2341 		    Tcl_ListObjLength(NULL, matchesObj, &end);
2342 		    while (repair < end) {
2343 			const char *bytes;
2344 			int numBytes;
2345 			Tcl_Obj *fixme, *newObj;
2346 
2347 			Tcl_ListObjIndex(NULL, matchesObj, repair, &fixme);
2348 			bytes = Tcl_GetStringFromObj(fixme, &numBytes);
2349 			newObj = Tcl_NewStringObj(bytes+2, numBytes-2);
2350 			Tcl_ListObjReplace(NULL, matchesObj, repair, 1,
2351 				1, &newObj);
2352 			repair++;
2353 		    }
2354 		    repair = -1;
2355 		}
2356 	    }
2357 	}
2358 	TclDecrRefCount(subdirsPtr);
2359 	return result;
2360     }
2361 
2362     /*
2363      * We reach here with no pattern char in current section
2364      */
2365 
2366     if (*p == '\0') {
2367 	int length;
2368 	Tcl_DString append;
2369 
2370 	/*
2371 	 * This is the code path reached by a command like 'glob foo'.
2372 	 *
2373 	 * There are no more wildcards in the pattern and no more unprocessed
2374 	 * characters in the pattern, so now we can construct the path, and
2375 	 * pass it to Tcl_FSMatchInDirectory with an empty pattern to verify
2376 	 * the existence of the file and check it is of the correct type (if a
2377 	 * 'types' flag it given -- if no such flag was given, we could just
2378 	 * use 'Tcl_FSLStat', but for simplicity we keep to a common
2379 	 * approach).
2380 	 */
2381 
2382 	Tcl_DStringInit(&append);
2383 	Tcl_DStringAppend(&append, pattern, p-pattern);
2384 
2385 	if (pathPtr != NULL) {
2386 	    (void) Tcl_GetStringFromObj(pathPtr, &length);
2387 	} else {
2388 	    length = 0;
2389 	}
2390 
2391 	switch (tclPlatform) {
2392 	case TCL_PLATFORM_WINDOWS:
2393 	    if (length == 0 && (Tcl_DStringLength(&append) == 0)) {
2394 		if (((*name == '\\') && (name[1] == '/' ||
2395 			name[1] == '\\')) || (*name == '/')) {
2396 		    Tcl_DStringAppend(&append, "/", 1);
2397 		} else {
2398 		    Tcl_DStringAppend(&append, ".", 1);
2399 		}
2400 	    }
2401 
2402 	    break;
2403 
2404 	case TCL_PLATFORM_UNIX:
2405 	    if (length == 0 && (Tcl_DStringLength(&append) == 0)) {
2406 		if ((*name == '\\' && name[1] == '/') || (*name == '/')) {
2407 		    Tcl_DStringAppend(&append, "/", 1);
2408 		} else {
2409 		    Tcl_DStringAppend(&append, ".", 1);
2410 		}
2411 	    }
2412 	    break;
2413 	}
2414 
2415 	/*
2416 	 * Common for all platforms.
2417 	 */
2418 
2419 	if (pathPtr == NULL) {
2420 	    joinedPtr = Tcl_NewStringObj(Tcl_DStringValue(&append),
2421 		    Tcl_DStringLength(&append));
2422 	} else if (flags) {
2423 	    joinedPtr = TclNewFSPathObj(pathPtr, Tcl_DStringValue(&append),
2424 		    Tcl_DStringLength(&append));
2425 	} else {
2426 	    joinedPtr = Tcl_DuplicateObj(pathPtr);
2427 	    if (strchr(separators, Tcl_DStringValue(&append)[0]) == NULL) {
2428 		/*
2429 		 * The current prefix must end in a separator.
2430 		 */
2431 
2432 		int len;
2433 		const char *joined = Tcl_GetStringFromObj(joinedPtr,&len);
2434 
2435 		if (strchr(separators, joined[len-1]) == NULL) {
2436 		    Tcl_AppendToObj(joinedPtr, "/", 1);
2437 		}
2438 	    }
2439 	    Tcl_AppendToObj(joinedPtr, Tcl_DStringValue(&append),
2440 		    Tcl_DStringLength(&append));
2441 	}
2442 	Tcl_IncrRefCount(joinedPtr);
2443 	Tcl_DStringFree(&append);
2444 	result = Tcl_FSMatchInDirectory(interp, matchesObj, joinedPtr, NULL,
2445 		types);
2446 	Tcl_DecrRefCount(joinedPtr);
2447 	return result;
2448     }
2449 
2450     /*
2451      * If it's not the end of the string, we must recurse
2452      */
2453 
2454     if (pathPtr == NULL) {
2455 	joinedPtr = Tcl_NewStringObj(pattern, p-pattern);
2456     } else if (flags) {
2457 	joinedPtr = TclNewFSPathObj(pathPtr, pattern, p-pattern);
2458     } else {
2459 	joinedPtr = Tcl_DuplicateObj(pathPtr);
2460 	if (strchr(separators, pattern[0]) == NULL) {
2461 	    /*
2462 	     * The current prefix must end in a separator, unless this is a
2463 	     * volume-relative path. In particular globbing in Windows shares,
2464 	     * when not using -dir or -path, e.g. 'glob [file join
2465 	     * //machine/share/subdir *]' requires adding a separator here.
2466 	     * This behaviour is not currently tested for in the test suite.
2467 	     */
2468 
2469 	    int len;
2470 	    const char *joined = Tcl_GetStringFromObj(joinedPtr,&len);
2471 
2472 	    if (strchr(separators, joined[len-1]) == NULL) {
2473 		if (Tcl_FSGetPathType(pathPtr) != TCL_PATH_VOLUME_RELATIVE) {
2474 		    Tcl_AppendToObj(joinedPtr, "/", 1);
2475 		}
2476 	    }
2477 	}
2478 	Tcl_AppendToObj(joinedPtr, pattern, p-pattern);
2479     }
2480 
2481     Tcl_IncrRefCount(joinedPtr);
2482     result = DoGlob(interp, matchesObj, separators, joinedPtr, 1, p, types);
2483     Tcl_DecrRefCount(joinedPtr);
2484 
2485     return result;
2486 }
2487 
2488 /*
2489  *---------------------------------------------------------------------------
2490  *
2491  * Tcl_AllocStatBuf --
2492  *
2493  *	This procedure allocates a Tcl_StatBuf on the heap. It exists so that
2494  *	extensions may be used unchanged on systems where largefile support is
2495  *	optional.
2496  *
2497  * Results:
2498  *	A pointer to a Tcl_StatBuf which may be deallocated by being passed to
2499  *	ckfree().
2500  *
2501  * Side effects:
2502  *	None.
2503  *
2504  *---------------------------------------------------------------------------
2505  */
2506 
2507 Tcl_StatBuf *
Tcl_AllocStatBuf(void)2508 Tcl_AllocStatBuf(void)
2509 {
2510     return (Tcl_StatBuf *) ckalloc(sizeof(Tcl_StatBuf));
2511 }
2512 
2513 /*
2514  * Local Variables:
2515  * mode: c
2516  * c-basic-offset: 4
2517  * fill-column: 78
2518  * End:
2519  */
2520