os/persistentdata/persistentstorage/sqlite3api/TEST/TCL/tcldistribution/mac/tclMacFCmd.c
author sl
Tue, 10 Jun 2014 14:32:02 +0200
changeset 1 260cb5ec6c19
permissions -rw-r--r--
Update contrib.
     1 /* 
     2  * tclMacFCmd.c --
     3  *
     4  * Implements the Macintosh specific portions of the file manipulation
     5  * subcommands of the "file" command.
     6  *
     7  * Copyright (c) 1996-1998 Sun Microsystems, Inc.
     8  *
     9  * See the file "license.terms" for information on usage and redistribution
    10  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
    11  *
    12  * RCS: @(#) $Id: tclMacFCmd.c,v 1.19 2003/02/04 17:06:51 vincentdarley Exp $
    13  */
    14 
    15 #include "tclInt.h"
    16 #include "tclMac.h"
    17 #include "tclMacInt.h"
    18 #include "tclPort.h"
    19 #include <FSpCompat.h>
    20 #include <MoreFilesExtras.h>
    21 #include <Strings.h>
    22 #include <Errors.h>
    23 #include <FileCopy.h>
    24 #include <DirectoryCopy.h>
    25 #include <Script.h>
    26 #include <string.h>
    27 #include <Finder.h>
    28 #include <Aliases.h>
    29 
    30 /*
    31  * Callback for the file attributes code.
    32  */
    33 
    34 static int		GetFileFinderAttributes _ANSI_ARGS_((Tcl_Interp *interp,
    35 			    int objIndex, Tcl_Obj *fileName,
    36 			    Tcl_Obj **attributePtrPtr));
    37 static int		GetFileReadOnly _ANSI_ARGS_((Tcl_Interp *interp,
    38 			    int objIndex, Tcl_Obj *fileName,
    39 			    Tcl_Obj **readOnlyPtrPtr));
    40 static int		SetFileFinderAttributes _ANSI_ARGS_((Tcl_Interp *interp,
    41 			    int objIndex, Tcl_Obj *fileName,
    42 			    Tcl_Obj *attributePtr));
    43 static int		SetFileReadOnly _ANSI_ARGS_((Tcl_Interp *interp,
    44 			    int objIndex, Tcl_Obj *fileName,
    45 			    Tcl_Obj *readOnlyPtr));
    46 
    47 /*
    48  * These are indeces into the tclpFileAttrsStrings table below.
    49  */
    50 
    51 #define MAC_CREATOR_ATTRIBUTE	0
    52 #define MAC_HIDDEN_ATTRIBUTE	1
    53 #define MAC_READONLY_ATTRIBUTE	2
    54 #define MAC_TYPE_ATTRIBUTE	3
    55 
    56 /*
    57  * Global variables for the file attributes code.
    58  */
    59 
    60 CONST char *tclpFileAttrStrings[] = {"-creator", "-hidden", "-readonly",
    61 	"-type", (char *) NULL};
    62 CONST TclFileAttrProcs tclpFileAttrProcs[] = {
    63 	{GetFileFinderAttributes, SetFileFinderAttributes},
    64 	{GetFileFinderAttributes, SetFileFinderAttributes},
    65 	{GetFileReadOnly, SetFileReadOnly},
    66 	{GetFileFinderAttributes, SetFileFinderAttributes}};
    67 
    68 /*
    69  * File specific static data
    70  */
    71 
    72 static long startSeed = 248923489;
    73 
    74 /*
    75  * Prototypes for procedure only used in this file
    76  */
    77 
    78 static pascal Boolean 	CopyErrHandler _ANSI_ARGS_((OSErr error, 
    79 			    short failedOperation,
    80 			    short srcVRefNum, long srcDirID,
    81 			    ConstStr255Param srcName, short dstVRefNum,
    82 			    long dstDirID,ConstStr255Param dstName));
    83 static int		DoCopyDirectory _ANSI_ARGS_((CONST char *src,
    84 			    CONST char *dst, Tcl_DString *errorPtr));
    85 static int		DoCopyFile _ANSI_ARGS_((CONST char *src, 
    86 			    CONST char *dst));
    87 static int		DoCreateDirectory _ANSI_ARGS_((CONST char *path));
    88 static int		DoRemoveDirectory _ANSI_ARGS_((CONST char *path, 
    89 			    int recursive, Tcl_DString *errorPtr));
    90 static int		DoRenameFile _ANSI_ARGS_((CONST char *src,
    91 			    CONST char *dst));
    92 OSErr			FSpGetFLockCompat _ANSI_ARGS_((const FSSpec *specPtr, 
    93 			    Boolean *lockedPtr));
    94 static OSErr		GetFileSpecs _ANSI_ARGS_((CONST char *path, 
    95 			    FSSpec *pathSpecPtr, FSSpec *dirSpecPtr,	
    96 			    Boolean *pathExistsPtr, 
    97 			    Boolean *pathIsDirectoryPtr));
    98 static OSErr		MoveRename _ANSI_ARGS_((const FSSpec *srcSpecPtr, 
    99 			    const FSSpec *dstSpecPtr, StringPtr copyName));
   100 static int		Pstrequal _ANSI_ARGS_((ConstStr255Param stringA, 
   101 			    ConstStr255Param stringB));
   102                  
   103 /*
   104  *---------------------------------------------------------------------------
   105  *
   106  * TclpObjRenameFile, DoRenameFile --
   107  *
   108  *      Changes the name of an existing file or directory, from src to dst.
   109  *	If src and dst refer to the same file or directory, does nothing
   110  *	and returns success.  Otherwise if dst already exists, it will be
   111  *	deleted and replaced by src subject to the following conditions:
   112  *	    If src is a directory, dst may be an empty directory.
   113  *	    If src is a file, dst may be a file.
   114  *	In any other situation where dst already exists, the rename will
   115  *	fail.  
   116  *
   117  * Results:
   118  *	If the directory was successfully created, returns TCL_OK.
   119  *	Otherwise the return value is TCL_ERROR and errno is set to
   120  *	indicate the error.  Some possible values for errno are:
   121  *
   122  *	EACCES:     src or dst parent directory can't be read and/or written.
   123  *	EEXIST:	    dst is a non-empty directory.
   124  *	EINVAL:	    src is a root directory or dst is a subdirectory of src.
   125  *	EISDIR:	    dst is a directory, but src is not.
   126  *	ENOENT:	    src doesn't exist.  src or dst is "".
   127  *	ENOTDIR:    src is a directory, but dst is not.  
   128  *	EXDEV:	    src and dst are on different filesystems.
   129  *	
   130  * Side effects:
   131  *	The implementation of rename may allow cross-filesystem renames,
   132  *	but the caller should be prepared to emulate it with copy and
   133  *	delete if errno is EXDEV.
   134  *
   135  *---------------------------------------------------------------------------
   136  */
   137 
   138 int 
   139 TclpObjRenameFile(srcPathPtr, destPathPtr)
   140     Tcl_Obj *srcPathPtr;
   141     Tcl_Obj *destPathPtr;
   142 {
   143     return DoRenameFile(Tcl_FSGetNativePath(srcPathPtr),
   144 			Tcl_FSGetNativePath(destPathPtr));
   145 }
   146 
   147 static int
   148 DoRenameFile(
   149     CONST char *src,		/* Pathname of file or dir to be renamed
   150 				 * (native). */
   151     CONST char *dst)		/* New pathname of file or directory
   152 				 * (native). */
   153 {
   154     FSSpec srcFileSpec, dstFileSpec, dstDirSpec;
   155     OSErr err; 
   156     long srcID, dummy;
   157     Boolean srcIsDirectory, dstIsDirectory, dstExists, dstLocked;
   158 
   159     err = FSpLLocationFromPath(strlen(src), src, &srcFileSpec);
   160     if (err == noErr) {
   161 	FSpGetDirectoryID(&srcFileSpec, &srcID, &srcIsDirectory);
   162     }
   163     if (err == noErr) {
   164         err = GetFileSpecs(dst, &dstFileSpec, &dstDirSpec, &dstExists, 
   165         	&dstIsDirectory);
   166     }
   167     if (err == noErr) {
   168 	if (dstExists == 0) {
   169             err = MoveRename(&srcFileSpec, &dstDirSpec, dstFileSpec.name);
   170             goto end;
   171         }
   172         err = FSpGetFLockCompat(&dstFileSpec, &dstLocked);
   173         if (dstLocked) {
   174             FSpRstFLockCompat(&dstFileSpec);
   175         }
   176     }
   177     if (err == noErr) {
   178         if (srcIsDirectory) {
   179 	    if (dstIsDirectory) {
   180 		/*
   181 		 * The following call will remove an empty directory.  If it
   182 		 * fails, it's because it wasn't empty.
   183 		 */
   184 		 
   185                 if (DoRemoveDirectory(dst, 0, NULL) != TCL_OK) {
   186                     return TCL_ERROR;
   187                 }
   188                 
   189                 /*
   190 		 * Now that that empty directory is gone, we can try
   191 		 * renaming src.  If that fails, we'll put this empty
   192 		 * directory back, for completeness.
   193 		 */
   194 
   195 		err = MoveRename(&srcFileSpec, &dstDirSpec, dstFileSpec.name);
   196                 if (err != noErr) {
   197 		    FSpDirCreateCompat(&dstFileSpec, smSystemScript, &dummy);
   198 		    if (dstLocked) {
   199 		        FSpSetFLockCompat(&dstFileSpec);
   200 		    }
   201 		}
   202 	    } else {
   203 	        errno = ENOTDIR;
   204 	        return TCL_ERROR;
   205 	    }
   206 	} else {   
   207 	    if (dstIsDirectory) {
   208 		errno = EISDIR;
   209 		return TCL_ERROR;
   210 	    } else {                                
   211 		/*
   212 		 * Overwrite existing file by:
   213 		 * 
   214 		 * 1. Rename existing file to temp name.
   215 		 * 2. Rename old file to new name.
   216 		 * 3. If success, delete temp file.  If failure,
   217 		 *    put temp file back to old name.
   218 		 */
   219 
   220 	        Str31 tmpName;
   221 	        FSSpec tmpFileSpec;
   222 
   223 	        err = GenerateUniqueName(dstFileSpec.vRefNum, &startSeed,
   224 	        	dstFileSpec.parID, dstFileSpec.parID, tmpName);
   225 	        if (err == noErr) {
   226 	            err = FSpRenameCompat(&dstFileSpec, tmpName);
   227 	        }
   228 	        if (err == noErr) {
   229 	            err = FSMakeFSSpecCompat(dstFileSpec.vRefNum,
   230 	            	    dstFileSpec.parID, tmpName, &tmpFileSpec);
   231 	        }
   232 	        if (err == noErr) {
   233 	            err = MoveRename(&srcFileSpec, &dstDirSpec, 
   234 	            	    dstFileSpec.name);
   235 	        }
   236 	        if (err == noErr) {
   237 		    FSpDeleteCompat(&tmpFileSpec);
   238 		} else {
   239 		    FSpDeleteCompat(&dstFileSpec);
   240 		    FSpRenameCompat(&tmpFileSpec, dstFileSpec.name);
   241 	            if (dstLocked) {
   242 	            	FSpSetFLockCompat(&dstFileSpec);
   243 	            }
   244 	        }
   245 	    }
   246    	}
   247     }    
   248 
   249     end:    
   250     if (err != noErr) {
   251 	errno = TclMacOSErrorToPosixError(err);
   252 	return TCL_ERROR;
   253     }
   254     return TCL_OK;
   255 }
   256 
   257 /*
   258  *--------------------------------------------------------------------------
   259  *
   260  * MoveRename --
   261  *
   262  *	Helper function for TclpRenameFile.  Renames a file or directory
   263  *	into the same directory or another directory.  The target name
   264  * 	must not already exist in the destination directory.
   265  *
   266  *	Don't use FSpMoveRenameCompat because it doesn't work with
   267  *	directories or with locked files. 
   268  *
   269  * Results:
   270  *	Returns a mac error indicating the cause of the failure.
   271  *
   272  * Side effects:
   273  *	Creates a temp file in the target directory to handle a rename
   274  *	between directories.
   275  *
   276  *--------------------------------------------------------------------------
   277  */
   278   
   279 static OSErr		
   280 MoveRename(
   281     const FSSpec *srcFileSpecPtr,   /* Source object. */
   282     const FSSpec *dstDirSpecPtr,    /* Destination directory. */
   283     StringPtr copyName)		    /* New name for object in destination 
   284     				     * directory. */
   285 {
   286     OSErr err;
   287     long srcID, dstID;
   288     Boolean srcIsDir, dstIsDir;
   289     Str31 tmpName;
   290     FSSpec dstFileSpec, srcDirSpec, tmpSrcFileSpec, tmpDstFileSpec;
   291     Boolean locked;
   292     
   293     if (srcFileSpecPtr->parID == 1) {
   294         /*
   295          * Trying to rename a volume.
   296          */
   297           
   298         return badMovErr;
   299     }
   300     if (srcFileSpecPtr->vRefNum != dstDirSpecPtr->vRefNum) {
   301 	/*
   302 	 * Renaming across volumes.
   303 	 */
   304 	 
   305         return diffVolErr;
   306     }
   307     err = FSpGetFLockCompat(srcFileSpecPtr, &locked);
   308     if (locked) {
   309         FSpRstFLockCompat(srcFileSpecPtr);
   310     }
   311     if (err == noErr) {
   312 	err = FSpGetDirectoryID(dstDirSpecPtr, &dstID, &dstIsDir);
   313     }
   314     if (err == noErr) {
   315         if (srcFileSpecPtr->parID == dstID) {
   316             /*
   317              * Renaming object within directory. 
   318              */
   319             
   320             err = FSpRenameCompat(srcFileSpecPtr, copyName);
   321             goto done; 
   322         }
   323         if (Pstrequal(srcFileSpecPtr->name, copyName)) {
   324 	    /*
   325 	     * Moving object to another directory (under same name). 
   326 	     */
   327 	 
   328 	    err = FSpCatMoveCompat(srcFileSpecPtr, dstDirSpecPtr);
   329 	    goto done; 
   330         } 
   331         err = FSpGetDirectoryID(srcFileSpecPtr, &srcID, &srcIsDir);
   332     } 
   333     if (err == noErr) {
   334         /*
   335          * Fullblown: rename source object to temp name, move temp to
   336          * dest directory, and rename temp to target.
   337          */
   338           
   339         err = GenerateUniqueName(srcFileSpecPtr->vRefNum, &startSeed,
   340        		srcFileSpecPtr->parID, dstID, tmpName);
   341         FSMakeFSSpecCompat(srcFileSpecPtr->vRefNum, srcFileSpecPtr->parID,
   342          	tmpName, &tmpSrcFileSpec);
   343         FSMakeFSSpecCompat(dstDirSpecPtr->vRefNum, dstID, tmpName,
   344          	&tmpDstFileSpec);
   345     }
   346     if (err == noErr) {
   347         err = FSpRenameCompat(srcFileSpecPtr, tmpName);
   348     }
   349     if (err == noErr) {
   350         err = FSpCatMoveCompat(&tmpSrcFileSpec, dstDirSpecPtr);
   351         if (err == noErr) {
   352             err = FSpRenameCompat(&tmpDstFileSpec, copyName);
   353             if (err == noErr) {
   354                 goto done;
   355             }
   356             FSMakeFSSpecCompat(srcFileSpecPtr->vRefNum, srcFileSpecPtr->parID,
   357              	    NULL, &srcDirSpec);
   358             FSpCatMoveCompat(&tmpDstFileSpec, &srcDirSpec);
   359         }                 
   360         FSpRenameCompat(&tmpSrcFileSpec, srcFileSpecPtr->name);
   361     }
   362     
   363     done:
   364     if (locked != false) {
   365     	if (err == noErr) {
   366 	    FSMakeFSSpecCompat(dstDirSpecPtr->vRefNum, 
   367 	    	    dstID, copyName, &dstFileSpec);
   368             FSpSetFLockCompat(&dstFileSpec);
   369         } else {
   370             FSpSetFLockCompat(srcFileSpecPtr);
   371         }
   372     }
   373     return err;
   374 }     
   375 
   376 /*
   377  *---------------------------------------------------------------------------
   378  *
   379  * TclpObjCopyFile, DoCopyFile --
   380  *
   381  *      Copy a single file (not a directory).  If dst already exists and
   382  *	is not a directory, it is removed.
   383  *
   384  * Results:
   385  *	If the file was successfully copied, returns TCL_OK.  Otherwise
   386  *	the return value is TCL_ERROR and errno is set to indicate the
   387  *	error.  Some possible values for errno are:
   388  *
   389  *	EACCES:     src or dst parent directory can't be read and/or written.
   390  *	EISDIR:	    src or dst is a directory.
   391  *	ENOENT:	    src doesn't exist.  src or dst is "".
   392  *
   393  * Side effects:
   394  *      This procedure will also copy symbolic links, block, and
   395  *      character devices, and fifos.  For symbolic links, the links 
   396  *      themselves will be copied and not what they point to.  For the
   397  *	other special file types, the directory entry will be copied and
   398  *	not the contents of the device that it refers to.
   399  *
   400  *---------------------------------------------------------------------------
   401  */
   402  
   403 int 
   404 TclpObjCopyFile(srcPathPtr, destPathPtr)
   405     Tcl_Obj *srcPathPtr;
   406     Tcl_Obj *destPathPtr;
   407 {
   408     return DoCopyFile(Tcl_FSGetNativePath(srcPathPtr),
   409 		      Tcl_FSGetNativePath(destPathPtr));
   410 }
   411 
   412 static int
   413 DoCopyFile(
   414     CONST char *src,		/* Pathname of file to be copied (native). */
   415     CONST char *dst)		/* Pathname of file to copy to (native). */
   416 {
   417     OSErr err, dstErr;
   418     Boolean dstExists, dstIsDirectory, dstLocked;
   419     FSSpec srcFileSpec, dstFileSpec, dstDirSpec, tmpFileSpec;
   420     Str31 tmpName;
   421 	
   422     err = FSpLLocationFromPath(strlen(src), src, &srcFileSpec);
   423     if (err == noErr) {
   424         err = GetFileSpecs(dst, &dstFileSpec, &dstDirSpec, &dstExists,
   425         	&dstIsDirectory);
   426     }
   427     if (dstExists) {
   428         if (dstIsDirectory) {
   429             errno = EISDIR;
   430             return TCL_ERROR;
   431         }
   432         err = FSpGetFLockCompat(&dstFileSpec, &dstLocked);
   433         if (dstLocked) {
   434             FSpRstFLockCompat(&dstFileSpec);
   435         }
   436         
   437         /*
   438          * Backup dest file.
   439          */
   440          
   441         dstErr = GenerateUniqueName(dstFileSpec.vRefNum, &startSeed, dstFileSpec.parID, 
   442     	        dstFileSpec.parID, tmpName);
   443         if (dstErr == noErr) {
   444             dstErr = FSpRenameCompat(&dstFileSpec, tmpName);
   445         }   
   446     }
   447     if (err == noErr) {
   448     	err = FSpFileCopy(&srcFileSpec, &dstDirSpec, 
   449     		(StringPtr) dstFileSpec.name, NULL, 0, true);
   450     }
   451     if ((dstExists != false) && (dstErr == noErr)) {
   452         FSMakeFSSpecCompat(dstFileSpec.vRefNum, dstFileSpec.parID,
   453         	tmpName, &tmpFileSpec);
   454 	if (err == noErr) {
   455 	    /* 
   456 	     * Delete backup file. 
   457 	     */
   458 	     
   459 	    FSpDeleteCompat(&tmpFileSpec);
   460 	} else {
   461 	
   462 	    /* 
   463 	     * Restore backup file.
   464 	     */
   465 	     
   466 	    FSpDeleteCompat(&dstFileSpec);
   467 	    FSpRenameCompat(&tmpFileSpec, dstFileSpec.name);
   468 	    if (dstLocked) {
   469 	        FSpSetFLockCompat(&dstFileSpec);
   470 	    }
   471 	}
   472     }
   473     
   474     if (err != noErr) {
   475 	errno = TclMacOSErrorToPosixError(err);
   476 	return TCL_ERROR;
   477     }
   478     return TCL_OK;
   479 }
   480 
   481 /*
   482  *---------------------------------------------------------------------------
   483  *
   484  * TclpObjDeleteFile, TclpDeleteFile --
   485  *
   486  *      Removes a single file (not a directory).
   487  *
   488  * Results:
   489  *	If the file was successfully deleted, returns TCL_OK.  Otherwise
   490  *	the return value is TCL_ERROR and errno is set to indicate the
   491  *	error.  Some possible values for errno are:
   492  *
   493  *	EACCES:     a parent directory can't be read and/or written.
   494  *	EISDIR:	    path is a directory.
   495  *	ENOENT:	    path doesn't exist or is "".
   496  *
   497  * Side effects:
   498  *      The file is deleted, even if it is read-only.
   499  *
   500  *---------------------------------------------------------------------------
   501  */
   502 
   503 int 
   504 TclpObjDeleteFile(pathPtr)
   505     Tcl_Obj *pathPtr;
   506 {
   507     return TclpDeleteFile(Tcl_FSGetNativePath(pathPtr));
   508 }
   509 
   510 int
   511 TclpDeleteFile(
   512     CONST char *path)		/* Pathname of file to be removed (native). */
   513 {
   514     OSErr err;
   515     FSSpec fileSpec;
   516     Boolean isDirectory;
   517     long dirID;
   518     
   519     err = FSpLLocationFromPath(strlen(path), path, &fileSpec);
   520     if (err == noErr) {
   521 	/*
   522      	 * Since FSpDeleteCompat will delete an empty directory, make sure
   523      	 * that this isn't a directory first.
   524          */
   525         
   526         FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory);
   527 	if (isDirectory == true) {
   528             errno = EISDIR;
   529             return TCL_ERROR;
   530         }
   531     }
   532     err = FSpDeleteCompat(&fileSpec);
   533     if (err == fLckdErr) {
   534     	FSpRstFLockCompat(&fileSpec);
   535     	err = FSpDeleteCompat(&fileSpec);
   536     	if (err != noErr) {
   537     	    FSpSetFLockCompat(&fileSpec);
   538     	}
   539     }
   540     if (err != noErr) {
   541 	errno = TclMacOSErrorToPosixError(err);
   542 	return TCL_ERROR;
   543     }
   544     return TCL_OK;
   545 }
   546 
   547 /*
   548  *---------------------------------------------------------------------------
   549  *
   550  * TclpObjCreateDirectory, DoCreateDirectory --
   551  *
   552  *      Creates the specified directory.  All parent directories of the
   553  *	specified directory must already exist.  The directory is
   554  *	automatically created with permissions so that user can access
   555  *	the new directory and create new files or subdirectories in it.
   556  *
   557  * Results:
   558  *	If the directory was successfully created, returns TCL_OK.
   559  *	Otherwise the return value is TCL_ERROR and errno is set to
   560  *	indicate the error.  Some possible values for errno are:
   561  *
   562  *	EACCES:     a parent directory can't be read and/or written.
   563  *	EEXIST:	    path already exists.
   564  *	ENOENT:	    a parent directory doesn't exist.
   565  *
   566  * Side effects:
   567  *      A directory is created with the current umask, except that
   568  *	permission for u+rwx will always be added.
   569  *
   570  *---------------------------------------------------------------------------
   571  */
   572 
   573 int 
   574 TclpObjCreateDirectory(pathPtr)
   575     Tcl_Obj *pathPtr;
   576 {
   577     return DoCreateDirectory(Tcl_FSGetNativePath(pathPtr));
   578 }
   579 
   580 static int
   581 DoCreateDirectory(
   582     CONST char *path)		/* Pathname of directory to create (native). */
   583 {
   584     OSErr err;
   585     FSSpec dirSpec;
   586     long outDirID;
   587 	
   588     err = FSpLocationFromPath(strlen(path), path, &dirSpec);
   589     if (err == noErr) {
   590         err = dupFNErr;		/* EEXIST. */
   591     } else if (err == fnfErr) {
   592         err = FSpDirCreateCompat(&dirSpec, smSystemScript, &outDirID);
   593     } 
   594     
   595     if (err != noErr) {
   596 	errno = TclMacOSErrorToPosixError(err);
   597 	return TCL_ERROR;
   598     }
   599     return TCL_OK;
   600 }
   601 
   602 /*
   603  *---------------------------------------------------------------------------
   604  *
   605  * TclpObjCopyDirectory, DoCopyDirectory --
   606  *
   607  *      Recursively copies a directory.  The target directory dst must
   608  *	not already exist.  Note that this function does not merge two
   609  *	directory hierarchies, even if the target directory is an an
   610  *	empty directory.
   611  *
   612  * Results:
   613  *	If the directory was successfully copied, returns TCL_OK.
   614  *	Otherwise the return value is TCL_ERROR, errno is set to indicate
   615  *	the error, and the pathname of the file that caused the error
   616  *	is stored in errorPtr.  See TclpCreateDirectory and TclpCopyFile
   617  *	for a description of possible values for errno.
   618  *
   619  * Side effects:
   620  *      An exact copy of the directory hierarchy src will be created
   621  *	with the name dst.  If an error occurs, the error will
   622  *      be returned immediately, and remaining files will not be
   623  *	processed.
   624  *
   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     int ret;
   636     ret = DoCopyDirectory(Tcl_FSGetNativePath(srcPathPtr),
   637 			  Tcl_FSGetNativePath(destPathPtr), &ds);
   638     if (ret != TCL_OK) {
   639 	*errorPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), -1);
   640 	Tcl_DStringFree(&ds);
   641 	Tcl_IncrRefCount(*errorPtr);
   642     }
   643     return ret;
   644 }
   645 
   646 static int
   647 DoCopyDirectory(
   648     CONST char *src,		/* Pathname of directory to be copied
   649 				 * (Native). */
   650     CONST char *dst,		/* Pathname of target directory (Native). */
   651     Tcl_DString *errorPtr)	/* If non-NULL, uninitialized or free
   652 				 * DString filled with UTF-8 name of file
   653 				 * causing error. */
   654 {
   655     OSErr err, saveErr;
   656     long srcID, tmpDirID;
   657     FSSpec srcFileSpec, dstFileSpec, dstDirSpec, tmpDirSpec, tmpFileSpec;
   658     Boolean srcIsDirectory, srcLocked;
   659     Boolean dstIsDirectory, dstExists;
   660     Str31 tmpName;
   661 
   662     err = FSpLocationFromPath(strlen(src), src, &srcFileSpec);
   663     if (err == noErr) {
   664     	err = FSpGetDirectoryID(&srcFileSpec, &srcID, &srcIsDirectory);
   665     }
   666     if (err == noErr) {
   667         if (srcIsDirectory == false) {
   668             err = afpObjectTypeErr;	/* ENOTDIR. */
   669         }
   670     }
   671     if (err == noErr) {
   672         err = GetFileSpecs(dst, &dstFileSpec, &dstDirSpec, &dstExists,
   673         	&dstIsDirectory);
   674     }
   675     if (dstExists) {
   676         if (dstIsDirectory == false) {
   677             err = afpObjectTypeErr;	/* ENOTDIR. */
   678         } else {
   679             err = dupFNErr;		/* EEXIST. */
   680         }
   681     }
   682     if (err != noErr) {
   683         goto done;
   684     }        
   685     if ((srcFileSpec.vRefNum == dstFileSpec.vRefNum) &&
   686     	    (srcFileSpec.parID == dstFileSpec.parID) &&
   687             (Pstrequal(srcFileSpec.name, dstFileSpec.name) != 0)) {
   688         /*
   689          * Copying on top of self.  No-op.
   690          */
   691                     
   692         goto done;
   693     }
   694 
   695     /*
   696      * This algorthm will work making a copy of the source directory in
   697      * the current directory with a new name, in a new directory with the
   698      * same name, and in a new directory with a new name:
   699      *
   700      * 1. Make dstDir/tmpDir.
   701      * 2. Copy srcDir/src to dstDir/tmpDir/src
   702      * 3. Rename dstDir/tmpDir/src to dstDir/tmpDir/dst (if necessary).
   703      * 4. CatMove dstDir/tmpDir/dst to dstDir/dst.
   704      * 5. Remove dstDir/tmpDir.
   705      */
   706                 
   707     err = FSpGetFLockCompat(&srcFileSpec, &srcLocked);
   708     if (srcLocked) {
   709         FSpRstFLockCompat(&srcFileSpec);
   710     }
   711     if (err == noErr) {
   712         err = GenerateUniqueName(dstFileSpec.vRefNum, &startSeed, dstFileSpec.parID, 
   713     	        dstFileSpec.parID, tmpName);
   714     }
   715     if (err == noErr) {
   716         FSMakeFSSpecCompat(dstFileSpec.vRefNum, dstFileSpec.parID,
   717         	tmpName, &tmpDirSpec);
   718         err = FSpDirCreateCompat(&tmpDirSpec, smSystemScript, &tmpDirID);
   719     }
   720     if (err == noErr) {
   721 	err = FSpDirectoryCopy(&srcFileSpec, &tmpDirSpec, NULL, NULL, 0, true,
   722 	    	CopyErrHandler);
   723     }
   724     
   725     /* 
   726      * Even if the Copy failed, Rename/Move whatever did get copied to the
   727      * appropriate final destination, if possible.  
   728      */
   729      
   730     saveErr = err;
   731     err = noErr;
   732     if (Pstrequal(srcFileSpec.name, dstFileSpec.name) == 0) {
   733         err = FSMakeFSSpecCompat(tmpDirSpec.vRefNum, tmpDirID, 
   734         	srcFileSpec.name, &tmpFileSpec);
   735         if (err == noErr) {
   736             err = FSpRenameCompat(&tmpFileSpec, dstFileSpec.name);
   737         }
   738     }
   739     if (err == noErr) {
   740         err = FSMakeFSSpecCompat(tmpDirSpec.vRefNum, tmpDirID,
   741         	dstFileSpec.name, &tmpFileSpec);
   742     }
   743     if (err == noErr) {
   744         err = FSpCatMoveCompat(&tmpFileSpec, &dstDirSpec);
   745     }
   746     if (err == noErr) {
   747         if (srcLocked) {
   748             FSpSetFLockCompat(&dstFileSpec);
   749         }
   750     }
   751     
   752     FSpDeleteCompat(&tmpDirSpec);
   753     
   754     if (saveErr != noErr) {
   755         err = saveErr;
   756     }
   757     
   758     done:
   759     if (err != noErr) {
   760         errno = TclMacOSErrorToPosixError(err);
   761         if (errorPtr != NULL) {
   762             Tcl_ExternalToUtfDString(NULL, dst, -1, errorPtr);
   763         }
   764         return TCL_ERROR;
   765     }
   766     return TCL_OK;
   767 }
   768 
   769 /*
   770  *----------------------------------------------------------------------
   771  *
   772  * CopyErrHandler --
   773  *
   774  *      This procedure is called from the MoreFiles procedure 
   775  *      FSpDirectoryCopy whenever an error occurs.
   776  *
   777  * Results:
   778  *      False if the condition should not be considered an error, true
   779  *      otherwise.
   780  *
   781  * Side effects:
   782  *      Since FSpDirectoryCopy() is called only after removing any 
   783  *      existing target directories, there shouldn't be any errors.
   784  *      
   785  *----------------------------------------------------------------------
   786  */
   787 
   788 static pascal Boolean 
   789 CopyErrHandler(
   790     OSErr error,		/* Error that occured */
   791     short failedOperation,	/* operation that caused the error */
   792     short srcVRefNum,		/* volume ref number of source */
   793     long srcDirID,		/* directory id of source */
   794     ConstStr255Param srcName,	/* name of source */
   795     short dstVRefNum,		/* volume ref number of dst */
   796     long dstDirID,		/* directory id of dst */
   797     ConstStr255Param dstName)	/* name of dst directory */
   798 {
   799     return true;
   800 }
   801 
   802 /*
   803  *---------------------------------------------------------------------------
   804  *
   805  * TclpObjRemoveDirectory, DoRemoveDirectory --
   806  *
   807  *	Removes directory (and its contents, if the recursive flag is set).
   808  *
   809  * Results:
   810  *	If the directory was successfully removed, returns TCL_OK.
   811  *	Otherwise the return value is TCL_ERROR, errno is set to indicate
   812  *	the error, and the pathname of the file that caused the error
   813  *	is stored in errorPtr.  Some possible values for errno are:
   814  *
   815  *	EACCES:     path directory can't be read and/or written.
   816  *	EEXIST:	    path is a non-empty directory.
   817  *	EINVAL:	    path is a root directory.
   818  *	ENOENT:	    path doesn't exist or is "".
   819  * 	ENOTDIR:    path is not a directory.
   820  *
   821  * Side effects:
   822  *	Directory removed.  If an error occurs, the error will be returned
   823  *	immediately, and remaining files will not be deleted.
   824  *
   825  *---------------------------------------------------------------------------
   826  */
   827  
   828 int 
   829 TclpObjRemoveDirectory(pathPtr, recursive, errorPtr)
   830     Tcl_Obj *pathPtr;
   831     int recursive;
   832     Tcl_Obj **errorPtr;
   833 {
   834     Tcl_DString ds;
   835     int ret;
   836     ret = DoRemoveDirectory(Tcl_FSGetNativePath(pathPtr),recursive, &ds);
   837     if (ret != TCL_OK) {
   838 	*errorPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), -1);
   839 	Tcl_DStringFree(&ds);
   840 	Tcl_IncrRefCount(*errorPtr);
   841     }
   842     return ret;
   843 }
   844 
   845 static int
   846 DoRemoveDirectory(
   847     CONST char *path,		/* Pathname of directory to be removed
   848 				 * (native). */
   849     int recursive,		/* If non-zero, removes directories that
   850 				 * are nonempty.  Otherwise, will only remove
   851 				 * empty directories. */
   852     Tcl_DString *errorPtr)	/* If non-NULL, uninitialized or free
   853 				 * DString filled with UTF-8 name of file
   854 				 * causing error. */
   855 {
   856     OSErr err;
   857     FSSpec fileSpec;
   858     long dirID;
   859     int locked;
   860     Boolean isDirectory;
   861     CInfoPBRec pb;
   862     Str255 fileName;
   863 
   864 
   865     locked = 0;
   866     err = FSpLocationFromPath(strlen(path), path, &fileSpec);
   867     if (err != noErr) {
   868         goto done;
   869     }   
   870 
   871     /*
   872      * Since FSpDeleteCompat will delete a file, make sure this isn't
   873      * a file first.
   874      */
   875          
   876     isDirectory = 1;
   877     FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory);
   878     if (isDirectory == 0) {
   879         errno = ENOTDIR;
   880         return TCL_ERROR;
   881     }
   882     
   883     err = FSpDeleteCompat(&fileSpec);
   884     if (err == fLckdErr) {
   885         locked = 1;
   886     	FSpRstFLockCompat(&fileSpec);
   887     	err = FSpDeleteCompat(&fileSpec);
   888     }
   889     if (err == noErr) {
   890 	return TCL_OK;
   891     }
   892     if (err != fBsyErr) {
   893         goto done;
   894     }
   895      
   896     if (recursive == 0) {
   897 	/*
   898 	 * fBsyErr means one of three things: file busy, directory not empty, 
   899 	 * or working directory control block open.  Determine if directory
   900 	 * is empty. If directory is not empty, return EEXIST.
   901 	 */
   902 
   903 	pb.hFileInfo.ioVRefNum = fileSpec.vRefNum;
   904 	pb.hFileInfo.ioDirID = dirID;
   905 	pb.hFileInfo.ioNamePtr = (StringPtr) fileName;
   906 	pb.hFileInfo.ioFDirIndex = 1;
   907 	if (PBGetCatInfoSync(&pb) == noErr) {
   908 	    err = dupFNErr;	/* EEXIST */
   909 	    goto done;
   910 	}
   911     }
   912 	
   913     /*
   914      * DeleteDirectory removes a directory and all its contents, including
   915      * any locked files.  There is no interface to get the name of the 
   916      * file that caused the error, if an error occurs deleting this tree,
   917      * unless we rewrite DeleteDirectory ourselves.
   918      */
   919 	 
   920     err = DeleteDirectory(fileSpec.vRefNum, dirID, NULL);
   921 
   922     done:
   923     if (err != noErr) {
   924 	if (errorPtr != NULL) {
   925 	    Tcl_UtfToExternalDString(NULL, path, -1, errorPtr);
   926 	}
   927         if (locked) {
   928             FSpSetFLockCompat(&fileSpec);
   929         }
   930     	errno = TclMacOSErrorToPosixError(err);
   931     	return TCL_ERROR;
   932     }
   933     return TCL_OK;
   934 }
   935 			    
   936 /*
   937  *---------------------------------------------------------------------------
   938  *
   939  * GetFileSpecs --
   940  *
   941  *	Gets FSSpecs for the specified path and its parent directory.
   942  *
   943  * Results:
   944  *	The return value is noErr if there was no error getting FSSpecs,
   945  *	otherwise it is an error describing the problem.  Fills buffers 
   946  *	with information, as above.  
   947  *
   948  * Side effects:
   949  *	None.
   950  *
   951  *---------------------------------------------------------------------------
   952  */
   953 
   954 static OSErr
   955 GetFileSpecs(
   956     CONST char *path,		/* The path to query. */
   957     FSSpec *pathSpecPtr,	/* Filled with information about path. */
   958     FSSpec *dirSpecPtr,		/* Filled with information about path's
   959     				 * parent directory. */
   960     Boolean *pathExistsPtr,	/* Set to true if path actually exists, 
   961     				 * false if it doesn't or there was an 
   962     				 * error reading the specified path. */
   963     Boolean *pathIsDirectoryPtr)/* Set to true if path is itself a directory,
   964     				 * otherwise false. */
   965 {
   966     CONST char *dirName;
   967     OSErr err;
   968     int argc;
   969     CONST char **argv;
   970     long d;
   971     Tcl_DString buffer;
   972         
   973     *pathExistsPtr = false;
   974     *pathIsDirectoryPtr = false;
   975     
   976     Tcl_DStringInit(&buffer);
   977     Tcl_SplitPath(path, &argc, &argv);
   978     if (argc == 1) {
   979         dirName = ":";
   980     } else {
   981         dirName = Tcl_JoinPath(argc - 1, argv, &buffer);
   982     }
   983     err = FSpLocationFromPath(strlen(dirName), dirName, dirSpecPtr);
   984     Tcl_DStringFree(&buffer);
   985     ckfree((char *) argv);
   986 
   987     if (err == noErr) {
   988         err = FSpLocationFromPath(strlen(path), path, pathSpecPtr);
   989         if (err == noErr) {
   990             *pathExistsPtr = true;
   991             err = FSpGetDirectoryID(pathSpecPtr, &d, pathIsDirectoryPtr);
   992         } else if (err == fnfErr) {
   993             err = noErr;
   994         }
   995     }
   996     return err;
   997 }
   998 
   999 /*
  1000  *-------------------------------------------------------------------------
  1001  *
  1002  * FSpGetFLockCompat --
  1003  *
  1004  *	Determines if there exists a software lock on the specified
  1005  *	file.  The software lock could prevent the file from being 
  1006  *	renamed or moved.
  1007  *
  1008  * Results:
  1009  *	Standard macintosh error code.  
  1010  *
  1011  * Side effects:
  1012  *	None.
  1013  *
  1014  *
  1015  *-------------------------------------------------------------------------
  1016  */
  1017  
  1018 OSErr
  1019 FSpGetFLockCompat(
  1020     const FSSpec *specPtr,	/* File to query. */
  1021     Boolean *lockedPtr)		/* Set to true if file is locked, false
  1022     				 * if it isn't or there was an error reading
  1023     				 * specified file. */
  1024 {
  1025     CInfoPBRec pb;
  1026     OSErr err;
  1027     
  1028     pb.hFileInfo.ioVRefNum = specPtr->vRefNum;
  1029     pb.hFileInfo.ioDirID = specPtr->parID;
  1030     pb.hFileInfo.ioNamePtr = (StringPtr) specPtr->name;
  1031     pb.hFileInfo.ioFDirIndex = 0;
  1032     
  1033     err = PBGetCatInfoSync(&pb);
  1034     if ((err == noErr) && (pb.hFileInfo.ioFlAttrib & 0x01)) {
  1035         *lockedPtr = true;
  1036     } else {
  1037         *lockedPtr = false;
  1038     }
  1039     return err;
  1040 }
  1041     
  1042 /*
  1043  *----------------------------------------------------------------------
  1044  *
  1045  * Pstrequal --
  1046  *
  1047  *      Pascal string compare. 
  1048  *
  1049  * Results:
  1050  *      Returns 1 if strings equal, 0 otherwise.
  1051  *
  1052  * Side effects:
  1053  *      None.
  1054  *      
  1055  *----------------------------------------------------------------------
  1056  */
  1057 
  1058 static int 
  1059 Pstrequal (
  1060     ConstStr255Param stringA,	/* Pascal string A */
  1061     ConstStr255Param stringB)   /* Pascal string B */
  1062 {
  1063     int i, len;
  1064     
  1065     len = *stringA;
  1066     for (i = 0; i <= len; i++) {
  1067         if (*stringA++ != *stringB++) {
  1068             return 0;
  1069         }
  1070     }
  1071     return 1;
  1072 }
  1073     
  1074 /*
  1075  *----------------------------------------------------------------------
  1076  *
  1077  * GetFileFinderAttributes --
  1078  *
  1079  *	Returns a Tcl_Obj containing the value of a file attribute
  1080  *	which is part of the FInfo record. Which attribute is controlled
  1081  *	by objIndex.
  1082  *
  1083  * Results:
  1084  *      Returns a standard TCL error. If the return value is TCL_OK,
  1085  *	the new creator or file type object is put into attributePtrPtr.
  1086  *	The object will have ref count 0. If there is an error,
  1087  *	attributePtrPtr is not touched.
  1088  *
  1089  * Side effects:
  1090  *      A new object is allocated if the file is valid.
  1091  *      
  1092  *----------------------------------------------------------------------
  1093  */
  1094 
  1095 static int
  1096 GetFileFinderAttributes(
  1097     Tcl_Interp *interp,		/* The interp to report errors with. */
  1098     int objIndex,		/* The index of the attribute option. */
  1099     Tcl_Obj *fileName,	/* The name of the file (UTF-8). */
  1100     Tcl_Obj **attributePtrPtr)	/* A pointer to return the object with. */
  1101 {
  1102     OSErr err;
  1103     FSSpec fileSpec;
  1104     FInfo finfo;
  1105     CONST char *native;
  1106 
  1107     native=Tcl_FSGetNativePath(fileName);
  1108     err = FSpLLocationFromPath(strlen(native),
  1109 	    native, &fileSpec);
  1110 
  1111     if (err == noErr) {
  1112     	err = FSpGetFInfo(&fileSpec, &finfo);
  1113     }
  1114     
  1115     if (err == noErr) {
  1116     	switch (objIndex) {
  1117     	    case MAC_CREATOR_ATTRIBUTE:
  1118     	    	*attributePtrPtr = Tcl_NewOSTypeObj(finfo.fdCreator);
  1119     	    	break;
  1120     	    case MAC_HIDDEN_ATTRIBUTE:
  1121     	    	*attributePtrPtr = Tcl_NewBooleanObj(finfo.fdFlags
  1122     	    		& kIsInvisible);
  1123     	    	break;
  1124     	    case MAC_TYPE_ATTRIBUTE:
  1125     	    	*attributePtrPtr = Tcl_NewOSTypeObj(finfo.fdType);
  1126     	    	break;
  1127     	}
  1128     } else if (err == fnfErr) {
  1129     	long dirID;
  1130     	Boolean isDirectory = 0;
  1131     	
  1132     	err = FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory);
  1133     	if ((err == noErr) && isDirectory) {
  1134     	    if (objIndex == MAC_HIDDEN_ATTRIBUTE) {
  1135     	    	*attributePtrPtr = Tcl_NewBooleanObj(0);
  1136     	    } else {
  1137     	    	*attributePtrPtr = Tcl_NewOSTypeObj('Fldr');
  1138     	    }
  1139     	}
  1140     }
  1141     
  1142     if (err != noErr) {
  1143     	errno = TclMacOSErrorToPosixError(err);
  1144     	Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), 
  1145     		"could not read \"", Tcl_GetString(fileName), "\": ",
  1146     		Tcl_PosixError(interp), (char *) NULL);
  1147     	return TCL_ERROR;
  1148     }
  1149     return TCL_OK;
  1150 }
  1151 
  1152 /*
  1153  *----------------------------------------------------------------------
  1154  *
  1155  * GetFileReadOnly --
  1156  *
  1157  *	Returns a Tcl_Obj containing a Boolean value indicating whether
  1158  *	or not the file is read-only. The object will have ref count 0.
  1159  *	This procedure just checks the Finder attributes; it does not
  1160  *	check AppleShare sharing attributes.
  1161  *
  1162  * Results:
  1163  *      Returns a standard TCL error. If the return value is TCL_OK,
  1164  *	the new creator type object is put into readOnlyPtrPtr.
  1165  *	If there is an error, readOnlyPtrPtr is not touched.
  1166  *
  1167  * Side effects:
  1168  *      A new object is allocated if the file is valid.
  1169  *      
  1170  *----------------------------------------------------------------------
  1171  */
  1172 
  1173 static int
  1174 GetFileReadOnly(
  1175     Tcl_Interp *interp,		/* The interp to report errors with. */
  1176     int objIndex,		/* The index of the attribute. */
  1177     Tcl_Obj *fileName,	/* The name of the file (UTF-8). */
  1178     Tcl_Obj **readOnlyPtrPtr)	/* A pointer to return the object with. */
  1179 {
  1180     OSErr err;
  1181     FSSpec fileSpec;
  1182     CInfoPBRec paramBlock;
  1183     CONST char *native;
  1184 
  1185     native=Tcl_FSGetNativePath(fileName);
  1186     err = FSpLLocationFromPath(strlen(native),
  1187 	    native, &fileSpec);
  1188     
  1189     if (err == noErr) {
  1190     	if (err == noErr) {
  1191     	    paramBlock.hFileInfo.ioCompletion = NULL;
  1192     	    paramBlock.hFileInfo.ioNamePtr = fileSpec.name;
  1193     	    paramBlock.hFileInfo.ioVRefNum = fileSpec.vRefNum;
  1194     	    paramBlock.hFileInfo.ioFDirIndex = 0;
  1195     	    paramBlock.hFileInfo.ioDirID = fileSpec.parID;
  1196     	    err = PBGetCatInfo(&paramBlock, 0);
  1197     	    if (err == noErr) {
  1198     	    
  1199     	    	/*
  1200     	    	 * For some unknown reason, the Mac does not give
  1201     	    	 * symbols for the bits in the ioFlAttrib field.
  1202     	    	 * 1 -> locked.
  1203     	    	 */
  1204     	    
  1205     	    	*readOnlyPtrPtr = Tcl_NewBooleanObj(
  1206     	    		paramBlock.hFileInfo.ioFlAttrib & 1);
  1207     	    }
  1208     	}
  1209     }
  1210     if (err != noErr) {
  1211     	errno = TclMacOSErrorToPosixError(err);
  1212     	Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), 
  1213     		"could not read \"", Tcl_GetString(fileName), "\": ",
  1214     		Tcl_PosixError(interp), (char *) NULL);
  1215     	return TCL_ERROR;
  1216     }
  1217     return TCL_OK;
  1218 }
  1219 
  1220 /*
  1221  *----------------------------------------------------------------------
  1222  *
  1223  * SetFileFinderAttributes --
  1224  *
  1225  *	Sets the file to the creator or file type given by attributePtr.
  1226  *	objIndex determines whether the creator or file type is set.
  1227  *
  1228  * Results:
  1229  *	Returns a standard TCL error.
  1230  *
  1231  * Side effects:
  1232  *      The file's attribute is set.
  1233  *      
  1234  *----------------------------------------------------------------------
  1235  */
  1236 
  1237 static int
  1238 SetFileFinderAttributes(
  1239     Tcl_Interp *interp,		/* The interp to report errors with. */
  1240     int objIndex,		/* The index of the attribute. */
  1241     Tcl_Obj *fileName,	/* The name of the file (UTF-8). */
  1242     Tcl_Obj *attributePtr)	/* The command line object. */
  1243 {
  1244     OSErr err;
  1245     FSSpec fileSpec;
  1246     FInfo finfo;
  1247     CONST char *native;
  1248 
  1249     native=Tcl_FSGetNativePath(fileName);
  1250     err = FSpLLocationFromPath(strlen(native),
  1251 	    native, &fileSpec);
  1252     
  1253     if (err == noErr) {
  1254     	err = FSpGetFInfo(&fileSpec, &finfo);
  1255     }
  1256     
  1257     if (err == noErr) {
  1258     	switch (objIndex) {
  1259     	    case MAC_CREATOR_ATTRIBUTE:
  1260     	    	if (Tcl_GetOSTypeFromObj(interp, attributePtr,
  1261     	    		&finfo.fdCreator) != TCL_OK) {
  1262     	    	    return TCL_ERROR;
  1263     	    	}
  1264     	    	break;
  1265     	    case MAC_HIDDEN_ATTRIBUTE: {
  1266     	    	int hidden;
  1267     	    	
  1268     	    	if (Tcl_GetBooleanFromObj(interp, attributePtr, &hidden)
  1269     	    		!= TCL_OK) {
  1270     	    	    return TCL_ERROR;
  1271     	    	}
  1272     	    	if (hidden) {
  1273     	    	    finfo.fdFlags |= kIsInvisible;
  1274     	    	} else {
  1275     	    	    finfo.fdFlags &= ~kIsInvisible;
  1276     	    	}
  1277     	    	break;
  1278     	    }
  1279     	    case MAC_TYPE_ATTRIBUTE:
  1280     	    	if (Tcl_GetOSTypeFromObj(interp, attributePtr,
  1281     	    		&finfo.fdType) != TCL_OK) {
  1282     	    	    return TCL_ERROR;
  1283     	    	}
  1284     	    	break;
  1285     	}
  1286     	err = FSpSetFInfo(&fileSpec, &finfo);
  1287     } else if (err == fnfErr) {
  1288     	long dirID;
  1289     	Boolean isDirectory = 0;
  1290     	
  1291     	err = FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory);
  1292     	if ((err == noErr) && isDirectory) {
  1293     	    Tcl_Obj *resultPtr = Tcl_GetObjResult(interp);
  1294     	    Tcl_AppendStringsToObj(resultPtr, "cannot set ",
  1295     	    	    tclpFileAttrStrings[objIndex], ": \"",
  1296     	    	    Tcl_GetString(fileName), "\" is a directory", (char *) NULL);
  1297     	    return TCL_ERROR;
  1298     	}
  1299     }
  1300     
  1301     if (err != noErr) {
  1302     	errno = TclMacOSErrorToPosixError(err);
  1303     	Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), 
  1304     		"could not read \"", Tcl_GetString(fileName), "\": ",
  1305     		Tcl_PosixError(interp), (char *) NULL);
  1306     	return TCL_ERROR;
  1307     }
  1308     return TCL_OK;
  1309 }
  1310 
  1311 /*
  1312  *----------------------------------------------------------------------
  1313  *
  1314  * SetFileReadOnly --
  1315  *
  1316  *	Sets the file to be read-only according to the Boolean value
  1317  *	given by hiddenPtr.
  1318  *
  1319  * Results:
  1320  *	Returns a standard TCL error.
  1321  *
  1322  * Side effects:
  1323  *      The file's attribute is set.
  1324  *      
  1325  *----------------------------------------------------------------------
  1326  */
  1327 
  1328 static int
  1329 SetFileReadOnly(
  1330     Tcl_Interp *interp,		/* The interp to report errors with. */
  1331     int objIndex,		/* The index of the attribute. */
  1332     Tcl_Obj *fileName,	/* The name of the file (UTF-8). */
  1333     Tcl_Obj *readOnlyPtr)	/* The command line object. */
  1334 {
  1335     OSErr err;
  1336     FSSpec fileSpec;
  1337     HParamBlockRec paramBlock;
  1338     int hidden;
  1339     CONST char *native;
  1340 
  1341     native=Tcl_FSGetNativePath(fileName);
  1342     err = FSpLLocationFromPath(strlen(native),
  1343 	    native, &fileSpec);
  1344     
  1345     if (err == noErr) {
  1346     	if (Tcl_GetBooleanFromObj(interp, readOnlyPtr, &hidden) != TCL_OK) {
  1347     	    return TCL_ERROR;
  1348     	}
  1349     
  1350     	paramBlock.fileParam.ioCompletion = NULL;
  1351     	paramBlock.fileParam.ioNamePtr = fileSpec.name;
  1352     	paramBlock.fileParam.ioVRefNum = fileSpec.vRefNum;
  1353     	paramBlock.fileParam.ioDirID = fileSpec.parID;
  1354     	if (hidden) {
  1355     	    err = PBHSetFLock(&paramBlock, 0);
  1356     	} else {
  1357     	    err = PBHRstFLock(&paramBlock, 0);
  1358     	}
  1359     }
  1360     
  1361     if (err == fnfErr) {
  1362     	long dirID;
  1363     	Boolean isDirectory = 0;
  1364     	err = FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory);
  1365     	if ((err == noErr) && isDirectory) {
  1366     	    Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
  1367     	    	    "cannot set a directory to read-only when File Sharing is turned off",
  1368     	    	    (char *) NULL);
  1369     	    return TCL_ERROR;
  1370     	} else {
  1371     	    err = fnfErr;
  1372     	}
  1373     }
  1374     
  1375     if (err != noErr) {
  1376     	errno = TclMacOSErrorToPosixError(err);
  1377     	Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), 
  1378     		"could not read \"", Tcl_GetString(fileName), "\": ",
  1379     		Tcl_PosixError(interp), (char *) NULL);
  1380     	return TCL_ERROR;
  1381     }
  1382     return TCL_OK;
  1383 }
  1384 
  1385 /*
  1386  *---------------------------------------------------------------------------
  1387  *
  1388  * TclpObjListVolumes --
  1389  *
  1390  *	Lists the currently mounted volumes
  1391  *
  1392  * Results:
  1393  *	The list of volumes.
  1394  *
  1395  * Side effects:
  1396  *	None
  1397  *
  1398  *---------------------------------------------------------------------------
  1399  */
  1400 Tcl_Obj*
  1401 TclpObjListVolumes(void)
  1402 {
  1403     HParamBlockRec pb;
  1404     Str255 name;
  1405     OSErr theError = noErr;
  1406     Tcl_Obj *resultPtr, *elemPtr;
  1407     short volIndex = 1;
  1408     Tcl_DString dstr;
  1409 
  1410     resultPtr = Tcl_NewObj();
  1411         
  1412     /*
  1413      * We use two facts:
  1414      * 1) The Mac volumes are enumerated by the ioVolIndex parameter of
  1415      * the HParamBlockRec.  They run through the integers contiguously, 
  1416      * starting at 1.  
  1417      * 2) PBHGetVInfoSync returns an error when you ask for a volume index
  1418      * that does not exist.
  1419      * 
  1420      */
  1421         
  1422     while ( 1 ) {
  1423         pb.volumeParam.ioNamePtr = (StringPtr) &name;
  1424         pb.volumeParam.ioVolIndex = volIndex;
  1425                 
  1426         theError = PBHGetVInfoSync(&pb);
  1427 
  1428         if ( theError != noErr ) {
  1429             break;
  1430         }
  1431         
  1432         Tcl_ExternalToUtfDString(NULL, (CONST char *)&name[1], name[0], &dstr);
  1433         elemPtr = Tcl_NewStringObj(Tcl_DStringValue(&dstr),
  1434 		Tcl_DStringLength(&dstr));
  1435         Tcl_AppendToObj(elemPtr, ":", 1);
  1436         Tcl_ListObjAppendElement(NULL, resultPtr, elemPtr);
  1437         
  1438         Tcl_DStringFree(&dstr);
  1439                 
  1440         volIndex++;             
  1441     }
  1442 
  1443     Tcl_IncrRefCount(resultPtr);
  1444     return resultPtr;
  1445 }
  1446 
  1447 /*
  1448  *---------------------------------------------------------------------------
  1449  *
  1450  * TclpObjNormalizePath --
  1451  *
  1452  *	This function scans through a path specification and replaces
  1453  *	it, in place, with a normalized version.  On MacOS, this means
  1454  *	resolving all aliases present in the path and replacing the head of
  1455  *	pathPtr with the absolute case-sensitive path to the last file or
  1456  *	directory that could be validated in the path.
  1457  *
  1458  * Results:
  1459  *	The new 'nextCheckpoint' value, giving as far as we could
  1460  *	understand in the path.
  1461  *
  1462  * Side effects:
  1463  *	The pathPtr string, which must contain a valid path, is
  1464  *	possibly modified in place.
  1465  *
  1466  *---------------------------------------------------------------------------
  1467  */
  1468 
  1469 int
  1470 TclpObjNormalizePath(interp, pathPtr, nextCheckpoint)
  1471     Tcl_Interp *interp;
  1472     Tcl_Obj *pathPtr;
  1473     int nextCheckpoint;
  1474 {
  1475     #define MAXMACFILENAMELEN 31  /* assumed to be < sizeof(StrFileName) */
  1476  
  1477     StrFileName fileName;
  1478     StringPtr fileNamePtr;
  1479     int fileNameLen,newPathLen;
  1480     Handle newPathHandle;
  1481     OSErr err;
  1482     short vRefNum;
  1483     long dirID;
  1484     Boolean isDirectory;
  1485     Boolean wasAlias=FALSE;
  1486     FSSpec fileSpec, lastFileSpec;
  1487     
  1488     Tcl_DString nativeds;
  1489 
  1490     char cur;
  1491     int firstCheckpoint=nextCheckpoint, lastCheckpoint;
  1492     int origPathLen;
  1493     char *path = Tcl_GetStringFromObj(pathPtr,&origPathLen);
  1494     
  1495     {
  1496 	int currDirValid=0;    
  1497 	/*
  1498 	 * check if substring to first ':' after initial
  1499 	 * nextCheckpoint is a valid relative or absolute
  1500 	 * path to a directory, if not we return without
  1501 	 * normalizing anything
  1502 	 */
  1503 	
  1504 	while (1) {
  1505 	    cur = path[nextCheckpoint];
  1506 	    if (cur == ':' || cur == 0) {
  1507 		if (cur == ':') { 
  1508 		    /* jump over separator */
  1509 		    nextCheckpoint++; cur = path[nextCheckpoint]; 
  1510 		} 
  1511 		Tcl_UtfToExternalDString(NULL,path,nextCheckpoint,&nativeds);
  1512 		err = FSpLLocationFromPath(Tcl_DStringLength(&nativeds), 
  1513 					  Tcl_DStringValue(&nativeds), 
  1514 					  &fileSpec);
  1515 		Tcl_DStringFree(&nativeds);
  1516 		if (err == noErr) {
  1517 			lastFileSpec=fileSpec;
  1518 			err = ResolveAliasFile(&fileSpec, true, &isDirectory, 
  1519 				       &wasAlias);
  1520 			if (err == noErr) {
  1521 		    err = FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory);
  1522 		    currDirValid = ((err == noErr) && isDirectory);
  1523 		    vRefNum = fileSpec.vRefNum;
  1524 		    }
  1525 		}
  1526 		break;
  1527 	    }
  1528 	    nextCheckpoint++;
  1529 	}
  1530 	
  1531 	if(!currDirValid) {
  1532 	    /* can't determine root dir, bail out */
  1533 	    return firstCheckpoint; 
  1534 	}
  1535     }
  1536 	
  1537     /*
  1538      * Now vRefNum and dirID point to a valid
  1539      * directory, so walk the rest of the path
  1540      * ( code adapted from FSpLocationFromPath() )
  1541      */
  1542 
  1543     lastCheckpoint=nextCheckpoint;
  1544     while (1) {
  1545 	cur = path[nextCheckpoint];
  1546 	if (cur == ':' || cur == 0) {
  1547 	    fileNameLen=nextCheckpoint-lastCheckpoint;
  1548 	    fileNamePtr=fileName;
  1549 	    if(fileNameLen==0) {
  1550 		if (cur == ':') {
  1551 		    /*
  1552 		     * special case for empty dirname i.e. encountered
  1553 		     * a '::' path component: get parent dir of currDir
  1554 		     */
  1555 		    fileName[0]=2;
  1556 		    strcpy((char *) fileName + 1, "::");
  1557 		    lastCheckpoint--;
  1558 		} else {
  1559 		    /*
  1560 		     * empty filename, i.e. want FSSpec for currDir
  1561 		     */
  1562 		    fileNamePtr=NULL;
  1563 		}
  1564 	    } else {
  1565 		Tcl_UtfToExternalDString(NULL,&path[lastCheckpoint],
  1566 					 fileNameLen,&nativeds);
  1567 		fileNameLen=Tcl_DStringLength(&nativeds);
  1568 		if(fileNameLen > MAXMACFILENAMELEN) { 
  1569 		    err = bdNamErr;
  1570 		} else {
  1571 		fileName[0]=fileNameLen;
  1572 		strncpy((char *) fileName + 1, Tcl_DStringValue(&nativeds), 
  1573 			fileNameLen);
  1574 		}
  1575 		Tcl_DStringFree(&nativeds);
  1576 	    }
  1577 	    if(err == noErr)
  1578 	    err=FSMakeFSSpecCompat(vRefNum, dirID, fileNamePtr, &fileSpec);
  1579 	    if(err != noErr) {
  1580 		if(err != fnfErr) {
  1581 		    /*
  1582 		     * this can occur if trying to get parent of a root
  1583 		     * volume via '::' or when using an illegal
  1584 		     * filename; revert to last checkpoint and stop
  1585 		     * processing path further
  1586 		     */
  1587 		    err=FSMakeFSSpecCompat(vRefNum, dirID, NULL, &fileSpec);
  1588 		    if(err != noErr) {
  1589 			/* should never happen, bail out */
  1590 			return firstCheckpoint; 
  1591 		    }
  1592 		    nextCheckpoint=lastCheckpoint;
  1593 		    cur = path[lastCheckpoint];
  1594 		}
  1595     		break; /* arrived at nonexistent file or dir */
  1596 	    } else {
  1597 		/* fileSpec could point to an alias, resolve it */
  1598 		lastFileSpec=fileSpec;
  1599 		err = ResolveAliasFile(&fileSpec, true, &isDirectory, 
  1600 				       &wasAlias);
  1601 		if (err != noErr || !isDirectory) {
  1602 		    break; /* fileSpec doesn't point to a dir */
  1603 		}
  1604 	    }
  1605 	    if (cur == 0) break; /* arrived at end of path */
  1606 	    
  1607 	    /* fileSpec points to possibly nonexisting subdirectory; validate */
  1608 	    err = FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory);
  1609 	    if (err != noErr || !isDirectory) {
  1610 	        break; /* fileSpec doesn't point to existing dir */
  1611 	    }
  1612 	    vRefNum = fileSpec.vRefNum;
  1613     	
  1614 	    /* found a new valid subdir in path, continue processing path */
  1615 	    lastCheckpoint=nextCheckpoint+1;
  1616 	}
  1617 	wasAlias=FALSE;
  1618 	nextCheckpoint++;
  1619     }
  1620     
  1621     if (wasAlias)
  1622     	fileSpec=lastFileSpec;
  1623     
  1624     /*
  1625      * fileSpec now points to a possibly nonexisting file or dir
  1626      *  inside a valid dir; get full path name to it
  1627      */
  1628     
  1629     err=FSpPathFromLocation(&fileSpec, &newPathLen, &newPathHandle);
  1630     if(err != noErr) {
  1631 	return firstCheckpoint; /* should not see any errors here, bail out */
  1632     }
  1633     
  1634     HLock(newPathHandle);
  1635     Tcl_ExternalToUtfDString(NULL,*newPathHandle,newPathLen,&nativeds);
  1636     if (cur != 0) {
  1637 	/* not at end, append remaining path */
  1638     	if ( newPathLen==0 || (*(*newPathHandle+(newPathLen-1))!=':' && path[nextCheckpoint] !=':')) {
  1639 	    Tcl_DStringAppend(&nativeds, ":" , 1);
  1640 	}
  1641 	Tcl_DStringAppend(&nativeds, &path[nextCheckpoint], 
  1642 			  strlen(&path[nextCheckpoint]));
  1643     }
  1644     DisposeHandle(newPathHandle);
  1645     
  1646     fileNameLen=Tcl_DStringLength(&nativeds);
  1647     Tcl_SetStringObj(pathPtr,Tcl_DStringValue(&nativeds),fileNameLen);
  1648     Tcl_DStringFree(&nativeds);
  1649     
  1650     return nextCheckpoint+(fileNameLen-origPathLen);
  1651 }
  1652