os/persistentdata/persistentstorage/sqlite3api/TEST/TCL/tcldistribution/unix/tclUnixFCmd.c
First public contribution.
4 * This file implements the unix specific portion of file manipulation
5 * subcommands of the "file" command. All filename arguments should
6 * already be translated to native format.
8 * Copyright (c) 1996-1998 Sun Microsystems, Inc.
9 * Portions Copyright (c) 2007-2008 Nokia Corporation and/or its subsidiaries. All rights reserved.
11 * See the file "license.terms" for information on usage and redistribution
12 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
14 * RCS: @(#) $Id: tclUnixFCmd.c,v 1.28.2.15 2007/04/29 02:19:51 das Exp $
16 * Portions of this code were derived from NetBSD source code which has
17 * the following copyright notice:
19 * Copyright (c) 1988, 1993, 1994
20 * The Regents of the University of California. All rights reserved.
22 * Redistribution and use in source and binary forms, with or without
23 * modification, are permitted provided that the following conditions
25 * 1. Redistributions of source code must retain the above copyright
26 * notice, this list of conditions and the following disclaimer.
27 * 2. Redistributions in binary form must reproduce the above copyright
28 * notice, this list of conditions and the following disclaimer in the
29 * documentation and/or other materials provided with the distribution.
30 * 3. All advertising materials mentioning features or use of this software
31 * must display the following acknowledgement:
32 * This product includes software developed by the University of
33 * California, Berkeley and its contributors.
34 * 4. Neither the name of the University nor the names of its contributors
35 * may be used to endorse or promote products derived from this software
36 * without specific prior written permission.
38 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
39 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
40 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
41 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
42 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
43 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
44 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
45 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
46 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
47 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
55 #ifndef HAVE_ST_BLKSIZE
57 #include <sys/statfs.h>
65 #include "convertPathSlashes.h"
66 void TclPrint1(const char* aFmt, const char* aStr);
69 * The following constants specify the type of callback when
70 * TraverseUnixTree() calls the traverseProc()
73 #define DOTREE_PRED 1 /* pre-order directory */
74 #define DOTREE_POSTD 2 /* post-order directory */
75 #define DOTREE_F 3 /* regular file */
78 * Callbacks for file attributes code.
81 static int GetGroupAttribute _ANSI_ARGS_((Tcl_Interp *interp,
82 int objIndex, Tcl_Obj *fileName,
83 Tcl_Obj **attributePtrPtr));
84 static int GetOwnerAttribute _ANSI_ARGS_((Tcl_Interp *interp,
85 int objIndex, Tcl_Obj *fileName,
86 Tcl_Obj **attributePtrPtr));
87 static int GetPermissionsAttribute _ANSI_ARGS_((
88 Tcl_Interp *interp, int objIndex,
89 Tcl_Obj *fileName, Tcl_Obj **attributePtrPtr));
90 static int SetGroupAttribute _ANSI_ARGS_((Tcl_Interp *interp,
91 int objIndex, Tcl_Obj *fileName,
92 Tcl_Obj *attributePtr));
93 static int SetOwnerAttribute _ANSI_ARGS_((Tcl_Interp *interp,
94 int objIndex, Tcl_Obj *fileName,
95 Tcl_Obj *attributePtr));
96 static int SetPermissionsAttribute _ANSI_ARGS_((
97 Tcl_Interp *interp, int objIndex,
98 Tcl_Obj *fileName, Tcl_Obj *attributePtr));
99 static int GetModeFromPermString _ANSI_ARGS_((
100 Tcl_Interp *interp, char *modeStringPtr,
104 * Prototype for the TraverseUnixTree callback function.
107 typedef int (TraversalProc) _ANSI_ARGS_((Tcl_DString *srcPtr,
108 Tcl_DString *dstPtr, CONST Tcl_StatBuf *statBufPtr, int type,
109 Tcl_DString *errorPtr));
112 * Constants and variables necessary for file attributes subcommand.
116 UNIX_GROUP_ATTRIBUTE,
117 UNIX_OWNER_ATTRIBUTE,
118 UNIX_PERMISSIONS_ATTRIBUTE
121 CONST char *tclpFileAttrStrings[] = {
128 CONST TclFileAttrProcs tclpFileAttrProcs[] = {
129 {GetGroupAttribute, SetGroupAttribute},
130 {GetOwnerAttribute, SetOwnerAttribute},
131 {GetPermissionsAttribute, SetPermissionsAttribute}
135 * This is the maximum number of consecutive readdir/unlink calls that can be
136 * made (with no intervening rewinddir or closedir/opendir) before triggering
137 * a bug that makes readdir return NULL even though some directory entries
138 * have not been processed. The bug afflicts SunOS's readdir when applied to
139 * ufs file systems and Darwin 6.5's (and OSX v.10.3.8's) HFS+. JH found the
140 * Darwin readdir to reset at 147, so 130 is chosen to be conservative. We
141 * can't do a general rewind on failure as NFS can create special files that
142 * recreate themselves when you try and delete them. 8.4.8 added a solution
143 * that was affected by a single such NFS file, this solution should not be
144 * affected by less than THRESHOLD such files. [Bug 1034337]
147 #define MAX_READDIR_UNLINK_THRESHOLD 130
150 * Declarations for local procedures defined in this file:
153 static int CopyFile _ANSI_ARGS_((CONST char *src,
154 CONST char *dst, CONST Tcl_StatBuf *statBufPtr));
155 static int CopyFileAtts _ANSI_ARGS_((CONST char *src,
156 CONST char *dst, CONST Tcl_StatBuf *statBufPtr));
157 static int DoCopyFile _ANSI_ARGS_((CONST char *srcPtr,
158 CONST char *dstPtr, CONST Tcl_StatBuf *statBufPtr));
159 static int DoCreateDirectory _ANSI_ARGS_((CONST char *pathPtr));
160 static int DoRemoveDirectory _ANSI_ARGS_((Tcl_DString *pathPtr,
161 int recursive, Tcl_DString *errorPtr));
162 static int DoRenameFile _ANSI_ARGS_((CONST char *src,
164 static int TraversalCopy _ANSI_ARGS_((Tcl_DString *srcPtr,
165 Tcl_DString *dstPtr, CONST Tcl_StatBuf *statBufPtr,
166 int type, Tcl_DString *errorPtr));
167 static int TraversalDelete _ANSI_ARGS_((Tcl_DString *srcPtr,
168 Tcl_DString *dstPtr, CONST Tcl_StatBuf *statBufPtr,
169 int type, Tcl_DString *errorPtr));
170 static int TraverseUnixTree _ANSI_ARGS_((
171 TraversalProc *traversalProc,
172 Tcl_DString *sourcePtr, Tcl_DString *destPtr,
173 Tcl_DString *errorPtr, int doRewind));
177 * realpath and purify don't mix happily. It has been noted that realpath
178 * should not be used with purify because of bogus warnings, but just
179 * memset'ing the resolved path will squelch those. This assumes we are
180 * passing the standard MAXPATHLEN size resolved arg.
182 static char * Realpath _ANSI_ARGS_((CONST char *path,
186 Realpath(path, resolved)
190 memset(resolved, 0, MAXPATHLEN);
191 return realpath(path, resolved);
194 #define Realpath realpath
198 #if defined(__APPLE__) && defined(TCL_THREADS) && \
199 defined(MAC_OS_X_VERSION_MIN_REQUIRED) && \
200 MAC_OS_X_VERSION_MIN_REQUIRED < 1030
202 * prior to Darwin 7, realpath is not threadsafe, c.f. bug 711232;
203 * if we might potentially be running on pre-10.3 OSX,
204 * check Darwin release at runtime before using realpath.
206 extern long tclMacOSXDarwinRelease;
207 #define haveRealpath (tclMacOSXDarwinRelease >= 7)
209 #define haveRealpath 1
211 #endif /* NO_REALPATH */
214 #ifdef HAVE_STRUCT_STAT64
215 /* fts doesn't do stat64 */
217 #elif defined(__APPLE__) && defined(__LP64__) && \
218 defined(MAC_OS_X_VERSION_MIN_REQUIRED) && \
219 MAC_OS_X_VERSION_MIN_REQUIRED < 1050
221 * prior to Darwin 9, 64bit fts_open() without FTS_NOSTAT may crash (due to a
222 * 64bit-unsafe ALIGN macro); if we could be running on pre-10.5 OSX, check
223 * Darwin release at runtime and do a separate stat() if necessary.
225 extern long tclMacOSXDarwinRelease;
226 #define noFtsStat (tclMacOSXDarwinRelease < 9)
230 #endif /* HAVE_FTS */
234 *---------------------------------------------------------------------------
236 * TclpObjRenameFile, DoRenameFile --
238 * Changes the name of an existing file or directory, from src to dst.
239 * If src and dst refer to the same file or directory, does nothing
240 * and returns success. Otherwise if dst already exists, it will be
241 * deleted and replaced by src subject to the following conditions:
242 * If src is a directory, dst may be an empty directory.
243 * If src is a file, dst may be a file.
244 * In any other situation where dst already exists, the rename will
248 * If the directory was successfully created, returns TCL_OK.
249 * Otherwise the return value is TCL_ERROR and errno is set to
250 * indicate the error. Some possible values for errno are:
252 * EACCES: src or dst parent directory can't be read and/or written.
253 * EEXIST: dst is a non-empty directory.
254 * EINVAL: src is a root directory or dst is a subdirectory of src.
255 * EISDIR: dst is a directory, but src is not.
256 * ENOENT: src doesn't exist, or src or dst is "".
257 * ENOTDIR: src is a directory, but dst is not.
258 * EXDEV: src and dst are on different filesystems.
261 * The implementation of rename may allow cross-filesystem renames,
262 * but the caller should be prepared to emulate it with copy and
263 * delete if errno is EXDEV.
265 *---------------------------------------------------------------------------
269 TclpObjRenameFile(srcPathPtr, destPathPtr)
271 Tcl_Obj *destPathPtr;
273 return DoRenameFile(Tcl_FSGetNativePath(srcPathPtr),
274 Tcl_FSGetNativePath(destPathPtr));
278 DoRenameFile(src, dst)
279 CONST char *src; /* Pathname of file or dir to be renamed
281 CONST char *dst; /* New pathname of file or directory
284 if (rename(src, dst) == 0) { /* INTL: Native. */
287 if (errno == ENOTEMPTY) {
292 * IRIX returns EIO when you attept to move a directory into
293 * itself. We just map EIO to EINVAL get the right message on SGI.
294 * Most platforms don't return EIO except in really strange cases.
303 * SunOS 4.1.4 reports overwriting a non-empty directory with a
304 * directory as EINVAL instead of EEXIST (first rule out the correct
305 * EINVAL result code for moving a directory into itself). Must be
306 * conditionally compiled because realpath() not defined on all systems.
309 if (errno == EINVAL && haveRealpath) {
310 char srcPath[MAXPATHLEN], dstPath[MAXPATHLEN];
312 Tcl_DirEntry *dirEntPtr;
314 if ((Realpath((char *) src, srcPath) != NULL) /* INTL: Native. */
315 && (Realpath((char *) dst, dstPath) != NULL) /* INTL: Native. */
316 && (strncmp(srcPath, dstPath, strlen(srcPath)) != 0)) {
317 dirPtr = opendir(dst); /* INTL: Native. */
318 if (dirPtr != NULL) {
320 dirEntPtr = TclOSreaddir(dirPtr); /* INTL: Native. */
321 if (dirEntPtr == NULL) {
324 if ((strcmp(dirEntPtr->d_name, ".") != 0) &&
325 (strcmp(dirEntPtr->d_name, "..") != 0)) {
336 #endif /* !NO_REALPATH */
338 if (strcmp(src, "/") == 0) {
340 * Alpha reports renaming / as EBUSY and Linux reports it as EACCES,
348 * DEC Alpha OSF1 V3.0 returns EACCES when attempting to move a
349 * file across filesystems and the parent directory of that file is
350 * not writable. Most other systems return EXDEV. Does nothing to
351 * correct this behavior.
358 *---------------------------------------------------------------------------
360 * TclpObjCopyFile, DoCopyFile --
362 * Copy a single file (not a directory). If dst already exists and
363 * is not a directory, it is removed.
366 * If the file was successfully copied, returns TCL_OK. Otherwise
367 * the return value is TCL_ERROR and errno is set to indicate the
368 * error. Some possible values for errno are:
370 * EACCES: src or dst parent directory can't be read and/or written.
371 * EISDIR: src or dst is a directory.
372 * ENOENT: src doesn't exist. src or dst is "".
375 * This procedure will also copy symbolic links, block, and
376 * character devices, and fifos. For symbolic links, the links
377 * themselves will be copied and not what they point to. For the
378 * other special file types, the directory entry will be copied and
379 * not the contents of the device that it refers to.
381 *---------------------------------------------------------------------------
385 TclpObjCopyFile(srcPathPtr, destPathPtr)
387 Tcl_Obj *destPathPtr;
389 CONST char *src = Tcl_FSGetNativePath(srcPathPtr);
390 Tcl_StatBuf srcStatBuf;
392 if (TclOSlstat(src, &srcStatBuf) != 0) { /* INTL: Native. */
396 return DoCopyFile(src, Tcl_FSGetNativePath(destPathPtr), &srcStatBuf);
400 DoCopyFile(src, dst, statBufPtr)
401 CONST char *src; /* Pathname of file to be copied (native). */
402 CONST char *dst; /* Pathname of file to copy to (native). */
403 CONST Tcl_StatBuf *statBufPtr;
404 /* Used to determine filetype. */
406 Tcl_StatBuf dstStatBuf;
408 if (S_ISDIR(statBufPtr->st_mode)) {
414 * symlink, and some of the other calls will fail if the target
415 * exists, so we remove it first
418 if (TclOSlstat(dst, &dstStatBuf) == 0) { /* INTL: Native. */
419 if (S_ISDIR(dstStatBuf.st_mode)) {
424 if (unlink(dst) != 0) { /* INTL: Native. */
425 if (errno != ENOENT) {
430 switch ((int) (statBufPtr->st_mode & S_IFMT)) {
433 char link[MAXPATHLEN];
436 length = readlink(src, link, sizeof(link)); /* INTL: Native. */
441 if (symlink(link, dst) < 0) { /* INTL: Native. */
445 #ifdef WEAK_IMPORT_COPYFILE
446 if (copyfile != NULL)
448 copyfile(src, dst, NULL, COPYFILE_XATTR|COPYFILE_NOFOLLOW_SRC);
456 // not supported by PIPS
459 if (mknod(dst, statBufPtr->st_mode, /* INTL: Native. */
460 statBufPtr->st_rdev) < 0) {
463 return CopyFileAtts(src, dst, statBufPtr);
467 if (mkfifo(dst, statBufPtr->st_mode) < 0) { /* INTL: Native. */
470 return CopyFileAtts(src, dst, statBufPtr);
473 return CopyFile(src, dst, statBufPtr);
480 *----------------------------------------------------------------------
484 * Helper function for TclpCopyFile. Copies one regular file,
485 * using read() and write().
488 * A standard Tcl result.
491 * A file is copied. Dst will be overwritten if it exists.
493 *----------------------------------------------------------------------
497 CopyFile(src, dst, statBufPtr)
498 CONST char *src; /* Pathname of file to copy (native). */
499 CONST char *dst; /* Pathname of file to create/overwrite
501 CONST Tcl_StatBuf *statBufPtr;
502 /* Used to determine mode and blocksize. */
506 unsigned blockSize; /* Optimal I/O blocksize for filesystem */
507 char *buffer; /* Data buffer for copy */
510 if ((srcFd = TclOSopen(src, O_RDONLY, 0)) < 0) { /* INTL: Native. */
514 dstFd = TclOSopen(dst, O_CREAT|O_TRUNC|O_WRONLY, /* INTL: Native. */
515 statBufPtr->st_mode);
521 #ifdef HAVE_ST_BLKSIZE
522 blockSize = statBufPtr->st_blksize;
527 if (fstatfs(srcFd, &fs, sizeof(fs), 0) == 0) {
528 blockSize = fs.f_bsize;
538 /* [SF Tcl Bug 1586470] Even if we HAVE_ST_BLKSIZE, there are
539 * filesystems which report a bogus value for the blocksize. An
540 * example is the Andrew Filesystem (afs), reporting a blocksize
541 * of 0. When detecting such a situation we now simply fall back
542 * to a hardwired default size.
545 if (blockSize <= 0) {
548 buffer = ckalloc(blockSize);
550 nread = read(srcFd, buffer, blockSize);
551 if ((nread == (size_t)-1) || (nread == 0)) {
554 if (write(dstFd, buffer, nread) != nread) {
562 if ((close(dstFd) != 0) || (nread == (size_t)-1)) {
563 unlink(dst); /* INTL: Native. */
566 if (CopyFileAtts(src, dst, statBufPtr) == TCL_ERROR) {
568 * The copy succeeded, but setting the permissions failed, so be in
569 * a consistent state, we remove the file that was created by the
573 unlink(dst); /* INTL: Native. */
580 *---------------------------------------------------------------------------
582 * TclpObjDeleteFile, TclpDeleteFile --
584 * Removes a single file (not a directory).
587 * If the file was successfully deleted, returns TCL_OK. Otherwise
588 * the return value is TCL_ERROR and errno is set to indicate the
589 * error. Some possible values for errno are:
591 * EACCES: a parent directory can't be read and/or written.
592 * EISDIR: path is a directory.
593 * ENOENT: path doesn't exist or is "".
596 * The file is deleted, even if it is read-only.
598 *---------------------------------------------------------------------------
602 TclpObjDeleteFile(pathPtr)
605 return TclpDeleteFile(Tcl_FSGetNativePath(pathPtr));
610 CONST char *path; /* Pathname of file to be removed (native). */
612 if (unlink(path) != 0) { /* INTL: Native. */
619 *---------------------------------------------------------------------------
621 * TclpCreateDirectory, DoCreateDirectory --
623 * Creates the specified directory. All parent directories of the
624 * specified directory must already exist. The directory is
625 * automatically created with permissions so that user can access
626 * the new directory and create new files or subdirectories in it.
629 * If the directory was successfully created, returns TCL_OK.
630 * Otherwise the return value is TCL_ERROR and errno is set to
631 * indicate the error. Some possible values for errno are:
633 * EACCES: a parent directory can't be read and/or written.
634 * EEXIST: path already exists.
635 * ENOENT: a parent directory doesn't exist.
638 * A directory is created with the current umask, except that
639 * permission for u+rwx will always be added.
641 *---------------------------------------------------------------------------
645 TclpObjCreateDirectory(pathPtr)
648 return DoCreateDirectory(Tcl_FSGetNativePath(pathPtr));
652 DoCreateDirectory(path)
653 CONST char *path; /* Pathname of directory to create (native). */
661 * umask return value is actually the inverse of the permissions.
664 mode = (0777 & ~mode) | S_IRUSR | S_IWUSR | S_IXUSR;
666 if (mkdir(path, mode) != 0) { /* INTL: Native. */
673 *---------------------------------------------------------------------------
675 * TclpObjCopyDirectory --
677 * Recursively copies a directory. The target directory dst must
678 * not already exist. Note that this function does not merge two
679 * directory hierarchies, even if the target directory is an an
683 * If the directory was successfully copied, returns TCL_OK.
684 * Otherwise the return value is TCL_ERROR, errno is set to indicate
685 * the error, and the pathname of the file that caused the error
686 * is stored in errorPtr. See TclpObjCreateDirectory and
687 * TclpObjCopyFile for a description of possible values for errno.
690 * An exact copy of the directory hierarchy src will be created
691 * with the name dst. If an error occurs, the error will
692 * be returned immediately, and remaining files will not be
695 *---------------------------------------------------------------------------
699 TclpObjCopyDirectory(srcPathPtr, destPathPtr, errorPtr)
701 Tcl_Obj *destPathPtr;
705 Tcl_DString srcString, dstString;
709 transPtr = Tcl_FSGetTranslatedPath(NULL,srcPathPtr);
710 Tcl_UtfToExternalDString(NULL,
711 (transPtr != NULL ? Tcl_GetString(transPtr) : NULL),
713 if (transPtr != NULL) {
714 Tcl_DecrRefCount(transPtr);
716 transPtr = Tcl_FSGetTranslatedPath(NULL,destPathPtr);
717 Tcl_UtfToExternalDString(NULL,
718 (transPtr != NULL ? Tcl_GetString(transPtr) : NULL),
720 if (transPtr != NULL) {
721 Tcl_DecrRefCount(transPtr);
724 ret = TraverseUnixTree(TraversalCopy, &srcString, &dstString, &ds, 0);
726 Tcl_DStringFree(&srcString);
727 Tcl_DStringFree(&dstString);
730 *errorPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), -1);
731 Tcl_DStringFree(&ds);
732 Tcl_IncrRefCount(*errorPtr);
739 *---------------------------------------------------------------------------
741 * TclpRemoveDirectory, DoRemoveDirectory --
743 * Removes directory (and its contents, if the recursive flag is set).
746 * If the directory was successfully removed, returns TCL_OK.
747 * Otherwise the return value is TCL_ERROR, errno is set to indicate
748 * the error, and the pathname of the file that caused the error
749 * is stored in errorPtr. Some possible values for errno are:
751 * EACCES: path directory can't be read and/or written.
752 * EEXIST: path is a non-empty directory.
753 * EINVAL: path is a root directory.
754 * ENOENT: path doesn't exist or is "".
755 * ENOTDIR: path is not a directory.
758 * Directory removed. If an error occurs, the error will be returned
759 * immediately, and remaining files will not be deleted.
761 *---------------------------------------------------------------------------
765 TclpObjRemoveDirectory(pathPtr, recursive, errorPtr)
771 Tcl_DString pathString;
773 Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(NULL, pathPtr);
775 Tcl_UtfToExternalDString(NULL,
776 (transPtr != NULL ? Tcl_GetString(transPtr) : NULL),
778 if (transPtr != NULL) {
779 Tcl_DecrRefCount(transPtr);
781 ret = DoRemoveDirectory(&pathString, recursive, &ds);
782 Tcl_DStringFree(&pathString);
785 *errorPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), -1);
786 Tcl_DStringFree(&ds);
787 Tcl_IncrRefCount(*errorPtr);
793 DoRemoveDirectory(pathPtr, recursive, errorPtr)
794 Tcl_DString *pathPtr; /* Pathname of directory to be removed
796 int recursive; /* If non-zero, removes directories that
797 * are nonempty. Otherwise, will only remove
798 * empty directories. */
799 Tcl_DString *errorPtr; /* If non-NULL, uninitialized or free
800 * DString filled with UTF-8 name of file
807 path = Tcl_DStringValue(pathPtr);
810 TclPrint1(" == DoRemoveDirectory() - \"%S\".\n", path);
813 if (recursive != 0) {
814 /* We should try to change permissions so this can be deleted */
818 if (TclOSstat(path, &statBuf) == 0) {
819 oldPerm = (mode_t) (statBuf.st_mode & 0x00007FFF);
822 newPerm = oldPerm | (64+128+256);
823 chmod(path, (mode_t) newPerm);
826 if (rmdir(path) == 0) { /* INTL: Native. */
829 if (errno == ENOTEMPTY) {
834 if ((errno != EEXIST) || (recursive == 0)) {
835 if (errorPtr != NULL) {
836 Tcl_ExternalToUtfDString(NULL, path, -1, errorPtr);
842 * The directory is nonempty, but the recursive flag has been
843 * specified, so we recursively remove all the files in the directory.
846 if (result == TCL_OK) {
847 result = TraverseUnixTree(TraversalDelete, pathPtr, NULL, errorPtr, 1);
850 if ((result != TCL_OK) && (recursive != 0)) {
851 /* Try to restore permissions */
852 chmod(path, oldPerm);
858 *---------------------------------------------------------------------------
860 * TraverseUnixTree --
862 * Traverse directory tree specified by sourcePtr, calling the function
863 * traverseProc for each file and directory encountered. If destPtr
864 * is non-null, each of name in the sourcePtr directory is appended to
865 * the directory specified by destPtr and passed as the second argument
866 * to traverseProc() .
869 * Standard Tcl result.
872 * None caused by TraverseUnixTree, however the user specified
873 * traverseProc() may change state. If an error occurs, the error will
874 * be returned immediately, and remaining files will not be processed.
876 *---------------------------------------------------------------------------
880 TraverseUnixTree(traverseProc, sourcePtr, targetPtr, errorPtr, doRewind)
881 TraversalProc *traverseProc;/* Function to call for every file and
882 * directory in source hierarchy. */
883 Tcl_DString *sourcePtr; /* Pathname of source directory to be
884 * traversed (native). */
885 Tcl_DString *targetPtr; /* Pathname of directory to traverse in
886 * parallel with source directory (native). */
887 Tcl_DString *errorPtr; /* If non-NULL, uninitialized or free
888 * DString filled with UTF-8 name of file
890 int doRewind; /* Flag indicating that to ensure complete
891 * traversal of source hierarchy, the readdir
892 * loop should be rewound whenever
893 * traverseProc has returned TCL_OK; this is
894 * required when traverseProc modifies the
895 * source hierarchy, e.g. by deleting files. */
898 CONST char *source, *errfile;
899 int result, sourceLen;
902 int numProcessed = 0;
903 Tcl_DirEntry *dirEntPtr;
906 CONST char *paths[2] = {NULL, NULL};
913 targetLen = 0; /* lint. */
915 source = Tcl_DStringValue(sourcePtr);
916 if (TclOSlstat(source, &statBuf) != 0) { /* INTL: Native. */
920 if (!S_ISDIR(statBuf.st_mode)) {
922 * Process the regular file
925 return (*traverseProc)(sourcePtr, targetPtr, &statBuf, DOTREE_F,
929 dirPtr = opendir(source); /* INTL: Native. */
930 if (dirPtr == NULL) {
932 * Can't read directory
938 result = (*traverseProc)(sourcePtr, targetPtr, &statBuf, DOTREE_PRED,
940 if (result != TCL_OK) {
945 Tcl_DStringAppend(sourcePtr, "/", 1);
946 sourceLen = Tcl_DStringLength(sourcePtr);
948 if (targetPtr != NULL) {
949 Tcl_DStringAppend(targetPtr, "/", 1);
950 targetLen = Tcl_DStringLength(targetPtr);
953 while ((dirEntPtr = TclOSreaddir(dirPtr)) != NULL) { /* INTL: Native. */
954 if ((dirEntPtr->d_name[0] == '.')
955 && ((dirEntPtr->d_name[1] == '\0')
956 || (strcmp(dirEntPtr->d_name, "..") == 0))) {
961 * Append name after slash, and recurse on the file.
964 Tcl_DStringAppend(sourcePtr, dirEntPtr->d_name, -1);
965 if (targetPtr != NULL) {
966 Tcl_DStringAppend(targetPtr, dirEntPtr->d_name, -1);
968 result = TraverseUnixTree(traverseProc, sourcePtr, targetPtr,
970 if (result != TCL_OK) {
977 * Remove name after slash.
980 Tcl_DStringSetLength(sourcePtr, sourceLen);
981 if (targetPtr != NULL) {
982 Tcl_DStringSetLength(targetPtr, targetLen);
984 if (doRewind && (numProcessed > MAX_READDIR_UNLINK_THRESHOLD)) {
986 * Call rewinddir if we've called unlink or rmdir so many times
987 * (since the opendir or the previous rewinddir), to avoid
988 * a NULL-return that may a symptom of a buggy readdir.
997 * Strip off the trailing slash we added
1000 Tcl_DStringSetLength(sourcePtr, sourceLen - 1);
1001 if (targetPtr != NULL) {
1002 Tcl_DStringSetLength(targetPtr, targetLen - 1);
1005 if (result == TCL_OK) {
1007 * Call traverseProc() on a directory after visiting all the
1008 * files in that directory.
1011 result = (*traverseProc)(sourcePtr, targetPtr, &statBuf, DOTREE_POSTD,
1014 #else /* HAVE_FTS */
1016 fts = fts_open((char**)paths, FTS_PHYSICAL | FTS_NOCHDIR |
1017 (noFtsStat || doRewind ? FTS_NOSTAT : 0), NULL);
1023 sourceLen = Tcl_DStringLength(sourcePtr);
1024 if (targetPtr != NULL) {
1025 targetLen = Tcl_DStringLength(targetPtr);
1028 while ((ent = fts_read(fts)) != NULL) {
1029 unsigned short info = ent->fts_info;
1030 char * path = ent->fts_path + sourceLen;
1031 unsigned short pathlen = ent->fts_pathlen - sourceLen;
1033 Tcl_StatBuf *statBufPtr = NULL;
1035 if (info == FTS_DNR || info == FTS_ERR || info == FTS_NS) {
1036 errfile = ent->fts_path;
1039 Tcl_DStringAppend(sourcePtr, path, pathlen);
1040 if (targetPtr != NULL) {
1041 Tcl_DStringAppend(targetPtr, path, pathlen);
1048 type = DOTREE_POSTD;
1054 if (!doRewind) { /* no need to stat for delete */
1056 statBufPtr = &statBuf;
1057 if (TclOSlstat(ent->fts_path, statBufPtr) != 0) {
1058 errfile = ent->fts_path;
1062 statBufPtr = ent->fts_statp;
1065 result = (*traverseProc)(sourcePtr, targetPtr, statBufPtr, type,
1067 if (result != TCL_OK) {
1070 Tcl_DStringSetLength(sourcePtr, sourceLen);
1071 if (targetPtr != NULL) {
1072 Tcl_DStringSetLength(targetPtr, targetLen);
1075 #endif /* HAVE_FTS */
1078 if (errfile != NULL) {
1079 if (errorPtr != NULL) {
1080 Tcl_ExternalToUtfDString(NULL, errfile, -1, errorPtr);
1088 #endif /* HAVE_FTS */
1094 *----------------------------------------------------------------------
1098 * Called from TraverseUnixTree in order to execute a recursive copy
1102 * Standard Tcl result.
1105 * The file or directory src may be copied to dst, depending on
1106 * the value of type.
1108 *----------------------------------------------------------------------
1112 TraversalCopy(srcPtr, dstPtr, statBufPtr, type, errorPtr)
1113 Tcl_DString *srcPtr; /* Source pathname to copy (native). */
1114 Tcl_DString *dstPtr; /* Destination pathname of copy (native). */
1115 CONST Tcl_StatBuf *statBufPtr;
1116 /* Stat info for file specified by srcPtr. */
1117 int type; /* Reason for call - see TraverseUnixTree(). */
1118 Tcl_DString *errorPtr; /* If non-NULL, uninitialized or free
1119 * DString filled with UTF-8 name of file
1124 if (DoCopyFile(Tcl_DStringValue(srcPtr), Tcl_DStringValue(dstPtr),
1125 statBufPtr) == TCL_OK) {
1131 if (DoCreateDirectory(Tcl_DStringValue(dstPtr)) == TCL_OK) {
1137 if (CopyFileAtts(Tcl_DStringValue(srcPtr),
1138 Tcl_DStringValue(dstPtr), statBufPtr) == TCL_OK) {
1146 * There shouldn't be a problem with src, because we already checked it
1150 if (errorPtr != NULL) {
1151 Tcl_ExternalToUtfDString(NULL, Tcl_DStringValue(dstPtr),
1152 Tcl_DStringLength(dstPtr), errorPtr);
1158 *---------------------------------------------------------------------------
1160 * TraversalDelete --
1162 * Called by procedure TraverseUnixTree for every file and directory
1163 * that it encounters in a directory hierarchy. This procedure unlinks
1164 * files, and removes directories after all the containing files
1165 * have been processed.
1168 * Standard Tcl result.
1171 * Files or directory specified by src will be deleted.
1173 *----------------------------------------------------------------------
1177 TraversalDelete(srcPtr, ignore, statBufPtr, type, errorPtr)
1178 Tcl_DString *srcPtr; /* Source pathname (native). */
1179 Tcl_DString *ignore; /* Destination pathname (not used). */
1180 CONST Tcl_StatBuf *statBufPtr;
1181 /* Stat info for file specified by srcPtr. */
1182 int type; /* Reason for call - see TraverseUnixTree(). */
1183 Tcl_DString *errorPtr; /* If non-NULL, uninitialized or free
1184 * DString filled with UTF-8 name of file
1189 if (TclpDeleteFile(Tcl_DStringValue(srcPtr)) == 0) {
1197 case DOTREE_POSTD: {
1198 if (DoRemoveDirectory(srcPtr, 0, NULL) == 0) {
1204 if (errorPtr != NULL) {
1205 Tcl_ExternalToUtfDString(NULL, Tcl_DStringValue(srcPtr),
1206 Tcl_DStringLength(srcPtr), errorPtr);
1212 *---------------------------------------------------------------------------
1216 * Copy the file attributes such as owner, group, permissions,
1217 * and modification date from one file to another.
1220 * Standard Tcl result.
1223 * user id, group id, permission bits, last modification time, and
1224 * last access time are updated in the new file to reflect the
1227 *---------------------------------------------------------------------------
1231 CopyFileAtts(src, dst, statBufPtr)
1232 CONST char *src; /* Path name of source file (native). */
1233 CONST char *dst; /* Path name of target file (native). */
1234 CONST Tcl_StatBuf *statBufPtr;
1235 /* Stat info for source file */
1237 struct utimbuf tval;
1240 newMode = statBufPtr->st_mode
1241 & (S_ISUID | S_ISGID | S_IRWXU | S_IRWXG | S_IRWXO);
1244 * Note that if you copy a setuid file that is owned by someone
1245 * else, and you are not root, then the copy will be setuid to you.
1246 * The most correct implementation would probably be to have the
1247 * copy not setuid to anyone if the original file was owned by
1248 * someone else, but this corner case isn't currently handled.
1249 * It would require another lstat(), or getuid().
1252 if (chmod(dst, newMode)) { /* INTL: Native. */
1253 newMode &= ~(S_ISUID | S_ISGID);
1254 if (chmod(dst, newMode)) { /* INTL: Native. */
1259 tval.actime = statBufPtr->st_atime;
1260 tval.modtime = statBufPtr->st_mtime;
1262 if (utime(dst, &tval)) { /* INTL: Native. */
1265 #ifdef HAVE_COPYFILE
1266 #ifdef WEAK_IMPORT_COPYFILE
1267 if (copyfile != NULL)
1269 copyfile(src, dst, NULL, COPYFILE_XATTR|COPYFILE_ACL);
1276 *----------------------------------------------------------------------
1280 * Gets the group attribute of a file.
1283 * Standard TCL result. Returns a new Tcl_Obj in attributePtrPtr
1284 * if there is no error.
1287 * A new object is allocated.
1289 *----------------------------------------------------------------------
1293 GetGroupAttribute(interp, objIndex, fileName, attributePtrPtr)
1294 Tcl_Interp *interp; /* The interp we are using for errors. */
1295 int objIndex; /* The index of the attribute. */
1296 Tcl_Obj *fileName; /* The name of the file (UTF-8). */
1297 Tcl_Obj **attributePtrPtr; /* A pointer to return the object with. */
1299 Tcl_StatBuf statBuf;
1300 struct group *groupPtr;
1303 result = TclpObjStat(fileName, &statBuf);
1306 Tcl_AppendResult(interp, "could not read \"",
1307 Tcl_GetString(fileName), "\": ",
1308 Tcl_PosixError(interp), (char *) NULL);
1312 groupPtr = TclpGetGrGid(statBuf.st_gid);
1314 if (result == -1 || groupPtr == NULL) {
1315 *attributePtrPtr = Tcl_NewIntObj((int) statBuf.st_gid);
1320 utf = Tcl_ExternalToUtfDString(NULL, groupPtr->gr_name, -1, &ds);
1321 *attributePtrPtr = Tcl_NewStringObj(utf, -1);
1322 Tcl_DStringFree(&ds);
1329 *----------------------------------------------------------------------
1333 * Gets the owner attribute of a file.
1336 * Standard TCL result. Returns a new Tcl_Obj in attributePtrPtr
1337 * if there is no error.
1340 * A new object is allocated.
1342 *----------------------------------------------------------------------
1346 GetOwnerAttribute(interp, objIndex, fileName, attributePtrPtr)
1347 Tcl_Interp *interp; /* The interp we are using for errors. */
1348 int objIndex; /* The index of the attribute. */
1349 Tcl_Obj *fileName; /* The name of the file (UTF-8). */
1350 Tcl_Obj **attributePtrPtr; /* A pointer to return the object with. */
1352 Tcl_StatBuf statBuf;
1353 struct passwd *pwPtr;
1356 result = TclpObjStat(fileName, &statBuf);
1359 Tcl_AppendResult(interp, "could not read \"",
1360 Tcl_GetString(fileName), "\": ",
1361 Tcl_PosixError(interp), (char *) NULL);
1365 pwPtr = TclpGetPwUid(statBuf.st_uid);
1367 if (result == -1 || pwPtr == NULL) {
1368 *attributePtrPtr = Tcl_NewIntObj((int) statBuf.st_uid);
1373 utf = Tcl_ExternalToUtfDString(NULL, pwPtr->pw_name, -1, &ds);
1374 *attributePtrPtr = Tcl_NewStringObj(utf, Tcl_DStringLength(&ds));
1375 Tcl_DStringFree(&ds);
1382 *----------------------------------------------------------------------
1384 * GetPermissionsAttribute
1386 * Gets the group attribute of a file.
1389 * Standard TCL result. Returns a new Tcl_Obj in attributePtrPtr
1390 * if there is no error. The object will have ref count 0.
1393 * A new object is allocated.
1395 *----------------------------------------------------------------------
1399 GetPermissionsAttribute(interp, objIndex, fileName, attributePtrPtr)
1400 Tcl_Interp *interp; /* The interp we are using for errors. */
1401 int objIndex; /* The index of the attribute. */
1402 Tcl_Obj *fileName; /* The name of the file (UTF-8). */
1403 Tcl_Obj **attributePtrPtr; /* A pointer to return the object with. */
1405 Tcl_StatBuf statBuf;
1406 char returnString[7];
1409 result = TclpObjStat(fileName, &statBuf);
1412 Tcl_AppendResult(interp, "could not read \"",
1413 Tcl_GetString(fileName), "\": ",
1414 Tcl_PosixError(interp), (char *) NULL);
1418 sprintf(returnString, "%0#5lo", (long) (statBuf.st_mode & 0x00007FFF));
1420 *attributePtrPtr = Tcl_NewStringObj(returnString, -1);
1426 *---------------------------------------------------------------------------
1428 * SetGroupAttribute --
1430 * Sets the group of the file to the specified group.
1433 * Standard TCL result.
1438 *---------------------------------------------------------------------------
1442 SetGroupAttribute(interp, objIndex, fileName, attributePtr)
1443 Tcl_Interp *interp; /* The interp for error reporting. */
1444 int objIndex; /* The index of the attribute. */
1445 Tcl_Obj *fileName; /* The name of the file (UTF-8). */
1446 Tcl_Obj *attributePtr; /* New group for file. */
1452 if (Tcl_GetLongFromObj(NULL, attributePtr, &gid) != TCL_OK) {
1454 struct group *groupPtr;
1458 string = Tcl_GetStringFromObj(attributePtr, &length);
1459 native = Tcl_UtfToExternalDString(NULL, string, length, &ds);
1460 groupPtr = TclpGetGrNam(native); /* INTL: Native. */
1461 Tcl_DStringFree(&ds);
1463 if (groupPtr == NULL) {
1465 Tcl_AppendResult(interp, "could not set group for file \"",
1466 Tcl_GetString(fileName), "\": group \"",
1467 string, "\" does not exist",
1471 gid = groupPtr->gr_gid;
1474 native = Tcl_FSGetNativePath(fileName);
1475 result = chown(native, (uid_t) -1, (gid_t) gid); /* INTL: Native. */
1479 Tcl_AppendResult(interp, "could not set group for file \"",
1480 Tcl_GetString(fileName), "\": ", Tcl_PosixError(interp),
1488 *---------------------------------------------------------------------------
1490 * SetOwnerAttribute --
1492 * Sets the owner of the file to the specified owner.
1495 * Standard TCL result.
1500 *---------------------------------------------------------------------------
1504 SetOwnerAttribute(interp, objIndex, fileName, attributePtr)
1505 Tcl_Interp *interp; /* The interp for error reporting. */
1506 int objIndex; /* The index of the attribute. */
1507 Tcl_Obj *fileName; /* The name of the file (UTF-8). */
1508 Tcl_Obj *attributePtr; /* New owner for file. */
1514 if (Tcl_GetLongFromObj(NULL, attributePtr, &uid) != TCL_OK) {
1516 struct passwd *pwPtr;
1520 string = Tcl_GetStringFromObj(attributePtr, &length);
1521 native = Tcl_UtfToExternalDString(NULL, string, length, &ds);
1522 pwPtr = TclpGetPwNam(native); /* INTL: Native. */
1523 Tcl_DStringFree(&ds);
1525 if (pwPtr == NULL) {
1527 Tcl_AppendResult(interp, "could not set owner for file \"",
1528 Tcl_GetString(fileName), "\": user \"",
1529 string, "\" does not exist",
1533 uid = pwPtr->pw_uid;
1536 native = Tcl_FSGetNativePath(fileName);
1537 result = chown(native, (uid_t) uid, (gid_t) -1); /* INTL: Native. */
1541 Tcl_AppendResult(interp, "could not set owner for file \"",
1542 Tcl_GetString(fileName), "\": ",
1543 Tcl_PosixError(interp), (char *) NULL);
1550 *---------------------------------------------------------------------------
1552 * SetPermissionsAttribute
1554 * Sets the file to the given permission.
1557 * Standard TCL result.
1560 * The permission of the file is changed.
1562 *---------------------------------------------------------------------------
1566 SetPermissionsAttribute(interp, objIndex, fileName, attributePtr)
1567 Tcl_Interp *interp; /* The interp we are using for errors. */
1568 int objIndex; /* The index of the attribute. */
1569 Tcl_Obj *fileName; /* The name of the file (UTF-8). */
1570 Tcl_Obj *attributePtr; /* The attribute to set. */
1578 * First try if the string is a number
1580 if (Tcl_GetLongFromObj(NULL, attributePtr, &mode) == TCL_OK) {
1581 newMode = (mode_t) (mode & 0x00007FFF);
1584 char *modeStringPtr = Tcl_GetString(attributePtr);
1587 * Try the forms "rwxrwxrwx" and "ugo=rwx"
1589 * We get the current mode of the file, in order to allow for
1590 * ug+-=rwx style chmod strings.
1592 result = TclpObjStat(fileName, &buf);
1594 Tcl_AppendResult(interp, "could not read \"",
1595 Tcl_GetString(fileName), "\": ",
1596 Tcl_PosixError(interp), (char *) NULL);
1599 newMode = (mode_t) (buf.st_mode & 0x00007FFF);
1601 if (GetModeFromPermString(NULL, modeStringPtr, &newMode) != TCL_OK) {
1602 Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
1603 "unknown permission string format \"",
1604 modeStringPtr, "\"", (char *) NULL);
1609 native = Tcl_FSGetNativePath(fileName);
1610 result = chmod(native, newMode); /* INTL: Native. */
1612 Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
1613 "could not set permissions for file \"",
1614 Tcl_GetString(fileName), "\": ",
1615 Tcl_PosixError(interp), (char *) NULL);
1622 *---------------------------------------------------------------------------
1624 * TclpObjListVolumes --
1626 * Lists the currently mounted volumes, which on UNIX is just /.
1629 * The list of volumes.
1634 *---------------------------------------------------------------------------
1638 TclpObjListVolumes(void)
1640 Tcl_Obj *resultPtr = Tcl_NewStringObj("/",1);
1642 Tcl_IncrRefCount(resultPtr);
1647 *----------------------------------------------------------------------
1649 * GetModeFromPermString --
1651 * This procedure is invoked to process the "file permissions"
1652 * Tcl command, to check for a "rwxrwxrwx" or "ugoa+-=rwxst" string.
1653 * See the user documentation for details on what it does.
1656 * A standard Tcl result.
1659 * See the user documentation.
1661 *----------------------------------------------------------------------
1665 GetModeFromPermString(interp, modeStringPtr, modePtr)
1666 Tcl_Interp *interp; /* The interp we are using for errors. */
1667 char *modeStringPtr; /* Permissions string */
1668 mode_t *modePtr; /* pointer to the mode value */
1671 mode_t oldMode; /* Storage for the value of the old mode
1672 * (that is passed in), to allow for the
1673 * chmod style manipulation */
1674 int i,n, who, op, what, op_found, who_found;
1677 * We start off checking for an "rwxrwxrwx" style permissions string
1679 if (strlen(modeStringPtr) != 9) {
1680 goto chmodStyleCheck;
1684 for (i = 0; i < 9; i++) {
1685 switch (*(modeStringPtr+i)) {
1688 goto chmodStyleCheck;
1690 newMode |= (1<<(8-i));
1694 goto chmodStyleCheck;
1696 newMode |= (1<<(8-i));
1700 goto chmodStyleCheck;
1702 newMode |= (1<<(8-i));
1705 if (((i%3) != 2) || (i > 5)) {
1706 goto chmodStyleCheck;
1708 newMode |= (1<<(8-i));
1709 newMode |= (1<<(11-(i/3)));
1712 if (((i%3) != 2) || (i > 5)) {
1713 goto chmodStyleCheck;
1715 newMode |= (1<<(11-(i/3)));
1719 goto chmodStyleCheck;
1721 newMode |= (1<<(8-i));
1726 goto chmodStyleCheck;
1734 * Oops, not what we thought it was, so go on
1736 goto chmodStyleCheck;
1744 * We now check for an "ugoa+-=rwxst" style permissions string
1747 for (n = 0 ; *(modeStringPtr+n) != '\0' ; n = n + i) {
1749 who = op = what = op_found = who_found = 0;
1750 for (i = 0 ; *(modeStringPtr+n+i) != '\0' ; i++ ) {
1753 switch (*(modeStringPtr+n+i)) {
1774 switch (*(modeStringPtr+n+i)) {
1792 switch (*(modeStringPtr+n+i)) {
1813 if (*(modeStringPtr+n+i) == ',') {
1820 *modePtr = oldMode | (who & what);
1823 *modePtr = oldMode & ~(who & what);
1826 *modePtr = (oldMode & ~who) | (who & what);
1834 *---------------------------------------------------------------------------
1836 * TclpObjNormalizePath --
1838 * This function scans through a path specification and replaces
1839 * it, in place, with a normalized version. A normalized version
1840 * is one in which all symlinks in the path are replaced with
1841 * their expanded form (except a symlink at the very end of the
1845 * The new 'nextCheckpoint' value, giving as far as we could
1846 * understand in the path.
1849 * The pathPtr string, is modified.
1851 *---------------------------------------------------------------------------
1854 TclpObjNormalizePath(interp, pathPtr, nextCheckpoint)
1859 char *currentPathEndPosition;
1862 char *path = Tcl_GetStringFromObj(pathPtr, &pathLen);
1864 char normPath[MAXPATHLEN];
1866 CONST char *nativePath;
1869 * We add '1' here because if nextCheckpoint is zero we know
1870 * that '/' exists, and if it isn't zero, it must point at
1871 * a directory separator which we also know exists.
1873 currentPathEndPosition = path + nextCheckpoint;
1874 if (*currentPathEndPosition == '/') {
1875 currentPathEndPosition++;
1879 /* For speed, try to get the entire path in one go */
1880 if (nextCheckpoint == 0 && haveRealpath) {
1881 char *lastDir = strrchr(currentPathEndPosition, '/');
1882 if (lastDir != NULL) {
1883 nativePath = Tcl_UtfToExternalDString(NULL, path,
1884 lastDir - path, &ds);
1886 if (Realpath(nativePath, normPath) != NULL) {
1888 if (*nativePath != '/' && *normPath == '/') {
1890 * realpath has transformed a relative path into an
1891 * absolute path, we do not know how to handle this.
1894 nextCheckpoint = lastDir - path;
1898 Tcl_DStringFree(&ds);
1901 /* Else do it the slow way */
1905 cur = *currentPathEndPosition;
1906 if ((cur == '/') && (path != currentPathEndPosition)) {
1907 /* Reached directory separator */
1909 CONST char *nativePath;
1912 nativePath = Tcl_UtfToExternalDString(NULL, path,
1913 currentPathEndPosition - path, &ds);
1914 accessOk = access(nativePath, F_OK);
1915 Tcl_DStringFree(&ds);
1916 if (accessOk != 0) {
1917 /* File doesn't exist */
1920 /* Update the acceptable point */
1921 nextCheckpoint = currentPathEndPosition - path;
1922 } else if (cur == 0) {
1923 /* Reached end of string */
1926 currentPathEndPosition++;
1929 * We should really now convert this to a canonical path. We do
1930 * that with 'realpath' if we have it available. Otherwise we could
1931 * step through every single path component, checking whether it is a
1932 * symlink, but that would be a lot of work, and most modern OSes
1938 * If we only had '/foo' or '/' then we never increment nextCheckpoint
1939 * and we don't need or want to go through 'Realpath'. Also, on some
1940 * platforms, passing an empty string to 'Realpath' will give us the
1941 * normalized pwd, which is not what we want at all!
1943 if (nextCheckpoint == 0) return 0;
1945 nativePath = Tcl_UtfToExternalDString(NULL, path, nextCheckpoint, &ds);
1947 if (Realpath(nativePath, normPath) != NULL) {
1950 newNormLen = strlen(normPath);
1951 if ((newNormLen == Tcl_DStringLength(&ds))
1952 && (strcmp(normPath, nativePath) == 0)) {
1953 /* String is unchanged */
1954 Tcl_DStringFree(&ds);
1955 if (path[nextCheckpoint] != '\0') {
1958 return nextCheckpoint;
1962 * Free up the native path and put in its place the
1963 * converted, normalized path.
1965 Tcl_DStringFree(&ds);
1966 Tcl_ExternalToUtfDString(NULL, normPath, (int) newNormLen, &ds);
1968 if (path[nextCheckpoint] != '\0') {
1969 /* not at end, append remaining path */
1970 int normLen = Tcl_DStringLength(&ds);
1971 Tcl_DStringAppend(&ds, path + nextCheckpoint,
1972 pathLen - nextCheckpoint);
1974 * We recognise up to and including the directory
1977 nextCheckpoint = normLen + 1;
1979 /* We recognise the whole string */
1980 nextCheckpoint = Tcl_DStringLength(&ds);
1983 * Overwrite with the normalized path.
1985 Tcl_SetStringObj(pathPtr, Tcl_DStringValue(&ds),
1986 Tcl_DStringLength(&ds));
1988 Tcl_DStringFree(&ds);
1990 #endif /* !NO_REALPATH */
1992 return nextCheckpoint;