Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

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

added tcl to libs

File size: 135.8 KB
Line 
1/*
2 * tclIOUtil.c --
3 *
4 *      This file contains the implementation of Tcl's generic filesystem
5 *      code, which supports a pluggable filesystem architecture allowing both
6 *      platform specific filesystems and 'virtual filesystems'. All
7 *      filesystem access should go through the functions defined in this
8 *      file. Most of this code was contributed by Vince Darley.
9 *
10 *      Parts of this file are based on code contributed by Karl Lehenbauer,
11 *      Mark Diekhans and Peter da Silva.
12 *
13 * Copyright (c) 1991-1994 The Regents of the University of California.
14 * Copyright (c) 1994-1997 Sun Microsystems, Inc.
15 * Copyright (c) 2001-2004 Vincent Darley.
16 *
17 * See the file "license.terms" for information on usage and redistribution of
18 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
19 *
20 * RCS: @(#) $Id: tclIOUtil.c,v 1.151 2008/02/27 03:35:49 jenglish Exp $
21 */
22
23#include "tclInt.h"
24#ifdef __WIN32__
25#   include "tclWinInt.h"
26#endif
27#include "tclFileSystem.h"
28
29/*
30 * Prototypes for functions defined later in this file.
31 */
32
33static FilesystemRecord*FsGetFirstFilesystem(void);
34static void             FsThrExitProc(ClientData cd);
35static Tcl_Obj *        FsListMounts(Tcl_Obj *pathPtr, const char *pattern);
36static void             FsAddMountsToGlobResult(Tcl_Obj *resultPtr,
37                            Tcl_Obj *pathPtr, const char *pattern,
38                            Tcl_GlobTypeData *types);
39static void             FsUpdateCwd(Tcl_Obj *cwdObj, ClientData clientData);
40
41#ifdef TCL_THREADS
42static void             FsRecacheFilesystemList(void);
43#endif
44
45/*
46 * These form part of the native filesystem support. They are needed here
47 * because we have a few native filesystem functions (which are the same for
48 * win/unix) in this file. There is no need to place them in tclInt.h, because
49 * they are not (and should not be) used anywhere else.
50 */
51
52MODULE_SCOPE const char *               tclpFileAttrStrings[];
53MODULE_SCOPE const TclFileAttrProcs     tclpFileAttrProcs[];
54
55/*
56 * The following functions are obsolete string based APIs, and should be
57 * removed in a future release (Tcl 9 would be a good time).
58 */
59
60
61/* Obsolete */
62int
63Tcl_Stat(
64    const char *path,           /* Path of file to stat (in current CP). */
65    struct stat *oldStyleBuf)   /* Filled with results of stat call. */
66{
67    int ret;
68    Tcl_StatBuf buf;
69    Tcl_Obj *pathPtr = Tcl_NewStringObj(path,-1);
70
71#ifndef TCL_WIDE_INT_IS_LONG
72    Tcl_WideInt tmp1, tmp2;
73#ifdef HAVE_ST_BLOCKS
74    Tcl_WideInt tmp3;
75#endif
76#endif
77
78    Tcl_IncrRefCount(pathPtr);
79    ret = Tcl_FSStat(pathPtr, &buf);
80    Tcl_DecrRefCount(pathPtr);
81    if (ret != -1) {
82#ifndef TCL_WIDE_INT_IS_LONG
83# define OUT_OF_RANGE(x) \
84        (((Tcl_WideInt)(x)) < Tcl_LongAsWide(LONG_MIN) || \
85         ((Tcl_WideInt)(x)) > Tcl_LongAsWide(LONG_MAX))
86# define OUT_OF_URANGE(x) \
87        (((Tcl_WideUInt)(x)) > ((Tcl_WideUInt)ULONG_MAX))
88
89        /*
90         * Perform the result-buffer overflow check manually.
91         *
92         * Note that ino_t/ino64_t is unsigned...
93         *
94         * Workaround gcc warning of "comparison is always false due to limited range of
95         * data type" by assigning to tmp var of type Tcl_WideInt.
96         */
97
98        tmp1 = (Tcl_WideInt) buf.st_ino;
99        tmp2 = (Tcl_WideInt) buf.st_size;
100#ifdef HAVE_ST_BLOCKS
101        tmp3 = (Tcl_WideInt) buf.st_blocks;
102#endif
103
104        if (OUT_OF_URANGE(tmp1) || OUT_OF_RANGE(tmp2)
105#ifdef HAVE_ST_BLOCKS
106                || OUT_OF_RANGE(tmp3)
107#endif
108            ) {
109#ifdef EFBIG
110            errno = EFBIG;
111#else
112#  ifdef EOVERFLOW
113            errno = EOVERFLOW;
114#  else
115#    error "What status should be returned for file size out of range?"
116#  endif
117#endif
118            return -1;
119        }
120
121#   undef OUT_OF_RANGE
122#   undef OUT_OF_URANGE
123#endif /* !TCL_WIDE_INT_IS_LONG */
124
125        /*
126         * Copy across all supported fields, with possible type coercions on
127         * those fields that change between the normal and lf64 versions of
128         * the stat structure (on Solaris at least). This is slow when the
129         * structure sizes coincide, but that's what you get for using an
130         * obsolete interface.
131         */
132
133        oldStyleBuf->st_mode    = buf.st_mode;
134        oldStyleBuf->st_ino     = (ino_t) buf.st_ino;
135        oldStyleBuf->st_dev     = buf.st_dev;
136        oldStyleBuf->st_rdev    = buf.st_rdev;
137        oldStyleBuf->st_nlink   = buf.st_nlink;
138        oldStyleBuf->st_uid     = buf.st_uid;
139        oldStyleBuf->st_gid     = buf.st_gid;
140        oldStyleBuf->st_size    = (off_t) buf.st_size;
141        oldStyleBuf->st_atime   = buf.st_atime;
142        oldStyleBuf->st_mtime   = buf.st_mtime;
143        oldStyleBuf->st_ctime   = buf.st_ctime;
144#ifdef HAVE_ST_BLOCKS
145        oldStyleBuf->st_blksize = buf.st_blksize;
146        oldStyleBuf->st_blocks  = (blkcnt_t) buf.st_blocks;
147#endif
148    }
149    return ret;
150}
151
152/* Obsolete */
153int
154Tcl_Access(
155    const char *path,           /* Path of file to access (in current CP). */
156    int mode)                   /* Permission setting. */
157{
158    int ret;
159    Tcl_Obj *pathPtr = Tcl_NewStringObj(path,-1);
160
161    Tcl_IncrRefCount(pathPtr);
162    ret = Tcl_FSAccess(pathPtr,mode);
163    Tcl_DecrRefCount(pathPtr);
164
165    return ret;
166}
167
168/* Obsolete */
169Tcl_Channel
170Tcl_OpenFileChannel(
171    Tcl_Interp *interp,         /* Interpreter for error reporting; can be
172                                 * NULL. */
173    const char *path,           /* Name of file to open. */
174    const char *modeString,     /* A list of POSIX open modes or a string such
175                                 * as "rw". */
176    int permissions)            /* If the open involves creating a file, with
177                                 * what modes to create it? */
178{
179    Tcl_Channel ret;
180    Tcl_Obj *pathPtr = Tcl_NewStringObj(path,-1);
181
182    Tcl_IncrRefCount(pathPtr);
183    ret = Tcl_FSOpenFileChannel(interp, pathPtr, modeString, permissions);
184    Tcl_DecrRefCount(pathPtr);
185
186    return ret;
187}
188
189/* Obsolete */
190int
191Tcl_Chdir(
192    const char *dirName)
193{
194    int ret;
195    Tcl_Obj *pathPtr = Tcl_NewStringObj(dirName,-1);
196    Tcl_IncrRefCount(pathPtr);
197    ret = Tcl_FSChdir(pathPtr);
198    Tcl_DecrRefCount(pathPtr);
199    return ret;
200}
201
202/* Obsolete */
203char *
204Tcl_GetCwd(
205    Tcl_Interp *interp,
206    Tcl_DString *cwdPtr)
207{
208    Tcl_Obj *cwd;
209    cwd = Tcl_FSGetCwd(interp);
210    if (cwd == NULL) {
211        return NULL;
212    } else {
213        Tcl_DStringInit(cwdPtr);
214        Tcl_DStringAppend(cwdPtr, Tcl_GetString(cwd), -1);
215        Tcl_DecrRefCount(cwd);
216        return Tcl_DStringValue(cwdPtr);
217    }
218}
219
220/* Obsolete */
221int
222Tcl_EvalFile(
223    Tcl_Interp *interp,         /* Interpreter in which to process file. */
224    const char *fileName)       /* Name of file to process. Tilde-substitution
225                                 * will be performed on this name. */
226{
227    int ret;
228    Tcl_Obj *pathPtr = Tcl_NewStringObj(fileName,-1);
229    Tcl_IncrRefCount(pathPtr);
230    ret = Tcl_FSEvalFile(interp, pathPtr);
231    Tcl_DecrRefCount(pathPtr);
232    return ret;
233}
234
235/*
236 * The 3 hooks for Stat, Access and OpenFileChannel are obsolete. The
237 * complete, general hooked filesystem APIs should be used instead. This
238 * define decides whether to include the obsolete hooks and related code. If
239 * these are removed, we'll also want to remove them from stubs/tclInt. The
240 * only known users of these APIs are prowrap and mktclapp. New
241 * code/extensions should not use them, since they do not provide as full
242 * support as the full filesystem API.
243 *
244 * As soon as prowrap and mktclapp are updated to use the full filesystem
245 * support, I suggest all these hooks are removed.
246 */
247
248#undef USE_OBSOLETE_FS_HOOKS
249
250#ifdef USE_OBSOLETE_FS_HOOKS
251
252/*
253 * The following typedef declarations allow for hooking into the chain of
254 * functions maintained for 'Tcl_Stat(...)', 'Tcl_Access(...)' &
255 * 'Tcl_OpenFileChannel(...)'. Basically for each hookable function a linked
256 * list is defined.
257 */
258
259typedef struct StatProc {
260    TclStatProc_ *proc;         /* Function to process a 'stat()' call */
261    struct StatProc *nextPtr;   /* The next 'stat()' function to call */
262} StatProc;
263
264typedef struct AccessProc {
265    TclAccessProc_ *proc;       /* Function to process a 'access()' call */
266    struct AccessProc *nextPtr; /* The next 'access()' function to call */
267} AccessProc;
268
269typedef struct OpenFileChannelProc {
270    TclOpenFileChannelProc_ *proc;
271                                /* Function to process a
272                                 * 'Tcl_OpenFileChannel()' call */
273    struct OpenFileChannelProc *nextPtr;
274                                /* The next 'Tcl_OpenFileChannel()' function
275                                 * to call */
276} OpenFileChannelProc;
277
278/*
279 * For each type of (obsolete) hookable function, a static node is declared to
280 * hold the function pointer for the "built-in" routine (e.g. 'TclpStat(...)')
281 * and the respective list is initialized as a pointer to that node.
282 *
283 * The "delete" functions (e.g. 'TclStatDeleteProc(...)') ensure that these
284 * statically declared list entry cannot be inadvertently removed.
285 *
286 * This method avoids the need to call any sort of "initialization" function.
287 *
288 * All three lists are protected by a global obsoleteFsHookMutex.
289 */
290
291static StatProc *statProcList = NULL;
292static AccessProc *accessProcList = NULL;
293static OpenFileChannelProc *openFileChannelProcList = NULL;
294
295TCL_DECLARE_MUTEX(obsoleteFsHookMutex)
296
297#endif /* USE_OBSOLETE_FS_HOOKS */
298
299/*
300 * Declare the native filesystem support. These functions should be considered
301 * private to Tcl, and should really not be called directly by any code other
302 * than this file (i.e. neither by Tcl's core nor by extensions). Similarly,
303 * the old string-based Tclp... native filesystem functions should not be
304 * called.
305 *
306 * The correct API to use now is the Tcl_FS... set of functions, which ensure
307 * correct and complete virtual filesystem support.
308 *
309 * We cannot make all of these static, since some of them are implemented in
310 * the platform-specific directories.
311 */
312
313static Tcl_FSFilesystemSeparatorProc NativeFilesystemSeparator;
314static Tcl_FSFreeInternalRepProc NativeFreeInternalRep;
315static Tcl_FSFileAttrStringsProc NativeFileAttrStrings;
316static Tcl_FSFileAttrsGetProc   NativeFileAttrsGet;
317static Tcl_FSFileAttrsSetProc   NativeFileAttrsSet;
318
319/*
320 * The only reason these functions are not static is that they are either
321 * called by code in the native (win/unix) directories or they are actually
322 * implemented in those directories. They should simply not be called by code
323 * outside Tcl's native filesystem core i.e. they should be considered
324 * 'static' to Tcl's filesystem code (if we ever built the native filesystem
325 * support into a separate code library, this could actually be enforced).
326 */
327
328Tcl_FSFilesystemPathTypeProc    TclpFilesystemPathType;
329Tcl_FSInternalToNormalizedProc  TclpNativeToNormalized;
330Tcl_FSStatProc                  TclpObjStat;
331Tcl_FSAccessProc                TclpObjAccess;
332Tcl_FSMatchInDirectoryProc      TclpMatchInDirectory;
333Tcl_FSChdirProc                 TclpObjChdir;
334Tcl_FSLstatProc                 TclpObjLstat;
335Tcl_FSCopyFileProc              TclpObjCopyFile;
336Tcl_FSDeleteFileProc            TclpObjDeleteFile;
337Tcl_FSRenameFileProc            TclpObjRenameFile;
338Tcl_FSCreateDirectoryProc       TclpObjCreateDirectory;
339Tcl_FSCopyDirectoryProc         TclpObjCopyDirectory;
340Tcl_FSRemoveDirectoryProc       TclpObjRemoveDirectory;
341Tcl_FSUnloadFileProc            TclpUnloadFile;
342Tcl_FSLinkProc                  TclpObjLink;
343Tcl_FSListVolumesProc           TclpObjListVolumes;
344
345/*
346 * Define the native filesystem dispatch table. If necessary, it is ok to make
347 * this non-static, but it should only be accessed by the functions actually
348 * listed within it (or perhaps other helper functions of them). Anything
349 * which is not part of this 'native filesystem implementation' should not be
350 * delving inside here!
351 */
352
353Tcl_Filesystem tclNativeFilesystem = {
354    "native",
355    sizeof(Tcl_Filesystem),
356    TCL_FILESYSTEM_VERSION_2,
357    &TclNativePathInFilesystem,
358    &TclNativeDupInternalRep,
359    &NativeFreeInternalRep,
360    &TclpNativeToNormalized,
361    &TclNativeCreateNativeRep,
362    &TclpObjNormalizePath,
363    &TclpFilesystemPathType,
364    &NativeFilesystemSeparator,
365    &TclpObjStat,
366    &TclpObjAccess,
367    &TclpOpenFileChannel,
368    &TclpMatchInDirectory,
369    &TclpUtime,
370#ifndef S_IFLNK
371    NULL,
372#else
373    &TclpObjLink,
374#endif /* S_IFLNK */
375    &TclpObjListVolumes,
376    &NativeFileAttrStrings,
377    &NativeFileAttrsGet,
378    &NativeFileAttrsSet,
379    &TclpObjCreateDirectory,
380    &TclpObjRemoveDirectory,
381    &TclpObjDeleteFile,
382    &TclpObjCopyFile,
383    &TclpObjRenameFile,
384    &TclpObjCopyDirectory,
385    &TclpObjLstat,
386    &TclpDlopen,
387    /* Needs a cast since we're using version_2 */
388    (Tcl_FSGetCwdProc *) &TclpGetNativeCwd,
389    &TclpObjChdir
390};
391
392/*
393 * Define the tail of the linked list. Note that for unconventional uses of
394 * Tcl without a native filesystem, we may in the future wish to modify the
395 * current approach of hard-coding the native filesystem in the lookup list
396 * 'filesystemList' below.
397 *
398 * We initialize the record so that it thinks one file uses it. This means it
399 * will never be freed.
400 */
401
402static FilesystemRecord nativeFilesystemRecord = {
403    NULL,
404    &tclNativeFilesystem,
405    1,
406    NULL
407};
408
409/*
410 * This is incremented each time we modify the linked list of filesystems. Any
411 * time it changes, all cached filesystem representations are suspect and must
412 * be freed. For multithreading builds, change of the filesystem epoch will
413 * trigger cache cleanup in all threads.
414 */
415
416static int theFilesystemEpoch = 0;
417
418/*
419 * Stores the linked list of filesystems. A 1:1 copy of this list is also
420 * maintained in the TSD for each thread. This is to avoid synchronization
421 * issues.
422 */
423
424static FilesystemRecord *filesystemList = &nativeFilesystemRecord;
425TCL_DECLARE_MUTEX(filesystemMutex)
426
427/*
428 * Used to implement Tcl_FSGetCwd in a file-system independent way.
429 */
430
431static Tcl_Obj* cwdPathPtr = NULL;
432static int cwdPathEpoch = 0;
433static ClientData cwdClientData = NULL;
434TCL_DECLARE_MUTEX(cwdMutex)
435
436Tcl_ThreadDataKey tclFsDataKey;
437
438/*
439 * Declare fallback support function and information for Tcl_FSLoadFile
440 */
441
442static Tcl_FSUnloadFileProc     FSUnloadTempFile;
443
444/*
445 * One of these structures is used each time we successfully load a file from
446 * a file system by way of making a temporary copy of the file on the native
447 * filesystem. We need to store both the actual unloadProc/clientData
448 * combination which was used, and the original and modified filenames, so
449 * that we can correctly undo the entire operation when we want to unload the
450 * code.
451 */
452
453typedef struct FsDivertLoad {
454    Tcl_LoadHandle loadHandle;
455    Tcl_FSUnloadFileProc *unloadProcPtr;
456    Tcl_Obj *divertedFile;
457    const Tcl_Filesystem *divertedFilesystem;
458    ClientData divertedFileNativeRep;
459} FsDivertLoad;
460
461/*
462 * Now move on to the basic filesystem implementation
463 */
464
465static void
466FsThrExitProc(
467    ClientData cd)
468{
469    ThreadSpecificData *tsdPtr = (ThreadSpecificData *) cd;
470    FilesystemRecord *fsRecPtr = NULL, *tmpFsRecPtr = NULL;
471
472    /*
473     * Trash the cwd copy.
474     */
475
476    if (tsdPtr->cwdPathPtr != NULL) {
477        Tcl_DecrRefCount(tsdPtr->cwdPathPtr);
478        tsdPtr->cwdPathPtr = NULL;
479    }
480    if (tsdPtr->cwdClientData != NULL) {
481        NativeFreeInternalRep(tsdPtr->cwdClientData);
482    }
483
484    /*
485     * Trash the filesystems cache.
486     */
487
488    fsRecPtr = tsdPtr->filesystemList;
489    while (fsRecPtr != NULL) {
490        tmpFsRecPtr = fsRecPtr->nextPtr;
491        if (--fsRecPtr->fileRefCount <= 0) {
492            ckfree((char *)fsRecPtr);
493        }
494        fsRecPtr = tmpFsRecPtr;
495    }
496    tsdPtr->initialized = 0;
497}
498
499int
500TclFSCwdIsNative(void)
501{
502    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&tclFsDataKey);
503
504    if (tsdPtr->cwdClientData != NULL) {
505        return 1;
506    } else {
507        return 0;
508    }
509}
510
511/*
512 *----------------------------------------------------------------------
513 *
514 * TclFSCwdPointerEquals --
515 *
516 *      Check whether the current working directory is equal to the path
517 *      given.
518 *
519 * Results:
520 *      1 (equal) or 0 (un-equal) as appropriate.
521 *
522 * Side effects:
523 *      If the paths are equal, but are not the same object, this method will
524 *      modify the given pathPtrPtr to refer to the same object. In this case
525 *      the object pointed to by pathPtrPtr will have its refCount
526 *      decremented, and it will be adjusted to point to the cwd (with a new
527 *      refCount).
528 *
529 *----------------------------------------------------------------------
530 */
531
532int
533TclFSCwdPointerEquals(
534    Tcl_Obj** pathPtrPtr)
535{
536    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&tclFsDataKey);
537
538    Tcl_MutexLock(&cwdMutex);
539    if (tsdPtr->cwdPathPtr == NULL
540            || tsdPtr->cwdPathEpoch != cwdPathEpoch) {
541        if (tsdPtr->cwdPathPtr != NULL) {
542            Tcl_DecrRefCount(tsdPtr->cwdPathPtr);
543        }
544        if (tsdPtr->cwdClientData != NULL) {
545            NativeFreeInternalRep(tsdPtr->cwdClientData);
546        }
547        if (cwdPathPtr == NULL) {
548            tsdPtr->cwdPathPtr = NULL;
549        } else {
550            tsdPtr->cwdPathPtr = Tcl_DuplicateObj(cwdPathPtr);
551            Tcl_IncrRefCount(tsdPtr->cwdPathPtr);
552        }
553        if (cwdClientData == NULL) {
554            tsdPtr->cwdClientData = NULL;
555        } else {
556            tsdPtr->cwdClientData = TclNativeDupInternalRep(cwdClientData);
557        }
558        tsdPtr->cwdPathEpoch = cwdPathEpoch;
559    }
560    Tcl_MutexUnlock(&cwdMutex);
561
562    if (tsdPtr->initialized == 0) {
563        Tcl_CreateThreadExitHandler(FsThrExitProc, (ClientData) tsdPtr);
564        tsdPtr->initialized = 1;
565    }
566
567    if (pathPtrPtr == NULL) {
568        return (tsdPtr->cwdPathPtr == NULL);
569    }
570
571    if (tsdPtr->cwdPathPtr == *pathPtrPtr) {
572        return 1;
573    } else {
574        int len1, len2;
575        const char *str1, *str2;
576
577        str1 = Tcl_GetStringFromObj(tsdPtr->cwdPathPtr, &len1);
578        str2 = Tcl_GetStringFromObj(*pathPtrPtr, &len2);
579        if (len1 == len2 && !strcmp(str1,str2)) {
580            /*
581             * They are equal, but different objects. Update so they will be
582             * the same object in the future.
583             */
584
585            Tcl_DecrRefCount(*pathPtrPtr);
586            *pathPtrPtr = tsdPtr->cwdPathPtr;
587            Tcl_IncrRefCount(*pathPtrPtr);
588            return 1;
589        } else {
590            return 0;
591        }
592    }
593}
594
595#ifdef TCL_THREADS
596static void
597FsRecacheFilesystemList(void)
598{
599    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&tclFsDataKey);
600    FilesystemRecord *fsRecPtr, *tmpFsRecPtr = NULL;
601
602    /*
603     * Trash the current cache.
604     */
605
606    fsRecPtr = tsdPtr->filesystemList;
607    while (fsRecPtr != NULL) {
608        tmpFsRecPtr = fsRecPtr->nextPtr;
609        if (--fsRecPtr->fileRefCount <= 0) {
610            ckfree((char *)fsRecPtr);
611        }
612        fsRecPtr = tmpFsRecPtr;
613    }
614    tsdPtr->filesystemList = NULL;
615
616    /*
617     * Code below operates on shared data. We are already called under mutex
618     * lock so we can safely proceed.
619     *
620     * Locate tail of the global filesystem list.
621     */
622
623    fsRecPtr = filesystemList;
624    while (fsRecPtr != NULL) {
625        tmpFsRecPtr = fsRecPtr;
626        fsRecPtr = fsRecPtr->nextPtr;
627    }
628
629    /*
630     * Refill the cache honouring the order.
631     */
632
633    fsRecPtr = tmpFsRecPtr;
634    while (fsRecPtr != NULL) {
635        tmpFsRecPtr = (FilesystemRecord *) ckalloc(sizeof(FilesystemRecord));
636        *tmpFsRecPtr = *fsRecPtr;
637        tmpFsRecPtr->nextPtr = tsdPtr->filesystemList;
638        tmpFsRecPtr->prevPtr = NULL;
639        if (tsdPtr->filesystemList) {
640            tsdPtr->filesystemList->prevPtr = tmpFsRecPtr;
641        }
642        tsdPtr->filesystemList = tmpFsRecPtr;
643        fsRecPtr = fsRecPtr->prevPtr;
644    }
645
646    /*
647     * Make sure the above gets released on thread exit.
648     */
649
650    if (tsdPtr->initialized == 0) {
651        Tcl_CreateThreadExitHandler(FsThrExitProc, (ClientData) tsdPtr);
652        tsdPtr->initialized = 1;
653    }
654}
655#endif /* TCL_THREADS */
656
657static FilesystemRecord *
658FsGetFirstFilesystem(void)
659{
660    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&tclFsDataKey);
661    FilesystemRecord *fsRecPtr;
662#ifndef TCL_THREADS
663    tsdPtr->filesystemEpoch = theFilesystemEpoch;
664    fsRecPtr = filesystemList;
665#else
666    Tcl_MutexLock(&filesystemMutex);
667    if (tsdPtr->filesystemList == NULL
668            || (tsdPtr->filesystemEpoch != theFilesystemEpoch)) {
669        FsRecacheFilesystemList();
670        tsdPtr->filesystemEpoch = theFilesystemEpoch;
671    }
672    Tcl_MutexUnlock(&filesystemMutex);
673    fsRecPtr = tsdPtr->filesystemList;
674#endif
675    return fsRecPtr;
676}
677
678/*
679 * The epoch can be changed both by filesystems being added or removed and by
680 * env(HOME) changing.
681 */
682
683int
684TclFSEpochOk(
685    int filesystemEpoch)
686{
687    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&tclFsDataKey);
688    (void) FsGetFirstFilesystem();
689    return (filesystemEpoch == tsdPtr->filesystemEpoch);
690}
691
692/*
693 * If non-NULL, clientData is owned by us and must be freed later.
694 */
695
696static void
697FsUpdateCwd(
698    Tcl_Obj *cwdObj,
699    ClientData clientData)
700{
701    int len;
702    char *str = NULL;
703    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&tclFsDataKey);
704
705    if (cwdObj != NULL) {
706        str = Tcl_GetStringFromObj(cwdObj, &len);
707    }
708
709    Tcl_MutexLock(&cwdMutex);
710    if (cwdPathPtr != NULL) {
711        Tcl_DecrRefCount(cwdPathPtr);
712    }
713    if (cwdClientData != NULL) {
714        NativeFreeInternalRep(cwdClientData);
715    }
716
717    if (cwdObj == NULL) {
718        cwdPathPtr = NULL;
719        cwdClientData = NULL;
720    } else {
721        /*
722         * This must be stored as string obj!
723         */
724
725        cwdPathPtr = Tcl_NewStringObj(str, len);
726        Tcl_IncrRefCount(cwdPathPtr);
727        cwdClientData = TclNativeDupInternalRep(clientData);
728    }
729
730    cwdPathEpoch++;
731    tsdPtr->cwdPathEpoch = cwdPathEpoch;
732    Tcl_MutexUnlock(&cwdMutex);
733
734    if (tsdPtr->cwdPathPtr) {
735        Tcl_DecrRefCount(tsdPtr->cwdPathPtr);
736    }
737    if (tsdPtr->cwdClientData) {
738        NativeFreeInternalRep(tsdPtr->cwdClientData);
739    }
740
741    if (cwdObj == NULL) {
742        tsdPtr->cwdPathPtr = NULL;
743        tsdPtr->cwdClientData = NULL;
744    } else {
745        tsdPtr->cwdPathPtr = Tcl_NewStringObj(str, len);
746        tsdPtr->cwdClientData = clientData;
747        Tcl_IncrRefCount(tsdPtr->cwdPathPtr);
748    }
749}
750
751/*
752 *----------------------------------------------------------------------
753 *
754 * TclFinalizeFilesystem --
755 *
756 *      Clean up the filesystem. After this, calls to all Tcl_FS... functions
757 *      will fail.
758 *
759 *      We will later call TclResetFilesystem to restore the FS to a pristine
760 *      state.
761 *
762 * Results:
763 *      None.
764 *
765 * Side effects:
766 *      Frees any memory allocated by the filesystem.
767 *
768 *----------------------------------------------------------------------
769 */
770
771void
772TclFinalizeFilesystem(void)
773{
774    FilesystemRecord *fsRecPtr;
775
776    /*
777     * Assumption that only one thread is active now. Otherwise we would need
778     * to put various mutexes around this code.
779     */
780
781    if (cwdPathPtr != NULL) {
782        Tcl_DecrRefCount(cwdPathPtr);
783        cwdPathPtr = NULL;
784        cwdPathEpoch = 0;
785    }
786    if (cwdClientData != NULL) {
787        NativeFreeInternalRep(cwdClientData);
788        cwdClientData = NULL;
789    }
790
791    /*
792     * Remove all filesystems, freeing any allocated memory that is no longer
793     * needed
794     */
795
796    fsRecPtr = filesystemList;
797    while (fsRecPtr != NULL) {
798        FilesystemRecord *tmpFsRecPtr = fsRecPtr->nextPtr;
799        if (fsRecPtr->fileRefCount <= 0) {
800            /*
801             * The native filesystem is static, so we don't free it.
802             */
803
804            if (fsRecPtr->fsPtr != &tclNativeFilesystem) {
805                ckfree((char *)fsRecPtr);
806            }
807        }
808        fsRecPtr = tmpFsRecPtr;
809    }
810    filesystemList = NULL;
811
812    /*
813     * Now filesystemList is NULL. This means that any attempt to use the
814     * filesystem is likely to fail.
815     */
816
817#ifdef USE_OBSOLETE_FS_HOOKS
818    statProcList = NULL;
819    accessProcList = NULL;
820    openFileChannelProcList = NULL;
821#endif
822#ifdef __WIN32__
823    TclWinEncodingsCleanup();
824#endif
825}
826
827/*
828 *----------------------------------------------------------------------
829 *
830 * TclResetFilesystem --
831 *
832 *      Restore the filesystem to a pristine state.
833 *
834 * Results:
835 *      None.
836 *
837 * Side effects:
838 *      None.
839 *
840 *----------------------------------------------------------------------
841 */
842
843void
844TclResetFilesystem(void)
845{
846    filesystemList = &nativeFilesystemRecord;
847
848    /*
849     * Note, at this point, I believe nativeFilesystemRecord -> fileRefCount
850     * should equal 1 and if not, we should try to track down the cause.
851     */
852
853#ifdef __WIN32__
854    /*
855     * Cleans up the win32 API filesystem proc lookup table. This must happen
856     * very late in finalization so that deleting of copied dlls can occur.
857     */
858
859    TclWinResetInterfaces();
860#endif
861}
862
863/*
864 *----------------------------------------------------------------------
865 *
866 * Tcl_FSRegister --
867 *
868 *      Insert the filesystem function table at the head of the list of
869 *      functions which are used during calls to all file-system operations.
870 *      The filesystem will be added even if it is already in the list. (You
871 *      can use Tcl_FSData to check if it is in the list, provided the
872 *      ClientData used was not NULL).
873 *
874 *      Note that the filesystem handling is head-to-tail of the list. Each
875 *      filesystem is asked in turn whether it can handle a particular
876 *      request, until one of them says 'yes'. At that point no further
877 *      filesystems are asked.
878 *
879 *      In particular this means if you want to add a diagnostic filesystem
880 *      (which simply reports all fs activity), it must be at the head of the
881 *      list: i.e. it must be the last registered.
882 *
883 * Results:
884 *      Normally TCL_OK; TCL_ERROR if memory for a new node in the list could
885 *      not be allocated.
886 *
887 * Side effects:
888 *      Memory allocated and modifies the link list for filesystems.
889 *
890 *----------------------------------------------------------------------
891 */
892
893int
894Tcl_FSRegister(
895    ClientData clientData,      /* Client specific data for this fs */
896    Tcl_Filesystem *fsPtr)      /* The filesystem record for the new fs. */
897{
898    FilesystemRecord *newFilesystemPtr;
899
900    if (fsPtr == NULL) {
901        return TCL_ERROR;
902    }
903
904    newFilesystemPtr = (FilesystemRecord *) ckalloc(sizeof(FilesystemRecord));
905
906    newFilesystemPtr->clientData = clientData;
907    newFilesystemPtr->fsPtr = fsPtr;
908
909    /*
910     * We start with a refCount of 1. If this drops to zero, then anyone is
911     * welcome to ckfree us.
912     */
913
914    newFilesystemPtr->fileRefCount = 1;
915
916    /*
917     * Is this lock and wait strictly speaking necessary? Since any iterators
918     * out there will have grabbed a copy of the head of the list and be
919     * iterating away from that, if we add a new element to the head of the
920     * list, it can't possibly have any effect on any of their loops. In fact
921     * it could be better not to wait, since we are adjusting the filesystem
922     * epoch, any cached representations calculated by existing iterators are
923     * going to have to be thrown away anyway.
924     *
925     * However, since registering and unregistering filesystems is a very rare
926     * action, this is not a very important point.
927     */
928
929    Tcl_MutexLock(&filesystemMutex);
930
931    newFilesystemPtr->nextPtr = filesystemList;
932    newFilesystemPtr->prevPtr = NULL;
933    if (filesystemList) {
934        filesystemList->prevPtr = newFilesystemPtr;
935    }
936    filesystemList = newFilesystemPtr;
937
938    /*
939     * Increment the filesystem epoch counter, since existing paths might
940     * conceivably now belong to different filesystems.
941     */
942
943    theFilesystemEpoch++;
944    Tcl_MutexUnlock(&filesystemMutex);
945
946    return TCL_OK;
947}
948
949/*
950 *----------------------------------------------------------------------
951 *
952 * Tcl_FSUnregister --
953 *
954 *      Remove the passed filesystem from the list of filesystem function
955 *      tables. It also ensures that the built-in (native) filesystem is not
956 *      removable, although we may wish to change that decision in the future
957 *      to allow a smaller Tcl core, in which the native filesystem is not
958 *      used at all (we could, say, initialise Tcl completely over a network
959 *      connection).
960 *
961 * Results:
962 *      TCL_OK if the function pointer was successfully removed, TCL_ERROR
963 *      otherwise.
964 *
965 * Side effects:
966 *      Memory may be deallocated (or will be later, once no "path" objects
967 *      refer to this filesystem), but the list of registered filesystems is
968 *      updated immediately.
969 *
970 *----------------------------------------------------------------------
971 */
972
973int
974Tcl_FSUnregister(
975    Tcl_Filesystem *fsPtr)      /* The filesystem record to remove. */
976{
977    int retVal = TCL_ERROR;
978    FilesystemRecord *fsRecPtr;
979
980    Tcl_MutexLock(&filesystemMutex);
981
982    /*
983     * Traverse the 'filesystemList' looking for the particular node whose
984     * 'fsPtr' member matches 'fsPtr' and remove that one from the list.
985     * Ensure that the "default" node cannot be removed.
986     */
987
988    fsRecPtr = filesystemList;
989    while ((retVal == TCL_ERROR) && (fsRecPtr->fsPtr != &tclNativeFilesystem)) {
990        if (fsRecPtr->fsPtr == fsPtr) {
991            if (fsRecPtr->prevPtr) {
992                fsRecPtr->prevPtr->nextPtr = fsRecPtr->nextPtr;
993            } else {
994                filesystemList = fsRecPtr->nextPtr;
995            }
996            if (fsRecPtr->nextPtr) {
997                fsRecPtr->nextPtr->prevPtr = fsRecPtr->prevPtr;
998            }
999
1000            /*
1001             * Increment the filesystem epoch counter, since existing paths
1002             * might conceivably now belong to different filesystems. This
1003             * should also ensure that paths which have cached the filesystem
1004             * which is about to be deleted do not reference that filesystem
1005             * (which would of course lead to memory exceptions).
1006             */
1007
1008            theFilesystemEpoch++;
1009
1010            fsRecPtr->fileRefCount--;
1011            if (fsRecPtr->fileRefCount <= 0) {
1012                ckfree((char *)fsRecPtr);
1013            }
1014
1015            retVal = TCL_OK;
1016        } else {
1017            fsRecPtr = fsRecPtr->nextPtr;
1018        }
1019    }
1020
1021    Tcl_MutexUnlock(&filesystemMutex);
1022    return retVal;
1023}
1024
1025/*
1026 *----------------------------------------------------------------------
1027 *
1028 * Tcl_FSMatchInDirectory --
1029 *
1030 *      This routine is used by the globbing code to search a directory for
1031 *      all files which match a given pattern. The appropriate function for
1032 *      the filesystem to which pathPtr belongs will be called. If pathPtr
1033 *      does not belong to any filesystem and if it is NULL or the empty
1034 *      string, then we assume the pattern is to be matched in the current
1035 *      working directory. To avoid have the Tcl_FSMatchInDirectoryProc for
1036 *      each filesystem from having to deal with this issue, we create a
1037 *      pathPtr on the fly (equal to the cwd), and then remove it from the
1038 *      results returned. This makes filesystems easy to write, since they can
1039 *      assume the pathPtr passed to them is an ordinary path. In fact this
1040 *      means we could remove such special case handling from Tcl's native
1041 *      filesystems.
1042 *
1043 *      If 'pattern' is NULL, then pathPtr is assumed to be a fully specified
1044 *      path of a single file/directory which must be checked for existence
1045 *      and correct type.
1046 *
1047 * Results:
1048 *
1049 *      The return value is a standard Tcl result indicating whether an error
1050 *      occurred in globbing. Error messages are placed in interp, but good
1051 *      results are placed in the resultPtr given.
1052 *
1053 *      Recursive searches, e.g.
1054 *              glob -dir $dir -join * pkgIndex.tcl
1055 *      which must recurse through each directory matching '*' are handled
1056 *      internally by Tcl, by passing specific flags in a modified 'types'
1057 *      parameter. This means the actual filesystem only ever sees patterns
1058 *      which match in a single directory.
1059 *
1060 * Side effects:
1061 *      The interpreter may have an error message inserted into it.
1062 *
1063 *----------------------------------------------------------------------
1064 */
1065
1066int
1067Tcl_FSMatchInDirectory(
1068    Tcl_Interp *interp,         /* Interpreter to receive error messages, but
1069                                 * may be NULL. */
1070    Tcl_Obj *resultPtr,         /* List object to receive results. */
1071    Tcl_Obj *pathPtr,           /* Contains path to directory to search. */
1072    const char *pattern,        /* Pattern to match against. */
1073    Tcl_GlobTypeData *types)    /* Object containing list of acceptable types.
1074                                 * May be NULL. In particular the directory
1075                                 * flag is very important. */
1076{
1077    const Tcl_Filesystem *fsPtr;
1078    Tcl_Obj *cwd, *tmpResultPtr, **elemsPtr;
1079    int resLength, i, ret = -1;
1080
1081    if (types != NULL && types->type & TCL_GLOB_TYPE_MOUNT) {
1082        /*
1083         * We don't currently allow querying of mounts by external code (a
1084         * valuable future step), so since we're the only function that
1085         * actually knows about mounts, this means we're being called
1086         * recursively by ourself. Return no matches.
1087         */
1088
1089        return TCL_OK;
1090    }
1091
1092    if (pathPtr != NULL) {
1093        fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
1094    } else {
1095        fsPtr = NULL;
1096    }
1097
1098    /*
1099     * Check if we've successfully mapped the path to a filesystem within
1100     * which to search.
1101     */
1102
1103    if (fsPtr != NULL) {
1104        if (fsPtr->matchInDirectoryProc == NULL) {
1105            Tcl_SetErrno(ENOENT);
1106            return -1;
1107        }
1108        ret = (*fsPtr->matchInDirectoryProc)(interp, resultPtr, pathPtr,
1109                pattern, types);
1110        if (ret == TCL_OK && pattern != NULL) {
1111            FsAddMountsToGlobResult(resultPtr, pathPtr, pattern, types);
1112        }
1113        return ret;
1114    }
1115
1116    /*
1117     * If the path isn't empty, we have no idea how to match files in a
1118     * directory which belongs to no known filesystem
1119     */
1120
1121    if (pathPtr != NULL && TclGetString(pathPtr)[0] != '\0') {
1122        Tcl_SetErrno(ENOENT);
1123        return -1;
1124    }
1125
1126    /*
1127     * We have an empty or NULL path. This is defined to mean we must search
1128     * for files within the current 'cwd'. We therefore use that, but then
1129     * since the proc we call will return results which include the cwd we
1130     * must then trim it off the front of each path in the result. We choose
1131     * to deal with this here (in the generic code), since if we don't, every
1132     * single filesystem's implementation of Tcl_FSMatchInDirectory will have
1133     * to deal with it for us.
1134     */
1135
1136    cwd = Tcl_FSGetCwd(NULL);
1137    if (cwd == NULL) {
1138        if (interp != NULL) {
1139            Tcl_SetResult(interp, "glob couldn't determine "
1140                    "the current working directory", TCL_STATIC);
1141        }
1142        return TCL_ERROR;
1143    }
1144
1145    fsPtr = Tcl_FSGetFileSystemForPath(cwd);
1146    if (fsPtr != NULL && fsPtr->matchInDirectoryProc != NULL) {
1147        TclNewObj(tmpResultPtr);
1148        Tcl_IncrRefCount(tmpResultPtr);
1149        ret = (*fsPtr->matchInDirectoryProc)(interp, tmpResultPtr, cwd,
1150                pattern, types);
1151        if (ret == TCL_OK) {
1152            FsAddMountsToGlobResult(tmpResultPtr, cwd, pattern, types);
1153
1154            /*
1155             * Note that we know resultPtr and tmpResultPtr are distinct.
1156             */
1157
1158            ret = Tcl_ListObjGetElements(interp, tmpResultPtr,
1159                    &resLength, &elemsPtr);
1160            for (i=0 ; ret==TCL_OK && i<resLength ; i++) {
1161                ret = Tcl_ListObjAppendElement(interp, resultPtr,
1162                        TclFSMakePathRelative(interp, elemsPtr[i], cwd));
1163            }
1164        }
1165        TclDecrRefCount(tmpResultPtr);
1166    }
1167    Tcl_DecrRefCount(cwd);
1168    return ret;
1169}
1170
1171/*
1172 *----------------------------------------------------------------------
1173 *
1174 * FsAddMountsToGlobResult --
1175 *
1176 *      This routine is used by the globbing code to take the results of a
1177 *      directory listing and add any mounted paths to that listing. This is
1178 *      required so that simple things like 'glob *' merge mounts and listings
1179 *      correctly.
1180 *
1181 * Results:
1182 *      None.
1183 *
1184 * Side effects:
1185 *      Modifies the resultPtr.
1186 *
1187 *----------------------------------------------------------------------
1188 */
1189
1190static void
1191FsAddMountsToGlobResult(
1192    Tcl_Obj *resultPtr,         /* The current list of matching paths; must
1193                                 * not be shared! */
1194    Tcl_Obj *pathPtr,           /* The directory in question */
1195    const char *pattern,        /* Pattern to match against. */
1196    Tcl_GlobTypeData *types)    /* Object containing list of acceptable types.
1197                                 * May be NULL. In particular the directory
1198                                 * flag is very important. */
1199{
1200    int mLength, gLength, i;
1201    int dir = (types == NULL || (types->type & TCL_GLOB_TYPE_DIR));
1202    Tcl_Obj *mounts = FsListMounts(pathPtr, pattern);
1203
1204    if (mounts == NULL) {
1205        return;
1206    }
1207
1208    if (Tcl_ListObjLength(NULL, mounts, &mLength) != TCL_OK || mLength == 0) {
1209        goto endOfMounts;
1210    }
1211    if (Tcl_ListObjLength(NULL, resultPtr, &gLength) != TCL_OK) {
1212        goto endOfMounts;
1213    }
1214    for (i=0 ; i<mLength ; i++) {
1215        Tcl_Obj *mElt;
1216        int j;
1217        int found = 0;
1218
1219        Tcl_ListObjIndex(NULL, mounts, i, &mElt);
1220
1221        for (j=0 ; j<gLength ; j++) {
1222            Tcl_Obj *gElt;
1223
1224            Tcl_ListObjIndex(NULL, resultPtr, j, &gElt);
1225            if (Tcl_FSEqualPaths(mElt, gElt)) {
1226                found = 1;
1227                if (!dir) {
1228                    /*
1229                     * We don't want to list this.
1230                     */
1231
1232                    Tcl_ListObjReplace(NULL, resultPtr, j, 1, 0, NULL);
1233                    gLength--;
1234                }
1235                break;          /* Break out of for loop */
1236            }
1237        }
1238        if (!found && dir) {
1239            Tcl_Obj *norm;
1240            int len, mlen;
1241
1242            /*
1243             * We know mElt is absolute normalized and lies inside pathPtr, so
1244             * now we must add to the result the right representation of mElt,
1245             * i.e. the representation which is relative to pathPtr.
1246             */
1247
1248            norm = Tcl_FSGetNormalizedPath(NULL, pathPtr);
1249            if (norm != NULL) {
1250                const char *path, *mount;
1251
1252                mount = Tcl_GetStringFromObj(mElt, &mlen);
1253                path = Tcl_GetStringFromObj(norm, &len);
1254                if (path[len-1] == '/') {
1255                    /*
1256                     * Deal with the root of the volume.
1257                     */
1258
1259                    len--;
1260                }
1261                len++; /* account for '/' in the mElt [Bug 1602539] */
1262                mElt = TclNewFSPathObj(pathPtr, mount + len, mlen - len);
1263                Tcl_ListObjAppendElement(NULL, resultPtr, mElt);
1264            }
1265            /*
1266             * No need to increment gLength, since we don't want to compare
1267             * mounts against mounts.
1268             */
1269        }
1270    }
1271
1272  endOfMounts:
1273    Tcl_DecrRefCount(mounts);
1274}
1275
1276/*
1277 *----------------------------------------------------------------------
1278 *
1279 * Tcl_FSMountsChanged --
1280 *
1281 *      Notify the filesystem that the available mounted filesystems (or
1282 *      within any one filesystem type, the number or location of mount
1283 *      points) have changed.
1284 *
1285 * Results:
1286 *      None.
1287 *
1288 * Side effects:
1289 *      The global filesystem variable 'theFilesystemEpoch' is incremented.
1290 *      The effect of this is to make all cached path representations invalid.
1291 *      Clearly it should only therefore be called when it is really required!
1292 *      There are a few circumstances when it should be called:
1293 *
1294 *      (1) when a new filesystem is registered or unregistered. Strictly
1295 *      speaking this is only necessary if the new filesystem accepts file
1296 *      paths as is (normally the filesystem itself is really a shell which
1297 *      hasn't yet had any mount points established and so its
1298 *      'pathInFilesystem' proc will always fail). However, for safety, Tcl
1299 *      always calls this for you in these circumstances.
1300 *
1301 *      (2) when additional mount points are established inside any existing
1302 *      filesystem (except the native fs)
1303 *
1304 *      (3) when any filesystem (except the native fs) changes the list of
1305 *      available volumes.
1306 *
1307 *      (4) when the mapping from a string representation of a file to a full,
1308 *      normalized path changes. For example, if 'env(HOME)' is modified, then
1309 *      any path containing '~' will map to a different filesystem location.
1310 *      Therefore all such paths need to have their internal representation
1311 *      invalidated.
1312 *
1313 *      Tcl has no control over (2) and (3), so any registered filesystem must
1314 *      make sure it calls this function when those situations occur.
1315 *
1316 *      (Note: the reason for the exception in 2,3 for the native filesystem
1317 *      is that the native filesystem by default claims all unknown files even
1318 *      if it really doesn't understand them or if they don't exist).
1319 *
1320 *----------------------------------------------------------------------
1321 */
1322
1323void
1324Tcl_FSMountsChanged(
1325    Tcl_Filesystem *fsPtr)
1326{
1327    /*
1328     * We currently don't do anything with this parameter. We could in the
1329     * future only invalidate files for this filesystem or otherwise take more
1330     * advanced action.
1331     */
1332
1333    (void)fsPtr;
1334
1335    /*
1336     * Increment the filesystem epoch counter, since existing paths might now
1337     * belong to different filesystems.
1338     */
1339
1340    Tcl_MutexLock(&filesystemMutex);
1341    theFilesystemEpoch++;
1342    Tcl_MutexUnlock(&filesystemMutex);
1343}
1344
1345/*
1346 *----------------------------------------------------------------------
1347 *
1348 * Tcl_FSData --
1349 *
1350 *      Retrieve the clientData field for the filesystem given, or NULL if
1351 *      that filesystem is not registered.
1352 *
1353 * Results:
1354 *      A clientData value, or NULL. Note that if the filesystem was
1355 *      registered with a NULL clientData field, this function will return
1356 *      that NULL value.
1357 *
1358 * Side effects:
1359 *      None.
1360 *
1361 *----------------------------------------------------------------------
1362 */
1363
1364ClientData
1365Tcl_FSData(
1366    Tcl_Filesystem *fsPtr) /* The filesystem record to query. */
1367{
1368    ClientData retVal = NULL;
1369    FilesystemRecord *fsRecPtr = FsGetFirstFilesystem();
1370
1371    /*
1372     * Traverse the list of filesystems look for a particular one. If found,
1373     * return that filesystem's clientData (originally provided when calling
1374     * Tcl_FSRegister).
1375     */
1376
1377    while ((retVal == NULL) && (fsRecPtr != NULL)) {
1378        if (fsRecPtr->fsPtr == fsPtr) {
1379            retVal = fsRecPtr->clientData;
1380        }
1381        fsRecPtr = fsRecPtr->nextPtr;
1382    }
1383
1384    return retVal;
1385}
1386
1387/*
1388 *---------------------------------------------------------------------------
1389 *
1390 * TclFSNormalizeToUniquePath --
1391 *
1392 *      Takes a path specification containing no ../, ./ sequences, and
1393 *      converts it into a unique path for the given platform. On Unix, this
1394 *      means the path must be free of symbolic links/aliases, and on Windows
1395 *      it means we want the long form, with that long form's case-dependence
1396 *      (which gives us a unique, case-dependent path).
1397 *
1398 * Results:
1399 *      The pathPtr is modified in place. The return value is the last byte
1400 *      offset which was recognised in the path string.
1401 *
1402 * Side effects:
1403 *      None (beyond the memory allocation for the result).
1404 *
1405 * Special notes:
1406 *      If the filesystem-specific normalizePathProcs can re-introduce ../, ./
1407 *      sequences into the path, then this function will not return the
1408 *      correct result. This may be possible with symbolic links on unix.
1409 *
1410 *      Important assumption: if startAt is non-zero, it must point to a
1411 *      directory separator that we know exists and is already normalized (so
1412 *      it is important not to point to the char just after the separator).
1413 *
1414 *---------------------------------------------------------------------------
1415 */
1416
1417int
1418TclFSNormalizeToUniquePath(
1419    Tcl_Interp *interp,         /* Used for error messages. */
1420    Tcl_Obj *pathPtr,           /* The path to normalize in place */
1421    int startAt,                /* Start at this char-offset */
1422    ClientData *clientDataPtr)  /* If we generated a complete normalized path
1423                                 * for a given filesystem, we can optionally
1424                                 * return an fs-specific clientdata here. */
1425{
1426    FilesystemRecord *fsRecPtr, *firstFsRecPtr;
1427    /* Ignore this variable */
1428    (void) clientDataPtr;
1429
1430    /*
1431     * Call each of the "normalise path" functions in succession. This is a
1432     * special case, in which if we have a native filesystem handler, we call
1433     * it first. This is because the root of Tcl's filesystem is always a
1434     * native filesystem (i.e. '/' on unix is native).
1435     */
1436
1437    firstFsRecPtr = FsGetFirstFilesystem();
1438
1439    fsRecPtr = firstFsRecPtr;
1440    while (fsRecPtr != NULL) {
1441        if (fsRecPtr->fsPtr == &tclNativeFilesystem) {
1442            Tcl_FSNormalizePathProc *proc = fsRecPtr->fsPtr->normalizePathProc;
1443            if (proc != NULL) {
1444                startAt = (*proc)(interp, pathPtr, startAt);
1445            }
1446            break;
1447        }
1448        fsRecPtr = fsRecPtr->nextPtr;
1449    }
1450
1451    fsRecPtr = firstFsRecPtr;
1452    while (fsRecPtr != NULL) {
1453        /*
1454         * Skip the native system next time through.
1455         */
1456
1457        if (fsRecPtr->fsPtr != &tclNativeFilesystem) {
1458            Tcl_FSNormalizePathProc *proc = fsRecPtr->fsPtr->normalizePathProc;
1459            if (proc != NULL) {
1460                startAt = (*proc)(interp, pathPtr, startAt);
1461            }
1462
1463            /*
1464             * We could add an efficiency check like this:
1465             *          if (retVal == length-of(pathPtr)) {break;}
1466             * but there's not much benefit.
1467             */
1468        }
1469        fsRecPtr = fsRecPtr->nextPtr;
1470    }
1471
1472    return startAt;
1473}
1474
1475/*
1476 *---------------------------------------------------------------------------
1477 *
1478 * TclGetOpenMode --
1479 *
1480 *      This routine is an obsolete, limited version of TclGetOpenModeEx()
1481 *      below. It exists only to satisfy any extensions imprudently using it
1482 *      via Tcl's internal stubs table.
1483 *
1484 * Results:
1485 *      Same as TclGetOpenModeEx().
1486 *
1487 * Side effects:
1488 *      Same as TclGetOpenModeEx().
1489 *
1490 *---------------------------------------------------------------------------
1491 */
1492
1493int
1494TclGetOpenMode(
1495    Tcl_Interp *interp,         /* Interpreter to use for error reporting -
1496                                 * may be NULL. */
1497    const char *modeString,     /* Mode string, e.g. "r+" or "RDONLY CREAT" */
1498    int *seekFlagPtr)           /* Set this to 1 if the caller should seek to
1499                                 * EOF during the opening of the file. */
1500{
1501    int binary = 0;
1502    return TclGetOpenModeEx(interp, modeString, seekFlagPtr, &binary);
1503}
1504
1505/*
1506 *---------------------------------------------------------------------------
1507 *
1508 * TclGetOpenModeEx --
1509 *
1510 *      Computes a POSIX mode mask for opening a file, from a given string,
1511 *      and also sets flags to indicate whether the caller should seek to EOF
1512 *      after opening the file, and whether the caller should configure the
1513 *      channel for binary data.
1514 *
1515 * Results:
1516 *      On success, returns mode to pass to "open". If an error occurs, the
1517 *      return value is -1 and if interp is not NULL, sets interp's result
1518 *      object to an error message.
1519 *
1520 * Side effects:
1521 *      Sets the integer referenced by seekFlagPtr to 1 to tell the caller to
1522 *      seek to EOF after opening the file, or to 0 otherwise. Sets the
1523 *      integer referenced by binaryPtr to 1 to tell the caller to seek to
1524 *      configure the channel for binary data, or to 0 otherwise.
1525 *
1526 * Special note:
1527 *      This code is based on a prototype implementation contributed by Mark
1528 *      Diekhans.
1529 *
1530 *---------------------------------------------------------------------------
1531 */
1532
1533int
1534TclGetOpenModeEx(
1535    Tcl_Interp *interp,         /* Interpreter to use for error reporting -
1536                                 * may be NULL. */
1537    const char *modeString,     /* Mode string, e.g. "r+" or "RDONLY CREAT" */
1538    int *seekFlagPtr,           /* Set this to 1 if the caller should seek to
1539                                 * EOF during the opening of the file. */
1540    int *binaryPtr)             /* Set this to 1 if the caller should
1541                                 * configure the opened channel for binary
1542                                 * operations */
1543{
1544    int mode, modeArgc, c, i, gotRW;
1545    const char **modeArgv, *flag;
1546#define RW_MODES (O_RDONLY|O_WRONLY|O_RDWR)
1547
1548    /*
1549     * Check for the simpler fopen-like access modes (e.g. "r"). They are
1550     * distinguished from the POSIX access modes by the presence of a
1551     * lower-case first letter.
1552     */
1553
1554    *seekFlagPtr = 0;
1555    *binaryPtr = 0;
1556    mode = 0;
1557
1558    /*
1559     * Guard against international characters before using byte oriented
1560     * routines.
1561     */
1562
1563    if (!(modeString[0] & 0x80)
1564            && islower(UCHAR(modeString[0]))) { /* INTL: ISO only. */
1565        switch (modeString[0]) {
1566        case 'r':
1567            mode = O_RDONLY;
1568            break;
1569        case 'w':
1570            mode = O_WRONLY|O_CREAT|O_TRUNC;
1571            break;
1572        case 'a':
1573            /*
1574             * Added O_APPEND for proper automatic seek-to-end-on-write by the
1575             * OS. [Bug 680143]
1576             */
1577
1578            mode = O_WRONLY|O_CREAT|O_APPEND;
1579            *seekFlagPtr = 1;
1580            break;
1581        default:
1582            goto error;
1583        }
1584        i=1;
1585        while (i<3 && modeString[i]) {
1586            if (modeString[i] == modeString[i-1]) {
1587                goto error;
1588            }
1589            switch (modeString[i++]) {
1590            case '+':
1591                /*
1592                 * Must remove the O_APPEND flag so that the seek command
1593                 * works. [Bug 1773127]
1594                 */
1595
1596                mode &= ~(O_RDONLY|O_WRONLY|O_APPEND);
1597                mode |= O_RDWR;
1598                break;
1599            case 'b':
1600                *binaryPtr = 1;
1601                break;
1602            default:
1603                goto error;
1604            }
1605        }
1606        if (modeString[i] != 0) {
1607            goto error;
1608        }
1609        return mode;
1610
1611    error:
1612        *seekFlagPtr = 0;
1613        *binaryPtr = 0;
1614        if (interp != NULL) {
1615            Tcl_AppendResult(interp, "illegal access mode \"", modeString,
1616                    "\"", NULL);
1617        }
1618        return -1;
1619    }
1620
1621    /*
1622     * The access modes are specified using a list of POSIX modes such as
1623     * O_CREAT.
1624     *
1625     * IMPORTANT NOTE: We rely on Tcl_SplitList working correctly when a NULL
1626     * interpreter is passed in.
1627     */
1628
1629    if (Tcl_SplitList(interp, modeString, &modeArgc, &modeArgv) != TCL_OK) {
1630        if (interp != NULL) {
1631            Tcl_AddErrorInfo(interp,
1632                    "\n    while processing open access modes \"");
1633            Tcl_AddErrorInfo(interp, modeString);
1634            Tcl_AddErrorInfo(interp, "\"");
1635        }
1636        return -1;
1637    }
1638
1639    gotRW = 0;
1640    for (i = 0; i < modeArgc; i++) {
1641        flag = modeArgv[i];
1642        c = flag[0];
1643        if ((c == 'R') && (strcmp(flag, "RDONLY") == 0)) {
1644            mode = (mode & ~RW_MODES) | O_RDONLY;
1645            gotRW = 1;
1646        } else if ((c == 'W') && (strcmp(flag, "WRONLY") == 0)) {
1647            mode = (mode & ~RW_MODES) | O_WRONLY;
1648            gotRW = 1;
1649        } else if ((c == 'R') && (strcmp(flag, "RDWR") == 0)) {
1650            mode = (mode & ~RW_MODES) | O_RDWR;
1651            gotRW = 1;
1652        } else if ((c == 'A') && (strcmp(flag, "APPEND") == 0)) {
1653            mode |= O_APPEND;
1654            *seekFlagPtr = 1;
1655        } else if ((c == 'C') && (strcmp(flag, "CREAT") == 0)) {
1656            mode |= O_CREAT;
1657        } else if ((c == 'E') && (strcmp(flag, "EXCL") == 0)) {
1658            mode |= O_EXCL;
1659
1660        } else if ((c == 'N') && (strcmp(flag, "NOCTTY") == 0)) {
1661#ifdef O_NOCTTY
1662            mode |= O_NOCTTY;
1663#else
1664            if (interp != NULL) {
1665                Tcl_AppendResult(interp, "access mode \"", flag,
1666                        "\" not supported by this system", NULL);
1667            }
1668            ckfree((char *) modeArgv);
1669            return -1;
1670#endif
1671
1672        } else if ((c == 'N') && (strcmp(flag, "NONBLOCK") == 0)) {
1673#ifdef O_NONBLOCK
1674            mode |= O_NONBLOCK;
1675#else
1676            if (interp != NULL) {
1677                Tcl_AppendResult(interp, "access mode \"", flag,
1678                        "\" not supported by this system", NULL);
1679            }
1680            ckfree((char *) modeArgv);
1681            return -1;
1682#endif
1683
1684        } else if ((c == 'T') && (strcmp(flag, "TRUNC") == 0)) {
1685            mode |= O_TRUNC;
1686        } else if ((c == 'B') && (strcmp(flag, "BINARY") == 0)) {
1687            *binaryPtr = 1;
1688        } else {
1689
1690            if (interp != NULL) {
1691                Tcl_AppendResult(interp, "invalid access mode \"", flag,
1692                        "\": must be RDONLY, WRONLY, RDWR, APPEND, BINARY, "
1693                        "CREAT, EXCL, NOCTTY, NONBLOCK, or TRUNC", NULL);
1694            }
1695            ckfree((char *) modeArgv);
1696            return -1;
1697        }
1698    }
1699
1700    ckfree((char *) modeArgv);
1701
1702    if (!gotRW) {
1703        if (interp != NULL) {
1704            Tcl_AppendResult(interp, "access mode must include either"
1705                    " RDONLY, WRONLY, or RDWR", NULL);
1706        }
1707        return -1;
1708    }
1709    return mode;
1710}
1711
1712/*
1713 * Tcl_FSEvalFile is Tcl_FSEvalFileEx without encoding argument.
1714 */
1715
1716int
1717Tcl_FSEvalFile(
1718    Tcl_Interp *interp,         /* Interpreter in which to process file. */
1719    Tcl_Obj *pathPtr)           /* Path of file to process. Tilde-substitution
1720                                 * will be performed on this name. */
1721{
1722    return Tcl_FSEvalFileEx(interp, pathPtr, NULL);
1723}
1724
1725/*
1726 *----------------------------------------------------------------------
1727 *
1728 * Tcl_FSEvalFileEx --
1729 *
1730 *      Read in a file and process the entire file as one gigantic Tcl
1731 *      command.
1732 *
1733 * Results:
1734 *      A standard Tcl result, which is either the result of executing the
1735 *      file or an error indicating why the file couldn't be read.
1736 *
1737 * Side effects:
1738 *      Depends on the commands in the file. During the evaluation of the
1739 *      contents of the file, iPtr->scriptFile is made to point to pathPtr
1740 *      (the old value is cached and replaced when this function returns).
1741 *
1742 *----------------------------------------------------------------------
1743 */
1744
1745int
1746Tcl_FSEvalFileEx(
1747    Tcl_Interp *interp,         /* Interpreter in which to process file. */
1748    Tcl_Obj *pathPtr,           /* Path of file to process. Tilde-substitution
1749                                 * will be performed on this name. */
1750    const char *encodingName)   /* If non-NULL, then use this encoding for the
1751                                 * file. NULL means use the system encoding. */
1752{
1753    int length, result = TCL_ERROR;
1754    Tcl_StatBuf statBuf;
1755    Tcl_Obj *oldScriptFile;
1756    Interp *iPtr;
1757    char *string;
1758    Tcl_Channel chan;
1759    Tcl_Obj *objPtr;
1760
1761    if (Tcl_FSGetNormalizedPath(interp, pathPtr) == NULL) {
1762        return result;
1763    }
1764
1765    if (Tcl_FSStat(pathPtr, &statBuf) == -1) {
1766        Tcl_SetErrno(errno);
1767        Tcl_AppendResult(interp, "couldn't read file \"",
1768                Tcl_GetString(pathPtr), "\": ", Tcl_PosixError(interp), NULL);
1769        return result;
1770    }
1771    chan = Tcl_FSOpenFileChannel(interp, pathPtr, "r", 0644);
1772    if (chan == (Tcl_Channel) NULL) {
1773        Tcl_ResetResult(interp);
1774        Tcl_AppendResult(interp, "couldn't read file \"",
1775                Tcl_GetString(pathPtr), "\": ", Tcl_PosixError(interp), NULL);
1776        return result;
1777    }
1778
1779    /*
1780     * The eofchar is \32 (^Z). This is the usual on Windows, but we effect
1781     * this cross-platform to allow for scripted documents. [Bug: 2040]
1782     */
1783
1784    Tcl_SetChannelOption(interp, chan, "-eofchar", "\32");
1785
1786    /*
1787     * If the encoding is specified, set it for the channel. Else don't touch
1788     * it (and use the system encoding) Report error on unknown encoding.
1789     */
1790
1791    if (encodingName != NULL) {
1792        if (Tcl_SetChannelOption(interp, chan, "-encoding", encodingName)
1793                != TCL_OK) {
1794            Tcl_Close(interp,chan);
1795            return result;
1796        }
1797    }
1798
1799    objPtr = Tcl_NewObj();
1800    Tcl_IncrRefCount(objPtr);
1801    if (Tcl_ReadChars(chan, objPtr, -1, 0) < 0) {
1802        Tcl_Close(interp, chan);
1803        Tcl_AppendResult(interp, "couldn't read file \"",
1804                Tcl_GetString(pathPtr), "\": ", Tcl_PosixError(interp), NULL);
1805        goto end;
1806    }
1807
1808    if (Tcl_Close(interp, chan) != TCL_OK) {
1809        goto end;
1810    }
1811
1812    iPtr = (Interp *) interp;
1813    oldScriptFile = iPtr->scriptFile;
1814    iPtr->scriptFile = pathPtr;
1815    Tcl_IncrRefCount(iPtr->scriptFile);
1816    string = Tcl_GetStringFromObj(objPtr, &length);
1817    /* TIP #280 Force the evaluator to open a frame for a sourced
1818     * file. */
1819    iPtr->evalFlags |= TCL_EVAL_FILE;
1820    result = Tcl_EvalEx(interp, string, length, 0);
1821
1822    /*
1823     * Now we have to be careful; the script may have changed the
1824     * iPtr->scriptFile value, so we must reset it without assuming it still
1825     * points to 'pathPtr'.
1826     */
1827
1828    if (iPtr->scriptFile != NULL) {
1829        Tcl_DecrRefCount(iPtr->scriptFile);
1830    }
1831    iPtr->scriptFile = oldScriptFile;
1832
1833    if (result == TCL_RETURN) {
1834        result = TclUpdateReturnInfo(iPtr);
1835    } else if (result == TCL_ERROR) {
1836        /*
1837         * Record information telling where the error occurred.
1838         */
1839
1840        const char *pathString = Tcl_GetStringFromObj(pathPtr, &length);
1841        int limit = 150;
1842        int overflow = (length > limit);
1843
1844        Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf(
1845                "\n    (file \"%.*s%s\" line %d)",
1846                (overflow ? limit : length), pathString,
1847                (overflow ? "..." : ""), interp->errorLine));
1848    }
1849
1850  end:
1851    Tcl_DecrRefCount(objPtr);
1852    return result;
1853}
1854
1855/*
1856 *----------------------------------------------------------------------
1857 *
1858 * Tcl_GetErrno --
1859 *
1860 *      Gets the current value of the Tcl error code variable. This is
1861 *      currently the global variable "errno" but could in the future change
1862 *      to something else.
1863 *
1864 * Results:
1865 *      The value of the Tcl error code variable.
1866 *
1867 * Side effects:
1868 *      None. Note that the value of the Tcl error code variable is UNDEFINED
1869 *      if a call to Tcl_SetErrno did not precede this call.
1870 *
1871 *----------------------------------------------------------------------
1872 */
1873
1874int
1875Tcl_GetErrno(void)
1876{
1877    return errno;
1878}
1879
1880/*
1881 *----------------------------------------------------------------------
1882 *
1883 * Tcl_SetErrno --
1884 *
1885 *      Sets the Tcl error code variable to the supplied value.
1886 *
1887 * Results:
1888 *      None.
1889 *
1890 * Side effects:
1891 *      Modifies the value of the Tcl error code variable.
1892 *
1893 *----------------------------------------------------------------------
1894 */
1895
1896void
1897Tcl_SetErrno(
1898    int err)                    /* The new value. */
1899{
1900    errno = err;
1901}
1902
1903/*
1904 *----------------------------------------------------------------------
1905 *
1906 * Tcl_PosixError --
1907 *
1908 *      This function is typically called after UNIX kernel calls return
1909 *      errors. It stores machine-readable information about the error in
1910 *      errorCode field of interp and returns an information string for the
1911 *      caller's use.
1912 *
1913 * Results:
1914 *      The return value is a human-readable string describing the error.
1915 *
1916 * Side effects:
1917 *      The errorCode field of the interp is set.
1918 *
1919 *----------------------------------------------------------------------
1920 */
1921
1922const char *
1923Tcl_PosixError(
1924    Tcl_Interp *interp)         /* Interpreter whose errorCode field is to be
1925                                 * set. */
1926{
1927    const char *id, *msg;
1928
1929    msg = Tcl_ErrnoMsg(errno);
1930    id = Tcl_ErrnoId();
1931    if (interp) {
1932        Tcl_SetErrorCode(interp, "POSIX", id, msg, NULL);
1933    }
1934    return msg;
1935}
1936
1937/*
1938 *----------------------------------------------------------------------
1939 *
1940 * Tcl_FSStat --
1941 *
1942 *      This function replaces the library version of stat and lsat.
1943 *
1944 *      The appropriate function for the filesystem to which pathPtr belongs
1945 *      will be called.
1946 *
1947 * Results:
1948 *      See stat documentation.
1949 *
1950 * Side effects:
1951 *      See stat documentation.
1952 *
1953 *----------------------------------------------------------------------
1954 */
1955
1956int
1957Tcl_FSStat(
1958    Tcl_Obj *pathPtr,           /* Path of file to stat (in current CP). */
1959    Tcl_StatBuf *buf)           /* Filled with results of stat call. */
1960{
1961    const Tcl_Filesystem *fsPtr;
1962#ifdef USE_OBSOLETE_FS_HOOKS
1963    struct stat oldStyleStatBuffer;
1964    int retVal = -1;
1965
1966    /*
1967     * Call each of the "stat" function in succession. A non-return value of
1968     * -1 indicates the particular function has succeeded.
1969     */
1970
1971    Tcl_MutexLock(&obsoleteFsHookMutex);
1972
1973    if (statProcList != NULL) {
1974        StatProc *statProcPtr;
1975        char *path;
1976        Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(NULL, pathPtr);
1977        if (transPtr == NULL) {
1978            path = NULL;
1979        } else {
1980            path = Tcl_GetString(transPtr);
1981        }
1982
1983        statProcPtr = statProcList;
1984        while ((retVal == -1) && (statProcPtr != NULL)) {
1985            retVal = (*statProcPtr->proc)(path, &oldStyleStatBuffer);
1986            statProcPtr = statProcPtr->nextPtr;
1987        }
1988        if (transPtr != NULL) {
1989            Tcl_DecrRefCount(transPtr);
1990        }
1991    }
1992
1993    Tcl_MutexUnlock(&obsoleteFsHookMutex);
1994    if (retVal != -1) {
1995        /*
1996         * Note that EOVERFLOW is not a problem here, and these assignments
1997         * should all be widening (if not identity.)
1998         */
1999
2000        buf->st_mode = oldStyleStatBuffer.st_mode;
2001        buf->st_ino = oldStyleStatBuffer.st_ino;
2002        buf->st_dev = oldStyleStatBuffer.st_dev;
2003        buf->st_rdev = oldStyleStatBuffer.st_rdev;
2004        buf->st_nlink = oldStyleStatBuffer.st_nlink;
2005        buf->st_uid = oldStyleStatBuffer.st_uid;
2006        buf->st_gid = oldStyleStatBuffer.st_gid;
2007        buf->st_size = Tcl_LongAsWide(oldStyleStatBuffer.st_size);
2008        buf->st_atime = oldStyleStatBuffer.st_atime;
2009        buf->st_mtime = oldStyleStatBuffer.st_mtime;
2010        buf->st_ctime = oldStyleStatBuffer.st_ctime;
2011#ifdef HAVE_ST_BLOCKS
2012        buf->st_blksize = oldStyleStatBuffer.st_blksize;
2013        buf->st_blocks = Tcl_LongAsWide(oldStyleStatBuffer.st_blocks);
2014#endif
2015        return retVal;
2016    }
2017#endif /* USE_OBSOLETE_FS_HOOKS */
2018
2019    fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
2020    if (fsPtr != NULL) {
2021        Tcl_FSStatProc *proc = fsPtr->statProc;
2022        if (proc != NULL) {
2023            return (*proc)(pathPtr, buf);
2024        }
2025    }
2026    Tcl_SetErrno(ENOENT);
2027    return -1;
2028}
2029
2030/*
2031 *----------------------------------------------------------------------
2032 *
2033 * Tcl_FSLstat --
2034 *
2035 *      This function replaces the library version of lstat. The appropriate
2036 *      function for the filesystem to which pathPtr belongs will be called.
2037 *      If no 'lstat' function is listed, but a 'stat' function is, then Tcl
2038 *      will fall back on the stat function.
2039 *
2040 * Results:
2041 *      See lstat documentation.
2042 *
2043 * Side effects:
2044 *      See lstat documentation.
2045 *
2046 *----------------------------------------------------------------------
2047 */
2048
2049int
2050Tcl_FSLstat(
2051    Tcl_Obj *pathPtr,           /* Path of file to stat (in current CP). */
2052    Tcl_StatBuf *buf)           /* Filled with results of stat call. */
2053{
2054    const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
2055    if (fsPtr != NULL) {
2056        Tcl_FSLstatProc *proc = fsPtr->lstatProc;
2057        if (proc != NULL) {
2058            return (*proc)(pathPtr, buf);
2059        } else {
2060            Tcl_FSStatProc *sproc = fsPtr->statProc;
2061            if (sproc != NULL) {
2062                return (*sproc)(pathPtr, buf);
2063            }
2064        }
2065    }
2066    Tcl_SetErrno(ENOENT);
2067    return -1;
2068}
2069
2070/*
2071 *----------------------------------------------------------------------
2072 *
2073 * Tcl_FSAccess --
2074 *
2075 *      This function replaces the library version of access. The appropriate
2076 *      function for the filesystem to which pathPtr belongs will be called.
2077 *
2078 * Results:
2079 *      See access documentation.
2080 *
2081 * Side effects:
2082 *      See access documentation.
2083 *
2084 *----------------------------------------------------------------------
2085 */
2086
2087int
2088Tcl_FSAccess(
2089    Tcl_Obj *pathPtr,           /* Path of file to access (in current CP). */
2090    int mode)                   /* Permission setting. */
2091{
2092    const Tcl_Filesystem *fsPtr;
2093#ifdef USE_OBSOLETE_FS_HOOKS
2094    int retVal = -1;
2095
2096    /*
2097     * Call each of the "access" function in succession. A non-return value of
2098     * -1 indicates the particular function has succeeded.
2099     */
2100
2101    Tcl_MutexLock(&obsoleteFsHookMutex);
2102
2103    if (accessProcList != NULL) {
2104        AccessProc *accessProcPtr;
2105        char *path;
2106        Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(NULL, pathPtr);
2107        if (transPtr == NULL) {
2108            path = NULL;
2109        } else {
2110            path = Tcl_GetString(transPtr);
2111        }
2112
2113        accessProcPtr = accessProcList;
2114        while ((retVal == -1) && (accessProcPtr != NULL)) {
2115            retVal = (*accessProcPtr->proc)(path, mode);
2116            accessProcPtr = accessProcPtr->nextPtr;
2117        }
2118        if (transPtr != NULL) {
2119            Tcl_DecrRefCount(transPtr);
2120        }
2121    }
2122
2123    Tcl_MutexUnlock(&obsoleteFsHookMutex);
2124    if (retVal != -1) {
2125        return retVal;
2126    }
2127#endif /* USE_OBSOLETE_FS_HOOKS */
2128
2129    fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
2130    if (fsPtr != NULL) {
2131        Tcl_FSAccessProc *proc = fsPtr->accessProc;
2132        if (proc != NULL) {
2133            return (*proc)(pathPtr, mode);
2134        }
2135    }
2136
2137    Tcl_SetErrno(ENOENT);
2138    return -1;
2139}
2140
2141/*
2142 *----------------------------------------------------------------------
2143 *
2144 * Tcl_FSOpenFileChannel --
2145 *
2146 *      The appropriate function for the filesystem to which pathPtr belongs
2147 *      will be called.
2148 *
2149 * Results:
2150 *      The new channel or NULL, if the named file could not be opened.
2151 *
2152 * Side effects:
2153 *      May open the channel and may cause creation of a file on the file
2154 *      system.
2155 *
2156 *----------------------------------------------------------------------
2157 */
2158
2159Tcl_Channel
2160Tcl_FSOpenFileChannel(
2161    Tcl_Interp *interp,         /* Interpreter for error reporting; can be
2162                                 * NULL. */
2163    Tcl_Obj *pathPtr,           /* Name of file to open. */
2164    const char *modeString,     /* A list of POSIX open modes or a string such
2165                                 * as "rw". */
2166    int permissions)            /* If the open involves creating a file, with
2167                                 * what modes to create it? */
2168{
2169    const Tcl_Filesystem *fsPtr;
2170    Tcl_Channel retVal = NULL;
2171
2172#ifdef USE_OBSOLETE_FS_HOOKS
2173    /*
2174     * Call each of the "Tcl_OpenFileChannel" functions in succession. A
2175     * non-NULL return value indicates the particular function has succeeded.
2176     */
2177
2178    Tcl_MutexLock(&obsoleteFsHookMutex);
2179    if (openFileChannelProcList != NULL) {
2180        OpenFileChannelProc *openFileChannelProcPtr;
2181        char *path;
2182        Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(interp, pathPtr);
2183
2184        if (transPtr == NULL) {
2185            path = NULL;
2186        } else {
2187            path = Tcl_GetString(transPtr);
2188        }
2189
2190        openFileChannelProcPtr = openFileChannelProcList;
2191
2192        while ((retVal == NULL) && (openFileChannelProcPtr != NULL)) {
2193            retVal = (*openFileChannelProcPtr->proc)(interp, path,
2194                    modeString, permissions);
2195            openFileChannelProcPtr = openFileChannelProcPtr->nextPtr;
2196        }
2197        if (transPtr != NULL) {
2198            Tcl_DecrRefCount(transPtr);
2199        }
2200    }
2201    Tcl_MutexUnlock(&obsoleteFsHookMutex);
2202    if (retVal != NULL) {
2203        return retVal;
2204    }
2205#endif /* USE_OBSOLETE_FS_HOOKS */
2206
2207    /*
2208     * We need this just to ensure we return the correct error messages under
2209     * some circumstances.
2210     */
2211
2212    if (Tcl_FSGetNormalizedPath(interp, pathPtr) == NULL) {
2213        return NULL;
2214    }
2215
2216    fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
2217    if (fsPtr != NULL) {
2218        Tcl_FSOpenFileChannelProc *proc = fsPtr->openFileChannelProc;
2219        if (proc != NULL) {
2220            int mode, seekFlag, binary;
2221
2222            /*
2223             * Parse the mode, picking up whether we want to seek to start
2224             * with and/or set the channel automatically into binary mode.
2225             */
2226
2227            mode = TclGetOpenModeEx(interp, modeString, &seekFlag, &binary);
2228            if (mode == -1) {
2229                return NULL;
2230            }
2231
2232            /*
2233             * Do the actual open() call.
2234             */
2235
2236            retVal = (*proc)(interp, pathPtr, mode, permissions);
2237            if (retVal == NULL) {
2238                return NULL;
2239            }
2240
2241            /*
2242             * Apply appropriate flags parsed out above.
2243             */
2244
2245            if (seekFlag && Tcl_Seek(retVal, (Tcl_WideInt)0,
2246                    SEEK_END) < (Tcl_WideInt)0) {
2247                if (interp != NULL) {
2248                    Tcl_AppendResult(interp, "could not seek to end "
2249                            "of file while opening \"", Tcl_GetString(pathPtr),
2250                            "\": ", Tcl_PosixError(interp), NULL);
2251                }
2252                Tcl_Close(NULL, retVal);
2253                return NULL;
2254            }
2255            if (binary) {
2256                Tcl_SetChannelOption(interp, retVal, "-translation", "binary");
2257            }
2258            return retVal;
2259        }
2260    }
2261
2262    /*
2263     * File doesn't belong to any filesystem that can open it.
2264     */
2265
2266    Tcl_SetErrno(ENOENT);
2267    if (interp != NULL) {
2268        Tcl_AppendResult(interp, "couldn't open \"", Tcl_GetString(pathPtr),
2269                "\": ", Tcl_PosixError(interp), NULL);
2270    }
2271    return NULL;
2272}
2273
2274/*
2275 *----------------------------------------------------------------------
2276 *
2277 * Tcl_FSUtime --
2278 *
2279 *      This function replaces the library version of utime. The appropriate
2280 *      function for the filesystem to which pathPtr belongs will be called.
2281 *
2282 * Results:
2283 *      See utime documentation.
2284 *
2285 * Side effects:
2286 *      See utime documentation.
2287 *
2288 *----------------------------------------------------------------------
2289 */
2290
2291int
2292Tcl_FSUtime(
2293    Tcl_Obj *pathPtr,           /* File to change access/modification times */
2294    struct utimbuf *tval)       /* Structure containing access/modification
2295                                 * times to use. Should not be modified. */
2296{
2297    const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
2298    if (fsPtr != NULL) {
2299        Tcl_FSUtimeProc *proc = fsPtr->utimeProc;
2300        if (proc != NULL) {
2301            return (*proc)(pathPtr, tval);
2302        }
2303    }
2304    return -1;
2305}
2306
2307/*
2308 *----------------------------------------------------------------------
2309 *
2310 * NativeFileAttrStrings --
2311 *
2312 *      This function implements the platform dependent 'file attributes'
2313 *      subcommand, for the native filesystem, for listing the set of possible
2314 *      attribute strings. This function is part of Tcl's native filesystem
2315 *      support, and is placed here because it is shared by Unix and Windows
2316 *      code.
2317 *
2318 * Results:
2319 *      An array of strings
2320 *
2321 * Side effects:
2322 *      None.
2323 *
2324 *----------------------------------------------------------------------
2325 */
2326
2327static const char **
2328NativeFileAttrStrings(
2329    Tcl_Obj *pathPtr,
2330    Tcl_Obj **objPtrRef)
2331{
2332    return tclpFileAttrStrings;
2333}
2334
2335/*
2336 *----------------------------------------------------------------------
2337 *
2338 * NativeFileAttrsGet --
2339 *
2340 *      This function implements the platform dependent 'file attributes'
2341 *      subcommand, for the native filesystem, for 'get' operations. This
2342 *      function is part of Tcl's native filesystem support, and is placed
2343 *      here because it is shared by Unix and Windows code.
2344 *
2345 * Results:
2346 *      Standard Tcl return code. The object placed in objPtrRef (if TCL_OK
2347 *      was returned) is likely to have a refCount of zero. Either way we must
2348 *      either store it somewhere (e.g. the Tcl result), or Incr/Decr its
2349 *      refCount to ensure it is properly freed.
2350 *
2351 * Side effects:
2352 *      None.
2353 *
2354 *----------------------------------------------------------------------
2355 */
2356
2357static int
2358NativeFileAttrsGet(
2359    Tcl_Interp *interp,         /* The interpreter for error reporting. */
2360    int index,                  /* index of the attribute command. */
2361    Tcl_Obj *pathPtr,           /* path of file we are operating on. */
2362    Tcl_Obj **objPtrRef)        /* for output. */
2363{
2364    return (*tclpFileAttrProcs[index].getProc)(interp, index, pathPtr,
2365            objPtrRef);
2366}
2367
2368/*
2369 *----------------------------------------------------------------------
2370 *
2371 * NativeFileAttrsSet --
2372 *
2373 *      This function implements the platform dependent 'file attributes'
2374 *      subcommand, for the native filesystem, for 'set' operations. This
2375 *      function is part of Tcl's native filesystem support, and is placed
2376 *      here because it is shared by Unix and Windows code.
2377 *
2378 * Results:
2379 *      Standard Tcl return code.
2380 *
2381 * Side effects:
2382 *      None.
2383 *
2384 *----------------------------------------------------------------------
2385 */
2386
2387static int
2388NativeFileAttrsSet(
2389    Tcl_Interp *interp,         /* The interpreter for error reporting. */
2390    int index,                  /* index of the attribute command. */
2391    Tcl_Obj *pathPtr,           /* path of file we are operating on. */
2392    Tcl_Obj *objPtr)            /* set to this value. */
2393{
2394    return (*tclpFileAttrProcs[index].setProc)(interp, index, pathPtr, objPtr);
2395}
2396
2397/*
2398 *----------------------------------------------------------------------
2399 *
2400 * Tcl_FSFileAttrStrings --
2401 *
2402 *      This function implements part of the hookable 'file attributes'
2403 *      subcommand. The appropriate function for the filesystem to which
2404 *      pathPtr belongs will be called.
2405 *
2406 * Results:
2407 *      The called function may either return an array of strings, or may
2408 *      instead return NULL and place a Tcl list into the given objPtrRef.
2409 *      Tcl will take that list and first increment its refCount before using
2410 *      it. On completion of that use, Tcl will decrement its refCount. Hence
2411 *      if the list should be disposed of by Tcl when done, it should have a
2412 *      refCount of zero, and if the list should not be disposed of, the
2413 *      filesystem should ensure it retains a refCount on the object.
2414 *
2415 * Side effects:
2416 *      None.
2417 *
2418 *----------------------------------------------------------------------
2419 */
2420
2421const char **
2422Tcl_FSFileAttrStrings(
2423    Tcl_Obj *pathPtr,
2424    Tcl_Obj **objPtrRef)
2425{
2426    const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
2427
2428    if (fsPtr != NULL) {
2429        Tcl_FSFileAttrStringsProc *proc = fsPtr->fileAttrStringsProc;
2430        if (proc != NULL) {
2431            return (*proc)(pathPtr, objPtrRef);
2432        }
2433    }
2434    Tcl_SetErrno(ENOENT);
2435    return NULL;
2436}
2437
2438/*
2439 *----------------------------------------------------------------------
2440 *
2441 * TclFSFileAttrIndex --
2442 *
2443 *      Helper function for converting an attribute name to an index into the
2444 *      attribute table.
2445 *
2446 * Results:
2447 *      Tcl result code, index written to *indexPtr on result==TCL_OK
2448 *
2449 * Side effects:
2450 *      None.
2451 *
2452 *----------------------------------------------------------------------
2453 */
2454
2455int
2456TclFSFileAttrIndex(
2457    Tcl_Obj *pathPtr,           /* File whose attributes are to be indexed
2458                                 * into. */
2459    const char *attributeName,  /* The attribute being looked for. */
2460    int *indexPtr)              /* Where to write the found index. */
2461{
2462    Tcl_Obj *listObj = NULL;
2463    const char **attrTable;
2464
2465    /*
2466     * Get the attribute table for the file.
2467     */
2468
2469    attrTable = Tcl_FSFileAttrStrings(pathPtr, &listObj);
2470    if (listObj != NULL) {
2471        Tcl_IncrRefCount(listObj);
2472    }
2473
2474    if (attrTable != NULL) {
2475        /*
2476         * It's a constant attribute table, so use T_GIFO.
2477         */
2478
2479        Tcl_Obj *tmpObj = Tcl_NewStringObj(attributeName, -1);
2480        int result;
2481
2482        result = Tcl_GetIndexFromObj(NULL, tmpObj, attrTable, NULL, TCL_EXACT,
2483                indexPtr);
2484        TclDecrRefCount(tmpObj);
2485        if (listObj != NULL) {
2486            TclDecrRefCount(listObj);
2487        }
2488        return result;
2489    } else if (listObj != NULL) {
2490        /*
2491         * It's a non-constant attribute list, so do a literal search.
2492         */
2493
2494        int i, objc;
2495        Tcl_Obj **objv;
2496
2497        if (Tcl_ListObjGetElements(NULL, listObj, &objc, &objv) != TCL_OK) {
2498            TclDecrRefCount(listObj);
2499            return TCL_ERROR;
2500        }
2501        for (i=0 ; i<objc ; i++) {
2502            if (!strcmp(attributeName, TclGetString(objv[i]))) {
2503                TclDecrRefCount(listObj);
2504                *indexPtr = i;
2505                return TCL_OK;
2506            }
2507        }
2508        TclDecrRefCount(listObj);
2509        return TCL_ERROR;
2510    } else {
2511        return TCL_ERROR;
2512    }
2513}
2514
2515/*
2516 *----------------------------------------------------------------------
2517 *
2518 * Tcl_FSFileAttrsGet --
2519 *
2520 *      This function implements read access for the hookable 'file
2521 *      attributes' subcommand. The appropriate function for the filesystem to
2522 *      which pathPtr belongs will be called.
2523 *
2524 * Results:
2525 *      Standard Tcl return code. The object placed in objPtrRef (if TCL_OK
2526 *      was returned) is likely to have a refCount of zero. Either way we must
2527 *      either store it somewhere (e.g. the Tcl result), or Incr/Decr its
2528 *      refCount to ensure it is properly freed.
2529 *
2530 * Side effects:
2531 *      None.
2532 *
2533 *----------------------------------------------------------------------
2534 */
2535
2536int
2537Tcl_FSFileAttrsGet(
2538    Tcl_Interp *interp,         /* The interpreter for error reporting. */
2539    int index,                  /* index of the attribute command. */
2540    Tcl_Obj *pathPtr,           /* filename we are operating on. */
2541    Tcl_Obj **objPtrRef)        /* for output. */
2542{
2543    const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
2544
2545    if (fsPtr != NULL) {
2546        Tcl_FSFileAttrsGetProc *proc = fsPtr->fileAttrsGetProc;
2547        if (proc != NULL) {
2548            return (*proc)(interp, index, pathPtr, objPtrRef);
2549        }
2550    }
2551    Tcl_SetErrno(ENOENT);
2552    return -1;
2553}
2554
2555/*
2556 *----------------------------------------------------------------------
2557 *
2558 * Tcl_FSFileAttrsSet --
2559 *
2560 *      This function implements write access for the hookable 'file
2561 *      attributes' subcommand. The appropriate function for the filesystem to
2562 *      which pathPtr belongs will be called.
2563 *
2564 * Results:
2565 *      Standard Tcl return code.
2566 *
2567 * Side effects:
2568 *      None.
2569 *
2570 *----------------------------------------------------------------------
2571 */
2572
2573int
2574Tcl_FSFileAttrsSet(
2575    Tcl_Interp *interp,         /* The interpreter for error reporting. */
2576    int index,                  /* index of the attribute command. */
2577    Tcl_Obj *pathPtr,           /* filename we are operating on. */
2578    Tcl_Obj *objPtr)            /* Input value. */
2579{
2580    const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
2581
2582    if (fsPtr != NULL) {
2583        Tcl_FSFileAttrsSetProc *proc = fsPtr->fileAttrsSetProc;
2584        if (proc != NULL) {
2585            return (*proc)(interp, index, pathPtr, objPtr);
2586        }
2587    }
2588    Tcl_SetErrno(ENOENT);
2589    return -1;
2590}
2591
2592/*
2593 *----------------------------------------------------------------------
2594 *
2595 * Tcl_FSGetCwd --
2596 *
2597 *      This function replaces the library version of getcwd().
2598 *
2599 *      Most VFS's will *not* implement a 'cwdProc'. Tcl now maintains its own
2600 *      record (in a Tcl_Obj) of the cwd, and an attempt is made to synch this
2601 *      with the cwd's containing filesystem, if that filesystem provides a
2602 *      cwdProc (e.g. the native filesystem).
2603 *
2604 *      Note that if Tcl's cwd is not in the native filesystem, then of course
2605 *      Tcl's cwd and the native cwd are different: extensions should
2606 *      therefore ensure they only access the cwd through this function to
2607 *      avoid confusion.
2608 *
2609 *      If a global cwdPathPtr already exists, it is cached in the thread's
2610 *      private data structures and reference to the cached copy is returned,
2611 *      subject to a synchronisation attempt in that cwdPathPtr's fs.
2612 *
2613 *      Otherwise, the chain of functions that have been "inserted" into the
2614 *      filesystem will be called in succession until either a value other
2615 *      than NULL is returned, or the entire list is visited.
2616 *
2617 * Results:
2618 *      The result is a pointer to a Tcl_Obj specifying the current directory,
2619 *      or NULL if the current directory could not be determined. If NULL is
2620 *      returned, an error message is left in the interp's result.
2621 *
2622 *      The result already has its refCount incremented for the caller. When
2623 *      it is no longer needed, that refCount should be decremented.
2624 *
2625 * Side effects:
2626 *      Various objects may be freed and allocated.
2627 *
2628 *----------------------------------------------------------------------
2629 */
2630
2631Tcl_Obj *
2632Tcl_FSGetCwd(
2633    Tcl_Interp *interp)
2634{
2635    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&tclFsDataKey);
2636
2637    if (TclFSCwdPointerEquals(NULL)) {
2638        FilesystemRecord *fsRecPtr;
2639        Tcl_Obj *retVal = NULL;
2640
2641        /*
2642         * We've never been called before, try to find a cwd. Call each of the
2643         * "Tcl_GetCwd" function in succession. A non-NULL return value
2644         * indicates the particular function has succeeded.
2645         */
2646
2647        fsRecPtr = FsGetFirstFilesystem();
2648        while ((retVal == NULL) && (fsRecPtr != NULL)) {
2649            Tcl_FSGetCwdProc *proc = fsRecPtr->fsPtr->getCwdProc;
2650            if (proc != NULL) {
2651                if (fsRecPtr->fsPtr->version != TCL_FILESYSTEM_VERSION_1) {
2652                    ClientData retCd;
2653                    TclFSGetCwdProc2 *proc2 = (TclFSGetCwdProc2*)proc;
2654
2655                    retCd = (*proc2)(NULL);
2656                    if (retCd != NULL) {
2657                        Tcl_Obj *norm;
2658                        /* Looks like a new current directory */
2659                        retVal = (*fsRecPtr->fsPtr->internalToNormalizedProc)(
2660                                retCd);
2661                        Tcl_IncrRefCount(retVal);
2662                        norm = TclFSNormalizeAbsolutePath(interp,retVal,NULL);
2663                        if (norm != NULL) {
2664                            /*
2665                             * We found a cwd, which is now in our global
2666                             * storage. We must make a copy. Norm already has
2667                             * a refCount of 1.
2668                             *
2669                             * Threading issue: note that multiple threads at
2670                             * system startup could in principle call this
2671                             * function simultaneously. They will therefore
2672                             * each set the cwdPathPtr independently. That
2673                             * behaviour is a bit peculiar, but should be
2674                             * fine. Once we have a cwd, we'll always be in
2675                             * the 'else' branch below which is simpler.
2676                             */
2677
2678                            FsUpdateCwd(norm, retCd);
2679                            Tcl_DecrRefCount(norm);
2680                        } else {
2681                            (*fsRecPtr->fsPtr->freeInternalRepProc)(retCd);
2682                        }
2683                        Tcl_DecrRefCount(retVal);
2684                        retVal = NULL;
2685                        goto cdDidNotChange;
2686                    } else if (interp != NULL) {
2687                        Tcl_AppendResult(interp,
2688                                "error getting working directory name: ",
2689                                Tcl_PosixError(interp), NULL);
2690                    }
2691                } else {
2692                    retVal = (*proc)(interp);
2693                }
2694            }
2695            fsRecPtr = fsRecPtr->nextPtr;
2696        }
2697
2698        /*
2699         * Now the 'cwd' may NOT be normalized, at least on some platforms.
2700         * For the sake of efficiency, we want a completely normalized cwd at
2701         * all times.
2702         *
2703         * Finally, if retVal is NULL, we do not have a cwd, which could be
2704         * problematic.
2705         */
2706
2707        if (retVal != NULL) {
2708            Tcl_Obj *norm = TclFSNormalizeAbsolutePath(interp, retVal, NULL);
2709            if (norm != NULL) {
2710                /*
2711                 * We found a cwd, which is now in our global storage. We must
2712                 * make a copy. Norm already has a refCount of 1.
2713                 *
2714                 * Threading issue: note that multiple threads at system
2715                 * startup could in principle call this function
2716                 * simultaneously. They will therefore each set the cwdPathPtr
2717                 * independently. That behaviour is a bit peculiar, but should
2718                 * be fine. Once we have a cwd, we'll always be in the 'else'
2719                 * branch below which is simpler.
2720                 */
2721
2722                ClientData cd = (ClientData) Tcl_FSGetNativePath(norm);
2723                FsUpdateCwd(norm, TclNativeDupInternalRep(cd));
2724                Tcl_DecrRefCount(norm);
2725            }
2726            Tcl_DecrRefCount(retVal);
2727        }
2728    } else {
2729        /*
2730         * We already have a cwd cached, but we want to give the filesystem it
2731         * is in a chance to check whether that cwd has changed, or is perhaps
2732         * no longer accessible. This allows an error to be thrown if, say,
2733         * the permissions on that directory have changed.
2734         */
2735
2736        const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(tsdPtr->cwdPathPtr);
2737
2738        /*
2739         * If the filesystem couldn't be found, or if no cwd function exists
2740         * for this filesystem, then we simply assume the cached cwd is ok.
2741         * If we do call a cwd, we must watch for errors (if the cwd returns
2742         * NULL). This ensures that, say, on Unix if the permissions of the
2743         * cwd change, 'pwd' does actually throw the correct error in Tcl.
2744         * (This is tested for in the test suite on unix).
2745         */
2746
2747        if (fsPtr != NULL) {
2748            Tcl_FSGetCwdProc *proc = fsPtr->getCwdProc;
2749            ClientData retCd = NULL;
2750            if (proc != NULL) {
2751                Tcl_Obj *retVal;
2752                if (fsPtr->version != TCL_FILESYSTEM_VERSION_1) {
2753                    TclFSGetCwdProc2 *proc2 = (TclFSGetCwdProc2*)proc;
2754
2755                    retCd = (*proc2)(tsdPtr->cwdClientData);
2756                    if (retCd == NULL && interp != NULL) {
2757                        Tcl_AppendResult(interp,
2758                                "error getting working directory name: ",
2759                                Tcl_PosixError(interp), NULL);
2760                    }
2761
2762                    if (retCd == tsdPtr->cwdClientData) {
2763                        goto cdDidNotChange;
2764                    }
2765
2766                    /*
2767                     * Looks like a new current directory.
2768                     */
2769
2770                    retVal = (*fsPtr->internalToNormalizedProc)(retCd);
2771                    Tcl_IncrRefCount(retVal);
2772                } else {
2773                    retVal = (*proc)(interp);
2774                }
2775                if (retVal != NULL) {
2776                    Tcl_Obj *norm = TclFSNormalizeAbsolutePath(interp,
2777                            retVal, NULL);
2778
2779                    /*
2780                     * Check whether cwd has changed from the value previously
2781                     * stored in cwdPathPtr. Really 'norm' shouldn't be NULL,
2782                     * but we are careful.
2783                     */
2784
2785                    if (norm == NULL) {
2786                        /* Do nothing */
2787                        if (retCd != NULL) {
2788                            (*fsPtr->freeInternalRepProc)(retCd);
2789                        }
2790                    } else if (norm == tsdPtr->cwdPathPtr) {
2791                        goto cdEqual;
2792                    } else {
2793                        /*
2794                         * Note that both 'norm' and 'tsdPtr->cwdPathPtr' are
2795                         * normalized paths. Therefore we can be more
2796                         * efficient than calling 'Tcl_FSEqualPaths', and in
2797                         * addition avoid a nasty infinite loop bug when
2798                         * trying to normalize tsdPtr->cwdPathPtr.
2799                         */
2800
2801                        int len1, len2;
2802                        char *str1, *str2;
2803
2804                        str1 = Tcl_GetStringFromObj(tsdPtr->cwdPathPtr, &len1);
2805                        str2 = Tcl_GetStringFromObj(norm, &len2);
2806                        if ((len1 == len2) && (strcmp(str1, str2) == 0)) {
2807                            /*
2808                             * If the paths were equal, we can be more
2809                             * efficient and retain the old path object which
2810                             * will probably already be shared. In this case
2811                             * we can simply free the normalized path we just
2812                             * calculated.
2813                             */
2814
2815                        cdEqual:
2816                            Tcl_DecrRefCount(norm);
2817                            if (retCd != NULL) {
2818                                (*fsPtr->freeInternalRepProc)(retCd);
2819                            }
2820                        } else {
2821                            FsUpdateCwd(norm, retCd);
2822                            Tcl_DecrRefCount(norm);
2823                        }
2824                    }
2825                    Tcl_DecrRefCount(retVal);
2826                } else {
2827                    /*
2828                     * The 'cwd' function returned an error; reset the cwd.
2829                     */
2830
2831                    FsUpdateCwd(NULL, NULL);
2832                }
2833            }
2834        }
2835    }
2836
2837  cdDidNotChange:
2838    if (tsdPtr->cwdPathPtr != NULL) {
2839        Tcl_IncrRefCount(tsdPtr->cwdPathPtr);
2840    }
2841
2842    return tsdPtr->cwdPathPtr;
2843}
2844
2845/*
2846 *----------------------------------------------------------------------
2847 *
2848 * Tcl_FSChdir --
2849 *
2850 *      This function replaces the library version of chdir().
2851 *
2852 *      The path is normalized and then passed to the filesystem which claims
2853 *      it.
2854 *
2855 * Results:
2856 *      See chdir() documentation. If successful, we keep a record of the
2857 *      successful path in cwdPathPtr for subsequent calls to getcwd.
2858 *
2859 * Side effects:
2860 *      See chdir() documentation. The global cwdPathPtr may change value.
2861 *
2862 *----------------------------------------------------------------------
2863 */
2864
2865int
2866Tcl_FSChdir(
2867    Tcl_Obj *pathPtr)
2868{
2869    const Tcl_Filesystem *fsPtr;
2870    int retVal = -1;
2871
2872    if (Tcl_FSGetNormalizedPath(NULL, pathPtr) == NULL) {
2873        Tcl_SetErrno(ENOENT);
2874        return retVal;
2875    }
2876
2877    fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
2878    if (fsPtr != NULL) {
2879        Tcl_FSChdirProc *proc = fsPtr->chdirProc;
2880        if (proc != NULL) {
2881            /*
2882             * If this fails, an appropriate errno will have been stored using
2883             * 'Tcl_SetErrno()'.
2884             */
2885
2886            retVal = (*proc)(pathPtr);
2887        } else {
2888            /*
2889             * Fallback on stat-based implementation.
2890             */
2891
2892            Tcl_StatBuf buf;
2893
2894            /*
2895             * If the file can be stat'ed and is a directory and is readable,
2896             * then we can chdir. If any of these actions fail, then
2897             * 'Tcl_SetErrno()' should automatically have been called to set
2898             * an appropriate error code
2899             */
2900
2901            if ((Tcl_FSStat(pathPtr, &buf) == 0) && (S_ISDIR(buf.st_mode))
2902                    && (Tcl_FSAccess(pathPtr, R_OK) == 0)) {
2903                /*
2904                 * We allow the chdir.
2905                 */
2906
2907                retVal = 0;
2908            }
2909        }
2910    } else {
2911        Tcl_SetErrno(ENOENT);
2912    }
2913
2914    /*
2915     * The cwd changed, or an error was thrown. If an error was thrown, we can
2916     * just continue (and that will report the error to the user). If there
2917     * was no error we must assume that the cwd was actually changed to the
2918     * normalized value we calculated above, and we must therefore cache that
2919     * information.
2920     */
2921
2922    /*
2923     * If the filesystem in question has a getCwdProc, then the correct logic
2924     * which performs the part below is already part of the Tcl_FSGetCwd()
2925     * call, so no need to replicate it again. This will have a side effect
2926     * though. The private authoritative representation of the current working
2927     * directory stored in cwdPathPtr in static memory will be out-of-sync
2928     * with the real OS-maintained value. The first call to Tcl_FSGetCwd will
2929     * however recalculate the private copy to match the OS-value so
2930     * everything will work right.
2931     *
2932     * However, if there is no getCwdProc, then we _must_ update our private
2933     * storage of the cwd, since this is the only opportunity to do that!
2934     *
2935     * Note: We currently call this block of code irrespective of whether
2936     * there was a getCwdProc or not, but the code should all in principle
2937     * work if we only call this block if fsPtr->getCwdProc == NULL.
2938     */
2939
2940    if (retVal == 0) {
2941        /*
2942         * Note that this normalized path may be different to what we found
2943         * above (or at least a different object), if the filesystem epoch
2944         * changed recently. This can actually happen with scripted documents
2945         * very easily. Therefore we ask for the normalized path again (the
2946         * correct value will have been cached as a result of the
2947         * Tcl_FSGetFileSystemForPath call above anyway).
2948         */
2949
2950        Tcl_Obj *normDirName = Tcl_FSGetNormalizedPath(NULL, pathPtr);
2951
2952        if (normDirName == NULL) {
2953            /* Not really true, but what else to do? */
2954            Tcl_SetErrno(ENOENT);
2955            return -1;
2956        }
2957
2958        if (fsPtr == &tclNativeFilesystem) {
2959            /*
2960             * For the native filesystem, we keep a cache of the native
2961             * representation of the cwd. But, we want to do that for the
2962             * exact format that is returned by 'getcwd' (so that we can later
2963             * compare the two representations for equality), which might not
2964             * be exactly the same char-string as the native representation of
2965             * the fully normalized path (e.g. on Windows there's a
2966             * forward-slash vs backslash difference). Hence we ask for this
2967             * again here. On Unix it might actually be true that we always
2968             * have the correct form in the native rep in which case we could
2969             * simply use:
2970             *          cd = Tcl_FSGetNativePath(pathPtr);
2971             * instead. This should be examined by someone on Unix.
2972             */
2973
2974            ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&tclFsDataKey);
2975            ClientData cd;
2976            ClientData oldcd = tsdPtr->cwdClientData;
2977
2978            /*
2979             * Assumption we are using a filesystem version 2.
2980             */
2981
2982            TclFSGetCwdProc2 *proc2 = (TclFSGetCwdProc2*)fsPtr->getCwdProc;
2983            cd = (*proc2)(oldcd);
2984            if (cd != oldcd) {
2985                FsUpdateCwd(normDirName, cd);
2986            }
2987        } else {
2988            FsUpdateCwd(normDirName, NULL);
2989        }
2990    }
2991
2992    return retVal;
2993}
2994
2995/*
2996 *----------------------------------------------------------------------
2997 *
2998 * Tcl_FSLoadFile --
2999 *
3000 *      Dynamically loads a binary code file into memory and returns the
3001 *      addresses of two functions within that file, if they are defined. The
3002 *      appropriate function for the filesystem to which pathPtr belongs will
3003 *      be called.
3004 *
3005 *      Note that the native filesystem doesn't actually assume 'pathPtr' is a
3006 *      path. Rather it assumes pathPtr is either a path or just the name
3007 *      (tail) of a file which can be found somewhere in the environment's
3008 *      loadable path. This behaviour is not very compatible with virtual
3009 *      filesystems (and has other problems documented in the load man-page),
3010 *      so it is advised that full paths are always used.
3011 *
3012 * Results:
3013 *      A standard Tcl completion code. If an error occurs, an error message
3014 *      is left in the interp's result.
3015 *
3016 * Side effects:
3017 *      New code suddenly appears in memory. This may later be unloaded by
3018 *      passing the clientData to the unloadProc.
3019 *
3020 *----------------------------------------------------------------------
3021 */
3022
3023int
3024Tcl_FSLoadFile(
3025    Tcl_Interp *interp,         /* Used for error reporting. */
3026    Tcl_Obj *pathPtr,           /* Name of the file containing the desired
3027                                 * code. */
3028    const char *sym1, const char *sym2,
3029                                /* Names of two functions to look up in the
3030                                 * file's symbol table. */
3031    Tcl_PackageInitProc **proc1Ptr, Tcl_PackageInitProc **proc2Ptr,
3032                                /* Where to return the addresses corresponding
3033                                 * to sym1 and sym2. */
3034    Tcl_LoadHandle *handlePtr,  /* Filled with token for dynamically loaded
3035                                 * file which will be passed back to
3036                                 * (*unloadProcPtr)() to unload the file. */
3037    Tcl_FSUnloadFileProc **unloadProcPtr)
3038                                /* Filled with address of Tcl_FSUnloadFileProc
3039                                 * function which should be used for this
3040                                 * file. */
3041{
3042    const char *symbols[2];
3043    Tcl_PackageInitProc **procPtrs[2];
3044    ClientData clientData;
3045    int res;
3046
3047    /*
3048     * Initialize the arrays.
3049     */
3050
3051    symbols[0] = sym1;
3052    symbols[1] = sym2;
3053    procPtrs[0] = proc1Ptr;
3054    procPtrs[1] = proc2Ptr;
3055
3056    /*
3057     * Perform the load.
3058     */
3059
3060    res = TclLoadFile(interp, pathPtr, 2, symbols, procPtrs, handlePtr,
3061            &clientData, unloadProcPtr);
3062
3063    /*
3064     * Due to an unfortunate mis-design in Tcl 8.4 fs, when loading a shared
3065     * library, we don't keep the loadHandle (for TclpFindSymbol) and the
3066     * clientData (for the unloadProc) separately. In fact we effectively
3067     * throw away the loadHandle and only use the clientData. It just so
3068     * happens, for the native filesystem only, that these two are identical.
3069     *
3070     * This also means that the signatures Tcl_FSUnloadFileProc and
3071     * Tcl_FSLoadFileProc are both misleading.
3072     */
3073
3074    *handlePtr = (Tcl_LoadHandle) clientData;
3075    return res;
3076}
3077
3078/*
3079 *----------------------------------------------------------------------
3080 *
3081 * TclLoadFile --
3082 *
3083 *      Dynamically loads a binary code file into memory and returns the
3084 *      addresses of a number of given functions within that file, if they are
3085 *      defined. The appropriate function for the filesystem to which pathPtr
3086 *      belongs will be called.
3087 *
3088 *      Note that the native filesystem doesn't actually assume 'pathPtr' is a
3089 *      path. Rather it assumes pathPtr is either a path or just the name
3090 *      (tail) of a file which can be found somewhere in the environment's
3091 *      loadable path. This behaviour is not very compatible with virtual
3092 *      filesystems (and has other problems documented in the load man-page),
3093 *      so it is advised that full paths are always used.
3094 *
3095 *      This function is currently private to Tcl. It may be exported in the
3096 *      future and its interface fixed (but we should clean up the
3097 *      loadHandle/clientData confusion at that time -- see the above comments
3098 *      in Tcl_FSLoadFile for details). For a public function, see
3099 *      Tcl_FSLoadFile.
3100 *
3101 * Results:
3102 *      A standard Tcl completion code. If an error occurs, an error message
3103 *      is left in the interp's result.
3104 *
3105 * Side effects:
3106 *      New code suddenly appears in memory. This may later be unloaded by
3107 *      passing the clientData to the unloadProc.
3108 *
3109 *----------------------------------------------------------------------
3110 */
3111
3112int
3113TclLoadFile(
3114    Tcl_Interp *interp,         /* Used for error reporting. */
3115    Tcl_Obj *pathPtr,           /* Name of the file containing the desired
3116                                 * code. */
3117    int symc,                   /* Number of symbols/procPtrs in the next two
3118                                 * arrays. */
3119    const char *symbols[],      /* Names of functions to look up in the file's
3120                                 * symbol table. */
3121    Tcl_PackageInitProc **procPtrs[],
3122                                /* Where to return the addresses corresponding
3123                                 * to symbols[]. */
3124    Tcl_LoadHandle *handlePtr,  /* Filled with token for shared library
3125                                 * information which can be used in
3126                                 * TclpFindSymbol. */
3127    ClientData *clientDataPtr,  /* Filled with token for dynamically loaded
3128                                 * file which will be passed back to
3129                                 * (*unloadProcPtr)() to unload the file. */
3130    Tcl_FSUnloadFileProc **unloadProcPtr)
3131                                /* Filled with address of Tcl_FSUnloadFileProc
3132                                 * function which should be used for this
3133                                 * file. */
3134{
3135    const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
3136    Tcl_FSLoadFileProc *proc;
3137    Tcl_Filesystem *copyFsPtr;
3138    Tcl_Obj *copyToPtr;
3139    Tcl_LoadHandle newLoadHandle = NULL;
3140    ClientData newClientData = NULL;
3141    Tcl_FSUnloadFileProc *newUnloadProcPtr = NULL;
3142    FsDivertLoad *tvdlPtr;
3143    int retVal;
3144
3145    if (fsPtr == NULL) {
3146        Tcl_SetErrno(ENOENT);
3147        return TCL_ERROR;
3148    }
3149
3150    proc = fsPtr->loadFileProc;
3151    if (proc != NULL) {
3152        int retVal = (*proc)(interp, pathPtr, handlePtr, unloadProcPtr);
3153        if (retVal == TCL_OK) {
3154            if (*handlePtr == NULL) {
3155                return TCL_ERROR;
3156            }
3157
3158            /*
3159             * Copy this across, since both are equal for the native fs.
3160             */
3161
3162            *clientDataPtr = (ClientData)*handlePtr;
3163            Tcl_ResetResult(interp);
3164            goto resolveSymbols;
3165        }
3166        if (Tcl_GetErrno() != EXDEV) {
3167            return retVal;
3168        }
3169    }
3170
3171    /*
3172     * The filesystem doesn't support 'load', so we fall back on the following
3173     * technique:
3174     *
3175     * First check if it is readable -- and exists!
3176     */
3177
3178    if (Tcl_FSAccess(pathPtr, R_OK) != 0) {
3179        Tcl_AppendResult(interp, "couldn't load library \"",
3180                Tcl_GetString(pathPtr), "\": ", Tcl_PosixError(interp), NULL);
3181        return TCL_ERROR;
3182    }
3183
3184#ifdef TCL_LOAD_FROM_MEMORY
3185    /*
3186     * The platform supports loading code from memory, so ask for a buffer of
3187     * the appropriate size, read the file into it and load the code from the
3188     * buffer:
3189     */
3190
3191    {
3192        int ret, size;
3193        void *buffer;
3194        Tcl_StatBuf statBuf;
3195        Tcl_Channel data;
3196
3197        ret = Tcl_FSStat(pathPtr, &statBuf);
3198        if (ret < 0) {
3199            goto mustCopyToTempAnyway;
3200        }
3201        size = (int) statBuf.st_size;
3202
3203        /*
3204         * Tcl_Read takes an int: check that file size isn't wide.
3205         */
3206
3207        if (size != (Tcl_WideInt) statBuf.st_size) {
3208            goto mustCopyToTempAnyway;
3209        }
3210        data = Tcl_FSOpenFileChannel(interp, pathPtr, "rb", 0666);
3211        if (!data) {
3212            goto mustCopyToTempAnyway;
3213        }
3214        buffer = TclpLoadMemoryGetBuffer(interp, size);
3215        if (!buffer) {
3216            Tcl_Close(interp, data);
3217            goto mustCopyToTempAnyway;
3218        }
3219        ret = Tcl_Read(data, buffer, size);
3220        Tcl_Close(interp, data);
3221        ret = TclpLoadMemory(interp, buffer, size, ret, handlePtr,
3222                unloadProcPtr);
3223        if (ret == TCL_OK && *handlePtr != NULL) {
3224            *clientDataPtr = (ClientData) *handlePtr;
3225            goto resolveSymbols;
3226        }
3227    }
3228
3229  mustCopyToTempAnyway:
3230    Tcl_ResetResult(interp);
3231#endif
3232
3233    /*
3234     * Get a temporary filename to use, first to copy the file into, and then
3235     * to load.
3236     */
3237
3238    copyToPtr = TclpTempFileName();
3239    if (copyToPtr == NULL) {
3240        Tcl_AppendResult(interp, "couldn't create temporary file: ",
3241                Tcl_PosixError(interp), NULL);
3242        return TCL_ERROR;
3243    }
3244    Tcl_IncrRefCount(copyToPtr);
3245
3246    copyFsPtr = Tcl_FSGetFileSystemForPath(copyToPtr);
3247    if ((copyFsPtr == NULL) || (copyFsPtr == fsPtr)) {
3248        /*
3249         * We already know we can't use Tcl_FSLoadFile from this filesystem,
3250         * and we must avoid a possible infinite loop. Try to delete the file
3251         * we probably created, and then exit.
3252         */
3253
3254        Tcl_FSDeleteFile(copyToPtr);
3255        Tcl_DecrRefCount(copyToPtr);
3256        Tcl_AppendResult(interp, "couldn't load from current filesystem",NULL);
3257        return TCL_ERROR;
3258    }
3259
3260    if (TclCrossFilesystemCopy(interp, pathPtr, copyToPtr) != TCL_OK) {
3261        /*
3262         * Cross-platform copy failed.
3263         */
3264
3265        Tcl_FSDeleteFile(copyToPtr);
3266        Tcl_DecrRefCount(copyToPtr);
3267        return TCL_ERROR;
3268    }
3269
3270#if !defined(__WIN32__)
3271    /*
3272     * Do we need to set appropriate permissions on the file? This may be
3273     * required on some systems. On Unix we could loop over the file
3274     * attributes, and set any that are called "-permissions" to 0700. However
3275     * we just do this directly, like this:
3276     */
3277
3278    {
3279        int index;
3280        Tcl_Obj *perm;
3281
3282        TclNewLiteralStringObj(perm, "0700");
3283        Tcl_IncrRefCount(perm);
3284        if (TclFSFileAttrIndex(copyToPtr, "-permissions", &index) == TCL_OK) {
3285            Tcl_FSFileAttrsSet(NULL, index, copyToPtr, perm);
3286        }
3287        Tcl_DecrRefCount(perm);
3288    }
3289#endif
3290
3291    /*
3292     * We need to reset the result now, because the cross-filesystem copy may
3293     * have stored the number of bytes in the result.
3294     */
3295
3296    Tcl_ResetResult(interp);
3297
3298    retVal = TclLoadFile(interp, copyToPtr, symc, symbols, procPtrs,
3299            &newLoadHandle, &newClientData, &newUnloadProcPtr);
3300    if (retVal != TCL_OK) {
3301        /*
3302         * The file didn't load successfully.
3303         */
3304
3305        Tcl_FSDeleteFile(copyToPtr);
3306        Tcl_DecrRefCount(copyToPtr);
3307        return retVal;
3308    }
3309
3310    /*
3311     * Try to delete the file immediately - this is possible in some OSes, and
3312     * avoids any worries about leaving the copy laying around on exit.
3313     */
3314
3315    if (Tcl_FSDeleteFile(copyToPtr) == TCL_OK) {
3316        Tcl_DecrRefCount(copyToPtr);
3317
3318        /*
3319         * We tell our caller about the real shared library which was loaded.
3320         * Note that this does mean that the package list maintained by 'load'
3321         * will store the original (vfs) path alongside the temporary load
3322         * handle and unload proc ptr.
3323         */
3324
3325        (*handlePtr) = newLoadHandle;
3326        (*clientDataPtr) = newClientData;
3327        (*unloadProcPtr) = newUnloadProcPtr;
3328        Tcl_ResetResult(interp);
3329        return TCL_OK;
3330    }
3331
3332    /*
3333     * When we unload this file, we need to divert the unloading so we can
3334     * unload and cleanup the temporary file correctly.
3335     */
3336
3337    tvdlPtr = (FsDivertLoad *) ckalloc(sizeof(FsDivertLoad));
3338
3339    /*
3340     * Remember three pieces of information. This allows us to cleanup the
3341     * diverted load completely, on platforms which allow proper unloading of
3342     * code.
3343     */
3344
3345    tvdlPtr->loadHandle = newLoadHandle;
3346    tvdlPtr->unloadProcPtr = newUnloadProcPtr;
3347
3348    if (copyFsPtr != &tclNativeFilesystem) {
3349        /*
3350         * copyToPtr is already incremented for this reference.
3351         */
3352
3353        tvdlPtr->divertedFile = copyToPtr;
3354
3355        /*
3356         * This is the filesystem we loaded it into. Since we have a reference
3357         * to 'copyToPtr', we already have a refCount on this filesystem, so
3358         * we don't need to worry about it disappearing on us.
3359         */
3360
3361        tvdlPtr->divertedFilesystem = copyFsPtr;
3362        tvdlPtr->divertedFileNativeRep = NULL;
3363    } else {
3364        /*
3365         * We need the native rep.
3366         */
3367
3368        tvdlPtr->divertedFileNativeRep = TclNativeDupInternalRep(
3369                Tcl_FSGetInternalRep(copyToPtr, copyFsPtr));
3370
3371        /*
3372         * We don't need or want references to the copied Tcl_Obj or the
3373         * filesystem if it is the native one.
3374         */
3375
3376        tvdlPtr->divertedFile = NULL;
3377        tvdlPtr->divertedFilesystem = NULL;
3378        Tcl_DecrRefCount(copyToPtr);
3379    }
3380
3381    copyToPtr = NULL;
3382    (*handlePtr) = newLoadHandle;
3383    (*clientDataPtr) = (ClientData) tvdlPtr;
3384    (*unloadProcPtr) = &FSUnloadTempFile;
3385
3386    Tcl_ResetResult(interp);
3387    return retVal;
3388
3389  resolveSymbols:
3390    {
3391        int i;
3392
3393        for (i=0 ; i<symc ; i++) {
3394            if (symbols[i] != NULL) {
3395                *procPtrs[i] = TclpFindSymbol(interp, *handlePtr, symbols[i]);
3396            }
3397        }
3398    }
3399    return TCL_OK;
3400}
3401/*
3402 * This function used to be in the platform specific directories, but it has
3403 * now been made to work cross-platform
3404 */
3405
3406int
3407TclpLoadFile(
3408    Tcl_Interp *interp,         /* Used for error reporting. */
3409    Tcl_Obj *pathPtr,           /* Name of the file containing the desired
3410                                 * code (UTF-8). */
3411    const char *sym1, CONST char *sym2,
3412                                /* Names of two functions to look up in the
3413                                 * file's symbol table. */
3414    Tcl_PackageInitProc **proc1Ptr, Tcl_PackageInitProc **proc2Ptr,
3415                                /* Where to return the addresses corresponding
3416                                 * to sym1 and sym2. */
3417    ClientData *clientDataPtr,  /* Filled with token for dynamically loaded
3418                                 * file which will be passed back to
3419                                 * (*unloadProcPtr)() to unload the file. */
3420    Tcl_FSUnloadFileProc **unloadProcPtr)
3421                                /* Filled with address of Tcl_FSUnloadFileProc
3422                                 * function which should be used for this
3423                                 * file. */
3424{
3425    Tcl_LoadHandle handle = NULL;
3426    int res;
3427
3428    res = TclpDlopen(interp, pathPtr, &handle, unloadProcPtr);
3429
3430    if (res != TCL_OK) {
3431        return res;
3432    }
3433
3434    if (handle == NULL) {
3435        return TCL_ERROR;
3436    }
3437
3438    *clientDataPtr = (ClientData) handle;
3439
3440    *proc1Ptr = TclpFindSymbol(interp, handle, sym1);
3441    *proc2Ptr = TclpFindSymbol(interp, handle, sym2);
3442    return TCL_OK;
3443}
3444
3445/*
3446 *---------------------------------------------------------------------------
3447 *
3448 * FSUnloadTempFile --
3449 *
3450 *      This function is called when we loaded a library of code via an
3451 *      intermediate temporary file. This function ensures the library is
3452 *      correctly unloaded and the temporary file is correctly deleted.
3453 *
3454 * Results:
3455 *      None.
3456 *
3457 * Side effects:
3458 *      The effects of the 'unload' function called, and of course the
3459 *      temporary file will be deleted.
3460 *
3461 *---------------------------------------------------------------------------
3462 */
3463
3464static void
3465FSUnloadTempFile(
3466    Tcl_LoadHandle loadHandle)  /* loadHandle returned by a previous call to
3467                                 * Tcl_FSLoadFile(). The loadHandle is a token
3468                                 * that represents the loaded file. */
3469{
3470    FsDivertLoad *tvdlPtr = (FsDivertLoad *) loadHandle;
3471
3472    /*
3473     * This test should never trigger, since we give the client data in the
3474     * function above.
3475     */
3476
3477    if (tvdlPtr == NULL) {
3478        return;
3479    }
3480
3481    /*
3482     * Call the real 'unloadfile' proc we actually used. It is very important
3483     * that we call this first, so that the shared library is actually
3484     * unloaded by the OS. Otherwise, the following 'delete' may well fail
3485     * because the shared library is still in use.
3486     */
3487
3488    if (tvdlPtr->unloadProcPtr != NULL) {
3489        (*tvdlPtr->unloadProcPtr)(tvdlPtr->loadHandle);
3490    }
3491
3492    if (tvdlPtr->divertedFilesystem == NULL) {
3493        /*
3494         * It was the native filesystem, and we have a special function
3495         * available just for this purpose, which we know works even at this
3496         * late stage.
3497         */
3498
3499        TclpDeleteFile(tvdlPtr->divertedFileNativeRep);
3500        NativeFreeInternalRep(tvdlPtr->divertedFileNativeRep);
3501
3502    } else {
3503        /*
3504         * Remove the temporary file we created. Note, we may crash here
3505         * because encodings have been taken down already.
3506         */
3507
3508        if (tvdlPtr->divertedFilesystem->deleteFileProc(tvdlPtr->divertedFile)
3509                != TCL_OK) {
3510            /*
3511             * The above may have failed because the filesystem, or something
3512             * it depends upon (e.g. encodings) have been taken down because
3513             * Tcl is exiting.
3514             *
3515             * We may need to work out how to delete this file more robustly
3516             * (or give the filesystem the information it needs to delete the
3517             * file more robustly).
3518             *
3519             * In particular, one problem might be that the filesystem cannot
3520             * extract the information it needs from the above path object
3521             * because Tcl's entire filesystem apparatus (the code in this
3522             * file) has been finalized, and it refuses to pass the internal
3523             * representation to the filesystem.
3524             */
3525        }
3526
3527        /*
3528         * And free up the allocations. This will also of course remove a
3529         * refCount from the Tcl_Filesystem to which this file belongs, which
3530         * could then free up the filesystem if we are exiting.
3531         */
3532
3533        Tcl_DecrRefCount(tvdlPtr->divertedFile);
3534    }
3535
3536    ckfree((char*)tvdlPtr);
3537}
3538
3539/*
3540 *---------------------------------------------------------------------------
3541 *
3542 * Tcl_FSLink --
3543 *
3544 *      This function replaces the library version of readlink() and can also
3545 *      be used to make links. The appropriate function for the filesystem to
3546 *      which pathPtr belongs will be called.
3547 *
3548 * Results:
3549 *      If toPtr is NULL, then the result is a Tcl_Obj specifying the contents
3550 *      of the symbolic link given by 'pathPtr', or NULL if the symbolic link
3551 *      could not be read. The result is owned by the caller, which should
3552 *      call Tcl_DecrRefCount when the result is no longer needed.
3553 *
3554 *      If toPtr is non-NULL, then the result is toPtr if the link action was
3555 *      successful, or NULL if not. In this case the result has no additional
3556 *      reference count, and need not be freed. The actual action to perform
3557 *      is given by the 'linkAction' flags, which is an or'd combination of:
3558 *
3559 *              TCL_CREATE_SYMBOLIC_LINK
3560 *              TCL_CREATE_HARD_LINK
3561 *
3562 *      Note that most filesystems will not support linking across to
3563 *      different filesystems, so this function will usually fail unless toPtr
3564 *      is in the same FS as pathPtr.
3565 *
3566 * Side effects:
3567 *      See readlink() documentation. A new filesystem link object may appear.
3568 *
3569 *---------------------------------------------------------------------------
3570 */
3571
3572Tcl_Obj *
3573Tcl_FSLink(
3574    Tcl_Obj *pathPtr,           /* Path of file to readlink or link */
3575    Tcl_Obj *toPtr,             /* NULL or path to be linked to */
3576    int linkAction)             /* Action to perform */
3577{
3578    const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
3579
3580    if (fsPtr != NULL) {
3581        Tcl_FSLinkProc *proc = fsPtr->linkProc;
3582
3583        if (proc != NULL) {
3584            return (*proc)(pathPtr, toPtr, linkAction);
3585        }
3586    }
3587
3588    /*
3589     * If S_IFLNK isn't defined it means that the machine doesn't support
3590     * symbolic links, so the file can't possibly be a symbolic link. Generate
3591     * an EINVAL error, which is what happens on machines that do support
3592     * symbolic links when you invoke readlink on a file that isn't a symbolic
3593     * link.
3594     */
3595
3596#ifndef S_IFLNK
3597    errno = EINVAL;
3598#else
3599    Tcl_SetErrno(ENOENT);
3600#endif /* S_IFLNK */
3601    return NULL;
3602}
3603
3604/*
3605 *---------------------------------------------------------------------------
3606 *
3607 * Tcl_FSListVolumes --
3608 *
3609 *      Lists the currently mounted volumes. The chain of functions that have
3610 *      been "inserted" into the filesystem will be called in succession; each
3611 *      may return a list of volumes, all of which are added to the result
3612 *      until all mounted file systems are listed.
3613 *
3614 *      Notice that we assume the lists returned by each filesystem (if non
3615 *      NULL) have been given a refCount for us already. However, we are NOT
3616 *      allowed to hang on to the list itself (it belongs to the filesystem we
3617 *      called). Therefore we quite naturally add its contents to the result
3618 *      we are building, and then decrement the refCount.
3619 *
3620 * Results:
3621 *      The list of volumes, in an object which has refCount 0.
3622 *
3623 * Side effects:
3624 *      None
3625 *
3626 *---------------------------------------------------------------------------
3627 */
3628
3629Tcl_Obj*
3630Tcl_FSListVolumes(void)
3631{
3632    FilesystemRecord *fsRecPtr;
3633    Tcl_Obj *resultPtr = Tcl_NewObj();
3634
3635    /*
3636     * Call each of the "listVolumes" function in succession. A non-NULL
3637     * return value indicates the particular function has succeeded. We call
3638     * all the functions registered, since we want a list of all drives from
3639     * all filesystems.
3640     */
3641
3642    fsRecPtr = FsGetFirstFilesystem();
3643    while (fsRecPtr != NULL) {
3644        Tcl_FSListVolumesProc *proc = fsRecPtr->fsPtr->listVolumesProc;
3645        if (proc != NULL) {
3646            Tcl_Obj *thisFsVolumes = (*proc)();
3647            if (thisFsVolumes != NULL) {
3648                Tcl_ListObjAppendList(NULL, resultPtr, thisFsVolumes);
3649                Tcl_DecrRefCount(thisFsVolumes);
3650            }
3651        }
3652        fsRecPtr = fsRecPtr->nextPtr;
3653    }
3654
3655    return resultPtr;
3656}
3657
3658/*
3659 *---------------------------------------------------------------------------
3660 *
3661 * FsListMounts --
3662 *
3663 *      List all mounts within the given directory, which match the given
3664 *      pattern.
3665 *
3666 * Results:
3667 *      The list of mounts, in a list object which has refCount 0, or NULL if
3668 *      we didn't even find any filesystems to try to list mounts.
3669 *
3670 * Side effects:
3671 *      None
3672 *
3673 *---------------------------------------------------------------------------
3674 */
3675
3676static Tcl_Obj *
3677FsListMounts(
3678    Tcl_Obj *pathPtr,           /* Contains path to directory to search. */
3679    const char *pattern)        /* Pattern to match against. */
3680{
3681    FilesystemRecord *fsRecPtr;
3682    Tcl_GlobTypeData mountsOnly = { TCL_GLOB_TYPE_MOUNT, 0, NULL, NULL };
3683    Tcl_Obj *resultPtr = NULL;
3684
3685    /*
3686     * Call each of the "matchInDirectory" functions in succession, with the
3687     * specific type information 'mountsOnly'. A non-NULL return value
3688     * indicates the particular function has succeeded. We call all the
3689     * functions registered, since we want a list from each filesystems.
3690     */
3691
3692    fsRecPtr = FsGetFirstFilesystem();
3693    while (fsRecPtr != NULL) {
3694        if (fsRecPtr->fsPtr != &tclNativeFilesystem) {
3695            Tcl_FSMatchInDirectoryProc *proc =
3696                    fsRecPtr->fsPtr->matchInDirectoryProc;
3697            if (proc != NULL) {
3698                if (resultPtr == NULL) {
3699                    resultPtr = Tcl_NewObj();
3700                }
3701                (*proc)(NULL, resultPtr, pathPtr, pattern, &mountsOnly);
3702            }
3703        }
3704        fsRecPtr = fsRecPtr->nextPtr;
3705    }
3706
3707    return resultPtr;
3708}
3709
3710/*
3711 *---------------------------------------------------------------------------
3712 *
3713 * Tcl_FSSplitPath --
3714 *
3715 *      This function takes the given Tcl_Obj, which should be a valid path,
3716 *      and returns a Tcl List object containing each segment of that path as
3717 *      an element.
3718 *
3719 * Results:
3720 *      Returns list object with refCount of zero. If the passed in lenPtr is
3721 *      non-NULL, we use it to return the number of elements in the returned
3722 *      list.
3723 *
3724 * Side effects:
3725 *      None.
3726 *
3727 *---------------------------------------------------------------------------
3728 */
3729
3730Tcl_Obj *
3731Tcl_FSSplitPath(
3732    Tcl_Obj *pathPtr,           /* Path to split. */
3733    int *lenPtr)                /* int to store number of path elements. */
3734{
3735    Tcl_Obj *result = NULL;     /* Needed only to prevent gcc warnings. */
3736    Tcl_Filesystem *fsPtr;
3737    char separator = '/';
3738    int driveNameLength;
3739    char *p;
3740
3741    /*
3742     * Perform platform specific splitting.
3743     */
3744
3745    if (TclFSGetPathType(pathPtr, &fsPtr,
3746            &driveNameLength) == TCL_PATH_ABSOLUTE) {
3747        if (fsPtr == &tclNativeFilesystem) {
3748            return TclpNativeSplitPath(pathPtr, lenPtr);
3749        }
3750    } else {
3751        return TclpNativeSplitPath(pathPtr, lenPtr);
3752    }
3753
3754    /*
3755     * We assume separators are single characters.
3756     */
3757
3758    if (fsPtr->filesystemSeparatorProc != NULL) {
3759        Tcl_Obj *sep = (*fsPtr->filesystemSeparatorProc)(pathPtr);
3760        if (sep != NULL) {
3761            Tcl_IncrRefCount(sep);
3762            separator = Tcl_GetString(sep)[0];
3763            Tcl_DecrRefCount(sep);
3764        }
3765    }
3766
3767    /*
3768     * Place the drive name as first element of the result list. The drive
3769     * name may contain strange characters, like colons and multiple forward
3770     * slashes (for example 'ftp://' is a valid vfs drive name)
3771     */
3772
3773    result = Tcl_NewObj();
3774    p = Tcl_GetString(pathPtr);
3775    Tcl_ListObjAppendElement(NULL, result,
3776            Tcl_NewStringObj(p, driveNameLength));
3777    p += driveNameLength;
3778
3779    /*
3780     * Add the remaining path elements to the list.
3781     */
3782
3783    for (;;) {
3784        char *elementStart = p;
3785        int length;
3786        while ((*p != '\0') && (*p != separator)) {
3787            p++;
3788        }
3789        length = p - elementStart;
3790        if (length > 0) {
3791            Tcl_Obj *nextElt;
3792            if (elementStart[0] == '~') {
3793                TclNewLiteralStringObj(nextElt, "./");
3794                Tcl_AppendToObj(nextElt, elementStart, length);
3795            } else {
3796                nextElt = Tcl_NewStringObj(elementStart, length);
3797            }
3798            Tcl_ListObjAppendElement(NULL, result, nextElt);
3799        }
3800        if (*p++ == '\0') {
3801            break;
3802        }
3803    }
3804
3805    /*
3806     * Compute the number of elements in the result.
3807     */
3808
3809    if (lenPtr != NULL) {
3810        TclListObjLength(NULL, result, lenPtr);
3811    }
3812    return result;
3813}
3814
3815/* Simple helper function */
3816Tcl_Obj *
3817TclFSInternalToNormalized(
3818    Tcl_Filesystem *fromFilesystem,
3819    ClientData clientData,
3820    FilesystemRecord **fsRecPtrPtr)
3821{
3822    FilesystemRecord *fsRecPtr = FsGetFirstFilesystem();
3823
3824    while (fsRecPtr != NULL) {
3825        if (fsRecPtr->fsPtr == fromFilesystem) {
3826            *fsRecPtrPtr = fsRecPtr;
3827            break;
3828        }
3829        fsRecPtr = fsRecPtr->nextPtr;
3830    }
3831
3832    if ((fsRecPtr != NULL)
3833            && (fromFilesystem->internalToNormalizedProc != NULL)) {
3834        return (*fromFilesystem->internalToNormalizedProc)(clientData);
3835    } else {
3836        return NULL;
3837    }
3838}
3839
3840/*
3841 *----------------------------------------------------------------------
3842 *
3843 * TclGetPathType --
3844 *
3845 *      Helper function used by FSGetPathType.
3846 *
3847 * Results:
3848 *      Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or
3849 *      TCL_PATH_VOLUME_RELATIVE. The filesystem reference will be set if and
3850 *      only if it is non-NULL and the function's return value is
3851 *      TCL_PATH_ABSOLUTE.
3852 *
3853 * Side effects:
3854 *      None.
3855 *
3856 *----------------------------------------------------------------------
3857 */
3858
3859Tcl_PathType
3860TclGetPathType(
3861    Tcl_Obj *pathPtr,           /* Path to determine type for */
3862    Tcl_Filesystem **filesystemPtrPtr,
3863                                /* If absolute path and this is not NULL, then
3864                                 * set to the filesystem which claims this
3865                                 * path. */
3866    int *driveNameLengthPtr,    /* If the path is absolute, and this is
3867                                 * non-NULL, then set to the length of the
3868                                 * driveName. */
3869    Tcl_Obj **driveNameRef)     /* If the path is absolute, and this is
3870                                 * non-NULL, then set to the name of the
3871                                 * drive, network-volume which contains the
3872                                 * path, already with a refCount for the
3873                                 * caller. */
3874{
3875    int pathLen;
3876    char *path;
3877    Tcl_PathType type;
3878
3879    path = Tcl_GetStringFromObj(pathPtr, &pathLen);
3880
3881    type = TclFSNonnativePathType(path, pathLen, filesystemPtrPtr,
3882            driveNameLengthPtr, driveNameRef);
3883
3884    if (type != TCL_PATH_ABSOLUTE) {
3885        type = TclpGetNativePathType(pathPtr, driveNameLengthPtr,
3886                driveNameRef);
3887        if ((type == TCL_PATH_ABSOLUTE) && (filesystemPtrPtr != NULL)) {
3888            *filesystemPtrPtr = &tclNativeFilesystem;
3889        }
3890    }
3891    return type;
3892}
3893
3894/*
3895 *----------------------------------------------------------------------
3896 *
3897 * TclFSNonnativePathType --
3898 *
3899 *      Helper function used by TclGetPathType. Its purpose is to check
3900 *      whether the given path starts with a string which corresponds to a
3901 *      file volume in any registered filesystem except the native one. For
3902 *      speed and historical reasons the native filesystem has special
3903 *      hard-coded checks dotted here and there in the filesystem code.
3904 *
3905 * Results:
3906 *      Returns one of TCL_PATH_ABSOLUTE or TCL_PATH_RELATIVE. The filesystem
3907 *      reference will be set if and only if it is non-NULL and the function's
3908 *      return value is TCL_PATH_ABSOLUTE.
3909 *
3910 * Side effects:
3911 *      None.
3912 *
3913 *----------------------------------------------------------------------
3914 */
3915
3916Tcl_PathType
3917TclFSNonnativePathType(
3918    const char *path,           /* Path to determine type for */
3919    int pathLen,                /* Length of the path */
3920    Tcl_Filesystem **filesystemPtrPtr,
3921                                /* If absolute path and this is not NULL, then
3922                                 * set to the filesystem which claims this
3923                                 * path. */
3924    int *driveNameLengthPtr,    /* If the path is absolute, and this is
3925                                 * non-NULL, then set to the length of the
3926                                 * driveName. */
3927    Tcl_Obj **driveNameRef)     /* If the path is absolute, and this is
3928                                 * non-NULL, then set to the name of the
3929                                 * drive, network-volume which contains the
3930                                 * path, already with a refCount for the
3931                                 * caller. */
3932{
3933    FilesystemRecord *fsRecPtr;
3934    Tcl_PathType type = TCL_PATH_RELATIVE;
3935
3936    /*
3937     * Call each of the "listVolumes" function in succession, checking whether
3938     * the given path is an absolute path on any of the volumes returned (this
3939     * is done by checking whether the path's prefix matches).
3940     */
3941
3942    fsRecPtr = FsGetFirstFilesystem();
3943    while (fsRecPtr != NULL) {
3944        Tcl_FSListVolumesProc *proc = fsRecPtr->fsPtr->listVolumesProc;
3945
3946        /*
3947         * We want to skip the native filesystem in this loop because
3948         * otherwise we won't necessarily pass all the Tcl testsuite -- this
3949         * is because some of the tests artificially change the current
3950         * platform (between win, unix) but the list of volumes we get by
3951         * calling (*proc) will reflect the current (real) platform only and
3952         * this may cause some tests to fail. In particular, on unix '/' will
3953         * match the beginning of certain absolute Windows paths starting '//'
3954         * and those tests will go wrong.
3955         *
3956         * Besides these test-suite issues, there is one other reason to skip
3957         * the native filesystem --- since the tclFilename.c code has nice
3958         * fast 'absolute path' checkers, we don't want to waste time
3959         * repeating that effort here, and this function is actually called
3960         * quite often, so if we can save the overhead of the native
3961         * filesystem returning us a list of volumes all the time, it is
3962         * better.
3963         */
3964
3965        if ((fsRecPtr->fsPtr != &tclNativeFilesystem) && (proc != NULL)) {
3966            int numVolumes;
3967            Tcl_Obj *thisFsVolumes = (*proc)();
3968
3969            if (thisFsVolumes != NULL) {
3970                if (Tcl_ListObjLength(NULL, thisFsVolumes, &numVolumes)
3971                        != TCL_OK) {
3972                    /*
3973                     * This is VERY bad; the Tcl_FSListVolumesProc didn't
3974                     * return a valid list. Set numVolumes to -1 so that we
3975                     * skip the while loop below and just return with the
3976                     * current value of 'type'.
3977                     *
3978                     * It would be better if we could signal an error here
3979                     * (but Tcl_Panic seems a bit excessive).
3980                     */
3981
3982                    numVolumes = -1;
3983                }
3984                while (numVolumes > 0) {
3985                    Tcl_Obj *vol;
3986                    int len;
3987                    char *strVol;
3988
3989                    numVolumes--;
3990                    Tcl_ListObjIndex(NULL, thisFsVolumes, numVolumes, &vol);
3991                    strVol = Tcl_GetStringFromObj(vol,&len);
3992                    if (pathLen < len) {
3993                        continue;
3994                    }
3995                    if (strncmp(strVol, path, (size_t) len) == 0) {
3996                        type = TCL_PATH_ABSOLUTE;
3997                        if (filesystemPtrPtr != NULL) {
3998                            *filesystemPtrPtr = fsRecPtr->fsPtr;
3999                        }
4000                        if (driveNameLengthPtr != NULL) {
4001                            *driveNameLengthPtr = len;
4002                        }
4003                        if (driveNameRef != NULL) {
4004                            *driveNameRef = vol;
4005                            Tcl_IncrRefCount(vol);
4006                        }
4007                        break;
4008                    }
4009                }
4010                Tcl_DecrRefCount(thisFsVolumes);
4011                if (type == TCL_PATH_ABSOLUTE) {
4012                    /*
4013                     * We don't need to examine any more filesystems.
4014                     */
4015                    break;
4016                }
4017            }
4018        }
4019        fsRecPtr = fsRecPtr->nextPtr;
4020    }
4021    return type;
4022}
4023
4024/*
4025 *---------------------------------------------------------------------------
4026 *
4027 * Tcl_FSRenameFile --
4028 *
4029 *      If the two paths given belong to the same filesystem, we call that
4030 *      filesystems rename function. Otherwise we simply return the POSIX
4031 *      error 'EXDEV', and -1.
4032 *
4033 * Results:
4034 *      Standard Tcl error code if a function was called.
4035 *
4036 * Side effects:
4037 *      A file may be renamed.
4038 *
4039 *---------------------------------------------------------------------------
4040 */
4041
4042int
4043Tcl_FSRenameFile(
4044    Tcl_Obj* srcPathPtr,        /* Pathname of file or dir to be renamed
4045                                 * (UTF-8). */
4046    Tcl_Obj *destPathPtr)       /* New pathname of file or directory
4047                                 * (UTF-8). */
4048{
4049    int retVal = -1;
4050    const Tcl_Filesystem *fsPtr, *fsPtr2;
4051    fsPtr = Tcl_FSGetFileSystemForPath(srcPathPtr);
4052    fsPtr2 = Tcl_FSGetFileSystemForPath(destPathPtr);
4053
4054    if ((fsPtr == fsPtr2) && (fsPtr != NULL)) {
4055        Tcl_FSRenameFileProc *proc = fsPtr->renameFileProc;
4056        if (proc != NULL) {
4057            retVal = (*proc)(srcPathPtr, destPathPtr);
4058        }
4059    }
4060    if (retVal == -1) {
4061        Tcl_SetErrno(EXDEV);
4062    }
4063    return retVal;
4064}
4065
4066/*
4067 *---------------------------------------------------------------------------
4068 *
4069 * Tcl_FSCopyFile --
4070 *
4071 *      If the two paths given belong to the same filesystem, we call that
4072 *      filesystem's copy function. Otherwise we simply return the POSIX error
4073 *      'EXDEV', and -1.
4074 *
4075 *      Note that in the native filesystems, 'copyFileProc' is defined to copy
4076 *      soft links (i.e. it copies the links themselves, not the things they
4077 *      point to).
4078 *
4079 * Results:
4080 *      Standard Tcl error code if a function was called.
4081 *
4082 * Side effects:
4083 *      A file may be copied.
4084 *
4085 *---------------------------------------------------------------------------
4086 */
4087
4088int
4089Tcl_FSCopyFile(
4090    Tcl_Obj *srcPathPtr,        /* Pathname of file to be copied (UTF-8). */
4091    Tcl_Obj *destPathPtr)       /* Pathname of file to copy to (UTF-8). */
4092{
4093    int retVal = -1;
4094    const Tcl_Filesystem *fsPtr, *fsPtr2;
4095    fsPtr = Tcl_FSGetFileSystemForPath(srcPathPtr);
4096    fsPtr2 = Tcl_FSGetFileSystemForPath(destPathPtr);
4097
4098    if (fsPtr == fsPtr2 && fsPtr != NULL) {
4099        Tcl_FSCopyFileProc *proc = fsPtr->copyFileProc;
4100        if (proc != NULL) {
4101            retVal = (*proc)(srcPathPtr, destPathPtr);
4102        }
4103    }
4104    if (retVal == -1) {
4105        Tcl_SetErrno(EXDEV);
4106    }
4107    return retVal;
4108}
4109
4110/*
4111 *---------------------------------------------------------------------------
4112 *
4113 * TclCrossFilesystemCopy --
4114 *
4115 *      Helper for above function, and for Tcl_FSLoadFile, to copy files from
4116 *      one filesystem to another. This function will overwrite the target
4117 *      file if it already exists.
4118 *
4119 * Results:
4120 *      Standard Tcl error code.
4121 *
4122 * Side effects:
4123 *      A file may be created.
4124 *
4125 *---------------------------------------------------------------------------
4126 */
4127int
4128TclCrossFilesystemCopy(
4129    Tcl_Interp *interp,         /* For error messages */
4130    Tcl_Obj *source,            /* Pathname of file to be copied (UTF-8). */
4131    Tcl_Obj *target)            /* Pathname of file to copy to (UTF-8). */
4132{
4133    int result = TCL_ERROR;
4134    int prot = 0666;
4135    Tcl_Channel in, out;
4136    Tcl_StatBuf sourceStatBuf;
4137    struct utimbuf tval;
4138
4139    out = Tcl_FSOpenFileChannel(interp, target, "wb", prot);
4140    if (out == NULL) {
4141        /*
4142         * It looks like we cannot copy it over. Bail out...
4143         */
4144        goto done;
4145    }
4146
4147    in = Tcl_FSOpenFileChannel(interp, source, "rb", prot);
4148    if (in == NULL) {
4149        /*
4150         * This is very strange, caller should have checked this...
4151         */
4152
4153        Tcl_Close(interp, out);
4154        goto done;
4155    }
4156
4157    /*
4158     * Copy it synchronously. We might wish to add an asynchronous option to
4159     * support vfs's which are slow (e.g. network sockets).
4160     */
4161
4162    if (TclCopyChannel(interp, in, out, -1, NULL) == TCL_OK) {
4163        result = TCL_OK;
4164    }
4165
4166    /*
4167     * If the copy failed, assume that copy channel left a good error message.
4168     */
4169
4170    Tcl_Close(interp, in);
4171    Tcl_Close(interp, out);
4172
4173    /*
4174     * Set modification date of copied file.
4175     */
4176
4177    if (Tcl_FSLstat(source, &sourceStatBuf) == 0) {
4178        tval.actime = sourceStatBuf.st_atime;
4179        tval.modtime = sourceStatBuf.st_mtime;
4180        Tcl_FSUtime(target, &tval);
4181    }
4182
4183  done:
4184    return result;
4185}
4186
4187/*
4188 *---------------------------------------------------------------------------
4189 *
4190 * Tcl_FSDeleteFile --
4191 *
4192 *      The appropriate function for the filesystem to which pathPtr belongs
4193 *      will be called.
4194 *
4195 * Results:
4196 *      Standard Tcl error code.
4197 *
4198 * Side effects:
4199 *      A file may be deleted.
4200 *
4201 *---------------------------------------------------------------------------
4202 */
4203
4204int
4205Tcl_FSDeleteFile(
4206    Tcl_Obj *pathPtr)           /* Pathname of file to be removed (UTF-8). */
4207{
4208    const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
4209    if (fsPtr != NULL) {
4210        Tcl_FSDeleteFileProc *proc = fsPtr->deleteFileProc;
4211        if (proc != NULL) {
4212            return (*proc)(pathPtr);
4213        }
4214    }
4215    Tcl_SetErrno(ENOENT);
4216    return -1;
4217}
4218
4219/*
4220 *---------------------------------------------------------------------------
4221 *
4222 * Tcl_FSCreateDirectory --
4223 *
4224 *      The appropriate function for the filesystem to which pathPtr belongs
4225 *      will be called.
4226 *
4227 * Results:
4228 *      Standard Tcl error code.
4229 *
4230 * Side effects:
4231 *      A directory may be created.
4232 *
4233 *---------------------------------------------------------------------------
4234 */
4235
4236int
4237Tcl_FSCreateDirectory(
4238    Tcl_Obj *pathPtr)           /* Pathname of directory to create (UTF-8). */
4239{
4240    const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
4241    if (fsPtr != NULL) {
4242        Tcl_FSCreateDirectoryProc *proc = fsPtr->createDirectoryProc;
4243        if (proc != NULL) {
4244            return (*proc)(pathPtr);
4245        }
4246    }
4247    Tcl_SetErrno(ENOENT);
4248    return -1;
4249}
4250
4251/*
4252 *---------------------------------------------------------------------------
4253 *
4254 * Tcl_FSCopyDirectory --
4255 *
4256 *      If the two paths given belong to the same filesystem, we call that
4257 *      filesystems copy-directory function. Otherwise we simply return the
4258 *      POSIX error 'EXDEV', and -1.
4259 *
4260 * Results:
4261 *      Standard Tcl error code if a function was called.
4262 *
4263 * Side effects:
4264 *      A directory may be copied.
4265 *
4266 *---------------------------------------------------------------------------
4267 */
4268
4269int
4270Tcl_FSCopyDirectory(
4271    Tcl_Obj* srcPathPtr,        /* Pathname of directory to be copied
4272                                 * (UTF-8). */
4273    Tcl_Obj *destPathPtr,       /* Pathname of target directory (UTF-8). */
4274    Tcl_Obj **errorPtr)         /* If non-NULL, then will be set to a new
4275                                 * object containing name of file causing
4276                                 * error, with refCount 1. */
4277{
4278    int retVal = -1;
4279    const Tcl_Filesystem *fsPtr, *fsPtr2;
4280    fsPtr = Tcl_FSGetFileSystemForPath(srcPathPtr);
4281    fsPtr2 = Tcl_FSGetFileSystemForPath(destPathPtr);
4282
4283    if (fsPtr == fsPtr2 && fsPtr != NULL) {
4284        Tcl_FSCopyDirectoryProc *proc = fsPtr->copyDirectoryProc;
4285        if (proc != NULL) {
4286            retVal = (*proc)(srcPathPtr, destPathPtr, errorPtr);
4287        }
4288    }
4289    if (retVal == -1) {
4290        Tcl_SetErrno(EXDEV);
4291    }
4292    return retVal;
4293}
4294
4295/*
4296 *---------------------------------------------------------------------------
4297 *
4298 * Tcl_FSRemoveDirectory --
4299 *
4300 *      The appropriate function for the filesystem to which pathPtr belongs
4301 *      will be called.
4302 *
4303 * Results:
4304 *      Standard Tcl error code.
4305 *
4306 * Side effects:
4307 *      A directory may be deleted.
4308 *
4309 *---------------------------------------------------------------------------
4310 */
4311
4312int
4313Tcl_FSRemoveDirectory(
4314    Tcl_Obj *pathPtr,           /* Pathname of directory to be removed
4315                                 * (UTF-8). */
4316    int recursive,              /* If non-zero, removes directories that are
4317                                 * nonempty. Otherwise, will only remove empty
4318                                 * directories. */
4319    Tcl_Obj **errorPtr)         /* If non-NULL, then will be set to a new
4320                                 * object containing name of file causing
4321                                 * error, with refCount 1. */
4322{
4323    const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
4324    if (fsPtr != NULL && fsPtr->removeDirectoryProc != NULL) {
4325        Tcl_FSRemoveDirectoryProc *proc = fsPtr->removeDirectoryProc;
4326        if (recursive) {
4327            /*
4328             * We check whether the cwd lies inside this directory and move it
4329             * if it does.
4330             */
4331
4332            Tcl_Obj *cwdPtr = Tcl_FSGetCwd(NULL);
4333
4334            if (cwdPtr != NULL) {
4335                char *cwdStr, *normPathStr;
4336                int cwdLen, normLen;
4337                Tcl_Obj *normPath = Tcl_FSGetNormalizedPath(NULL, pathPtr);
4338
4339                if (normPath != NULL) {
4340                    normPathStr = Tcl_GetStringFromObj(normPath, &normLen);
4341                    cwdStr = Tcl_GetStringFromObj(cwdPtr, &cwdLen);
4342                    if ((cwdLen >= normLen) && (strncmp(normPathStr, cwdStr,
4343                            (size_t) normLen) == 0)) {
4344                        /*
4345                         * The cwd is inside the directory, so we perform a
4346                         * 'cd [file dirname $path]'.
4347                         */
4348
4349                        Tcl_Obj *dirPtr = TclPathPart(NULL, pathPtr,
4350                                TCL_PATH_DIRNAME);
4351
4352                        Tcl_FSChdir(dirPtr);
4353                        Tcl_DecrRefCount(dirPtr);
4354                    }
4355                }
4356                Tcl_DecrRefCount(cwdPtr);
4357            }
4358        }
4359        return (*proc)(pathPtr, recursive, errorPtr);
4360    }
4361    Tcl_SetErrno(ENOENT);
4362    return -1;
4363}
4364
4365/*
4366 *---------------------------------------------------------------------------
4367 *
4368 * Tcl_FSGetFileSystemForPath --
4369 *
4370 *      This function determines which filesystem to use for a particular path
4371 *      object, and returns the filesystem which accepts this file. If no
4372 *      filesystem will accept this object as a valid file path, then NULL is
4373 *      returned.
4374 *
4375 * Results:
4376 *      NULL or a filesystem which will accept this path.
4377 *
4378 * Side effects:
4379 *      The object may be converted to a path type.
4380 *
4381 *---------------------------------------------------------------------------
4382 */
4383
4384Tcl_Filesystem *
4385Tcl_FSGetFileSystemForPath(
4386    Tcl_Obj* pathPtr)
4387{
4388    FilesystemRecord *fsRecPtr;
4389    Tcl_Filesystem* retVal = NULL;
4390
4391    if (pathPtr == NULL) {
4392        Tcl_Panic("Tcl_FSGetFileSystemForPath called with NULL object");
4393        return NULL;
4394    }
4395
4396    /*
4397     * If the object has a refCount of zero, we reject it. This is to avoid
4398     * possible segfaults or nondeterministic memory leaks (i.e. the user
4399     * doesn't know if they should decrement the ref count on return or not).
4400     */
4401
4402    if (pathPtr->refCount == 0) {
4403        Tcl_Panic("Tcl_FSGetFileSystemForPath called with object with refCount == 0");
4404        return NULL;
4405    }
4406
4407    /*
4408     * Check if the filesystem has changed in some way since this object's
4409     * internal representation was calculated. Before doing that, assure we
4410     * have the most up-to-date copy of the master filesystem. This is
4411     * accomplished by the FsGetFirstFilesystem() call.
4412     */
4413
4414    fsRecPtr = FsGetFirstFilesystem();
4415
4416    if (TclFSEnsureEpochOk(pathPtr, &retVal) != TCL_OK) {
4417        return NULL;
4418    }
4419
4420    /*
4421     * Call each of the "pathInFilesystem" functions in succession. A
4422     * non-return value of -1 indicates the particular function has succeeded.
4423     */
4424
4425    while ((retVal == NULL) && (fsRecPtr != NULL)) {
4426        Tcl_FSPathInFilesystemProc *proc =
4427                fsRecPtr->fsPtr->pathInFilesystemProc;
4428
4429        if (proc != NULL) {
4430            ClientData clientData = NULL;
4431            if ((*proc)(pathPtr, &clientData) != -1) {
4432                /*
4433                 * We assume the type of pathPtr hasn't been changed by the
4434                 * above call to the pathInFilesystemProc.
4435                 */
4436
4437                TclFSSetPathDetails(pathPtr, fsRecPtr, clientData);
4438                retVal = fsRecPtr->fsPtr;
4439            }
4440        }
4441        fsRecPtr = fsRecPtr->nextPtr;
4442    }
4443
4444    return retVal;
4445}
4446
4447/*
4448 *---------------------------------------------------------------------------
4449 *
4450 * Tcl_FSGetNativePath --
4451 *
4452 *      This function is for use by the Win/Unix native filesystems, so that
4453 *      they can easily retrieve the native (char* or TCHAR*) representation
4454 *      of a path. Other filesystems will probably want to implement similar
4455 *      functions. They basically act as a safety net around
4456 *      Tcl_FSGetInternalRep. Normally your file-system functions will always
4457 *      be called with path objects already converted to the correct
4458 *      filesystem, but if for some reason they are called directly (i.e. by
4459 *      functions not in this file), then one cannot necessarily guarantee
4460 *      that the path object pointer is from the correct filesystem.
4461 *
4462 *      Note: in the future it might be desireable to have separate versions
4463 *      of this function with different signatures, for example
4464 *      Tcl_FSGetNativeWinPath, Tcl_FSGetNativeUnixPath etc. Right now, since
4465 *      native paths are all string based, we use just one function.
4466 *
4467 * Results:
4468 *      NULL or a valid native path.
4469 *
4470 * Side effects:
4471 *      See Tcl_FSGetInternalRep.
4472 *
4473 *---------------------------------------------------------------------------
4474 */
4475
4476const char *
4477Tcl_FSGetNativePath(
4478    Tcl_Obj *pathPtr)
4479{
4480    return (const char *) Tcl_FSGetInternalRep(pathPtr, &tclNativeFilesystem);
4481}
4482
4483/*
4484 *---------------------------------------------------------------------------
4485 *
4486 * NativeFreeInternalRep --
4487 *
4488 *      Free a native internal representation, which will be non-NULL.
4489 *
4490 * Results:
4491 *      None.
4492 *
4493 * Side effects:
4494 *      Memory is released.
4495 *
4496 *---------------------------------------------------------------------------
4497 */
4498
4499static void
4500NativeFreeInternalRep(
4501    ClientData clientData)
4502{
4503    ckfree((char *) clientData);
4504}
4505
4506/*
4507 *---------------------------------------------------------------------------
4508 *
4509 * Tcl_FSFileSystemInfo --
4510 *
4511 *      This function returns a list of two elements. The first element is the
4512 *      name of the filesystem (e.g. "native" or "vfs"), and the second is the
4513 *      particular type of the given path within that filesystem.
4514 *
4515 * Results:
4516 *      A list of two elements.
4517 *
4518 * Side effects:
4519 *      The object may be converted to a path type.
4520 *
4521 *---------------------------------------------------------------------------
4522 */
4523
4524Tcl_Obj *
4525Tcl_FSFileSystemInfo(
4526    Tcl_Obj *pathPtr)
4527{
4528    Tcl_Obj *resPtr;
4529    Tcl_FSFilesystemPathTypeProc *proc;
4530    const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
4531
4532    if (fsPtr == NULL) {
4533        return NULL;
4534    }
4535
4536    resPtr = Tcl_NewListObj(0, NULL);
4537    Tcl_ListObjAppendElement(NULL,resPtr,Tcl_NewStringObj(fsPtr->typeName,-1));
4538
4539    proc = fsPtr->filesystemPathTypeProc;
4540    if (proc != NULL) {
4541        Tcl_Obj *typePtr = (*proc)(pathPtr);
4542        if (typePtr != NULL) {
4543            Tcl_ListObjAppendElement(NULL, resPtr, typePtr);
4544        }
4545    }
4546
4547    return resPtr;
4548}
4549
4550/*
4551 *---------------------------------------------------------------------------
4552 *
4553 * Tcl_FSPathSeparator --
4554 *
4555 *      This function returns the separator to be used for a given path. The
4556 *      object returned should have a refCount of zero
4557 *
4558 * Results:
4559 *      A Tcl object, with a refCount of zero. If the caller needs to retain a
4560 *      reference to the object, it should call Tcl_IncrRefCount, and should
4561 *      otherwise free the object.
4562 *
4563 * Side effects:
4564 *      The path object may be converted to a path type.
4565 *
4566 *---------------------------------------------------------------------------
4567 */
4568
4569Tcl_Obj *
4570Tcl_FSPathSeparator(
4571    Tcl_Obj *pathPtr)
4572{
4573    const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
4574
4575    if (fsPtr == NULL) {
4576        return NULL;
4577    }
4578    if (fsPtr->filesystemSeparatorProc != NULL) {
4579        return (*fsPtr->filesystemSeparatorProc)(pathPtr);
4580    } else {
4581        Tcl_Obj *resultObj;
4582
4583        /*
4584         * Allow filesystems not to provide a filesystemSeparatorProc if they
4585         * wish to use the standard forward slash.
4586         */
4587
4588        TclNewLiteralStringObj(resultObj, "/");
4589        return resultObj;
4590    }
4591}
4592
4593/*
4594 *---------------------------------------------------------------------------
4595 *
4596 * NativeFilesystemSeparator --
4597 *
4598 *      This function is part of the native filesystem support, and returns
4599 *      the separator for the given path.
4600 *
4601 * Results:
4602 *      String object containing the separator character.
4603 *
4604 * Side effects:
4605 *      None.
4606 *
4607 *---------------------------------------------------------------------------
4608 */
4609
4610static Tcl_Obj *
4611NativeFilesystemSeparator(
4612    Tcl_Obj *pathPtr)
4613{
4614    const char *separator = NULL; /* lint */
4615    switch (tclPlatform) {
4616    case TCL_PLATFORM_UNIX:
4617        separator = "/";
4618        break;
4619    case TCL_PLATFORM_WINDOWS:
4620        separator = "\\";
4621        break;
4622    }
4623    return Tcl_NewStringObj(separator,1);
4624}
4625
4626/* Everything from here on is contained in this obsolete ifdef */
4627#ifdef USE_OBSOLETE_FS_HOOKS
4628
4629/*
4630 *----------------------------------------------------------------------
4631 *
4632 * TclStatInsertProc --
4633 *
4634 *      Insert the passed function pointer at the head of the list of
4635 *      functions which are used during a call to 'TclStat(...)'. The passed
4636 *      function should behave exactly like 'TclStat' when called during that
4637 *      time (see 'TclStat(...)' for more information). The function will be
4638 *      added even if it already in the list.
4639 *
4640 * Results:
4641 *      Normally TCL_OK; TCL_ERROR if memory for a new node in the list could
4642 *      not be allocated.
4643 *
4644 * Side effects:
4645 *      Memory allocated and modifies the link list for 'TclStat' functions.
4646 *
4647 *----------------------------------------------------------------------
4648 */
4649
4650int
4651TclStatInsertProc(
4652    TclStatProc_ *proc)
4653{
4654    int retVal = TCL_ERROR;
4655
4656    if (proc != NULL) {
4657        StatProc *newStatProcPtr;
4658
4659        newStatProcPtr = (StatProc *)ckalloc(sizeof(StatProc));
4660
4661        if (newStatProcPtr != NULL) {
4662            newStatProcPtr->proc = proc;
4663            Tcl_MutexLock(&obsoleteFsHookMutex);
4664            newStatProcPtr->nextPtr = statProcList;
4665            statProcList = newStatProcPtr;
4666            Tcl_MutexUnlock(&obsoleteFsHookMutex);
4667
4668            retVal = TCL_OK;
4669        }
4670    }
4671
4672    return retVal;
4673}
4674
4675/*
4676 *----------------------------------------------------------------------
4677 *
4678 * TclStatDeleteProc --
4679 *
4680 *      Removed the passed function pointer from the list of 'TclStat'
4681 *      functions. Ensures that the built-in stat function is not removable.
4682 *
4683 * Results:
4684 *      TCL_OK if the function pointer was successfully removed, TCL_ERROR
4685 *      otherwise.
4686 *
4687 * Side effects:
4688 *      Memory is deallocated and the respective list updated.
4689 *
4690 *----------------------------------------------------------------------
4691 */
4692
4693int
4694TclStatDeleteProc(
4695    TclStatProc_ *proc)
4696{
4697    int retVal = TCL_ERROR;
4698    StatProc *tmpStatProcPtr;
4699    StatProc *prevStatProcPtr = NULL;
4700
4701    Tcl_MutexLock(&obsoleteFsHookMutex);
4702    tmpStatProcPtr = statProcList;
4703
4704    /*
4705     * Traverse the 'statProcList' looking for the particular node whose
4706     * 'proc' member matches 'proc' and remove that one from the list. Ensure
4707     * that the "default" node cannot be removed.
4708     */
4709
4710    while ((retVal == TCL_ERROR) && (tmpStatProcPtr != NULL)) {
4711        if (tmpStatProcPtr->proc == proc) {
4712            if (prevStatProcPtr == NULL) {
4713                statProcList = tmpStatProcPtr->nextPtr;
4714            } else {
4715                prevStatProcPtr->nextPtr = tmpStatProcPtr->nextPtr;
4716            }
4717
4718            ckfree((char *)tmpStatProcPtr);
4719
4720            retVal = TCL_OK;
4721        } else {
4722            prevStatProcPtr = tmpStatProcPtr;
4723            tmpStatProcPtr = tmpStatProcPtr->nextPtr;
4724        }
4725    }
4726
4727    Tcl_MutexUnlock(&obsoleteFsHookMutex);
4728
4729    return retVal;
4730}
4731
4732/*
4733 *----------------------------------------------------------------------
4734 *
4735 * TclAccessInsertProc --
4736 *
4737 *      Insert the passed function pointer at the head of the list of
4738 *      functions which are used during a call to 'TclAccess(...)'. The passed
4739 *      function should behave exactly like 'TclAccess' when called during
4740 *      that time (see 'TclAccess(...)' for more information). The function
4741 *      will be added even if it already in the list.
4742 *
4743 * Results:
4744 *      Normally TCL_OK; TCL_ERROR if memory for a new node in the list could
4745 *      not be allocated.
4746 *
4747 * Side effects:
4748 *      Memory allocated and modifies the link list for 'TclAccess' functions.
4749 *
4750 *----------------------------------------------------------------------
4751 */
4752
4753int
4754TclAccessInsertProc(
4755    TclAccessProc_ *proc)
4756{
4757    int retVal = TCL_ERROR;
4758
4759    if (proc != NULL) {
4760        AccessProc *newAccessProcPtr;
4761
4762        newAccessProcPtr = (AccessProc *)ckalloc(sizeof(AccessProc));
4763
4764        if (newAccessProcPtr != NULL) {
4765            newAccessProcPtr->proc = proc;
4766            Tcl_MutexLock(&obsoleteFsHookMutex);
4767            newAccessProcPtr->nextPtr = accessProcList;
4768            accessProcList = newAccessProcPtr;
4769            Tcl_MutexUnlock(&obsoleteFsHookMutex);
4770
4771            retVal = TCL_OK;
4772        }
4773    }
4774
4775    return retVal;
4776}
4777
4778/*
4779 *----------------------------------------------------------------------
4780 *
4781 * TclAccessDeleteProc --
4782 *
4783 *      Removed the passed function pointer from the list of 'TclAccess'
4784 *      functions. Ensures that the built-in access function is not removable.
4785 *
4786 * Results:
4787 *      TCL_OK if the function pointer was successfully removed, TCL_ERROR
4788 *      otherwise.
4789 *
4790 * Side effects:
4791 *      Memory is deallocated and the respective list updated.
4792 *
4793 *----------------------------------------------------------------------
4794 */
4795
4796int
4797TclAccessDeleteProc(
4798    TclAccessProc_ *proc)
4799{
4800    int retVal = TCL_ERROR;
4801    AccessProc *tmpAccessProcPtr;
4802    AccessProc *prevAccessProcPtr = NULL;
4803
4804    /*
4805     * Traverse the 'accessProcList' looking for the particular node whose
4806     * 'proc' member matches 'proc' and remove that one from the list. Ensure
4807     * that the "default" node cannot be removed.
4808     */
4809
4810    Tcl_MutexLock(&obsoleteFsHookMutex);
4811    tmpAccessProcPtr = accessProcList;
4812    while ((retVal == TCL_ERROR) && (tmpAccessProcPtr != NULL)) {
4813        if (tmpAccessProcPtr->proc == proc) {
4814            if (prevAccessProcPtr == NULL) {
4815                accessProcList = tmpAccessProcPtr->nextPtr;
4816            } else {
4817                prevAccessProcPtr->nextPtr = tmpAccessProcPtr->nextPtr;
4818            }
4819
4820            ckfree((char *)tmpAccessProcPtr);
4821
4822            retVal = TCL_OK;
4823        } else {
4824            prevAccessProcPtr = tmpAccessProcPtr;
4825            tmpAccessProcPtr = tmpAccessProcPtr->nextPtr;
4826        }
4827    }
4828    Tcl_MutexUnlock(&obsoleteFsHookMutex);
4829
4830    return retVal;
4831}
4832
4833/*
4834 *----------------------------------------------------------------------
4835 *
4836 * TclOpenFileChannelInsertProc --
4837 *
4838 *      Insert the passed function pointer at the head of the list of
4839 *      functions which are used during a call to 'Tcl_OpenFileChannel(...)'.
4840 *      The passed function should behave exactly like 'Tcl_OpenFileChannel'
4841 *      when called during that time (see 'Tcl_OpenFileChannel(...)' for more
4842 *      information). The function will be added even if it already in the
4843 *      list.
4844 *
4845 * Results:
4846 *      Normally TCL_OK; TCL_ERROR if memory for a new node in the list could
4847 *      not be allocated.
4848 *
4849 * Side effects:
4850 *      Memory allocated and modifies the link list for 'Tcl_OpenFileChannel'
4851 *      functions.
4852 *
4853 *----------------------------------------------------------------------
4854 */
4855
4856int
4857TclOpenFileChannelInsertProc(
4858    TclOpenFileChannelProc_ *proc)
4859{
4860    int retVal = TCL_ERROR;
4861
4862    if (proc != NULL) {
4863        OpenFileChannelProc *newOpenFileChannelProcPtr;
4864
4865        newOpenFileChannelProcPtr = (OpenFileChannelProc *)
4866                ckalloc(sizeof(OpenFileChannelProc));
4867
4868        newOpenFileChannelProcPtr->proc = proc;
4869        Tcl_MutexLock(&obsoleteFsHookMutex);
4870        newOpenFileChannelProcPtr->nextPtr = openFileChannelProcList;
4871        openFileChannelProcList = newOpenFileChannelProcPtr;
4872        Tcl_MutexUnlock(&obsoleteFsHookMutex);
4873
4874        retVal = TCL_OK;
4875    }
4876
4877    return retVal;
4878}
4879
4880/*
4881 *----------------------------------------------------------------------
4882 *
4883 * TclOpenFileChannelDeleteProc --
4884 *
4885 *      Removed the passed function pointer from the list of
4886 *      'Tcl_OpenFileChannel' functions. Ensures that the built-in open file
4887 *      channel function is not removable.
4888 *
4889 * Results:
4890 *      TCL_OK if the function pointer was successfully removed, TCL_ERROR
4891 *      otherwise.
4892 *
4893 * Side effects:
4894 *      Memory is deallocated and the respective list updated.
4895 *
4896 *----------------------------------------------------------------------
4897 */
4898
4899int
4900TclOpenFileChannelDeleteProc(
4901    TclOpenFileChannelProc_ *proc)
4902{
4903    int retVal = TCL_ERROR;
4904    OpenFileChannelProc *tmpOpenFileChannelProcPtr = openFileChannelProcList;
4905    OpenFileChannelProc *prevOpenFileChannelProcPtr = NULL;
4906
4907    /*
4908     * Traverse the 'openFileChannelProcList' looking for the particular node
4909     * whose 'proc' member matches 'proc' and remove that one from the list.
4910     */
4911
4912    Tcl_MutexLock(&obsoleteFsHookMutex);
4913    tmpOpenFileChannelProcPtr = openFileChannelProcList;
4914    while ((retVal == TCL_ERROR) &&
4915            (tmpOpenFileChannelProcPtr != NULL)) {
4916        if (tmpOpenFileChannelProcPtr->proc == proc) {
4917            if (prevOpenFileChannelProcPtr == NULL) {
4918                openFileChannelProcList = tmpOpenFileChannelProcPtr->nextPtr;
4919            } else {
4920                prevOpenFileChannelProcPtr->nextPtr =
4921                        tmpOpenFileChannelProcPtr->nextPtr;
4922            }
4923
4924            ckfree((char *) tmpOpenFileChannelProcPtr);
4925
4926            retVal = TCL_OK;
4927        } else {
4928            prevOpenFileChannelProcPtr = tmpOpenFileChannelProcPtr;
4929            tmpOpenFileChannelProcPtr = tmpOpenFileChannelProcPtr->nextPtr;
4930        }
4931    }
4932    Tcl_MutexUnlock(&obsoleteFsHookMutex);
4933
4934    return retVal;
4935}
4936#endif /* USE_OBSOLETE_FS_HOOKS */
4937
4938/*
4939 * Local Variables:
4940 * mode: c
4941 * c-basic-offset: 4
4942 * fill-column: 78
4943 * End:
4944 */
Note: See TracBrowser for help on using the repository browser.