4 Contains: A Copy/Delete Files/Folders engine which uses the HFS+ API's.
5 This code is a combination of MoreFilesX and MPFileCopy
6 with some added features. This code will run on OS 9.1 and up
7 and 10.1.x (Classic and Carbon)
9 Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc.
10 ("Apple") in consideration of your agreement to the following terms, and your
11 use, installation, modification or redistribution of this Apple software
12 constitutes acceptance of these terms. If you do not agree with these terms,
13 please do not use, install, modify or redistribute this Apple software.
15 In consideration of your agreement to abide by the following terms, and subject
16 to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs
17 copyrights in this original Apple software (the "Apple Software"), to use,
18 reproduce, modify and redistribute the Apple Software, with or without
19 modifications, in source and/or binary forms; provided that if you redistribute
20 the Apple Software in its entirety and without modifications, you must retain
21 this notice and the following text and disclaimers in all such redistributions of
22 the Apple Software. Neither the name, trademarks, service marks or logos of
23 Apple Computer, Inc. may be used to endorse or promote products derived from the
24 Apple Software without specific prior written permission from Apple. Except as
25 expressly stated in this notice, no other rights or licenses, express or implied,
26 are granted by Apple herein, including but not limited to any patent rights that
27 may be infringed by your derivative works or by other works in which the Apple
28 Software may be incorporated.
30 The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
31 WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
32 WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
33 PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
34 COMBINATION WITH YOUR PRODUCTS.
36 IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
37 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
38 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
39 ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
40 OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
41 (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
42 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44 Copyright © 2002 Apple Computer, Inc., All Rights Reserved
47 // Modified 2006-01-23 - added this comment.
49 #include "FSCopyObject.h"
50 #include <UnicodeConverter.h>
58 #pragma mark ----- Tunable Parameters -----
60 // The following constants control the behavior of the copy engine.
62 enum { // BufferSizeForThisVolumeSpeed
63 // kDefaultCopyBufferSize = 2L * 1024 * 1024, // Fast be not very responsive.
64 kDefaultCopyBufferSize
= 256L * 1024, // Slower, but can still use machine.
65 kMaximumCopyBufferSize
= 2L * 1024 * 1024,
66 kMinimumCopyBufferSize
= 1024
69 enum { // CalculateForksToCopy
70 kExpectedForkCount
= 10 // Number of non-classic forks we expect.
71 }; // (i.e. non resource/data forks)
73 enum { // CheckForDestInsideSource
74 errFSDestInsideSource
= -1234
78 // for use with PBHGetDirAccess in IsDropBox
79 kPrivilegesMask
= kioACAccessUserWriteMask
| kioACAccessUserReadMask
| kioACAccessUserSearchMask
,
81 // for use with FSGetCatalogInfo and FSPermissionInfo->mode
82 // from sys/stat.h... note -- sys/stat.h definitions are in octal
84 // You can use these values to adjust the users/groups permissions
85 // on a file/folder with FSSetCatalogInfo and extracting the
86 // kFSCatInfoPermissions field. See code below for examples
87 kRWXUserAccessMask
= 0x01C0,
88 kReadAccessUser
= 0x0100,
89 kWriteAccessUser
= 0x0080,
90 kExecuteAccessUser
= 0x0040,
92 kRWXGroupAccessMask
= 0x0038,
93 kReadAccessGroup
= 0x0020,
94 kWriteAccessGroup
= 0x0010,
95 kExecuteAccessGroup
= 0x0008,
97 kRWXOtherAccessMask
= 0x0007,
98 kReadAccessOther
= 0x0004,
99 kWriteAccessOther
= 0x0002,
100 kExecuteAccessOther
= 0x0001,
102 kDropFolderValue
= kWriteAccessOther
| kExecuteAccessOther
105 #pragma mark ----- Struct Definitions -----
107 #define VolHasCopyFile(volParms) \
108 (((volParms)->vMAttrib & (1L << bHasCopyFile)) != 0)
110 // The CopyParams data structure holds the copy buffer used
111 // when copying the forks over, as well as special case
112 // info on the destination
114 UTCDateTime magicBusyCreateDate
;
116 ByteCount copyBufferSize
;
117 Boolean copyingToDropFolder
;
118 Boolean copyingToLocalVolume
;
120 typedef struct CopyParams CopyParams
;
122 // The FilterParams data structure holds the date and info
123 // that the caller wants passed into the Filter Proc, as well
124 // as the Filter Proc Pointer itself
125 struct FilterParams
{
126 FSCatalogInfoBitmap whichInfo
;
127 CopyObjectFilterProcPtr filterProcPtr
;
130 HFSUniStr255 fileName
;
131 HFSUniStr255
*fileNamePtr
;
134 typedef struct FilterParams FilterParams
;
136 // The ForkTracker data structure holds information about a specific fork,
137 // specifically the name and the refnum. We use this to build a list of
138 // all the forks before we start copying them. We need to do this because,
139 // if we're copying into a drop folder, we must open all the forks before
140 // we start copying data into any of them.
141 // Plus it's a convenient way to keep track of all the forks...
143 HFSUniStr255 forkName
;
145 SInt16 forkDestRefNum
;
147 typedef struct ForkTracker ForkTracker
;
148 typedef ForkTracker
*ForkTrackerPtr
;
150 // The FSCopyObjectGlobals data structure holds information needed to do
151 // the recursive copy of a directory.
152 struct FSCopyObjectGlobals
154 FSCatalogInfo catalogInfo
;
155 FSRef ref
; /* FSRef to the source file/folder*/
156 FSRef destRef
; /* FSRef to the destination directory */
157 CopyParams
*copyParams
; /* pointer to info needed to do the copy */
158 FilterParams
*filterParams
; /* pointer to info needed for the optional filter proc */
159 ItemCount maxLevels
; /* maximum levels to iterate through */
160 ItemCount currentLevel
; /* the current level FSCopyFolderLevel is on */
161 Boolean quitFlag
; /* set to true if filter wants to kill interation */
162 Boolean containerChanged
; /* temporary - set to true if the current container changed during iteration */
163 OSErr result
; /* result */
164 ItemCount actualObjects
; /* number of objects returned */
166 typedef struct FSCopyObjectGlobals FSCopyObjectGlobals
;
168 // The FSDeleteObjectGlobals data structure holds information needed to
169 // recursively delete a directory
170 struct FSDeleteObjectGlobals
172 FSCatalogInfo catalogInfo
; /* FSCatalogInfo */
173 ItemCount actualObjects
; /* number of objects returned */
174 OSErr result
; /* result */
176 typedef struct FSDeleteObjectGlobals FSDeleteObjectGlobals
;
178 #pragma mark ----- Local Prototypes -----
180 static OSErr
FSCopyFile( const FSRef
*source
,
181 const FSRef
*destDir
,
182 const HFSUniStr255
*destName
, /* can be NULL (no rename during copy) */
183 CopyParams
*copyParams
,
184 FilterParams
*filterParams
,
185 FSRef
*newFile
); /* can be NULL */
187 static OSErr
CopyFile( const FSRef
*source
,
188 FSCatalogInfo
*sourceCatInfo
,
189 const FSRef
*destDir
,
190 const HFSUniStr255
*destName
,
191 CopyParams
*copyParams
,
192 FSRef
*newRef
); /* can be NULL */
194 static OSErr
FSUsePBHCopyFile( const FSRef
*srcFileRef
,
195 const FSRef
*dstDirectoryRef
,
196 UniCharCount nameLength
,
197 const UniChar
*copyName
, /* can be NULL (no rename during copy) */
198 TextEncoding textEncodingHint
,
199 FSRef
*newRef
); /* can be NULL */
201 static OSErr
DoCopyFile( const FSRef
*source
,
202 FSCatalogInfo
*sourceCatInfo
,
203 const FSRef
*destDir
,
204 const HFSUniStr255
*destName
,
206 FSRef
*newRef
); /* can be NULL */
208 static OSErr
FSCopyFolder( const FSRef
*source
,
209 const FSRef
*destDir
,
210 const HFSUniStr255
*destName
, /* can be NULL (no rename during copy) */
211 CopyParams
* copyParams
,
212 FilterParams
*filterParams
,
214 FSRef
* newDir
); /* can be NULL */
216 static OSErr
FSCopyFolderLevel( FSCopyObjectGlobals
*theGlobals
, const HFSUniStr255
*destName
);
218 static OSErr
CheckForDestInsideSource( const FSRef
*source
,
219 const FSRef
*destDir
);
221 static OSErr
CopyItemsForks( const FSRef
*source
,
225 static OSErr
OpenAllForks( const FSRef
*dest
,
226 const ForkTrackerPtr dataFork
,
227 const ForkTrackerPtr rsrcFork
,
228 ForkTrackerPtr otherForks
,
229 ItemCount otherForksCount
);
231 static OSErr
CopyFork( const FSRef
*source
,
233 const ForkTrackerPtr sourceFork
,
234 const CopyParams
*params
);
236 static OSErr
CloseAllForks( SInt16 dataRefNum
,
238 ForkTrackerPtr otherForks
,
239 ItemCount otherForksCount
);
241 static OSErr
CalculateForksToCopy( const FSRef
*source
,
242 const ForkTrackerPtr dataFork
,
243 const ForkTrackerPtr rsrcFork
,
244 ForkTrackerPtr
*otherForksParam
,
245 ItemCount
*otherForksCountParam
);
247 static OSErr
CalculateBufferSize( const FSRef
*source
,
248 const FSRef
*destDir
,
249 ByteCount
* bufferSize
);
251 static ByteCount
BufferSizeForThisVolume(FSVolumeRefNum vRefNum
);
253 static ByteCount
BufferSizeForThisVolumeSpeed(UInt32 volumeBytesPerSecond
);
255 static OSErr
IsDropBox( const FSRef
* source
,
258 static OSErr
GetMagicBusyCreationDate( UTCDateTime
*date
);
260 static Boolean
CompareHFSUniStr255(const HFSUniStr255
*lhs
,
261 const HFSUniStr255
*rhs
);
263 static OSErr
FSGetVRefNum( const FSRef
*ref
,
264 FSVolumeRefNum
*vRefNum
);
266 static OSErr
FSGetVolParms( FSVolumeRefNum volRefNum
,
268 GetVolParmsInfoBuffer
*volParmsInfo
,
269 UInt32
*actualInfoSize
); /* Can Be NULL */
271 static OSErr
UnicodeNameGetHFSName( UniCharCount nameLength
,
273 TextEncoding textEncodingHint
,
274 Boolean isVolumeName
,
277 static OSErr
FSMakeFSRef( FSVolumeRefNum volRefNum
,
279 ConstStr255Param name
,
282 static OSErr
FSDeleteFolder( const FSRef
*container
);
284 static void FSDeleteFolderLevel( const FSRef
*container
,
285 FSDeleteObjectGlobals
*theGlobals
);
287 /*****************************************************************************/
288 /*****************************************************************************/
289 /*****************************************************************************/
291 #pragma mark ----- Copy Objects -----
293 // This routine acts as the top level of the copy engine. It exists
294 // to a) present a nicer API than the various recursive routines, and
295 // b) minimise the local variables in the recursive routines.
296 OSErr
FSCopyObject( const FSRef
*source
,
297 const FSRef
*destDir
,
298 UniCharCount nameLength
,
299 const UniChar
*copyName
, // can be NULL (no rename during copy)
301 FSCatalogInfoBitmap whichInfo
,
304 CopyObjectFilterProcPtr filterProcPtr
, // can be NULL
305 void *yourDataPtr
, // can be NULL
306 FSRef
*newObject
) // can be NULL
308 CopyParams copyParams
;
309 FilterParams filterParams
;
310 HFSUniStr255 destName
;
311 HFSUniStr255
*destNamePtr
;
313 OSErr osErr
= ( source
!= NULL
&& destDir
!= NULL
) ? noErr
: paramErr
;
317 if (nameLength
<= 255)
319 BlockMoveData(copyName
, destName
.unicode
, nameLength
* sizeof(UniChar
));
320 destName
.length
= nameLength
;
321 destNamePtr
= &destName
;
329 // we want the settable info no matter what the user asked for
330 filterParams
.whichInfo
= whichInfo
| kFSCatInfoSettableInfo
;
331 filterParams
.filterProcPtr
= filterProcPtr
;
332 filterParams
.fileSpecPtr
= ( wantFSSpec
) ? &filterParams
.fileSpec
: NULL
;
333 filterParams
.fileNamePtr
= ( wantName
) ? &filterParams
.fileName
: NULL
;
334 filterParams
.yourDataPtr
= yourDataPtr
;
336 // Calculate the optimal buffer size to copy the forks over
337 // and create the buffer
339 osErr
= CalculateBufferSize( source
, destDir
, ©Params
.copyBufferSize
);
343 copyParams
.copyBuffer
= NewPtr( copyParams
.copyBufferSize
);
344 if( copyParams
.copyBuffer
== NULL
)
349 osErr
= GetMagicBusyCreationDate( ©Params
.magicBusyCreateDate
);
351 if( osErr
== noErr
) // figure out if source is a file or folder
352 { // if it is on a local volume,
353 // if destination is a drop box
354 GetVolParmsInfoBuffer volParms
;
355 FSCatalogInfo tmpCatInfo
;
356 FSVolumeRefNum destVRefNum
;
358 // to figure out if the souce is a folder or directory
359 osErr
= FSGetCatalogInfo(source
, kFSCatInfoNodeFlags
, &tmpCatInfo
, NULL
, NULL
, NULL
);
362 isDirectory
= ((tmpCatInfo
.nodeFlags
& kFSNodeIsDirectoryMask
) != 0);
363 // are we copying to a drop folder?
364 osErr
= IsDropBox( destDir
, ©Params
.copyingToDropFolder
);
367 osErr
= FSGetVRefNum(destDir
, &destVRefNum
);
369 osErr
= FSGetVolParms( destVRefNum
, sizeof(volParms
), &volParms
, NULL
);
370 if( osErr
== noErr
) // volParms.vMServerAdr is non-zero for remote volumes
371 copyParams
.copyingToLocalVolume
= (volParms
.vMServerAdr
== 0);
374 // now copy the file/folder...
379 osErr
= CheckForDestInsideSource(source
, destDir
);
381 osErr
= FSCopyFolder( source
, destDir
, destNamePtr
, ©Params
, &filterParams
, maxLevels
, newObject
);
384 osErr
= FSCopyFile(source
, destDir
, destNamePtr
, ©Params
, &filterParams
, newObject
);
387 // Clean up for space and safety... Who me?
388 if( copyParams
.copyBuffer
!= NULL
)
389 DisposePtr((char*)copyParams
.copyBuffer
);
391 mycheck_noerr( osErr
); // put up debug assert in debug builds
396 /*****************************************************************************/
398 #pragma mark ----- Copy Files -----
400 OSErr
FSCopyFile( const FSRef
*source
,
401 const FSRef
*destDir
,
402 const HFSUniStr255
*destName
,
403 CopyParams
*copyParams
,
404 FilterParams
*filterParams
,
407 FSCatalogInfo sourceCatInfo
;
409 OSErr osErr
= ( source
!= NULL
&& destDir
!= NULL
&&
410 copyParams
!= NULL
&& filterParams
!= NULL
) ? noErr
: paramErr
;
412 // get needed info about the source file
413 if ( osErr
== noErr
)
417 osErr
= FSGetCatalogInfo(source
, filterParams
->whichInfo
, &sourceCatInfo
, NULL
, NULL
, NULL
);
418 filterParams
->fileName
= *destName
;
421 osErr
= FSGetCatalogInfo(source
, filterParams
->whichInfo
, &sourceCatInfo
, &filterParams
->fileName
, NULL
, NULL
);
424 osErr
= CopyFile(source
, &sourceCatInfo
, destDir
, &filterParams
->fileName
, copyParams
, &tmpRef
);
426 // Call the IterateFilterProc _after_ the new file was created
427 // even if an error occured
428 if( filterParams
->filterProcPtr
!= NULL
)
430 (void) CallCopyObjectFilterProc(filterParams
->filterProcPtr
, false, 0, osErr
, &sourceCatInfo
,
431 &tmpRef
, filterParams
->fileSpecPtr
,
432 filterParams
->fileNamePtr
, filterParams
->yourDataPtr
);
435 if( osErr
== noErr
&& newFile
!= NULL
)
438 mycheck_noerr(osErr
); // put up debug assert in debug builds
443 /*****************************************************************************/
445 OSErr
CopyFile( const FSRef
*source
,
446 FSCatalogInfo
*sourceCatInfo
,
447 const FSRef
*destDir
,
448 ConstHFSUniStr255Param destName
,
452 OSErr osErr
= paramErr
;
454 // Clear the "inited" bit so that the Finder positions the icon for us.
455 ((FInfo
*)(sourceCatInfo
->finderInfo
))->fdFlags
&= ~kHasBeenInited
;
457 // if the destination is on a remote volume, try to use PBHCopyFile
458 if( params
->copyingToLocalVolume
== 0 )
459 osErr
= FSUsePBHCopyFile( source
, destDir
, 0, NULL
, kTextEncodingUnknown
, newFile
);
461 // if PBHCopyFile didn't work or not supported,
462 if( osErr
!= noErr
) // then try old school file transfer
463 osErr
= DoCopyFile( source
, sourceCatInfo
, destDir
, destName
, params
, newFile
);
465 mycheck_noerr(osErr
); // put up debug assert in debug builds
470 /*****************************************************************************/
472 OSErr
FSUsePBHCopyFile( const FSRef
*srcFileRef
,
473 const FSRef
*dstDirectoryRef
,
474 UniCharCount nameLength
,
475 const UniChar
*copyName
, /* can be NULL (no rename during copy) */
476 TextEncoding textEncodingHint
,
477 FSRef
*newRef
) /* can be NULL */
480 FSCatalogInfo catalogInfo
;
481 GetVolParmsInfoBuffer volParmsInfo
;
486 // get source FSSpec from source FSRef
487 osErr
= FSGetCatalogInfo(srcFileRef
, kFSCatInfoNone
, NULL
, NULL
, &srcFileSpec
, NULL
);
488 if( osErr
== noErr
) // Make sure the volume supports CopyFile
489 osErr
= FSGetVolParms( srcFileSpec
.vRefNum
, sizeof(GetVolParmsInfoBuffer
), &volParmsInfo
, NULL
);
491 osErr
= VolHasCopyFile(&volParmsInfo
) ? noErr
: paramErr
;
492 if( osErr
== noErr
) // get the destination vRefNum and dirID
493 osErr
= FSGetCatalogInfo(dstDirectoryRef
, kFSCatInfoVolume
| kFSCatInfoNodeID
, &catalogInfo
, NULL
, NULL
, NULL
);
494 if( osErr
== noErr
) // gather all the info needed
496 pb
.copyParam
.ioVRefNum
= srcFileSpec
.vRefNum
;
497 pb
.copyParam
.ioDirID
= srcFileSpec
.parID
;
498 pb
.copyParam
.ioNamePtr
= (StringPtr
)srcFileSpec
.name
;
499 pb
.copyParam
.ioDstVRefNum
= catalogInfo
.volume
;
500 pb
.copyParam
.ioNewDirID
= (long)catalogInfo
.nodeID
;
501 pb
.copyParam
.ioNewName
= NULL
;
502 if( copyName
!= NULL
)
503 osErr
= UnicodeNameGetHFSName(nameLength
, copyName
, textEncodingHint
, false, hfsName
);
504 pb
.copyParam
.ioCopyName
= ( copyName
!= NULL
&& osErr
== noErr
) ? hfsName
: NULL
;
506 if( osErr
== noErr
) // tell the server to copy the object
507 osErr
= PBHCopyFileSync(&pb
);
509 if( osErr
== noErr
&& newRef
!= NULL
)
511 myverify_noerr(FSMakeFSRef(pb
.copyParam
.ioDstVRefNum
, pb
.copyParam
.ioNewDirID
,
512 pb
.copyParam
.ioCopyName
, newRef
));
515 if( osErr
!= paramErr
) // returning paramErr is ok, it means PBHCopyFileSync was not supported
516 mycheck_noerr(osErr
); // put up debug assert in debug builds
521 /*****************************************************************************/
523 // Copies a file referenced by source to the directory referenced by
524 // destDir. destName is the name the file should be given in the
525 // destination directory. sourceCatInfo is the catalogue info of
526 // the file, which is passed in as an optimization (we could get it
527 // by doing a FSGetCatalogInfo but the caller has already done that
528 // so we might as well take advantage of that).
530 OSErr
DoCopyFile( const FSRef
*source
,
531 FSCatalogInfo
*sourceCatInfo
,
532 const FSRef
*destDir
,
533 ConstHFSUniStr255Param destName
,
538 FSPermissionInfo originalPermissions
;
539 UTCDateTime originalCreateDate
;
540 OSType originalFileType
;
541 UInt16 originalNodeFlags
;
544 // If we're copying to a drop folder, we won't be able to reset this
545 // information once the copy is done, so we don't mess it up in
546 // the first place. We still clear the locked bit though; items dropped
547 // into a drop folder always become unlocked.
548 if (!params
->copyingToDropFolder
)
550 // Remember to clear the file's type, so the Finder doesn't
551 // look at the file until we're done.
552 originalFileType
= ((FInfo
*) &sourceCatInfo
->finderInfo
)->fdType
;
553 ((FInfo
*) &sourceCatInfo
->finderInfo
)->fdType
= kFirstMagicBusyFiletype
;
555 // Remember and clear the file's locked status, so that we can
556 // actually write the forks we're about to create.
557 originalNodeFlags
= sourceCatInfo
->nodeFlags
;
559 // Set the file's creation date to kMagicBusyCreationDate,
560 // remembering the old value for restoration later.
561 originalCreateDate
= sourceCatInfo
->createDate
;
562 sourceCatInfo
->createDate
= params
->magicBusyCreateDate
;
564 sourceCatInfo
->nodeFlags
&= ~kFSNodeLockedMask
;
566 // we need to have user level read/write/execute access to the file we are going to create
567 // otherwise FSCreateFileUnicode will return -5000 (afpAccessDenied),
568 // and the FSRef returned will be invalid, yet the file is created (size 0k)... bug?
569 originalPermissions
= *((FSPermissionInfo
*)sourceCatInfo
->permissions
);
570 ((FSPermissionInfo
*)sourceCatInfo
->permissions
)->mode
|= kRWXUserAccessMask
;
572 // Classic only supports 9.1 and higher, so we don't have to worry about 2397324
573 osErr
= FSCreateFileUnicode(destDir
, destName
->length
, destName
->unicode
, kFSCatInfoSettableInfo
, sourceCatInfo
, &dest
, NULL
);
574 if( osErr
== noErr
) // Copy the forks over to the new file
575 osErr
= CopyItemsForks(source
, &dest
, params
);
577 // Restore the original file type, creation and modification dates,
578 // locked status and permissions.
579 // This is one of the places where we need to handle drop
580 // folders as a special case because this FSSetCatalogInfo will fail for
581 // an item in a drop folder, so we don't even attempt it.
582 if (osErr
== noErr
&& !params
->copyingToDropFolder
)
584 ((FInfo
*) &sourceCatInfo
->finderInfo
)->fdType
= originalFileType
;
585 sourceCatInfo
->createDate
= originalCreateDate
;
586 sourceCatInfo
->nodeFlags
= originalNodeFlags
;
587 *((FSPermissionInfo
*)sourceCatInfo
->permissions
) = originalPermissions
;
589 osErr
= FSSetCatalogInfo(&dest
, kFSCatInfoSettableInfo
, sourceCatInfo
);
592 // If we created the file and the copy failed, try to clean up by
593 // deleting the file we created. We do this because, while it's
594 // possible for the copy to fail halfway through and the File Manager
595 // doesn't really clean up that well, we *really* don't wan't
596 // any half-created files being left around.
597 // if the file already existed, we don't want to delete it
599 // Note that there are cases where the assert can fire which are not
600 // errors (for example, if the destination is in a drop folder) but
601 // I'll leave it in anyway because I'm interested in discovering those
602 // cases. Note that, if this fires and we're running MP, current versions
603 // of MacsBug won't catch the exception and the MP task will terminate
604 // with a kMPTaskAbortedErr error.
605 if (osErr
!= noErr
&& osErr
!= dupFNErr
)
606 myverify_noerr( FSDeleteObjects(&dest
) );
607 else if( newRef
!= NULL
) // if everything was fine, then return the new file
610 mycheck_noerr(osErr
); // put up debug assert in debug builds
615 /*****************************************************************************/
617 #pragma mark ----- Copy Folders -----
619 OSErr
FSCopyFolder( const FSRef
*source
, const FSRef
*destDir
, const HFSUniStr255
*destName
,
620 CopyParams
* copyParams
, FilterParams
*filterParams
, ItemCount maxLevels
, FSRef
* newDir
)
622 FSCopyObjectGlobals theGlobals
;
624 theGlobals
.ref
= *source
;
625 theGlobals
.destRef
= *destDir
;
626 theGlobals
.copyParams
= copyParams
;
627 theGlobals
.filterParams
= filterParams
;
628 theGlobals
.maxLevels
= maxLevels
;
629 theGlobals
.currentLevel
= 0;
630 theGlobals
.quitFlag
= false;
631 theGlobals
.containerChanged
= false;
632 theGlobals
.result
= ( source
!= NULL
&& destDir
!= NULL
&&
633 copyParams
!= NULL
&& filterParams
!= NULL
) ?
635 theGlobals
.actualObjects
= 0;
637 // here we go into recursion land...
638 if( theGlobals
.result
== noErr
)
639 theGlobals
.result
= FSCopyFolderLevel(&theGlobals
, destName
);
641 if( theGlobals
.result
== noErr
&& newDir
!= NULL
)
642 *newDir
= theGlobals
.ref
;
644 // Call the IterateFilterProc _after_ the new folder is created
645 // even if we failed...
646 if( filterParams
->filterProcPtr
!= NULL
)
648 (void) CallCopyObjectFilterProc(filterParams
->filterProcPtr
, theGlobals
.containerChanged
,
649 theGlobals
.currentLevel
, theGlobals
.result
, &theGlobals
.catalogInfo
,
650 &theGlobals
.ref
, filterParams
->fileSpecPtr
,
651 filterParams
->fileNamePtr
, filterParams
->yourDataPtr
);
654 mycheck_noerr(theGlobals
.result
); // put up debug assert in debug builds
656 return ( theGlobals
.result
);
659 /*****************************************************************************/
661 OSErr
FSCopyFolderLevel( FSCopyObjectGlobals
*theGlobals
, const HFSUniStr255
*destName
)
663 // If maxLevels is zero, we aren't checking levels
664 // If currentLevel < maxLevels, look at this level
665 if ( (theGlobals
->maxLevels
== 0) ||
666 (theGlobals
->currentLevel
< theGlobals
->maxLevels
) )
669 UTCDateTime originalCreateDate
;
670 FSPermissionInfo originalPermissions
;
672 FilterParams
*filterPtr
= theGlobals
->filterParams
;
674 // get the info we need on the source file...
675 theGlobals
->result
= FSGetCatalogInfo( &theGlobals
->ref
, filterPtr
->whichInfo
,
676 &theGlobals
->catalogInfo
, &filterPtr
->fileName
,
679 if (theGlobals
->currentLevel
== 0 && destName
)
680 filterPtr
->fileName
= *destName
;
682 // Clear the "inited" bit so that the Finder positions the icon for us.
683 ((FInfo
*)(theGlobals
->catalogInfo
.finderInfo
))->fdFlags
&= ~kHasBeenInited
;
685 // Set the folder's creation date to kMagicBusyCreationDate
686 // so that the Finder doesn't mess with the folder while
687 // it's copying. We remember the old value for restoration
688 // later. We only do this if we're not copying to a drop
689 // folder, because if we are copying to a drop folder we don't
690 // have the opportunity to reset the information at the end of
692 if ( theGlobals
->result
== noErr
&& !theGlobals
->copyParams
->copyingToDropFolder
)
694 originalCreateDate
= theGlobals
->catalogInfo
.createDate
;
695 theGlobals
->catalogInfo
.createDate
= theGlobals
->copyParams
->magicBusyCreateDate
;
698 // we need to have user level read/write/execute access to the folder we are going to create,
699 // otherwise FSCreateDirectoryUnicode will return -5000 (afpAccessDenied),
700 // and the FSRef returned will be invalid, yet the folder is created... bug?
701 originalPermissions
= *((FSPermissionInfo
*)theGlobals
->catalogInfo
.permissions
);
702 ((FSPermissionInfo
*)theGlobals
->catalogInfo
.permissions
)->mode
|= kRWXUserAccessMask
;
704 // create the new directory
705 if( theGlobals
->result
== noErr
)
707 theGlobals
->result
= FSCreateDirectoryUnicode( &theGlobals
->destRef
, filterPtr
->fileName
.length
,
708 filterPtr
->fileName
.unicode
, kFSCatInfoSettableInfo
,
709 &theGlobals
->catalogInfo
, &newDirRef
,
710 &filterPtr
->fileSpec
, NULL
);
713 ++theGlobals
->currentLevel
; // setup to go to the next level
715 // With the new APIs, folders can have forks as well as files. Before
716 // we start copying items in the folder, we must copy over the forks
717 if( theGlobals
->result
== noErr
)
718 theGlobals
->result
= CopyItemsForks(&theGlobals
->ref
, &newDirRef
, theGlobals
->copyParams
);
719 if( theGlobals
->result
== noErr
) // Open FSIterator for flat access to theGlobals->ref
720 theGlobals
->result
= FSOpenIterator(&theGlobals
->ref
, kFSIterateFlat
, &iterator
);
721 if( theGlobals
->result
== noErr
)
725 // Call FSGetCatalogInfoBulk in loop to get all items in the container
728 theGlobals
->result
= FSGetCatalogInfoBulk( iterator
, 1, &theGlobals
->actualObjects
,
729 &theGlobals
->containerChanged
, filterPtr
->whichInfo
,
730 &theGlobals
->catalogInfo
, &theGlobals
->ref
,
731 filterPtr
->fileSpecPtr
, &filterPtr
->fileName
);
732 if ( ( (theGlobals
->result
== noErr
) || (theGlobals
->result
== errFSNoMoreItems
) ) &&
733 ( theGlobals
->actualObjects
!= 0 ) )
735 // Any errors in here will be passed to the filter proc
736 // we don't want an error in here to prematurely
737 // cancel the recursive copy, leaving a half filled directory
739 // is the new object a directory?
740 if ( (theGlobals
->catalogInfo
.nodeFlags
& kFSNodeIsDirectoryMask
) != 0 )
742 theGlobals
->destRef
= newDirRef
;
743 osErr
= FSCopyFolderLevel(theGlobals
, NULL
);
744 theGlobals
->result
= noErr
; // don't want one silly mistake to kill the party...
748 osErr
= CopyFile( &theGlobals
->ref
, &theGlobals
->catalogInfo
,
749 &newDirRef
, &filterPtr
->fileName
,
750 theGlobals
->copyParams
, &theGlobals
->ref
);
753 // Call the filter proc _after_ the file/folder was created completly
754 if( filterPtr
->filterProcPtr
!= NULL
&& !theGlobals
->quitFlag
)
756 theGlobals
->quitFlag
= CallCopyObjectFilterProc(filterPtr
->filterProcPtr
,
757 theGlobals
->containerChanged
, theGlobals
->currentLevel
,
758 osErr
, &theGlobals
->catalogInfo
,
759 &theGlobals
->ref
, filterPtr
->fileSpecPtr
,
760 filterPtr
->fileNamePtr
, filterPtr
->yourDataPtr
);
763 } while ( ( theGlobals
->result
== noErr
) && ( !theGlobals
->quitFlag
) );
765 // Close the FSIterator (closing an open iterator should never fail)
766 (void) FSCloseIterator(iterator
);
769 // errFSNoMoreItems is OK - it only means we hit the end of this level
770 // afpAccessDenied is OK, too - it only means we cannot see inside a directory
771 if ( (theGlobals
->result
== errFSNoMoreItems
) || (theGlobals
->result
== afpAccessDenied
) )
772 theGlobals
->result
= noErr
;
774 // store away the name, and an FSSpec and FSRef of the new directory
775 // for use in filter proc one level up...
776 if( theGlobals
->result
== noErr
)
778 theGlobals
->ref
= newDirRef
;
779 theGlobals
->result
= FSGetCatalogInfo(&newDirRef
, kFSCatInfoNone
, NULL
,
780 &filterPtr
->fileName
, &filterPtr
->fileSpec
, NULL
);
783 // Return to previous level as we leave
784 --theGlobals
->currentLevel
;
786 // Reset the modification dates and permissions, except when copying to a drop folder
787 // where this won't work.
788 if (theGlobals
->result
== noErr
&& ! theGlobals
->copyParams
->copyingToDropFolder
)
790 theGlobals
->catalogInfo
.createDate
= originalCreateDate
;
791 *((FSPermissionInfo
*)theGlobals
->catalogInfo
.permissions
) = originalPermissions
;
792 theGlobals
->result
= FSSetCatalogInfo(&newDirRef
, kFSCatInfoCreateDate
794 | kFSCatInfoContentMod
795 | kFSCatInfoPermissions
, &theGlobals
->catalogInfo
);
798 // If we created the folder and the copy failed, try to clean up by
799 // deleting the folder we created. We do this because, while it's
800 // possible for the copy to fail halfway through and the File Manager
801 // doesn't really clean up that well, we *really* don't wan't any
802 // half-created files/folders being left around.
803 // if the file already existed, we don't want to delete it
804 if( theGlobals
->result
!= noErr
&& theGlobals
->result
!= dupFNErr
)
805 myverify_noerr( FSDeleteObjects(&newDirRef
) );
808 mycheck_noerr( theGlobals
->result
); // put up debug assert in debug builds
810 return theGlobals
->result
;
813 /*****************************************************************************/
815 // Determines whether the destination directory is equal to the source
816 // item, or whether it's nested inside the source item. Returns a
817 // errFSDestInsideSource if that's the case. We do this to prevent
818 // endless recursion while copying.
820 OSErr
CheckForDestInsideSource(const FSRef
*source
, const FSRef
*destDir
)
822 FSRef thisDir
= *destDir
;
823 FSCatalogInfo thisDirInfo
;
824 Boolean done
= false;
829 osErr
= FSCompareFSRefs(source
, &thisDir
);
831 osErr
= errFSDestInsideSource
;
832 else if (osErr
== diffVolErr
)
837 else if (osErr
== errFSRefsDifferent
)
839 // This is somewhat tricky. We can ask for the parent of thisDir
840 // by setting the parentRef parameter to FSGetCatalogInfo but, if
841 // thisDir is the volume's FSRef, this will give us back junk.
842 // So we also ask for the parent's dir ID to be returned in the
843 // FSCatalogInfo record, and then check that against the node
844 // ID of the root's parent (ie 1). If we match that, we've made
845 // it to the top of the hierarchy without hitting source, so
846 // we leave with no error.
848 osErr
= FSGetCatalogInfo(&thisDir
, kFSCatInfoParentDirID
, &thisDirInfo
, NULL
, NULL
, &thisDir
);
849 if( ( osErr
== noErr
) && ( thisDirInfo
.parentDirID
== fsRtParID
) )
852 } while ( osErr
== noErr
&& ! done
);
854 mycheck_noerr( osErr
); // put up debug assert in debug builds
859 /*****************************************************************************/
861 #pragma mark ----- Copy Forks -----
863 OSErr
CopyItemsForks(const FSRef
*source
, const FSRef
*dest
, CopyParams
*params
)
865 ForkTracker dataFork
,
867 ForkTrackerPtr otherForks
;
868 ItemCount otherForksCount
,
872 dataFork
.forkDestRefNum
= 0;
873 rsrcFork
.forkDestRefNum
= 0;
877 // Get the constant names for the resource and data fork, which
878 // we're going to need inside the copy engine.
879 osErr
= FSGetDataForkName(&dataFork
.forkName
);
881 osErr
= FSGetResourceForkName(&rsrcFork
.forkName
);
882 if( osErr
== noErr
) // First determine the list of forks that the source has.
883 osErr
= CalculateForksToCopy(source
, &dataFork
, &rsrcFork
, &otherForks
, &otherForksCount
);
886 // If we're copying into a drop folder, open up all of those forks.
887 // We have to do this because, once we've starting writing to a fork
888 // in a drop folder, we can't open any more forks.
890 // We only do this if we're copying into a drop folder in order
891 // to conserve FCBs in the more common, non-drop folder case.
893 if (params
->copyingToDropFolder
)
894 osErr
= OpenAllForks(dest
, &dataFork
, &rsrcFork
, otherForks
, otherForksCount
);
897 if (osErr
== noErr
&& (dataFork
.forkSize
!= 0)) // copy data fork
898 osErr
= CopyFork(source
, dest
, &dataFork
, params
);
899 if (osErr
== noErr
&& (rsrcFork
.forkSize
!= 0)) // copy resource fork
900 osErr
= CopyFork(source
, dest
, &rsrcFork
, params
);
901 if (osErr
== noErr
) { // copy other forks
902 for (thisForkIndex
= 0; thisForkIndex
< otherForksCount
&& osErr
== noErr
; thisForkIndex
++)
903 osErr
= CopyFork(source
,dest
, &otherForks
[thisForkIndex
], params
);
906 // Close any forks that might be left open. Note that we have to call
907 // this regardless of an error. Also note that this only closes forks
908 // that were opened by OpenAllForks. If we're not copying into a drop
909 // folder, the forks are opened and closed by CopyFork.
911 OSErr osErr2
= CloseAllForks(dataFork
.forkDestRefNum
, rsrcFork
.forkDestRefNum
, otherForks
, otherForksCount
);
912 mycheck_noerr(osErr2
);
919 if (otherForks
!= NULL
)
920 DisposePtr((char*)otherForks
);
922 mycheck_noerr( osErr
); // put up debug assert in debug builds
927 /*****************************************************************************/
929 // Open all the forks of the file. We need to do this when we're copying
930 // into a drop folder, where you must open all the forks before starting
931 // to write to any of them.
933 // IMPORTANT: If it fails, this routine won't close forks that opened successfully.
934 // You must call CloseAllForks regardless of whether this routine returns an error.
935 OSErr
OpenAllForks( const FSRef
*dest
,
936 const ForkTrackerPtr dataFork
,
937 const ForkTrackerPtr rsrcFork
,
938 ForkTrackerPtr otherForks
,
939 ItemCount otherForksCount
)
941 ItemCount thisForkIndex
;
944 // Open the resource and data forks as a special case, if they exist in this object
945 if (dataFork
->forkSize
!= 0) // Data fork never needs to be created, so I don't have to FSCreateFork it here.
946 osErr
= FSOpenFork(dest
, dataFork
->forkName
.length
, dataFork
->forkName
.unicode
, fsWrPerm
, &dataFork
->forkDestRefNum
);
947 if (osErr
== noErr
&& rsrcFork
->forkSize
!= 0) // Resource fork never needs to be created, so I don't have to FSCreateFork it here.
948 osErr
= FSOpenFork(dest
, rsrcFork
->forkName
.length
, rsrcFork
->forkName
.unicode
, fsWrPerm
, &rsrcFork
->forkDestRefNum
);
950 if (osErr
== noErr
&& otherForks
!= NULL
&& otherForksCount
> 0) // Open the other forks.
952 for (thisForkIndex
= 0; thisForkIndex
< otherForksCount
&& osErr
== noErr
; thisForkIndex
++)
954 // Create the fork. Swallow afpAccessDenied because this operation
955 // causes the external file system compatibility shim in Mac OS 9 to
956 // generate a GetCatInfo request to the AppleShare external file system,
957 // which in turn causes an AFP GetFileDirParms request on the wire,
958 // which the AFP server bounces with afpAccessDenied because the file
959 // is in a drop folder. As there's no native support for non-classic
960 // forks in current AFP, there's no way I can decide how I should
961 // handle this in a non-test case. So I just swallow the error and
962 // hope that when native AFP support arrives, the right thing will happen.
963 osErr
= FSCreateFork(dest
, otherForks
[thisForkIndex
].forkName
.length
, otherForks
[thisForkIndex
].forkName
.unicode
);
964 if (osErr
== noErr
|| osErr
== afpAccessDenied
)
967 // Previously I avoided opening up the fork if the fork if the
968 // length was empty, but that confused CopyFork into thinking
969 // this wasn't a drop folder copy, so I decided to simply avoid
970 // this trivial optimization. In drop folders, we always open
973 osErr
= FSOpenFork(dest
, otherForks
[thisForkIndex
].forkName
.length
, otherForks
[thisForkIndex
].forkName
.unicode
, fsWrPerm
, &otherForks
[thisForkIndex
].forkDestRefNum
);
977 mycheck_noerr( osErr
); // put up debug assert in debug builds
982 /*****************************************************************************/
984 // Copies the fork whose name is forkName from source to dest.
985 // A refnum for the destination fork may be supplied in forkDestRefNum.
986 // If forkDestRefNum is 0, we must open the destination fork ourselves,
987 // otherwise it has been opened for us and we shouldn't close it.
988 OSErr
CopyFork( const FSRef
*source
, const FSRef
*dest
, const ForkTrackerPtr sourceFork
, const CopyParams
*params
)
990 UInt64 bytesRemaining
;
991 UInt64 bytesToReadThisTime
;
992 UInt64 bytesToWriteThisTime
;
996 OSErr osErr2
= noErr
;
998 // If we haven't been passed in a sourceFork->forkDestRefNum (which basically
999 // means we're copying into a non-drop folder), create the destination
1000 // fork. We have to do this regardless of whether sourceFork->forkSize is
1001 // 0, because we want to preserve empty forks.
1002 if (sourceFork
->forkDestRefNum
== 0)
1004 osErr
= FSCreateFork(dest
, sourceFork
->forkName
.length
, sourceFork
->forkName
.unicode
);
1006 // Mac OS 9.0 has a bug (in the AppleShare external file system,
1007 // I think) [2410374] that causes FSCreateFork to return an errFSForkExists
1008 // error even though the fork is empty. The following code swallows
1009 // the error (which is harmless) in that case.
1010 if (osErr
== errFSForkExists
&& !params
->copyingToLocalVolume
)
1014 // The remainder of this code only applies if there is actual data
1015 // in the source fork.
1017 if (osErr
== noErr
&& sourceFork
->forkSize
!= 0) {
1019 // Prepare for failure.
1024 // Open up the destination fork, if we're asked to, otherwise
1025 // just use the passed in sourceFork->forkDestRefNum.
1026 if( sourceFork
->forkDestRefNum
== 0 )
1027 osErr
= FSOpenFork(dest
, sourceFork
->forkName
.length
, sourceFork
->forkName
.unicode
, fsWrPerm
, &destRef
);
1029 destRef
= sourceFork
->forkDestRefNum
;
1031 // Open up the source fork.
1033 osErr
= FSOpenFork(source
, sourceFork
->forkName
.length
, sourceFork
->forkName
.unicode
, fsRdPerm
, &sourceRef
);
1035 // Here we create space for the entire fork on the destination volume.
1036 // FSAllocateFork has the right semantics on both traditional Mac OS
1037 // and Mac OS X. On traditional Mac OS it will allocate space for the
1038 // file in one hit without any other special action. On Mac OS X,
1039 // FSAllocateFork is preferable to FSSetForkSize because it prevents
1040 // the system from zero filling the bytes that were added to the end
1041 // of the fork (which would be waste becasue we're about to write over
1042 // those bytes anyway.
1043 if( osErr
== noErr
)
1044 osErr
= FSAllocateFork(destRef
, kFSAllocNoRoundUpMask
, fsFromStart
, 0, sourceFork
->forkSize
, NULL
);
1046 // Copy the file from the source to the destination in chunks of
1047 // no more than params->copyBufferSize bytes. This is fairly
1048 // boring code except for the bytesToReadThisTime/bytesToWriteThisTime
1049 // distinction. On the last chunk, we round bytesToWriteThisTime
1050 // up to the next 512 byte boundary and then, after we exit the loop,
1051 // we set the file's EOF back to the real location (if the fork size
1052 // is not a multiple of 512 bytes).
1054 // This technique works around a 'bug' in the traditional Mac OS File Manager,
1055 // where the File Manager will put the last 512-byte block of a large write into
1056 // the cache (even if we specifically request no caching) if that block is not
1057 // full. If the block goes into the cache it will eventually have to be
1058 // flushed, which causes sub-optimal disk performance.
1060 // This is only done if the destination volume is local. For a network
1061 // volume, it's better to just write the last bytes directly.
1063 // This is extreme over-optimization given the other limits of this
1064 // sample, but I will hopefully get to the other limits eventually.
1065 bytesRemaining
= sourceFork
->forkSize
;
1066 while (osErr
== noErr
&& bytesRemaining
!= 0)
1068 if (bytesRemaining
> params
->copyBufferSize
)
1070 bytesToReadThisTime
= params
->copyBufferSize
;
1071 bytesToWriteThisTime
= bytesToReadThisTime
;
1075 bytesToReadThisTime
= bytesRemaining
;
1076 bytesToWriteThisTime
= (params
->copyingToLocalVolume
) ?
1077 (bytesRemaining
+ 0x01FF) & ~0x01FF :
1081 osErr
= FSReadFork(sourceRef
, fsAtMark
+ noCacheMask
, 0, bytesToReadThisTime
, params
->copyBuffer
, NULL
);
1083 osErr
= FSWriteFork(destRef
, fsAtMark
+ noCacheMask
, 0, bytesToWriteThisTime
, params
->copyBuffer
, NULL
);
1085 bytesRemaining
-= bytesToReadThisTime
;
1088 if (osErr
== noErr
&& (params
->copyingToLocalVolume
&& ((sourceFork
->forkSize
& 0x01FF) != 0)) )
1089 osErr
= FSSetForkSize(destRef
, fsFromStart
, sourceFork
->forkSize
);
1094 osErr2
= FSCloseFork(sourceRef
);
1095 mycheck_noerr(osErr2
);
1100 // Only close destRef if we were asked to open it (ie sourceFork->forkDestRefNum == 0) and
1101 // we actually managed to open it (ie destRef != 0).
1102 if (sourceFork
->forkDestRefNum
== 0 && destRef
!= 0)
1104 osErr2
= FSCloseFork(destRef
);
1105 mycheck_noerr(osErr2
);
1111 mycheck_noerr( osErr
); // put up debug assert in debug builds
1116 /*****************************************************************************/
1118 // Close all the forks that might have been opened by OpenAllForks.
1119 OSErr
CloseAllForks(SInt16 dataRefNum
, SInt16 rsrcRefNum
, ForkTrackerPtr otherForks
, ItemCount otherForksCount
)
1121 ItemCount thisForkIndex
;
1122 OSErr osErr
= noErr
,
1125 if (dataRefNum
!= 0)
1127 osErr2
= FSCloseFork(dataRefNum
);
1128 mycheck_noerr(osErr2
);
1132 if (rsrcRefNum
!= 0)
1134 osErr2
= FSCloseFork(rsrcRefNum
);
1135 mycheck_noerr(osErr2
);
1139 if( otherForks
!= NULL
&& otherForksCount
> 0 )
1141 for (thisForkIndex
= 0; thisForkIndex
< otherForksCount
; thisForkIndex
++)
1143 if (otherForks
[thisForkIndex
].forkDestRefNum
!= 0)
1145 osErr2
= FSCloseFork(otherForks
[thisForkIndex
].forkDestRefNum
);
1146 mycheck_noerr(osErr2
);
1153 mycheck_noerr( osErr
); // put up debug assert in debug builds
1158 /*****************************************************************************/
1160 // This routine determines the list of forks that a file has.
1161 // dataFork is populated if the file has a data fork.
1162 // rsrcFork is populated if the file has a resource fork.
1163 // otherForksParam is set to point to a memory block allocated with
1164 // NewPtr if the file has forks beyond the resource and data
1165 // forks. You must free that block with DisposePtr. otherForksCountParam
1166 // is set to the number of forks in the otherForksParam
1167 // array. This count does *not* include the resource and data forks.
1168 OSErr
CalculateForksToCopy( const FSRef
*source
,
1169 const ForkTrackerPtr dataFork
,
1170 const ForkTrackerPtr rsrcFork
,
1171 ForkTrackerPtr
*otherForksParam
,
1172 ItemCount
*otherForksCountParam
)
1175 CatPositionRec iterator
;
1176 HFSUniStr255 thisForkName
;
1177 SInt64 thisForkSize
;
1178 ForkTrackerPtr otherForks
;
1179 ItemCount otherForksCount
;
1180 ItemCount otherForksMemoryBlockCount
;
1181 OSErr osErr
= ( (source
!= NULL
) && (dataFork
!= NULL
) &&
1182 (rsrcFork
!= NULL
) && (otherForksParam
!= NULL
) &&
1183 (otherForksCountParam
!= NULL
) ) ?
1186 dataFork
->forkSize
= 0;
1187 rsrcFork
->forkSize
= 0;
1189 otherForksCount
= 0;
1190 iterator
.initialize
= 0;
1193 // Iterate through the list of forks, processing each fork name in turn.
1194 while (osErr
== noErr
&& ! done
)
1196 osErr
= FSIterateForks(source
, &iterator
, &thisForkName
, &thisForkSize
, NULL
);
1197 if (osErr
== errFSNoMoreItems
)
1202 else if (osErr
== noErr
)
1204 if ( CompareHFSUniStr255(&thisForkName
, &dataFork
->forkName
) )
1205 dataFork
->forkSize
= thisForkSize
;
1206 else if ( CompareHFSUniStr255(&thisForkName
, &rsrcFork
->forkName
) )
1207 rsrcFork
->forkSize
= thisForkSize
;
1210 // We've found a fork other than the resource and data forks.
1211 // We have to add it to the otherForks array. But the array
1212 // a) may not have been created yet, and b) may not contain
1213 // enough elements to hold the new fork.
1215 if (otherForks
== NULL
) // The array hasn't been allocated yet, allocate it.
1217 otherForksMemoryBlockCount
= kExpectedForkCount
;
1218 otherForks
= ( ForkTracker
* ) NewPtr( sizeof(ForkTracker
) * kExpectedForkCount
);
1219 if (otherForks
== NULL
)
1222 else if (otherForksCount
== otherForksMemoryBlockCount
)
1223 { // If the array doesn't contain enough elements, grow it.
1224 ForkTrackerPtr newOtherForks
;
1226 newOtherForks
= (ForkTracker
*)NewPtr(sizeof(ForkTracker
) * (otherForksCount
+ kExpectedForkCount
));
1227 if( newOtherForks
!= NULL
)
1229 BlockMoveData(otherForks
, newOtherForks
, sizeof(ForkTracker
) * otherForksCount
);
1230 otherForksMemoryBlockCount
+= kExpectedForkCount
;
1231 DisposePtr((char*)otherForks
);
1232 otherForks
= newOtherForks
;
1238 // If we have no error, we know we have space in the otherForks
1239 // array to place the new fork. Put it there and increment the
1244 BlockMoveData(&thisForkName
, &otherForks
[otherForksCount
].forkName
, sizeof(thisForkName
));
1245 otherForks
[otherForksCount
].forkSize
= thisForkSize
;
1246 otherForks
[otherForksCount
].forkDestRefNum
= 0;
1257 if (otherForks
!= NULL
)
1259 DisposePtr((char*)otherForks
);
1262 otherForksCount
= 0;
1265 *otherForksParam
= otherForks
;
1266 *otherForksCountParam
= otherForksCount
;
1268 mycheck_noerr( osErr
); // put up debug assert in debug builds
1273 /*****************************************************************************/
1275 #pragma mark ----- Calculate Buffer Size -----
1277 OSErr
CalculateBufferSize( const FSRef
*source
, const FSRef
*destDir
,
1278 ByteCount
* bufferSize
)
1280 FSVolumeRefNum sourceVRefNum
,
1282 ByteCount tmpBufferSize
= 0;
1283 OSErr osErr
= ( source
!= NULL
&& destDir
!= NULL
&& bufferSize
!= NULL
) ?
1286 if( osErr
== noErr
)
1287 osErr
= FSGetVRefNum( source
, &sourceVRefNum
);
1288 if( osErr
== noErr
)
1289 osErr
= FSGetVRefNum( destDir
, &destVRefNum
);
1292 tmpBufferSize
= BufferSizeForThisVolume(sourceVRefNum
);
1293 if (destVRefNum
!= sourceVRefNum
)
1295 ByteCount tmp
= BufferSizeForThisVolume(destVRefNum
);
1296 if (tmp
< tmpBufferSize
)
1297 tmpBufferSize
= tmp
;
1301 *bufferSize
= tmpBufferSize
;
1303 mycheck_noerr( osErr
); // put up debug assert in debug builds
1308 /*****************************************************************************/
1310 // This routine calculates the appropriate buffer size for
1311 // the given vRefNum. It's a simple composition of FSGetVolParms
1312 // BufferSizeForThisVolumeSpeed.
1313 ByteCount
BufferSizeForThisVolume(FSVolumeRefNum vRefNum
)
1315 GetVolParmsInfoBuffer volParms
;
1316 ByteCount volumeBytesPerSecond
= 0;
1320 osErr
= FSGetVolParms( vRefNum
, sizeof(volParms
), &volParms
, &actualSize
);
1321 if( osErr
== noErr
)
1323 // Version 1 of the GetVolParmsInfoBuffer included the vMAttrib
1324 // field, so we don't really need to test actualSize. A noErr
1325 // result indicates that we have the info we need. This is
1326 // just a paranoia check.
1328 mycheck(actualSize
>= offsetof(GetVolParmsInfoBuffer
, vMVolumeGrade
));
1330 // On the other hand, vMVolumeGrade was not introduced until
1331 // version 2 of the GetVolParmsInfoBuffer, so we have to explicitly
1332 // test whether we got a useful value.
1334 if( ( actualSize
>= offsetof(GetVolParmsInfoBuffer
, vMForeignPrivID
) ) &&
1335 ( volParms
.vMVolumeGrade
<= 0 ) )
1337 volumeBytesPerSecond
= -volParms
.vMVolumeGrade
;
1341 mycheck_noerr( osErr
); // put up debug assert in debug builds
1343 return BufferSizeForThisVolumeSpeed(volumeBytesPerSecond
);
1346 /*****************************************************************************/
1348 // Calculate an appropriate copy buffer size based on the volumes
1349 // rated speed. Our target is to use a buffer that takes 0.25
1350 // seconds to fill. This is necessary because the volume might be
1351 // mounted over a very slow link (like ARA), and if we do a 256 KB
1352 // read over an ARA link we'll block the File Manager queue for
1353 // so long that other clients (who might have innocently just
1354 // called PBGetCatInfoSync) will block for a noticeable amount of time.
1356 // Note that volumeBytesPerSecond might be 0, in which case we assume
1357 // some default value.
1358 ByteCount
BufferSizeForThisVolumeSpeed(UInt32 volumeBytesPerSecond
)
1360 ByteCount bufferSize
;
1362 if (volumeBytesPerSecond
== 0)
1363 bufferSize
= kDefaultCopyBufferSize
;
1365 { // We want to issue a single read that takes 0.25 of a second,
1366 // so devide the bytes per second by 4.
1367 bufferSize
= volumeBytesPerSecond
/ 4;
1370 // Round bufferSize down to 512 byte boundary.
1371 bufferSize
&= ~0x01FF;
1373 // Clip to sensible limits.
1374 if (bufferSize
< kMinimumCopyBufferSize
)
1375 bufferSize
= kMinimumCopyBufferSize
;
1376 else if (bufferSize
> kMaximumCopyBufferSize
)
1377 bufferSize
= kMaximumCopyBufferSize
;
1382 /*****************************************************************************/
1384 #pragma mark ----- Delete Objects -----
1386 OSErr
FSDeleteObjects( const FSRef
*source
)
1388 FSCatalogInfo catalogInfo
;
1389 OSErr osErr
= ( source
!= NULL
) ? noErr
: paramErr
;
1391 // get nodeFlags for container
1392 if( osErr
== noErr
)
1393 osErr
= FSGetCatalogInfo(source
, kFSCatInfoNodeFlags
, &catalogInfo
, NULL
, NULL
,NULL
);
1394 if( osErr
== noErr
&& (catalogInfo
.nodeFlags
& kFSNodeIsDirectoryMask
) != 0 )
1395 { // its a directory, so delete its contents before we delete it
1396 osErr
= FSDeleteFolder(source
);
1398 if( osErr
== noErr
&& (catalogInfo
.nodeFlags
& kFSNodeLockedMask
) != 0 ) // is object locked?
1399 { // then attempt to unlock the object (ignore osErr since FSDeleteObject will set it correctly)
1400 catalogInfo
.nodeFlags
&= ~kFSNodeLockedMask
;
1401 (void) FSSetCatalogInfo(source
, kFSCatInfoNodeFlags
, &catalogInfo
);
1403 if( osErr
== noErr
) // delete the object (if it was a directory it is now empty, so we can delete it)
1404 osErr
= FSDeleteObject(source
);
1406 mycheck_noerr( osErr
);
1411 /*****************************************************************************/
1413 #pragma mark ----- Delete Folders -----
1415 OSErr
FSDeleteFolder( const FSRef
*container
)
1417 FSDeleteObjectGlobals theGlobals
;
1419 theGlobals
.result
= ( container
!= NULL
) ? noErr
: paramErr
;
1421 // delete container's contents
1422 if( theGlobals
.result
== noErr
)
1423 FSDeleteFolderLevel(container
, &theGlobals
);
1425 mycheck_noerr( theGlobals
.result
);
1427 return ( theGlobals
.result
);
1430 /*****************************************************************************/
1432 void FSDeleteFolderLevel( const FSRef
*container
,
1433 FSDeleteObjectGlobals
*theGlobals
)
1435 FSIterator iterator
;
1439 // Open FSIterator for flat access and give delete optimization hint
1440 theGlobals
->result
= FSOpenIterator(container
, kFSIterateFlat
+ kFSIterateDelete
, &iterator
);
1441 if ( theGlobals
->result
== noErr
)
1443 do // delete the contents of the directory
1445 // get 1 item to delete
1446 theGlobals
->result
= FSGetCatalogInfoBulk( iterator
, 1, &theGlobals
->actualObjects
,
1447 NULL
, kFSCatInfoNodeFlags
, &theGlobals
->catalogInfo
,
1448 &itemToDelete
, NULL
, NULL
);
1449 if ( (theGlobals
->result
== noErr
) && (theGlobals
->actualObjects
== 1) )
1451 // save node flags in local in case we have to recurse */
1452 nodeFlags
= theGlobals
->catalogInfo
.nodeFlags
;
1454 // is it a directory?
1455 if ( (nodeFlags
& kFSNodeIsDirectoryMask
) != 0 )
1456 { // yes -- delete its contents before attempting to delete it */
1457 FSDeleteFolderLevel(&itemToDelete
, theGlobals
);
1459 if ( theGlobals
->result
== noErr
) // are we still OK to delete?
1461 if ( (nodeFlags
& kFSNodeLockedMask
) != 0 ) // is item locked?
1462 { // then attempt to unlock it (ignore result since FSDeleteObject will set it correctly)
1463 theGlobals
->catalogInfo
.nodeFlags
= nodeFlags
& ~kFSNodeLockedMask
;
1464 (void) FSSetCatalogInfo(&itemToDelete
, kFSCatInfoNodeFlags
, &theGlobals
->catalogInfo
);
1467 theGlobals
->result
= FSDeleteObject(&itemToDelete
);
1470 } while ( theGlobals
->result
== noErr
);
1472 // we found the end of the items normally, so return noErr
1473 if ( theGlobals
->result
== errFSNoMoreItems
)
1474 theGlobals
->result
= noErr
;
1476 // close the FSIterator (closing an open iterator should never fail)
1477 myverify_noerr(FSCloseIterator(iterator
));
1480 mycheck_noerr( theGlobals
->result
);
1485 /*****************************************************************************/
1487 #pragma mark ----- Utilities -----
1489 // Figures out if the given directory is a drop box or not
1490 // if it is, the Copy Engine will behave slightly differently
1491 OSErr
IsDropBox(const FSRef
* source
, Boolean
*isDropBox
)
1493 FSCatalogInfo tmpCatInfo
;
1495 Boolean isDrop
= false;
1498 // get info about the destination, and an FSSpec to it for PBHGetDirAccess
1499 osErr
= FSGetCatalogInfo(source
, kFSCatInfoNodeFlags
| kFSCatInfoPermissions
, &tmpCatInfo
, NULL
, &sourceSpec
, NULL
);
1500 if( osErr
== noErr
) // make sure the source is a directory
1501 osErr
= ((tmpCatInfo
.nodeFlags
& kFSNodeIsDirectoryMask
) != 0) ? noErr
: errFSNotAFolder
;
1502 if( osErr
== noErr
)
1506 BlockZero(&hPB
, sizeof( HParamBlockRec
));
1508 hPB
.accessParam
.ioNamePtr
= sourceSpec
.name
;
1509 hPB
.accessParam
.ioVRefNum
= sourceSpec
.vRefNum
;
1510 hPB
.accessParam
.ioDirID
= sourceSpec
.parID
;
1512 // This is the official way (reads: the way X Finder does it) to figure
1513 // out the current users access privileges to a given directory
1514 osErr
= PBHGetDirAccessSync(&hPB
);
1515 if( osErr
== noErr
) // its a drop folder if the current user only has write access
1516 isDrop
= (hPB
.accessParam
.ioACAccess
& kPrivilegesMask
) == kioACAccessUserWriteMask
;
1517 else if ( osErr
== paramErr
)
1519 // There is a bug (2908703) in the Classic File System (not OS 9.x or Carbon)
1520 // on 10.1.x where PBHGetDirAccessSync sometimes returns paramErr even when the
1521 // data passed in is correct. This is a workaround/hack for that problem,
1522 // but is not as accurate.
1523 // Basically, if "Everyone" has only Write/Search access then its a drop folder
1524 // that is the most common case when its a drop folder
1525 FSPermissionInfo
*tmpPerm
= (FSPermissionInfo
*)tmpCatInfo
.permissions
;
1526 isDrop
= ((tmpPerm
->mode
& kRWXOtherAccessMask
) == kDropFolderValue
);
1531 *isDropBox
= isDrop
;
1533 mycheck_noerr( osErr
);
1538 // The copy engine is going to set each item's creation date
1539 // to kMagicBusyCreationDate while it's copying the item.
1540 // But kMagicBusyCreationDate is an old-style 32-bit date/time,
1541 // while the HFS Plus APIs use the new 64-bit date/time. So
1542 // we have to call a happy UTC utilities routine to convert from
1543 // the local time kMagicBusyCreationDate to a UTCDateTime
1544 // gMagicBusyCreationDate, which the File Manager will store
1545 // on disk and which the Finder we read back using the old
1546 // APIs, whereupon the File Manager will convert it back
1547 // to local time (and hopefully get the kMagicBusyCreationDate
1549 OSErr
GetMagicBusyCreationDate( UTCDateTime
*date
)
1551 UTCDateTime tmpDate
= {0,0,0};
1552 OSErr osErr
= ( date
!= NULL
) ? noErr
: paramErr
;
1554 if( osErr
== noErr
)
1555 osErr
= ConvertLocalTimeToUTC(kMagicBusyCreationDate
, &tmpDate
.lowSeconds
);
1556 if( osErr
== noErr
)
1559 mycheck_noerr( osErr
); // put up debug assert in debug builds
1564 // compares two HFSUniStr255 for equality
1565 // return true if they are identical, false if not
1566 Boolean
CompareHFSUniStr255(const HFSUniStr255
*lhs
, const HFSUniStr255
*rhs
)
1568 return (lhs
->length
== rhs
->length
)
1569 && (memcmp(lhs
->unicode
, rhs
->unicode
, lhs
->length
* sizeof(UniChar
)) == 0);
1572 /*****************************************************************************/
1574 OSErr
FSGetVRefNum(const FSRef
*ref
, FSVolumeRefNum
*vRefNum
)
1576 FSCatalogInfo catalogInfo
;
1577 OSErr osErr
= ( ref
!= NULL
&& vRefNum
!= NULL
) ? noErr
: paramErr
;
1579 if( osErr
== noErr
) /* get the volume refNum from the FSRef */
1580 osErr
= FSGetCatalogInfo(ref
, kFSCatInfoVolume
, &catalogInfo
, NULL
, NULL
, NULL
);
1581 if( osErr
== noErr
)
1582 *vRefNum
= catalogInfo
.volume
;
1584 mycheck_noerr( osErr
);
1589 /*****************************************************************************/
1591 OSErr
FSGetVolParms( FSVolumeRefNum volRefNum
,
1593 GetVolParmsInfoBuffer
*volParmsInfo
,
1594 UInt32
*actualInfoSize
) /* Can Be NULL */
1597 OSErr osErr
= ( volParmsInfo
!= NULL
) ? noErr
: paramErr
;
1599 if( osErr
== noErr
)
1601 pb
.ioParam
.ioNamePtr
= NULL
;
1602 pb
.ioParam
.ioVRefNum
= volRefNum
;
1603 pb
.ioParam
.ioBuffer
= (Ptr
)volParmsInfo
;
1604 pb
.ioParam
.ioReqCount
= (SInt32
)bufferSize
;
1605 osErr
= PBHGetVolParmsSync(&pb
);
1607 /* return number of bytes the file system returned in volParmsInfo buffer */
1608 if( osErr
== noErr
&& actualInfoSize
!= NULL
)
1609 *actualInfoSize
= (UInt32
)pb
.ioParam
.ioActCount
;
1611 mycheck_noerr( osErr
); // put up debug assert in debug builds
1616 /*****************************************************************************/
1618 OSErr
UnicodeNameGetHFSName( UniCharCount nameLength
,
1619 const UniChar
*name
,
1620 TextEncoding textEncodingHint
,
1621 Boolean isVolumeName
,
1624 UnicodeMapping uMapping
;
1625 UnicodeToTextInfo utInfo
;
1626 ByteCount unicodeByteLength
;
1627 ByteCount unicodeBytesConverted
;
1628 ByteCount actualPascalBytes
;
1629 OSErr osErr
= (hfsName
!= NULL
&& name
!= NULL
) ? noErr
: paramErr
;
1631 // make sure output is valid in case we get errors or there's nothing to convert
1634 unicodeByteLength
= nameLength
* sizeof(UniChar
);
1635 if ( unicodeByteLength
== 0 )
1636 osErr
= noErr
; /* do nothing */
1639 // if textEncodingHint is kTextEncodingUnknown, get a "default" textEncodingHint
1640 if ( kTextEncodingUnknown
== textEncodingHint
)
1645 script
= (ScriptCode
)GetScriptManagerVariable(smSysScript
);
1646 region
= (RegionCode
)GetScriptManagerVariable(smRegionCode
);
1647 osErr
= UpgradeScriptInfoToTextEncoding(script
, kTextLanguageDontCare
,
1648 region
, NULL
, &textEncodingHint
);
1649 if ( osErr
== paramErr
)
1650 { // ok, ignore the region and try again
1651 osErr
= UpgradeScriptInfoToTextEncoding(script
, kTextLanguageDontCare
,
1652 kTextRegionDontCare
, NULL
,
1653 &textEncodingHint
);
1655 if ( osErr
!= noErr
) // ok... try something
1656 textEncodingHint
= kTextEncodingMacRoman
;
1659 uMapping
.unicodeEncoding
= CreateTextEncoding( kTextEncodingUnicodeV2_0
,
1660 kUnicodeCanonicalDecompVariant
,
1661 kUnicode16BitFormat
);
1662 uMapping
.otherEncoding
= GetTextEncodingBase(textEncodingHint
);
1663 uMapping
.mappingVersion
= kUnicodeUseHFSPlusMapping
;
1665 osErr
= CreateUnicodeToTextInfo(&uMapping
, &utInfo
);
1666 if( osErr
== noErr
)
1668 osErr
= ConvertFromUnicodeToText( utInfo
, unicodeByteLength
, name
, kUnicodeLooseMappingsMask
,
1669 0, NULL
, 0, NULL
, /* offsetCounts & offsetArrays */
1670 isVolumeName
? kHFSMaxVolumeNameChars
: kHFSMaxFileNameChars
,
1671 &unicodeBytesConverted
, &actualPascalBytes
, &hfsName
[1]);
1673 if( osErr
== noErr
)
1674 hfsName
[0] = actualPascalBytes
;
1676 // verify the result in debug builds -- there's really not anything you can do if it fails
1677 myverify_noerr(DisposeUnicodeToTextInfo(&utInfo
));
1680 mycheck_noerr( osErr
); // put up debug assert in debug builds
1685 /*****************************************************************************/
1687 OSErr
FSMakeFSRef( FSVolumeRefNum volRefNum
,
1689 ConstStr255Param name
,
1693 OSErr osErr
= ( ref
!= NULL
) ? noErr
: paramErr
;
1695 if( osErr
== noErr
)
1697 pb
.ioVRefNum
= volRefNum
;
1699 pb
.ioNamePtr
= (StringPtr
)name
;
1701 osErr
= PBMakeFSRefSync(&pb
);
1704 mycheck_noerr( osErr
); // put up debug assert in debug builds