/* File: FSCopyObject.c Contains: A Copy/Delete Files/Folders engine which uses the HFS+ API's. This code is a combination of MoreFilesX and MPFileCopy with some added features. This code will run on OS 9.1 and up and 10.1.x (Classic and Carbon) */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Communicator client code, released * March 31, 1998. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 2000 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // Modified 2006-01-23 - added this comment. #include "FSCopyObject.h" #include #include #include /* */ #pragma mark ----- Tunable Parameters ----- // The following constants control the behavior of the copy engine. enum { // BufferSizeForThisVolumeSpeed // kDefaultCopyBufferSize = 2L * 1024 * 1024, // Fast be not very responsive. kDefaultCopyBufferSize = 256L * 1024, // Slower, but can still use machine. kMaximumCopyBufferSize = 2L * 1024 * 1024, kMinimumCopyBufferSize = 1024 }; enum { // CalculateForksToCopy kExpectedForkCount = 10 // Number of non-classic forks we expect. }; // (i.e. non resource/data forks) enum { // CheckForDestInsideSource errFSDestInsideSource = -1234 }; enum { // for use with PBHGetDirAccess in IsDropBox kPrivilegesMask = kioACAccessUserWriteMask | kioACAccessUserReadMask | kioACAccessUserSearchMask, // for use with FSGetCatalogInfo and FSPermissionInfo->mode // from sys/stat.h... note -- sys/stat.h definitions are in octal // // You can use these values to adjust the users/groups permissions // on a file/folder with FSSetCatalogInfo and extracting the // kFSCatInfoPermissions field. See code below for examples kRWXUserAccessMask = 0x01C0, kReadAccessUser = 0x0100, kWriteAccessUser = 0x0080, kExecuteAccessUser = 0x0040, kRWXGroupAccessMask = 0x0038, kReadAccessGroup = 0x0020, kWriteAccessGroup = 0x0010, kExecuteAccessGroup = 0x0008, kRWXOtherAccessMask = 0x0007, kReadAccessOther = 0x0004, kWriteAccessOther = 0x0002, kExecuteAccessOther = 0x0001, kDropFolderValue = kWriteAccessOther | kExecuteAccessOther }; #pragma mark ----- Struct Definitions ----- #define VolHasCopyFile(volParms) \ (((volParms)->vMAttrib & (1L << bHasCopyFile)) != 0) // The CopyParams data structure holds the copy buffer used // when copying the forks over, as well as special case // info on the destination struct CopyParams { UTCDateTime magicBusyCreateDate; void *copyBuffer; ByteCount copyBufferSize; Boolean copyingToDropFolder; Boolean copyingToLocalVolume; }; typedef struct CopyParams CopyParams; // The FilterParams data structure holds the date and info // that the caller wants passed into the Filter Proc, as well // as the Filter Proc Pointer itself struct FilterParams { FSCatalogInfoBitmap whichInfo; CopyObjectFilterProcPtr filterProcPtr; FSSpec fileSpec; FSSpec *fileSpecPtr; HFSUniStr255 fileName; HFSUniStr255 *fileNamePtr; void *yourDataPtr; }; typedef struct FilterParams FilterParams; // The ForkTracker data structure holds information about a specific fork, // specifically the name and the refnum. We use this to build a list of // all the forks before we start copying them. We need to do this because, // if we're copying into a drop folder, we must open all the forks before // we start copying data into any of them. // Plus it's a convenient way to keep track of all the forks... struct ForkTracker { HFSUniStr255 forkName; SInt64 forkSize; SInt16 forkDestRefNum; }; typedef struct ForkTracker ForkTracker; typedef ForkTracker *ForkTrackerPtr; // The FSCopyObjectGlobals data structure holds information needed to do // the recursive copy of a directory. struct FSCopyObjectGlobals { FSCatalogInfo catalogInfo; FSRef ref; /* FSRef to the source file/folder*/ FSRef destRef; /* FSRef to the destination directory */ CopyParams *copyParams; /* pointer to info needed to do the copy */ FilterParams *filterParams; /* pointer to info needed for the optional filter proc */ ItemCount maxLevels; /* maximum levels to iterate through */ ItemCount currentLevel; /* the current level FSCopyFolderLevel is on */ Boolean quitFlag; /* set to true if filter wants to kill interation */ Boolean containerChanged; /* temporary - set to true if the current container changed during iteration */ OSErr result; /* result */ ItemCount actualObjects; /* number of objects returned */ }; typedef struct FSCopyObjectGlobals FSCopyObjectGlobals; // The FSDeleteObjectGlobals data structure holds information needed to // recursively delete a directory struct FSDeleteObjectGlobals { FSCatalogInfo catalogInfo; /* FSCatalogInfo */ ItemCount actualObjects; /* number of objects returned */ OSErr result; /* result */ }; typedef struct FSDeleteObjectGlobals FSDeleteObjectGlobals; #pragma mark ----- Local Prototypes ----- static OSErr FSCopyFile( const FSRef *source, const FSRef *destDir, const HFSUniStr255 *destName, /* can be NULL (no rename during copy) */ CopyParams *copyParams, FilterParams *filterParams, FSRef *newFile); /* can be NULL */ static OSErr CopyFile( const FSRef *source, FSCatalogInfo *sourceCatInfo, const FSRef *destDir, const HFSUniStr255 *destName, CopyParams *copyParams, FSRef *newRef); /* can be NULL */ static OSErr FSUsePBHCopyFile( const FSRef *srcFileRef, const FSRef *dstDirectoryRef, UniCharCount nameLength, const UniChar *copyName, /* can be NULL (no rename during copy) */ TextEncoding textEncodingHint, FSRef *newRef); /* can be NULL */ static OSErr DoCopyFile( const FSRef *source, FSCatalogInfo *sourceCatInfo, const FSRef *destDir, const HFSUniStr255 *destName, CopyParams *params, FSRef *newRef); /* can be NULL */ static OSErr FSCopyFolder( const FSRef *source, const FSRef *destDir, const HFSUniStr255 *destName, /* can be NULL (no rename during copy) */ CopyParams* copyParams, FilterParams *filterParams, ItemCount maxLevels, FSRef* newDir); /* can be NULL */ static OSErr FSCopyFolderLevel( FSCopyObjectGlobals *theGlobals, const HFSUniStr255 *destName ); static OSErr CheckForDestInsideSource( const FSRef *source, const FSRef *destDir); static OSErr CopyItemsForks( const FSRef *source, const FSRef *dest, CopyParams *params); static OSErr OpenAllForks( const FSRef *dest, const ForkTrackerPtr dataFork, const ForkTrackerPtr rsrcFork, ForkTrackerPtr otherForks, ItemCount otherForksCount); static OSErr CopyFork( const FSRef *source, const FSRef *dest, const ForkTrackerPtr sourceFork, const CopyParams *params); static OSErr CloseAllForks( SInt16 dataRefNum, SInt16 rsrcRefNum, ForkTrackerPtr otherForks, ItemCount otherForksCount); static OSErr CalculateForksToCopy( const FSRef *source, const ForkTrackerPtr dataFork, const ForkTrackerPtr rsrcFork, ForkTrackerPtr *otherForksParam, ItemCount *otherForksCountParam); static OSErr CalculateBufferSize( const FSRef *source, const FSRef *destDir, ByteCount * bufferSize); static ByteCount BufferSizeForThisVolume(FSVolumeRefNum vRefNum); static ByteCount BufferSizeForThisVolumeSpeed(UInt32 volumeBytesPerSecond); static OSErr IsDropBox( const FSRef* source, Boolean *isDropBox); static OSErr GetMagicBusyCreationDate( UTCDateTime *date ); static Boolean CompareHFSUniStr255(const HFSUniStr255 *lhs, const HFSUniStr255 *rhs); static OSErr FSGetVRefNum( const FSRef *ref, FSVolumeRefNum *vRefNum); static OSErr FSGetVolParms( FSVolumeRefNum volRefNum, UInt32 bufferSize, GetVolParmsInfoBuffer *volParmsInfo, UInt32 *actualInfoSize); /* Can Be NULL */ static OSErr UnicodeNameGetHFSName( UniCharCount nameLength, const UniChar *name, TextEncoding textEncodingHint, Boolean isVolumeName, Str31 hfsName); static OSErr FSMakeFSRef( FSVolumeRefNum volRefNum, SInt32 dirID, ConstStr255Param name, FSRef *ref); static OSErr FSDeleteFolder( const FSRef *container ); static void FSDeleteFolderLevel( const FSRef *container, FSDeleteObjectGlobals *theGlobals); /*****************************************************************************/ /*****************************************************************************/ /*****************************************************************************/ #pragma mark ----- Copy Objects ----- // This routine acts as the top level of the copy engine. It exists // to a) present a nicer API than the various recursive routines, and // b) minimise the local variables in the recursive routines. OSErr FSCopyObject( const FSRef *source, const FSRef *destDir, UniCharCount nameLength, const UniChar *copyName, // can be NULL (no rename during copy) ItemCount maxLevels, FSCatalogInfoBitmap whichInfo, Boolean wantFSSpec, Boolean wantName, CopyObjectFilterProcPtr filterProcPtr, // can be NULL void *yourDataPtr, // can be NULL FSRef *newObject) // can be NULL { CopyParams copyParams; FilterParams filterParams; HFSUniStr255 destName; HFSUniStr255 *destNamePtr; Boolean isDirectory; OSErr osErr = ( source != NULL && destDir != NULL ) ? noErr : paramErr; if (copyName) { if (nameLength <= 255) { BlockMoveData(copyName, destName.unicode, nameLength * sizeof(UniChar)); destName.length = nameLength; destNamePtr = &destName; } else osErr = paramErr; } else destNamePtr = NULL; // we want the settable info no matter what the user asked for filterParams.whichInfo = whichInfo | kFSCatInfoSettableInfo; filterParams.filterProcPtr = filterProcPtr; filterParams.fileSpecPtr = ( wantFSSpec ) ? &filterParams.fileSpec : NULL; filterParams.fileNamePtr = ( wantName ) ? &filterParams.fileName : NULL; filterParams.yourDataPtr = yourDataPtr; // Calculate the optimal buffer size to copy the forks over // and create the buffer if( osErr == noErr ) osErr = CalculateBufferSize( source, destDir, ©Params.copyBufferSize); if( osErr == noErr ) { copyParams.copyBuffer = NewPtr( copyParams.copyBufferSize ); if( copyParams.copyBuffer == NULL ) osErr = memFullErr; } if( osErr == noErr ) osErr = GetMagicBusyCreationDate( ©Params.magicBusyCreateDate ); if( osErr == noErr ) // figure out if source is a file or folder { // if it is on a local volume, // if destination is a drop box GetVolParmsInfoBuffer volParms; FSCatalogInfo tmpCatInfo; FSVolumeRefNum destVRefNum; // to figure out if the souce is a folder or directory osErr = FSGetCatalogInfo(source, kFSCatInfoNodeFlags, &tmpCatInfo, NULL, NULL, NULL); if( osErr == noErr ) { isDirectory = ((tmpCatInfo.nodeFlags & kFSNodeIsDirectoryMask) != 0); // are we copying to a drop folder? osErr = IsDropBox( destDir, ©Params.copyingToDropFolder ); } if( osErr == noErr ) osErr = FSGetVRefNum(destDir, &destVRefNum); if( osErr == noErr ) osErr = FSGetVolParms( destVRefNum, sizeof(volParms), &volParms, NULL ); if( osErr == noErr ) // volParms.vMServerAdr is non-zero for remote volumes copyParams.copyingToLocalVolume = (volParms.vMServerAdr == 0); } // now copy the file/folder... if( osErr == noErr ) { // is it a folder? if ( isDirectory ) { // yes osErr = CheckForDestInsideSource(source, destDir); if( osErr == noErr ) osErr = FSCopyFolder( source, destDir, destNamePtr, ©Params, &filterParams, maxLevels, newObject ); } else // no osErr = FSCopyFile(source, destDir, destNamePtr, ©Params, &filterParams, newObject); } // Clean up for space and safety... Who me? if( copyParams.copyBuffer != NULL ) DisposePtr((char*)copyParams.copyBuffer); mycheck_noerr( osErr ); // put up debug assert in debug builds return osErr; } /*****************************************************************************/ #pragma mark ----- Copy Files ----- OSErr FSCopyFile( const FSRef *source, const FSRef *destDir, const HFSUniStr255 *destName, CopyParams *copyParams, FilterParams *filterParams, FSRef *newFile) { FSCatalogInfo sourceCatInfo; FSRef tmpRef; OSErr osErr = ( source != NULL && destDir != NULL && copyParams != NULL && filterParams != NULL ) ? noErr : paramErr; // get needed info about the source file if ( osErr == noErr ) { if (destName) { osErr = FSGetCatalogInfo(source, filterParams->whichInfo, &sourceCatInfo, NULL, NULL, NULL); filterParams->fileName = *destName; } else osErr = FSGetCatalogInfo(source, filterParams->whichInfo, &sourceCatInfo, &filterParams->fileName, NULL, NULL); } if( osErr == noErr ) osErr = CopyFile(source, &sourceCatInfo, destDir, &filterParams->fileName, copyParams, &tmpRef); // Call the IterateFilterProc _after_ the new file was created // even if an error occured if( filterParams->filterProcPtr != NULL ) { (void) CallCopyObjectFilterProc(filterParams->filterProcPtr, false, 0, osErr, &sourceCatInfo, &tmpRef, filterParams->fileSpecPtr, filterParams->fileNamePtr, filterParams->yourDataPtr); } if( osErr == noErr && newFile != NULL ) *newFile = tmpRef; mycheck_noerr(osErr); // put up debug assert in debug builds return osErr; } /*****************************************************************************/ OSErr CopyFile( const FSRef *source, FSCatalogInfo *sourceCatInfo, const FSRef *destDir, ConstHFSUniStr255Param destName, CopyParams *params, FSRef* newFile) { OSErr osErr = paramErr; // Clear the "inited" bit so that the Finder positions the icon for us. ((FInfo *)(sourceCatInfo->finderInfo))->fdFlags &= ~kHasBeenInited; // if the destination is on a remote volume, try to use PBHCopyFile if( params->copyingToLocalVolume == 0 ) osErr = FSUsePBHCopyFile( source, destDir, 0, NULL, kTextEncodingUnknown, newFile ); // if PBHCopyFile didn't work or not supported, if( osErr != noErr ) // then try old school file transfer osErr = DoCopyFile( source, sourceCatInfo, destDir, destName, params, newFile ); mycheck_noerr(osErr); // put up debug assert in debug builds return osErr; } /*****************************************************************************/ OSErr FSUsePBHCopyFile( const FSRef *srcFileRef, const FSRef *dstDirectoryRef, UniCharCount nameLength, const UniChar *copyName, /* can be NULL (no rename during copy) */ TextEncoding textEncodingHint, FSRef *newRef) /* can be NULL */ { FSSpec srcFileSpec; FSCatalogInfo catalogInfo; GetVolParmsInfoBuffer volParmsInfo; HParamBlockRec pb; Str31 hfsName; OSErr osErr; // get source FSSpec from source FSRef osErr = FSGetCatalogInfo(srcFileRef, kFSCatInfoNone, NULL, NULL, &srcFileSpec, NULL); if( osErr == noErr ) // Make sure the volume supports CopyFile osErr = FSGetVolParms( srcFileSpec.vRefNum, sizeof(GetVolParmsInfoBuffer), &volParmsInfo, NULL); if( osErr == noErr ) osErr = VolHasCopyFile(&volParmsInfo) ? noErr : paramErr; if( osErr == noErr ) // get the destination vRefNum and dirID osErr = FSGetCatalogInfo(dstDirectoryRef, kFSCatInfoVolume | kFSCatInfoNodeID, &catalogInfo, NULL, NULL, NULL); if( osErr == noErr ) // gather all the info needed { pb.copyParam.ioVRefNum = srcFileSpec.vRefNum; pb.copyParam.ioDirID = srcFileSpec.parID; pb.copyParam.ioNamePtr = (StringPtr)srcFileSpec.name; pb.copyParam.ioDstVRefNum = catalogInfo.volume; pb.copyParam.ioNewDirID = (long)catalogInfo.nodeID; pb.copyParam.ioNewName = NULL; if( copyName != NULL ) osErr = UnicodeNameGetHFSName(nameLength, copyName, textEncodingHint, false, hfsName); pb.copyParam.ioCopyName = ( copyName != NULL && osErr == noErr ) ? hfsName : NULL; } if( osErr == noErr ) // tell the server to copy the object osErr = PBHCopyFileSync(&pb); if( osErr == noErr && newRef != NULL ) { myverify_noerr(FSMakeFSRef(pb.copyParam.ioDstVRefNum, pb.copyParam.ioNewDirID, pb.copyParam.ioCopyName, newRef)); } if( osErr != paramErr ) // returning paramErr is ok, it means PBHCopyFileSync was not supported mycheck_noerr(osErr); // put up debug assert in debug builds return osErr; } /*****************************************************************************/ // Copies a file referenced by source to the directory referenced by // destDir. destName is the name the file should be given in the // destination directory. sourceCatInfo is the catalogue info of // the file, which is passed in as an optimization (we could get it // by doing a FSGetCatalogInfo but the caller has already done that // so we might as well take advantage of that). // OSErr DoCopyFile( const FSRef *source, FSCatalogInfo *sourceCatInfo, const FSRef *destDir, ConstHFSUniStr255Param destName, CopyParams *params, FSRef *newRef) { FSRef dest; FSPermissionInfo originalPermissions; UTCDateTime originalCreateDate; OSType originalFileType; UInt16 originalNodeFlags; OSErr osErr; // If we're copying to a drop folder, we won't be able to reset this // information once the copy is done, so we don't mess it up in // the first place. We still clear the locked bit though; items dropped // into a drop folder always become unlocked. if (!params->copyingToDropFolder) { // Remember to clear the file's type, so the Finder doesn't // look at the file until we're done. originalFileType = ((FInfo *) &sourceCatInfo->finderInfo)->fdType; ((FInfo *) &sourceCatInfo->finderInfo)->fdType = kFirstMagicBusyFiletype; // Remember and clear the file's locked status, so that we can // actually write the forks we're about to create. originalNodeFlags = sourceCatInfo->nodeFlags; // Set the file's creation date to kMagicBusyCreationDate, // remembering the old value for restoration later. originalCreateDate = sourceCatInfo->createDate; sourceCatInfo->createDate = params->magicBusyCreateDate; } sourceCatInfo->nodeFlags &= ~kFSNodeLockedMask; // we need to have user level read/write/execute access to the file we are going to create // otherwise FSCreateFileUnicode will return -5000 (afpAccessDenied), // and the FSRef returned will be invalid, yet the file is created (size 0k)... bug? originalPermissions = *((FSPermissionInfo*)sourceCatInfo->permissions); ((FSPermissionInfo*)sourceCatInfo->permissions)->mode |= kRWXUserAccessMask; // Classic only supports 9.1 and higher, so we don't have to worry about 2397324 osErr = FSCreateFileUnicode(destDir, destName->length, destName->unicode, kFSCatInfoSettableInfo, sourceCatInfo, &dest, NULL); if( osErr == noErr ) // Copy the forks over to the new file osErr = CopyItemsForks(source, &dest, params); // Restore the original file type, creation and modification dates, // locked status and permissions. // This is one of the places where we need to handle drop // folders as a special case because this FSSetCatalogInfo will fail for // an item in a drop folder, so we don't even attempt it. if (osErr == noErr && !params->copyingToDropFolder) { ((FInfo *) &sourceCatInfo->finderInfo)->fdType = originalFileType; sourceCatInfo->createDate = originalCreateDate; sourceCatInfo->nodeFlags = originalNodeFlags; *((FSPermissionInfo*)sourceCatInfo->permissions) = originalPermissions; osErr = FSSetCatalogInfo(&dest, kFSCatInfoSettableInfo, sourceCatInfo); } // If we created the file and the copy failed, try to clean up by // deleting the file we created. We do this because, while it's // possible for the copy to fail halfway through and the File Manager // doesn't really clean up that well, we *really* don't wan't // any half-created files being left around. // if the file already existed, we don't want to delete it // // Note that there are cases where the assert can fire which are not // errors (for example, if the destination is in a drop folder) but // I'll leave it in anyway because I'm interested in discovering those // cases. Note that, if this fires and we're running MP, current versions // of MacsBug won't catch the exception and the MP task will terminate // with a kMPTaskAbortedErr error. if (osErr != noErr && osErr != dupFNErr ) myverify_noerr( FSDeleteObjects(&dest) ); else if( newRef != NULL ) // if everything was fine, then return the new file *newRef = dest; mycheck_noerr(osErr); // put up debug assert in debug builds return osErr; } /*****************************************************************************/ #pragma mark ----- Copy Folders ----- OSErr FSCopyFolder( const FSRef *source, const FSRef *destDir, const HFSUniStr255 *destName, CopyParams* copyParams, FilterParams *filterParams, ItemCount maxLevels, FSRef* newDir) { FSCopyObjectGlobals theGlobals; theGlobals.ref = *source; theGlobals.destRef = *destDir; theGlobals.copyParams = copyParams; theGlobals.filterParams = filterParams; theGlobals.maxLevels = maxLevels; theGlobals.currentLevel = 0; theGlobals.quitFlag = false; theGlobals.containerChanged = false; theGlobals.result = ( source != NULL && destDir != NULL && copyParams != NULL && filterParams != NULL ) ? noErr : paramErr; theGlobals.actualObjects = 0; // here we go into recursion land... if( theGlobals.result == noErr ) theGlobals.result = FSCopyFolderLevel(&theGlobals, destName); if( theGlobals.result == noErr && newDir != NULL) *newDir = theGlobals.ref; // Call the IterateFilterProc _after_ the new folder is created // even if we failed... if( filterParams->filterProcPtr != NULL ) { (void) CallCopyObjectFilterProc(filterParams->filterProcPtr, theGlobals.containerChanged, theGlobals.currentLevel, theGlobals.result, &theGlobals.catalogInfo, &theGlobals.ref, filterParams->fileSpecPtr, filterParams->fileNamePtr, filterParams->yourDataPtr); } mycheck_noerr(theGlobals.result); // put up debug assert in debug builds return ( theGlobals.result ); } /*****************************************************************************/ OSErr FSCopyFolderLevel( FSCopyObjectGlobals *theGlobals, const HFSUniStr255 *destName ) { // If maxLevels is zero, we aren't checking levels // If currentLevel < maxLevels, look at this level if ( (theGlobals->maxLevels == 0) || (theGlobals->currentLevel < theGlobals->maxLevels) ) { FSRef newDirRef; UTCDateTime originalCreateDate; FSPermissionInfo originalPermissions; FSIterator iterator; FilterParams *filterPtr = theGlobals->filterParams; // get the info we need on the source file... theGlobals->result = FSGetCatalogInfo( &theGlobals->ref, filterPtr->whichInfo, &theGlobals->catalogInfo, &filterPtr->fileName, NULL, NULL); if (theGlobals->currentLevel == 0 && destName) filterPtr->fileName = *destName; // Clear the "inited" bit so that the Finder positions the icon for us. ((FInfo *)(theGlobals->catalogInfo.finderInfo))->fdFlags &= ~kHasBeenInited; // Set the folder's creation date to kMagicBusyCreationDate // so that the Finder doesn't mess with the folder while // it's copying. We remember the old value for restoration // later. We only do this if we're not copying to a drop // folder, because if we are copying to a drop folder we don't // have the opportunity to reset the information at the end of // this routine. if ( theGlobals->result == noErr && !theGlobals->copyParams->copyingToDropFolder) { originalCreateDate = theGlobals->catalogInfo.createDate; theGlobals->catalogInfo.createDate = theGlobals->copyParams->magicBusyCreateDate; } // we need to have user level read/write/execute access to the folder we are going to create, // otherwise FSCreateDirectoryUnicode will return -5000 (afpAccessDenied), // and the FSRef returned will be invalid, yet the folder is created... bug? originalPermissions = *((FSPermissionInfo*)theGlobals->catalogInfo.permissions); ((FSPermissionInfo*)theGlobals->catalogInfo.permissions)->mode |= kRWXUserAccessMask; // create the new directory if( theGlobals->result == noErr ) { theGlobals->result = FSCreateDirectoryUnicode( &theGlobals->destRef, filterPtr->fileName.length, filterPtr->fileName.unicode, kFSCatInfoSettableInfo, &theGlobals->catalogInfo, &newDirRef, &filterPtr->fileSpec, NULL); } ++theGlobals->currentLevel; // setup to go to the next level // With the new APIs, folders can have forks as well as files. Before // we start copying items in the folder, we must copy over the forks if( theGlobals->result == noErr ) theGlobals->result = CopyItemsForks(&theGlobals->ref, &newDirRef, theGlobals->copyParams); if( theGlobals->result == noErr ) // Open FSIterator for flat access to theGlobals->ref theGlobals->result = FSOpenIterator(&theGlobals->ref, kFSIterateFlat, &iterator); if( theGlobals->result == noErr ) { OSErr osErr; // Call FSGetCatalogInfoBulk in loop to get all items in the container do { theGlobals->result = FSGetCatalogInfoBulk( iterator, 1, &theGlobals->actualObjects, &theGlobals->containerChanged, filterPtr->whichInfo, &theGlobals->catalogInfo, &theGlobals->ref, filterPtr->fileSpecPtr, &filterPtr->fileName); if ( ( (theGlobals->result == noErr) || (theGlobals->result == errFSNoMoreItems) ) && ( theGlobals->actualObjects != 0 ) ) { // Any errors in here will be passed to the filter proc // we don't want an error in here to prematurely // cancel the recursive copy, leaving a half filled directory // is the new object a directory? if ( (theGlobals->catalogInfo.nodeFlags & kFSNodeIsDirectoryMask) != 0 ) { // yes theGlobals->destRef = newDirRef; osErr = FSCopyFolderLevel(theGlobals, NULL); theGlobals->result = noErr; // don't want one silly mistake to kill the party... } else // no { osErr = CopyFile( &theGlobals->ref, &theGlobals->catalogInfo, &newDirRef, &filterPtr->fileName, theGlobals->copyParams, &theGlobals->ref); } // Call the filter proc _after_ the file/folder was created completly if( filterPtr->filterProcPtr != NULL && !theGlobals->quitFlag ) { theGlobals->quitFlag = CallCopyObjectFilterProc(filterPtr->filterProcPtr, theGlobals->containerChanged, theGlobals->currentLevel, osErr, &theGlobals->catalogInfo, &theGlobals->ref, filterPtr->fileSpecPtr, filterPtr->fileNamePtr, filterPtr->yourDataPtr); } } } while ( ( theGlobals->result == noErr ) && ( !theGlobals->quitFlag ) ); // Close the FSIterator (closing an open iterator should never fail) (void) FSCloseIterator(iterator); } // errFSNoMoreItems is OK - it only means we hit the end of this level // afpAccessDenied is OK, too - it only means we cannot see inside a directory if ( (theGlobals->result == errFSNoMoreItems) || (theGlobals->result == afpAccessDenied) ) theGlobals->result = noErr; // store away the name, and an FSSpec and FSRef of the new directory // for use in filter proc one level up... if( theGlobals->result == noErr ) { theGlobals->ref = newDirRef; theGlobals->result = FSGetCatalogInfo(&newDirRef, kFSCatInfoNone, NULL, &filterPtr->fileName, &filterPtr->fileSpec, NULL); } // Return to previous level as we leave --theGlobals->currentLevel; // Reset the modification dates and permissions, except when copying to a drop folder // where this won't work. if (theGlobals->result == noErr && ! theGlobals->copyParams->copyingToDropFolder) { theGlobals->catalogInfo.createDate = originalCreateDate; *((FSPermissionInfo*)theGlobals->catalogInfo.permissions) = originalPermissions; theGlobals->result = FSSetCatalogInfo(&newDirRef, kFSCatInfoCreateDate | kFSCatInfoAttrMod | kFSCatInfoContentMod | kFSCatInfoPermissions, &theGlobals->catalogInfo); } // If we created the folder and the copy failed, try to clean up by // deleting the folder we created. We do this because, while it's // possible for the copy to fail halfway through and the File Manager // doesn't really clean up that well, we *really* don't wan't any // half-created files/folders being left around. // if the file already existed, we don't want to delete it if( theGlobals->result != noErr && theGlobals->result != dupFNErr ) myverify_noerr( FSDeleteObjects(&newDirRef) ); } mycheck_noerr( theGlobals->result ); // put up debug assert in debug builds return theGlobals->result; } /*****************************************************************************/ // Determines whether the destination directory is equal to the source // item, or whether it's nested inside the source item. Returns a // errFSDestInsideSource if that's the case. We do this to prevent // endless recursion while copying. // OSErr CheckForDestInsideSource(const FSRef *source, const FSRef *destDir) { FSRef thisDir = *destDir; FSCatalogInfo thisDirInfo; Boolean done = false; OSErr osErr; do { osErr = FSCompareFSRefs(source, &thisDir); if (osErr == noErr) osErr = errFSDestInsideSource; else if (osErr == diffVolErr) { osErr = noErr; done = true; } else if (osErr == errFSRefsDifferent) { // This is somewhat tricky. We can ask for the parent of thisDir // by setting the parentRef parameter to FSGetCatalogInfo but, if // thisDir is the volume's FSRef, this will give us back junk. // So we also ask for the parent's dir ID to be returned in the // FSCatalogInfo record, and then check that against the node // ID of the root's parent (ie 1). If we match that, we've made // it to the top of the hierarchy without hitting source, so // we leave with no error. osErr = FSGetCatalogInfo(&thisDir, kFSCatInfoParentDirID, &thisDirInfo, NULL, NULL, &thisDir); if( ( osErr == noErr ) && ( thisDirInfo.parentDirID == fsRtParID ) ) done = true; } } while ( osErr == noErr && ! done ); mycheck_noerr( osErr ); // put up debug assert in debug builds return osErr; } /*****************************************************************************/ #pragma mark ----- Copy Forks ----- OSErr CopyItemsForks(const FSRef *source, const FSRef *dest, CopyParams *params) { ForkTracker dataFork, rsrcFork; ForkTrackerPtr otherForks; ItemCount otherForksCount, thisForkIndex; OSErr osErr; dataFork.forkDestRefNum = 0; rsrcFork.forkDestRefNum = 0; otherForks = NULL; otherForksCount = 0; // Get the constant names for the resource and data fork, which // we're going to need inside the copy engine. osErr = FSGetDataForkName(&dataFork.forkName); if( osErr == noErr ) osErr = FSGetResourceForkName(&rsrcFork.forkName); if( osErr == noErr ) // First determine the list of forks that the source has. osErr = CalculateForksToCopy(source, &dataFork, &rsrcFork, &otherForks, &otherForksCount); if (osErr == noErr) { // If we're copying into a drop folder, open up all of those forks. // We have to do this because, once we've starting writing to a fork // in a drop folder, we can't open any more forks. // // We only do this if we're copying into a drop folder in order // to conserve FCBs in the more common, non-drop folder case. if (params->copyingToDropFolder) osErr = OpenAllForks(dest, &dataFork, &rsrcFork, otherForks, otherForksCount); // Copy each fork. if (osErr == noErr && (dataFork.forkSize != 0)) // copy data fork osErr = CopyFork(source, dest, &dataFork, params); if (osErr == noErr && (rsrcFork.forkSize != 0)) // copy resource fork osErr = CopyFork(source, dest, &rsrcFork, params); if (osErr == noErr) { // copy other forks for (thisForkIndex = 0; thisForkIndex < otherForksCount && osErr == noErr; thisForkIndex++) osErr = CopyFork(source,dest, &otherForks[thisForkIndex], params); } // Close any forks that might be left open. Note that we have to call // this regardless of an error. Also note that this only closes forks // that were opened by OpenAllForks. If we're not copying into a drop // folder, the forks are opened and closed by CopyFork. { OSErr osErr2 = CloseAllForks(dataFork.forkDestRefNum, rsrcFork.forkDestRefNum, otherForks, otherForksCount); mycheck_noerr(osErr2); if (osErr == noErr) osErr = osErr2; } } // Clean up. if (otherForks != NULL) DisposePtr((char*)otherForks); mycheck_noerr( osErr ); // put up debug assert in debug builds return osErr; } /*****************************************************************************/ // Open all the forks of the file. We need to do this when we're copying // into a drop folder, where you must open all the forks before starting // to write to any of them. // // IMPORTANT: If it fails, this routine won't close forks that opened successfully. // You must call CloseAllForks regardless of whether this routine returns an error. OSErr OpenAllForks( const FSRef *dest, const ForkTrackerPtr dataFork, const ForkTrackerPtr rsrcFork, ForkTrackerPtr otherForks, ItemCount otherForksCount) { ItemCount thisForkIndex; OSErr osErr = noErr; // Open the resource and data forks as a special case, if they exist in this object if (dataFork->forkSize != 0) // Data fork never needs to be created, so I don't have to FSCreateFork it here. osErr = FSOpenFork(dest, dataFork->forkName.length, dataFork->forkName.unicode, fsWrPerm, &dataFork->forkDestRefNum); if (osErr == noErr && rsrcFork->forkSize != 0) // Resource fork never needs to be created, so I don't have to FSCreateFork it here. osErr = FSOpenFork(dest, rsrcFork->forkName.length, rsrcFork->forkName.unicode, fsWrPerm, &rsrcFork->forkDestRefNum); if (osErr == noErr && otherForks != NULL && otherForksCount > 0) // Open the other forks. { for (thisForkIndex = 0; thisForkIndex < otherForksCount && osErr == noErr; thisForkIndex++) { // Create the fork. Swallow afpAccessDenied because this operation // causes the external file system compatibility shim in Mac OS 9 to // generate a GetCatInfo request to the AppleShare external file system, // which in turn causes an AFP GetFileDirParms request on the wire, // which the AFP server bounces with afpAccessDenied because the file // is in a drop folder. As there's no native support for non-classic // forks in current AFP, there's no way I can decide how I should // handle this in a non-test case. So I just swallow the error and // hope that when native AFP support arrives, the right thing will happen. osErr = FSCreateFork(dest, otherForks[thisForkIndex].forkName.length, otherForks[thisForkIndex].forkName.unicode); if (osErr == noErr || osErr == afpAccessDenied) osErr = noErr; // Previously I avoided opening up the fork if the fork if the // length was empty, but that confused CopyFork into thinking // this wasn't a drop folder copy, so I decided to simply avoid // this trivial optimization. In drop folders, we always open // all forks. if (osErr == noErr) osErr = FSOpenFork(dest, otherForks[thisForkIndex].forkName.length, otherForks[thisForkIndex].forkName.unicode, fsWrPerm, &otherForks[thisForkIndex].forkDestRefNum); } } mycheck_noerr( osErr ); // put up debug assert in debug builds return osErr; } /*****************************************************************************/ // Copies the fork whose name is forkName from source to dest. // A refnum for the destination fork may be supplied in forkDestRefNum. // If forkDestRefNum is 0, we must open the destination fork ourselves, // otherwise it has been opened for us and we shouldn't close it. OSErr CopyFork( const FSRef *source, const FSRef *dest, const ForkTrackerPtr sourceFork, const CopyParams *params) { UInt64 bytesRemaining; UInt64 bytesToReadThisTime; UInt64 bytesToWriteThisTime; SInt16 sourceRef; SInt16 destRef; OSErr osErr = noErr; OSErr osErr2 = noErr; // If we haven't been passed in a sourceFork->forkDestRefNum (which basically // means we're copying into a non-drop folder), create the destination // fork. We have to do this regardless of whether sourceFork->forkSize is // 0, because we want to preserve empty forks. if (sourceFork->forkDestRefNum == 0) { osErr = FSCreateFork(dest, sourceFork->forkName.length, sourceFork->forkName.unicode); // Mac OS 9.0 has a bug (in the AppleShare external file system, // I think) [2410374] that causes FSCreateFork to return an errFSForkExists // error even though the fork is empty. The following code swallows // the error (which is harmless) in that case. if (osErr == errFSForkExists && !params->copyingToLocalVolume) osErr = noErr; } // The remainder of this code only applies if there is actual data // in the source fork. if (osErr == noErr && sourceFork->forkSize != 0) { // Prepare for failure. sourceRef = 0; destRef = 0; // Open up the destination fork, if we're asked to, otherwise // just use the passed in sourceFork->forkDestRefNum. if( sourceFork->forkDestRefNum == 0 ) osErr = FSOpenFork(dest, sourceFork->forkName.length, sourceFork->forkName.unicode, fsWrPerm, &destRef); else destRef = sourceFork->forkDestRefNum; // Open up the source fork. if (osErr == noErr) osErr = FSOpenFork(source, sourceFork->forkName.length, sourceFork->forkName.unicode, fsRdPerm, &sourceRef); // Here we create space for the entire fork on the destination volume. // FSAllocateFork has the right semantics on both traditional Mac OS // and Mac OS X. On traditional Mac OS it will allocate space for the // file in one hit without any other special action. On Mac OS X, // FSAllocateFork is preferable to FSSetForkSize because it prevents // the system from zero filling the bytes that were added to the end // of the fork (which would be waste becasue we're about to write over // those bytes anyway. if( osErr == noErr ) osErr = FSAllocateFork(destRef, kFSAllocNoRoundUpMask, fsFromStart, 0, sourceFork->forkSize, NULL); // Copy the file from the source to the destination in chunks of // no more than params->copyBufferSize bytes. This is fairly // boring code except for the bytesToReadThisTime/bytesToWriteThisTime // distinction. On the last chunk, we round bytesToWriteThisTime // up to the next 512 byte boundary and then, after we exit the loop, // we set the file's EOF back to the real location (if the fork size // is not a multiple of 512 bytes). // // This technique works around a 'bug' in the traditional Mac OS File Manager, // where the File Manager will put the last 512-byte block of a large write into // the cache (even if we specifically request no caching) if that block is not // full. If the block goes into the cache it will eventually have to be // flushed, which causes sub-optimal disk performance. // // This is only done if the destination volume is local. For a network // volume, it's better to just write the last bytes directly. // // This is extreme over-optimization given the other limits of this // sample, but I will hopefully get to the other limits eventually. bytesRemaining = sourceFork->forkSize; while (osErr == noErr && bytesRemaining != 0) { if (bytesRemaining > params->copyBufferSize) { bytesToReadThisTime = params->copyBufferSize; bytesToWriteThisTime = bytesToReadThisTime; } else { bytesToReadThisTime = bytesRemaining; bytesToWriteThisTime = (params->copyingToLocalVolume) ? (bytesRemaining + 0x01FF) & ~0x01FF : bytesRemaining; } osErr = FSReadFork(sourceRef, fsAtMark + noCacheMask, 0, bytesToReadThisTime, params->copyBuffer, NULL); if (osErr == noErr) osErr = FSWriteFork(destRef, fsAtMark + noCacheMask, 0, bytesToWriteThisTime, params->copyBuffer, NULL); if (osErr == noErr) bytesRemaining -= bytesToReadThisTime; } if (osErr == noErr && (params->copyingToLocalVolume && ((sourceFork->forkSize & 0x01FF) != 0)) ) osErr = FSSetForkSize(destRef, fsFromStart, sourceFork->forkSize); // Clean up. if (sourceRef != 0) { osErr2 = FSCloseFork(sourceRef); mycheck_noerr(osErr2); if (osErr == noErr) osErr = osErr2; } // Only close destRef if we were asked to open it (ie sourceFork->forkDestRefNum == 0) and // we actually managed to open it (ie destRef != 0). if (sourceFork->forkDestRefNum == 0 && destRef != 0) { osErr2 = FSCloseFork(destRef); mycheck_noerr(osErr2); if (osErr == noErr) osErr = osErr2; } } mycheck_noerr( osErr ); // put up debug assert in debug builds return osErr; } /*****************************************************************************/ // Close all the forks that might have been opened by OpenAllForks. OSErr CloseAllForks(SInt16 dataRefNum, SInt16 rsrcRefNum, ForkTrackerPtr otherForks, ItemCount otherForksCount) { ItemCount thisForkIndex; OSErr osErr = noErr, osErr2; if (dataRefNum != 0) { osErr2 = FSCloseFork(dataRefNum); mycheck_noerr(osErr2); if (osErr == noErr) osErr = osErr2; } if (rsrcRefNum != 0) { osErr2 = FSCloseFork(rsrcRefNum); mycheck_noerr(osErr2); if (osErr == noErr) osErr = osErr2; } if( otherForks != NULL && otherForksCount > 0 ) { for (thisForkIndex = 0; thisForkIndex < otherForksCount; thisForkIndex++) { if (otherForks[thisForkIndex].forkDestRefNum != 0) { osErr2 = FSCloseFork(otherForks[thisForkIndex].forkDestRefNum); mycheck_noerr(osErr2); if (osErr == noErr) osErr = osErr2; } } } mycheck_noerr( osErr ); // put up debug assert in debug builds return osErr; } /*****************************************************************************/ // This routine determines the list of forks that a file has. // dataFork is populated if the file has a data fork. // rsrcFork is populated if the file has a resource fork. // otherForksParam is set to point to a memory block allocated with // NewPtr if the file has forks beyond the resource and data // forks. You must free that block with DisposePtr. otherForksCountParam // is set to the number of forks in the otherForksParam // array. This count does *not* include the resource and data forks. OSErr CalculateForksToCopy( const FSRef *source, const ForkTrackerPtr dataFork, const ForkTrackerPtr rsrcFork, ForkTrackerPtr *otherForksParam, ItemCount *otherForksCountParam) { Boolean done; CatPositionRec iterator; HFSUniStr255 thisForkName; SInt64 thisForkSize; ForkTrackerPtr otherForks; ItemCount otherForksCount; ItemCount otherForksMemoryBlockCount; OSErr osErr = ( (source != NULL) && (dataFork != NULL) && (rsrcFork != NULL) && (otherForksParam != NULL) && (otherForksCountParam != NULL) ) ? noErr : paramErr; dataFork->forkSize = 0; rsrcFork->forkSize = 0; otherForks = NULL; otherForksCount = 0; iterator.initialize = 0; done = false; // Iterate through the list of forks, processing each fork name in turn. while (osErr == noErr && ! done) { osErr = FSIterateForks(source, &iterator, &thisForkName, &thisForkSize, NULL); if (osErr == errFSNoMoreItems) { osErr = noErr; done = true; } else if (osErr == noErr) { if ( CompareHFSUniStr255(&thisForkName, &dataFork->forkName) ) dataFork->forkSize = thisForkSize; else if ( CompareHFSUniStr255(&thisForkName, &rsrcFork->forkName) ) rsrcFork->forkSize = thisForkSize; else { // We've found a fork other than the resource and data forks. // We have to add it to the otherForks array. But the array // a) may not have been created yet, and b) may not contain // enough elements to hold the new fork. if (otherForks == NULL) // The array hasn't been allocated yet, allocate it. { otherForksMemoryBlockCount = kExpectedForkCount; otherForks = ( ForkTracker* ) NewPtr( sizeof(ForkTracker) * kExpectedForkCount ); if (otherForks == NULL) osErr = memFullErr; } else if (otherForksCount == otherForksMemoryBlockCount) { // If the array doesn't contain enough elements, grow it. ForkTrackerPtr newOtherForks; newOtherForks = (ForkTracker*)NewPtr(sizeof(ForkTracker) * (otherForksCount + kExpectedForkCount)); if( newOtherForks != NULL) { BlockMoveData(otherForks, newOtherForks, sizeof(ForkTracker) * otherForksCount); otherForksMemoryBlockCount += kExpectedForkCount; DisposePtr((char*)otherForks); otherForks = newOtherForks; } else osErr = memFullErr; } // If we have no error, we know we have space in the otherForks // array to place the new fork. Put it there and increment the // count of forks. if (osErr == noErr) { BlockMoveData(&thisForkName, &otherForks[otherForksCount].forkName, sizeof(thisForkName)); otherForks[otherForksCount].forkSize = thisForkSize; otherForks[otherForksCount].forkDestRefNum = 0; ++otherForksCount; } } } } // Clean up. if (osErr != noErr) { if (otherForks != NULL) { DisposePtr((char*)otherForks); otherForks = NULL; } otherForksCount = 0; } *otherForksParam = otherForks; *otherForksCountParam = otherForksCount; mycheck_noerr( osErr ); // put up debug assert in debug builds return osErr; } /*****************************************************************************/ #pragma mark ----- Calculate Buffer Size ----- OSErr CalculateBufferSize( const FSRef *source, const FSRef *destDir, ByteCount * bufferSize ) { FSVolumeRefNum sourceVRefNum, destVRefNum; ByteCount tmpBufferSize = 0; OSErr osErr = ( source != NULL && destDir != NULL && bufferSize != NULL ) ? noErr : paramErr; if( osErr == noErr ) osErr = FSGetVRefNum( source, &sourceVRefNum ); if( osErr == noErr ) osErr = FSGetVRefNum( destDir, &destVRefNum); if( osErr == noErr) { tmpBufferSize = BufferSizeForThisVolume(sourceVRefNum); if (destVRefNum != sourceVRefNum) { ByteCount tmp = BufferSizeForThisVolume(destVRefNum); if (tmp < tmpBufferSize) tmpBufferSize = tmp; } } *bufferSize = tmpBufferSize; mycheck_noerr( osErr ); // put up debug assert in debug builds return osErr; } /*****************************************************************************/ // This routine calculates the appropriate buffer size for // the given vRefNum. It's a simple composition of FSGetVolParms // BufferSizeForThisVolumeSpeed. ByteCount BufferSizeForThisVolume(FSVolumeRefNum vRefNum) { GetVolParmsInfoBuffer volParms; ByteCount volumeBytesPerSecond = 0; UInt32 actualSize; OSErr osErr; osErr = FSGetVolParms( vRefNum, sizeof(volParms), &volParms, &actualSize ); if( osErr == noErr ) { // Version 1 of the GetVolParmsInfoBuffer included the vMAttrib // field, so we don't really need to test actualSize. A noErr // result indicates that we have the info we need. This is // just a paranoia check. mycheck(actualSize >= offsetof(GetVolParmsInfoBuffer, vMVolumeGrade)); // On the other hand, vMVolumeGrade was not introduced until // version 2 of the GetVolParmsInfoBuffer, so we have to explicitly // test whether we got a useful value. if( ( actualSize >= offsetof(GetVolParmsInfoBuffer, vMForeignPrivID) ) && ( volParms.vMVolumeGrade <= 0 ) ) { volumeBytesPerSecond = -volParms.vMVolumeGrade; } } mycheck_noerr( osErr ); // put up debug assert in debug builds return BufferSizeForThisVolumeSpeed(volumeBytesPerSecond); } /*****************************************************************************/ // Calculate an appropriate copy buffer size based on the volumes // rated speed. Our target is to use a buffer that takes 0.25 // seconds to fill. This is necessary because the volume might be // mounted over a very slow link (like ARA), and if we do a 256 KB // read over an ARA link we'll block the File Manager queue for // so long that other clients (who might have innocently just // called PBGetCatInfoSync) will block for a noticeable amount of time. // // Note that volumeBytesPerSecond might be 0, in which case we assume // some default value. ByteCount BufferSizeForThisVolumeSpeed(UInt32 volumeBytesPerSecond) { ByteCount bufferSize; if (volumeBytesPerSecond == 0) bufferSize = kDefaultCopyBufferSize; else { // We want to issue a single read that takes 0.25 of a second, // so devide the bytes per second by 4. bufferSize = volumeBytesPerSecond / 4; } // Round bufferSize down to 512 byte boundary. bufferSize &= ~0x01FF; // Clip to sensible limits. if (bufferSize < kMinimumCopyBufferSize) bufferSize = kMinimumCopyBufferSize; else if (bufferSize > kMaximumCopyBufferSize) bufferSize = kMaximumCopyBufferSize; return bufferSize; } /*****************************************************************************/ #pragma mark ----- Delete Objects ----- OSErr FSDeleteObjects( const FSRef *source ) { FSCatalogInfo catalogInfo; OSErr osErr = ( source != NULL ) ? noErr : paramErr; // get nodeFlags for container if( osErr == noErr ) osErr = FSGetCatalogInfo(source, kFSCatInfoNodeFlags, &catalogInfo, NULL, NULL,NULL); if( osErr == noErr && (catalogInfo.nodeFlags & kFSNodeIsDirectoryMask) != 0 ) { // its a directory, so delete its contents before we delete it osErr = FSDeleteFolder(source); } if( osErr == noErr && (catalogInfo.nodeFlags & kFSNodeLockedMask) != 0 ) // is object locked? { // then attempt to unlock the object (ignore osErr since FSDeleteObject will set it correctly) catalogInfo.nodeFlags &= ~kFSNodeLockedMask; (void) FSSetCatalogInfo(source, kFSCatInfoNodeFlags, &catalogInfo); } if( osErr == noErr ) // delete the object (if it was a directory it is now empty, so we can delete it) osErr = FSDeleteObject(source); mycheck_noerr( osErr ); return ( osErr ); } /*****************************************************************************/ #pragma mark ----- Delete Folders ----- OSErr FSDeleteFolder( const FSRef *container ) { FSDeleteObjectGlobals theGlobals; theGlobals.result = ( container != NULL ) ? noErr : paramErr; // delete container's contents if( theGlobals.result == noErr ) FSDeleteFolderLevel(container, &theGlobals); mycheck_noerr( theGlobals.result ); return ( theGlobals.result ); } /*****************************************************************************/ void FSDeleteFolderLevel( const FSRef *container, FSDeleteObjectGlobals *theGlobals) { FSIterator iterator; FSRef itemToDelete; UInt16 nodeFlags; // Open FSIterator for flat access and give delete optimization hint theGlobals->result = FSOpenIterator(container, kFSIterateFlat + kFSIterateDelete, &iterator); if ( theGlobals->result == noErr ) { do // delete the contents of the directory { // get 1 item to delete theGlobals->result = FSGetCatalogInfoBulk( iterator, 1, &theGlobals->actualObjects, NULL, kFSCatInfoNodeFlags, &theGlobals->catalogInfo, &itemToDelete, NULL, NULL); if ( (theGlobals->result == noErr) && (theGlobals->actualObjects == 1) ) { // save node flags in local in case we have to recurse */ nodeFlags = theGlobals->catalogInfo.nodeFlags; // is it a directory? if ( (nodeFlags & kFSNodeIsDirectoryMask) != 0 ) { // yes -- delete its contents before attempting to delete it */ FSDeleteFolderLevel(&itemToDelete, theGlobals); } if ( theGlobals->result == noErr) // are we still OK to delete? { if ( (nodeFlags & kFSNodeLockedMask) != 0 ) // is item locked? { // then attempt to unlock it (ignore result since FSDeleteObject will set it correctly) theGlobals->catalogInfo.nodeFlags = nodeFlags & ~kFSNodeLockedMask; (void) FSSetCatalogInfo(&itemToDelete, kFSCatInfoNodeFlags, &theGlobals->catalogInfo); } // delete the item theGlobals->result = FSDeleteObject(&itemToDelete); } } } while ( theGlobals->result == noErr ); // we found the end of the items normally, so return noErr if ( theGlobals->result == errFSNoMoreItems ) theGlobals->result = noErr; // close the FSIterator (closing an open iterator should never fail) myverify_noerr(FSCloseIterator(iterator)); } mycheck_noerr( theGlobals->result ); return; } /*****************************************************************************/ #pragma mark ----- Utilities ----- // Figures out if the given directory is a drop box or not // if it is, the Copy Engine will behave slightly differently OSErr IsDropBox(const FSRef* source, Boolean *isDropBox) { FSCatalogInfo tmpCatInfo; FSSpec sourceSpec; Boolean isDrop = false; OSErr osErr; // get info about the destination, and an FSSpec to it for PBHGetDirAccess osErr = FSGetCatalogInfo(source, kFSCatInfoNodeFlags | kFSCatInfoPermissions, &tmpCatInfo, NULL, &sourceSpec, NULL); if( osErr == noErr ) // make sure the source is a directory osErr = ((tmpCatInfo.nodeFlags & kFSNodeIsDirectoryMask) != 0) ? noErr : errFSNotAFolder; if( osErr == noErr ) { HParamBlockRec hPB; BlockZero(&hPB, sizeof( HParamBlockRec )); hPB.accessParam.ioNamePtr = sourceSpec.name; hPB.accessParam.ioVRefNum = sourceSpec.vRefNum; hPB.accessParam.ioDirID = sourceSpec.parID; // This is the official way (reads: the way X Finder does it) to figure // out the current users access privileges to a given directory osErr = PBHGetDirAccessSync(&hPB); if( osErr == noErr ) // its a drop folder if the current user only has write access isDrop = (hPB.accessParam.ioACAccess & kPrivilegesMask) == kioACAccessUserWriteMask; else if ( osErr == paramErr ) { // There is a bug (2908703) in the Classic File System (not OS 9.x or Carbon) // on 10.1.x where PBHGetDirAccessSync sometimes returns paramErr even when the // data passed in is correct. This is a workaround/hack for that problem, // but is not as accurate. // Basically, if "Everyone" has only Write/Search access then its a drop folder // that is the most common case when its a drop folder FSPermissionInfo *tmpPerm = (FSPermissionInfo *)tmpCatInfo.permissions; isDrop = ((tmpPerm->mode & kRWXOtherAccessMask) == kDropFolderValue); osErr = noErr; } } *isDropBox = isDrop; mycheck_noerr( osErr ); return osErr; } // The copy engine is going to set each item's creation date // to kMagicBusyCreationDate while it's copying the item. // But kMagicBusyCreationDate is an old-style 32-bit date/time, // while the HFS Plus APIs use the new 64-bit date/time. So // we have to call a happy UTC utilities routine to convert from // the local time kMagicBusyCreationDate to a UTCDateTime // gMagicBusyCreationDate, which the File Manager will store // on disk and which the Finder we read back using the old // APIs, whereupon the File Manager will convert it back // to local time (and hopefully get the kMagicBusyCreationDate // back!). OSErr GetMagicBusyCreationDate( UTCDateTime *date ) { UTCDateTime tmpDate = {0,0,0}; OSErr osErr = ( date != NULL ) ? noErr : paramErr; if( osErr == noErr ) osErr = ConvertLocalTimeToUTC(kMagicBusyCreationDate, &tmpDate.lowSeconds); if( osErr == noErr ) *date = tmpDate; mycheck_noerr( osErr ); // put up debug assert in debug builds return osErr; } // compares two HFSUniStr255 for equality // return true if they are identical, false if not Boolean CompareHFSUniStr255(const HFSUniStr255 *lhs, const HFSUniStr255 *rhs) { return (lhs->length == rhs->length) && (memcmp(lhs->unicode, rhs->unicode, lhs->length * sizeof(UniChar)) == 0); } /*****************************************************************************/ OSErr FSGetVRefNum(const FSRef *ref, FSVolumeRefNum *vRefNum) { FSCatalogInfo catalogInfo; OSErr osErr = ( ref != NULL && vRefNum != NULL ) ? noErr : paramErr; if( osErr == noErr ) /* get the volume refNum from the FSRef */ osErr = FSGetCatalogInfo(ref, kFSCatInfoVolume, &catalogInfo, NULL, NULL, NULL); if( osErr == noErr ) *vRefNum = catalogInfo.volume; mycheck_noerr( osErr ); return osErr; } /*****************************************************************************/ OSErr FSGetVolParms( FSVolumeRefNum volRefNum, UInt32 bufferSize, GetVolParmsInfoBuffer *volParmsInfo, UInt32 *actualInfoSize) /* Can Be NULL */ { HParamBlockRec pb; OSErr osErr = ( volParmsInfo != NULL ) ? noErr : paramErr; if( osErr == noErr ) { pb.ioParam.ioNamePtr = NULL; pb.ioParam.ioVRefNum = volRefNum; pb.ioParam.ioBuffer = (Ptr)volParmsInfo; pb.ioParam.ioReqCount = (SInt32)bufferSize; osErr = PBHGetVolParmsSync(&pb); } /* return number of bytes the file system returned in volParmsInfo buffer */ if( osErr == noErr && actualInfoSize != NULL) *actualInfoSize = (UInt32)pb.ioParam.ioActCount; mycheck_noerr( osErr ); // put up debug assert in debug builds return ( osErr ); } /*****************************************************************************/ OSErr UnicodeNameGetHFSName( UniCharCount nameLength, const UniChar *name, TextEncoding textEncodingHint, Boolean isVolumeName, Str31 hfsName) { UnicodeMapping uMapping; UnicodeToTextInfo utInfo; ByteCount unicodeByteLength; ByteCount unicodeBytesConverted; ByteCount actualPascalBytes; OSErr osErr = (hfsName != NULL && name != NULL ) ? noErr : paramErr; // make sure output is valid in case we get errors or there's nothing to convert hfsName[0] = 0; unicodeByteLength = nameLength * sizeof(UniChar); if ( unicodeByteLength == 0 ) osErr = noErr; /* do nothing */ else { // if textEncodingHint is kTextEncodingUnknown, get a "default" textEncodingHint if ( kTextEncodingUnknown == textEncodingHint ) { ScriptCode script; RegionCode region; script = (ScriptCode)GetScriptManagerVariable(smSysScript); region = (RegionCode)GetScriptManagerVariable(smRegionCode); osErr = UpgradeScriptInfoToTextEncoding(script, kTextLanguageDontCare, region, NULL, &textEncodingHint ); if ( osErr == paramErr ) { // ok, ignore the region and try again osErr = UpgradeScriptInfoToTextEncoding(script, kTextLanguageDontCare, kTextRegionDontCare, NULL, &textEncodingHint ); } if ( osErr != noErr ) // ok... try something textEncodingHint = kTextEncodingMacRoman; } uMapping.unicodeEncoding = CreateTextEncoding( kTextEncodingUnicodeV2_0, kUnicodeCanonicalDecompVariant, kUnicode16BitFormat); uMapping.otherEncoding = GetTextEncodingBase(textEncodingHint); uMapping.mappingVersion = kUnicodeUseHFSPlusMapping; osErr = CreateUnicodeToTextInfo(&uMapping, &utInfo); if( osErr == noErr ) { osErr = ConvertFromUnicodeToText( utInfo, unicodeByteLength, name, kUnicodeLooseMappingsMask, 0, NULL, 0, NULL, /* offsetCounts & offsetArrays */ isVolumeName ? kHFSMaxVolumeNameChars : kHFSMaxFileNameChars, &unicodeBytesConverted, &actualPascalBytes, &hfsName[1]); } if( osErr == noErr ) hfsName[0] = actualPascalBytes; // verify the result in debug builds -- there's really not anything you can do if it fails myverify_noerr(DisposeUnicodeToTextInfo(&utInfo)); } mycheck_noerr( osErr ); // put up debug assert in debug builds return ( osErr ); } /*****************************************************************************/ OSErr FSMakeFSRef( FSVolumeRefNum volRefNum, SInt32 dirID, ConstStr255Param name, FSRef *ref) { FSRefParam pb; OSErr osErr = ( ref != NULL ) ? noErr : paramErr; if( osErr == noErr ) { pb.ioVRefNum = volRefNum; pb.ioDirID = dirID; pb.ioNamePtr = (StringPtr)name; pb.newRef = ref; osErr = PBMakeFSRefSync(&pb); } mycheck_noerr( osErr ); // put up debug assert in debug builds return ( osErr ); }