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

通讯编程

开发平台:

Visual C++

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