tclUnixFCmd.c

Go to the documentation of this file.
00001 /*
00002  * tclUnixFCmd.c
00003  *
00004  *      This file implements the unix specific portion of file manipulation
00005  *      subcommands of the "file" command. All filename arguments should
00006  *      already be translated to native format.
00007  *
00008  * Copyright (c) 1996-1998 Sun Microsystems, Inc.
00009  *
00010  * See the file "license.terms" for information on usage and redistribution of
00011  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
00012  *
00013  * RCS: @(#) $Id: tclUnixFCmd.c,v 1.65 2007/12/13 15:28:42 dgp Exp $
00014  *
00015  * Portions of this code were derived from NetBSD source code which has the
00016  * following copyright notice:
00017  *
00018  * Copyright (c) 1988, 1993, 1994
00019  *      The Regents of the University of California. All rights reserved.
00020  *
00021  * Redistribution and use in source and binary forms, with or without
00022  * modification, are permitted provided that the following conditions are met:
00023  * 1. Redistributions of source code must retain the above copyright notice,
00024  *    this list of conditions and the following disclaimer.
00025  * 2. Redistributions in binary form must reproduce the above copyright
00026  *    notice, this list of conditions and the following disclaimer in the
00027  *    documentation and/or other materials provided with the distribution.
00028  * 3. All advertising materials mentioning features or use of this software
00029  *    must display the following acknowledgement:
00030  *      This product includes software developed by the University of
00031  *      California, Berkeley and its contributors.
00032  * 4. Neither the name of the University nor the names of its contributors may
00033  *    be used to endorse or promote products derived from this software
00034  *    without specific prior written permission.
00035  *
00036  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
00037  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00038  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
00039  * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
00040  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
00041  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
00042  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00043  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00044  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
00045  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
00046  * DAMAGE.
00047  */
00048 
00049 #include "tclInt.h"
00050 #include <utime.h>
00051 #include <grp.h>
00052 #ifndef HAVE_ST_BLKSIZE
00053 #ifndef NO_FSTATFS
00054 #include <sys/statfs.h>
00055 #endif
00056 #endif
00057 #ifdef HAVE_FTS
00058 #include <fts.h>
00059 #endif
00060 
00061 /*
00062  * The following constants specify the type of callback when
00063  * TraverseUnixTree() calls the traverseProc()
00064  */
00065 
00066 #define DOTREE_PRED     1       /* pre-order directory */
00067 #define DOTREE_POSTD    2       /* post-order directory */
00068 #define DOTREE_F        3       /* regular file */
00069 
00070 /*
00071  * Callbacks for file attributes code.
00072  */
00073 
00074 static int              GetGroupAttribute(Tcl_Interp *interp, int objIndex,
00075                             Tcl_Obj *fileName, Tcl_Obj **attributePtrPtr);
00076 static int              GetOwnerAttribute(Tcl_Interp *interp, int objIndex,
00077                             Tcl_Obj *fileName, Tcl_Obj **attributePtrPtr);
00078 static int              GetPermissionsAttribute(Tcl_Interp *interp,
00079                             int objIndex, Tcl_Obj *fileName,
00080                             Tcl_Obj **attributePtrPtr);
00081 static int              SetGroupAttribute(Tcl_Interp *interp, int objIndex,
00082                             Tcl_Obj *fileName, Tcl_Obj *attributePtr);
00083 static int              SetOwnerAttribute(Tcl_Interp *interp, int objIndex,
00084                             Tcl_Obj *fileName, Tcl_Obj *attributePtr);
00085 static int              SetPermissionsAttribute(Tcl_Interp *interp,
00086                             int objIndex, Tcl_Obj *fileName,
00087                             Tcl_Obj *attributePtr);
00088 static int              GetModeFromPermString(Tcl_Interp *interp,
00089                             char *modeStringPtr, mode_t *modePtr);
00090 #if defined(HAVE_CHFLAGS) && defined(UF_IMMUTABLE)
00091 static int              GetReadOnlyAttribute(Tcl_Interp *interp, int objIndex,
00092                             Tcl_Obj *fileName, Tcl_Obj **attributePtrPtr);
00093 static int              SetReadOnlyAttribute(Tcl_Interp *interp, int objIndex,
00094                             Tcl_Obj *fileName, Tcl_Obj *attributePtr);
00095 #endif
00096 
00097 /*
00098  * Prototype for the TraverseUnixTree callback function.
00099  */
00100 
00101 typedef int (TraversalProc)(Tcl_DString *srcPtr, Tcl_DString *dstPtr,
00102         CONST Tcl_StatBuf *statBufPtr, int type, Tcl_DString *errorPtr);
00103 
00104 /*
00105  * Constants and variables necessary for file attributes subcommand.
00106  *
00107  * IMPORTANT: The permissions attribute is assumed to be the third item (i.e.
00108  * to be indexed with '2' in arrays) in code in tclIOUtil.c and possibly
00109  * elsewhere in Tcl's core.
00110  */
00111 
00112 #ifdef DJGPP
00113 
00114 /*
00115  * See contrib/djgpp/tclDjgppFCmd.c for definition.
00116  */
00117 
00118 extern TclFileAttrProcs tclpFileAttrProcs[];
00119 extern char *tclpFileAttrStrings[];
00120 
00121 #else
00122 enum {
00123     UNIX_GROUP_ATTRIBUTE, UNIX_OWNER_ATTRIBUTE, UNIX_PERMISSIONS_ATTRIBUTE,
00124 #if defined(HAVE_CHFLAGS) && defined(UF_IMMUTABLE)
00125     UNIX_READONLY_ATTRIBUTE,
00126 #endif
00127 #ifdef MAC_OSX_TCL
00128     MACOSX_CREATOR_ATTRIBUTE, MACOSX_TYPE_ATTRIBUTE, MACOSX_HIDDEN_ATTRIBUTE,
00129     MACOSX_RSRCLENGTH_ATTRIBUTE,
00130 #endif
00131     UNIX_INVALID_ATTRIBUTE /* lint - last enum value needs no trailing , */
00132 };
00133 
00134 MODULE_SCOPE CONST char *tclpFileAttrStrings[];
00135 CONST char *tclpFileAttrStrings[] = {
00136     "-group", "-owner", "-permissions",
00137 #if defined(HAVE_CHFLAGS) && defined(UF_IMMUTABLE)
00138     "-readonly",
00139 #endif
00140 #ifdef MAC_OSX_TCL
00141     "-creator", "-type", "-hidden", "-rsrclength",
00142 #endif
00143     NULL
00144 };
00145 
00146 MODULE_SCOPE CONST TclFileAttrProcs tclpFileAttrProcs[];
00147 CONST TclFileAttrProcs tclpFileAttrProcs[] = {
00148     {GetGroupAttribute, SetGroupAttribute},
00149     {GetOwnerAttribute, SetOwnerAttribute},
00150     {GetPermissionsAttribute, SetPermissionsAttribute},
00151 #if defined(HAVE_CHFLAGS) && defined(UF_IMMUTABLE)
00152     {GetReadOnlyAttribute, SetReadOnlyAttribute},
00153 #endif
00154 #ifdef MAC_OSX_TCL
00155     {TclMacOSXGetFileAttribute, TclMacOSXSetFileAttribute},
00156     {TclMacOSXGetFileAttribute, TclMacOSXSetFileAttribute},
00157     {TclMacOSXGetFileAttribute, TclMacOSXSetFileAttribute},
00158     {TclMacOSXGetFileAttribute, TclMacOSXSetFileAttribute},
00159 #endif
00160 };
00161 #endif
00162 
00163 /*
00164  * This is the maximum number of consecutive readdir/unlink calls that can be
00165  * made (with no intervening rewinddir or closedir/opendir) before triggering
00166  * a bug that makes readdir return NULL even though some directory entries
00167  * have not been processed. The bug afflicts SunOS's readdir when applied to
00168  * ufs file systems and Darwin 6.5's (and OSX v.10.3.8's) HFS+. JH found the
00169  * Darwin readdir to reset at 147, so 130 is chosen to be conservative. We
00170  * can't do a general rewind on failure as NFS can create special files that
00171  * recreate themselves when you try and delete them. 8.4.8 added a solution
00172  * that was affected by a single such NFS file, this solution should not be
00173  * affected by less than THRESHOLD such files. [Bug 1034337]
00174  */
00175 
00176 #define MAX_READDIR_UNLINK_THRESHOLD 130
00177 
00178 /*
00179  * Declarations for local procedures defined in this file:
00180  */
00181 
00182 static int              CopyFileAtts(CONST char *src,
00183                             CONST char *dst, CONST Tcl_StatBuf *statBufPtr);
00184 static int              DoCopyFile(CONST char *srcPtr, CONST char *dstPtr,
00185                             CONST Tcl_StatBuf *statBufPtr);
00186 static int              DoCreateDirectory(CONST char *pathPtr);
00187 static int              DoRemoveDirectory(Tcl_DString *pathPtr,
00188                             int recursive, Tcl_DString *errorPtr);
00189 static int              DoRenameFile(CONST char *src, CONST char *dst);
00190 static int              TraversalCopy(Tcl_DString *srcPtr,
00191                             Tcl_DString *dstPtr, CONST Tcl_StatBuf *statBufPtr,
00192                             int type, Tcl_DString *errorPtr);
00193 static int              TraversalDelete(Tcl_DString *srcPtr,
00194                             Tcl_DString *dstPtr, CONST Tcl_StatBuf *statBufPtr,
00195                             int type, Tcl_DString *errorPtr);
00196 static int              TraverseUnixTree(TraversalProc *traversalProc,
00197                             Tcl_DString *sourcePtr, Tcl_DString *destPtr,
00198                             Tcl_DString *errorPtr, int doRewind);
00199 
00200 #ifdef PURIFY
00201 /*
00202  * realpath and purify don't mix happily. It has been noted that realpath
00203  * should not be used with purify because of bogus warnings, but just
00204  * memset'ing the resolved path will squelch those. This assumes we are
00205  * passing the standard MAXPATHLEN size resolved arg.
00206  */
00207 
00208 static char *           Realpath(CONST char *path, char *resolved);
00209 
00210 char *
00211 Realpath(
00212     CONST char *path,
00213     char *resolved)
00214 {
00215     memset(resolved, 0, MAXPATHLEN);
00216     return realpath(path, resolved);
00217 }
00218 #else
00219 #define Realpath realpath
00220 #endif
00221 
00222 #ifndef NO_REALPATH
00223 #if defined(__APPLE__) && defined(TCL_THREADS) && \
00224         defined(MAC_OS_X_VERSION_MIN_REQUIRED) && \
00225         MAC_OS_X_VERSION_MIN_REQUIRED < 1030
00226 /*
00227  * Prior to Darwin 7, realpath is not thread-safe, c.f. Bug 711232; if we
00228  * might potentially be running on pre-10.3 OSX, check Darwin release at
00229  * runtime before using realpath.
00230  */
00231 
00232 MODULE_SCOPE long tclMacOSXDarwinRelease;
00233 #define haveRealpath (tclMacOSXDarwinRelease >= 7)
00234 #else
00235 #define haveRealpath 1
00236 #endif
00237 #endif /* NO_REALPATH */
00238 
00239 #ifdef HAVE_FTS
00240 #ifdef HAVE_STRUCT_STAT64
00241 /* fts doesn't do stat64 */
00242 #define noFtsStat 1
00243 #elif defined(__APPLE__) && defined(__LP64__) && \
00244         defined(MAC_OS_X_VERSION_MIN_REQUIRED) && \
00245         MAC_OS_X_VERSION_MIN_REQUIRED < 1050
00246 /*
00247  * Prior to Darwin 9, 64bit fts_open() without FTS_NOSTAT may crash (due to a
00248  * 64bit-unsafe ALIGN macro); if we could be running on pre-10.5 OSX, check
00249  * Darwin release at runtime and do a separate stat() if necessary.
00250  */
00251 
00252 MODULE_SCOPE long tclMacOSXDarwinRelease;
00253 #define noFtsStat (tclMacOSXDarwinRelease < 9)
00254 #else
00255 #define noFtsStat 0
00256 #endif
00257 #endif /* HAVE_FTS */
00258 
00259 /*
00260  *---------------------------------------------------------------------------
00261  *
00262  * TclpObjRenameFile, DoRenameFile --
00263  *
00264  *      Changes the name of an existing file or directory, from src to dst. If
00265  *      src and dst refer to the same file or directory, does nothing and
00266  *      returns success. Otherwise if dst already exists, it will be deleted
00267  *      and replaced by src subject to the following conditions:
00268  *          If src is a directory, dst may be an empty directory.
00269  *          If src is a file, dst may be a file.
00270  *      In any other situation where dst already exists, the rename will fail.
00271  *
00272  * Results:
00273  *      If the directory was successfully created, returns TCL_OK. Otherwise
00274  *      the return value is TCL_ERROR and errno is set to indicate the error.
00275  *      Some possible values for errno are:
00276  *
00277  *      EACCES:     src or dst parent directory can't be read and/or written.
00278  *      EEXIST:     dst is a non-empty directory.
00279  *      EINVAL:     src is a root directory or dst is a subdirectory of src.
00280  *      EISDIR:     dst is a directory, but src is not.
00281  *      ENOENT:     src doesn't exist, or src or dst is "".
00282  *      ENOTDIR:    src is a directory, but dst is not.
00283  *      EXDEV:      src and dst are on different filesystems.
00284  *
00285  * Side effects:
00286  *      The implementation of rename may allow cross-filesystem renames, but
00287  *      the caller should be prepared to emulate it with copy and delete if
00288  *      errno is EXDEV.
00289  *
00290  *---------------------------------------------------------------------------
00291  */
00292 
00293 int
00294 TclpObjRenameFile(
00295     Tcl_Obj *srcPathPtr,
00296     Tcl_Obj *destPathPtr)
00297 {
00298     return DoRenameFile(Tcl_FSGetNativePath(srcPathPtr),
00299             Tcl_FSGetNativePath(destPathPtr));
00300 }
00301 
00302 static int
00303 DoRenameFile(
00304     CONST char *src,            /* Pathname of file or dir to be renamed
00305                                  * (native). */
00306     CONST char *dst)            /* New pathname of file or directory
00307                                  * (native). */
00308 {
00309     if (rename(src, dst) == 0) {                        /* INTL: Native. */
00310         return TCL_OK;
00311     }
00312     if (errno == ENOTEMPTY) {
00313         errno = EEXIST;
00314     }
00315 
00316     /*
00317      * IRIX returns EIO when you attept to move a directory into itself. We
00318      * just map EIO to EINVAL get the right message on SGI. Most platforms
00319      * don't return EIO except in really strange cases.
00320      */
00321 
00322     if (errno == EIO) {
00323         errno = EINVAL;
00324     }
00325 
00326 #ifndef NO_REALPATH
00327     /*
00328      * SunOS 4.1.4 reports overwriting a non-empty directory with a directory
00329      * as EINVAL instead of EEXIST (first rule out the correct EINVAL result
00330      * code for moving a directory into itself). Must be conditionally
00331      * compiled because realpath() not defined on all systems.
00332      */
00333 
00334     if (errno == EINVAL && haveRealpath) {
00335         char srcPath[MAXPATHLEN], dstPath[MAXPATHLEN];
00336         DIR *dirPtr;
00337         Tcl_DirEntry *dirEntPtr;
00338 
00339         if ((Realpath((char *) src, srcPath) != NULL)   /* INTL: Native. */
00340                 && (Realpath((char *) dst, dstPath) != NULL) /* INTL: Native */
00341                 && (strncmp(srcPath, dstPath, strlen(srcPath)) != 0)) {
00342             dirPtr = opendir(dst);                      /* INTL: Native. */
00343             if (dirPtr != NULL) {
00344                 while (1) {
00345                     dirEntPtr = TclOSreaddir(dirPtr);   /* INTL: Native. */
00346                     if (dirEntPtr == NULL) {
00347                         break;
00348                     }
00349                     if ((strcmp(dirEntPtr->d_name, ".") != 0) &&
00350                             (strcmp(dirEntPtr->d_name, "..") != 0)) {
00351                         errno = EEXIST;
00352                         closedir(dirPtr);
00353                         return TCL_ERROR;
00354                     }
00355                 }
00356                 closedir(dirPtr);
00357             }
00358         }
00359         errno = EINVAL;
00360     }
00361 #endif  /* !NO_REALPATH */
00362 
00363     if (strcmp(src, "/") == 0) {
00364         /*
00365          * Alpha reports renaming / as EBUSY and Linux reports it as EACCES,
00366          * instead of EINVAL.
00367          */
00368 
00369         errno = EINVAL;
00370     }
00371 
00372     /*
00373      * DEC Alpha OSF1 V3.0 returns EACCES when attempting to move a file
00374      * across filesystems and the parent directory of that file is not
00375      * writable. Most other systems return EXDEV. Does nothing to correct this
00376      * behavior.
00377      */
00378 
00379     return TCL_ERROR;
00380 }
00381 
00382 /*
00383  *---------------------------------------------------------------------------
00384  *
00385  * TclpObjCopyFile, DoCopyFile --
00386  *
00387  *      Copy a single file (not a directory). If dst already exists and is not
00388  *      a directory, it is removed.
00389  *
00390  * Results:
00391  *      If the file was successfully copied, returns TCL_OK. Otherwise the
00392  *      return value is TCL_ERROR and errno is set to indicate the error. Some
00393  *      possible values for errno are:
00394  *
00395  *      EACCES:     src or dst parent directory can't be read and/or written.
00396  *      EISDIR:     src or dst is a directory.
00397  *      ENOENT:     src doesn't exist. src or dst is "".
00398  *
00399  * Side effects:
00400  *      This procedure will also copy symbolic links, block, and character
00401  *      devices, and fifos. For symbolic links, the links themselves will be
00402  *      copied and not what they point to. For the other special file types,
00403  *      the directory entry will be copied and not the contents of the device
00404  *      that it refers to.
00405  *
00406  *---------------------------------------------------------------------------
00407  */
00408 
00409 int
00410 TclpObjCopyFile(
00411     Tcl_Obj *srcPathPtr,
00412     Tcl_Obj *destPathPtr)
00413 {
00414     CONST char *src = Tcl_FSGetNativePath(srcPathPtr);
00415     Tcl_StatBuf srcStatBuf;
00416 
00417     if (TclOSlstat(src, &srcStatBuf) != 0) {            /* INTL: Native. */
00418         return TCL_ERROR;
00419     }
00420 
00421     return DoCopyFile(src, Tcl_FSGetNativePath(destPathPtr), &srcStatBuf);
00422 }
00423 
00424 static int
00425 DoCopyFile(
00426     CONST char *src,            /* Pathname of file to be copied (native). */
00427     CONST char *dst,            /* Pathname of file to copy to (native). */
00428     CONST Tcl_StatBuf *statBufPtr)
00429                                 /* Used to determine filetype. */
00430 {
00431     Tcl_StatBuf dstStatBuf;
00432 
00433     if (S_ISDIR(statBufPtr->st_mode)) {
00434         errno = EISDIR;
00435         return TCL_ERROR;
00436     }
00437 
00438     /*
00439      * Symlink, and some of the other calls will fail if the target exists, so
00440      * we remove it first.
00441      */
00442 
00443     if (TclOSlstat(dst, &dstStatBuf) == 0) {            /* INTL: Native. */
00444         if (S_ISDIR(dstStatBuf.st_mode)) {
00445             errno = EISDIR;
00446             return TCL_ERROR;
00447         }
00448     }
00449     if (unlink(dst) != 0) {                             /* INTL: Native. */
00450         if (errno != ENOENT) {
00451             return TCL_ERROR;
00452         }
00453     }
00454 
00455     switch ((int) (statBufPtr->st_mode & S_IFMT)) {
00456 #ifndef DJGPP
00457     case S_IFLNK: {
00458         char link[MAXPATHLEN];
00459         int length;
00460 
00461         length = readlink(src, link, sizeof(link));     /* INTL: Native. */
00462         if (length == -1) {
00463             return TCL_ERROR;
00464         }
00465         link[length] = '\0';
00466         if (symlink(link, dst) < 0) {                   /* INTL: Native. */
00467             return TCL_ERROR;
00468         }
00469 #ifdef MAC_OSX_TCL
00470         TclMacOSXCopyFileAttributes(src, dst, statBufPtr);
00471 #endif
00472         break;
00473     }
00474 #endif
00475     case S_IFBLK:
00476     case S_IFCHR:
00477         if (mknod(dst, statBufPtr->st_mode,             /* INTL: Native. */
00478                 statBufPtr->st_rdev) < 0) {
00479             return TCL_ERROR;
00480         }
00481         return CopyFileAtts(src, dst, statBufPtr);
00482     case S_IFIFO:
00483         if (mkfifo(dst, statBufPtr->st_mode) < 0) {     /* INTL: Native. */
00484             return TCL_ERROR;
00485         }
00486         return CopyFileAtts(src, dst, statBufPtr);
00487     default:
00488         return TclUnixCopyFile(src, dst, statBufPtr, 0);
00489     }
00490     return TCL_OK;
00491 }
00492 
00493 /*
00494  *----------------------------------------------------------------------
00495  *
00496  * TclUnixCopyFile -
00497  *
00498  *      Helper function for TclpCopyFile. Copies one regular file, using
00499  *      read() and write().
00500  *
00501  * Results:
00502  *      A standard Tcl result.
00503  *
00504  * Side effects:
00505  *      A file is copied. Dst will be overwritten if it exists.
00506  *
00507  *----------------------------------------------------------------------
00508  */
00509 
00510 int
00511 TclUnixCopyFile(
00512     CONST char *src,            /* Pathname of file to copy (native). */
00513     CONST char *dst,            /* Pathname of file to create/overwrite
00514                                  * (native). */
00515     CONST Tcl_StatBuf *statBufPtr,
00516                                 /* Used to determine mode and blocksize. */
00517     int dontCopyAtts)           /* If flag set, don't copy attributes. */
00518 {
00519     int srcFd, dstFd;
00520     unsigned blockSize;         /* Optimal I/O blocksize for filesystem */
00521     char *buffer;               /* Data buffer for copy */
00522     size_t nread;
00523 
00524 #ifdef DJGPP
00525 #define BINMODE |O_BINARY
00526 #else
00527 #define BINMODE
00528 #endif
00529 
00530     if ((srcFd = TclOSopen(src, O_RDONLY BINMODE, 0)) < 0) { /* INTL: Native */
00531         return TCL_ERROR;
00532     }
00533 
00534     dstFd = TclOSopen(dst, O_CREAT|O_TRUNC|O_WRONLY BINMODE, /* INTL: Native */
00535             statBufPtr->st_mode);
00536     if (dstFd < 0) {
00537         close(srcFd);
00538         return TCL_ERROR;
00539     }
00540 
00541     /*
00542      * Try to work out the best size of buffer to use for copying. If we
00543      * can't, it's no big deal as we can just use a (32-bit) page, since
00544      * that's likely to be fairly efficient anyway.
00545      */
00546 
00547 #ifdef HAVE_ST_BLKSIZE
00548     blockSize = statBufPtr->st_blksize;
00549 #elif !defined(NO_FSTATFS)
00550     {
00551         struct statfs fs;
00552 
00553         if (fstatfs(srcFd, &fs, sizeof(fs), 0) == 0) {
00554             blockSize = fs.f_bsize;
00555         } else {
00556             blockSize = 4096;
00557         }
00558     }
00559 #else
00560     blockSize = 4096;
00561 #endif /* HAVE_ST_BLKSIZE */
00562 
00563     /*
00564      * [SF Tcl Bug 1586470] Even if we HAVE_ST_BLKSIZE, there are filesystems
00565      * which report a bogus value for the blocksize. An example is the Andrew
00566      * Filesystem (afs), reporting a blocksize of 0. When detecting such a
00567      * situation we now simply fall back to a hardwired default size.
00568      */
00569 
00570     if (blockSize <= 0) {
00571         blockSize = 4096;
00572     }
00573     buffer = ckalloc(blockSize);
00574     while (1) {
00575         nread = (size_t) read(srcFd, buffer, blockSize);
00576         if ((nread == (size_t) -1) || (nread == 0)) {
00577             break;
00578         }
00579         if ((size_t) write(dstFd, buffer, nread) != nread) {
00580             nread = (size_t) -1;
00581             break;
00582         }
00583     }
00584 
00585     ckfree(buffer);
00586     close(srcFd);
00587     if ((close(dstFd) != 0) || (nread == (size_t) -1)) {
00588         unlink(dst);                                    /* INTL: Native. */
00589         return TCL_ERROR;
00590     }
00591     if (!dontCopyAtts && CopyFileAtts(src, dst, statBufPtr) == TCL_ERROR) {
00592         /*
00593          * The copy succeeded, but setting the permissions failed, so be in a
00594          * consistent state, we remove the file that was created by the copy.
00595          */
00596 
00597         unlink(dst);                                    /* INTL: Native. */
00598         return TCL_ERROR;
00599     }
00600     return TCL_OK;
00601 }
00602 
00603 /*
00604  *---------------------------------------------------------------------------
00605  *
00606  * TclpObjDeleteFile, TclpDeleteFile --
00607  *
00608  *      Removes a single file (not a directory).
00609  *
00610  * Results:
00611  *      If the file was successfully deleted, returns TCL_OK. Otherwise the
00612  *      return value is TCL_ERROR and errno is set to indicate the error. Some
00613  *      possible values for errno are:
00614  *
00615  *      EACCES:     a parent directory can't be read and/or written.
00616  *      EISDIR:     path is a directory.
00617  *      ENOENT:     path doesn't exist or is "".
00618  *
00619  * Side effects:
00620  *      The file is deleted, even if it is read-only.
00621  *
00622  *---------------------------------------------------------------------------
00623  */
00624 
00625 int
00626 TclpObjDeleteFile(
00627     Tcl_Obj *pathPtr)
00628 {
00629     return TclpDeleteFile(Tcl_FSGetNativePath(pathPtr));
00630 }
00631 
00632 int
00633 TclpDeleteFile(
00634     CONST char *path)           /* Pathname of file to be removed (native). */
00635 {
00636     if (unlink(path) != 0) {                            /* INTL: Native. */
00637         return TCL_ERROR;
00638     }
00639     return TCL_OK;
00640 }
00641 
00642 /*
00643  *---------------------------------------------------------------------------
00644  *
00645  * TclpCreateDirectory, DoCreateDirectory --
00646  *
00647  *      Creates the specified directory. All parent directories of the
00648  *      specified directory must already exist. The directory is automatically
00649  *      created with permissions so that user can access the new directory and
00650  *      create new files or subdirectories in it.
00651  *
00652  * Results:
00653  *      If the directory was successfully created, returns TCL_OK. Otherwise
00654  *      the return value is TCL_ERROR and errno is set to indicate the error.
00655  *      Some possible values for errno are:
00656  *
00657  *      EACCES:     a parent directory can't be read and/or written.
00658  *      EEXIST:     path already exists.
00659  *      ENOENT:     a parent directory doesn't exist.
00660  *
00661  * Side effects:
00662  *      A directory is created with the current umask, except that permission
00663  *      for u+rwx will always be added.
00664  *
00665  *---------------------------------------------------------------------------
00666  */
00667 
00668 int
00669 TclpObjCreateDirectory(
00670     Tcl_Obj *pathPtr)
00671 {
00672     return DoCreateDirectory(Tcl_FSGetNativePath(pathPtr));
00673 }
00674 
00675 static int
00676 DoCreateDirectory(
00677     CONST char *path)           /* Pathname of directory to create (native). */
00678 {
00679     mode_t mode;
00680 
00681     mode = umask(0);
00682     umask(mode);
00683 
00684     /*
00685      * umask return value is actually the inverse of the permissions.
00686      */
00687 
00688     mode = (0777 & ~mode) | S_IRUSR | S_IWUSR | S_IXUSR;
00689 
00690     if (mkdir(path, mode) != 0) {                       /* INTL: Native. */
00691         return TCL_ERROR;
00692     }
00693     return TCL_OK;
00694 }
00695 
00696 /*
00697  *---------------------------------------------------------------------------
00698  *
00699  * TclpObjCopyDirectory --
00700  *
00701  *      Recursively copies a directory. The target directory dst must not
00702  *      already exist. Note that this function does not merge two directory
00703  *      hierarchies, even if the target directory is an an empty directory.
00704  *
00705  * Results:
00706  *      If the directory was successfully copied, returns TCL_OK. Otherwise
00707  *      the return value is TCL_ERROR, errno is set to indicate the error, and
00708  *      the pathname of the file that caused the error is stored in errorPtr.
00709  *      See TclpObjCreateDirectory and TclpObjCopyFile for a description of
00710  *      possible values for errno.
00711  *
00712  * Side effects:
00713  *      An exact copy of the directory hierarchy src will be created with the
00714  *      name dst. If an error occurs, the error will be returned immediately,
00715  *      and remaining files will not be processed.
00716  *
00717  *---------------------------------------------------------------------------
00718  */
00719 
00720 int
00721 TclpObjCopyDirectory(
00722     Tcl_Obj *srcPathPtr,
00723     Tcl_Obj *destPathPtr,
00724     Tcl_Obj **errorPtr)
00725 {
00726     Tcl_DString ds;
00727     Tcl_DString srcString, dstString;
00728     int ret;
00729     Tcl_Obj *transPtr;
00730 
00731     transPtr = Tcl_FSGetTranslatedPath(NULL,srcPathPtr);
00732     Tcl_UtfToExternalDString(NULL,
00733             (transPtr != NULL ? TclGetString(transPtr) : NULL),
00734             -1, &srcString);
00735     if (transPtr != NULL) {
00736         Tcl_DecrRefCount(transPtr);
00737     }
00738     transPtr = Tcl_FSGetTranslatedPath(NULL,destPathPtr);
00739     Tcl_UtfToExternalDString(NULL,
00740             (transPtr != NULL ? TclGetString(transPtr) : NULL),
00741             -1, &dstString);
00742     if (transPtr != NULL) {
00743         Tcl_DecrRefCount(transPtr);
00744     }
00745 
00746     ret = TraverseUnixTree(TraversalCopy, &srcString, &dstString, &ds, 0);
00747 
00748     Tcl_DStringFree(&srcString);
00749     Tcl_DStringFree(&dstString);
00750 
00751     if (ret != TCL_OK) {
00752         *errorPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), -1);
00753         Tcl_DStringFree(&ds);
00754         Tcl_IncrRefCount(*errorPtr);
00755     }
00756     return ret;
00757 }
00758 
00759 /*
00760  *---------------------------------------------------------------------------
00761  *
00762  * TclpRemoveDirectory, DoRemoveDirectory --
00763  *
00764  *      Removes directory (and its contents, if the recursive flag is set).
00765  *
00766  * Results:
00767  *      If the directory was successfully removed, returns TCL_OK. Otherwise
00768  *      the return value is TCL_ERROR, errno is set to indicate the error, and
00769  *      the pathname of the file that caused the error is stored in errorPtr.
00770  *      Some possible values for errno are:
00771  *
00772  *      EACCES:     path directory can't be read and/or written.
00773  *      EEXIST:     path is a non-empty directory.
00774  *      EINVAL:     path is a root directory.
00775  *      ENOENT:     path doesn't exist or is "".
00776  *      ENOTDIR:    path is not a directory.
00777  *
00778  * Side effects:
00779  *      Directory removed. If an error occurs, the error will be returned
00780  *      immediately, and remaining files will not be deleted.
00781  *
00782  *---------------------------------------------------------------------------
00783  */
00784 
00785 int
00786 TclpObjRemoveDirectory(
00787     Tcl_Obj *pathPtr,
00788     int recursive,
00789     Tcl_Obj **errorPtr)
00790 {
00791     Tcl_DString ds;
00792     Tcl_DString pathString;
00793     int ret;
00794     Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(NULL, pathPtr);
00795 
00796     Tcl_UtfToExternalDString(NULL,
00797             (transPtr != NULL ? TclGetString(transPtr) : NULL),
00798             -1, &pathString);
00799     if (transPtr != NULL) {
00800         Tcl_DecrRefCount(transPtr);
00801     }
00802     ret = DoRemoveDirectory(&pathString, recursive, &ds);
00803     Tcl_DStringFree(&pathString);
00804 
00805     if (ret != TCL_OK) {
00806         *errorPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), -1);
00807         Tcl_DStringFree(&ds);
00808         Tcl_IncrRefCount(*errorPtr);
00809     }
00810     return ret;
00811 }
00812 
00813 static int
00814 DoRemoveDirectory(
00815     Tcl_DString *pathPtr,       /* Pathname of directory to be removed
00816                                  * (native). */
00817     int recursive,              /* If non-zero, removes directories that are
00818                                  * nonempty. Otherwise, will only remove empty
00819                                  * directories. */
00820     Tcl_DString *errorPtr)      /* If non-NULL, uninitialized or free DString
00821                                  * filled with UTF-8 name of file causing
00822                                  * error. */
00823 {
00824     CONST char *path;
00825     mode_t oldPerm = 0;
00826     int result;
00827 
00828     path = Tcl_DStringValue(pathPtr);
00829 
00830     if (recursive != 0) {
00831         /*
00832          * We should try to change permissions so this can be deleted.
00833          */
00834 
00835         Tcl_StatBuf statBuf;
00836         int newPerm;
00837 
00838         if (TclOSstat(path, &statBuf) == 0) {
00839             oldPerm = (mode_t) (statBuf.st_mode & 0x00007FFF);
00840         }
00841 
00842         newPerm = oldPerm | (64+128+256);
00843         chmod(path, (mode_t) newPerm);
00844     }
00845 
00846     if (rmdir(path) == 0) {                             /* INTL: Native. */
00847         return TCL_OK;
00848     }
00849     if (errno == ENOTEMPTY) {
00850         errno = EEXIST;
00851     }
00852 
00853     result = TCL_OK;
00854     if ((errno != EEXIST) || (recursive == 0)) {
00855         if (errorPtr != NULL) {
00856             Tcl_ExternalToUtfDString(NULL, path, -1, errorPtr);
00857         }
00858         result = TCL_ERROR;
00859     }
00860 
00861     /*
00862      * The directory is nonempty, but the recursive flag has been specified,
00863      * so we recursively remove all the files in the directory.
00864      */
00865 
00866     if (result == TCL_OK) {
00867         result = TraverseUnixTree(TraversalDelete, pathPtr, NULL, errorPtr, 1);
00868     }
00869 
00870     if ((result != TCL_OK) && (recursive != 0)) {
00871         /*
00872          * Try to restore permissions.
00873          */
00874 
00875         chmod(path, oldPerm);
00876     }
00877     return result;
00878 }
00879 
00880 /*
00881  *---------------------------------------------------------------------------
00882  *
00883  * TraverseUnixTree --
00884  *
00885  *      Traverse directory tree specified by sourcePtr, calling the function
00886  *      traverseProc for each file and directory encountered. If destPtr is
00887  *      non-null, each of name in the sourcePtr directory is appended to the
00888  *      directory specified by destPtr and passed as the second argument to
00889  *      traverseProc().
00890  *
00891  * Results:
00892  *      Standard Tcl result.
00893  *
00894  * Side effects:
00895  *      None caused by TraverseUnixTree, however the user specified
00896  *      traverseProc() may change state. If an error occurs, the error will be
00897  *      returned immediately, and remaining files will not be processed.
00898  *
00899  *---------------------------------------------------------------------------
00900  */
00901 
00902 static int
00903 TraverseUnixTree(
00904     TraversalProc *traverseProc,/* Function to call for every file and
00905                                  * directory in source hierarchy. */
00906     Tcl_DString *sourcePtr,     /* Pathname of source directory to be
00907                                  * traversed (native). */
00908     Tcl_DString *targetPtr,     /* Pathname of directory to traverse in
00909                                  * parallel with source directory (native). */
00910     Tcl_DString *errorPtr,      /* If non-NULL, uninitialized or free DString
00911                                  * filled with UTF-8 name of file causing
00912                                  * error. */
00913     int doRewind)               /* Flag indicating that to ensure complete
00914                                  * traversal of source hierarchy, the readdir
00915                                  * loop should be rewound whenever
00916                                  * traverseProc has returned TCL_OK; this is
00917                                  * required when traverseProc modifies the
00918                                  * source hierarchy, e.g. by deleting
00919                                  * files. */
00920 {
00921     Tcl_StatBuf statBuf;
00922     CONST char *source, *errfile;
00923     int result, sourceLen;
00924     int targetLen;
00925 #ifndef HAVE_FTS
00926     int numProcessed = 0;
00927     Tcl_DirEntry *dirEntPtr;
00928     DIR *dirPtr;
00929 #else
00930     CONST char *paths[2] = {NULL, NULL};
00931     FTS *fts = NULL;
00932     FTSENT *ent;
00933 #endif
00934 
00935     errfile = NULL;
00936     result = TCL_OK;
00937     targetLen = 0;              /* lint. */
00938 
00939     source = Tcl_DStringValue(sourcePtr);
00940     if (TclOSlstat(source, &statBuf) != 0) {            /* INTL: Native. */
00941         errfile = source;
00942         goto end;
00943     }
00944     if (!S_ISDIR(statBuf.st_mode)) {
00945         /*
00946          * Process the regular file
00947          */
00948 
00949         return (*traverseProc)(sourcePtr, targetPtr, &statBuf, DOTREE_F,
00950                 errorPtr);
00951     }
00952 #ifndef HAVE_FTS
00953     dirPtr = opendir(source);                           /* INTL: Native. */
00954     if (dirPtr == NULL) {
00955         /*
00956          * Can't read directory
00957          */
00958 
00959         errfile = source;
00960         goto end;
00961     }
00962     result = (*traverseProc)(sourcePtr, targetPtr, &statBuf, DOTREE_PRED,
00963             errorPtr);
00964     if (result != TCL_OK) {
00965         closedir(dirPtr);
00966         return result;
00967     }
00968 
00969     Tcl_DStringAppend(sourcePtr, "/", 1);
00970     sourceLen = Tcl_DStringLength(sourcePtr);
00971 
00972     if (targetPtr != NULL) {
00973         Tcl_DStringAppend(targetPtr, "/", 1);
00974         targetLen = Tcl_DStringLength(targetPtr);
00975     }
00976 
00977     while ((dirEntPtr = TclOSreaddir(dirPtr)) != NULL) { /* INTL: Native. */
00978         if ((dirEntPtr->d_name[0] == '.')
00979                 && ((dirEntPtr->d_name[1] == '\0')
00980                         || (strcmp(dirEntPtr->d_name, "..") == 0))) {
00981             continue;
00982         }
00983 
00984         /*
00985          * Append name after slash, and recurse on the file.
00986          */
00987 
00988         Tcl_DStringAppend(sourcePtr, dirEntPtr->d_name, -1);
00989         if (targetPtr != NULL) {
00990             Tcl_DStringAppend(targetPtr, dirEntPtr->d_name, -1);
00991         }
00992         result = TraverseUnixTree(traverseProc, sourcePtr, targetPtr,
00993                 errorPtr, doRewind);
00994         if (result != TCL_OK) {
00995             break;
00996         } else {
00997             numProcessed++;
00998         }
00999 
01000         /*
01001          * Remove name after slash.
01002          */
01003 
01004         Tcl_DStringSetLength(sourcePtr, sourceLen);
01005         if (targetPtr != NULL) {
01006             Tcl_DStringSetLength(targetPtr, targetLen);
01007         }
01008         if (doRewind && (numProcessed > MAX_READDIR_UNLINK_THRESHOLD)) {
01009             /*
01010              * Call rewinddir if we've called unlink or rmdir so many times
01011              * (since the opendir or the previous rewinddir), to avoid a
01012              * NULL-return that may a symptom of a buggy readdir.
01013              */
01014 
01015             rewinddir(dirPtr);
01016             numProcessed = 0;
01017         }
01018     }
01019     closedir(dirPtr);
01020 
01021     /*
01022      * Strip off the trailing slash we added
01023      */
01024 
01025     Tcl_DStringSetLength(sourcePtr, sourceLen - 1);
01026     if (targetPtr != NULL) {
01027         Tcl_DStringSetLength(targetPtr, targetLen - 1);
01028     }
01029 
01030     if (result == TCL_OK) {
01031         /*
01032          * Call traverseProc() on a directory after visiting all the files in
01033          * that directory.
01034          */
01035 
01036         result = (*traverseProc)(sourcePtr, targetPtr, &statBuf, DOTREE_POSTD,
01037                 errorPtr);
01038     }
01039 #else /* HAVE_FTS */
01040     paths[0] = source;
01041     fts = fts_open((char**)paths, FTS_PHYSICAL | FTS_NOCHDIR |
01042             (noFtsStat || doRewind ? FTS_NOSTAT : 0), NULL);
01043     if (fts == NULL) {
01044         errfile = source;
01045         goto end;
01046     }
01047 
01048     sourceLen = Tcl_DStringLength(sourcePtr);
01049     if (targetPtr != NULL) {
01050         targetLen = Tcl_DStringLength(targetPtr);
01051     }
01052 
01053     while ((ent = fts_read(fts)) != NULL) {
01054         unsigned short info = ent->fts_info;
01055         char *path = ent->fts_path + sourceLen;
01056         unsigned short pathlen = ent->fts_pathlen - sourceLen;
01057         int type;
01058         Tcl_StatBuf *statBufPtr = NULL;
01059         
01060         if (info == FTS_DNR || info == FTS_ERR || info == FTS_NS) {
01061             errfile = ent->fts_path;
01062             break;
01063         }
01064         Tcl_DStringAppend(sourcePtr, path, pathlen);
01065         if (targetPtr != NULL) {
01066             Tcl_DStringAppend(targetPtr, path, pathlen);
01067         }
01068         switch (info) {
01069         case FTS_D:
01070             type = DOTREE_PRED;
01071             break;
01072         case FTS_DP:
01073             type = DOTREE_POSTD;
01074             break;
01075         default:
01076             type = DOTREE_F;
01077             break;
01078         }
01079         if (!doRewind) { /* no need to stat for delete */
01080             if (noFtsStat) {
01081                 statBufPtr = &statBuf;
01082                 if (TclOSlstat(ent->fts_path, statBufPtr) != 0) {
01083                     errfile = ent->fts_path;
01084                     break;
01085                 }
01086             } else {
01087                 statBufPtr = (Tcl_StatBuf *) ent->fts_statp;
01088             }
01089         }
01090         result = (*traverseProc)(sourcePtr, targetPtr, statBufPtr, type,
01091                 errorPtr);
01092         if (result != TCL_OK) {
01093             break;
01094         }
01095         Tcl_DStringSetLength(sourcePtr, sourceLen);
01096         if (targetPtr != NULL) {
01097             Tcl_DStringSetLength(targetPtr, targetLen);
01098         }
01099     }
01100 #endif /* HAVE_FTS */
01101 
01102   end:
01103     if (errfile != NULL) {
01104         if (errorPtr != NULL) {
01105             Tcl_ExternalToUtfDString(NULL, errfile, -1, errorPtr);
01106         }
01107         result = TCL_ERROR;
01108     }
01109 #ifdef HAVE_FTS
01110     if (fts != NULL) {
01111         fts_close(fts);
01112     }
01113 #endif
01114 
01115     return result;
01116 }
01117 
01118 /*
01119  *----------------------------------------------------------------------
01120  *
01121  * TraversalCopy
01122  *
01123  *      Called from TraverseUnixTree in order to execute a recursive copy of a
01124  *      directory.
01125  *
01126  * Results:
01127  *      Standard Tcl result.
01128  *
01129  * Side effects:
01130  *      The file or directory src may be copied to dst, depending on the value
01131  *      of type.
01132  *
01133  *----------------------------------------------------------------------
01134  */
01135 
01136 static int
01137 TraversalCopy(
01138     Tcl_DString *srcPtr,        /* Source pathname to copy (native). */
01139     Tcl_DString *dstPtr,        /* Destination pathname of copy (native). */
01140     CONST Tcl_StatBuf *statBufPtr,
01141                                 /* Stat info for file specified by srcPtr. */
01142     int type,                   /* Reason for call - see TraverseUnixTree(). */
01143     Tcl_DString *errorPtr)      /* If non-NULL, uninitialized or free DString
01144                                  * filled with UTF-8 name of file causing
01145                                  * error. */
01146 {
01147     switch (type) {
01148     case DOTREE_F:
01149         if (DoCopyFile(Tcl_DStringValue(srcPtr), Tcl_DStringValue(dstPtr),
01150                 statBufPtr) == TCL_OK) {
01151             return TCL_OK;
01152         }
01153         break;
01154 
01155     case DOTREE_PRED:
01156         if (DoCreateDirectory(Tcl_DStringValue(dstPtr)) == TCL_OK) {
01157             return TCL_OK;
01158         }
01159         break;
01160 
01161     case DOTREE_POSTD:
01162         if (CopyFileAtts(Tcl_DStringValue(srcPtr),
01163                 Tcl_DStringValue(dstPtr), statBufPtr) == TCL_OK) {
01164             return TCL_OK;
01165         }
01166         break;
01167     }
01168 
01169     /*
01170      * There shouldn't be a problem with src, because we already checked it to
01171      * get here.
01172      */
01173 
01174     if (errorPtr != NULL) {
01175         Tcl_ExternalToUtfDString(NULL, Tcl_DStringValue(dstPtr),
01176                 Tcl_DStringLength(dstPtr), errorPtr);
01177     }
01178     return TCL_ERROR;
01179 }
01180 
01181 /*
01182  *---------------------------------------------------------------------------
01183  *
01184  * TraversalDelete --
01185  *
01186  *      Called by procedure TraverseUnixTree for every file and directory that
01187  *      it encounters in a directory hierarchy. This procedure unlinks files,
01188  *      and removes directories after all the containing files have been
01189  *      processed.
01190  *
01191  * Results:
01192  *      Standard Tcl result.
01193  *
01194  * Side effects:
01195  *      Files or directory specified by src will be deleted.
01196  *
01197  *----------------------------------------------------------------------
01198  */
01199 
01200 static int
01201 TraversalDelete(
01202     Tcl_DString *srcPtr,        /* Source pathname (native). */
01203     Tcl_DString *ignore,        /* Destination pathname (not used). */
01204     CONST Tcl_StatBuf *statBufPtr,
01205                                 /* Stat info for file specified by srcPtr. */
01206     int type,                   /* Reason for call - see TraverseUnixTree(). */
01207     Tcl_DString *errorPtr)      /* If non-NULL, uninitialized or free DString
01208                                  * filled with UTF-8 name of file causing
01209                                  * error. */
01210 {
01211     switch (type) {
01212     case DOTREE_F:
01213         if (TclpDeleteFile(Tcl_DStringValue(srcPtr)) == 0) {
01214             return TCL_OK;
01215         }
01216         break;
01217     case DOTREE_PRED:
01218         return TCL_OK;
01219     case DOTREE_POSTD:
01220         if (DoRemoveDirectory(srcPtr, 0, NULL) == 0) {
01221             return TCL_OK;
01222         }
01223         break;
01224     }
01225     if (errorPtr != NULL) {
01226         Tcl_ExternalToUtfDString(NULL, Tcl_DStringValue(srcPtr),
01227                 Tcl_DStringLength(srcPtr), errorPtr);
01228     }
01229     return TCL_ERROR;
01230 }
01231 
01232 /*
01233  *---------------------------------------------------------------------------
01234  *
01235  * CopyFileAtts --
01236  *
01237  *      Copy the file attributes such as owner, group, permissions, and
01238  *      modification date from one file to another.
01239  *
01240  * Results:
01241  *      Standard Tcl result.
01242  *
01243  * Side effects:
01244  *      User id, group id, permission bits, last modification time, and last
01245  *      access time are updated in the new file to reflect the old file.
01246  *
01247  *---------------------------------------------------------------------------
01248  */
01249 
01250 static int
01251 CopyFileAtts(
01252     CONST char *src,            /* Path name of source file (native). */
01253     CONST char *dst,            /* Path name of target file (native). */
01254     CONST Tcl_StatBuf *statBufPtr)
01255                                 /* Stat info for source file */
01256 {
01257     struct utimbuf tval;
01258     mode_t newMode;
01259 
01260     newMode = statBufPtr->st_mode
01261             & (S_ISUID | S_ISGID | S_IRWXU | S_IRWXG | S_IRWXO);
01262 
01263     /*
01264      * Note that if you copy a setuid file that is owned by someone else, and
01265      * you are not root, then the copy will be setuid to you. The most correct
01266      * implementation would probably be to have the copy not setuid to anyone
01267      * if the original file was owned by someone else, but this corner case
01268      * isn't currently handled. It would require another lstat(), or getuid().
01269      */
01270 
01271     if (chmod(dst, newMode)) {                          /* INTL: Native. */
01272         newMode &= ~(S_ISUID | S_ISGID);
01273         if (chmod(dst, newMode)) {                      /* INTL: Native. */
01274             return TCL_ERROR;
01275         }
01276     }
01277 
01278     tval.actime = statBufPtr->st_atime;
01279     tval.modtime = statBufPtr->st_mtime;
01280 
01281     if (utime(dst, &tval)) {                            /* INTL: Native. */
01282         return TCL_ERROR;
01283     }
01284 #ifdef MAC_OSX_TCL
01285     TclMacOSXCopyFileAttributes(src, dst, statBufPtr);
01286 #endif
01287     return TCL_OK;
01288 }
01289 
01290 /*
01291  *----------------------------------------------------------------------
01292  *
01293  * GetGroupAttribute
01294  *
01295  *      Gets the group attribute of a file.
01296  *
01297  * Results:
01298  *      Standard TCL result. Returns a new Tcl_Obj in attributePtrPtr if there
01299  *      is no error.
01300  *
01301  * Side effects:
01302  *      A new object is allocated.
01303  *
01304  *----------------------------------------------------------------------
01305  */
01306 
01307 static int
01308 GetGroupAttribute(
01309     Tcl_Interp *interp,         /* The interp we are using for errors. */
01310     int objIndex,               /* The index of the attribute. */
01311     Tcl_Obj *fileName,          /* The name of the file (UTF-8). */
01312     Tcl_Obj **attributePtrPtr)  /* A pointer to return the object with. */
01313 {
01314     Tcl_StatBuf statBuf;
01315     struct group *groupPtr;
01316     int result;
01317 
01318     result = TclpObjStat(fileName, &statBuf);
01319 
01320     if (result != 0) {
01321         if (interp != NULL) {
01322             Tcl_AppendResult(interp, "could not read \"",
01323                     TclGetString(fileName), "\": ",
01324                     Tcl_PosixError(interp), NULL);
01325         }
01326         return TCL_ERROR;
01327     }
01328 
01329     groupPtr = TclpGetGrGid(statBuf.st_gid);
01330 
01331     if (groupPtr == NULL) {
01332         *attributePtrPtr = Tcl_NewIntObj((int) statBuf.st_gid);
01333     } else {
01334         Tcl_DString ds;
01335         CONST char *utf;
01336 
01337         utf = Tcl_ExternalToUtfDString(NULL, groupPtr->gr_name, -1, &ds);
01338         *attributePtrPtr = Tcl_NewStringObj(utf, -1);
01339         Tcl_DStringFree(&ds);
01340     }
01341     endgrent();
01342     return TCL_OK;
01343 }
01344 
01345 /*
01346  *----------------------------------------------------------------------
01347  *
01348  * GetOwnerAttribute
01349  *
01350  *      Gets the owner attribute of a file.
01351  *
01352  * Results:
01353  *      Standard TCL result. Returns a new Tcl_Obj in attributePtrPtr if there
01354  *      is no error.
01355  *
01356  * Side effects:
01357  *      A new object is allocated.
01358  *
01359  *----------------------------------------------------------------------
01360  */
01361 
01362 static int
01363 GetOwnerAttribute(
01364     Tcl_Interp *interp,         /* The interp we are using for errors. */
01365     int objIndex,               /* The index of the attribute. */
01366     Tcl_Obj *fileName,          /* The name of the file (UTF-8). */
01367     Tcl_Obj **attributePtrPtr)  /* A pointer to return the object with. */
01368 {
01369     Tcl_StatBuf statBuf;
01370     struct passwd *pwPtr;
01371     int result;
01372 
01373     result = TclpObjStat(fileName, &statBuf);
01374 
01375     if (result != 0) {
01376         if (interp != NULL) {
01377             Tcl_AppendResult(interp, "could not read \"",
01378                     TclGetString(fileName), "\": ",
01379                     Tcl_PosixError(interp), NULL);
01380         }
01381         return TCL_ERROR;
01382     }
01383 
01384     pwPtr = TclpGetPwUid(statBuf.st_uid);
01385 
01386     if (pwPtr == NULL) {
01387         *attributePtrPtr = Tcl_NewIntObj((int) statBuf.st_uid);
01388     } else {
01389         Tcl_DString ds;
01390         CONST char *utf;
01391 
01392         utf = Tcl_ExternalToUtfDString(NULL, pwPtr->pw_name, -1, &ds);
01393         *attributePtrPtr = Tcl_NewStringObj(utf, Tcl_DStringLength(&ds));
01394         Tcl_DStringFree(&ds);
01395     }
01396     endpwent();
01397     return TCL_OK;
01398 }
01399 
01400 /*
01401  *----------------------------------------------------------------------
01402  *
01403  * GetPermissionsAttribute
01404  *
01405  *      Gets the group attribute of a file.
01406  *
01407  * Results:
01408  *      Standard TCL result. Returns a new Tcl_Obj in attributePtrPtr if there
01409  *      is no error. The object will have ref count 0.
01410  *
01411  * Side effects:
01412  *      A new object is allocated.
01413  *
01414  *----------------------------------------------------------------------
01415  */
01416 
01417 static int
01418 GetPermissionsAttribute(
01419     Tcl_Interp *interp,             /* The interp we are using for errors. */
01420     int objIndex,                   /* The index of the attribute. */
01421     Tcl_Obj *fileName,              /* The name of the file (UTF-8). */
01422     Tcl_Obj **attributePtrPtr)      /* A pointer to return the object with. */
01423 {
01424     Tcl_StatBuf statBuf;
01425     int result;
01426 
01427     result = TclpObjStat(fileName, &statBuf);
01428 
01429     if (result != 0) {
01430         if (interp != NULL) {
01431             Tcl_AppendResult(interp, "could not read \"",
01432                     TclGetString(fileName), "\": ",
01433                     Tcl_PosixError(interp), NULL);
01434         }
01435         return TCL_ERROR;
01436     }
01437 
01438     *attributePtrPtr = Tcl_ObjPrintf(
01439             "%0#5lo", (long) (statBuf.st_mode & 0x00007FFF));
01440     return TCL_OK;
01441 }
01442 
01443 /*
01444  *---------------------------------------------------------------------------
01445  *
01446  * SetGroupAttribute --
01447  *
01448  *      Sets the group of the file to the specified group.
01449  *
01450  * Results:
01451  *      Standard TCL result.
01452  *
01453  * Side effects:
01454  *      As above.
01455  *
01456  *---------------------------------------------------------------------------
01457  */
01458 
01459 static int
01460 SetGroupAttribute(
01461     Tcl_Interp *interp,         /* The interp for error reporting. */
01462     int objIndex,               /* The index of the attribute. */
01463     Tcl_Obj *fileName,          /* The name of the file (UTF-8). */
01464     Tcl_Obj *attributePtr)      /* New group for file. */
01465 {
01466     long gid;
01467     int result;
01468     CONST char *native;
01469 
01470     if (Tcl_GetLongFromObj(NULL, attributePtr, &gid) != TCL_OK) {
01471         Tcl_DString ds;
01472         struct group *groupPtr = NULL;
01473         CONST char *string;
01474         int length;
01475 
01476         string = Tcl_GetStringFromObj(attributePtr, &length);
01477 
01478         native = Tcl_UtfToExternalDString(NULL, string, length, &ds);
01479         groupPtr = TclpGetGrNam(native); /* INTL: Native. */
01480         Tcl_DStringFree(&ds);
01481 
01482         if (groupPtr == NULL) {
01483             endgrent();
01484             if (interp != NULL) {
01485                 Tcl_AppendResult(interp, "could not set group for file \"",
01486                         TclGetString(fileName), "\": group \"", string,
01487                         "\" does not exist", NULL);
01488             }
01489             return TCL_ERROR;
01490         }
01491         gid = groupPtr->gr_gid;
01492     }
01493 
01494     native = Tcl_FSGetNativePath(fileName);
01495     result = chown(native, (uid_t) -1, (gid_t) gid);    /* INTL: Native. */
01496 
01497     endgrent();
01498     if (result != 0) {
01499         if (interp != NULL) {
01500             Tcl_AppendResult(interp, "could not set group for file \"",
01501                     TclGetString(fileName), "\": ", Tcl_PosixError(interp),
01502                     NULL);
01503         }
01504         return TCL_ERROR;
01505     }
01506     return TCL_OK;
01507 }
01508 
01509 /*
01510  *---------------------------------------------------------------------------
01511  *
01512  * SetOwnerAttribute --
01513  *
01514  *      Sets the owner of the file to the specified owner.
01515  *
01516  * Results:
01517  *      Standard TCL result.
01518  *
01519  * Side effects:
01520  *      As above.
01521  *
01522  *---------------------------------------------------------------------------
01523  */
01524 
01525 static int
01526 SetOwnerAttribute(
01527     Tcl_Interp *interp,         /* The interp for error reporting. */
01528     int objIndex,               /* The index of the attribute. */
01529     Tcl_Obj *fileName,          /* The name of the file (UTF-8). */
01530     Tcl_Obj *attributePtr)      /* New owner for file. */
01531 {
01532     long uid;
01533     int result;
01534     CONST char *native;
01535 
01536     if (Tcl_GetLongFromObj(NULL, attributePtr, &uid) != TCL_OK) {
01537         Tcl_DString ds;
01538         struct passwd *pwPtr = NULL;
01539         CONST char *string;
01540         int length;
01541 
01542         string = Tcl_GetStringFromObj(attributePtr, &length);
01543 
01544         native = Tcl_UtfToExternalDString(NULL, string, length, &ds);
01545         pwPtr = TclpGetPwNam(native); /* INTL: Native. */
01546         Tcl_DStringFree(&ds);
01547 
01548         if (pwPtr == NULL) {
01549             if (interp != NULL) {
01550                 Tcl_AppendResult(interp, "could not set owner for file \"",
01551                         TclGetString(fileName), "\": user \"", string,
01552                         "\" does not exist", NULL);
01553             }
01554             return TCL_ERROR;
01555         }
01556         uid = pwPtr->pw_uid;
01557     }
01558 
01559     native = Tcl_FSGetNativePath(fileName);
01560     result = chown(native, (uid_t) uid, (gid_t) -1);    /* INTL: Native. */
01561 
01562     if (result != 0) {
01563         if (interp != NULL) {
01564             Tcl_AppendResult(interp, "could not set owner for file \"",
01565                     TclGetString(fileName), "\": ", Tcl_PosixError(interp),
01566                     NULL);
01567         }
01568         return TCL_ERROR;
01569     }
01570     return TCL_OK;
01571 }
01572 
01573 /*
01574  *---------------------------------------------------------------------------
01575  *
01576  * SetPermissionsAttribute
01577  *
01578  *      Sets the file to the given permission.
01579  *
01580  * Results:
01581  *      Standard TCL result.
01582  *
01583  * Side effects:
01584  *      The permission of the file is changed.
01585  *
01586  *---------------------------------------------------------------------------
01587  */
01588 
01589 static int
01590 SetPermissionsAttribute(
01591     Tcl_Interp *interp,         /* The interp we are using for errors. */
01592     int objIndex,               /* The index of the attribute. */
01593     Tcl_Obj *fileName,          /* The name of the file (UTF-8). */
01594     Tcl_Obj *attributePtr)      /* The attribute to set. */
01595 {
01596     long mode;
01597     mode_t newMode;
01598     int result = TCL_ERROR;
01599     CONST char *native;
01600     char *modeStringPtr = TclGetString(attributePtr);
01601     int scanned = TclParseAllWhiteSpace(modeStringPtr, -1);
01602 
01603     /*
01604      * First supply support for octal number format
01605      */
01606 
01607     if ((modeStringPtr[scanned] == '0')
01608             && (modeStringPtr[scanned+1] >= '0')
01609             && (modeStringPtr[scanned+1] <= '7')) {
01610         /* Leading zero - attempt octal interpretation */
01611         Tcl_Obj *modeObj;
01612 
01613         TclNewLiteralStringObj(modeObj, "0o");
01614         Tcl_AppendToObj(modeObj, modeStringPtr+scanned+1, -1);
01615         result = Tcl_GetLongFromObj(NULL, modeObj, &mode);
01616         Tcl_DecrRefCount(modeObj);
01617     }
01618     if (result == TCL_OK
01619             || Tcl_GetLongFromObj(NULL, attributePtr, &mode) == TCL_OK) {
01620         newMode = (mode_t) (mode & 0x00007FFF);
01621     } else {
01622         Tcl_StatBuf buf;
01623 
01624         /*
01625          * Try the forms "rwxrwxrwx" and "ugo=rwx"
01626          *
01627          * We get the current mode of the file, in order to allow for ug+-=rwx
01628          * style chmod strings.
01629          */
01630 
01631         result = TclpObjStat(fileName, &buf);
01632         if (result != 0) {
01633             if (interp != NULL) {
01634                 Tcl_AppendResult(interp, "could not read \"",
01635                         TclGetString(fileName), "\": ",
01636                         Tcl_PosixError(interp), NULL);
01637             }
01638             return TCL_ERROR;
01639         }
01640         newMode = (mode_t) (buf.st_mode & 0x00007FFF);
01641 
01642         if (GetModeFromPermString(NULL, modeStringPtr, &newMode) != TCL_OK) {
01643             if (interp != NULL) {
01644                 Tcl_AppendResult(interp, "unknown permission string format \"",
01645                         modeStringPtr, "\"", NULL);
01646             }
01647             return TCL_ERROR;
01648         }
01649     }
01650 
01651     native = Tcl_FSGetNativePath(fileName);
01652     result = chmod(native, newMode);            /* INTL: Native. */
01653     if (result != 0) {
01654         if (interp != NULL) {
01655             Tcl_AppendResult(interp, "could not set permissions for file \"",
01656                     TclGetString(fileName), "\": ",
01657                     Tcl_PosixError(interp), NULL);
01658         }
01659         return TCL_ERROR;
01660     }
01661     return TCL_OK;
01662 }
01663 
01664 #ifndef DJGPP
01665 /*
01666  *---------------------------------------------------------------------------
01667  *
01668  * TclpObjListVolumes --
01669  *
01670  *      Lists the currently mounted volumes, which on UNIX is just /.
01671  *
01672  * Results:
01673  *      The list of volumes.
01674  *
01675  * Side effects:
01676  *      None.
01677  *
01678  *---------------------------------------------------------------------------
01679  */
01680 
01681 Tcl_Obj *
01682 TclpObjListVolumes(void)
01683 {
01684     Tcl_Obj *resultPtr = Tcl_NewStringObj("/", 1);
01685 
01686     Tcl_IncrRefCount(resultPtr);
01687     return resultPtr;
01688 }
01689 #endif
01690 
01691 /*
01692  *----------------------------------------------------------------------
01693  *
01694  * GetModeFromPermString --
01695  *
01696  *      This procedure is invoked to process the "file permissions" Tcl
01697  *      command, to check for a "rwxrwxrwx" or "ugoa+-=rwxst" string. See the
01698  *      user documentation for details on what it does.
01699  *
01700  * Results:
01701  *      A standard Tcl result.
01702  *
01703  * Side effects:
01704  *      See the user documentation.
01705  *
01706  *----------------------------------------------------------------------
01707  */
01708 
01709 static int
01710 GetModeFromPermString(
01711     Tcl_Interp *interp,         /* The interp we are using for errors. */
01712     char *modeStringPtr,        /* Permissions string */
01713     mode_t *modePtr)            /* pointer to the mode value */
01714 {
01715     mode_t newMode;
01716     mode_t oldMode;             /* Storage for the value of the old mode (that
01717                                  * is passed in), to allow for the chmod style
01718                                  * manipulation. */
01719     int i,n, who, op, what, op_found, who_found;
01720 
01721     /*
01722      * We start off checking for an "rwxrwxrwx" style permissions string
01723      */
01724 
01725     if (strlen(modeStringPtr) != 9) {
01726         goto chmodStyleCheck;
01727     }
01728 
01729     newMode = 0;
01730     for (i = 0; i < 9; i++) {
01731         switch (*(modeStringPtr+i)) {
01732         case 'r':
01733             if ((i%3) != 0) {
01734                 goto chmodStyleCheck;
01735             }
01736             newMode |= (1<<(8-i));
01737             break;
01738         case 'w':
01739             if ((i%3) != 1) {
01740                 goto chmodStyleCheck;
01741             }
01742             newMode |= (1<<(8-i));
01743             break;
01744         case 'x':
01745             if ((i%3) != 2) {
01746                 goto chmodStyleCheck;
01747             }
01748             newMode |= (1<<(8-i));
01749             break;
01750         case 's':
01751             if (((i%3) != 2) || (i > 5)) {
01752                 goto chmodStyleCheck;
01753             }
01754             newMode |= (1<<(8-i));
01755             newMode |= (1<<(11-(i/3)));
01756             break;
01757         case 'S':
01758             if (((i%3) != 2) || (i > 5)) {
01759                 goto chmodStyleCheck;
01760             }
01761             newMode |= (1<<(11-(i/3)));
01762             break;
01763         case 't':
01764             if (i != 8) {
01765                 goto chmodStyleCheck;
01766             }
01767             newMode |= (1<<(8-i));
01768             newMode |= (1<<9);
01769             break;
01770         case 'T':
01771             if (i != 8) {
01772                 goto chmodStyleCheck;
01773             }
01774             newMode |= (1<<9);
01775             break;
01776         case '-':
01777             break;
01778         default:
01779             /*
01780              * Oops, not what we thought it was, so go on
01781              */
01782             goto chmodStyleCheck;
01783         }
01784     }
01785     *modePtr = newMode;
01786     return TCL_OK;
01787 
01788   chmodStyleCheck:
01789     /*
01790      * We now check for an "ugoa+-=rwxst" style permissions string
01791      */
01792 
01793     for (n = 0 ; *(modeStringPtr+n) != '\0' ; n = n + i) {
01794         oldMode = *modePtr;
01795         who = op = what = op_found = who_found = 0;
01796         for (i = 0 ; *(modeStringPtr+n+i) != '\0' ; i++ ) {
01797             if (!who_found) {
01798                 /* who */
01799                 switch (*(modeStringPtr+n+i)) {
01800                 case 'u':
01801                     who |= 0x9c0;
01802                     continue;
01803                 case 'g':
01804                     who |= 0x438;
01805                     continue;
01806                 case 'o':
01807                     who |= 0x207;
01808                     continue;
01809                 case 'a':
01810                     who |= 0xfff;
01811                     continue;
01812                 }
01813             }
01814             who_found = 1;
01815             if (who == 0) {
01816                 who = 0xfff;
01817             }
01818             if (!op_found) {
01819                 /* op */
01820                 switch (*(modeStringPtr+n+i)) {
01821                 case '+':
01822                     op = 1;
01823                     op_found = 1;
01824                     continue;
01825                 case '-':
01826                     op = 2;
01827                     op_found = 1;
01828                     continue;
01829                 case '=':
01830                     op = 3;
01831                     op_found = 1;
01832                     continue;
01833                 default:
01834                     return TCL_ERROR;
01835                 }
01836             }
01837             /* what */
01838             switch (*(modeStringPtr+n+i)) {
01839             case 'r':
01840                 what |= 0x124;
01841                 continue;
01842             case 'w':
01843                 what |= 0x92;
01844                 continue;
01845             case 'x':
01846                 what |= 0x49;
01847                 continue;
01848             case 's':
01849                 what |= 0xc00;
01850                 continue;
01851             case 't':
01852                 what |= 0x200;
01853                 continue;
01854             case ',':
01855                 break;
01856             default:
01857                 return TCL_ERROR;
01858             }
01859             if (*(modeStringPtr+n+i) == ',') {
01860                 i++;
01861                 break;
01862             }
01863         }
01864         switch (op) {
01865         case 1:
01866             *modePtr = oldMode | (who & what);
01867             continue;
01868         case 2:
01869             *modePtr = oldMode & ~(who & what);
01870             continue;
01871         case 3:
01872             *modePtr = (oldMode & ~who) | (who & what);
01873             continue;
01874         }
01875     }
01876     return TCL_OK;
01877 }
01878 
01879 /*
01880  *---------------------------------------------------------------------------
01881  *
01882  * TclpObjNormalizePath --
01883  *
01884  *      This function scans through a path specification and replaces it, in
01885  *      place, with a normalized version. A normalized version is one in which
01886  *      all symlinks in the path are replaced with their expanded form (except
01887  *      a symlink at the very end of the path).
01888  *
01889  * Results:
01890  *      The new 'nextCheckpoint' value, giving as far as we could understand
01891  *      in the path.
01892  *
01893  * Side effects:
01894  *      The pathPtr string, is modified.
01895  *
01896  *---------------------------------------------------------------------------
01897  */
01898 
01899 int
01900 TclpObjNormalizePath(
01901     Tcl_Interp *interp,
01902     Tcl_Obj *pathPtr,
01903     int nextCheckpoint)
01904 {
01905     char *currentPathEndPosition;
01906     int pathLen;
01907     char cur;
01908     char *path = Tcl_GetStringFromObj(pathPtr, &pathLen);
01909 #ifndef NO_REALPATH
01910     char normPath[MAXPATHLEN];
01911     Tcl_DString ds;
01912     CONST char *nativePath;
01913 #endif
01914 
01915     /*
01916      * We add '1' here because if nextCheckpoint is zero we know that '/'
01917      * exists, and if it isn't zero, it must point at a directory separator
01918      * which we also know exists.
01919      */
01920 
01921     currentPathEndPosition = path + nextCheckpoint;
01922     if (*currentPathEndPosition == '/') {
01923         currentPathEndPosition++;
01924     }
01925 
01926 #ifndef NO_REALPATH
01927     /*
01928      * For speed, try to get the entire path in one go.
01929      */
01930 
01931     if (nextCheckpoint == 0 && haveRealpath) {
01932         char *lastDir = strrchr(currentPathEndPosition, '/');
01933 
01934         if (lastDir != NULL) {
01935             nativePath = Tcl_UtfToExternalDString(NULL, path,
01936                     lastDir-path, &ds);
01937             if (Realpath(nativePath, normPath) != NULL) {
01938                 if (*nativePath != '/' && *normPath == '/') {
01939                     /*
01940                      * realpath has transformed a relative path into an
01941                      * absolute path, we do not know how to handle this.
01942                      */
01943                 } else {
01944                     nextCheckpoint = lastDir - path;
01945                     goto wholeStringOk;
01946                 }
01947             }
01948             Tcl_DStringFree(&ds);
01949         }
01950     }
01951 
01952     /*
01953      * Else do it the slow way.
01954      */
01955 #endif
01956 
01957     while (1) {
01958         cur = *currentPathEndPosition;
01959         if ((cur == '/') && (path != currentPathEndPosition)) {
01960             /*
01961              * Reached directory separator.
01962              */
01963 
01964             Tcl_DString ds;
01965             CONST char *nativePath;
01966             int accessOk;
01967 
01968             nativePath = Tcl_UtfToExternalDString(NULL, path,
01969                     currentPathEndPosition - path, &ds);
01970             accessOk = access(nativePath, F_OK);
01971             Tcl_DStringFree(&ds);
01972 
01973             if (accessOk != 0) {
01974                 /*
01975                  * File doesn't exist.
01976                  */
01977 
01978                 break;
01979             }
01980 
01981             /*
01982              * Update the acceptable point.
01983              */
01984 
01985             nextCheckpoint = currentPathEndPosition - path;
01986         } else if (cur == 0) {
01987             /*
01988              * Reached end of string.
01989              */
01990 
01991             break;
01992         }
01993         currentPathEndPosition++;
01994     }
01995 
01996     /*
01997      * We should really now convert this to a canonical path. We do that with
01998      * 'realpath' if we have it available. Otherwise we could step through
01999      * every single path component, checking whether it is a symlink, but that
02000      * would be a lot of work, and most modern OSes have 'realpath'.
02001      */
02002 
02003 #ifndef NO_REALPATH
02004     if (haveRealpath) {
02005         /*
02006          * If we only had '/foo' or '/' then we never increment nextCheckpoint
02007          * and we don't need or want to go through 'Realpath'. Also, on some
02008          * platforms, passing an empty string to 'Realpath' will give us the
02009          * normalized pwd, which is not what we want at all!
02010          */
02011 
02012         if (nextCheckpoint == 0) {
02013             return 0;
02014         }
02015 
02016         nativePath = Tcl_UtfToExternalDString(NULL, path, nextCheckpoint, &ds);
02017         if (Realpath(nativePath, normPath) != NULL) {
02018             int newNormLen;
02019 
02020         wholeStringOk:
02021             newNormLen = strlen(normPath);
02022             if ((newNormLen == Tcl_DStringLength(&ds))
02023                     && (strcmp(normPath, nativePath) == 0)) {
02024                 /*
02025                  * String is unchanged.
02026                  */
02027 
02028                 Tcl_DStringFree(&ds);
02029 
02030                 /*
02031                  * Enable this to have the native FS claim normalization of
02032                  * the whole path for existing files. That would permit the
02033                  * caller to declare normalization complete without calls to
02034                  * additional filesystems. Saving lots of calls is probably
02035                  * worth the extra access() time here. When no other FS's are
02036                  * registered though, things are less clear.
02037                  *
02038                 if (0 == access(normPath, F_OK)) {
02039                     return pathLen;
02040                 }
02041                  */
02042 
02043                 return nextCheckpoint;
02044             }
02045 
02046             /*
02047              * Free up the native path and put in its place the converted,
02048              * normalized path.
02049              */
02050 
02051             Tcl_DStringFree(&ds);
02052             Tcl_ExternalToUtfDString(NULL, normPath, (int) newNormLen, &ds);
02053 
02054             if (path[nextCheckpoint] != '\0') {
02055                 /*
02056                  * Not at end, append remaining path.
02057                  */
02058 
02059                 int normLen = Tcl_DStringLength(&ds);
02060 
02061                 Tcl_DStringAppend(&ds, path + nextCheckpoint,
02062                         pathLen - nextCheckpoint);
02063 
02064                 /*
02065                  * We recognise up to and including the directory separator.
02066                  */
02067 
02068                 nextCheckpoint = normLen + 1;
02069             } else {
02070                 /*
02071                  * We recognise the whole string.
02072                  */
02073 
02074                 nextCheckpoint = Tcl_DStringLength(&ds);
02075             }
02076 
02077             /*
02078              * Overwrite with the normalized path.
02079              */
02080 
02081             Tcl_SetStringObj(pathPtr, Tcl_DStringValue(&ds),
02082                     Tcl_DStringLength(&ds));
02083         }
02084         Tcl_DStringFree(&ds);
02085     }
02086 #endif  /* !NO_REALPATH */
02087 
02088     return nextCheckpoint;
02089 }
02090 
02091 #if defined(HAVE_CHFLAGS) && defined(UF_IMMUTABLE)
02092 /*
02093  *----------------------------------------------------------------------
02094  *
02095  * GetReadOnlyAttribute
02096  *
02097  *      Gets the readonly attribute (user immutable flag) of a file.
02098  *
02099  * Results:
02100  *      Standard TCL result. Returns a new Tcl_Obj in attributePtrPtr if there
02101  *      is no error. The object will have ref count 0.
02102  *
02103  * Side effects:
02104  *      A new object is allocated.
02105  *
02106  *----------------------------------------------------------------------
02107  */
02108 
02109 static int
02110 GetReadOnlyAttribute(
02111     Tcl_Interp *interp,         /* The interp we are using for errors. */
02112     int objIndex,               /* The index of the attribute. */
02113     Tcl_Obj *fileName,          /* The name of the file (UTF-8). */
02114     Tcl_Obj **attributePtrPtr)  /* A pointer to return the object with. */
02115 {
02116     Tcl_StatBuf statBuf;
02117     int result;
02118 
02119     result = TclpObjStat(fileName, &statBuf);
02120 
02121     if (result != 0) {
02122         if (interp != NULL) {
02123             Tcl_AppendResult(interp, "could not read \"",
02124                     TclGetString(fileName), "\": ", Tcl_PosixError(interp),
02125                     NULL);
02126         }
02127         return TCL_ERROR;
02128     }
02129 
02130     *attributePtrPtr = Tcl_NewBooleanObj((statBuf.st_flags&UF_IMMUTABLE) != 0);
02131 
02132     return TCL_OK;
02133 }
02134 
02135 /*
02136  *---------------------------------------------------------------------------
02137  *
02138  * SetReadOnlyAttribute
02139  *
02140  *      Sets the readonly attribute (user immutable flag) of a file.
02141  *
02142  * Results:
02143  *      Standard TCL result.
02144  *
02145  * Side effects:
02146  *      The readonly attribute of the file is changed.
02147  *
02148  *---------------------------------------------------------------------------
02149  */
02150 
02151 static int
02152 SetReadOnlyAttribute(
02153     Tcl_Interp *interp,         /* The interp we are using for errors. */
02154     int objIndex,               /* The index of the attribute. */
02155     Tcl_Obj *fileName,          /* The name of the file (UTF-8). */
02156     Tcl_Obj *attributePtr)      /* The attribute to set. */
02157 {
02158     Tcl_StatBuf statBuf;
02159     int result;
02160     int readonly;
02161     CONST char *native;
02162 
02163     if (Tcl_GetBooleanFromObj(interp, attributePtr, &readonly) != TCL_OK) {
02164         return TCL_ERROR;
02165     }
02166 
02167     result = TclpObjStat(fileName, &statBuf);
02168 
02169     if (result != 0) {
02170         if (interp != NULL) {
02171             Tcl_AppendResult(interp, "could not read \"",
02172                     TclGetString(fileName), "\": ", Tcl_PosixError(interp),
02173                     NULL);
02174         }
02175         return TCL_ERROR;
02176     }
02177 
02178     if (readonly) {
02179         statBuf.st_flags |= UF_IMMUTABLE;
02180     } else {
02181         statBuf.st_flags &= ~UF_IMMUTABLE;
02182     }
02183 
02184     native = Tcl_FSGetNativePath(fileName);
02185     result = chflags(native, statBuf.st_flags);         /* INTL: Native. */
02186     if (result != 0) {
02187         if (interp != NULL) {
02188             Tcl_AppendResult(interp, "could not set flags for file \"",
02189                     TclGetString(fileName), "\": ", Tcl_PosixError(interp),
02190                     NULL);
02191         }
02192         return TCL_ERROR;
02193     }
02194     return TCL_OK;
02195 }
02196 #endif /* defined(HAVE_CHFLAGS) && defined(UF_IMMUTABLE) */
02197 
02198 /*
02199  * Local Variables:
02200  * mode: c
02201  * c-basic-offset: 4
02202  * fill-column: 78
02203  * End:
02204  */



Generated on Wed Mar 12 12:18:26 2008 by  doxygen 1.5.1