--- /dev/null
+/*
+** Apple Macintosh Developer Technical Support
+**
+** FileCopy: A robust, general purpose file copy routine.
+**
+** by Jim Luther, Apple Developer Technical Support Emeritus
+**
+** File: FileCopy.c
+**
+** Copyright © 1992-1998 Apple Computer, Inc.
+** All rights reserved.
+**
+** You may incorporate this sample code into your applications without
+** restriction, though the sample code has been provided "AS IS" and the
+** responsibility for its operation is 100% yours. However, what you are
+** not permitted to do is to redistribute the source as "DSC Sample Code"
+** after having made changes. If you're going to re-distribute the source,
+** we require that you make it clear in the source that the code was
+** descended from Apple Sample Code, but that you've made changes.
+*/
+
+#include <Types.h>
+#include <Errors.h>
+#include <Memory.h>
+#include <Files.h>
+
+#define __COMPILINGMOREFILES
+
+#include "MoreFile.h"
+#include "MoreExtr.h"
+#include "MoreDesk.h"
+#include "FileCopy.h"
+
+/*****************************************************************************/
+
+/* local constants */
+
+/* The deny-mode privileges to use when opening the source and destination files. */
+
+enum
+{
+ srcCopyMode = dmRdDenyWr,
+ dstCopyMode = dmWrDenyRdWr
+};
+
+/* The largest (16K) and smallest (.5K) copy buffer to use if the caller doesn't supply
+** their own copy buffer. */
+
+enum
+{
+ bigCopyBuffSize = 0x00004000,
+ minCopyBuffSize = 0x00000200
+};
+
+/*****************************************************************************/
+
+/* static prototypes */
+
+static OSErr GetDestinationDirInfo(short vRefNum,
+ long dirID,
+ ConstStr255Param name,
+ long *theDirID,
+ Boolean *isDirectory,
+ Boolean *isDropBox);
+/* GetDestinationDirInfo tells us if the destination is a directory, it's
+ directory ID, and if it's an AppleShare drop box (write privileges only --
+ no read or search privileges).
+ vRefNum input: Volume specification.
+ dirID input: Directory ID.
+ name input: Pointer to object name, or nil when dirID
+ specifies a directory that's the object.
+ theDirID output: If the object is a file, then its parent directory
+ ID. If the object is a directory, then its ID.
+ isDirectory output: True if object is a directory; false if
+ object is a file.
+ isDropBox output: True if directory is an AppleShare drop box.
+*/
+
+static OSErr CheckForForks(short vRefNum,
+ long dirID,
+ ConstStr255Param name,
+ Boolean *hasDataFork,
+ Boolean *hasResourceFork);
+/* CheckForForks tells us if there is a data or resource fork to copy.
+ vRefNum input: Volume specification of the file's current
+ location.
+ dirID input: Directory ID of the file's current location.
+ name input: The name of the file.
+*/
+
+static OSErr PreflightFileCopySpace(short srcVRefNum,
+ long srcDirID,
+ ConstStr255Param srcName,
+ ConstStr255Param dstVolName,
+ short dstVRefNum,
+ Boolean *spaceOK);
+/* PreflightFileCopySpace determines if there's enough space on a
+ volume to copy the specified file to that volume.
+ Note: The results of this routine are not perfect. For example if the
+ volume's catalog or extents overflow file grows when the new file is
+ created, more allocation blocks may be needed beyond those needed for
+ the file's data and resource forks.
+
+ srcVRefNum input: Volume specification of the file's current
+ location.
+ srcDirID input: Directory ID of the file's current location.
+ srcName input: The name of the file.
+ dstVolName input: A pointer to the name of the volume where
+ the file will be copied or NULL.
+ dstVRefNum input: Volume specification indicating the volume
+ where the file will be copied.
+ spaceOK output: true if there's enough space on the volume for
+ the file's data and resource forks.
+*/
+
+/*****************************************************************************/
+
+static OSErr GetDestinationDirInfo(short vRefNum,
+ long dirID,
+ ConstStr255Param name,
+ long *theDirID,
+ Boolean *isDirectory,
+ Boolean *isDropBox)
+{
+ CInfoPBRec pb;
+ OSErr error;
+
+ pb.dirInfo.ioACUser = 0; /* ioACUser used to be filler2, clear it before calling GetCatInfo */
+ error = GetCatInfoNoName(vRefNum, dirID, name, &pb);
+ *theDirID = pb.dirInfo.ioDrDirID;
+ *isDirectory = (pb.dirInfo.ioFlAttrib & ioDirMask) != 0;
+ /* see if access priviledges are make changes, not see folder, and not see files (drop box) */
+ *isDropBox = ((pb.dirInfo.ioACUser & 0x07) == 0x03);
+
+ return ( error );
+}
+
+/*****************************************************************************/
+
+static OSErr CheckForForks(short vRefNum,
+ long dirID,
+ ConstStr255Param name,
+ Boolean *hasDataFork,
+ Boolean *hasResourceFork)
+{
+ HParamBlockRec pb;
+ OSErr error;
+
+ pb.fileParam.ioNamePtr = (StringPtr)name;
+ pb.fileParam.ioVRefNum = vRefNum;
+ pb.fileParam.ioFVersNum = 0;
+ pb.fileParam.ioDirID = dirID;
+ pb.fileParam.ioFDirIndex = 0;
+ error = PBHGetFInfoSync(&pb);
+ *hasDataFork = (pb.fileParam.ioFlLgLen != 0);
+ *hasResourceFork = (pb.fileParam.ioFlRLgLen != 0);
+
+ return ( error );
+}
+
+/*****************************************************************************/
+
+static OSErr PreflightFileCopySpace(short srcVRefNum,
+ long srcDirID,
+ ConstStr255Param srcName,
+ ConstStr255Param dstVolName,
+ short dstVRefNum,
+ Boolean *spaceOK)
+{
+ UniversalFMPB pb;
+ OSErr error;
+ unsigned long dstFreeBlocks;
+ unsigned long dstBlksPerAllocBlk;
+ unsigned long srcDataBlks;
+ unsigned long srcResourceBlks;
+
+ error = XGetVolumeInfoNoName(dstVolName, dstVRefNum, &pb.xPB);
+ if ( error == noErr )
+ {
+ /* get allocation block size (always multiple of 512) and divide by 512
+ to get number of 512-byte blocks per allocation block */
+ dstBlksPerAllocBlk = ((unsigned long)pb.xPB.ioVAlBlkSiz >> 9);
+
+ /* Convert freeBytes to free disk blocks (512-byte blocks) */
+ dstFreeBlocks = (pb.xPB.ioVFreeBytes.hi << 23) + (pb.xPB.ioVFreeBytes.lo >> 9);
+
+ /* Now, get the size of the file's data resource forks */
+ pb.hPB.fileParam.ioNamePtr = (StringPtr)srcName;
+ pb.hPB.fileParam.ioVRefNum = srcVRefNum;
+ pb.hPB.fileParam.ioFVersNum = 0;
+ pb.hPB.fileParam.ioDirID = srcDirID;
+ pb.hPB.fileParam.ioFDirIndex = 0;
+ error = PBHGetFInfoSync(&pb.hPB);
+ if ( error == noErr )
+ {
+ /* Since space on Mac OS disks is always allocated in allocation blocks, */
+ /* this code takes into account rounding up to the end of an allocation block. */
+
+ /* get number of 512-byte blocks needed for data fork */
+ if ( ((unsigned long)pb.hPB.fileParam.ioFlLgLen & 0x000001ff) != 0 )
+ {
+ srcDataBlks = ((unsigned long)pb.hPB.fileParam.ioFlLgLen >> 9) + 1;
+ }
+ else
+ {
+ srcDataBlks = (unsigned long)pb.hPB.fileParam.ioFlLgLen >> 9;
+ }
+
+ /* now, calculate number of new allocation blocks needed */
+ if ( srcDataBlks % dstBlksPerAllocBlk )
+ {
+ srcDataBlks = (srcDataBlks / dstBlksPerAllocBlk) + 1;
+ }
+ else
+ {
+ srcDataBlks /= dstBlksPerAllocBlk;
+ }
+
+ /* get number of 512-byte blocks needed for resource fork */
+ if ( ((unsigned long)pb.hPB.fileParam.ioFlRLgLen & 0x000001ff) != 0 )
+ {
+ srcResourceBlks = ((unsigned long)pb.hPB.fileParam.ioFlRLgLen >> 9) + 1;
+ }
+ else
+ {
+ srcResourceBlks = (unsigned long)pb.hPB.fileParam.ioFlRLgLen >> 9;
+ }
+
+ /* now, calculate number of new allocation blocks needed */
+ if ( srcResourceBlks % dstBlksPerAllocBlk )
+ {
+ srcResourceBlks = (srcResourceBlks / dstBlksPerAllocBlk) + 1;
+ }
+ else
+ {
+ srcResourceBlks /= dstBlksPerAllocBlk;
+ }
+
+ /* Is there enough room on the destination volume for the source file? */
+ *spaceOK = ( ((srcDataBlks + srcResourceBlks) * dstBlksPerAllocBlk) <= dstFreeBlocks );
+ }
+ }
+
+ return ( error );
+}
+
+/*****************************************************************************/
+
+pascal OSErr FileCopy(short srcVRefNum,
+ long srcDirID,
+ ConstStr255Param srcName,
+ short dstVRefNum,
+ long dstDirID,
+ ConstStr255Param dstPathname,
+ ConstStr255Param copyName,
+ void *copyBufferPtr,
+ long copyBufferSize,
+ Boolean preflight)
+{
+ OSErr err;
+
+ short srcRefNum = 0, /* 0 when source data and resource fork are closed */
+ dstDataRefNum = 0, /* 0 when destination data fork is closed */
+ dstRsrcRefNum = 0; /* 0 when destination resource fork is closed */
+
+ Str63 dstName; /* The filename of the destination. It might be the
+ ** source filename, it might be a new name... */
+
+ GetVolParmsInfoBuffer infoBuffer; /* Where PBGetVolParms dumps its info */
+ long srcServerAdr; /* AppleTalk server address of source (if any) */
+
+ Boolean dstCreated = false, /* true when destination file has been created */
+ ourCopyBuffer = false, /* true if we had to allocate the copy buffer */
+ isDirectory, /* true if destination is really a directory */
+ isDropBox; /* true if destination is an AppleShare drop box */
+
+ long tempLong;
+ short tempInt;
+
+ Boolean spaceOK; /* true if there's enough room to copy the file to the destination volume */
+
+ Boolean hasDataFork;
+ Boolean hasResourceFork;
+
+ /* Preflight for size */
+ if ( preflight )
+ {
+ err = PreflightFileCopySpace(srcVRefNum, srcDirID, srcName,
+ dstPathname, dstVRefNum, &spaceOK);
+ if ( err != noErr )
+ {
+ return ( err );
+ }
+
+ if ( !spaceOK )
+ {
+ return ( dskFulErr );
+ }
+ }
+
+ /* get the destination's real dirID and make sure it really is a directory */
+ err = GetDestinationDirInfo(dstVRefNum, dstDirID, dstPathname,
+ &dstDirID, &isDirectory, &isDropBox);
+ if ( err != noErr )
+ {
+ goto ErrorExit;
+ }
+
+ if ( !isDirectory )
+ {
+ return ( dirNFErr );
+ }
+
+ /* get the destination's real vRefNum */
+ err = DetermineVRefNum(dstPathname, dstVRefNum, &dstVRefNum);
+ if ( err != noErr )
+ {
+ goto ErrorExit;
+ }
+
+ /* See if PBHCopyFile can be used. Using PBHCopyFile saves time by letting the file server
+ ** copy the file if the source and destination locations are on the same file server. */
+ tempLong = sizeof(infoBuffer);
+ err = HGetVolParms(srcName, srcVRefNum, &infoBuffer, &tempLong);
+ if ( (err != noErr) && (err != paramErr) )
+ {
+ return ( err );
+ }
+
+ if ( (err != paramErr) && hasCopyFile(infoBuffer) )
+ {
+ /* The source volume supports PBHCopyFile. */
+ srcServerAdr = infoBuffer.vMServerAdr;
+
+ /* Now, see if the destination volume is on the same file server. */
+ tempLong = sizeof(infoBuffer);
+ err = HGetVolParms(NULL, dstVRefNum, &infoBuffer, &tempLong);
+ if ( (err != noErr) && (err != paramErr) )
+ {
+ return ( err );
+ }
+ if ( (err != paramErr) && (srcServerAdr == infoBuffer.vMServerAdr) )
+ {
+ /* Source and Dest are on same server and PBHCopyFile is supported. Copy with CopyFile. */
+ err = HCopyFile(srcVRefNum, srcDirID, srcName, dstVRefNum, dstDirID, NULL, copyName);
+ if ( err != noErr )
+ {
+ return ( err );
+ }
+
+ /* AppleShare's CopyFile clears the isAlias bit, so I still need to attempt to copy
+ the File's attributes to attempt to get things right. */
+ if ( copyName != NULL ) /* Did caller supply copy file name? */
+ {
+ /* Yes, use the caller supplied copy file name. */
+ (void) CopyFileMgrAttributes(srcVRefNum, srcDirID, srcName,
+ dstVRefNum, dstDirID, copyName, true);
+ }
+ else
+ {
+ /* They didn't, so get the source file name and use it. */
+ if ( GetFilenameFromPathname(srcName, dstName) == noErr )
+ {
+ /* */
+ (void) CopyFileMgrAttributes(srcVRefNum, srcDirID, srcName,
+ dstVRefNum, dstDirID, dstName, true);
+ }
+ }
+ return ( err );
+ }
+ }
+
+ /* If we're here, then PBHCopyFile couldn't be used so we have to copy the file by hand. */
+
+ /* Make sure a copy buffer is allocated. */
+ if ( copyBufferPtr == NULL )
+ {
+ /* The caller didn't supply a copy buffer so grab one from the application heap.
+ ** Try to get a big copy buffer, if we can't, try for a 512-byte buffer.
+ ** If 512 bytes aren't available, we're in trouble. */
+ copyBufferSize = bigCopyBuffSize;
+ copyBufferPtr = NewPtr(copyBufferSize);
+ if ( copyBufferPtr == NULL )
+ {
+ copyBufferSize = minCopyBuffSize;
+ copyBufferPtr = NewPtr(copyBufferSize);
+ if ( copyBufferPtr == NULL )
+ {
+ return ( memFullErr );
+ }
+ }
+ ourCopyBuffer = true;
+ }
+
+ /* Open the source data fork. */
+ err = HOpenAware(srcVRefNum, srcDirID, srcName, srcCopyMode, &srcRefNum);
+ if ( err != noErr )
+ return ( err );
+
+ /* Once a file is opened, we have to exit via ErrorExit to make sure things are cleaned up */
+
+ /* See if the copy will be renamed. */
+ if ( copyName != NULL ) /* Did caller supply copy file name? */
+ BlockMoveData(copyName, dstName, copyName[0] + 1); /* Yes, use the caller supplied copy file name. */
+ else
+ { /* They didn't, so get the source file name and use it. */
+ err = GetFileLocation(srcRefNum, &tempInt, &tempLong, dstName);
+ if ( err != noErr )
+ {
+ goto ErrorExit;
+ }
+ }
+
+ /* Create the destination file. */
+ err = HCreateMinimum(dstVRefNum, dstDirID, dstName);
+ if ( err != noErr )
+ {
+ goto ErrorExit;
+ }
+ dstCreated = true; /* After creating the destination file, any
+ ** error conditions should delete the destination file */
+
+ /* An AppleShare dropbox folder is a folder for which the user has the Make Changes
+ ** privilege (write access), but not See Files (read access) and See Folders (search access).
+ ** Copying a file into an AppleShare dropbox presents some special problems. Here are the
+ ** rules we have to follow to copy a file into a dropbox:
+ ** ¥ File attributes can be changed only when both forks of a file are empty.
+ ** ¥ DeskTop Manager comments can be added to a file only when both forks of a file
+ ** are empty.
+ ** ¥ A fork can be opened for write access only when both forks of a file are empty.
+ ** So, with those rules to live with, we'll do those operations now while both forks
+ ** are empty. */
+
+ if ( isDropBox )
+ {
+ /* We only set the file attributes now if the file is being copied into a
+ ** drop box. In all other cases, it is better to set the attributes last
+ ** so that if FileCopy is modified to give up time to other processes
+ ** periodicly, the Finder won't try to read any bundle information (because
+ ** the bundle-bit will still be clear) from a partially copied file. If the
+ ** copy is into a drop box, we have to set the attributes now, but since the
+ ** destination forks are opened with write/deny-read/deny-write permissions,
+ ** any Finder that might see the file in the drop box won't be able to open
+ ** its resource fork until the resource fork is closed.
+ **
+ ** Note: if you do modify FileCopy to give up time to other processes, don't
+ ** give up time between the time the destination file is created (above) and
+ ** the time both forks are opened (below). That way, you stand the best chance
+ ** of making sure the Finder doesn't read a partially copied resource fork.
+ */
+ /* Copy attributes but don't lock the destination. */
+ err = CopyFileMgrAttributes(srcVRefNum, srcDirID, srcName,
+ dstVRefNum, dstDirID, dstName, false);
+ if ( err != noErr )
+ {
+ goto ErrorExit;
+ }
+ }
+
+ /* Attempt to copy the comments while both forks are empty.
+ ** Ignore the result because we really don't care if it worked or not. */
+ (void) DTCopyComment(srcVRefNum, srcDirID, srcName, dstVRefNum, dstDirID, dstName);
+
+ /* See which forks we need to copy. By doing this, we won't create a data or resource fork
+ ** for the destination unless it's really needed (some foreign file systems such as
+ ** the ProDOS File System and Macintosh PC Exchange have to create additional disk
+ ** structures to support resource forks). */
+ err = CheckForForks(srcVRefNum, srcDirID, srcName, &hasDataFork, &hasResourceFork);
+ if ( err != noErr )
+ {
+ goto ErrorExit;
+ }
+
+ if ( hasDataFork )
+ {
+ /* Open the destination data fork. */
+ err = HOpenAware(dstVRefNum, dstDirID, dstName, dstCopyMode, &dstDataRefNum);
+ if ( err != noErr )
+ {
+ goto ErrorExit;
+ }
+ }
+
+ if ( hasResourceFork )
+ {
+ /* Open the destination resource fork. */
+ err = HOpenRFAware(dstVRefNum, dstDirID, dstName, dstCopyMode, &dstRsrcRefNum);
+ if ( err != noErr )
+ {
+ goto ErrorExit;
+ }
+ }
+
+ if ( hasDataFork )
+ {
+ /* Copy the data fork. */
+ err = CopyFork(srcRefNum, dstDataRefNum, copyBufferPtr, copyBufferSize);
+ if ( err != noErr )
+ {
+ goto ErrorExit;
+ }
+
+ /* Close both data forks and clear reference numbers. */
+ (void) FSClose(srcRefNum);
+ (void) FSClose(dstDataRefNum);
+ srcRefNum = dstDataRefNum = 0;
+ }
+ else
+ {
+ /* Close the source data fork since it was opened earlier */
+ (void) FSClose(srcRefNum);
+ srcRefNum = 0;
+ }
+
+ if ( hasResourceFork )
+ {
+ /* Open the source resource fork. */
+ err = HOpenRFAware(srcVRefNum, srcDirID, srcName, srcCopyMode, &srcRefNum);
+ if ( err != noErr )
+ {
+ goto ErrorExit;
+ }
+
+ /* Copy the resource fork. */
+ err = CopyFork(srcRefNum, dstRsrcRefNum, copyBufferPtr, copyBufferSize);
+ if ( err != noErr )
+ {
+ goto ErrorExit;
+ }
+
+ /* Close both resource forks and clear reference numbers. */
+ (void) FSClose(srcRefNum);
+ (void) FSClose(dstRsrcRefNum);
+ srcRefNum = dstRsrcRefNum = 0;
+ }
+
+ /* Get rid of the copy buffer if we allocated it. */
+ if ( ourCopyBuffer )
+ {
+ DisposePtr((Ptr)copyBufferPtr);
+ }
+
+ /* Attempt to copy attributes again to set mod date. Copy lock condition this time
+ ** since we're done with the copy operation. This operation will fail if we're copying
+ ** into an AppleShare dropbox, so we don't check for error conditions. */
+ CopyFileMgrAttributes(srcVRefNum, srcDirID, srcName,
+ dstVRefNum, dstDirID, dstName, true);
+
+ /* Hey, we did it! */
+ return ( noErr );
+
+ErrorExit:
+ if ( srcRefNum != 0 )
+ {
+ (void) FSClose(srcRefNum); /* Close the source file */
+ }
+ if ( dstDataRefNum != 0 )
+ {
+ (void) FSClose(dstDataRefNum); /* Close the destination file data fork */
+ }
+ if ( dstRsrcRefNum != 0 )
+ {
+ (void) FSClose(dstRsrcRefNum); /* Close the destination file resource fork */
+ }
+ if ( dstCreated )
+ {
+ (void) HDelete(dstVRefNum, dstDirID, dstName); /* Delete dest file. This may fail if the file
+ is in a "drop folder" */
+ }
+ if ( ourCopyBuffer ) /* dispose of any memory we allocated */
+ {
+ DisposePtr((Ptr)copyBufferPtr);
+ }
+
+ return ( err );
+}
+
+/*****************************************************************************/
+
+pascal OSErr FSpFileCopy(const FSSpec *srcSpec,
+ const FSSpec *dstSpec,
+ ConstStr255Param copyName,
+ void *copyBufferPtr,
+ long copyBufferSize,
+ Boolean preflight)
+{
+ return ( FileCopy(srcSpec->vRefNum, srcSpec->parID, srcSpec->name,
+ dstSpec->vRefNum, dstSpec->parID, dstSpec->name,
+ copyName, copyBufferPtr, copyBufferSize, preflight) );
+}
+
+/*****************************************************************************/
+