Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: downloads/tcl8.5.2/generic/tclPathObj.c @ 35

Last change on this file since 35 was 25, checked in by landauf, 17 years ago

added tcl to libs

File size: 70.7 KB
Line 
1/*
2 * tclPathObj.c --
3 *
4 *      This file contains the implementation of Tcl's "path" object type used
5 *      to represent and manipulate a general (virtual) filesystem entity in
6 *      an efficient manner.
7 *
8 * Copyright (c) 2003 Vince Darley.
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 * RCS: @(#) $Id: tclPathObj.c,v 1.66 2007/12/13 15:23:20 dgp Exp $
14 */
15
16#include "tclInt.h"
17#include "tclFileSystem.h"
18
19/*
20 * Prototypes for functions defined later in this file.
21 */
22
23static void             DupFsPathInternalRep(Tcl_Obj *srcPtr,
24                            Tcl_Obj *copyPtr);
25static void             FreeFsPathInternalRep(Tcl_Obj *pathPtr);
26static void             UpdateStringOfFsPath(Tcl_Obj *pathPtr);
27static int              SetFsPathFromAny(Tcl_Interp *interp, Tcl_Obj *pathPtr);
28static int              FindSplitPos(const char *path, int separator);
29static int              IsSeparatorOrNull(int ch);
30static Tcl_Obj *        GetExtension(Tcl_Obj *pathPtr);
31
32/*
33 * Define the 'path' object type, which Tcl uses to represent file paths
34 * internally.
35 */
36
37static Tcl_ObjType tclFsPathType = {
38    "path",                             /* name */
39    FreeFsPathInternalRep,              /* freeIntRepProc */
40    DupFsPathInternalRep,               /* dupIntRepProc */
41    UpdateStringOfFsPath,               /* updateStringProc */
42    SetFsPathFromAny                    /* setFromAnyProc */
43};
44
45/*
46 * struct FsPath --
47 *
48 * Internal representation of a Tcl_Obj of "path" type. This can be used to
49 * represent relative or absolute paths, and has certain optimisations when
50 * used to represent paths which are already normalized and absolute.
51 *
52 * Note that both 'translatedPathPtr' and 'normPathPtr' can be a circular
53 * reference to the container Tcl_Obj of this FsPath.
54 *
55 * There are two cases, with the first being the most common:
56 *
57 * (i) flags == 0, => Ordinary path.
58 *
59 * translatedPathPtr contains the translated path (which may be a circular
60 * reference to the object itself). If it is NULL then the path is pure
61 * normalized (and the normPathPtr will be a circular reference). cwdPtr is
62 * null for an absolute path, and non-null for a relative path (unless the cwd
63 * has never been set, in which case the cwdPtr may also be null for a
64 * relative path).
65 *
66 * (ii) flags != 0, => Special path, see TclNewFSPathObj
67 *
68 * Now, this is a path like 'file join $dir $tail' where, cwdPtr is the $dir
69 * and normPathPtr is the $tail.
70 *
71 */
72
73typedef struct FsPath {
74    Tcl_Obj *translatedPathPtr; /* Name without any ~user sequences. If this
75                                 * is NULL, then this is a pure normalized,
76                                 * absolute path object, in which the parent
77                                 * Tcl_Obj's string rep is already both
78                                 * translated and normalized. */
79    Tcl_Obj *normPathPtr;       /* Normalized absolute path, without ., .. or
80                                 * ~user sequences. If the Tcl_Obj containing
81                                 * this FsPath is already normalized, this may
82                                 * be a circular reference back to the
83                                 * container. If that is NOT the case, we have
84                                 * a refCount on the object. */
85    Tcl_Obj *cwdPtr;            /* If null, path is absolute, else this points
86                                 * to the cwd object used for this path. We
87                                 * have a refCount on the object. */
88    int flags;                  /* Flags to describe interpretation - see
89                                 * below. */
90    ClientData nativePathPtr;   /* Native representation of this path, which
91                                 * is filesystem dependent. */
92    int filesystemEpoch;        /* Used to ensure the path representation was
93                                 * generated during the correct filesystem
94                                 * epoch. The epoch changes when
95                                 * filesystem-mounts are changed. */
96    struct FilesystemRecord *fsRecPtr;
97                                /* Pointer to the filesystem record entry to
98                                 * use for this path. */
99} FsPath;
100
101/*
102 * Flag values for FsPath->flags.
103 */
104
105#define TCLPATH_APPENDED 1
106
107/*
108 * Define some macros to give us convenient access to path-object specific
109 * fields.
110 */
111
112#define PATHOBJ(pathPtr) ((FsPath *) (pathPtr)->internalRep.otherValuePtr)
113#define SETPATHOBJ(pathPtr,fsPathPtr) \
114        ((pathPtr)->internalRep.otherValuePtr = (VOID *) (fsPathPtr))
115#define PATHFLAGS(pathPtr) (PATHOBJ(pathPtr)->flags)
116
117/*
118 *---------------------------------------------------------------------------
119 *
120 * TclFSNormalizeAbsolutePath --
121 *
122 *      Takes an absolute path specification and computes a 'normalized' path
123 *      from it.
124 *
125 *      A normalized path is one which has all '../', './' removed. Also it is
126 *      one which is in the 'standard' format for the native platform. On
127 *      Unix, this means the path must be free of symbolic links/aliases, and
128 *      on Windows it means we want the long form, with that long form's
129 *      case-dependence (which gives us a unique, case-dependent path).
130 *
131 *      The behaviour of this function if passed a non-absolute path is NOT
132 *      defined.
133 *
134 *      pathPtr may have a refCount of zero, or may be a shared object.
135 *
136 * Results:
137 *      The result is returned in a Tcl_Obj with a refCount of 1, which is
138 *      therefore owned by the caller. It must be freed (with
139 *      Tcl_DecrRefCount) by the caller when no longer needed.
140 *
141 * Side effects:
142 *      None (beyond the memory allocation for the result).
143 *
144 * Special note:
145 *      This code was originally based on code from Matt Newman and
146 *      Jean-Claude Wippler, but has since been totally rewritten by Vince
147 *      Darley to deal with symbolic links.
148 *
149 *---------------------------------------------------------------------------
150 */
151
152Tcl_Obj *
153TclFSNormalizeAbsolutePath(
154    Tcl_Interp *interp,         /* Interpreter to use */
155    Tcl_Obj *pathPtr,           /* Absolute path to normalize */
156    ClientData *clientDataPtr)  /* If non-NULL, then may be set to the
157                                 * fs-specific clientData for this path. This
158                                 * will happen when that extra information can
159                                 * be calculated efficiently as a side-effect
160                                 * of normalization. */
161{
162    ClientData clientData = NULL;
163    const char *dirSep, *oldDirSep;
164    int first = 1;              /* Set to zero once we've passed the first
165                                 * directory separator - we can't use '..' to
166                                 * remove the volume in a path. */
167    Tcl_Obj *retVal = NULL;
168    dirSep = TclGetString(pathPtr);
169
170    if (tclPlatform == TCL_PLATFORM_WINDOWS) {
171        if (   (dirSep[0] == '/' || dirSep[0] == '\\')
172            && (dirSep[1] == '/' || dirSep[1] == '\\')
173            && (dirSep[2] == '?')
174            && (dirSep[3] == '/' || dirSep[3] == '\\')) {
175            /* NT extended path */
176            dirSep += 4;
177
178            if (   (dirSep[0] == 'U' || dirSep[0] == 'u')
179                && (dirSep[1] == 'N' || dirSep[1] == 'n')
180                && (dirSep[2] == 'C' || dirSep[2] == 'c')
181                && (dirSep[3] == '/' || dirSep[3] == '\\')) {
182                /* NT extended UNC path */
183                dirSep += 4;
184            }
185        }
186        if (dirSep[0] != 0 && dirSep[1] == ':' &&
187                (dirSep[2] == '/' || dirSep[2] == '\\')) {
188            /* Do nothing */
189        } else if ((dirSep[0] == '/' || dirSep[0] == '\\')
190                && (dirSep[1] == '/' || dirSep[1] == '\\')) {
191            /*
192             * UNC style path, where we must skip over the first separator,
193             * since the first two segments are actually inseparable.
194             */
195
196            dirSep += 2;
197            dirSep += FindSplitPos(dirSep, '/');
198            if (*dirSep != 0) {
199                dirSep++;
200            }
201        }
202    }
203
204    /*
205     * Scan forward from one directory separator to the next, checking for
206     * '..' and '.' sequences which must be handled specially. In particular
207     * handling of '..' can be complicated if the directory before is a link,
208     * since we will have to expand the link to be able to back up one level.
209     */
210
211    while (*dirSep != 0) {
212        oldDirSep = dirSep;
213        if (!first) {
214            dirSep++;
215        }
216        dirSep += FindSplitPos(dirSep, '/');
217        if (dirSep[0] == 0 || dirSep[1] == 0) {
218            if (retVal != NULL) {
219                Tcl_AppendToObj(retVal, oldDirSep, dirSep - oldDirSep);
220            }
221            break;
222        }
223        if (dirSep[1] == '.') {
224            if (retVal != NULL) {
225                Tcl_AppendToObj(retVal, oldDirSep, dirSep - oldDirSep);
226                oldDirSep = dirSep;
227            }
228        again:
229            if (IsSeparatorOrNull(dirSep[2])) {
230                /*
231                 * Need to skip '.' in the path.
232                 */
233                int curLen;
234
235                if (retVal == NULL) {
236                    const char *path = TclGetString(pathPtr);
237                    retVal = Tcl_NewStringObj(path, dirSep - path);
238                    Tcl_IncrRefCount(retVal);
239                }
240                Tcl_GetStringFromObj(retVal, &curLen);
241                if (curLen == 0) {
242                    Tcl_AppendToObj(retVal, dirSep, 1);
243                }
244                dirSep += 2;
245                oldDirSep = dirSep;
246                if (dirSep[0] != 0 && dirSep[1] == '.') {
247                    goto again;
248                }
249                continue;
250            }
251            if (dirSep[2] == '.' && IsSeparatorOrNull(dirSep[3])) {
252                Tcl_Obj *link;
253                int curLen;
254                char *linkStr;
255
256                /*
257                 * Have '..' so need to skip previous directory.
258                 */
259
260                if (retVal == NULL) {
261                    const char *path = TclGetString(pathPtr);
262                    retVal = Tcl_NewStringObj(path, dirSep - path);
263                    Tcl_IncrRefCount(retVal);
264                }
265                Tcl_GetStringFromObj(retVal, &curLen);
266                if (curLen == 0) {
267                    Tcl_AppendToObj(retVal, dirSep, 1);
268                }
269                if (!first || (tclPlatform == TCL_PLATFORM_UNIX)) {
270                    link = Tcl_FSLink(retVal, NULL, 0);
271                    if (link != NULL) {
272                        /*
273                         * Got a link. Need to check if the link is relative
274                         * or absolute, for those platforms where relative
275                         * links exist.
276                         */
277
278                        if (tclPlatform != TCL_PLATFORM_WINDOWS &&
279                                Tcl_FSGetPathType(link) == TCL_PATH_RELATIVE) {
280                            /*
281                             * We need to follow this link which is relative
282                             * to retVal's directory. This means concatenating
283                             * the link onto the directory of the path so far.
284                             */
285
286                            const char *path =
287                                    Tcl_GetStringFromObj(retVal, &curLen);
288
289                            while (--curLen >= 0) {
290                                if (IsSeparatorOrNull(path[curLen])) {
291                                    break;
292                                }
293                            }
294                            if (Tcl_IsShared(retVal)) {
295                                TclDecrRefCount(retVal);
296                                retVal = Tcl_DuplicateObj(retVal);
297                                Tcl_IncrRefCount(retVal);
298                            }
299
300                            /*
301                             * We want the trailing slash.
302                             */
303
304                            Tcl_SetObjLength(retVal, curLen+1);
305                            Tcl_AppendObjToObj(retVal, link);
306                            TclDecrRefCount(link);
307                            linkStr = Tcl_GetStringFromObj(retVal, &curLen);
308                        } else {
309                            /*
310                             * Absolute link.
311                             */
312
313                            TclDecrRefCount(retVal);
314                            retVal = link;
315                            linkStr = Tcl_GetStringFromObj(retVal, &curLen);
316
317                            /*
318                             * Convert to forward-slashes on windows.
319                             */
320
321                            if (tclPlatform == TCL_PLATFORM_WINDOWS) {
322                                int i;
323                                for (i = 0; i < curLen; i++) {
324                                    if (linkStr[i] == '\\') {
325                                        linkStr[i] = '/';
326                                    }
327                                }
328                            }
329                        }
330                    } else {
331                        linkStr = Tcl_GetStringFromObj(retVal, &curLen);
332                    }
333
334                    /*
335                     * Either way, we now remove the last path element.
336                     * (but not the first character of the path)
337                     */
338
339                    while (--curLen >= 0) {
340                        if (IsSeparatorOrNull(linkStr[curLen])) {
341                            if (curLen) {
342                                Tcl_SetObjLength(retVal, curLen);
343                            } else {
344                                Tcl_SetObjLength(retVal, 1);
345                            }
346                            break;
347                        }
348                    }
349                }
350                dirSep += 3;
351                oldDirSep = dirSep;
352
353                if ((curLen == 0) && (dirSep[0] != 0)) {
354                    Tcl_SetObjLength(retVal, 0);
355                }
356
357                if (dirSep[0] != 0 && dirSep[1] == '.') {
358                    goto again;
359                }
360                continue;
361            }
362        }
363        first = 0;
364        if (retVal != NULL) {
365            Tcl_AppendToObj(retVal, oldDirSep, dirSep - oldDirSep);
366        }
367    }
368
369    /*
370     * If we didn't make any changes, just use the input path.
371     */
372
373    if (retVal == NULL) {
374        retVal = pathPtr;
375        Tcl_IncrRefCount(retVal);
376
377        if (Tcl_IsShared(retVal)) {
378            /*
379             * Unfortunately, the platform-specific normalization code which
380             * will be called below has no way of dealing with the case where
381             * an object is shared. It is expecting to modify an object in
382             * place. So, we must duplicate this here to ensure an object with
383             * a single ref-count.
384             *
385             * If that changes in the future (e.g. the normalize proc is given
386             * one object and is able to return a different one), then we
387             * could remove this code.
388             */
389
390            TclDecrRefCount(retVal);
391            retVal = Tcl_DuplicateObj(pathPtr);
392            Tcl_IncrRefCount(retVal);
393        }
394    }
395
396    /*
397     * Ensure a windows drive like C:/ has a trailing separator
398     */
399
400    if (tclPlatform == TCL_PLATFORM_WINDOWS) {
401        int len;
402        const char *path = Tcl_GetStringFromObj(retVal, &len);
403
404        if (len == 2 && path[0] != 0 && path[1] == ':') {
405            if (Tcl_IsShared(retVal)) {
406                TclDecrRefCount(retVal);
407                retVal = Tcl_DuplicateObj(retVal);
408                Tcl_IncrRefCount(retVal);
409            }
410            Tcl_AppendToObj(retVal, "/", 1);
411        }
412    }
413
414    /*
415     * Now we have an absolute path, with no '..', '.' sequences, but it still
416     * may not be in 'unique' form, depending on the platform. For instance,
417     * Unix is case-sensitive, so the path is ok. Windows is case-insensitive,
418     * and also has the weird 'longname/shortname' thing (e.g. C:/Program
419     * Files/ and C:/Progra~1/ are equivalent).
420     *
421     * Virtual file systems which may be registered may have other criteria
422     * for normalizing a path.
423     */
424
425    TclFSNormalizeToUniquePath(interp, retVal, 0, &clientData);
426
427    /*
428     * Since we know it is a normalized path, we can actually convert this
429     * object into an FsPath for greater efficiency
430     */
431
432    TclFSMakePathFromNormalized(interp, retVal, clientData);
433    if (clientDataPtr != NULL) {
434        *clientDataPtr = clientData;
435    }
436
437    /*
438     * This has a refCount of 1 for the caller, unlike many Tcl_Obj APIs.
439     */
440
441    return retVal;
442}
443
444/*
445 *----------------------------------------------------------------------
446 *
447 * Tcl_FSGetPathType --
448 *
449 *      Determines whether a given path is relative to the current directory,
450 *      relative to the current volume, or absolute.
451 *
452 * Results:
453 *      Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or
454 *      TCL_PATH_VOLUME_RELATIVE.
455 *
456 * Side effects:
457 *      None.
458 *
459 *----------------------------------------------------------------------
460 */
461
462Tcl_PathType
463Tcl_FSGetPathType(
464    Tcl_Obj *pathPtr)
465{
466    return TclFSGetPathType(pathPtr, NULL, NULL);
467}
468
469/*
470 *----------------------------------------------------------------------
471 *
472 * TclFSGetPathType --
473 *
474 *      Determines whether a given path is relative to the current directory,
475 *      relative to the current volume, or absolute. If the caller wishes to
476 *      know which filesystem claimed the path (in the case for which the path
477 *      is absolute), then a reference to a filesystem pointer can be passed
478 *      in (but passing NULL is acceptable).
479 *
480 * Results:
481 *      Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or
482 *      TCL_PATH_VOLUME_RELATIVE. The filesystem reference will be set if and
483 *      only if it is non-NULL and the function's return value is
484 *      TCL_PATH_ABSOLUTE.
485 *
486 * Side effects:
487 *      None.
488 *
489 *----------------------------------------------------------------------
490 */
491
492Tcl_PathType
493TclFSGetPathType(
494    Tcl_Obj *pathPtr,
495    Tcl_Filesystem **filesystemPtrPtr,
496    int *driveNameLengthPtr)
497{
498    FsPath *fsPathPtr;
499
500    if (Tcl_FSConvertToPathType(NULL, pathPtr) != TCL_OK) {
501        return TclGetPathType(pathPtr, filesystemPtrPtr, driveNameLengthPtr,
502                NULL);
503    }
504
505    fsPathPtr = PATHOBJ(pathPtr);
506    if (fsPathPtr->cwdPtr == NULL) {
507        return TclGetPathType(pathPtr, filesystemPtrPtr, driveNameLengthPtr,
508                NULL);
509    }
510
511    if (PATHFLAGS(pathPtr) == 0) {
512        return TCL_PATH_RELATIVE;
513    }
514    return TclFSGetPathType(fsPathPtr->cwdPtr, filesystemPtrPtr,
515            driveNameLengthPtr);
516}
517
518/*
519 *---------------------------------------------------------------------------
520 *
521 * TclPathPart
522 *
523 *      This function calculates the requested part of the given path, which
524 *      can be:
525 *
526 *      - the directory above ('file dirname')
527 *      - the tail            ('file tail')
528 *      - the extension       ('file extension')
529 *      - the root            ('file root')
530 *
531 *      The 'portion' parameter dictates which of these to calculate. There
532 *      are a number of special cases both to be more efficient, and because
533 *      the behaviour when given a path with only a single element is defined
534 *      to require the expansion of that single element, where possible.
535 *
536 *      Should look into integrating 'FileBasename' in tclFCmd.c into this
537 *      function.
538 *
539 * Results:
540 *      NULL if an error occurred, otherwise a Tcl_Obj owned by the caller
541 *      (i.e. most likely with refCount 1).
542 *
543 * Side effects:
544 *      None.
545 *
546 *---------------------------------------------------------------------------
547 */
548
549Tcl_Obj *
550TclPathPart(
551    Tcl_Interp *interp,         /* Used for error reporting */
552    Tcl_Obj *pathPtr,           /* Path to take dirname of */
553    Tcl_PathPart portion)       /* Requested portion of name */
554{
555    if (pathPtr->typePtr == &tclFsPathType) {
556        FsPath *fsPathPtr = PATHOBJ(pathPtr);
557
558        if (TclFSEpochOk(fsPathPtr->filesystemEpoch)
559                && (PATHFLAGS(pathPtr) != 0)) {
560            switch (portion) {
561            case TCL_PATH_DIRNAME: {
562                /*
563                 * Check if the joined-on bit has any directory delimiters in
564                 * it. If so, the 'dirname' would be a joining of the main
565                 * part with the dirname of the joined-on bit. We could handle
566                 * that special case here, but we don't, and instead just use
567                 * the standardPath code.
568                 */
569
570                const char *rest = TclGetString(fsPathPtr->normPathPtr);
571
572                if (strchr(rest, '/') != NULL) {
573                    goto standardPath;
574                }
575                if (tclPlatform == TCL_PLATFORM_WINDOWS
576                        && strchr(rest, '\\') != NULL) {
577                    goto standardPath;
578                }
579
580                /*
581                 * The joined-on path is simple, so we can just return here.
582                 */
583
584                Tcl_IncrRefCount(fsPathPtr->cwdPtr);
585                return fsPathPtr->cwdPtr;
586            }
587            case TCL_PATH_TAIL: {
588                /*
589                 * Check if the joined-on bit has any directory delimiters in
590                 * it. If so, the 'tail' would be only the part following the
591                 * last delimiter. We could handle that special case here, but
592                 * we don't, and instead just use the standardPath code.
593                 */
594
595                const char *rest = TclGetString(fsPathPtr->normPathPtr);
596
597                if (strchr(rest, '/') != NULL) {
598                    goto standardPath;
599                }
600                if (tclPlatform == TCL_PLATFORM_WINDOWS
601                        && strchr(rest, '\\') != NULL) {
602                    goto standardPath;
603                }
604                Tcl_IncrRefCount(fsPathPtr->normPathPtr);
605                return fsPathPtr->normPathPtr;
606            }
607            case TCL_PATH_EXTENSION:
608                return GetExtension(fsPathPtr->normPathPtr);
609            case TCL_PATH_ROOT: {
610                const char *fileName, *extension;
611                int length;
612
613                fileName = Tcl_GetStringFromObj(fsPathPtr->normPathPtr,
614                        &length);
615                extension = TclGetExtension(fileName);
616                if (extension == NULL) {
617                    /*
618                     * There is no extension so the root is the same as the
619                     * path we were given.
620                     */
621
622                    Tcl_IncrRefCount(pathPtr);
623                    return pathPtr;
624                } else {
625                    /*
626                     * Duplicate the object we were given and then trim off
627                     * the extension of the tail component of the path.
628                     */
629
630                    FsPath *fsDupPtr;
631                    Tcl_Obj *root = Tcl_DuplicateObj(pathPtr);
632
633                    Tcl_IncrRefCount(root);
634                    fsDupPtr = PATHOBJ(root);
635                    if (Tcl_IsShared(fsDupPtr->normPathPtr)) {
636                        TclDecrRefCount(fsDupPtr->normPathPtr);
637                        fsDupPtr->normPathPtr = Tcl_NewStringObj(fileName,
638                                (int)(length - strlen(extension)));
639                        Tcl_IncrRefCount(fsDupPtr->normPathPtr);
640                    } else {
641                        Tcl_SetObjLength(fsDupPtr->normPathPtr,
642                                (int)(length - strlen(extension)));
643                    }
644
645                    /*
646                     * Must also trim the string representation if we have it.
647                     */
648
649                    if (root->bytes != NULL && root->length > 0) {
650                        root->length -= strlen(extension);
651                        root->bytes[root->length] = 0;
652                    }
653                    return root;
654                }
655            }
656            default:
657                /* We should never get here */
658                Tcl_Panic("Bad portion to TclPathPart");
659                /* For less clever compilers */
660                return NULL;
661            }
662        } else if (fsPathPtr->cwdPtr != NULL) {
663            /* Relative path */
664            goto standardPath;
665        } else {
666            /* Absolute path */
667            goto standardPath;
668        }
669    } else {
670        int splitElements;
671        Tcl_Obj *splitPtr, *resultPtr;
672
673    standardPath:
674        resultPtr = NULL;
675        if (portion == TCL_PATH_EXTENSION) {
676            return GetExtension(pathPtr);
677        } else if (portion == TCL_PATH_ROOT) {
678            int length;
679            const char *fileName, *extension;
680
681            fileName = Tcl_GetStringFromObj(pathPtr, &length);
682            extension = TclGetExtension(fileName);
683            if (extension == NULL) {
684                Tcl_IncrRefCount(pathPtr);
685                return pathPtr;
686            } else {
687                Tcl_Obj *root = Tcl_NewStringObj(fileName,
688                        (int) (length - strlen(extension)));
689                Tcl_IncrRefCount(root);
690                return root;
691            }
692        }
693
694        /*
695         * The behaviour we want here is slightly different to the standard
696         * Tcl_FSSplitPath in the handling of home directories;
697         * Tcl_FSSplitPath preserves the "~" while this code computes the
698         * actual full path name, if we had just a single component.
699         */
700
701        splitPtr = Tcl_FSSplitPath(pathPtr, &splitElements);
702        Tcl_IncrRefCount(splitPtr);
703        if (splitElements == 1  &&  TclGetString(pathPtr)[0] == '~') {
704            Tcl_Obj *norm;
705
706            TclDecrRefCount(splitPtr);
707            norm = Tcl_FSGetNormalizedPath(interp, pathPtr);
708            if (norm == NULL) {
709                return NULL;
710            }
711            splitPtr = Tcl_FSSplitPath(norm, &splitElements);
712            Tcl_IncrRefCount(splitPtr);
713        }
714        if (portion == TCL_PATH_TAIL) {
715            /*
716             * Return the last component, unless it is the only component, and
717             * it is the root of an absolute path.
718             */
719
720            if ((splitElements > 0) && ((splitElements > 1) ||
721                    (Tcl_FSGetPathType(pathPtr) == TCL_PATH_RELATIVE))) {
722                Tcl_ListObjIndex(NULL, splitPtr, splitElements-1, &resultPtr);
723            } else {
724                resultPtr = Tcl_NewObj();
725            }
726        } else {
727            /*
728             * Return all but the last component. If there is only one
729             * component, return it if the path was non-relative, otherwise
730             * return the current directory.
731             */
732
733            if (splitElements > 1) {
734                resultPtr = Tcl_FSJoinPath(splitPtr, splitElements - 1);
735            } else if (splitElements == 0 ||
736                    (Tcl_FSGetPathType(pathPtr) == TCL_PATH_RELATIVE)) {
737                TclNewLiteralStringObj(resultPtr, ".");
738            } else {
739                Tcl_ListObjIndex(NULL, splitPtr, 0, &resultPtr);
740            }
741        }
742        Tcl_IncrRefCount(resultPtr);
743        TclDecrRefCount(splitPtr);
744        return resultPtr;
745    }
746}
747
748/*
749 * Simple helper function
750 */
751
752static Tcl_Obj *
753GetExtension(
754    Tcl_Obj *pathPtr)
755{
756    const char *tail, *extension;
757    Tcl_Obj *ret;
758
759    tail = TclGetString(pathPtr);
760    extension = TclGetExtension(tail);
761    if (extension == NULL) {
762        ret = Tcl_NewObj();
763    } else {
764        ret = Tcl_NewStringObj(extension, -1);
765    }
766    Tcl_IncrRefCount(ret);
767    return ret;
768}
769
770/*
771 *---------------------------------------------------------------------------
772 *
773 * Tcl_FSJoinPath --
774 *
775 *      This function takes the given Tcl_Obj, which should be a valid list,
776 *      and returns the path object given by considering the first 'elements'
777 *      elements as valid path segments (each path segment may be a complete
778 *      path, a partial path or just a single possible directory or file
779 *      name). If any path segment is actually an absolute path, then all
780 *      prior path segments are discarded.
781 *
782 *      If elements < 0, we use the entire list that was given.
783 *
784 *      It is possible that the returned object is actually an element of the
785 *      given list, so the caller should be careful to store a refCount to it
786 *      before freeing the list.
787 *
788 * Results:
789 *      Returns object with refCount of zero, (or if non-zero, it has
790 *      references elsewhere in Tcl). Either way, the caller must increment
791 *      its refCount before use. Note that in the case where the caller has
792 *      asked to join zero elements of the list, the return value will be an
793 *      empty-string Tcl_Obj.
794 *
795 *      If the given listObj was invalid, then the calling routine has a bug,
796 *      and this function will just return NULL.
797 *
798 * Side effects:
799 *      None.
800 *
801 *---------------------------------------------------------------------------
802 */
803
804Tcl_Obj *
805Tcl_FSJoinPath(
806    Tcl_Obj *listObj,           /* Path elements to join, may have a zero
807                                 * reference count. */
808    int elements)               /* Number of elements to use (-1 = all) */
809{
810    Tcl_Obj *res;
811    int i;
812    Tcl_Filesystem *fsPtr = NULL;
813
814    if (elements < 0) {
815        if (Tcl_ListObjLength(NULL, listObj, &elements) != TCL_OK) {
816            return NULL;
817        }
818    } else {
819        /*
820         * Just make sure it is a valid list.
821         */
822
823        int listTest;
824
825        if (Tcl_ListObjLength(NULL, listObj, &listTest) != TCL_OK) {
826            return NULL;
827        }
828
829        /*
830         * Correct this if it is too large, otherwise we will waste our time
831         * joining null elements to the path.
832         */
833
834        if (elements > listTest) {
835            elements = listTest;
836        }
837    }
838
839    res = NULL;
840
841    for (i = 0; i < elements; i++) {
842        Tcl_Obj *elt, *driveName = NULL;
843        int driveNameLength, strEltLen, length;
844        Tcl_PathType type;
845        char *strElt, *ptr;
846
847        Tcl_ListObjIndex(NULL, listObj, i, &elt);
848
849        /*
850         * This is a special case where we can be much more efficient, where
851         * we are joining a single relative path onto an object that is
852         * already of path type. The 'TclNewFSPathObj' call below creates an
853         * object which can be normalized more efficiently. Currently we only
854         * use the special case when we have exactly two elements, but we
855         * could expand that in the future.
856         */
857
858        if ((i == (elements-2)) && (i == 0) && (elt->typePtr == &tclFsPathType)
859                && !(elt->bytes != NULL && (elt->bytes[0] == '\0'))) {
860            Tcl_Obj *tail;
861            Tcl_PathType type;
862
863            Tcl_ListObjIndex(NULL, listObj, i+1, &tail);
864            type = TclGetPathType(tail, NULL, NULL, NULL);
865            if (type == TCL_PATH_RELATIVE) {
866                const char *str;
867                int len;
868
869                str = Tcl_GetStringFromObj(tail, &len);
870                if (len == 0) {
871                    /*
872                     * This happens if we try to handle the root volume '/'.
873                     * There's no need to return a special path object, when
874                     * the base itself is just fine!
875                     */
876
877                    if (res != NULL) {
878                        TclDecrRefCount(res);
879                    }
880                    return elt;
881                }
882
883                /*
884                 * If it doesn't begin with '.' and is a unix path or it a
885                 * windows path without backslashes, then we can be very
886                 * efficient here. (In fact even a windows path with
887                 * backslashes can be joined efficiently, but the path object
888                 * would not have forward slashes only, and this would
889                 * therefore contradict our 'file join' documentation).
890                 */
891
892                if (str[0] != '.' && ((tclPlatform != TCL_PLATFORM_WINDOWS)
893                        || (strchr(str, '\\') == NULL))) {
894                    /*
895                     * Finally, on Windows, 'file join' is defined to convert
896                     * all backslashes to forward slashes, so the base part
897                     * cannot have backslashes either.
898                     */
899
900                    if ((tclPlatform != TCL_PLATFORM_WINDOWS)
901                            || (strchr(Tcl_GetString(elt), '\\') == NULL)) {
902                        if (res != NULL) {
903                            TclDecrRefCount(res);
904                        }
905                        return TclNewFSPathObj(elt, str, len);
906                    }
907                }
908
909                /*
910                 * Otherwise we don't have an easy join, and we must let the
911                 * more general code below handle things
912                 */
913            } else if (tclPlatform == TCL_PLATFORM_UNIX) {
914                if (res != NULL) {
915                    TclDecrRefCount(res);
916                }
917                return tail;
918            } else {
919                const char *str = Tcl_GetString(tail);
920
921                if (tclPlatform == TCL_PLATFORM_WINDOWS) {
922                    if (strchr(str, '\\') == NULL) {
923                        if (res != NULL) {
924                            TclDecrRefCount(res);
925                        }
926                        return tail;
927                    }
928                }
929            }
930        }
931        strElt = Tcl_GetStringFromObj(elt, &strEltLen);
932        type = TclGetPathType(elt, &fsPtr, &driveNameLength, &driveName);
933        if (type != TCL_PATH_RELATIVE) {
934            /*
935             * Zero out the current result.
936             */
937
938            if (res != NULL) {
939                TclDecrRefCount(res);
940            }
941
942            if (driveName != NULL) {
943                /*
944                 * We've been given a separate drive-name object, because the
945                 * prefix in 'elt' is not in a suitable format for us (e.g. it
946                 * may contain irrelevant multiple separators, like
947                 * C://///foo).
948                 */
949
950                res = Tcl_DuplicateObj(driveName);
951                TclDecrRefCount(driveName);
952
953                /*
954                 * Do not set driveName to NULL, because we will check its
955                 * value below (but we won't access the contents, since those
956                 * have been cleaned-up).
957                 */
958            } else {
959                res = Tcl_NewStringObj(strElt, driveNameLength);
960            }
961            strElt += driveNameLength;
962        } else if (driveName != NULL) {
963            Tcl_DecrRefCount(driveName);
964        }
965
966        /*
967         * Optimisation block: if this is the last element to be examined, and
968         * it is absolute or the only element, and the drive-prefix was ok (if
969         * there is one), it might be that the path is already in a suitable
970         * form to be returned. Then we can short-cut the rest of this
971         * function.
972         */
973
974        if ((driveName == NULL) && (i == (elements - 1))
975                && (type != TCL_PATH_RELATIVE || res == NULL)) {
976            /*
977             * It's the last path segment. Perform a quick check if the path
978             * is already in a suitable form.
979             */
980
981            if (tclPlatform == TCL_PLATFORM_WINDOWS) {
982                if (strchr(strElt, '\\') != NULL) {
983                    goto noQuickReturn;
984                }
985            }
986            ptr = strElt;
987            while (*ptr != '\0') {
988                if (*ptr == '/' && (ptr[1] == '/' || ptr[1] == '\0')) {
989                    /*
990                     * We have a repeated file separator, which means the path
991                     * is not in normalized form
992                     */
993
994                    goto noQuickReturn;
995                }
996                ptr++;
997            }
998            if (res != NULL) {
999                TclDecrRefCount(res);
1000            }
1001
1002            /*
1003             * This element is just what we want to return already - no
1004             * further manipulation is requred.
1005             */
1006
1007            return elt;
1008        }
1009
1010        /*
1011         * The path element was not of a suitable form to be returned as is.
1012         * We need to perform a more complex operation here.
1013         */
1014
1015    noQuickReturn:
1016        if (res == NULL) {
1017            res = Tcl_NewObj();
1018            ptr = Tcl_GetStringFromObj(res, &length);
1019        } else {
1020            ptr = Tcl_GetStringFromObj(res, &length);
1021        }
1022
1023        /*
1024         * Strip off any './' before a tilde, unless this is the beginning of
1025         * the path.
1026         */
1027
1028        if (length > 0 && strEltLen > 0 && (strElt[0] == '.') &&
1029                (strElt[1] == '/') && (strElt[2] == '~')) {
1030            strElt += 2;
1031        }
1032
1033        /*
1034         * A NULL value for fsPtr at this stage basically means we're trying
1035         * to join a relative path onto something which is also relative (or
1036         * empty). There's nothing particularly wrong with that.
1037         */
1038
1039        if (*strElt == '\0') {
1040            continue;
1041        }
1042
1043        if (fsPtr == &tclNativeFilesystem || fsPtr == NULL) {
1044            TclpNativeJoinPath(res, strElt);
1045        } else {
1046            char separator = '/';
1047            int needsSep = 0;
1048
1049            if (fsPtr->filesystemSeparatorProc != NULL) {
1050                Tcl_Obj *sep = (*fsPtr->filesystemSeparatorProc)(res);
1051
1052                if (sep != NULL) {
1053                    separator = TclGetString(sep)[0];
1054                }
1055            }
1056
1057            if (length > 0 && ptr[length -1] != '/') {
1058                Tcl_AppendToObj(res, &separator, 1);
1059                length++;
1060            }
1061            Tcl_SetObjLength(res, length + (int) strlen(strElt));
1062
1063            ptr = TclGetString(res) + length;
1064            for (; *strElt != '\0'; strElt++) {
1065                if (*strElt == separator) {
1066                    while (strElt[1] == separator) {
1067                        strElt++;
1068                    }
1069                    if (strElt[1] != '\0') {
1070                        if (needsSep) {
1071                            *ptr++ = separator;
1072                        }
1073                    }
1074                } else {
1075                    *ptr++ = *strElt;
1076                    needsSep = 1;
1077                }
1078            }
1079            length = ptr - TclGetString(res);
1080            Tcl_SetObjLength(res, length);
1081        }
1082    }
1083    if (res == NULL) {
1084        res = Tcl_NewObj();
1085    }
1086    return res;
1087}
1088
1089/*
1090 *---------------------------------------------------------------------------
1091 *
1092 * Tcl_FSConvertToPathType --
1093 *
1094 *      This function tries to convert the given Tcl_Obj to a valid Tcl path
1095 *      type, taking account of the fact that the cwd may have changed even if
1096 *      this object is already supposedly of the correct type.
1097 *
1098 *      The filename may begin with "~" (to indicate current user's home
1099 *      directory) or "~<user>" (to indicate any user's home directory).
1100 *
1101 * Results:
1102 *      Standard Tcl error code.
1103 *
1104 * Side effects:
1105 *      The old representation may be freed, and new memory allocated.
1106 *
1107 *---------------------------------------------------------------------------
1108 */
1109
1110int
1111Tcl_FSConvertToPathType(
1112    Tcl_Interp *interp,         /* Interpreter in which to store error message
1113                                 * (if necessary). */
1114    Tcl_Obj *pathPtr)           /* Object to convert to a valid, current path
1115                                 * type. */
1116{
1117    /*
1118     * While it is bad practice to examine an object's type directly, this is
1119     * actually the best thing to do here. The reason is that if we are
1120     * converting this object to FsPath type for the first time, we don't need
1121     * to worry whether the 'cwd' has changed. On the other hand, if this
1122     * object is already of FsPath type, and is a relative path, we do have to
1123     * worry about the cwd. If the cwd has changed, we must recompute the
1124     * path.
1125     */
1126
1127    if (pathPtr->typePtr == &tclFsPathType) {
1128        if (TclFSEpochOk(PATHOBJ(pathPtr)->filesystemEpoch)) {
1129            return TCL_OK;
1130        }
1131
1132        if (pathPtr->bytes == NULL) {
1133            UpdateStringOfFsPath(pathPtr);
1134        }
1135        FreeFsPathInternalRep(pathPtr);
1136        pathPtr->typePtr = NULL;
1137    }
1138
1139    return Tcl_ConvertToType(interp, pathPtr, &tclFsPathType);
1140
1141    /*
1142     * We used to have more complex code here:
1143     *
1144     * FsPath *fsPathPtr = PATHOBJ(pathPtr);
1145     * if (fsPathPtr->cwdPtr == NULL || PATHFLAGS(pathPtr) != 0) {
1146     *     return TCL_OK;
1147     * } else {
1148     *     if (TclFSCwdPointerEquals(&fsPathPtr->cwdPtr)) {
1149     *         return TCL_OK;
1150     *     } else {
1151     *         if (pathPtr->bytes == NULL) {
1152     *             UpdateStringOfFsPath(pathPtr);
1153     *         }
1154     *         FreeFsPathInternalRep(pathPtr);
1155     *         pathPtr->typePtr = NULL;
1156     *         return Tcl_ConvertToType(interp, pathPtr, &tclFsPathType);
1157     *     }
1158     * }
1159     *
1160     * But we no longer believe this is necessary.
1161     */
1162}
1163
1164/*
1165 * Helper function for normalization.
1166 */
1167
1168static int
1169IsSeparatorOrNull(
1170    int ch)
1171{
1172    if (ch == 0) {
1173        return 1;
1174    }
1175    switch (tclPlatform) {
1176    case TCL_PLATFORM_UNIX:
1177        return (ch == '/' ? 1 : 0);
1178    case TCL_PLATFORM_WINDOWS:
1179        return ((ch == '/' || ch == '\\') ? 1 : 0);
1180    }
1181    return 0;
1182}
1183
1184/*
1185 * Helper function for SetFsPathFromAny. Returns position of first directory
1186 * delimiter in the path. If no separator is found, then returns the position
1187 * of the end of the string.
1188 */
1189
1190static int
1191FindSplitPos(
1192    const char *path,
1193    int separator)
1194{
1195    int count = 0;
1196    switch (tclPlatform) {
1197    case TCL_PLATFORM_UNIX:
1198        while (path[count] != 0) {
1199            if (path[count] == separator) {
1200                return count;
1201            }
1202            count++;
1203        }
1204        break;
1205
1206    case TCL_PLATFORM_WINDOWS:
1207        while (path[count] != 0) {
1208            if (path[count] == separator || path[count] == '\\') {
1209                return count;
1210            }
1211            count++;
1212        }
1213        break;
1214    }
1215    return count;
1216}
1217
1218/*
1219 *---------------------------------------------------------------------------
1220 *
1221 * TclNewFSPathObj --
1222 *
1223 *      Creates a path object whose string representation is '[file join
1224 *      dirPtr addStrRep]', but does so in a way that allows for more
1225 *      efficient creation and caching of normalized paths, and more efficient
1226 *      'file dirname', 'file tail', etc.
1227 *
1228 * Assumptions:
1229 *      'dirPtr' must be an absolute path. 'len' may not be zero.
1230 *
1231 * Results:
1232 *      The new Tcl object, with refCount zero.
1233 *
1234 * Side effects:
1235 *      Memory is allocated. 'dirPtr' gets an additional refCount.
1236 *
1237 *---------------------------------------------------------------------------
1238 */
1239
1240Tcl_Obj *
1241TclNewFSPathObj(
1242    Tcl_Obj *dirPtr,
1243    const char *addStrRep,
1244    int len)
1245{
1246    FsPath *fsPathPtr;
1247    Tcl_Obj *pathPtr;
1248    ThreadSpecificData *tsdPtr;
1249
1250    tsdPtr = TCL_TSD_INIT(&tclFsDataKey);
1251
1252    pathPtr = Tcl_NewObj();
1253    fsPathPtr = (FsPath *) ckalloc(sizeof(FsPath));
1254
1255    /*
1256     * Set up the path.
1257     */
1258
1259    fsPathPtr->translatedPathPtr = NULL;
1260    fsPathPtr->normPathPtr = Tcl_NewStringObj(addStrRep, len);
1261    Tcl_IncrRefCount(fsPathPtr->normPathPtr);
1262    fsPathPtr->cwdPtr = dirPtr;
1263    Tcl_IncrRefCount(dirPtr);
1264    fsPathPtr->nativePathPtr = NULL;
1265    fsPathPtr->fsRecPtr = NULL;
1266    fsPathPtr->filesystemEpoch = tsdPtr->filesystemEpoch;
1267
1268    SETPATHOBJ(pathPtr, fsPathPtr);
1269    PATHFLAGS(pathPtr) = TCLPATH_APPENDED;
1270    pathPtr->typePtr = &tclFsPathType;
1271    pathPtr->bytes = NULL;
1272    pathPtr->length = 0;
1273
1274    return pathPtr;
1275}
1276
1277/*
1278 *---------------------------------------------------------------------------
1279 *
1280 * TclFSMakePathRelative --
1281 *
1282 *      Only for internal use.
1283 *
1284 *      Takes a path and a directory, where we _assume_ both path and
1285 *      directory are absolute, normalized and that the path lies inside the
1286 *      directory. Returns a Tcl_Obj representing filename of the path
1287 *      relative to the directory.
1288 *
1289 *      In the case where the resulting path would start with a '~', we take
1290 *      special care to return an ordinary string. This means to use that path
1291 *      (and not have it interpreted as a user name), one must prepend './'.
1292 *      This may seem strange, but that is how 'glob' is currently defined.
1293 *
1294 * Results:
1295 *      NULL on error, otherwise a valid object, typically with refCount of
1296 *      zero, which it is assumed the caller will increment.
1297 *
1298 * Side effects:
1299 *      The old representation may be freed, and new memory allocated.
1300 *
1301 *---------------------------------------------------------------------------
1302 */
1303
1304Tcl_Obj *
1305TclFSMakePathRelative(
1306    Tcl_Interp *interp,         /* Used for error reporting if not NULL. */
1307    Tcl_Obj *pathPtr,           /* The path we have. */
1308    Tcl_Obj *cwdPtr)            /* Make it relative to this. */
1309{
1310    int cwdLen, len;
1311    const char *tempStr;
1312    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&tclFsDataKey);
1313
1314    if (pathPtr->typePtr == &tclFsPathType) {
1315        FsPath *fsPathPtr = PATHOBJ(pathPtr);
1316
1317        if (PATHFLAGS(pathPtr) != 0
1318                && fsPathPtr->cwdPtr == cwdPtr) {
1319            pathPtr = fsPathPtr->normPathPtr;
1320
1321            /*
1322             * Free old representation.
1323             */
1324
1325            if (pathPtr->typePtr != NULL) {
1326                if (pathPtr->bytes == NULL) {
1327                    if (pathPtr->typePtr->updateStringProc == NULL) {
1328                        if (interp != NULL) {
1329                            Tcl_ResetResult(interp);
1330                            Tcl_AppendResult(interp, "can't find object"
1331                                    "string representation", NULL);
1332                        }
1333                        return NULL;
1334                    }
1335                    pathPtr->typePtr->updateStringProc(pathPtr);
1336                }
1337                TclFreeIntRep(pathPtr);
1338            }
1339
1340            /*
1341             * Now pathPtr is a string object.
1342             */
1343
1344            if (Tcl_GetString(pathPtr)[0] == '~') {
1345                /*
1346                 * If the first character of the path is a tilde, we must just
1347                 * return the path as is, to agree with the defined behaviour
1348                 * of 'glob'.
1349                 */
1350
1351                return pathPtr;
1352            }
1353
1354            fsPathPtr = (FsPath *) ckalloc(sizeof(FsPath));
1355
1356            /*
1357             * Circular reference, by design.
1358             */
1359
1360            fsPathPtr->translatedPathPtr = pathPtr;
1361            fsPathPtr->normPathPtr = NULL;
1362            fsPathPtr->cwdPtr = cwdPtr;
1363            Tcl_IncrRefCount(cwdPtr);
1364            fsPathPtr->nativePathPtr = NULL;
1365            fsPathPtr->fsRecPtr = NULL;
1366            fsPathPtr->filesystemEpoch = tsdPtr->filesystemEpoch;
1367
1368            SETPATHOBJ(pathPtr, fsPathPtr);
1369            PATHFLAGS(pathPtr) = 0;
1370            pathPtr->typePtr = &tclFsPathType;
1371
1372            return pathPtr;
1373        }
1374    }
1375
1376    /*
1377     * We know the cwd is a normalised object which does not end in a
1378     * directory delimiter, unless the cwd is the name of a volume, in which
1379     * case it will end in a delimiter! We handle this situation here. A
1380     * better test than the '!= sep' might be to simply check if 'cwd' is a
1381     * root volume.
1382     *
1383     * Note that if we get this wrong, we will strip off either too much or
1384     * too little below, leading to wrong answers returned by glob.
1385     */
1386
1387    tempStr = Tcl_GetStringFromObj(cwdPtr, &cwdLen);
1388
1389    /*
1390     * Should we perhaps use 'Tcl_FSPathSeparator'? But then what about the
1391     * Windows special case? Perhaps we should just check if cwd is a root
1392     * volume.
1393     */
1394
1395    switch (tclPlatform) {
1396    case TCL_PLATFORM_UNIX:
1397        if (tempStr[cwdLen-1] != '/') {
1398            cwdLen++;
1399        }
1400        break;
1401    case TCL_PLATFORM_WINDOWS:
1402        if (tempStr[cwdLen-1] != '/' && tempStr[cwdLen-1] != '\\') {
1403            cwdLen++;
1404        }
1405        break;
1406    }
1407    tempStr = Tcl_GetStringFromObj(pathPtr, &len);
1408
1409    return Tcl_NewStringObj(tempStr + cwdLen, len - cwdLen);
1410}
1411
1412/*
1413 *---------------------------------------------------------------------------
1414 *
1415 * TclFSMakePathFromNormalized --
1416 *
1417 *      Like SetFsPathFromAny, but assumes the given object is an absolute
1418 *      normalized path. Only for internal use.
1419 *
1420 * Results:
1421 *      Standard Tcl error code.
1422 *
1423 * Side effects:
1424 *      The old representation may be freed, and new memory allocated.
1425 *
1426 *---------------------------------------------------------------------------
1427 */
1428
1429int
1430TclFSMakePathFromNormalized(
1431    Tcl_Interp *interp,         /* Used for error reporting if not NULL. */
1432    Tcl_Obj *pathPtr,           /* The object to convert. */
1433    ClientData nativeRep)       /* The native rep for the object, if known
1434                                 * else NULL. */
1435{
1436    FsPath *fsPathPtr;
1437    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&tclFsDataKey);
1438
1439    if (pathPtr->typePtr == &tclFsPathType) {
1440        return TCL_OK;
1441    }
1442
1443    /*
1444     * Free old representation
1445     */
1446
1447    if (pathPtr->typePtr != NULL) {
1448        if (pathPtr->bytes == NULL) {
1449            if (pathPtr->typePtr->updateStringProc == NULL) {
1450                if (interp != NULL) {
1451                    Tcl_ResetResult(interp);
1452                    Tcl_AppendResult(interp, "can't find object"
1453                            "string representation", NULL);
1454                }
1455                return TCL_ERROR;
1456            }
1457            pathPtr->typePtr->updateStringProc(pathPtr);
1458        }
1459        TclFreeIntRep(pathPtr);
1460    }
1461
1462    fsPathPtr = (FsPath *) ckalloc(sizeof(FsPath));
1463
1464    /*
1465     * It's a pure normalized absolute path.
1466     */
1467
1468    fsPathPtr->translatedPathPtr = NULL;
1469
1470    /*
1471     * Circular reference by design.
1472     */
1473
1474    fsPathPtr->normPathPtr = pathPtr;
1475    fsPathPtr->cwdPtr = NULL;
1476    fsPathPtr->nativePathPtr = nativeRep;
1477    fsPathPtr->fsRecPtr = NULL;
1478    fsPathPtr->filesystemEpoch = tsdPtr->filesystemEpoch;
1479
1480    SETPATHOBJ(pathPtr, fsPathPtr);
1481    PATHFLAGS(pathPtr) = 0;
1482    pathPtr->typePtr = &tclFsPathType;
1483
1484    return TCL_OK;
1485}
1486
1487/*
1488 *---------------------------------------------------------------------------
1489 *
1490 * Tcl_FSNewNativePath --
1491 *
1492 *      This function performs the something like the reverse of the usual
1493 *      obj->path->nativerep conversions. If some code retrieves a path in
1494 *      native form (from, e.g. readlink or a native dialog), and that path is
1495 *      to be used at the Tcl level, then calling this function is an
1496 *      efficient way of creating the appropriate path object type.
1497 *
1498 *      Any memory which is allocated for 'clientData' should be retained
1499 *      until clientData is passed to the filesystem's freeInternalRepProc
1500 *      when it can be freed. The built in platform-specific filesystems use
1501 *      'ckalloc' to allocate clientData, and ckfree to free it.
1502 *
1503 * Results:
1504 *      NULL or a valid path object pointer, with refCount zero.
1505 *
1506 * Side effects:
1507 *      New memory may be allocated.
1508 *
1509 *---------------------------------------------------------------------------
1510 */
1511
1512Tcl_Obj *
1513Tcl_FSNewNativePath(
1514    Tcl_Filesystem *fromFilesystem,
1515    ClientData clientData)
1516{
1517    Tcl_Obj *pathPtr;
1518    FsPath *fsPathPtr;
1519
1520    FilesystemRecord *fsFromPtr;
1521    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&tclFsDataKey);
1522
1523    pathPtr = TclFSInternalToNormalized(fromFilesystem, clientData,
1524            &fsFromPtr);
1525    if (pathPtr == NULL) {
1526        return NULL;
1527    }
1528
1529    /*
1530     * Free old representation; shouldn't normally be any, but best to be
1531     * safe.
1532     */
1533
1534    if (pathPtr->typePtr != NULL) {
1535        if (pathPtr->bytes == NULL) {
1536            if (pathPtr->typePtr->updateStringProc == NULL) {
1537                return NULL;
1538            }
1539            pathPtr->typePtr->updateStringProc(pathPtr);
1540        }
1541        TclFreeIntRep(pathPtr);
1542    }
1543
1544    fsPathPtr = (FsPath *) ckalloc(sizeof(FsPath));
1545
1546    fsPathPtr->translatedPathPtr = NULL;
1547
1548    /*
1549     * Circular reference, by design.
1550     */
1551
1552    fsPathPtr->normPathPtr = pathPtr;
1553    fsPathPtr->cwdPtr = NULL;
1554    fsPathPtr->nativePathPtr = clientData;
1555    fsPathPtr->fsRecPtr = fsFromPtr;
1556    fsPathPtr->fsRecPtr->fileRefCount++;
1557    fsPathPtr->filesystemEpoch = tsdPtr->filesystemEpoch;
1558
1559    SETPATHOBJ(pathPtr, fsPathPtr);
1560    PATHFLAGS(pathPtr) = 0;
1561    pathPtr->typePtr = &tclFsPathType;
1562
1563    return pathPtr;
1564}
1565
1566/*
1567 *---------------------------------------------------------------------------
1568 *
1569 * Tcl_FSGetTranslatedPath --
1570 *
1571 *      This function attempts to extract the translated path from the given
1572 *      Tcl_Obj. If the translation succeeds (i.e. the object is a valid
1573 *      path), then it is returned. Otherwise NULL will be returned, and an
1574 *      error message may be left in the interpreter (if it is non-NULL)
1575 *
1576 * Results:
1577 *      NULL or a valid Tcl_Obj pointer.
1578 *
1579 * Side effects:
1580 *      Only those of 'Tcl_FSConvertToPathType'
1581 *
1582 *---------------------------------------------------------------------------
1583 */
1584
1585Tcl_Obj *
1586Tcl_FSGetTranslatedPath(
1587    Tcl_Interp *interp,
1588    Tcl_Obj *pathPtr)
1589{
1590    Tcl_Obj *retObj = NULL;
1591    FsPath *srcFsPathPtr;
1592
1593    if (Tcl_FSConvertToPathType(interp, pathPtr) != TCL_OK) {
1594        return NULL;
1595    }
1596    srcFsPathPtr = PATHOBJ(pathPtr);
1597    if (srcFsPathPtr->translatedPathPtr == NULL) {
1598        if (PATHFLAGS(pathPtr) != 0) {
1599            retObj = Tcl_FSGetNormalizedPath(interp, pathPtr);
1600        } else {
1601            /*
1602             * It is a pure absolute, normalized path object. This is
1603             * something like being a 'pure list'. The object's string,
1604             * translatedPath and normalizedPath are all identical.
1605             */
1606
1607            retObj = srcFsPathPtr->normPathPtr;
1608        }
1609    } else {
1610        /*
1611         * It is an ordinary path object.
1612         */
1613
1614        retObj = srcFsPathPtr->translatedPathPtr;
1615    }
1616
1617    if (retObj != NULL) {
1618        Tcl_IncrRefCount(retObj);
1619    }
1620    return retObj;
1621}
1622
1623/*
1624 *---------------------------------------------------------------------------
1625 *
1626 * Tcl_FSGetTranslatedStringPath --
1627 *
1628 *      This function attempts to extract the translated path from the given
1629 *      Tcl_Obj. If the translation succeeds (i.e. the object is a valid
1630 *      path), then the path is returned. Otherwise NULL will be returned, and
1631 *      an error message may be left in the interpreter (if it is non-NULL)
1632 *
1633 * Results:
1634 *      NULL or a valid string.
1635 *
1636 * Side effects:
1637 *      Only those of 'Tcl_FSConvertToPathType'
1638 *
1639 *---------------------------------------------------------------------------
1640 */
1641
1642const char *
1643Tcl_FSGetTranslatedStringPath(
1644    Tcl_Interp *interp,
1645    Tcl_Obj *pathPtr)
1646{
1647    Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(interp, pathPtr);
1648
1649    if (transPtr != NULL) {
1650        int len;
1651        const char *orig = Tcl_GetStringFromObj(transPtr, &len);
1652        char *result = (char *) ckalloc((unsigned) len+1);
1653
1654        memcpy(result, orig, (size_t) len+1);
1655        TclDecrRefCount(transPtr);
1656        return result;
1657    }
1658
1659    return NULL;
1660}
1661
1662/*
1663 *---------------------------------------------------------------------------
1664 *
1665 * Tcl_FSGetNormalizedPath --
1666 *
1667 *      This important function attempts to extract from the given Tcl_Obj a
1668 *      unique normalised path representation, whose string value can be used
1669 *      as a unique identifier for the file.
1670 *
1671 * Results:
1672 *      NULL or a valid path object pointer.
1673 *
1674 * Side effects:
1675 *      New memory may be allocated. The Tcl 'errno' may be modified in the
1676 *      process of trying to examine various path possibilities.
1677 *
1678 *---------------------------------------------------------------------------
1679 */
1680
1681Tcl_Obj *
1682Tcl_FSGetNormalizedPath(
1683    Tcl_Interp *interp,
1684    Tcl_Obj *pathPtr)
1685{
1686    FsPath *fsPathPtr;
1687
1688    if (Tcl_FSConvertToPathType(interp, pathPtr) != TCL_OK) {
1689        return NULL;
1690    }
1691    fsPathPtr = PATHOBJ(pathPtr);
1692
1693    if (PATHFLAGS(pathPtr) != 0) {
1694        /*
1695         * This is a special path object which is the result of something like
1696         * 'file join'
1697         */
1698
1699        Tcl_Obj *dir, *copy;
1700        int cwdLen;
1701        int pathType;
1702        const char *cwdStr;
1703        ClientData clientData = NULL;
1704
1705        pathType = Tcl_FSGetPathType(fsPathPtr->cwdPtr);
1706        dir = Tcl_FSGetNormalizedPath(interp, fsPathPtr->cwdPtr);
1707        if (dir == NULL) {
1708            return NULL;
1709        }
1710        if (pathPtr->bytes == NULL) {
1711            UpdateStringOfFsPath(pathPtr);
1712        }
1713        copy = Tcl_DuplicateObj(dir);
1714        Tcl_IncrRefCount(copy);
1715        Tcl_IncrRefCount(dir);
1716
1717        /*
1718         * We now own a reference on both 'dir' and 'copy'
1719         */
1720
1721        cwdStr = Tcl_GetStringFromObj(copy, &cwdLen);
1722
1723        /*
1724         * Should we perhaps use 'Tcl_FSPathSeparator'? But then what about
1725         * the Windows special case? Perhaps we should just check if cwd is a
1726         * root volume. We should never get cwdLen == 0 in this code path.
1727         */
1728
1729        switch (tclPlatform) {
1730        case TCL_PLATFORM_UNIX:
1731            if (cwdStr[cwdLen-1] != '/') {
1732                Tcl_AppendToObj(copy, "/", 1);
1733                cwdLen++;
1734            }
1735            break;
1736        case TCL_PLATFORM_WINDOWS:
1737            if (cwdStr[cwdLen-1] != '/' && cwdStr[cwdLen-1] != '\\') {
1738                Tcl_AppendToObj(copy, "/", 1);
1739                cwdLen++;
1740            }
1741            break;
1742        }
1743        Tcl_AppendObjToObj(copy, fsPathPtr->normPathPtr);
1744
1745        /*
1746         * Normalize the combined string, but only starting after the end of
1747         * the previously normalized 'dir'. This should be much faster! We use
1748         * 'cwdLen-1' so that we are already pointing at the dir-separator
1749         * that we know about. The normalization code will actually start off
1750         * directly after that separator.
1751         */
1752
1753        TclFSNormalizeToUniquePath(interp, copy, cwdLen-1,
1754                (fsPathPtr->nativePathPtr == NULL ? &clientData : NULL));
1755
1756        /*
1757         * Now we need to construct the new path object
1758         */
1759
1760        if (pathType == TCL_PATH_RELATIVE) {
1761            Tcl_Obj *origDir = fsPathPtr->cwdPtr;
1762            FsPath *origDirFsPathPtr = PATHOBJ(origDir);
1763
1764            fsPathPtr->cwdPtr = origDirFsPathPtr->cwdPtr;
1765            Tcl_IncrRefCount(fsPathPtr->cwdPtr);
1766
1767            TclDecrRefCount(fsPathPtr->normPathPtr);
1768            fsPathPtr->normPathPtr = copy;
1769
1770            /*
1771             * That's our reference to copy used.
1772             */
1773
1774            TclDecrRefCount(dir);
1775            TclDecrRefCount(origDir);
1776        } else {
1777            TclDecrRefCount(fsPathPtr->cwdPtr);
1778            fsPathPtr->cwdPtr = NULL;
1779            TclDecrRefCount(fsPathPtr->normPathPtr);
1780            fsPathPtr->normPathPtr = copy;
1781
1782            /*
1783             * That's our reference to copy used.
1784             */
1785
1786            TclDecrRefCount(dir);
1787        }
1788        if (clientData != NULL) {
1789            fsPathPtr->nativePathPtr = clientData;
1790        }
1791        PATHFLAGS(pathPtr) = 0;
1792    }
1793
1794    /*
1795     * Ensure cwd hasn't changed.
1796     */
1797
1798    if (fsPathPtr->cwdPtr != NULL) {
1799        if (!TclFSCwdPointerEquals(&fsPathPtr->cwdPtr)) {
1800            if (pathPtr->bytes == NULL) {
1801                UpdateStringOfFsPath(pathPtr);
1802            }
1803            FreeFsPathInternalRep(pathPtr);
1804            pathPtr->typePtr = NULL;
1805            if (Tcl_ConvertToType(interp, pathPtr, &tclFsPathType) != TCL_OK) {
1806                return NULL;
1807            }
1808            fsPathPtr = PATHOBJ(pathPtr);
1809        } else if (fsPathPtr->normPathPtr == NULL) {
1810            int cwdLen;
1811            Tcl_Obj *copy;
1812            const char *cwdStr;
1813            ClientData clientData = NULL;
1814
1815            copy = Tcl_DuplicateObj(fsPathPtr->cwdPtr);
1816            Tcl_IncrRefCount(copy);
1817            cwdStr = Tcl_GetStringFromObj(copy, &cwdLen);
1818
1819            /*
1820             * Should we perhaps use 'Tcl_FSPathSeparator'? But then what
1821             * about the Windows special case? Perhaps we should just check if
1822             * cwd is a root volume. We should never get cwdLen == 0 in this
1823             * code path.
1824             */
1825
1826            switch (tclPlatform) {
1827            case TCL_PLATFORM_UNIX:
1828                if (cwdStr[cwdLen-1] != '/') {
1829                    Tcl_AppendToObj(copy, "/", 1);
1830                    cwdLen++;
1831                }
1832                break;
1833            case TCL_PLATFORM_WINDOWS:
1834                if (cwdStr[cwdLen-1] != '/' && cwdStr[cwdLen-1] != '\\') {
1835                    Tcl_AppendToObj(copy, "/", 1);
1836                    cwdLen++;
1837                }
1838                break;
1839            }
1840            Tcl_AppendObjToObj(copy, pathPtr);
1841
1842            /*
1843             * Normalize the combined string, but only starting after the end
1844             * of the previously normalized 'dir'. This should be much faster!
1845             */
1846
1847            TclFSNormalizeToUniquePath(interp, copy, cwdLen-1,
1848                    (fsPathPtr->nativePathPtr == NULL ? &clientData : NULL));
1849            fsPathPtr->normPathPtr = copy;
1850            if (clientData != NULL) {
1851                fsPathPtr->nativePathPtr = clientData;
1852            }
1853        }
1854    }
1855    if (fsPathPtr->normPathPtr == NULL) {
1856        ClientData clientData = NULL;
1857        Tcl_Obj *useThisCwd = NULL;
1858
1859        /*
1860         * Since normPathPtr is NULL, but this is a valid path object, we know
1861         * that the translatedPathPtr cannot be NULL.
1862         */
1863
1864        Tcl_Obj *absolutePath = fsPathPtr->translatedPathPtr;
1865        const char *path = TclGetString(absolutePath);
1866        Tcl_IncrRefCount(absolutePath);
1867
1868        /*
1869         * We have to be a little bit careful here to avoid infinite loops
1870         * we're asking Tcl_FSGetPathType to return the path's type, but that
1871         * call can actually result in a lot of other filesystem action, which
1872         * might loop back through here.
1873         */
1874
1875        if (path[0] != '\0') {
1876            /*
1877             * We don't ask for the type of 'pathPtr' here, because that is
1878             * not correct for our purposes when we have a path like '~'. Tcl
1879             * has a bit of a contradiction in that '~' paths are defined as
1880             * 'absolute', but in reality can be just about anything,
1881             * depending on how env(HOME) is set.
1882             */
1883
1884            Tcl_PathType type = Tcl_FSGetPathType(absolutePath);
1885
1886            if (type == TCL_PATH_RELATIVE) {
1887                useThisCwd = Tcl_FSGetCwd(interp);
1888
1889                if (useThisCwd == NULL) {
1890                    return NULL;
1891                }
1892
1893                Tcl_DecrRefCount(absolutePath);
1894                absolutePath = Tcl_FSJoinToPath(useThisCwd, 1, &absolutePath);
1895                Tcl_IncrRefCount(absolutePath);
1896
1897                /*
1898                 * We have a refCount on the cwd.
1899                 */
1900#ifdef __WIN32__
1901            } else if (type == TCL_PATH_VOLUME_RELATIVE) {
1902                /*
1903                 * Only Windows has volume-relative paths.
1904                 */
1905
1906                Tcl_DecrRefCount(absolutePath);
1907                absolutePath = TclWinVolumeRelativeNormalize(interp,
1908                        path, &useThisCwd);
1909                if (absolutePath == NULL) {
1910                    return NULL;
1911                }
1912#endif /* __WIN32__ */
1913            }
1914        }
1915
1916        /*
1917         * Already has refCount incremented.
1918         */
1919
1920        fsPathPtr->normPathPtr = TclFSNormalizeAbsolutePath(interp,
1921                absolutePath,
1922                (fsPathPtr->nativePathPtr == NULL ? &clientData : NULL));
1923        if (0 && (clientData != NULL)) {
1924            fsPathPtr->nativePathPtr =
1925                (*fsPathPtr->fsRecPtr->fsPtr->dupInternalRepProc)(clientData);
1926        }
1927
1928        /*
1929         * Check if path is pure normalized (this can only be the case if it
1930         * is an absolute path).
1931         */
1932
1933        if (useThisCwd == NULL) {
1934            if (!strcmp(TclGetString(fsPathPtr->normPathPtr),
1935                    TclGetString(pathPtr))) {
1936                /*
1937                 * The path was already normalized. Get rid of the duplicate.
1938                 */
1939
1940                TclDecrRefCount(fsPathPtr->normPathPtr);
1941
1942                /*
1943                 * We do *not* increment the refCount for this circular
1944                 * reference.
1945                 */
1946
1947                fsPathPtr->normPathPtr = pathPtr;
1948            }
1949        } else {
1950            /*
1951             * We just need to free an object we allocated above for relative
1952             * paths (this was returned by Tcl_FSJoinToPath above), and then
1953             * of course store the cwd.
1954             */
1955
1956            fsPathPtr->cwdPtr = useThisCwd;
1957        }
1958        TclDecrRefCount(absolutePath);
1959    }
1960
1961    return fsPathPtr->normPathPtr;
1962}
1963
1964/*
1965 *---------------------------------------------------------------------------
1966 *
1967 * Tcl_FSGetInternalRep --
1968 *
1969 *      Extract the internal representation of a given path object, in the
1970 *      given filesystem. If the path object belongs to a different
1971 *      filesystem, we return NULL.
1972 *
1973 *      If the internal representation is currently NULL, we attempt to
1974 *      generate it, by calling the filesystem's
1975 *      'Tcl_FSCreateInternalRepProc'.
1976 *
1977 * Results:
1978 *      NULL or a valid internal representation.
1979 *
1980 * Side effects:
1981 *      An attempt may be made to convert the object.
1982 *
1983 *---------------------------------------------------------------------------
1984 */
1985
1986ClientData
1987Tcl_FSGetInternalRep(
1988    Tcl_Obj *pathPtr,
1989    Tcl_Filesystem *fsPtr)
1990{
1991    FsPath *srcFsPathPtr;
1992
1993    if (Tcl_FSConvertToPathType(NULL, pathPtr) != TCL_OK) {
1994        return NULL;
1995    }
1996    srcFsPathPtr = PATHOBJ(pathPtr);
1997
1998    /*
1999     * We will only return the native representation for the caller's
2000     * filesystem. Otherwise we will simply return NULL. This means that there
2001     * must be a unique bi-directional mapping between paths and filesystems,
2002     * and that this mapping will not allow 'remapped' files -- files which
2003     * are in one filesystem but mapped into another. Another way of putting
2004     * this is that 'stacked' filesystems are not allowed. We recognise that
2005     * this is a potentially useful feature for the future.
2006     *
2007     * Even something simple like a 'pass through' filesystem which logs all
2008     * activity and passes the calls onto the native system would be nice, but
2009     * not easily achievable with the current implementation.
2010     */
2011
2012    if (srcFsPathPtr->fsRecPtr == NULL) {
2013        /*
2014         * This only usually happens in wrappers like TclpStat which create a
2015         * string object and pass it to TclpObjStat. Code which calls the
2016         * Tcl_FS.. functions should always have a filesystem already set.
2017         * Whether this code path is legal or not depends on whether we decide
2018         * to allow external code to call the native filesystem directly. It
2019         * is at least safer to allow this sub-optimal routing.
2020         */
2021
2022        Tcl_FSGetFileSystemForPath(pathPtr);
2023
2024        /*
2025         * If we fail through here, then the path is probably not a valid path
2026         * in the filesystsem, and is most likely to be a use of the empty
2027         * path "" via a direct call to one of the objectified interfaces
2028         * (e.g. from the Tcl testsuite).
2029         */
2030
2031        srcFsPathPtr = PATHOBJ(pathPtr);
2032        if (srcFsPathPtr->fsRecPtr == NULL) {
2033            return NULL;
2034        }
2035    }
2036
2037    /*
2038     * There is still one possibility we should consider; if the file belongs
2039     * to a different filesystem, perhaps it is actually linked through to a
2040     * file in our own filesystem which we do care about. The way we can check
2041     * for this is we ask what filesystem this path belongs to.
2042     */
2043
2044    if (fsPtr != srcFsPathPtr->fsRecPtr->fsPtr) {
2045        const Tcl_Filesystem *actualFs = Tcl_FSGetFileSystemForPath(pathPtr);
2046
2047        if (actualFs == fsPtr) {
2048            return Tcl_FSGetInternalRep(pathPtr, fsPtr);
2049        }
2050        return NULL;
2051    }
2052
2053    if (srcFsPathPtr->nativePathPtr == NULL) {
2054        Tcl_FSCreateInternalRepProc *proc;
2055        char *nativePathPtr;
2056
2057        proc = srcFsPathPtr->fsRecPtr->fsPtr->createInternalRepProc;
2058        if (proc == NULL) {
2059            return NULL;
2060        }
2061
2062        nativePathPtr = (*proc)(pathPtr);
2063        srcFsPathPtr = PATHOBJ(pathPtr);
2064        srcFsPathPtr->nativePathPtr = nativePathPtr;
2065    }
2066
2067    return srcFsPathPtr->nativePathPtr;
2068}
2069
2070/*
2071 *---------------------------------------------------------------------------
2072 *
2073 * TclFSEnsureEpochOk --
2074 *
2075 *      This will ensure the pathPtr is up to date and can be converted into a
2076 *      "path" type, and that we are able to generate a complete normalized
2077 *      path which is used to determine the filesystem match.
2078 *
2079 * Results:
2080 *      Standard Tcl return code.
2081 *
2082 * Side effects:
2083 *      An attempt may be made to convert the object.
2084 *
2085 *---------------------------------------------------------------------------
2086 */
2087
2088int
2089TclFSEnsureEpochOk(
2090    Tcl_Obj *pathPtr,
2091    Tcl_Filesystem **fsPtrPtr)
2092{
2093    FsPath *srcFsPathPtr;
2094
2095    if (pathPtr->typePtr != &tclFsPathType) {
2096        return TCL_OK;
2097    }
2098
2099    srcFsPathPtr = PATHOBJ(pathPtr);
2100
2101    /*
2102     * Check if the filesystem has changed in some way since this object's
2103     * internal representation was calculated.
2104     */
2105
2106    if (!TclFSEpochOk(srcFsPathPtr->filesystemEpoch)) {
2107        /*
2108         * We have to discard the stale representation and recalculate it.
2109         */
2110
2111        if (pathPtr->bytes == NULL) {
2112            UpdateStringOfFsPath(pathPtr);
2113        }
2114        FreeFsPathInternalRep(pathPtr);
2115        pathPtr->typePtr = NULL;
2116        if (SetFsPathFromAny(NULL, pathPtr) != TCL_OK) {
2117            return TCL_ERROR;
2118        }
2119        srcFsPathPtr = PATHOBJ(pathPtr);
2120    }
2121
2122    /*
2123     * Check whether the object is already assigned to a fs.
2124     */
2125
2126    if (srcFsPathPtr->fsRecPtr != NULL) {
2127        *fsPtrPtr = srcFsPathPtr->fsRecPtr->fsPtr;
2128    }
2129    return TCL_OK;
2130}
2131
2132/*
2133 *---------------------------------------------------------------------------
2134 *
2135 * TclFSSetPathDetails --
2136 *
2137 *      ???
2138 *
2139 * Results:
2140 *      None
2141 *
2142 * Side effects:
2143 *      ???
2144 *
2145 *---------------------------------------------------------------------------
2146 */
2147
2148void
2149TclFSSetPathDetails(
2150    Tcl_Obj *pathPtr,
2151    FilesystemRecord *fsRecPtr,
2152    ClientData clientData)
2153{
2154    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&tclFsDataKey);
2155    FsPath *srcFsPathPtr;
2156
2157    /*
2158     * Make sure pathPtr is of the correct type.
2159     */
2160
2161    if (pathPtr->typePtr != &tclFsPathType) {
2162        if (SetFsPathFromAny(NULL, pathPtr) != TCL_OK) {
2163            return;
2164        }
2165    }
2166
2167    srcFsPathPtr = PATHOBJ(pathPtr);
2168    srcFsPathPtr->fsRecPtr = fsRecPtr;
2169    srcFsPathPtr->nativePathPtr = clientData;
2170    srcFsPathPtr->filesystemEpoch = tsdPtr->filesystemEpoch;
2171    fsRecPtr->fileRefCount++;
2172}
2173
2174/*
2175 *---------------------------------------------------------------------------
2176 *
2177 * Tcl_FSEqualPaths --
2178 *
2179 *      This function tests whether the two paths given are equal path
2180 *      objects. If either or both is NULL, 0 is always returned.
2181 *
2182 * Results:
2183 *      1 or 0.
2184 *
2185 * Side effects:
2186 *      None.
2187 *
2188 *---------------------------------------------------------------------------
2189 */
2190
2191int
2192Tcl_FSEqualPaths(
2193    Tcl_Obj *firstPtr,
2194    Tcl_Obj *secondPtr)
2195{
2196    char *firstStr, *secondStr;
2197    int firstLen, secondLen, tempErrno;
2198
2199    if (firstPtr == secondPtr) {
2200        return 1;
2201    }
2202
2203    if (firstPtr == NULL || secondPtr == NULL) {
2204        return 0;
2205    }
2206    firstStr = Tcl_GetStringFromObj(firstPtr, &firstLen);
2207    secondStr = Tcl_GetStringFromObj(secondPtr, &secondLen);
2208    if ((firstLen == secondLen) && (strcmp(firstStr, secondStr) == 0)) {
2209        return 1;
2210    }
2211
2212    /*
2213     * Try the most thorough, correct method of comparing fully normalized
2214     * paths.
2215     */
2216
2217    tempErrno = Tcl_GetErrno();
2218    firstPtr = Tcl_FSGetNormalizedPath(NULL, firstPtr);
2219    secondPtr = Tcl_FSGetNormalizedPath(NULL, secondPtr);
2220    Tcl_SetErrno(tempErrno);
2221
2222    if (firstPtr == NULL || secondPtr == NULL) {
2223        return 0;
2224    }
2225
2226    firstStr = Tcl_GetStringFromObj(firstPtr, &firstLen);
2227    secondStr = Tcl_GetStringFromObj(secondPtr, &secondLen);
2228    return (firstLen == secondLen) && (strcmp(firstStr, secondStr) == 0);
2229}
2230
2231/*
2232 *---------------------------------------------------------------------------
2233 *
2234 * SetFsPathFromAny --
2235 *
2236 *      This function tries to convert the given Tcl_Obj to a valid Tcl path
2237 *      type.
2238 *
2239 *      The filename may begin with "~" (to indicate current user's home
2240 *      directory) or "~<user>" (to indicate any user's home directory).
2241 *
2242 * Results:
2243 *      Standard Tcl error code.
2244 *
2245 * Side effects:
2246 *      The old representation may be freed, and new memory allocated.
2247 *
2248 *---------------------------------------------------------------------------
2249 */
2250
2251static int
2252SetFsPathFromAny(
2253    Tcl_Interp *interp,         /* Used for error reporting if not NULL. */
2254    Tcl_Obj *pathPtr)           /* The object to convert. */
2255{
2256    int len;
2257    FsPath *fsPathPtr;
2258    Tcl_Obj *transPtr;
2259    char *name;
2260    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&tclFsDataKey);
2261
2262    if (pathPtr->typePtr == &tclFsPathType) {
2263        return TCL_OK;
2264    }
2265
2266    /*
2267     * First step is to translate the filename. This is similar to
2268     * Tcl_TranslateFilename, but shouldn't convert everything to windows
2269     * backslashes on that platform. The current implementation of this piece
2270     * is a slightly optimised version of the various Tilde/Split/Join stuff
2271     * to avoid multiple split/join operations.
2272     *
2273     * We remove any trailing directory separator.
2274     *
2275     * However, the split/join routines are quite complex, and one has to make
2276     * sure not to break anything on Unix or Win (fCmd.test, fileName.test and
2277     * cmdAH.test exercise most of the code).
2278     */
2279
2280    name = Tcl_GetStringFromObj(pathPtr, &len);
2281
2282    /*
2283     * Handle tilde substitutions, if needed.
2284     */
2285
2286    if (name[0] == '~') {
2287        char *expandedUser;
2288        Tcl_DString temp;
2289        int split;
2290        char separator='/';
2291
2292        split = FindSplitPos(name, separator);
2293        if (split != len) {
2294            /*
2295             * We have multiple pieces '~user/foo/bar...'
2296             */
2297
2298            name[split] = '\0';
2299        }
2300
2301        /*
2302         * Do some tilde substitution.
2303         */
2304
2305        if (name[1] == '\0') {
2306            /*
2307             * We have just '~'
2308             */
2309
2310            const char *dir;
2311            Tcl_DString dirString;
2312
2313            if (split != len) {
2314                name[split] = separator;
2315            }
2316
2317            dir = TclGetEnv("HOME", &dirString);
2318            if (dir == NULL) {
2319                if (interp) {
2320                    Tcl_ResetResult(interp);
2321                    Tcl_AppendResult(interp, "couldn't find HOME environment "
2322                            "variable to expand path", NULL);
2323                }
2324                return TCL_ERROR;
2325            }
2326            Tcl_DStringInit(&temp);
2327            Tcl_JoinPath(1, &dir, &temp);
2328            Tcl_DStringFree(&dirString);
2329        } else {
2330            /*
2331             * We have a user name '~user'
2332             */
2333
2334            Tcl_DStringInit(&temp);
2335            if (TclpGetUserHome(name+1, &temp) == NULL) {
2336                if (interp != NULL) {
2337                    Tcl_ResetResult(interp);
2338                    Tcl_AppendResult(interp, "user \"", name+1,
2339                            "\" doesn't exist", NULL);
2340                }
2341                Tcl_DStringFree(&temp);
2342                if (split != len) {
2343                    name[split] = separator;
2344                }
2345                return TCL_ERROR;
2346            }
2347            if (split != len) {
2348                name[split] = separator;
2349            }
2350        }
2351
2352        expandedUser = Tcl_DStringValue(&temp);
2353        transPtr = Tcl_NewStringObj(expandedUser, Tcl_DStringLength(&temp));
2354
2355        if (split != len) {
2356            /*
2357             * Join up the tilde substitution with the rest.
2358             */
2359
2360            if (name[split+1] == separator) {
2361                /*
2362                 * Somewhat tricky case like ~//foo/bar. Make use of
2363                 * Split/Join machinery to get it right. Assumes all paths
2364                 * beginning with ~ are part of the native filesystem.
2365                 */
2366
2367                int objc;
2368                Tcl_Obj **objv;
2369                Tcl_Obj *parts = TclpNativeSplitPath(pathPtr, NULL);
2370
2371                Tcl_ListObjGetElements(NULL, parts, &objc, &objv);
2372
2373                /*
2374                 * Skip '~'. It's replaced by its expansion.
2375                 */
2376
2377                objc--; objv++;
2378                while (objc--) {
2379                    TclpNativeJoinPath(transPtr, Tcl_GetString(*objv++));
2380                }
2381                TclDecrRefCount(parts);
2382            } else {
2383                /*
2384                 * Simple case. "rest" is relative path. Just join it. The
2385                 * "rest" object will be freed when Tcl_FSJoinToPath returns
2386                 * (unless something else claims a refCount on it).
2387                 */
2388
2389                Tcl_Obj *joined;
2390                Tcl_Obj *rest = Tcl_NewStringObj(name+split+1, -1);
2391
2392                Tcl_IncrRefCount(transPtr);
2393                joined = Tcl_FSJoinToPath(transPtr, 1, &rest);
2394                TclDecrRefCount(transPtr);
2395                transPtr = joined;
2396            }
2397        }
2398        Tcl_DStringFree(&temp);
2399    } else {
2400        transPtr = Tcl_FSJoinToPath(pathPtr, 0, NULL);
2401    }
2402
2403#if defined(__CYGWIN__) && defined(__WIN32__)
2404    {
2405        extern int cygwin_conv_to_win32_path(const char *, char *);
2406        char winbuf[MAX_PATH+1];
2407
2408        /*
2409         * In the Cygwin world, call conv_to_win32_path in order to use the
2410         * mount table to translate the file name into something Windows will
2411         * understand. Take care when converting empty strings!
2412         */
2413
2414        name = Tcl_GetStringFromObj(transPtr, &len);
2415        if (len > 0) {
2416            cygwin_conv_to_win32_path(name, winbuf);
2417            TclWinNoBackslash(winbuf);
2418            Tcl_SetStringObj(transPtr, winbuf, -1);
2419        }
2420    }
2421#endif /* __CYGWIN__ && __WIN32__ */
2422
2423    /*
2424     * Now we have a translated filename in 'transPtr'. This will have forward
2425     * slashes on Windows, and will not contain any ~user sequences.
2426     */
2427
2428    fsPathPtr = (FsPath *) ckalloc(sizeof(FsPath));
2429
2430    fsPathPtr->translatedPathPtr = transPtr;
2431    if (transPtr != pathPtr) {
2432        Tcl_IncrRefCount(fsPathPtr->translatedPathPtr);
2433    }
2434    fsPathPtr->normPathPtr = NULL;
2435    fsPathPtr->cwdPtr = NULL;
2436    fsPathPtr->nativePathPtr = NULL;
2437    fsPathPtr->fsRecPtr = NULL;
2438    fsPathPtr->filesystemEpoch = tsdPtr->filesystemEpoch;
2439
2440    /*
2441     * Free old representation before installing our new one.
2442     */
2443
2444    TclFreeIntRep(pathPtr);
2445    SETPATHOBJ(pathPtr, fsPathPtr);
2446    PATHFLAGS(pathPtr) = 0;
2447    pathPtr->typePtr = &tclFsPathType;
2448
2449    return TCL_OK;
2450}
2451
2452static void
2453FreeFsPathInternalRep(
2454    Tcl_Obj *pathPtr)           /* Path object with internal rep to free. */
2455{
2456    FsPath *fsPathPtr = PATHOBJ(pathPtr);
2457
2458    if (fsPathPtr->translatedPathPtr != NULL) {
2459        if (fsPathPtr->translatedPathPtr != pathPtr) {
2460            TclDecrRefCount(fsPathPtr->translatedPathPtr);
2461        }
2462    }
2463    if (fsPathPtr->normPathPtr != NULL) {
2464        if (fsPathPtr->normPathPtr != pathPtr) {
2465            TclDecrRefCount(fsPathPtr->normPathPtr);
2466        }
2467        fsPathPtr->normPathPtr = NULL;
2468    }
2469    if (fsPathPtr->cwdPtr != NULL) {
2470        TclDecrRefCount(fsPathPtr->cwdPtr);
2471    }
2472    if (fsPathPtr->nativePathPtr != NULL && fsPathPtr->fsRecPtr != NULL) {
2473        Tcl_FSFreeInternalRepProc *freeProc =
2474                fsPathPtr->fsRecPtr->fsPtr->freeInternalRepProc;
2475        if (freeProc != NULL) {
2476            (*freeProc)(fsPathPtr->nativePathPtr);
2477            fsPathPtr->nativePathPtr = NULL;
2478        }
2479    }
2480    if (fsPathPtr->fsRecPtr != NULL) {
2481        fsPathPtr->fsRecPtr->fileRefCount--;
2482        if (fsPathPtr->fsRecPtr->fileRefCount <= 0) {
2483            /*
2484             * It has been unregistered already.
2485             */
2486
2487            ckfree((char *) fsPathPtr->fsRecPtr);
2488        }
2489    }
2490
2491    ckfree((char*) fsPathPtr);
2492}
2493
2494static void
2495DupFsPathInternalRep(
2496    Tcl_Obj *srcPtr,            /* Path obj with internal rep to copy. */
2497    Tcl_Obj *copyPtr)           /* Path obj with internal rep to set. */
2498{
2499    FsPath *srcFsPathPtr = PATHOBJ(srcPtr);
2500    FsPath *copyFsPathPtr = (FsPath *) ckalloc(sizeof(FsPath));
2501
2502    SETPATHOBJ(copyPtr, copyFsPathPtr);
2503
2504    if (srcFsPathPtr->translatedPathPtr != NULL) {
2505        copyFsPathPtr->translatedPathPtr = srcFsPathPtr->translatedPathPtr;
2506        if (copyFsPathPtr->translatedPathPtr != copyPtr) {
2507            Tcl_IncrRefCount(copyFsPathPtr->translatedPathPtr);
2508        }
2509    } else {
2510        copyFsPathPtr->translatedPathPtr = NULL;
2511    }
2512
2513    if (srcFsPathPtr->normPathPtr != NULL) {
2514        copyFsPathPtr->normPathPtr = srcFsPathPtr->normPathPtr;
2515        if (copyFsPathPtr->normPathPtr != copyPtr) {
2516            Tcl_IncrRefCount(copyFsPathPtr->normPathPtr);
2517        }
2518    } else {
2519        copyFsPathPtr->normPathPtr = NULL;
2520    }
2521
2522    if (srcFsPathPtr->cwdPtr != NULL) {
2523        copyFsPathPtr->cwdPtr = srcFsPathPtr->cwdPtr;
2524        Tcl_IncrRefCount(copyFsPathPtr->cwdPtr);
2525    } else {
2526        copyFsPathPtr->cwdPtr = NULL;
2527    }
2528
2529    copyFsPathPtr->flags = srcFsPathPtr->flags;
2530
2531    if (srcFsPathPtr->fsRecPtr != NULL
2532            && srcFsPathPtr->nativePathPtr != NULL) {
2533        Tcl_FSDupInternalRepProc *dupProc =
2534                srcFsPathPtr->fsRecPtr->fsPtr->dupInternalRepProc;
2535        if (dupProc != NULL) {
2536            copyFsPathPtr->nativePathPtr =
2537                    (*dupProc)(srcFsPathPtr->nativePathPtr);
2538        } else {
2539            copyFsPathPtr->nativePathPtr = NULL;
2540        }
2541    } else {
2542        copyFsPathPtr->nativePathPtr = NULL;
2543    }
2544    copyFsPathPtr->fsRecPtr = srcFsPathPtr->fsRecPtr;
2545    copyFsPathPtr->filesystemEpoch = srcFsPathPtr->filesystemEpoch;
2546    if (copyFsPathPtr->fsRecPtr != NULL) {
2547        copyFsPathPtr->fsRecPtr->fileRefCount++;
2548    }
2549
2550    copyPtr->typePtr = &tclFsPathType;
2551}
2552
2553/*
2554 *---------------------------------------------------------------------------
2555 *
2556 * UpdateStringOfFsPath --
2557 *
2558 *      Gives an object a valid string rep.
2559 *
2560 * Results:
2561 *      None.
2562 *
2563 * Side effects:
2564 *      Memory may be allocated.
2565 *
2566 *---------------------------------------------------------------------------
2567 */
2568
2569static void
2570UpdateStringOfFsPath(
2571    register Tcl_Obj *pathPtr)  /* path obj with string rep to update. */
2572{
2573    FsPath *fsPathPtr = PATHOBJ(pathPtr);
2574    const char *cwdStr;
2575    int cwdLen;
2576    Tcl_Obj *copy;
2577
2578    if (PATHFLAGS(pathPtr) == 0 || fsPathPtr->cwdPtr == NULL) {
2579        Tcl_Panic("Called UpdateStringOfFsPath with invalid object");
2580    }
2581
2582    copy = Tcl_DuplicateObj(fsPathPtr->cwdPtr);
2583    Tcl_IncrRefCount(copy);
2584
2585    cwdStr = Tcl_GetStringFromObj(copy, &cwdLen);
2586
2587    /*
2588     * Should we perhaps use 'Tcl_FSPathSeparator'? But then what about the
2589     * Windows special case? Perhaps we should just check if cwd is a root
2590     * volume. We should never get cwdLen == 0 in this code path.
2591     */
2592
2593    switch (tclPlatform) {
2594    case TCL_PLATFORM_UNIX:
2595        if (cwdStr[cwdLen-1] != '/') {
2596            Tcl_AppendToObj(copy, "/", 1);
2597            cwdLen++;
2598        }
2599        break;
2600
2601    case TCL_PLATFORM_WINDOWS:
2602        /*
2603         * We need the extra 'cwdLen != 2', and ':' checks because a volume
2604         * relative path doesn't get a '/'. For example 'glob C:*cat*.exe'
2605         * will return 'C:cat32.exe'
2606         */
2607
2608        if (cwdStr[cwdLen-1] != '/' && cwdStr[cwdLen-1] != '\\') {
2609            if (cwdLen != 2 || cwdStr[1] != ':') {
2610                Tcl_AppendToObj(copy, "/", 1);
2611                cwdLen++;
2612            }
2613        }
2614        break;
2615    }
2616
2617    Tcl_AppendObjToObj(copy, fsPathPtr->normPathPtr);
2618    pathPtr->bytes = Tcl_GetStringFromObj(copy, &cwdLen);
2619    pathPtr->length = cwdLen;
2620    copy->bytes = tclEmptyStringRep;
2621    copy->length = 0;
2622    TclDecrRefCount(copy);
2623}
2624
2625/*
2626 *---------------------------------------------------------------------------
2627 *
2628 * TclNativePathInFilesystem --
2629 *
2630 *      Any path object is acceptable to the native filesystem, by default (we
2631 *      will throw errors when illegal paths are actually tried to be used).
2632 *
2633 *      However, this behavior means the native filesystem must be the last
2634 *      filesystem in the lookup list (otherwise it will claim all files
2635 *      belong to it, and other filesystems will never get a look in).
2636 *
2637 * Results:
2638 *      TCL_OK, to indicate 'yes', -1 to indicate no.
2639 *
2640 * Side effects:
2641 *      None.
2642 *
2643 *---------------------------------------------------------------------------
2644 */
2645
2646int
2647TclNativePathInFilesystem(
2648    Tcl_Obj *pathPtr,
2649    ClientData *clientDataPtr)
2650{
2651    /*
2652     * A special case is required to handle the empty path "". This is a valid
2653     * path (i.e. the user should be able to do 'file exists ""' without
2654     * throwing an error), but equally the path doesn't exist. Those are the
2655     * semantics of Tcl (at present anyway), so we have to abide by them here.
2656     */
2657
2658    if (pathPtr->typePtr == &tclFsPathType) {
2659        if (pathPtr->bytes != NULL && pathPtr->bytes[0] == '\0') {
2660            /*
2661             * We reject the empty path "".
2662             */
2663
2664            return -1;
2665        }
2666
2667        /*
2668         * Otherwise there is no way this path can be empty.
2669         */
2670    } else {
2671        /*
2672         * It is somewhat unusual to reach this code path without the object
2673         * being of tclFsPathType. However, we do our best to deal with the
2674         * situation.
2675         */
2676
2677        int len;
2678
2679        Tcl_GetStringFromObj(pathPtr, &len);
2680        if (len == 0) {
2681            /*
2682             * We reject the empty path "".
2683             */
2684
2685            return -1;
2686        }
2687    }
2688
2689    /*
2690     * Path is of correct type, or is of non-zero length, so we accept it.
2691     */
2692
2693    return TCL_OK;
2694}
2695
2696/*
2697 * Local Variables:
2698 * mode: c
2699 * c-basic-offset: 4
2700 * fill-column: 78
2701 * End:
2702 */
Note: See TracBrowser for help on using the repository browser.