os/persistentdata/persistentstorage/sqlite3api/TEST/TCL/tcldistribution/unix/tclUnixFCmd.c
author sl@SLION-WIN7.fritz.box
Fri, 15 Jun 2012 03:10:57 +0200
changeset 0 bde4ae8d615e
permissions -rw-r--r--
First public contribution.
     1 /*
     2  * tclUnixFCmd.c
     3  *
     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.
     7  *
     8  * Copyright (c) 1996-1998 Sun Microsystems, Inc.
     9  * Portions Copyright (c) 2007-2008 Nokia Corporation and/or its subsidiaries. All rights reserved.   
    10  *
    11  * See the file "license.terms" for information on usage and redistribution
    12  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
    13  *
    14  * RCS: @(#) $Id: tclUnixFCmd.c,v 1.28.2.15 2007/04/29 02:19:51 das Exp $
    15  *
    16  * Portions of this code were derived from NetBSD source code which has
    17  * the following copyright notice:
    18  *
    19  * Copyright (c) 1988, 1993, 1994
    20  *      The Regents of the University of California.  All rights reserved.
    21  *
    22  * Redistribution and use in source and binary forms, with or without
    23  * modification, are permitted provided that the following conditions
    24  * are met:
    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.
    37  *
    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
    48  * SUCH DAMAGE.
    49  */
    50 
    51 #include "tclInt.h"
    52 #include "tclPort.h"
    53 #include <utime.h>
    54 #include <grp.h>
    55 #ifndef HAVE_ST_BLKSIZE
    56 #ifndef NO_FSTATFS
    57 #include <sys/statfs.h>
    58 #endif
    59 #endif
    60 #ifdef HAVE_FTS
    61 #include <fts.h>
    62 #endif
    63 
    64 #ifdef __SYMBIAN32__  
    65 #include "convertPathSlashes.h"  
    66 void TclPrint1(const char* aFmt, const char* aStr);
    67 #endif
    68 /*
    69  * The following constants specify the type of callback when
    70  * TraverseUnixTree() calls the traverseProc()
    71  */
    72 
    73 #define DOTREE_PRED   1     /* pre-order directory  */
    74 #define DOTREE_POSTD  2     /* post-order directory */
    75 #define DOTREE_F      3     /* regular file */
    76 
    77 /*
    78  * Callbacks for file attributes code.
    79  */
    80 
    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,
   101 			    mode_t *modePtr));
   102 
   103 /*
   104  * Prototype for the TraverseUnixTree callback function.
   105  */
   106 
   107 typedef int (TraversalProc) _ANSI_ARGS_((Tcl_DString *srcPtr,
   108 	Tcl_DString *dstPtr, CONST Tcl_StatBuf *statBufPtr, int type,
   109 	Tcl_DString *errorPtr));
   110 
   111 /*
   112  * Constants and variables necessary for file attributes subcommand.
   113  */
   114 
   115 enum {
   116     UNIX_GROUP_ATTRIBUTE,
   117     UNIX_OWNER_ATTRIBUTE,
   118     UNIX_PERMISSIONS_ATTRIBUTE
   119 };
   120 
   121 CONST char *tclpFileAttrStrings[] = {
   122     "-group",
   123     "-owner",
   124     "-permissions",
   125     (char *) NULL
   126 };
   127 
   128 CONST TclFileAttrProcs tclpFileAttrProcs[] = {
   129     {GetGroupAttribute,		SetGroupAttribute},
   130     {GetOwnerAttribute,		SetOwnerAttribute},
   131     {GetPermissionsAttribute,	SetPermissionsAttribute}
   132 };
   133 
   134 /*
   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]
   145  */
   146 
   147 #define MAX_READDIR_UNLINK_THRESHOLD 130
   148 
   149 /*
   150  * Declarations for local procedures defined in this file:
   151  */
   152 
   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,
   163 			    CONST char *dst));
   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));
   174 
   175 #ifdef PURIFY
   176 /*
   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.
   181  */
   182 static char *		Realpath _ANSI_ARGS_((CONST char *path,
   183 			    char *resolved));
   184 
   185 char *
   186 Realpath(path, resolved)
   187     CONST char *path;
   188     char *resolved;
   189 {
   190     memset(resolved, 0, MAXPATHLEN);
   191     return realpath(path, resolved);
   192 }
   193 #else
   194 #define Realpath realpath
   195 #endif
   196 
   197 #ifndef NO_REALPATH
   198 #if defined(__APPLE__) && defined(TCL_THREADS) && \
   199 	defined(MAC_OS_X_VERSION_MIN_REQUIRED) && \
   200 	MAC_OS_X_VERSION_MIN_REQUIRED < 1030
   201 /*
   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.
   205  */
   206 extern long tclMacOSXDarwinRelease;
   207 #define haveRealpath (tclMacOSXDarwinRelease >= 7)
   208 #else
   209 #define haveRealpath 1
   210 #endif
   211 #endif /* NO_REALPATH */
   212 
   213 #ifdef HAVE_FTS
   214 #ifdef HAVE_STRUCT_STAT64
   215 /* fts doesn't do stat64 */
   216 #define noFtsStat 1
   217 #elif defined(__APPLE__) && defined(__LP64__) && \
   218 	defined(MAC_OS_X_VERSION_MIN_REQUIRED) && \
   219 	MAC_OS_X_VERSION_MIN_REQUIRED < 1050
   220 /*
   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.
   224  */
   225 extern long tclMacOSXDarwinRelease;
   226 #define noFtsStat (tclMacOSXDarwinRelease < 9)
   227 #else
   228 #define noFtsStat 0
   229 #endif
   230 #endif /* HAVE_FTS */
   231 
   232 
   233 /*
   234  *---------------------------------------------------------------------------
   235  *
   236  * TclpObjRenameFile, DoRenameFile --
   237  *
   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
   245  *	fail.  
   246  *
   247  * Results:
   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:
   251  *
   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.
   259  *	
   260  * Side effects:
   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.
   264  *
   265  *---------------------------------------------------------------------------
   266  */
   267 
   268 int 
   269 TclpObjRenameFile(srcPathPtr, destPathPtr)
   270     Tcl_Obj *srcPathPtr;
   271     Tcl_Obj *destPathPtr;
   272 {
   273     return DoRenameFile(Tcl_FSGetNativePath(srcPathPtr), 
   274 			Tcl_FSGetNativePath(destPathPtr));
   275 }
   276 
   277 static int
   278 DoRenameFile(src, dst)
   279     CONST char *src;		/* Pathname of file or dir to be renamed
   280 				 * (native). */
   281     CONST char *dst;		/* New pathname of file or directory
   282 				 * (native). */
   283 {
   284     if (rename(src, dst) == 0) {			/* INTL: Native. */
   285 	return TCL_OK;
   286     }
   287     if (errno == ENOTEMPTY) {
   288 	errno = EEXIST;
   289     }
   290 
   291     /*
   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.
   295      */
   296     
   297     if (errno == EIO) {
   298 	errno = EINVAL;
   299     }
   300     
   301 #ifndef NO_REALPATH
   302     /*
   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.
   307      */
   308 
   309     if (errno == EINVAL && haveRealpath) {
   310 	char srcPath[MAXPATHLEN], dstPath[MAXPATHLEN];
   311 	DIR *dirPtr;
   312 	Tcl_DirEntry *dirEntPtr;
   313 
   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) {
   319 		while (1) {
   320 		    dirEntPtr = TclOSreaddir(dirPtr); /* INTL: Native. */
   321 		    if (dirEntPtr == NULL) {
   322 			break;
   323 		    }
   324 		    if ((strcmp(dirEntPtr->d_name, ".") != 0) &&
   325 			    (strcmp(dirEntPtr->d_name, "..") != 0)) {
   326 			errno = EEXIST;
   327 			closedir(dirPtr);
   328 			return TCL_ERROR;
   329 		    }
   330 		}
   331 		closedir(dirPtr);
   332 	    }
   333 	}
   334 	errno = EINVAL;
   335     }
   336 #endif	/* !NO_REALPATH */
   337 
   338     if (strcmp(src, "/") == 0) {
   339 	/*
   340 	 * Alpha reports renaming / as EBUSY and Linux reports it as EACCES,
   341 	 * instead of EINVAL.
   342 	 */
   343 	 
   344 	errno = EINVAL;
   345     }
   346 
   347     /*
   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.
   352      */
   353 
   354     return TCL_ERROR;
   355 }
   356 
   357 /*
   358  *---------------------------------------------------------------------------
   359  *
   360  * TclpObjCopyFile, DoCopyFile --
   361  *
   362  *      Copy a single file (not a directory).  If dst already exists and
   363  *	is not a directory, it is removed.
   364  *
   365  * Results:
   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:
   369  *
   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 "".
   373  *
   374  * Side effects:
   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.
   380  *
   381  *---------------------------------------------------------------------------
   382  */
   383 
   384 int 
   385 TclpObjCopyFile(srcPathPtr, destPathPtr)
   386     Tcl_Obj *srcPathPtr;
   387     Tcl_Obj *destPathPtr;
   388 {
   389     CONST char *src = Tcl_FSGetNativePath(srcPathPtr);
   390     Tcl_StatBuf srcStatBuf;
   391 
   392     if (TclOSlstat(src, &srcStatBuf) != 0) {		/* INTL: Native. */
   393 	return TCL_ERROR;
   394     }
   395 
   396     return DoCopyFile(src, Tcl_FSGetNativePath(destPathPtr), &srcStatBuf);
   397 }
   398 
   399 static int
   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. */
   405 {
   406     Tcl_StatBuf dstStatBuf;
   407 
   408     if (S_ISDIR(statBufPtr->st_mode)) {
   409 	errno = EISDIR;
   410 	return TCL_ERROR;
   411     }
   412 
   413     /*
   414      * symlink, and some of the other calls will fail if the target 
   415      * exists, so we remove it first
   416      */
   417     
   418     if (TclOSlstat(dst, &dstStatBuf) == 0) {		/* INTL: Native. */
   419 	if (S_ISDIR(dstStatBuf.st_mode)) {
   420 	    errno = EISDIR;
   421 	    return TCL_ERROR;
   422 	}
   423     }
   424     if (unlink(dst) != 0) {				/* INTL: Native. */
   425 	if (errno != ENOENT) {
   426 	    return TCL_ERROR;
   427 	} 
   428     }
   429 
   430     switch ((int) (statBufPtr->st_mode & S_IFMT)) {
   431 #ifndef DJGPP
   432         case S_IFLNK: {
   433 	    char link[MAXPATHLEN];
   434 	    int length;
   435 
   436 	    length = readlink(src, link, sizeof(link)); /* INTL: Native. */
   437 	    if (length == -1) {
   438 		return TCL_ERROR;
   439 	    }
   440 	    link[length] = '\0';
   441 	    if (symlink(link, dst) < 0) {		/* INTL: Native. */
   442 		return TCL_ERROR;
   443 	    }
   444 #ifdef HAVE_COPYFILE
   445 #ifdef WEAK_IMPORT_COPYFILE
   446 	    if (copyfile != NULL)
   447 #endif
   448 	    copyfile(src, dst, NULL, COPYFILE_XATTR|COPYFILE_NOFOLLOW_SRC);
   449 #endif
   450 	    break;
   451 	}
   452 #endif
   453         case S_IFBLK:
   454         case S_IFCHR: {
   455 #ifdef __SYMBIAN32__          
   456 		// not supported by PIPS	    
   457 		return TCL_ERROR;
   458 #else
   459 	    if (mknod(dst, statBufPtr->st_mode,		/* INTL: Native. */
   460 		    statBufPtr->st_rdev) < 0) {
   461 		return TCL_ERROR;
   462 	    }
   463 	    return CopyFileAtts(src, dst, statBufPtr);
   464 #endif
   465 	    }
   466         case S_IFIFO: {
   467 	    if (mkfifo(dst, statBufPtr->st_mode) < 0) {	/* INTL: Native. */
   468 		return TCL_ERROR;
   469 	    }
   470 	    return CopyFileAtts(src, dst, statBufPtr);
   471 	}
   472         default: {
   473 	    return CopyFile(src, dst, statBufPtr);
   474 	}
   475     }
   476     return TCL_OK;
   477 }
   478 
   479 /*
   480  *----------------------------------------------------------------------
   481  *
   482  * CopyFile - 
   483  *
   484  *      Helper function for TclpCopyFile.  Copies one regular file,
   485  *	using read() and write().
   486  *
   487  * Results:
   488  *	A standard Tcl result.
   489  *
   490  * Side effects:
   491  *      A file is copied.  Dst will be overwritten if it exists.
   492  *
   493  *----------------------------------------------------------------------
   494  */
   495 
   496 static int 
   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
   500 				 * (native). */
   501     CONST Tcl_StatBuf *statBufPtr;
   502 				/* Used to determine mode and blocksize. */
   503 {
   504     int srcFd;
   505     int dstFd;
   506     unsigned blockSize;		/* Optimal I/O blocksize for filesystem */
   507     char *buffer;		/* Data buffer for copy */
   508     size_t nread;
   509 
   510     if ((srcFd = TclOSopen(src, O_RDONLY, 0)) < 0) {	/* INTL: Native. */
   511 	return TCL_ERROR;
   512     }
   513 
   514     dstFd = TclOSopen(dst, O_CREAT|O_TRUNC|O_WRONLY,	/* INTL: Native. */
   515 	    statBufPtr->st_mode);
   516     if (dstFd < 0) {
   517 	close(srcFd); 
   518 	return TCL_ERROR;
   519     }
   520 
   521 #ifdef HAVE_ST_BLKSIZE
   522     blockSize = statBufPtr->st_blksize;
   523 #else
   524 #ifndef NO_FSTATFS
   525     {
   526 	struct statfs fs;
   527 	if (fstatfs(srcFd, &fs, sizeof(fs), 0) == 0) {
   528 	    blockSize = fs.f_bsize;
   529 	} else {
   530 	    blockSize = 4096;
   531 	}
   532     }
   533 #else 
   534     blockSize = 4096;
   535 #endif
   536 #endif
   537 
   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.
   543      */
   544 
   545     if (blockSize <= 0) {
   546         blockSize = 4096;
   547     }
   548     buffer = ckalloc(blockSize);
   549     while (1) {
   550 	nread = read(srcFd, buffer, blockSize);
   551 	if ((nread == (size_t)-1) || (nread == 0)) {
   552 	    break;
   553 	}
   554 	if (write(dstFd, buffer, nread) != nread) {
   555 	    nread = (size_t) -1;
   556 	    break;
   557 	}
   558     }
   559 
   560     ckfree(buffer);
   561     close(srcFd);
   562     if ((close(dstFd) != 0) || (nread == (size_t)-1)) {
   563 	unlink(dst);					/* INTL: Native. */
   564 	return TCL_ERROR;
   565     }
   566     if (CopyFileAtts(src, dst, statBufPtr) == TCL_ERROR) {
   567 	/*
   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
   570 	 * copy.
   571 	 */
   572 
   573 	unlink(dst);					/* INTL: Native. */
   574 	return TCL_ERROR;
   575     }
   576     return TCL_OK;
   577 }
   578 
   579 /*
   580  *---------------------------------------------------------------------------
   581  *
   582  * TclpObjDeleteFile, TclpDeleteFile --
   583  *
   584  *      Removes a single file (not a directory).
   585  *
   586  * Results:
   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:
   590  *
   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 "".
   594  *
   595  * Side effects:
   596  *      The file is deleted, even if it is read-only.
   597  *
   598  *---------------------------------------------------------------------------
   599  */
   600 
   601 int 
   602 TclpObjDeleteFile(pathPtr)
   603     Tcl_Obj *pathPtr;
   604 {
   605     return TclpDeleteFile(Tcl_FSGetNativePath(pathPtr));
   606 }
   607 
   608 int
   609 TclpDeleteFile(path)
   610     CONST char *path;	/* Pathname of file to be removed (native). */
   611 {
   612     if (unlink(path) != 0) {				/* INTL: Native. */
   613 	return TCL_ERROR;
   614     }
   615     return TCL_OK;
   616 }
   617 
   618 /*
   619  *---------------------------------------------------------------------------
   620  *
   621  * TclpCreateDirectory, DoCreateDirectory --
   622  *
   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.
   627  *
   628  * Results:
   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:
   632  *
   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.
   636  *
   637  * Side effects:
   638  *      A directory is created with the current umask, except that
   639  *	permission for u+rwx will always be added.
   640  *
   641  *---------------------------------------------------------------------------
   642  */
   643 
   644 int 
   645 TclpObjCreateDirectory(pathPtr)
   646     Tcl_Obj *pathPtr;
   647 {
   648     return DoCreateDirectory(Tcl_FSGetNativePath(pathPtr));
   649 }
   650 
   651 static int
   652 DoCreateDirectory(path)
   653     CONST char *path;	/* Pathname of directory to create (native). */
   654 {
   655     mode_t mode;
   656 
   657     mode = umask(0);
   658     umask(mode);
   659 
   660     /*
   661      * umask return value is actually the inverse of the permissions.
   662      */
   663 
   664     mode = (0777 & ~mode) | S_IRUSR | S_IWUSR | S_IXUSR;
   665 
   666     if (mkdir(path, mode) != 0) {			/* INTL: Native. */
   667 	return TCL_ERROR;
   668     }
   669     return TCL_OK;
   670 }
   671 
   672 /*
   673  *---------------------------------------------------------------------------
   674  *
   675  * TclpObjCopyDirectory --
   676  *
   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
   680  *	empty directory.
   681  *
   682  * Results:
   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.
   688  *
   689  * Side effects:
   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
   693  *	processed.
   694  *
   695  *---------------------------------------------------------------------------
   696  */
   697 
   698 int 
   699 TclpObjCopyDirectory(srcPathPtr, destPathPtr, errorPtr)
   700     Tcl_Obj *srcPathPtr;
   701     Tcl_Obj *destPathPtr;
   702     Tcl_Obj **errorPtr;
   703 {
   704     Tcl_DString ds;
   705     Tcl_DString srcString, dstString;
   706     int ret;
   707     Tcl_Obj *transPtr;
   708     
   709     transPtr = Tcl_FSGetTranslatedPath(NULL,srcPathPtr);
   710     Tcl_UtfToExternalDString(NULL, 
   711 			     (transPtr != NULL ? Tcl_GetString(transPtr) : NULL), 
   712 			     -1, &srcString);
   713     if (transPtr != NULL) {
   714 	Tcl_DecrRefCount(transPtr);
   715     }
   716     transPtr = Tcl_FSGetTranslatedPath(NULL,destPathPtr);
   717     Tcl_UtfToExternalDString(NULL, 
   718 			     (transPtr != NULL ? Tcl_GetString(transPtr) : NULL), 
   719 			     -1, &dstString);
   720     if (transPtr != NULL) {
   721 	Tcl_DecrRefCount(transPtr);
   722     }
   723 
   724     ret = TraverseUnixTree(TraversalCopy, &srcString, &dstString, &ds, 0);
   725 
   726     Tcl_DStringFree(&srcString);
   727     Tcl_DStringFree(&dstString);
   728 
   729     if (ret != TCL_OK) {
   730 	*errorPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), -1);
   731 	Tcl_DStringFree(&ds);
   732 	Tcl_IncrRefCount(*errorPtr);
   733     }
   734     return ret;
   735 }
   736 
   737 
   738 /*
   739  *---------------------------------------------------------------------------
   740  *
   741  * TclpRemoveDirectory, DoRemoveDirectory --
   742  *
   743  *	Removes directory (and its contents, if the recursive flag is set).
   744  *
   745  * Results:
   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:
   750  *
   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.
   756  *
   757  * Side effects:
   758  *	Directory removed.  If an error occurs, the error will be returned
   759  *	immediately, and remaining files will not be deleted.
   760  *
   761  *---------------------------------------------------------------------------
   762  */
   763  
   764 int 
   765 TclpObjRemoveDirectory(pathPtr, recursive, errorPtr)
   766     Tcl_Obj *pathPtr;
   767     int recursive;
   768     Tcl_Obj **errorPtr;
   769 {
   770     Tcl_DString ds;
   771     Tcl_DString pathString;
   772     int ret;
   773     Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(NULL, pathPtr);
   774 
   775     Tcl_UtfToExternalDString(NULL, 
   776 			     (transPtr != NULL ? Tcl_GetString(transPtr) : NULL), 
   777 			     -1, &pathString);
   778     if (transPtr != NULL) {
   779 	Tcl_DecrRefCount(transPtr);
   780     }
   781     ret = DoRemoveDirectory(&pathString, recursive, &ds);
   782     Tcl_DStringFree(&pathString);
   783 
   784     if (ret != TCL_OK) {
   785 	*errorPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), -1);
   786 	Tcl_DStringFree(&ds);
   787 	Tcl_IncrRefCount(*errorPtr);
   788     }
   789     return ret;
   790 }
   791 
   792 static int
   793 DoRemoveDirectory(pathPtr, recursive, errorPtr)
   794     Tcl_DString *pathPtr;	/* Pathname of directory to be removed
   795 				 * (native). */
   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
   801 				 * causing error. */
   802 {
   803     CONST char *path;
   804     mode_t oldPerm = 0;
   805     int result;
   806     
   807     path = Tcl_DStringValue(pathPtr);
   808    
   809 #ifdef __SYMBIAN32__  
   810     TclPrint1(" == DoRemoveDirectory() - \"%S\".\n", path);
   811 #endif
   812     
   813     if (recursive != 0) {
   814 	/* We should try to change permissions so this can be deleted */
   815 	Tcl_StatBuf statBuf;
   816 	int newPerm;
   817 
   818 	if (TclOSstat(path, &statBuf) == 0) {
   819 	    oldPerm = (mode_t) (statBuf.st_mode & 0x00007FFF);
   820 	}
   821 	
   822 	newPerm = oldPerm | (64+128+256);
   823 	chmod(path, (mode_t) newPerm);
   824     }
   825     
   826     if (rmdir(path) == 0) {				/* INTL: Native. */
   827 	return TCL_OK;
   828     }
   829     if (errno == ENOTEMPTY) {
   830 	errno = EEXIST;
   831     }
   832 
   833     result = TCL_OK;
   834     if ((errno != EEXIST) || (recursive == 0)) {
   835 	if (errorPtr != NULL) {
   836 	    Tcl_ExternalToUtfDString(NULL, path, -1, errorPtr);
   837 	}
   838 	result = TCL_ERROR;
   839     }
   840     
   841     /*
   842      * The directory is nonempty, but the recursive flag has been
   843      * specified, so we recursively remove all the files in the directory.
   844      */
   845 
   846     if (result == TCL_OK) {
   847 	result = TraverseUnixTree(TraversalDelete, pathPtr, NULL, errorPtr, 1);
   848     }
   849     
   850     if ((result != TCL_OK) && (recursive != 0)) {
   851         /* Try to restore permissions */
   852         chmod(path, oldPerm);
   853     }
   854     return result;
   855 }
   856 
   857 /*
   858  *---------------------------------------------------------------------------
   859  *
   860  * TraverseUnixTree --
   861  *
   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() .
   867  *
   868  * Results:
   869  *      Standard Tcl result.
   870  *
   871  * Side effects:
   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.
   875  *
   876  *---------------------------------------------------------------------------
   877  */
   878 
   879 static int 
   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
   889 				 * causing error. */
   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. */
   896 {
   897     Tcl_StatBuf statBuf;
   898     CONST char *source, *errfile;
   899     int result, sourceLen;
   900     int targetLen;
   901 #ifndef HAVE_FTS
   902     int numProcessed = 0;
   903     Tcl_DirEntry *dirEntPtr;
   904     DIR *dirPtr;
   905 #else
   906     CONST char *paths[2] = {NULL, NULL};
   907     FTS *fts = NULL;
   908     FTSENT *ent;
   909 #endif
   910 
   911     errfile = NULL;
   912     result = TCL_OK;
   913     targetLen = 0;		/* lint. */
   914 
   915     source = Tcl_DStringValue(sourcePtr);
   916     if (TclOSlstat(source, &statBuf) != 0) {		/* INTL: Native. */
   917 	errfile = source;
   918 	goto end;
   919     }
   920     if (!S_ISDIR(statBuf.st_mode)) {
   921 	/*
   922 	 * Process the regular file
   923 	 */
   924 
   925 	return (*traverseProc)(sourcePtr, targetPtr, &statBuf, DOTREE_F,
   926 		errorPtr);
   927     }
   928 #ifndef HAVE_FTS
   929     dirPtr = opendir(source);				/* INTL: Native. */
   930     if (dirPtr == NULL) {
   931 	/* 
   932 	 * Can't read directory
   933 	 */
   934 
   935 	errfile = source;
   936 	goto end;
   937     }
   938     result = (*traverseProc)(sourcePtr, targetPtr, &statBuf, DOTREE_PRED,
   939 	    errorPtr);
   940     if (result != TCL_OK) {
   941 	closedir(dirPtr);
   942 	return result;
   943     }
   944 
   945     Tcl_DStringAppend(sourcePtr, "/", 1);
   946     sourceLen = Tcl_DStringLength(sourcePtr);
   947 
   948     if (targetPtr != NULL) {
   949 	Tcl_DStringAppend(targetPtr, "/", 1);
   950 	targetLen = Tcl_DStringLength(targetPtr);
   951     }
   952 
   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))) {
   957 	    continue;
   958 	}
   959 
   960 	/*
   961 	 * Append name after slash, and recurse on the file.
   962 	 */
   963 
   964 	Tcl_DStringAppend(sourcePtr, dirEntPtr->d_name, -1);
   965 	if (targetPtr != NULL) {
   966 	    Tcl_DStringAppend(targetPtr, dirEntPtr->d_name, -1);
   967 	}
   968 	result = TraverseUnixTree(traverseProc, sourcePtr, targetPtr,
   969 		errorPtr, doRewind);
   970 	if (result != TCL_OK) {
   971 	    break;
   972 	} else {
   973 	    numProcessed++;
   974 	}
   975 
   976 	/*
   977 	 * Remove name after slash.
   978 	 */
   979 
   980 	Tcl_DStringSetLength(sourcePtr, sourceLen);
   981 	if (targetPtr != NULL) {
   982 	    Tcl_DStringSetLength(targetPtr, targetLen);
   983 	}
   984 	if (doRewind && (numProcessed > MAX_READDIR_UNLINK_THRESHOLD)) {
   985 	    /*
   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.
   989 	     */
   990 	    rewinddir(dirPtr);
   991 	    numProcessed = 0;
   992 	}
   993     }
   994     closedir(dirPtr);
   995 
   996     /*
   997      * Strip off the trailing slash we added
   998      */
   999 
  1000     Tcl_DStringSetLength(sourcePtr, sourceLen - 1);
  1001     if (targetPtr != NULL) {
  1002 	Tcl_DStringSetLength(targetPtr, targetLen - 1);
  1003     }
  1004 
  1005     if (result == TCL_OK) {
  1006 	/*
  1007 	 * Call traverseProc() on a directory after visiting all the
  1008 	 * files in that directory.
  1009 	 */
  1010 
  1011 	result = (*traverseProc)(sourcePtr, targetPtr, &statBuf, DOTREE_POSTD,
  1012 		errorPtr);
  1013     }
  1014 #else /* HAVE_FTS */
  1015     paths[0] = source;
  1016     fts = fts_open((char**)paths, FTS_PHYSICAL | FTS_NOCHDIR |
  1017 	    (noFtsStat || doRewind ? FTS_NOSTAT : 0),  NULL);
  1018     if (fts == NULL) {
  1019 	errfile = source;
  1020 	goto end;
  1021     }
  1022 
  1023     sourceLen = Tcl_DStringLength(sourcePtr);
  1024     if (targetPtr != NULL) {
  1025 	targetLen = Tcl_DStringLength(targetPtr);
  1026     }
  1027 
  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;
  1032 	int type;
  1033 	Tcl_StatBuf *statBufPtr = NULL;
  1034 	
  1035 	if (info == FTS_DNR || info == FTS_ERR || info == FTS_NS) {
  1036 	    errfile = ent->fts_path;
  1037 	    break;
  1038 	}
  1039 	Tcl_DStringAppend(sourcePtr, path, pathlen);
  1040 	if (targetPtr != NULL) {
  1041 	    Tcl_DStringAppend(targetPtr, path, pathlen);
  1042 	}
  1043 	switch (info) {
  1044 	    case FTS_D:
  1045 		type = DOTREE_PRED;
  1046 		break;
  1047 	    case FTS_DP:
  1048 		type = DOTREE_POSTD;
  1049 		break;
  1050 	    default:
  1051 		type = DOTREE_F;
  1052 		break;
  1053 	}
  1054 	if (!doRewind) { /* no need to stat for delete */
  1055 	    if (noFtsStat) {
  1056 		statBufPtr = &statBuf;
  1057 		if (TclOSlstat(ent->fts_path, statBufPtr) != 0) {
  1058 		    errfile = ent->fts_path;
  1059 		    break;
  1060 		}
  1061 	    } else {
  1062 		statBufPtr = ent->fts_statp;
  1063 	    }
  1064 	}
  1065 	result = (*traverseProc)(sourcePtr, targetPtr, statBufPtr, type,
  1066 		errorPtr);
  1067 	if (result != TCL_OK) {
  1068 	    break;
  1069 	}
  1070 	Tcl_DStringSetLength(sourcePtr, sourceLen);
  1071 	if (targetPtr != NULL) {
  1072 	    Tcl_DStringSetLength(targetPtr, targetLen);
  1073 	}
  1074     }
  1075 #endif /* HAVE_FTS */
  1076 
  1077     end:
  1078     if (errfile != NULL) {
  1079 	if (errorPtr != NULL) {
  1080 	    Tcl_ExternalToUtfDString(NULL, errfile, -1, errorPtr);
  1081 	}
  1082 	result = TCL_ERROR;
  1083     }
  1084 #ifdef HAVE_FTS
  1085     if (fts != NULL) {
  1086 	fts_close(fts);
  1087     }
  1088 #endif /* HAVE_FTS */
  1089 
  1090     return result;
  1091 }
  1092 
  1093 /*
  1094  *----------------------------------------------------------------------
  1095  *
  1096  * TraversalCopy
  1097  *
  1098  *      Called from TraverseUnixTree in order to execute a recursive copy
  1099  *      of a directory.
  1100  *
  1101  * Results:
  1102  *      Standard Tcl result.
  1103  *
  1104  * Side effects:
  1105  *      The file or directory src may be copied to dst, depending on 
  1106  *      the value of type.
  1107  *      
  1108  *----------------------------------------------------------------------
  1109  */
  1110 
  1111 static int 
  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
  1120 				 * causing error. */
  1121 {
  1122     switch (type) {
  1123 	case DOTREE_F:
  1124 	    if (DoCopyFile(Tcl_DStringValue(srcPtr), Tcl_DStringValue(dstPtr),
  1125 		    statBufPtr) == TCL_OK) {
  1126 		return TCL_OK;
  1127 	    }
  1128 	    break;
  1129 
  1130 	case DOTREE_PRED:
  1131 	    if (DoCreateDirectory(Tcl_DStringValue(dstPtr)) == TCL_OK) {
  1132 		return TCL_OK;
  1133 	    }
  1134 	    break;
  1135 
  1136 	case DOTREE_POSTD:
  1137 	    if (CopyFileAtts(Tcl_DStringValue(srcPtr),
  1138 		    Tcl_DStringValue(dstPtr), statBufPtr) == TCL_OK) {
  1139 		return TCL_OK;
  1140 	    }
  1141 	    break;
  1142 
  1143     }
  1144 
  1145     /*
  1146      * There shouldn't be a problem with src, because we already checked it
  1147      * to get here.
  1148      */
  1149 
  1150     if (errorPtr != NULL) {
  1151 	Tcl_ExternalToUtfDString(NULL, Tcl_DStringValue(dstPtr),
  1152 		Tcl_DStringLength(dstPtr), errorPtr);
  1153     }
  1154     return TCL_ERROR;
  1155 }
  1156 
  1157 /*
  1158  *---------------------------------------------------------------------------
  1159  *
  1160  * TraversalDelete --
  1161  *
  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.
  1166  *
  1167  * Results:
  1168  *      Standard Tcl result.
  1169  *
  1170  * Side effects:
  1171  *      Files or directory specified by src will be deleted.
  1172  *
  1173  *----------------------------------------------------------------------
  1174  */
  1175 
  1176 static int
  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
  1185 				 * causing error. */
  1186 {
  1187     switch (type) {
  1188         case DOTREE_F: {
  1189 	    if (TclpDeleteFile(Tcl_DStringValue(srcPtr)) == 0) {
  1190 		return TCL_OK;
  1191 	    }
  1192 	    break;
  1193 	}
  1194         case DOTREE_PRED: {
  1195 	    return TCL_OK;
  1196 	}
  1197         case DOTREE_POSTD: {
  1198 	    if (DoRemoveDirectory(srcPtr, 0, NULL) == 0) {
  1199 		return TCL_OK;
  1200 	    }
  1201 	    break;
  1202 	}	    
  1203     }
  1204     if (errorPtr != NULL) {
  1205 	Tcl_ExternalToUtfDString(NULL, Tcl_DStringValue(srcPtr),
  1206 		Tcl_DStringLength(srcPtr), errorPtr);
  1207     }
  1208     return TCL_ERROR;
  1209 }
  1210 
  1211 /*
  1212  *---------------------------------------------------------------------------
  1213  *
  1214  * CopyFileAtts --
  1215  *
  1216  *	Copy the file attributes such as owner, group, permissions,
  1217  *	and modification date from one file to another.
  1218  *
  1219  * Results:
  1220  *	Standard Tcl result.
  1221  *
  1222  * Side effects:
  1223  *	user id, group id, permission bits, last modification time, and
  1224  *	last access time are updated in the new file to reflect the
  1225  *	old file.
  1226  *
  1227  *---------------------------------------------------------------------------
  1228  */
  1229 
  1230 static int
  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 */
  1236 {
  1237     struct utimbuf tval;
  1238     mode_t newMode;
  1239     
  1240     newMode = statBufPtr->st_mode
  1241 	    & (S_ISUID | S_ISGID | S_IRWXU | S_IRWXG | S_IRWXO);
  1242 	
  1243     /* 
  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().
  1250      */
  1251     
  1252     if (chmod(dst, newMode)) {				/* INTL: Native. */
  1253 	newMode &= ~(S_ISUID | S_ISGID);
  1254 	if (chmod(dst, newMode)) {			/* INTL: Native. */
  1255 	    return TCL_ERROR;
  1256 	}
  1257     }
  1258 
  1259     tval.actime = statBufPtr->st_atime; 
  1260     tval.modtime = statBufPtr->st_mtime; 
  1261 
  1262     if (utime(dst, &tval)) {				/* INTL: Native. */
  1263 	return TCL_ERROR;
  1264     }
  1265 #ifdef HAVE_COPYFILE
  1266 #ifdef WEAK_IMPORT_COPYFILE
  1267     if (copyfile != NULL)
  1268 #endif
  1269     copyfile(src, dst, NULL, COPYFILE_XATTR|COPYFILE_ACL);
  1270 #endif
  1271     return TCL_OK;
  1272 }
  1273 
  1274 
  1275 /*
  1276  *----------------------------------------------------------------------
  1277  *
  1278  * GetGroupAttribute
  1279  *
  1280  *      Gets the group attribute of a file.
  1281  *
  1282  * Results:
  1283  *      Standard TCL result. Returns a new Tcl_Obj in attributePtrPtr
  1284  *	if there is no error.
  1285  *
  1286  * Side effects:
  1287  *      A new object is allocated.
  1288  *      
  1289  *----------------------------------------------------------------------
  1290  */
  1291 
  1292 static int
  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. */
  1298 {
  1299     Tcl_StatBuf statBuf;
  1300     struct group *groupPtr;
  1301     int result;
  1302 
  1303     result = TclpObjStat(fileName, &statBuf);
  1304     
  1305     if (result != 0) {
  1306 	Tcl_AppendResult(interp, "could not read \"", 
  1307 		Tcl_GetString(fileName), "\": ",
  1308 		Tcl_PosixError(interp), (char *) NULL);
  1309 	return TCL_ERROR;
  1310     }
  1311 
  1312     groupPtr = TclpGetGrGid(statBuf.st_gid);
  1313 
  1314     if (result == -1 || groupPtr == NULL) {
  1315 	*attributePtrPtr = Tcl_NewIntObj((int) statBuf.st_gid);
  1316     } else {
  1317 	Tcl_DString ds;
  1318 	CONST char *utf;
  1319 
  1320 	utf = Tcl_ExternalToUtfDString(NULL, groupPtr->gr_name, -1, &ds); 
  1321 	*attributePtrPtr = Tcl_NewStringObj(utf, -1);
  1322 	Tcl_DStringFree(&ds);
  1323     }
  1324     endgrent();
  1325     return TCL_OK;
  1326 }
  1327 
  1328 /*
  1329  *----------------------------------------------------------------------
  1330  *
  1331  * GetOwnerAttribute
  1332  *
  1333  *      Gets the owner attribute of a file.
  1334  *
  1335  * Results:
  1336  *      Standard TCL result. Returns a new Tcl_Obj in attributePtrPtr
  1337  *	if there is no error.
  1338  *
  1339  * Side effects:
  1340  *      A new object is allocated.
  1341  *      
  1342  *----------------------------------------------------------------------
  1343  */
  1344 
  1345 static int
  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. */
  1351 {
  1352     Tcl_StatBuf statBuf;
  1353     struct passwd *pwPtr;
  1354     int result;
  1355 
  1356     result = TclpObjStat(fileName, &statBuf);
  1357     
  1358     if (result != 0) {
  1359 	Tcl_AppendResult(interp, "could not read \"", 
  1360 		Tcl_GetString(fileName), "\": ",
  1361 		Tcl_PosixError(interp), (char *) NULL);
  1362 	return TCL_ERROR;
  1363     }
  1364 
  1365     pwPtr = TclpGetPwUid(statBuf.st_uid);
  1366 
  1367     if (result == -1 || pwPtr == NULL) {
  1368 	*attributePtrPtr = Tcl_NewIntObj((int) statBuf.st_uid);
  1369     } else {
  1370 	Tcl_DString ds;
  1371 	CONST char *utf;
  1372 
  1373 	utf = Tcl_ExternalToUtfDString(NULL, pwPtr->pw_name, -1, &ds); 
  1374 	*attributePtrPtr = Tcl_NewStringObj(utf, Tcl_DStringLength(&ds));
  1375 	Tcl_DStringFree(&ds);
  1376     }
  1377     endpwent();
  1378     return TCL_OK;
  1379 }
  1380 
  1381 /*
  1382  *----------------------------------------------------------------------
  1383  *
  1384  * GetPermissionsAttribute
  1385  *
  1386  *      Gets the group attribute of a file.
  1387  *
  1388  * Results:
  1389  *      Standard TCL result. Returns a new Tcl_Obj in attributePtrPtr
  1390  *	if there is no error. The object will have ref count 0.
  1391  *
  1392  * Side effects:
  1393  *      A new object is allocated.
  1394  *      
  1395  *----------------------------------------------------------------------
  1396  */
  1397 
  1398 static int
  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. */
  1404 {
  1405     Tcl_StatBuf statBuf;
  1406     char returnString[7];
  1407     int result;
  1408 
  1409     result = TclpObjStat(fileName, &statBuf);
  1410     
  1411     if (result != 0) {
  1412 	Tcl_AppendResult(interp, "could not read \"", 
  1413 		Tcl_GetString(fileName), "\": ",
  1414 		Tcl_PosixError(interp), (char *) NULL);
  1415 	return TCL_ERROR;
  1416     }
  1417 
  1418     sprintf(returnString, "%0#5lo", (long) (statBuf.st_mode & 0x00007FFF));
  1419 
  1420     *attributePtrPtr = Tcl_NewStringObj(returnString, -1);
  1421     
  1422     return TCL_OK;
  1423 }
  1424 
  1425 /*
  1426  *---------------------------------------------------------------------------
  1427  *
  1428  * SetGroupAttribute --
  1429  *
  1430  *      Sets the group of the file to the specified group.
  1431  *
  1432  * Results:
  1433  *      Standard TCL result.
  1434  *
  1435  * Side effects:
  1436  *      As above.
  1437  *      
  1438  *---------------------------------------------------------------------------
  1439  */
  1440 
  1441 static int
  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. */
  1447 {
  1448     long gid;
  1449     int result;
  1450     CONST char *native;
  1451 
  1452     if (Tcl_GetLongFromObj(NULL, attributePtr, &gid) != TCL_OK) {
  1453 	Tcl_DString ds;
  1454 	struct group *groupPtr;
  1455 	CONST char *string;
  1456 	int length;
  1457 
  1458 	string = Tcl_GetStringFromObj(attributePtr, &length);
  1459 	native = Tcl_UtfToExternalDString(NULL, string, length, &ds);
  1460 	groupPtr = TclpGetGrNam(native); /* INTL: Native. */
  1461 	Tcl_DStringFree(&ds);
  1462 
  1463 	if (groupPtr == NULL) {
  1464 	    endgrent();
  1465 	    Tcl_AppendResult(interp, "could not set group for file \"",
  1466 		    Tcl_GetString(fileName), "\": group \"", 
  1467 		    string, "\" does not exist",
  1468 		    (char *) NULL);
  1469 	    return TCL_ERROR;
  1470 	}
  1471 	gid = groupPtr->gr_gid;
  1472     }
  1473 
  1474     native = Tcl_FSGetNativePath(fileName);
  1475     result = chown(native, (uid_t) -1, (gid_t) gid);	/* INTL: Native. */
  1476 
  1477     endgrent();
  1478     if (result != 0) {
  1479 	Tcl_AppendResult(interp, "could not set group for file \"",
  1480 	    Tcl_GetString(fileName), "\": ", Tcl_PosixError(interp), 
  1481 	    (char *) NULL);
  1482 	return TCL_ERROR;
  1483     }    
  1484     return TCL_OK;
  1485 }
  1486 
  1487 /*
  1488  *---------------------------------------------------------------------------
  1489  *
  1490  * SetOwnerAttribute --
  1491  *
  1492  *      Sets the owner of the file to the specified owner.
  1493  *
  1494  * Results:
  1495  *      Standard TCL result.
  1496  *
  1497  * Side effects:
  1498  *      As above.
  1499  *      
  1500  *---------------------------------------------------------------------------
  1501  */
  1502 
  1503 static int
  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. */
  1509 {
  1510     long uid;
  1511     int result;
  1512     CONST char *native;
  1513 
  1514     if (Tcl_GetLongFromObj(NULL, attributePtr, &uid) != TCL_OK) {
  1515 	Tcl_DString ds;
  1516 	struct passwd *pwPtr;
  1517 	CONST char *string;
  1518 	int length;
  1519 
  1520 	string = Tcl_GetStringFromObj(attributePtr, &length);
  1521 	native = Tcl_UtfToExternalDString(NULL, string, length, &ds);
  1522 	pwPtr = TclpGetPwNam(native); /* INTL: Native. */
  1523 	Tcl_DStringFree(&ds);
  1524 
  1525 	if (pwPtr == NULL) {
  1526 	    endpwent();
  1527 	    Tcl_AppendResult(interp, "could not set owner for file \"",
  1528 			     Tcl_GetString(fileName), "\": user \"", 
  1529 			     string, "\" does not exist",
  1530 		    (char *) NULL);
  1531 	    return TCL_ERROR;
  1532 	}
  1533 	uid = pwPtr->pw_uid;
  1534     }
  1535 
  1536     native = Tcl_FSGetNativePath(fileName);
  1537     result = chown(native, (uid_t) uid, (gid_t) -1);   /* INTL: Native. */
  1538     
  1539     endpwent();
  1540     if (result != 0) {
  1541 	Tcl_AppendResult(interp, "could not set owner for file \"", 
  1542 			 Tcl_GetString(fileName), "\": ", 
  1543 			 Tcl_PosixError(interp), (char *) NULL);
  1544 	return TCL_ERROR;
  1545     }
  1546     return TCL_OK;
  1547 }
  1548 
  1549 /*
  1550  *---------------------------------------------------------------------------
  1551  *
  1552  * SetPermissionsAttribute
  1553  *
  1554  *      Sets the file to the given permission.
  1555  *
  1556  * Results:
  1557  *      Standard TCL result.
  1558  *
  1559  * Side effects:
  1560  *      The permission of the file is changed.
  1561  *      
  1562  *---------------------------------------------------------------------------
  1563  */
  1564 
  1565 static int
  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. */
  1571 {
  1572     long mode;
  1573     mode_t newMode;
  1574     int result;
  1575     CONST char *native;
  1576 
  1577     /*
  1578      * First try if the string is a number
  1579      */
  1580     if (Tcl_GetLongFromObj(NULL, attributePtr, &mode) == TCL_OK) {
  1581         newMode = (mode_t) (mode & 0x00007FFF);
  1582     } else {
  1583 	Tcl_StatBuf buf;
  1584 	char *modeStringPtr = Tcl_GetString(attributePtr);
  1585 
  1586 	/*
  1587 	 * Try the forms "rwxrwxrwx" and "ugo=rwx"
  1588 	 *
  1589 	 * We get the current mode of the file, in order to allow for
  1590 	 * ug+-=rwx style chmod strings.
  1591 	 */
  1592 	result = TclpObjStat(fileName, &buf);
  1593 	if (result != 0) {
  1594 	    Tcl_AppendResult(interp, "could not read \"", 
  1595 		    Tcl_GetString(fileName), "\": ",
  1596 		    Tcl_PosixError(interp), (char *) NULL);
  1597 	    return TCL_ERROR;
  1598 	}
  1599 	newMode = (mode_t) (buf.st_mode & 0x00007FFF);
  1600 
  1601 	if (GetModeFromPermString(NULL, modeStringPtr, &newMode) != TCL_OK) {
  1602 	    Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
  1603 		    "unknown permission string format \"",
  1604 		    modeStringPtr, "\"", (char *) NULL);
  1605 	    return TCL_ERROR;
  1606 	}
  1607     }
  1608 
  1609     native = Tcl_FSGetNativePath(fileName);
  1610     result = chmod(native, newMode);		/* INTL: Native. */
  1611     if (result != 0) {
  1612 	Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
  1613 		"could not set permissions for file \"", 
  1614 		Tcl_GetString(fileName), "\": ",
  1615 		Tcl_PosixError(interp), (char *) NULL);
  1616 	return TCL_ERROR;
  1617     }
  1618     return TCL_OK;
  1619 }
  1620 
  1621 /*
  1622  *---------------------------------------------------------------------------
  1623  *
  1624  * TclpObjListVolumes --
  1625  *
  1626  *	Lists the currently mounted volumes, which on UNIX is just /.
  1627  *
  1628  * Results:
  1629  *	The list of volumes.
  1630  *
  1631  * Side effects:
  1632  *	None.
  1633  *
  1634  *---------------------------------------------------------------------------
  1635  */
  1636 
  1637 Tcl_Obj*
  1638 TclpObjListVolumes(void)
  1639 {
  1640     Tcl_Obj *resultPtr = Tcl_NewStringObj("/",1);
  1641 
  1642     Tcl_IncrRefCount(resultPtr);
  1643     return resultPtr;
  1644 }
  1645 
  1646 /*
  1647  *----------------------------------------------------------------------
  1648  *
  1649  * GetModeFromPermString --
  1650  *
  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.
  1654  *
  1655  * Results:
  1656  *	A standard Tcl result.
  1657  *
  1658  * Side effects:
  1659  *	See the user documentation.
  1660  *
  1661  *----------------------------------------------------------------------
  1662  */
  1663 
  1664 static int
  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 */
  1669 {
  1670     mode_t newMode;
  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;
  1675 
  1676     /*
  1677      * We start off checking for an "rwxrwxrwx" style permissions string
  1678      */
  1679     if (strlen(modeStringPtr) != 9) {
  1680         goto chmodStyleCheck;
  1681     }
  1682 
  1683     newMode = 0;
  1684     for (i = 0; i < 9; i++) {
  1685 	switch (*(modeStringPtr+i)) {
  1686 	    case 'r':
  1687 		if ((i%3) != 0) {
  1688 		    goto chmodStyleCheck;
  1689 		}
  1690 		newMode |= (1<<(8-i));
  1691 		break;
  1692 	    case 'w':
  1693 		if ((i%3) != 1) {
  1694 		    goto chmodStyleCheck;
  1695 		}
  1696 		newMode |= (1<<(8-i));
  1697 		break;
  1698 	    case 'x':
  1699 		if ((i%3) != 2) {
  1700 		    goto chmodStyleCheck;
  1701 		}
  1702 		newMode |= (1<<(8-i));
  1703 		break;
  1704 	    case 's':
  1705 		if (((i%3) != 2) || (i > 5)) {
  1706 		    goto chmodStyleCheck;
  1707 		}
  1708 		newMode |= (1<<(8-i));
  1709 		newMode |= (1<<(11-(i/3)));
  1710 		break;
  1711 	    case 'S':
  1712 		if (((i%3) != 2) || (i > 5)) {
  1713 		    goto chmodStyleCheck;
  1714 		}
  1715 		newMode |= (1<<(11-(i/3)));
  1716 		break;
  1717 	    case 't':
  1718 		if (i != 8) {
  1719 		    goto chmodStyleCheck;
  1720 		}
  1721 		newMode |= (1<<(8-i));
  1722 		newMode |= (1<<9);
  1723 		break;
  1724 	    case 'T':
  1725 		if (i != 8) {
  1726 		    goto chmodStyleCheck;
  1727 		}
  1728 		newMode |= (1<<9);
  1729 		break;
  1730 	    case '-':
  1731 		break;
  1732 	    default:
  1733 		/*
  1734 		 * Oops, not what we thought it was, so go on
  1735 		 */
  1736 		goto chmodStyleCheck;
  1737 	}
  1738     }
  1739     *modePtr = newMode;
  1740     return TCL_OK;
  1741 
  1742     chmodStyleCheck:
  1743     /*
  1744      * We now check for an "ugoa+-=rwxst" style permissions string
  1745      */
  1746 
  1747     for (n = 0 ; *(modeStringPtr+n) != '\0' ; n = n + i) {
  1748 	oldMode = *modePtr;
  1749 	who = op = what = op_found = who_found = 0;
  1750 	for (i = 0 ; *(modeStringPtr+n+i) != '\0' ; i++ ) {
  1751 	    if (!who_found) {
  1752 		/* who */
  1753 		switch (*(modeStringPtr+n+i)) {
  1754 		    case 'u' :
  1755 			who |= 0x9c0;
  1756 			continue;
  1757 		    case 'g' :
  1758 			who |= 0x438;
  1759 			continue;
  1760 		    case 'o' :
  1761 			who |= 0x207;
  1762 			continue;
  1763 		    case 'a' :
  1764 			who |= 0xfff;
  1765 			continue;
  1766 		}
  1767 	    }
  1768 	    who_found = 1;
  1769 	    if (who == 0) {
  1770 		who = 0xfff;
  1771 	    }
  1772 	    if (!op_found) {
  1773 		/* op */
  1774 		switch (*(modeStringPtr+n+i)) {
  1775 		    case '+' :
  1776 			op = 1;
  1777 			op_found = 1;
  1778 			continue;
  1779 		    case '-' :
  1780 			op = 2;
  1781 			op_found = 1;
  1782 			continue;
  1783 		    case '=' :
  1784 			op = 3;
  1785 			op_found = 1;
  1786 			continue;
  1787 		    default  :
  1788 			return TCL_ERROR;
  1789 		}
  1790 	    }
  1791 	    /* what */
  1792 	    switch (*(modeStringPtr+n+i)) {
  1793 		case 'r' :
  1794 		    what |= 0x124;
  1795 		    continue;
  1796 		case 'w' :
  1797 		    what |= 0x92;
  1798 		    continue;
  1799 		case 'x' :
  1800 		    what |= 0x49;
  1801 		    continue;
  1802 		case 's' :
  1803 		    what |= 0xc00;
  1804 		    continue;
  1805 		case 't' :
  1806 		    what |= 0x200;
  1807 		    continue;
  1808 		case ',' :
  1809 		    break;
  1810 		default  :
  1811 		    return TCL_ERROR;
  1812 	    }
  1813 	    if (*(modeStringPtr+n+i) == ',') {
  1814 		i++;
  1815 		break;
  1816 	    }
  1817 	}
  1818 	switch (op) {
  1819 	    case 1 :
  1820 		*modePtr = oldMode | (who & what);
  1821 		continue;
  1822 	    case 2 :
  1823 		*modePtr = oldMode & ~(who & what);
  1824 		continue;
  1825 	    case 3 :
  1826 		*modePtr = (oldMode & ~who) | (who & what);
  1827 		continue;
  1828 	}
  1829     }
  1830     return TCL_OK;
  1831 }
  1832 
  1833 /*
  1834  *---------------------------------------------------------------------------
  1835  *
  1836  * TclpObjNormalizePath --
  1837  *
  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
  1842  *	path).
  1843  *
  1844  * Results:
  1845  *	The new 'nextCheckpoint' value, giving as far as we could
  1846  *	understand in the path.
  1847  *
  1848  * Side effects:
  1849  *	The pathPtr string, is modified.
  1850  *
  1851  *---------------------------------------------------------------------------
  1852  */
  1853 int
  1854 TclpObjNormalizePath(interp, pathPtr, nextCheckpoint)
  1855     Tcl_Interp *interp;
  1856     Tcl_Obj *pathPtr;
  1857     int nextCheckpoint;
  1858 {
  1859     char *currentPathEndPosition;
  1860     int pathLen;
  1861     char cur;
  1862     char *path = Tcl_GetStringFromObj(pathPtr, &pathLen);
  1863 #ifndef NO_REALPATH
  1864     char normPath[MAXPATHLEN];
  1865     Tcl_DString ds;
  1866     CONST char *nativePath; 
  1867 #endif
  1868     /* 
  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.
  1872      */
  1873     currentPathEndPosition = path + nextCheckpoint;
  1874     if (*currentPathEndPosition == '/') {
  1875 	currentPathEndPosition++;
  1876     }
  1877 
  1878 #ifndef NO_REALPATH
  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);
  1885 
  1886 		if (Realpath(nativePath, normPath) != NULL) { 
  1887 
  1888 		if (*nativePath != '/' && *normPath == '/') {
  1889 		    /*
  1890 		     * realpath has transformed a relative path into an
  1891 		     * absolute path, we do not know how to handle this.
  1892 		     */
  1893 		} else {
  1894 		    nextCheckpoint = lastDir - path;
  1895 		    goto wholeStringOk;
  1896 		}
  1897 	    }
  1898 	    Tcl_DStringFree(&ds);
  1899 	}
  1900     }
  1901     /* Else do it the slow way */
  1902 #endif
  1903     
  1904     while (1) {
  1905 	cur = *currentPathEndPosition;
  1906 	if ((cur == '/') && (path != currentPathEndPosition)) {
  1907 	    /* Reached directory separator */
  1908 	    Tcl_DString ds;
  1909 	    CONST char *nativePath;
  1910 	    int accessOk;
  1911 
  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 */
  1918 		break;
  1919 	    }
  1920 	    /* Update the acceptable point */
  1921 	    nextCheckpoint = currentPathEndPosition - path;
  1922 	} else if (cur == 0) {
  1923 	    /* Reached end of string */
  1924 	    break;
  1925 	}
  1926 	currentPathEndPosition++;
  1927     }
  1928     /* 
  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 
  1933      * have 'realpath'.
  1934      */
  1935 #ifndef NO_REALPATH
  1936     if (haveRealpath) {
  1937 	/* 
  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!
  1942 	 */
  1943 	if (nextCheckpoint == 0) return 0;
  1944 	
  1945 	nativePath = Tcl_UtfToExternalDString(NULL, path, nextCheckpoint, &ds);
  1946 
  1947 	if (Realpath(nativePath, normPath) != NULL) { 
  1948 	    int newNormLen;
  1949 	    wholeStringOk:
  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') {
  1956 		    nextCheckpoint++;
  1957 		}
  1958 		return nextCheckpoint;
  1959 	    }
  1960 	    
  1961 	    /* 
  1962 	     * Free up the native path and put in its place the
  1963 	     * converted, normalized path.
  1964 	     */
  1965 	    Tcl_DStringFree(&ds);
  1966 	    Tcl_ExternalToUtfDString(NULL, normPath, (int) newNormLen, &ds);
  1967     
  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);
  1973 		/* 
  1974 		 * We recognise up to and including the directory
  1975 		 * separator.
  1976 		 */	
  1977 		nextCheckpoint = normLen + 1;
  1978 	    } else {
  1979 		/* We recognise the whole string */ 
  1980 		nextCheckpoint = Tcl_DStringLength(&ds);
  1981 	    }
  1982 	    /* 
  1983 	     * Overwrite with the normalized path.
  1984 	     */
  1985 	    Tcl_SetStringObj(pathPtr, Tcl_DStringValue(&ds),
  1986 		    Tcl_DStringLength(&ds));
  1987 	}
  1988 	Tcl_DStringFree(&ds);
  1989     }
  1990 #endif	/* !NO_REALPATH */
  1991 
  1992     return nextCheckpoint;
  1993 }