tclUnixFCmd.c
上传用户:rrhhcc
上传日期:2015-12-11
资源大小:54129k
文件大小:54k
源码类别:

通讯编程

开发平台:

Visual C++

  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.  *
  10.  * See the file "license.terms" for information on usage and redistribution
  11.  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  12.  *
  13.  * RCS: @(#) $Id: tclUnixFCmd.c,v 1.28.2.15 2007/04/29 02:19:51 das Exp $
  14.  *
  15.  * Portions of this code were derived from NetBSD source code which has
  16.  * the following copyright notice:
  17.  *
  18.  * Copyright (c) 1988, 1993, 1994
  19.  *      The Regents of the University of California.  All rights reserved.
  20.  *
  21.  * Redistribution and use in source and binary forms, with or without
  22.  * modification, are permitted provided that the following conditions
  23.  * are met:
  24.  * 1. Redistributions of source code must retain the above copyright
  25.  *    notice, this list of conditions and the following disclaimer.
  26.  * 2. Redistributions in binary form must reproduce the above copyright
  27.  *    notice, this list of conditions and the following disclaimer in the
  28.  *    documentation and/or other materials provided with the distribution.
  29.  * 3. All advertising materials mentioning features or use of this software
  30.  *    must display the following acknowledgement:
  31.  *      This product includes software developed by the University of
  32.  *      California, Berkeley and its contributors.
  33.  * 4. Neither the name of the University nor the names of its contributors
  34.  *    may be used to endorse or promote products derived from this software
  35.  *    without specific prior written permission.
  36.  *
  37.  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
  38.  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  39.  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  40.  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
  41.  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  42.  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  43.  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  44.  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  45.  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  46.  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  47.  * SUCH DAMAGE.
  48.  */
  49. #include "tclInt.h"
  50. #include "tclPort.h"
  51. #include <utime.h>
  52. #include <grp.h>
  53. #ifndef HAVE_ST_BLKSIZE
  54. #ifndef NO_FSTATFS
  55. #include <sys/statfs.h>
  56. #endif
  57. #endif
  58. #ifdef HAVE_FTS
  59. #include <fts.h>
  60. #endif
  61. /*
  62.  * The following constants specify the type of callback when
  63.  * TraverseUnixTree() calls the traverseProc()
  64.  */
  65. #define DOTREE_PRED   1     /* pre-order directory  */
  66. #define DOTREE_POSTD  2     /* post-order directory */
  67. #define DOTREE_F      3     /* regular file */
  68. /*
  69.  * Callbacks for file attributes code.
  70.  */
  71. static int GetGroupAttribute _ANSI_ARGS_((Tcl_Interp *interp,
  72.     int objIndex, Tcl_Obj *fileName,
  73.     Tcl_Obj **attributePtrPtr));
  74. static int GetOwnerAttribute _ANSI_ARGS_((Tcl_Interp *interp,
  75.     int objIndex, Tcl_Obj *fileName,
  76.     Tcl_Obj **attributePtrPtr));
  77. static int GetPermissionsAttribute _ANSI_ARGS_((
  78.     Tcl_Interp *interp, int objIndex,
  79.     Tcl_Obj *fileName, Tcl_Obj **attributePtrPtr));
  80. static int SetGroupAttribute _ANSI_ARGS_((Tcl_Interp *interp,
  81.     int objIndex, Tcl_Obj *fileName,
  82.     Tcl_Obj *attributePtr));
  83. static int SetOwnerAttribute _ANSI_ARGS_((Tcl_Interp *interp,
  84.     int objIndex, Tcl_Obj *fileName,
  85.     Tcl_Obj *attributePtr));
  86. static int SetPermissionsAttribute _ANSI_ARGS_((
  87.     Tcl_Interp *interp, int objIndex,
  88.     Tcl_Obj *fileName, Tcl_Obj *attributePtr));
  89. static int GetModeFromPermString _ANSI_ARGS_((
  90.     Tcl_Interp *interp, char *modeStringPtr,
  91.     mode_t *modePtr));
  92. /*
  93.  * Prototype for the TraverseUnixTree callback function.
  94.  */
  95. typedef int (TraversalProc) _ANSI_ARGS_((Tcl_DString *srcPtr,
  96. Tcl_DString *dstPtr, CONST Tcl_StatBuf *statBufPtr, int type,
  97. Tcl_DString *errorPtr));
  98. /*
  99.  * Constants and variables necessary for file attributes subcommand.
  100.  */
  101. enum {
  102.     UNIX_GROUP_ATTRIBUTE,
  103.     UNIX_OWNER_ATTRIBUTE,
  104.     UNIX_PERMISSIONS_ATTRIBUTE
  105. };
  106. CONST char *tclpFileAttrStrings[] = {
  107.     "-group",
  108.     "-owner",
  109.     "-permissions",
  110.     (char *) NULL
  111. };
  112. CONST TclFileAttrProcs tclpFileAttrProcs[] = {
  113.     {GetGroupAttribute, SetGroupAttribute},
  114.     {GetOwnerAttribute, SetOwnerAttribute},
  115.     {GetPermissionsAttribute, SetPermissionsAttribute}
  116. };
  117. /*
  118.  * This is the maximum number of consecutive readdir/unlink calls that can be
  119.  * made (with no intervening rewinddir or closedir/opendir) before triggering
  120.  * a bug that makes readdir return NULL even though some directory entries
  121.  * have not been processed.  The bug afflicts SunOS's readdir when applied to
  122.  * ufs file systems and Darwin 6.5's (and OSX v.10.3.8's) HFS+.  JH found the
  123.  * Darwin readdir to reset at 147, so 130 is chosen to be conservative.  We
  124.  * can't do a general rewind on failure as NFS can create special files that
  125.  * recreate themselves when you try and delete them.  8.4.8 added a solution
  126.  * that was affected by a single such NFS file, this solution should not be
  127.  * affected by less than THRESHOLD such files. [Bug 1034337]
  128.  */
  129. #define MAX_READDIR_UNLINK_THRESHOLD 130
  130. /*
  131.  * Declarations for local procedures defined in this file:
  132.  */
  133. static int CopyFile _ANSI_ARGS_((CONST char *src,
  134.     CONST char *dst, CONST Tcl_StatBuf *statBufPtr));
  135. static int CopyFileAtts _ANSI_ARGS_((CONST char *src,
  136.     CONST char *dst, CONST Tcl_StatBuf *statBufPtr));
  137. static int DoCopyFile _ANSI_ARGS_((CONST char *srcPtr,
  138.     CONST char *dstPtr, CONST Tcl_StatBuf *statBufPtr));
  139. static int DoCreateDirectory _ANSI_ARGS_((CONST char *pathPtr));
  140. static int DoRemoveDirectory _ANSI_ARGS_((Tcl_DString *pathPtr,
  141.     int recursive, Tcl_DString *errorPtr));
  142. static int DoRenameFile _ANSI_ARGS_((CONST char *src,
  143.     CONST char *dst));
  144. static int TraversalCopy _ANSI_ARGS_((Tcl_DString *srcPtr,
  145.     Tcl_DString *dstPtr, CONST Tcl_StatBuf *statBufPtr,
  146.     int type, Tcl_DString *errorPtr));
  147. static int TraversalDelete _ANSI_ARGS_((Tcl_DString *srcPtr,
  148.     Tcl_DString *dstPtr, CONST Tcl_StatBuf *statBufPtr,
  149.     int type, Tcl_DString *errorPtr));
  150. static int TraverseUnixTree _ANSI_ARGS_((
  151.     TraversalProc *traversalProc,
  152.     Tcl_DString *sourcePtr, Tcl_DString *destPtr,
  153.     Tcl_DString *errorPtr, int doRewind));
  154. #ifdef PURIFY
  155. /*
  156.  * realpath and purify don't mix happily.  It has been noted that realpath
  157.  * should not be used with purify because of bogus warnings, but just
  158.  * memset'ing the resolved path will squelch those.  This assumes we are
  159.  * passing the standard MAXPATHLEN size resolved arg.
  160.  */
  161. static char * Realpath _ANSI_ARGS_((CONST char *path,
  162.     char *resolved));
  163. char *
  164. Realpath(path, resolved)
  165.     CONST char *path;
  166.     char *resolved;
  167. {
  168.     memset(resolved, 0, MAXPATHLEN);
  169.     return realpath(path, resolved);
  170. }
  171. #else
  172. #define Realpath realpath
  173. #endif
  174. #ifndef NO_REALPATH
  175. #if defined(__APPLE__) && defined(TCL_THREADS) && 
  176. defined(MAC_OS_X_VERSION_MIN_REQUIRED) && 
  177. MAC_OS_X_VERSION_MIN_REQUIRED < 1030
  178. /*
  179.  * prior to Darwin 7, realpath is not threadsafe, c.f. bug 711232;
  180.  * if we might potentially be running on pre-10.3 OSX,
  181.  * check Darwin release at runtime before using realpath.
  182.  */
  183. extern long tclMacOSXDarwinRelease;
  184. #define haveRealpath (tclMacOSXDarwinRelease >= 7)
  185. #else
  186. #define haveRealpath 1
  187. #endif
  188. #endif /* NO_REALPATH */
  189. #ifdef HAVE_FTS
  190. #ifdef HAVE_STRUCT_STAT64
  191. /* fts doesn't do stat64 */
  192. #define noFtsStat 1
  193. #elif defined(__APPLE__) && defined(__LP64__) && 
  194. defined(MAC_OS_X_VERSION_MIN_REQUIRED) && 
  195. MAC_OS_X_VERSION_MIN_REQUIRED < 1050
  196. /*
  197.  * prior to Darwin 9, 64bit fts_open() without FTS_NOSTAT may crash (due to a
  198.  * 64bit-unsafe ALIGN macro); if we could be running on pre-10.5 OSX, check
  199.  * Darwin release at runtime and do a separate stat() if necessary.
  200.  */
  201. extern long tclMacOSXDarwinRelease;
  202. #define noFtsStat (tclMacOSXDarwinRelease < 9)
  203. #else
  204. #define noFtsStat 0
  205. #endif
  206. #endif /* HAVE_FTS */
  207. /*
  208.  *---------------------------------------------------------------------------
  209.  *
  210.  * TclpObjRenameFile, DoRenameFile --
  211.  *
  212.  *      Changes the name of an existing file or directory, from src to dst.
  213.  * If src and dst refer to the same file or directory, does nothing
  214.  * and returns success.  Otherwise if dst already exists, it will be
  215.  * deleted and replaced by src subject to the following conditions:
  216.  *     If src is a directory, dst may be an empty directory.
  217.  *     If src is a file, dst may be a file.
  218.  * In any other situation where dst already exists, the rename will
  219.  * fail.  
  220.  *
  221.  * Results:
  222.  * If the directory was successfully created, returns TCL_OK.
  223.  * Otherwise the return value is TCL_ERROR and errno is set to
  224.  * indicate the error.  Some possible values for errno are:
  225.  *
  226.  * EACCES:     src or dst parent directory can't be read and/or written.
  227.  * EEXIST:     dst is a non-empty directory.
  228.  * EINVAL:     src is a root directory or dst is a subdirectory of src.
  229.  * EISDIR:     dst is a directory, but src is not.
  230.  * ENOENT:     src doesn't exist, or src or dst is "".
  231.  * ENOTDIR:    src is a directory, but dst is not.  
  232.  * EXDEV:     src and dst are on different filesystems.
  233.  *
  234.  * Side effects:
  235.  * The implementation of rename may allow cross-filesystem renames,
  236.  * but the caller should be prepared to emulate it with copy and
  237.  * delete if errno is EXDEV.
  238.  *
  239.  *---------------------------------------------------------------------------
  240.  */
  241. int 
  242. TclpObjRenameFile(srcPathPtr, destPathPtr)
  243.     Tcl_Obj *srcPathPtr;
  244.     Tcl_Obj *destPathPtr;
  245. {
  246.     return DoRenameFile(Tcl_FSGetNativePath(srcPathPtr), 
  247. Tcl_FSGetNativePath(destPathPtr));
  248. }
  249. static int
  250. DoRenameFile(src, dst)
  251.     CONST char *src; /* Pathname of file or dir to be renamed
  252.  * (native). */
  253.     CONST char *dst; /* New pathname of file or directory
  254.  * (native). */
  255. {
  256.     if (rename(src, dst) == 0) { /* INTL: Native. */
  257. return TCL_OK;
  258.     }
  259.     if (errno == ENOTEMPTY) {
  260. errno = EEXIST;
  261.     }
  262.     /*
  263.      * IRIX returns EIO when you attept to move a directory into
  264.      * itself.  We just map EIO to EINVAL get the right message on SGI.
  265.      * Most platforms don't return EIO except in really strange cases.
  266.      */
  267.     
  268.     if (errno == EIO) {
  269. errno = EINVAL;
  270.     }
  271.     
  272. #ifndef NO_REALPATH
  273.     /*
  274.      * SunOS 4.1.4 reports overwriting a non-empty directory with a
  275.      * directory as EINVAL instead of EEXIST (first rule out the correct
  276.      * EINVAL result code for moving a directory into itself).  Must be
  277.      * conditionally compiled because realpath() not defined on all systems.
  278.      */
  279.     if (errno == EINVAL && haveRealpath) {
  280. char srcPath[MAXPATHLEN], dstPath[MAXPATHLEN];
  281. DIR *dirPtr;
  282. Tcl_DirEntry *dirEntPtr;
  283. if ((Realpath((char *) src, srcPath) != NULL) /* INTL: Native. */
  284. && (Realpath((char *) dst, dstPath) != NULL) /* INTL: Native. */
  285. && (strncmp(srcPath, dstPath, strlen(srcPath)) != 0)) {
  286.     dirPtr = opendir(dst); /* INTL: Native. */
  287.     if (dirPtr != NULL) {
  288. while (1) {
  289.     dirEntPtr = TclOSreaddir(dirPtr); /* INTL: Native. */
  290.     if (dirEntPtr == NULL) {
  291. break;
  292.     }
  293.     if ((strcmp(dirEntPtr->d_name, ".") != 0) &&
  294.     (strcmp(dirEntPtr->d_name, "..") != 0)) {
  295. errno = EEXIST;
  296. closedir(dirPtr);
  297. return TCL_ERROR;
  298.     }
  299. }
  300. closedir(dirPtr);
  301.     }
  302. }
  303. errno = EINVAL;
  304.     }
  305. #endif /* !NO_REALPATH */
  306.     if (strcmp(src, "/") == 0) {
  307. /*
  308.  * Alpha reports renaming / as EBUSY and Linux reports it as EACCES,
  309.  * instead of EINVAL.
  310.  */
  311.  
  312. errno = EINVAL;
  313.     }
  314.     /*
  315.      * DEC Alpha OSF1 V3.0 returns EACCES when attempting to move a
  316.      * file across filesystems and the parent directory of that file is
  317.      * not writable.  Most other systems return EXDEV.  Does nothing to
  318.      * correct this behavior.
  319.      */
  320.     return TCL_ERROR;
  321. }
  322. /*
  323.  *---------------------------------------------------------------------------
  324.  *
  325.  * TclpObjCopyFile, DoCopyFile --
  326.  *
  327.  *      Copy a single file (not a directory).  If dst already exists and
  328.  * is not a directory, it is removed.
  329.  *
  330.  * Results:
  331.  * If the file was successfully copied, returns TCL_OK.  Otherwise
  332.  * the return value is TCL_ERROR and errno is set to indicate the
  333.  * error.  Some possible values for errno are:
  334.  *
  335.  * EACCES:     src or dst parent directory can't be read and/or written.
  336.  * EISDIR:     src or dst is a directory.
  337.  * ENOENT:     src doesn't exist.  src or dst is "".
  338.  *
  339.  * Side effects:
  340.  *      This procedure will also copy symbolic links, block, and
  341.  *      character devices, and fifos.  For symbolic links, the links 
  342.  *      themselves will be copied and not what they point to.  For the
  343.  * other special file types, the directory entry will be copied and
  344.  * not the contents of the device that it refers to.
  345.  *
  346.  *---------------------------------------------------------------------------
  347.  */
  348. int 
  349. TclpObjCopyFile(srcPathPtr, destPathPtr)
  350.     Tcl_Obj *srcPathPtr;
  351.     Tcl_Obj *destPathPtr;
  352. {
  353.     CONST char *src = Tcl_FSGetNativePath(srcPathPtr);
  354.     Tcl_StatBuf srcStatBuf;
  355.     if (TclOSlstat(src, &srcStatBuf) != 0) { /* INTL: Native. */
  356. return TCL_ERROR;
  357.     }
  358.     return DoCopyFile(src, Tcl_FSGetNativePath(destPathPtr), &srcStatBuf);
  359. }
  360. static int
  361. DoCopyFile(src, dst, statBufPtr)
  362.     CONST char *src; /* Pathname of file to be copied (native). */
  363.     CONST char *dst; /* Pathname of file to copy to (native). */
  364.     CONST Tcl_StatBuf *statBufPtr;
  365. /* Used to determine filetype. */
  366. {
  367.     Tcl_StatBuf dstStatBuf;
  368.     if (S_ISDIR(statBufPtr->st_mode)) {
  369. errno = EISDIR;
  370. return TCL_ERROR;
  371.     }
  372.     /*
  373.      * symlink, and some of the other calls will fail if the target 
  374.      * exists, so we remove it first
  375.      */
  376.     
  377.     if (TclOSlstat(dst, &dstStatBuf) == 0) { /* INTL: Native. */
  378. if (S_ISDIR(dstStatBuf.st_mode)) {
  379.     errno = EISDIR;
  380.     return TCL_ERROR;
  381. }
  382.     }
  383.     if (unlink(dst) != 0) { /* INTL: Native. */
  384. if (errno != ENOENT) {
  385.     return TCL_ERROR;
  386.     }
  387.     switch ((int) (statBufPtr->st_mode & S_IFMT)) {
  388. #ifndef DJGPP
  389.         case S_IFLNK: {
  390.     char link[MAXPATHLEN];
  391.     int length;
  392.     length = readlink(src, link, sizeof(link)); /* INTL: Native. */
  393.     if (length == -1) {
  394. return TCL_ERROR;
  395.     }
  396.     link[length] = '';
  397.     if (symlink(link, dst) < 0) { /* INTL: Native. */
  398. return TCL_ERROR;
  399.     }
  400. #ifdef HAVE_COPYFILE
  401. #ifdef WEAK_IMPORT_COPYFILE
  402.     if (copyfile != NULL)
  403. #endif
  404.     copyfile(src, dst, NULL, COPYFILE_XATTR|COPYFILE_NOFOLLOW_SRC);
  405. #endif
  406.     break;
  407. }
  408. #endif
  409.         case S_IFBLK:
  410.         case S_IFCHR: {
  411.     if (mknod(dst, statBufPtr->st_mode, /* INTL: Native. */
  412.     statBufPtr->st_rdev) < 0) {
  413. return TCL_ERROR;
  414.     }
  415.     return CopyFileAtts(src, dst, statBufPtr);
  416. }
  417.         case S_IFIFO: {
  418.     if (mkfifo(dst, statBufPtr->st_mode) < 0) { /* INTL: Native. */
  419. return TCL_ERROR;
  420.     }
  421.     return CopyFileAtts(src, dst, statBufPtr);
  422. }
  423.         default: {
  424.     return CopyFile(src, dst, statBufPtr);
  425. }
  426.     }
  427.     return TCL_OK;
  428. }
  429. /*
  430.  *----------------------------------------------------------------------
  431.  *
  432.  * CopyFile - 
  433.  *
  434.  *      Helper function for TclpCopyFile.  Copies one regular file,
  435.  * using read() and write().
  436.  *
  437.  * Results:
  438.  * A standard Tcl result.
  439.  *
  440.  * Side effects:
  441.  *      A file is copied.  Dst will be overwritten if it exists.
  442.  *
  443.  *----------------------------------------------------------------------
  444.  */
  445. static int 
  446. CopyFile(src, dst, statBufPtr) 
  447.     CONST char *src; /* Pathname of file to copy (native). */
  448.     CONST char *dst; /* Pathname of file to create/overwrite
  449.  * (native). */
  450.     CONST Tcl_StatBuf *statBufPtr;
  451. /* Used to determine mode and blocksize. */
  452. {
  453.     int srcFd;
  454.     int dstFd;
  455.     unsigned blockSize; /* Optimal I/O blocksize for filesystem */
  456.     char *buffer; /* Data buffer for copy */
  457.     size_t nread;
  458.     if ((srcFd = TclOSopen(src, O_RDONLY, 0)) < 0) { /* INTL: Native. */
  459. return TCL_ERROR;
  460.     }
  461.     dstFd = TclOSopen(dst, O_CREAT|O_TRUNC|O_WRONLY, /* INTL: Native. */
  462.     statBufPtr->st_mode);
  463.     if (dstFd < 0) {
  464. close(srcFd); 
  465. return TCL_ERROR;
  466.     }
  467. #ifdef HAVE_ST_BLKSIZE
  468.     blockSize = statBufPtr->st_blksize;
  469. #else
  470. #ifndef NO_FSTATFS
  471.     {
  472. struct statfs fs;
  473. if (fstatfs(srcFd, &fs, sizeof(fs), 0) == 0) {
  474.     blockSize = fs.f_bsize;
  475. } else {
  476.     blockSize = 4096;
  477. }
  478.     }
  479. #else 
  480.     blockSize = 4096;
  481. #endif
  482. #endif
  483.     /* [SF Tcl Bug 1586470] Even if we HAVE_ST_BLKSIZE, there are
  484.      * filesystems which report a bogus value for the blocksize.  An
  485.      * example is the Andrew Filesystem (afs), reporting a blocksize
  486.      * of 0. When detecting such a situation we now simply fall back
  487.      * to a hardwired default size.
  488.      */
  489.     if (blockSize <= 0) {
  490.         blockSize = 4096;
  491.     }
  492.     buffer = ckalloc(blockSize);
  493.     while (1) {
  494. nread = read(srcFd, buffer, blockSize);
  495. if ((nread == -1) || (nread == 0)) {
  496.     break;
  497. }
  498. if (write(dstFd, buffer, nread) != nread) {
  499.     nread = (size_t) -1;
  500.     break;
  501. }
  502.     }
  503.     ckfree(buffer);
  504.     close(srcFd);
  505.     if ((close(dstFd) != 0) || (nread == -1)) {
  506. unlink(dst); /* INTL: Native. */
  507. return TCL_ERROR;
  508.     }
  509.     if (CopyFileAtts(src, dst, statBufPtr) == TCL_ERROR) {
  510. /*
  511.  * The copy succeeded, but setting the permissions failed, so be in
  512.  * a consistent state, we remove the file that was created by the
  513.  * copy.
  514.  */
  515. unlink(dst); /* INTL: Native. */
  516. return TCL_ERROR;
  517.     }
  518.     return TCL_OK;
  519. }
  520. /*
  521.  *---------------------------------------------------------------------------
  522.  *
  523.  * TclpObjDeleteFile, TclpDeleteFile --
  524.  *
  525.  *      Removes a single file (not a directory).
  526.  *
  527.  * Results:
  528.  * If the file was successfully deleted, returns TCL_OK.  Otherwise
  529.  * the return value is TCL_ERROR and errno is set to indicate the
  530.  * error.  Some possible values for errno are:
  531.  *
  532.  * EACCES:     a parent directory can't be read and/or written.
  533.  * EISDIR:     path is a directory.
  534.  * ENOENT:     path doesn't exist or is "".
  535.  *
  536.  * Side effects:
  537.  *      The file is deleted, even if it is read-only.
  538.  *
  539.  *---------------------------------------------------------------------------
  540.  */
  541. int 
  542. TclpObjDeleteFile(pathPtr)
  543.     Tcl_Obj *pathPtr;
  544. {
  545.     return TclpDeleteFile(Tcl_FSGetNativePath(pathPtr));
  546. }
  547. int
  548. TclpDeleteFile(path)
  549.     CONST char *path; /* Pathname of file to be removed (native). */
  550. {
  551.     if (unlink(path) != 0) { /* INTL: Native. */
  552. return TCL_ERROR;
  553.     }
  554.     return TCL_OK;
  555. }
  556. /*
  557.  *---------------------------------------------------------------------------
  558.  *
  559.  * TclpCreateDirectory, DoCreateDirectory --
  560.  *
  561.  *      Creates the specified directory.  All parent directories of the
  562.  * specified directory must already exist.  The directory is
  563.  * automatically created with permissions so that user can access
  564.  * the new directory and create new files or subdirectories in it.
  565.  *
  566.  * Results:
  567.  * If the directory was successfully created, returns TCL_OK.
  568.  * Otherwise the return value is TCL_ERROR and errno is set to
  569.  * indicate the error.  Some possible values for errno are:
  570.  *
  571.  * EACCES:     a parent directory can't be read and/or written.
  572.  * EEXIST:     path already exists.
  573.  * ENOENT:     a parent directory doesn't exist.
  574.  *
  575.  * Side effects:
  576.  *      A directory is created with the current umask, except that
  577.  * permission for u+rwx will always be added.
  578.  *
  579.  *---------------------------------------------------------------------------
  580.  */
  581. int 
  582. TclpObjCreateDirectory(pathPtr)
  583.     Tcl_Obj *pathPtr;
  584. {
  585.     return DoCreateDirectory(Tcl_FSGetNativePath(pathPtr));
  586. }
  587. static int
  588. DoCreateDirectory(path)
  589.     CONST char *path; /* Pathname of directory to create (native). */
  590. {
  591.     mode_t mode;
  592.     mode = umask(0);
  593.     umask(mode);
  594.     /*
  595.      * umask return value is actually the inverse of the permissions.
  596.      */
  597.     mode = (0777 & ~mode) | S_IRUSR | S_IWUSR | S_IXUSR;
  598.     if (mkdir(path, mode) != 0) { /* INTL: Native. */
  599. return TCL_ERROR;
  600.     }
  601.     return TCL_OK;
  602. }
  603. /*
  604.  *---------------------------------------------------------------------------
  605.  *
  606.  * TclpObjCopyDirectory --
  607.  *
  608.  *      Recursively copies a directory.  The target directory dst must
  609.  * not already exist.  Note that this function does not merge two
  610.  * directory hierarchies, even if the target directory is an an
  611.  * empty directory.
  612.  *
  613.  * Results:
  614.  * If the directory was successfully copied, returns TCL_OK.
  615.  * Otherwise the return value is TCL_ERROR, errno is set to indicate
  616.  * the error, and the pathname of the file that caused the error
  617.  * is stored in errorPtr.  See TclpObjCreateDirectory and 
  618.  * TclpObjCopyFile for a description of possible values for errno.
  619.  *
  620.  * Side effects:
  621.  *      An exact copy of the directory hierarchy src will be created
  622.  * with the name dst.  If an error occurs, the error will
  623.  *      be returned immediately, and remaining files will not be
  624.  * processed.
  625.  *
  626.  *---------------------------------------------------------------------------
  627.  */
  628. int 
  629. TclpObjCopyDirectory(srcPathPtr, destPathPtr, errorPtr)
  630.     Tcl_Obj *srcPathPtr;
  631.     Tcl_Obj *destPathPtr;
  632.     Tcl_Obj **errorPtr;
  633. {
  634.     Tcl_DString ds;
  635.     Tcl_DString srcString, dstString;
  636.     int ret;
  637.     Tcl_Obj *transPtr;
  638.     
  639.     transPtr = Tcl_FSGetTranslatedPath(NULL,srcPathPtr);
  640.     Tcl_UtfToExternalDString(NULL, 
  641.      (transPtr != NULL ? Tcl_GetString(transPtr) : NULL), 
  642.      -1, &srcString);
  643.     if (transPtr != NULL) {
  644. Tcl_DecrRefCount(transPtr);
  645.     }
  646.     transPtr = Tcl_FSGetTranslatedPath(NULL,destPathPtr);
  647.     Tcl_UtfToExternalDString(NULL, 
  648.      (transPtr != NULL ? Tcl_GetString(transPtr) : NULL), 
  649.      -1, &dstString);
  650.     if (transPtr != NULL) {
  651. Tcl_DecrRefCount(transPtr);
  652.     }
  653.     ret = TraverseUnixTree(TraversalCopy, &srcString, &dstString, &ds, 0);
  654.     Tcl_DStringFree(&srcString);
  655.     Tcl_DStringFree(&dstString);
  656.     if (ret != TCL_OK) {
  657. *errorPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), -1);
  658. Tcl_DStringFree(&ds);
  659. Tcl_IncrRefCount(*errorPtr);
  660.     }
  661.     return ret;
  662. }
  663. /*
  664.  *---------------------------------------------------------------------------
  665.  *
  666.  * TclpRemoveDirectory, DoRemoveDirectory --
  667.  *
  668.  * Removes directory (and its contents, if the recursive flag is set).
  669.  *
  670.  * Results:
  671.  * If the directory was successfully removed, returns TCL_OK.
  672.  * Otherwise the return value is TCL_ERROR, errno is set to indicate
  673.  * the error, and the pathname of the file that caused the error
  674.  * is stored in errorPtr.  Some possible values for errno are:
  675.  *
  676.  * EACCES:     path directory can't be read and/or written.
  677.  * EEXIST:     path is a non-empty directory.
  678.  * EINVAL:     path is a root directory.
  679.  * ENOENT:     path doesn't exist or is "".
  680.  *  ENOTDIR:    path is not a directory.
  681.  *
  682.  * Side effects:
  683.  * Directory removed.  If an error occurs, the error will be returned
  684.  * immediately, and remaining files will not be deleted.
  685.  *
  686.  *---------------------------------------------------------------------------
  687.  */
  688.  
  689. int 
  690. TclpObjRemoveDirectory(pathPtr, recursive, errorPtr)
  691.     Tcl_Obj *pathPtr;
  692.     int recursive;
  693.     Tcl_Obj **errorPtr;
  694. {
  695.     Tcl_DString ds;
  696.     Tcl_DString pathString;
  697.     int ret;
  698.     Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(NULL, pathPtr);
  699.     Tcl_UtfToExternalDString(NULL, 
  700.      (transPtr != NULL ? Tcl_GetString(transPtr) : NULL), 
  701.      -1, &pathString);
  702.     if (transPtr != NULL) {
  703. Tcl_DecrRefCount(transPtr);
  704.     }
  705.     ret = DoRemoveDirectory(&pathString, recursive, &ds);
  706.     Tcl_DStringFree(&pathString);
  707.     if (ret != TCL_OK) {
  708. *errorPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), -1);
  709. Tcl_DStringFree(&ds);
  710. Tcl_IncrRefCount(*errorPtr);
  711.     }
  712.     return ret;
  713. }
  714. static int
  715. DoRemoveDirectory(pathPtr, recursive, errorPtr)
  716.     Tcl_DString *pathPtr; /* Pathname of directory to be removed
  717.  * (native). */
  718.     int recursive; /* If non-zero, removes directories that
  719.  * are nonempty.  Otherwise, will only remove
  720.  * empty directories. */
  721.     Tcl_DString *errorPtr; /* If non-NULL, uninitialized or free
  722.  * DString filled with UTF-8 name of file
  723.  * causing error. */
  724. {
  725.     CONST char *path;
  726.     mode_t oldPerm = 0;
  727.     int result;
  728.     
  729.     path = Tcl_DStringValue(pathPtr);
  730.     
  731.     if (recursive != 0) {
  732. /* We should try to change permissions so this can be deleted */
  733. Tcl_StatBuf statBuf;
  734. int newPerm;
  735. if (TclOSstat(path, &statBuf) == 0) {
  736.     oldPerm = (mode_t) (statBuf.st_mode & 0x00007FFF);
  737. }
  738. newPerm = oldPerm | (64+128+256);
  739. chmod(path, (mode_t) newPerm);
  740.     }
  741.     
  742.     if (rmdir(path) == 0) { /* INTL: Native. */
  743. return TCL_OK;
  744.     }
  745.     if (errno == ENOTEMPTY) {
  746. errno = EEXIST;
  747.     }
  748.     result = TCL_OK;
  749.     if ((errno != EEXIST) || (recursive == 0)) {
  750. if (errorPtr != NULL) {
  751.     Tcl_ExternalToUtfDString(NULL, path, -1, errorPtr);
  752. }
  753. result = TCL_ERROR;
  754.     }
  755.     
  756.     /*
  757.      * The directory is nonempty, but the recursive flag has been
  758.      * specified, so we recursively remove all the files in the directory.
  759.      */
  760.     if (result == TCL_OK) {
  761. result = TraverseUnixTree(TraversalDelete, pathPtr, NULL, errorPtr, 1);
  762.     }
  763.     
  764.     if ((result != TCL_OK) && (recursive != 0)) {
  765.         /* Try to restore permissions */
  766.         chmod(path, oldPerm);
  767.     }
  768.     return result;
  769. }
  770. /*
  771.  *---------------------------------------------------------------------------
  772.  *
  773.  * TraverseUnixTree --
  774.  *
  775.  *      Traverse directory tree specified by sourcePtr, calling the function 
  776.  * traverseProc for each file and directory encountered.  If destPtr 
  777.  * is non-null, each of name in the sourcePtr directory is appended to 
  778.  * the directory specified by destPtr and passed as the second argument 
  779.  * to traverseProc() .
  780.  *
  781.  * Results:
  782.  *      Standard Tcl result.
  783.  *
  784.  * Side effects:
  785.  *      None caused by TraverseUnixTree, however the user specified 
  786.  * traverseProc() may change state.  If an error occurs, the error will
  787.  *      be returned immediately, and remaining files will not be processed.
  788.  *
  789.  *---------------------------------------------------------------------------
  790.  */
  791. static int 
  792. TraverseUnixTree(traverseProc, sourcePtr, targetPtr, errorPtr, doRewind)
  793.     TraversalProc *traverseProc;/* Function to call for every file and
  794.  * directory in source hierarchy. */
  795.     Tcl_DString *sourcePtr; /* Pathname of source directory to be
  796.  * traversed (native). */
  797.     Tcl_DString *targetPtr; /* Pathname of directory to traverse in
  798.  * parallel with source directory (native). */
  799.     Tcl_DString *errorPtr; /* If non-NULL, uninitialized or free
  800.  * DString filled with UTF-8 name of file
  801.  * causing error. */
  802.     int doRewind; /* Flag indicating that to ensure complete
  803.       * traversal of source hierarchy, the readdir
  804.       * loop should be rewound whenever
  805.       * traverseProc has returned TCL_OK; this is
  806.       * required when traverseProc modifies the
  807.       * source hierarchy, e.g. by deleting files. */
  808. {
  809.     Tcl_StatBuf statBuf;
  810.     CONST char *source, *errfile;
  811.     int result, sourceLen;
  812.     int targetLen;
  813. #ifndef HAVE_FTS
  814.     int numProcessed = 0;
  815.     Tcl_DirEntry *dirEntPtr;
  816.     DIR *dirPtr;
  817. #else
  818.     CONST char *paths[2] = {NULL, NULL};
  819.     FTS *fts = NULL;
  820.     FTSENT *ent;
  821. #endif
  822.     errfile = NULL;
  823.     result = TCL_OK;
  824.     targetLen = 0; /* lint. */
  825.     source = Tcl_DStringValue(sourcePtr);
  826.     if (TclOSlstat(source, &statBuf) != 0) { /* INTL: Native. */
  827. errfile = source;
  828. goto end;
  829.     }
  830.     if (!S_ISDIR(statBuf.st_mode)) {
  831. /*
  832.  * Process the regular file
  833.  */
  834. return (*traverseProc)(sourcePtr, targetPtr, &statBuf, DOTREE_F,
  835. errorPtr);
  836.     }
  837. #ifndef HAVE_FTS
  838.     dirPtr = opendir(source); /* INTL: Native. */
  839.     if (dirPtr == NULL) {
  840. /* 
  841.  * Can't read directory
  842.  */
  843. errfile = source;
  844. goto end;
  845.     }
  846.     result = (*traverseProc)(sourcePtr, targetPtr, &statBuf, DOTREE_PRED,
  847.     errorPtr);
  848.     if (result != TCL_OK) {
  849. closedir(dirPtr);
  850. return result;
  851.     }
  852.     Tcl_DStringAppend(sourcePtr, "/", 1);
  853.     sourceLen = Tcl_DStringLength(sourcePtr);
  854.     if (targetPtr != NULL) {
  855. Tcl_DStringAppend(targetPtr, "/", 1);
  856. targetLen = Tcl_DStringLength(targetPtr);
  857.     }
  858.     while ((dirEntPtr = TclOSreaddir(dirPtr)) != NULL) { /* INTL: Native. */
  859. if ((dirEntPtr->d_name[0] == '.')
  860. && ((dirEntPtr->d_name[1] == '')
  861. || (strcmp(dirEntPtr->d_name, "..") == 0))) {
  862.     continue;
  863. }
  864. /*
  865.  * Append name after slash, and recurse on the file.
  866.  */
  867. Tcl_DStringAppend(sourcePtr, dirEntPtr->d_name, -1);
  868. if (targetPtr != NULL) {
  869.     Tcl_DStringAppend(targetPtr, dirEntPtr->d_name, -1);
  870. }
  871. result = TraverseUnixTree(traverseProc, sourcePtr, targetPtr,
  872. errorPtr, doRewind);
  873. if (result != TCL_OK) {
  874.     break;
  875. } else {
  876.     numProcessed++;
  877. }
  878. /*
  879.  * Remove name after slash.
  880.  */
  881. Tcl_DStringSetLength(sourcePtr, sourceLen);
  882. if (targetPtr != NULL) {
  883.     Tcl_DStringSetLength(targetPtr, targetLen);
  884. }
  885. if (doRewind && (numProcessed > MAX_READDIR_UNLINK_THRESHOLD)) {
  886.     /*
  887.      * Call rewinddir if we've called unlink or rmdir so many times
  888.      * (since the opendir or the previous rewinddir), to avoid
  889.      * a NULL-return that may a symptom of a buggy readdir.
  890.      */
  891.     rewinddir(dirPtr);
  892.     numProcessed = 0;
  893. }
  894.     }
  895.     closedir(dirPtr);
  896.     /*
  897.      * Strip off the trailing slash we added
  898.      */
  899.     Tcl_DStringSetLength(sourcePtr, sourceLen - 1);
  900.     if (targetPtr != NULL) {
  901. Tcl_DStringSetLength(targetPtr, targetLen - 1);
  902.     }
  903.     if (result == TCL_OK) {
  904. /*
  905.  * Call traverseProc() on a directory after visiting all the
  906.  * files in that directory.
  907.  */
  908. result = (*traverseProc)(sourcePtr, targetPtr, &statBuf, DOTREE_POSTD,
  909. errorPtr);
  910.     }
  911. #else /* HAVE_FTS */
  912.     paths[0] = source;
  913.     fts = fts_open((char**)paths, FTS_PHYSICAL | FTS_NOCHDIR |
  914.     (noFtsStat || doRewind ? FTS_NOSTAT : 0),  NULL);
  915.     if (fts == NULL) {
  916. errfile = source;
  917. goto end;
  918.     }
  919.     sourceLen = Tcl_DStringLength(sourcePtr);
  920.     if (targetPtr != NULL) {
  921. targetLen = Tcl_DStringLength(targetPtr);
  922.     }
  923.     while ((ent = fts_read(fts)) != NULL) {
  924. unsigned short info = ent->fts_info;
  925. char * path = ent->fts_path + sourceLen;
  926. unsigned short pathlen = ent->fts_pathlen - sourceLen;
  927. int type;
  928. Tcl_StatBuf *statBufPtr = NULL;
  929. if (info == FTS_DNR || info == FTS_ERR || info == FTS_NS) {
  930.     errfile = ent->fts_path;
  931.     break;
  932. }
  933. Tcl_DStringAppend(sourcePtr, path, pathlen);
  934. if (targetPtr != NULL) {
  935.     Tcl_DStringAppend(targetPtr, path, pathlen);
  936. }
  937. switch (info) {
  938.     case FTS_D:
  939. type = DOTREE_PRED;
  940. break;
  941.     case FTS_DP:
  942. type = DOTREE_POSTD;
  943. break;
  944.     default:
  945. type = DOTREE_F;
  946. break;
  947. }
  948. if (!doRewind) { /* no need to stat for delete */
  949.     if (noFtsStat) {
  950. statBufPtr = &statBuf;
  951. if (TclOSlstat(ent->fts_path, statBufPtr) != 0) {
  952.     errfile = ent->fts_path;
  953.     break;
  954. }
  955.     } else {
  956. statBufPtr = ent->fts_statp;
  957.     }
  958. }
  959. result = (*traverseProc)(sourcePtr, targetPtr, statBufPtr, type,
  960. errorPtr);
  961. if (result != TCL_OK) {
  962.     break;
  963. }
  964. Tcl_DStringSetLength(sourcePtr, sourceLen);
  965. if (targetPtr != NULL) {
  966.     Tcl_DStringSetLength(targetPtr, targetLen);
  967. }
  968.     }
  969. #endif /* HAVE_FTS */
  970.     end:
  971.     if (errfile != NULL) {
  972. if (errorPtr != NULL) {
  973.     Tcl_ExternalToUtfDString(NULL, errfile, -1, errorPtr);
  974. }
  975. result = TCL_ERROR;
  976.     }
  977. #ifdef HAVE_FTS
  978.     if (fts != NULL) {
  979. fts_close(fts);
  980.     }
  981. #endif /* HAVE_FTS */
  982.     return result;
  983. }
  984. /*
  985.  *----------------------------------------------------------------------
  986.  *
  987.  * TraversalCopy
  988.  *
  989.  *      Called from TraverseUnixTree in order to execute a recursive copy
  990.  *      of a directory.
  991.  *
  992.  * Results:
  993.  *      Standard Tcl result.
  994.  *
  995.  * Side effects:
  996.  *      The file or directory src may be copied to dst, depending on 
  997.  *      the value of type.
  998.  *      
  999.  *----------------------------------------------------------------------
  1000.  */
  1001. static int 
  1002. TraversalCopy(srcPtr, dstPtr, statBufPtr, type, errorPtr) 
  1003.     Tcl_DString *srcPtr; /* Source pathname to copy (native). */
  1004.     Tcl_DString *dstPtr; /* Destination pathname of copy (native). */
  1005.     CONST Tcl_StatBuf *statBufPtr;
  1006. /* Stat info for file specified by srcPtr. */
  1007.     int type;                   /* Reason for call - see TraverseUnixTree(). */
  1008.     Tcl_DString *errorPtr; /* If non-NULL, uninitialized or free
  1009.  * DString filled with UTF-8 name of file
  1010.  * causing error. */
  1011. {
  1012.     switch (type) {
  1013. case DOTREE_F:
  1014.     if (DoCopyFile(Tcl_DStringValue(srcPtr), Tcl_DStringValue(dstPtr),
  1015.     statBufPtr) == TCL_OK) {
  1016. return TCL_OK;
  1017.     }
  1018.     break;
  1019. case DOTREE_PRED:
  1020.     if (DoCreateDirectory(Tcl_DStringValue(dstPtr)) == TCL_OK) {
  1021. return TCL_OK;
  1022.     }
  1023.     break;
  1024. case DOTREE_POSTD:
  1025.     if (CopyFileAtts(Tcl_DStringValue(srcPtr),
  1026.     Tcl_DStringValue(dstPtr), statBufPtr) == TCL_OK) {
  1027. return TCL_OK;
  1028.     }
  1029.     break;
  1030.     }
  1031.     /*
  1032.      * There shouldn't be a problem with src, because we already checked it
  1033.      * to get here.
  1034.      */
  1035.     if (errorPtr != NULL) {
  1036. Tcl_ExternalToUtfDString(NULL, Tcl_DStringValue(dstPtr),
  1037. Tcl_DStringLength(dstPtr), errorPtr);
  1038.     }
  1039.     return TCL_ERROR;
  1040. }
  1041. /*
  1042.  *---------------------------------------------------------------------------
  1043.  *
  1044.  * TraversalDelete --
  1045.  *
  1046.  *      Called by procedure TraverseUnixTree for every file and directory
  1047.  * that it encounters in a directory hierarchy. This procedure unlinks
  1048.  *      files, and removes directories after all the containing files 
  1049.  *      have been processed.
  1050.  *
  1051.  * Results:
  1052.  *      Standard Tcl result.
  1053.  *
  1054.  * Side effects:
  1055.  *      Files or directory specified by src will be deleted.
  1056.  *
  1057.  *----------------------------------------------------------------------
  1058.  */
  1059. static int
  1060. TraversalDelete(srcPtr, ignore, statBufPtr, type, errorPtr) 
  1061.     Tcl_DString *srcPtr; /* Source pathname (native). */
  1062.     Tcl_DString *ignore; /* Destination pathname (not used). */
  1063.     CONST Tcl_StatBuf *statBufPtr;
  1064. /* Stat info for file specified by srcPtr. */
  1065.     int type;                   /* Reason for call - see TraverseUnixTree(). */
  1066.     Tcl_DString *errorPtr; /* If non-NULL, uninitialized or free
  1067.  * DString filled with UTF-8 name of file
  1068.  * causing error. */
  1069. {
  1070.     switch (type) {
  1071.         case DOTREE_F: {
  1072.     if (TclpDeleteFile(Tcl_DStringValue(srcPtr)) == 0) {
  1073. return TCL_OK;
  1074.     }
  1075.     break;
  1076. }
  1077.         case DOTREE_PRED: {
  1078.     return TCL_OK;
  1079. }
  1080.         case DOTREE_POSTD: {
  1081.     if (DoRemoveDirectory(srcPtr, 0, NULL) == 0) {
  1082. return TCL_OK;
  1083.     }
  1084.     break;
  1085. }     
  1086.     }
  1087.     if (errorPtr != NULL) {
  1088. Tcl_ExternalToUtfDString(NULL, Tcl_DStringValue(srcPtr),
  1089. Tcl_DStringLength(srcPtr), errorPtr);
  1090.     }
  1091.     return TCL_ERROR;
  1092. }
  1093. /*
  1094.  *---------------------------------------------------------------------------
  1095.  *
  1096.  * CopyFileAtts --
  1097.  *
  1098.  * Copy the file attributes such as owner, group, permissions,
  1099.  * and modification date from one file to another.
  1100.  *
  1101.  * Results:
  1102.  * Standard Tcl result.
  1103.  *
  1104.  * Side effects:
  1105.  * user id, group id, permission bits, last modification time, and
  1106.  * last access time are updated in the new file to reflect the
  1107.  * old file.
  1108.  *
  1109.  *---------------------------------------------------------------------------
  1110.  */
  1111. static int
  1112. CopyFileAtts(src, dst, statBufPtr) 
  1113.     CONST char *src; /* Path name of source file (native). */
  1114.     CONST char *dst; /* Path name of target file (native). */
  1115.     CONST Tcl_StatBuf *statBufPtr;
  1116. /* Stat info for source file */
  1117. {
  1118.     struct utimbuf tval;
  1119.     mode_t newMode;
  1120.     
  1121.     newMode = statBufPtr->st_mode
  1122.     & (S_ISUID | S_ISGID | S_IRWXU | S_IRWXG | S_IRWXO);
  1123.     /* 
  1124.      * Note that if you copy a setuid file that is owned by someone
  1125.      * else, and you are not root, then the copy will be setuid to you.
  1126.      * The most correct implementation would probably be to have the
  1127.      * copy not setuid to anyone if the original file was owned by 
  1128.      * someone else, but this corner case isn't currently handled.
  1129.      * It would require another lstat(), or getuid().
  1130.      */
  1131.     
  1132.     if (chmod(dst, newMode)) { /* INTL: Native. */
  1133. newMode &= ~(S_ISUID | S_ISGID);
  1134. if (chmod(dst, newMode)) { /* INTL: Native. */
  1135.     return TCL_ERROR;
  1136. }
  1137.     }
  1138.     tval.actime = statBufPtr->st_atime; 
  1139.     tval.modtime = statBufPtr->st_mtime; 
  1140.     if (utime(dst, &tval)) { /* INTL: Native. */
  1141. return TCL_ERROR;
  1142.     }
  1143. #ifdef HAVE_COPYFILE
  1144. #ifdef WEAK_IMPORT_COPYFILE
  1145.     if (copyfile != NULL)
  1146. #endif
  1147.     copyfile(src, dst, NULL, COPYFILE_XATTR|COPYFILE_ACL);
  1148. #endif
  1149.     return TCL_OK;
  1150. }
  1151. /*
  1152.  *----------------------------------------------------------------------
  1153.  *
  1154.  * GetGroupAttribute
  1155.  *
  1156.  *      Gets the group attribute of a file.
  1157.  *
  1158.  * Results:
  1159.  *      Standard TCL result. Returns a new Tcl_Obj in attributePtrPtr
  1160.  * if there is no error.
  1161.  *
  1162.  * Side effects:
  1163.  *      A new object is allocated.
  1164.  *      
  1165.  *----------------------------------------------------------------------
  1166.  */
  1167. static int
  1168. GetGroupAttribute(interp, objIndex, fileName, attributePtrPtr)
  1169.     Tcl_Interp *interp; /* The interp we are using for errors. */
  1170.     int objIndex; /* The index of the attribute. */
  1171.     Tcl_Obj *fileName;   /* The name of the file (UTF-8). */
  1172.     Tcl_Obj **attributePtrPtr; /* A pointer to return the object with. */
  1173. {
  1174.     Tcl_StatBuf statBuf;
  1175.     struct group *groupPtr;
  1176.     int result;
  1177.     result = TclpObjStat(fileName, &statBuf);
  1178.     
  1179.     if (result != 0) {
  1180. Tcl_AppendResult(interp, "could not read "", 
  1181. Tcl_GetString(fileName), "": ",
  1182. Tcl_PosixError(interp), (char *) NULL);
  1183. return TCL_ERROR;
  1184.     }
  1185.     groupPtr = TclpGetGrGid(statBuf.st_gid);
  1186.     if (result == -1 || groupPtr == NULL) {
  1187. *attributePtrPtr = Tcl_NewIntObj((int) statBuf.st_gid);
  1188.     } else {
  1189. Tcl_DString ds;
  1190. CONST char *utf;
  1191. utf = Tcl_ExternalToUtfDString(NULL, groupPtr->gr_name, -1, &ds); 
  1192. *attributePtrPtr = Tcl_NewStringObj(utf, -1);
  1193. Tcl_DStringFree(&ds);
  1194.     }
  1195.     endgrent();
  1196.     return TCL_OK;
  1197. }
  1198. /*
  1199.  *----------------------------------------------------------------------
  1200.  *
  1201.  * GetOwnerAttribute
  1202.  *
  1203.  *      Gets the owner attribute of a file.
  1204.  *
  1205.  * Results:
  1206.  *      Standard TCL result. Returns a new Tcl_Obj in attributePtrPtr
  1207.  * if there is no error.
  1208.  *
  1209.  * Side effects:
  1210.  *      A new object is allocated.
  1211.  *      
  1212.  *----------------------------------------------------------------------
  1213.  */
  1214. static int
  1215. GetOwnerAttribute(interp, objIndex, fileName, attributePtrPtr)
  1216.     Tcl_Interp *interp; /* The interp we are using for errors. */
  1217.     int objIndex; /* The index of the attribute. */
  1218.     Tcl_Obj *fileName;   /* The name of the file (UTF-8). */
  1219.     Tcl_Obj **attributePtrPtr; /* A pointer to return the object with. */
  1220. {
  1221.     Tcl_StatBuf statBuf;
  1222.     struct passwd *pwPtr;
  1223.     int result;
  1224.     result = TclpObjStat(fileName, &statBuf);
  1225.     
  1226.     if (result != 0) {
  1227. Tcl_AppendResult(interp, "could not read "", 
  1228. Tcl_GetString(fileName), "": ",
  1229. Tcl_PosixError(interp), (char *) NULL);
  1230. return TCL_ERROR;
  1231.     }
  1232.     pwPtr = TclpGetPwUid(statBuf.st_uid);
  1233.     if (result == -1 || pwPtr == NULL) {
  1234. *attributePtrPtr = Tcl_NewIntObj((int) statBuf.st_uid);
  1235.     } else {
  1236. Tcl_DString ds;
  1237. CONST char *utf;
  1238. utf = Tcl_ExternalToUtfDString(NULL, pwPtr->pw_name, -1, &ds); 
  1239. *attributePtrPtr = Tcl_NewStringObj(utf, Tcl_DStringLength(&ds));
  1240. Tcl_DStringFree(&ds);
  1241.     }
  1242.     endpwent();
  1243.     return TCL_OK;
  1244. }
  1245. /*
  1246.  *----------------------------------------------------------------------
  1247.  *
  1248.  * GetPermissionsAttribute
  1249.  *
  1250.  *      Gets the group attribute of a file.
  1251.  *
  1252.  * Results:
  1253.  *      Standard TCL result. Returns a new Tcl_Obj in attributePtrPtr
  1254.  * if there is no error. The object will have ref count 0.
  1255.  *
  1256.  * Side effects:
  1257.  *      A new object is allocated.
  1258.  *      
  1259.  *----------------------------------------------------------------------
  1260.  */
  1261. static int
  1262. GetPermissionsAttribute(interp, objIndex, fileName, attributePtrPtr)
  1263.     Tcl_Interp *interp;     /* The interp we are using for errors. */
  1264.     int objIndex;     /* The index of the attribute. */
  1265.     Tcl_Obj *fileName;       /* The name of the file (UTF-8). */
  1266.     Tcl_Obj **attributePtrPtr;     /* A pointer to return the object with. */
  1267. {
  1268.     Tcl_StatBuf statBuf;
  1269.     char returnString[7];
  1270.     int result;
  1271.     result = TclpObjStat(fileName, &statBuf);
  1272.     
  1273.     if (result != 0) {
  1274. Tcl_AppendResult(interp, "could not read "", 
  1275. Tcl_GetString(fileName), "": ",
  1276. Tcl_PosixError(interp), (char *) NULL);
  1277. return TCL_ERROR;
  1278.     }
  1279.     sprintf(returnString, "%0#5lo", (long) (statBuf.st_mode & 0x00007FFF));
  1280.     *attributePtrPtr = Tcl_NewStringObj(returnString, -1);
  1281.     
  1282.     return TCL_OK;
  1283. }
  1284. /*
  1285.  *---------------------------------------------------------------------------
  1286.  *
  1287.  * SetGroupAttribute --
  1288.  *
  1289.  *      Sets the group of the file to the specified group.
  1290.  *
  1291.  * Results:
  1292.  *      Standard TCL result.
  1293.  *
  1294.  * Side effects:
  1295.  *      As above.
  1296.  *      
  1297.  *---------------------------------------------------------------------------
  1298.  */
  1299. static int
  1300. SetGroupAttribute(interp, objIndex, fileName, attributePtr)
  1301.     Tcl_Interp *interp;     /* The interp for error reporting. */
  1302.     int objIndex;     /* The index of the attribute. */
  1303.     Tcl_Obj *fileName;             /* The name of the file (UTF-8). */
  1304.     Tcl_Obj *attributePtr;     /* New group for file. */
  1305. {
  1306.     long gid;
  1307.     int result;
  1308.     CONST char *native;
  1309.     if (Tcl_GetLongFromObj(NULL, attributePtr, &gid) != TCL_OK) {
  1310. Tcl_DString ds;
  1311. struct group *groupPtr;
  1312. CONST char *string;
  1313. int length;
  1314. string = Tcl_GetStringFromObj(attributePtr, &length);
  1315. native = Tcl_UtfToExternalDString(NULL, string, length, &ds);
  1316. groupPtr = TclpGetGrNam(native); /* INTL: Native. */
  1317. Tcl_DStringFree(&ds);
  1318. if (groupPtr == NULL) {
  1319.     endgrent();
  1320.     Tcl_AppendResult(interp, "could not set group for file "",
  1321.     Tcl_GetString(fileName), "": group "", 
  1322.     string, "" does not exist",
  1323.     (char *) NULL);
  1324.     return TCL_ERROR;
  1325. }
  1326. gid = groupPtr->gr_gid;
  1327.     }
  1328.     native = Tcl_FSGetNativePath(fileName);
  1329.     result = chown(native, (uid_t) -1, (gid_t) gid); /* INTL: Native. */
  1330.     endgrent();
  1331.     if (result != 0) {
  1332. Tcl_AppendResult(interp, "could not set group for file "",
  1333.     Tcl_GetString(fileName), "": ", Tcl_PosixError(interp), 
  1334.     (char *) NULL);
  1335. return TCL_ERROR;
  1336.     }    
  1337.     return TCL_OK;
  1338. }
  1339. /*
  1340.  *---------------------------------------------------------------------------
  1341.  *
  1342.  * SetOwnerAttribute --
  1343.  *
  1344.  *      Sets the owner of the file to the specified owner.
  1345.  *
  1346.  * Results:
  1347.  *      Standard TCL result.
  1348.  *
  1349.  * Side effects:
  1350.  *      As above.
  1351.  *      
  1352.  *---------------------------------------------------------------------------
  1353.  */
  1354. static int
  1355. SetOwnerAttribute(interp, objIndex, fileName, attributePtr)
  1356.     Tcl_Interp *interp;     /* The interp for error reporting. */
  1357.     int objIndex;     /* The index of the attribute. */
  1358.     Tcl_Obj *fileName;        /* The name of the file (UTF-8). */
  1359.     Tcl_Obj *attributePtr;     /* New owner for file. */
  1360. {
  1361.     long uid;
  1362.     int result;
  1363.     CONST char *native;
  1364.     if (Tcl_GetLongFromObj(NULL, attributePtr, &uid) != TCL_OK) {
  1365. Tcl_DString ds;
  1366. struct passwd *pwPtr;
  1367. CONST char *string;
  1368. int length;
  1369. string = Tcl_GetStringFromObj(attributePtr, &length);
  1370. native = Tcl_UtfToExternalDString(NULL, string, length, &ds);
  1371. pwPtr = TclpGetPwNam(native); /* INTL: Native. */
  1372. Tcl_DStringFree(&ds);
  1373. if (pwPtr == NULL) {
  1374.     endpwent();
  1375.     Tcl_AppendResult(interp, "could not set owner for file "",
  1376.      Tcl_GetString(fileName), "": user "", 
  1377.      string, "" does not exist",
  1378.     (char *) NULL);
  1379.     return TCL_ERROR;
  1380. }
  1381. uid = pwPtr->pw_uid;
  1382.     }
  1383.     native = Tcl_FSGetNativePath(fileName);
  1384.     result = chown(native, (uid_t) uid, (gid_t) -1);   /* INTL: Native. */
  1385.     
  1386.     endpwent();
  1387.     if (result != 0) {
  1388. Tcl_AppendResult(interp, "could not set owner for file "", 
  1389.  Tcl_GetString(fileName), "": ", 
  1390.  Tcl_PosixError(interp), (char *) NULL);
  1391. return TCL_ERROR;
  1392.     }
  1393.     return TCL_OK;
  1394. }
  1395. /*
  1396.  *---------------------------------------------------------------------------
  1397.  *
  1398.  * SetPermissionsAttribute
  1399.  *
  1400.  *      Sets the file to the given permission.
  1401.  *
  1402.  * Results:
  1403.  *      Standard TCL result.
  1404.  *
  1405.  * Side effects:
  1406.  *      The permission of the file is changed.
  1407.  *      
  1408.  *---------------------------------------------------------------------------
  1409.  */
  1410. static int
  1411. SetPermissionsAttribute(interp, objIndex, fileName, attributePtr)
  1412.     Tcl_Interp *interp;     /* The interp we are using for errors. */
  1413.     int objIndex;     /* The index of the attribute. */
  1414.     Tcl_Obj *fileName;       /* The name of the file (UTF-8). */
  1415.     Tcl_Obj *attributePtr;     /* The attribute to set. */
  1416. {
  1417.     long mode;
  1418.     mode_t newMode;
  1419.     int result;
  1420.     CONST char *native;
  1421.     /*
  1422.      * First try if the string is a number
  1423.      */
  1424.     if (Tcl_GetLongFromObj(NULL, attributePtr, &mode) == TCL_OK) {
  1425.         newMode = (mode_t) (mode & 0x00007FFF);
  1426.     } else {
  1427. Tcl_StatBuf buf;
  1428. char *modeStringPtr = Tcl_GetString(attributePtr);
  1429. /*
  1430.  * Try the forms "rwxrwxrwx" and "ugo=rwx"
  1431.  *
  1432.  * We get the current mode of the file, in order to allow for
  1433.  * ug+-=rwx style chmod strings.
  1434.  */
  1435. result = TclpObjStat(fileName, &buf);
  1436. if (result != 0) {
  1437.     Tcl_AppendResult(interp, "could not read "", 
  1438.     Tcl_GetString(fileName), "": ",
  1439.     Tcl_PosixError(interp), (char *) NULL);
  1440.     return TCL_ERROR;
  1441. }
  1442. newMode = (mode_t) (buf.st_mode & 0x00007FFF);
  1443. if (GetModeFromPermString(NULL, modeStringPtr, &newMode) != TCL_OK) {
  1444.     Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
  1445.     "unknown permission string format "",
  1446.     modeStringPtr, """, (char *) NULL);
  1447.     return TCL_ERROR;
  1448. }
  1449.     }
  1450.     native = Tcl_FSGetNativePath(fileName);
  1451.     result = chmod(native, newMode); /* INTL: Native. */
  1452.     if (result != 0) {
  1453. Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
  1454. "could not set permissions for file "", 
  1455. Tcl_GetString(fileName), "": ",
  1456. Tcl_PosixError(interp), (char *) NULL);
  1457. return TCL_ERROR;
  1458.     }
  1459.     return TCL_OK;
  1460. }
  1461. /*
  1462.  *---------------------------------------------------------------------------
  1463.  *
  1464.  * TclpObjListVolumes --
  1465.  *
  1466.  * Lists the currently mounted volumes, which on UNIX is just /.
  1467.  *
  1468.  * Results:
  1469.  * The list of volumes.
  1470.  *
  1471.  * Side effects:
  1472.  * None.
  1473.  *
  1474.  *---------------------------------------------------------------------------
  1475.  */
  1476. Tcl_Obj*
  1477. TclpObjListVolumes(void)
  1478. {
  1479.     Tcl_Obj *resultPtr = Tcl_NewStringObj("/",1);
  1480.     Tcl_IncrRefCount(resultPtr);
  1481.     return resultPtr;
  1482. }
  1483. /*
  1484.  *----------------------------------------------------------------------
  1485.  *
  1486.  * GetModeFromPermString --
  1487.  *
  1488.  * This procedure is invoked to process the "file permissions"
  1489.  * Tcl command, to check for a "rwxrwxrwx" or "ugoa+-=rwxst" string.
  1490.  * See the user documentation for details on what it does.
  1491.  *
  1492.  * Results:
  1493.  * A standard Tcl result.
  1494.  *
  1495.  * Side effects:
  1496.  * See the user documentation.
  1497.  *
  1498.  *----------------------------------------------------------------------
  1499.  */
  1500. static int
  1501. GetModeFromPermString(interp, modeStringPtr, modePtr)
  1502.     Tcl_Interp *interp; /* The interp we are using for errors. */
  1503.     char *modeStringPtr; /* Permissions string */
  1504.     mode_t *modePtr; /* pointer to the mode value */
  1505. {
  1506.     mode_t newMode;
  1507.     mode_t oldMode; /* Storage for the value of the old mode
  1508.  * (that is passed in), to allow for the
  1509.  * chmod style manipulation */
  1510.     int i,n, who, op, what, op_found, who_found;
  1511.     /*
  1512.      * We start off checking for an "rwxrwxrwx" style permissions string
  1513.      */
  1514.     if (strlen(modeStringPtr) != 9) {
  1515.         goto chmodStyleCheck;
  1516.     }
  1517.     newMode = 0;
  1518.     for (i = 0; i < 9; i++) {
  1519. switch (*(modeStringPtr+i)) {
  1520.     case 'r':
  1521. if ((i%3) != 0) {
  1522.     goto chmodStyleCheck;
  1523. }
  1524. newMode |= (1<<(8-i));
  1525. break;
  1526.     case 'w':
  1527. if ((i%3) != 1) {
  1528.     goto chmodStyleCheck;
  1529. }
  1530. newMode |= (1<<(8-i));
  1531. break;
  1532.     case 'x':
  1533. if ((i%3) != 2) {
  1534.     goto chmodStyleCheck;
  1535. }
  1536. newMode |= (1<<(8-i));
  1537. break;
  1538.     case 's':
  1539. if (((i%3) != 2) || (i > 5)) {
  1540.     goto chmodStyleCheck;
  1541. }
  1542. newMode |= (1<<(8-i));
  1543. newMode |= (1<<(11-(i/3)));
  1544. break;
  1545.     case 'S':
  1546. if (((i%3) != 2) || (i > 5)) {
  1547.     goto chmodStyleCheck;
  1548. }
  1549. newMode |= (1<<(11-(i/3)));
  1550. break;
  1551.     case 't':
  1552. if (i != 8) {
  1553.     goto chmodStyleCheck;
  1554. }
  1555. newMode |= (1<<(8-i));
  1556. newMode |= (1<<9);
  1557. break;
  1558.     case 'T':
  1559. if (i != 8) {
  1560.     goto chmodStyleCheck;
  1561. }
  1562. newMode |= (1<<9);
  1563. break;
  1564.     case '-':
  1565. break;
  1566.     default:
  1567. /*
  1568.  * Oops, not what we thought it was, so go on
  1569.  */
  1570. goto chmodStyleCheck;
  1571. }
  1572.     }
  1573.     *modePtr = newMode;
  1574.     return TCL_OK;
  1575.     chmodStyleCheck:
  1576.     /*
  1577.      * We now check for an "ugoa+-=rwxst" style permissions string
  1578.      */
  1579.     for (n = 0 ; *(modeStringPtr+n) != '' ; n = n + i) {
  1580. oldMode = *modePtr;
  1581. who = op = what = op_found = who_found = 0;
  1582. for (i = 0 ; *(modeStringPtr+n+i) != '' ; i++ ) {
  1583.     if (!who_found) {
  1584. /* who */
  1585. switch (*(modeStringPtr+n+i)) {
  1586.     case 'u' :
  1587. who |= 0x9c0;
  1588. continue;
  1589.     case 'g' :
  1590. who |= 0x438;
  1591. continue;
  1592.     case 'o' :
  1593. who |= 0x207;
  1594. continue;
  1595.     case 'a' :
  1596. who |= 0xfff;
  1597. continue;
  1598. }
  1599.     }
  1600.     who_found = 1;
  1601.     if (who == 0) {
  1602. who = 0xfff;
  1603.     }
  1604.     if (!op_found) {
  1605. /* op */
  1606. switch (*(modeStringPtr+n+i)) {
  1607.     case '+' :
  1608. op = 1;
  1609. op_found = 1;
  1610. continue;
  1611.     case '-' :
  1612. op = 2;
  1613. op_found = 1;
  1614. continue;
  1615.     case '=' :
  1616. op = 3;
  1617. op_found = 1;
  1618. continue;
  1619.     default  :
  1620. return TCL_ERROR;
  1621. }
  1622.     }
  1623.     /* what */
  1624.     switch (*(modeStringPtr+n+i)) {
  1625. case 'r' :
  1626.     what |= 0x124;
  1627.     continue;
  1628. case 'w' :
  1629.     what |= 0x92;
  1630.     continue;
  1631. case 'x' :
  1632.     what |= 0x49;
  1633.     continue;
  1634. case 's' :
  1635.     what |= 0xc00;
  1636.     continue;
  1637. case 't' :
  1638.     what |= 0x200;
  1639.     continue;
  1640. case ',' :
  1641.     break;
  1642. default  :
  1643.     return TCL_ERROR;
  1644.     }
  1645.     if (*(modeStringPtr+n+i) == ',') {
  1646. i++;
  1647. break;
  1648.     }
  1649. }
  1650. switch (op) {
  1651.     case 1 :
  1652. *modePtr = oldMode | (who & what);
  1653. continue;
  1654.     case 2 :
  1655. *modePtr = oldMode & ~(who & what);
  1656. continue;
  1657.     case 3 :
  1658. *modePtr = (oldMode & ~who) | (who & what);
  1659. continue;
  1660. }
  1661.     }
  1662.     return TCL_OK;
  1663. }
  1664. /*
  1665.  *---------------------------------------------------------------------------
  1666.  *
  1667.  * TclpObjNormalizePath --
  1668.  *
  1669.  * This function scans through a path specification and replaces
  1670.  * it, in place, with a normalized version.  A normalized version
  1671.  * is one in which all symlinks in the path are replaced with
  1672.  * their expanded form (except a symlink at the very end of the
  1673.  * path).
  1674.  *
  1675.  * Results:
  1676.  * The new 'nextCheckpoint' value, giving as far as we could
  1677.  * understand in the path.
  1678.  *
  1679.  * Side effects:
  1680.  * The pathPtr string, is modified.
  1681.  *
  1682.  *---------------------------------------------------------------------------
  1683.  */
  1684. int
  1685. TclpObjNormalizePath(interp, pathPtr, nextCheckpoint)
  1686.     Tcl_Interp *interp;
  1687.     Tcl_Obj *pathPtr;
  1688.     int nextCheckpoint;
  1689. {
  1690.     char *currentPathEndPosition;
  1691.     int pathLen;
  1692.     char cur;
  1693.     char *path = Tcl_GetStringFromObj(pathPtr, &pathLen);
  1694. #ifndef NO_REALPATH
  1695.     char normPath[MAXPATHLEN];
  1696.     Tcl_DString ds;
  1697.     CONST char *nativePath; 
  1698. #endif
  1699.     /* 
  1700.      * We add '1' here because if nextCheckpoint is zero we know
  1701.      * that '/' exists, and if it isn't zero, it must point at
  1702.      * a directory separator which we also know exists.
  1703.      */
  1704.     currentPathEndPosition = path + nextCheckpoint;
  1705.     if (*currentPathEndPosition == '/') {
  1706. currentPathEndPosition++;
  1707.     }
  1708. #ifndef NO_REALPATH
  1709.     /* For speed, try to get the entire path in one go */
  1710.     if (nextCheckpoint == 0 && haveRealpath) {
  1711.         char *lastDir = strrchr(currentPathEndPosition, '/');
  1712. if (lastDir != NULL) {
  1713.     nativePath = Tcl_UtfToExternalDString(NULL, path, 
  1714.   lastDir - path, &ds);
  1715.     if (Realpath(nativePath, normPath) != NULL) {
  1716. if (*nativePath != '/' && *normPath == '/') {
  1717.     /*
  1718.      * realpath has transformed a relative path into an
  1719.      * absolute path, we do not know how to handle this.
  1720.      */
  1721. } else {
  1722.     nextCheckpoint = lastDir - path;
  1723.     goto wholeStringOk;
  1724. }
  1725.     }
  1726.     Tcl_DStringFree(&ds);
  1727. }
  1728.     }
  1729.     /* Else do it the slow way */
  1730. #endif
  1731.     
  1732.     while (1) {
  1733. cur = *currentPathEndPosition;
  1734. if ((cur == '/') && (path != currentPathEndPosition)) {
  1735.     /* Reached directory separator */
  1736.     Tcl_DString ds;
  1737.     CONST char *nativePath;
  1738.     int accessOk;
  1739.     nativePath = Tcl_UtfToExternalDString(NULL, path, 
  1740.     currentPathEndPosition - path, &ds);
  1741.     accessOk = access(nativePath, F_OK);
  1742.     Tcl_DStringFree(&ds);
  1743.     if (accessOk != 0) {
  1744. /* File doesn't exist */
  1745. break;
  1746.     }
  1747.     /* Update the acceptable point */
  1748.     nextCheckpoint = currentPathEndPosition - path;
  1749. } else if (cur == 0) {
  1750.     /* Reached end of string */
  1751.     break;
  1752. }
  1753. currentPathEndPosition++;
  1754.     }
  1755.     /* 
  1756.      * We should really now convert this to a canonical path.  We do
  1757.      * that with 'realpath' if we have it available.  Otherwise we could
  1758.      * step through every single path component, checking whether it is a 
  1759.      * symlink, but that would be a lot of work, and most modern OSes 
  1760.      * have 'realpath'.
  1761.      */
  1762. #ifndef NO_REALPATH
  1763.     if (haveRealpath) {
  1764. /* 
  1765.  * If we only had '/foo' or '/' then we never increment nextCheckpoint
  1766.  * and we don't need or want to go through 'Realpath'.  Also, on some
  1767.  * platforms, passing an empty string to 'Realpath' will give us the
  1768.  * normalized pwd, which is not what we want at all!
  1769.  */
  1770. if (nextCheckpoint == 0) return 0;
  1771. nativePath = Tcl_UtfToExternalDString(NULL, path, nextCheckpoint, &ds);
  1772. if (Realpath(nativePath, normPath) != NULL) {
  1773.     int newNormLen;
  1774.     wholeStringOk:
  1775.     newNormLen = strlen(normPath);
  1776.     if ((newNormLen == Tcl_DStringLength(&ds))
  1777.     && (strcmp(normPath, nativePath) == 0)) {
  1778. /* String is unchanged */
  1779. Tcl_DStringFree(&ds);
  1780. if (path[nextCheckpoint] != '') {
  1781.     nextCheckpoint++;
  1782. }
  1783. return nextCheckpoint;
  1784.     }
  1785.     
  1786.     /* 
  1787.      * Free up the native path and put in its place the
  1788.      * converted, normalized path.
  1789.      */
  1790.     Tcl_DStringFree(&ds);
  1791.     Tcl_ExternalToUtfDString(NULL, normPath, (int) newNormLen, &ds);
  1792.     
  1793.     if (path[nextCheckpoint] != '') {
  1794. /* not at end, append remaining path */
  1795. int normLen = Tcl_DStringLength(&ds);
  1796. Tcl_DStringAppend(&ds, path + nextCheckpoint,
  1797. pathLen - nextCheckpoint);
  1798. /* 
  1799.  * We recognise up to and including the directory
  1800.  * separator.
  1801.  */
  1802. nextCheckpoint = normLen + 1;
  1803.     } else {
  1804. /* We recognise the whole string */ 
  1805. nextCheckpoint = Tcl_DStringLength(&ds);
  1806.     }
  1807.     /* 
  1808.      * Overwrite with the normalized path.
  1809.      */
  1810.     Tcl_SetStringObj(pathPtr, Tcl_DStringValue(&ds),
  1811.     Tcl_DStringLength(&ds));
  1812. }
  1813. Tcl_DStringFree(&ds);
  1814.     }
  1815. #endif /* !NO_REALPATH */
  1816.     return nextCheckpoint;
  1817. }