4 Contains: A robust, general purpose directory copy routine.
8 Copyright: © 1992-2001 by Apple Computer, Inc., all rights reserved.
10 You may incorporate this sample code into your applications without
11 restriction, though the sample code has been provided "AS IS" and the
12 responsibility for its operation is 100% yours. However, what you are
13 not permitted to do is to redistribute the source as "DSC Sample Code"
14 after having made changes. If you're going to re-distribute the source,
15 we require that you make it clear in the source that the code was
16 descended from Apple Sample Code, but that you've made changes.
20 DRI: Apple Macintosh Developer Technical Support
22 Other Contact: Apple Macintosh Developer Technical Support
23 <http://developer.apple.com/bugreporter/>
25 Technology: DTS Sample Code
31 Change History (most recent first):
33 <2> 2/7/01 JL Added standard header. Updated names of includes.
34 <1> 12/06/99 JL MoreFiles 1.5.
38 #include <MacErrors.h>
39 #include <MacMemory.h>
44 #define __COMPILINGMOREFILES
46 #include "MoreFiles.h"
47 #include "MoreFilesExtras.h"
48 #include "MoreDesktopMgr.h"
50 #include "DirectoryCopy.h"
52 /*****************************************************************************/
58 dirCopyBigCopyBuffSize
= 0x00004000,
59 dirCopyMinCopyBuffSize
= 0x00000200
63 /*****************************************************************************/
65 /* local data structures */
67 /* The EnumerateGlobals structure is used to minimize the amount of
68 ** stack space used when recursively calling CopyLevel and to hold
69 ** global information that might be needed at any time. */
71 #if PRAGMA_STRUCT_ALIGN
72 #pragma options align=mac68k
74 struct EnumerateGlobals
76 Ptr copyBuffer
; /* pointer to buffer used for file copy operations */
77 long bufferSize
; /* the size of the copy buffer */
78 CopyErrProcPtr errorHandler
; /* pointer to error handling function */
79 CopyFilterProcPtr copyFilterProc
; /* pointer to filter function */
80 OSErr error
; /* temporary holder of results - saves 2 bytes of stack each level */
81 Boolean bailout
; /* set to true to by error handling function if fatal error */
82 short destinationVRefNum
; /* the destination vRefNum */
83 Str63 itemName
; /* the name of the current item */
84 CInfoPBRec myCPB
; /* the parameter block used for PBGetCatInfo calls */
86 #if PRAGMA_STRUCT_ALIGN
87 #pragma options align=reset
90 typedef struct EnumerateGlobals EnumerateGlobals
;
91 typedef EnumerateGlobals
*EnumerateGlobalsPtr
;
94 /* The PreflightGlobals structure is used to minimize the amount of
95 ** stack space used when recursively calling GetLevelSize and to hold
96 ** global information that might be needed at any time. */
98 #if PRAGMA_STRUCT_ALIGN
99 #pragma options align=mac68k
101 struct PreflightGlobals
103 OSErr result
; /* temporary holder of results - saves 2 bytes of stack each level */
104 Str63 itemName
; /* the name of the current item */
105 CInfoPBRec myCPB
; /* the parameter block used for PBGetCatInfo calls */
107 unsigned long dstBlksPerAllocBlk
; /* the number of 512 byte blocks per allocation block on destination */
109 unsigned long allocBlksNeeded
; /* the total number of allocation blocks needed */
111 unsigned long tempBlocks
; /* temporary storage for calculations (save some stack space) */
112 CopyFilterProcPtr copyFilterProc
; /* pointer to filter function */
114 #if PRAGMA_STRUCT_ALIGN
115 #pragma options align=reset
118 typedef struct PreflightGlobals PreflightGlobals
;
119 typedef PreflightGlobals
*PreflightGlobalsPtr
;
121 /*****************************************************************************/
123 /* static prototypes */
125 static void GetLevelSize(long currentDirID
,
126 PreflightGlobals
*theGlobals
);
128 static OSErr
PreflightDirectoryCopySpace(short srcVRefNum
,
131 CopyFilterProcPtr copyFilterProc
,
134 static void CopyLevel(long sourceDirID
,
136 EnumerateGlobals
*theGlobals
);
138 /*****************************************************************************/
140 static void GetLevelSize(long currentDirID
,
141 PreflightGlobals
*theGlobals
)
147 theGlobals
->myCPB
.dirInfo
.ioFDirIndex
= index
;
148 theGlobals
->myCPB
.dirInfo
.ioDrDirID
= currentDirID
; /* we need to do this every time */
149 /* through, since GetCatInfo */
150 /* returns ioFlNum in this field */
151 theGlobals
->result
= PBGetCatInfoSync(&theGlobals
->myCPB
);
152 if ( theGlobals
->result
== noErr
)
154 if ( (theGlobals
->copyFilterProc
== NULL
) ||
155 CallCopyFilterProc(theGlobals
->copyFilterProc
, &theGlobals
->myCPB
) ) /* filter if filter proc was supplied */
157 /* Either there's no filter proc OR the filter proc says to use this item */
158 if ( (theGlobals
->myCPB
.dirInfo
.ioFlAttrib
& kioFlAttribDirMask
) != 0 )
160 /* we have a directory */
162 GetLevelSize(theGlobals
->myCPB
.dirInfo
.ioDrDirID
, theGlobals
); /* recurse */
163 theGlobals
->result
= noErr
; /* clear error return on way back */
167 /* We have a file - add its allocation blocks to allocBlksNeeded. */
168 /* Since space on Mac OS disks is always allocated in allocation blocks, */
169 /* this takes into account rounding up to the end of an allocation block. */
171 /* get number of 512-byte blocks needed for data fork */
172 if ( ((unsigned long)theGlobals
->myCPB
.hFileInfo
.ioFlLgLen
& 0x000001ff) != 0 )
174 theGlobals
->tempBlocks
= ((unsigned long)theGlobals
->myCPB
.hFileInfo
.ioFlLgLen
>> 9) + 1;
178 theGlobals
->tempBlocks
= (unsigned long)theGlobals
->myCPB
.hFileInfo
.ioFlLgLen
>> 9;
180 /* now, calculate number of new allocation blocks needed for the data fork and add it to the total */
181 if ( theGlobals
->tempBlocks
% theGlobals
->dstBlksPerAllocBlk
)
183 theGlobals
->allocBlksNeeded
+= (theGlobals
->tempBlocks
/ theGlobals
->dstBlksPerAllocBlk
) + 1;
187 theGlobals
->allocBlksNeeded
+= theGlobals
->tempBlocks
/ theGlobals
->dstBlksPerAllocBlk
;
190 /* get number of 512-byte blocks needed for resource fork */
191 if ( ((unsigned long)theGlobals
->myCPB
.hFileInfo
.ioFlRLgLen
& 0x000001ff) != 0 )
193 theGlobals
->tempBlocks
= ((unsigned long)theGlobals
->myCPB
.hFileInfo
.ioFlRLgLen
>> 9) + 1;
197 theGlobals
->tempBlocks
= (unsigned long)theGlobals
->myCPB
.hFileInfo
.ioFlRLgLen
>> 9;
199 /* now, calculate number of new allocation blocks needed for the resource fork and add it to the total */
200 if ( theGlobals
->tempBlocks
% theGlobals
->dstBlksPerAllocBlk
)
202 theGlobals
->allocBlksNeeded
+= (theGlobals
->tempBlocks
/ theGlobals
->dstBlksPerAllocBlk
) + 1;
206 theGlobals
->allocBlksNeeded
+= theGlobals
->tempBlocks
/ theGlobals
->dstBlksPerAllocBlk
;
212 } while ( theGlobals
->result
== noErr
);
215 /*****************************************************************************/
217 static OSErr
PreflightDirectoryCopySpace(short srcVRefNum
,
220 CopyFilterProcPtr copyFilterProc
,
225 unsigned long dstFreeBlocks
;
226 PreflightGlobals theGlobals
;
228 error
= XGetVolumeInfoNoName(NULL
, dstVRefNum
, &pb
);
229 if ( error
== noErr
)
231 /* Convert freeBytes to free disk blocks (512-byte blocks) */
232 dstFreeBlocks
= U32SetU(U64ShiftRight(pb
.ioVFreeBytes
, 9));
234 /* get allocation block size (always multiple of 512) and divide by 512
235 to get number of 512-byte blocks per allocation block */
236 theGlobals
.dstBlksPerAllocBlk
= ((unsigned long)pb
.ioVAlBlkSiz
>> 9);
238 theGlobals
.allocBlksNeeded
= 0;
240 theGlobals
.myCPB
.dirInfo
.ioNamePtr
= theGlobals
.itemName
;
241 theGlobals
.myCPB
.dirInfo
.ioVRefNum
= srcVRefNum
;
243 theGlobals
.copyFilterProc
= copyFilterProc
;
245 GetLevelSize(srcDirID
, &theGlobals
);
247 /* Is there enough room on the destination volume for the source file? */
248 /* Note: This will work because the largest number of disk blocks supported */
249 /* on a 2TB volume is 0xffffffff and (allocBlksNeeded * dstBlksPerAllocBlk) */
250 /* will always be less than 0xffffffff. */
251 *spaceOK
= ((theGlobals
.allocBlksNeeded
* theGlobals
.dstBlksPerAllocBlk
) <= dstFreeBlocks
);
257 /*****************************************************************************/
259 static void CopyLevel(long sourceDirID
,
261 EnumerateGlobals
*theGlobals
)
263 long currentSrcDirID
= 0 ;
269 /* Get next source item at the current directory level */
271 theGlobals
->myCPB
.dirInfo
.ioFDirIndex
= index
;
272 theGlobals
->myCPB
.dirInfo
.ioDrDirID
= sourceDirID
;
273 theGlobals
->error
= PBGetCatInfoSync(&theGlobals
->myCPB
);
275 if ( theGlobals
->error
== noErr
)
277 if ( (theGlobals
->copyFilterProc
== NULL
) ||
278 CallCopyFilterProc(theGlobals
->copyFilterProc
, &theGlobals
->myCPB
) ) /* filter if filter proc was supplied */
280 /* Either there's no filter proc OR the filter proc says to use this item */
282 /* We have an item. Is it a file or directory? */
283 if ( (theGlobals
->myCPB
.hFileInfo
.ioFlAttrib
& kioFlAttribDirMask
) != 0 )
285 /* We have a directory */
287 /* Create a new directory at the destination. No errors allowed! */
288 theGlobals
->error
= DirCreate(theGlobals
->destinationVRefNum
, dstDirID
, theGlobals
->itemName
, &newDirID
);
289 if ( theGlobals
->error
== noErr
)
291 /* Save the current source directory ID where we can get it when we come back
292 ** from recursion land. */
293 currentSrcDirID
= theGlobals
->myCPB
.dirInfo
.ioDrDirID
;
295 /* Dive again (copy the directory level we just found below this one) */
296 CopyLevel(theGlobals
->myCPB
.dirInfo
.ioDrDirID
, newDirID
, theGlobals
);
298 if ( !theGlobals
->bailout
)
300 /* Copy comment from old to new directory. */
301 /* Ignore the result because we really don't care if it worked or not. */
302 (void) DTCopyComment(theGlobals
->myCPB
.dirInfo
.ioVRefNum
, currentSrcDirID
, NULL
, theGlobals
->destinationVRefNum
, newDirID
, NULL
);
304 /* Copy directory attributes (dates, etc.) to newDirID. */
305 /* No errors allowed */
306 theGlobals
->error
= CopyFileMgrAttributes(theGlobals
->myCPB
.dirInfo
.ioVRefNum
, currentSrcDirID
, NULL
, theGlobals
->destinationVRefNum
, newDirID
, NULL
, true);
308 /* handle any errors from CopyFileMgrAttributes */
309 if ( theGlobals
->error
!= noErr
)
311 if ( theGlobals
->errorHandler
!= NULL
)
313 theGlobals
->bailout
= CallCopyErrProc(theGlobals
->errorHandler
, theGlobals
->error
, copyDirFMAttributesOp
,
314 theGlobals
->myCPB
.dirInfo
.ioVRefNum
, currentSrcDirID
, NULL
,
315 theGlobals
->destinationVRefNum
, newDirID
, NULL
);
319 /* If you don't handle the errors with an error handler, */
320 /* then the copy stops here. */
321 theGlobals
->bailout
= true;
326 else /* error handling for DirCreate */
328 /* note that currentSrcDirID has not been initialised when entering this execution path */
329 if ( theGlobals
->errorHandler
!= NULL
)
331 theGlobals
->bailout
= CallCopyErrProc(theGlobals
->errorHandler
, theGlobals
->error
, dirCreateOp
,
332 theGlobals
->myCPB
.dirInfo
.ioVRefNum
, currentSrcDirID
, NULL
,
333 theGlobals
->destinationVRefNum
, dstDirID
, theGlobals
->itemName
);
337 /* If you don't handle the errors with an error handler, */
338 /* then the copy stops here. */
339 theGlobals
->bailout
= true;
343 if ( !theGlobals
->bailout
)
345 /* clear error return on way back if we aren't bailing out */
346 theGlobals
->error
= noErr
;
351 /* We have a file, so copy it */
353 theGlobals
->error
= FileCopy(theGlobals
->myCPB
.hFileInfo
.ioVRefNum
,
354 theGlobals
->myCPB
.hFileInfo
.ioFlParID
,
355 theGlobals
->itemName
,
356 theGlobals
->destinationVRefNum
,
360 theGlobals
->copyBuffer
,
361 theGlobals
->bufferSize
,
364 /* handle any errors from FileCopy */
365 if ( theGlobals
->error
!= noErr
)
367 if ( theGlobals
->errorHandler
!= NULL
)
369 theGlobals
->bailout
= CallCopyErrProc(theGlobals
->errorHandler
, theGlobals
->error
, fileCopyOp
,
370 theGlobals
->myCPB
.hFileInfo
.ioVRefNum
, theGlobals
->myCPB
.hFileInfo
.ioFlParID
, theGlobals
->itemName
,
371 theGlobals
->destinationVRefNum
, dstDirID
, NULL
);
372 if ( !theGlobals
->bailout
)
374 /* If the CopyErrProc handled the problem, clear the error here */
375 theGlobals
->error
= noErr
;
380 /* If you don't handle the errors with an error handler, */
381 /* then the copy stops here. */
382 theGlobals
->bailout
= true;
389 { /* error handling for PBGetCatInfo */
390 /* it's normal to get a fnfErr when indexing; that only means you've hit the end of the directory */
391 if ( theGlobals
->error
!= fnfErr
)
393 if ( theGlobals
->errorHandler
!= NULL
)
395 theGlobals
->bailout
= CallCopyErrProc(theGlobals
->errorHandler
, theGlobals
->error
, getNextItemOp
,
396 theGlobals
->myCPB
.dirInfo
.ioVRefNum
, sourceDirID
, NULL
, 0, 0, NULL
);
397 if ( !theGlobals
->bailout
)
399 /* If the CopyErrProc handled the problem, clear the error here */
400 theGlobals
->error
= noErr
;
405 /* If you don't handle the errors with an error handler, */
406 /* then the copy stops here. */
407 theGlobals
->bailout
= true;
411 ++index
; /* prepare to get next item */
412 } while ( (theGlobals
->error
== noErr
) && (!theGlobals
->bailout
) ); /* time to fall back a level? */
415 /*****************************************************************************/
417 pascal OSErr
FilteredDirectoryCopy(short srcVRefNum
,
419 ConstStr255Param srcName
,
422 ConstStr255Param dstName
,
423 ConstStr255Param copyName
,
427 CopyErrProcPtr copyErrHandler
,
428 CopyFilterProcPtr copyFilterProc
)
430 EnumerateGlobals theGlobals
;
433 Boolean ourCopyBuffer
= false;
434 Str63 srcDirName
, oldDiskName
;
437 /* Make sure a copy buffer is allocated. */
438 if ( copyBufferPtr
== NULL
)
440 /* The caller didn't supply a copy buffer so grab one from the application heap.
441 ** Try to get a big copy buffer, if we can't, try for a 512-byte buffer.
442 ** If 512 bytes aren't available, we're in trouble. */
443 copyBufferSize
= dirCopyBigCopyBuffSize
;
444 copyBufferPtr
= NewPtr(copyBufferSize
);
445 if ( copyBufferPtr
== NULL
)
447 copyBufferSize
= dirCopyMinCopyBuffSize
;
448 copyBufferPtr
= NewPtr(copyBufferSize
);
449 if ( copyBufferPtr
== NULL
)
451 return ( memFullErr
);
454 ourCopyBuffer
= true;
457 /* Get the real dirID where we're copying from and make sure it is a directory. */
458 error
= GetDirectoryID(srcVRefNum
, srcDirID
, srcName
, &srcDirID
, &isDirectory
);
459 if ( error
!= noErr
)
469 /* Special case destination if it is the root parent directory. */
470 /* Since you can't create the root directory, this is needed if */
471 /* you want to copy a directory's content to a disk's root directory. */
472 if ( (dstDirID
== fsRtParID
) && (dstName
== NULL
) )
474 dstDirID
= fsRtParID
;
480 /* Get the real dirID where we're going to put the copy and make sure it is a directory. */
481 error
= GetDirectoryID(dstVRefNum
, dstDirID
, dstName
, &dstDirID
, &isDirectory
);
482 if ( error
!= noErr
)
493 /* Get the real vRefNum of both the source and destination */
494 error
= DetermineVRefNum(srcName
, srcVRefNum
, &srcVRefNum
);
495 if ( error
!= noErr
)
499 error
= DetermineVRefNum(dstName
, dstVRefNum
, &dstVRefNum
);
500 if ( error
!= noErr
)
507 error
= PreflightDirectoryCopySpace(srcVRefNum
, srcDirID
, dstVRefNum
, copyFilterProc
, &spaceOK
);
508 if ( error
!= noErr
)
514 error
= dskFulErr
; /* not enough room on destination */
519 /* Create the new directory in the destination directory with the */
520 /* same name as the source directory. */
521 error
= GetDirName(srcVRefNum
, srcDirID
, srcDirName
);
522 if ( error
!= noErr
)
527 /* Again, special case destination if the destination is the */
528 /* root parent directory. This time, we'll rename the disk to */
529 /* the source directory name. */
530 if ( dstDirID
== fsRtParID
)
532 /* Get the current name of the destination disk */
533 error
= GetDirName(dstVRefNum
, fsRtDirID
, oldDiskName
);
534 if ( error
== noErr
)
536 /* use the copyName as srcDirName if supplied */
537 if ( copyName
!= NULL
)
539 /* make a copy since copyName is a const input */
540 BlockMoveData(copyName
, srcDirName
, sizeof(Str31
));
542 /* Shorten the name if it's too long to be the volume name */
543 TruncPString(srcDirName
, srcDirName
, 27);
545 /* Rename the disk */
546 error
= HRename(dstVRefNum
, fsRtParID
, oldDiskName
, srcDirName
);
548 /* and copy to the root directory */
549 dstDirID
= fsRtDirID
;
554 /* use the copyName as srcDirName if supplied */
555 error
= DirCreate(dstVRefNum
, dstDirID
, ((copyName
!= NULL
) ? copyName
: srcDirName
), &dstDirID
);
557 if ( error
!= noErr
)
559 /* handle any errors from DirCreate */
560 if ( copyErrHandler
!= NULL
)
562 if ( CallCopyErrProc(copyErrHandler
, error
, dirCreateOp
,
563 srcVRefNum
, srcDirID
, NULL
,
564 dstVRefNum
, dstDirID
, srcDirName
) )
570 /* If the CopyErrProc handled the problem, clear the error here */
577 /* If you don't handle the errors with an error handler, */
578 /* then the copy stops here. */
583 /* dstDirID is now the newly created directory! */
585 /* Set up the globals we need to access from the recursive routine. */
586 theGlobals
.copyBuffer
= (Ptr
)copyBufferPtr
;
587 theGlobals
.bufferSize
= copyBufferSize
;
588 theGlobals
.destinationVRefNum
= dstVRefNum
; /* so we can get to it always */
589 theGlobals
.myCPB
.hFileInfo
.ioNamePtr
= (StringPtr
)&theGlobals
.itemName
;
590 theGlobals
.myCPB
.hFileInfo
.ioVRefNum
= srcVRefNum
;
591 theGlobals
.errorHandler
= copyErrHandler
;
592 theGlobals
.bailout
= false;
593 theGlobals
.copyFilterProc
= copyFilterProc
;
595 /* Here we go into recursion land... */
596 CopyLevel(srcDirID
, dstDirID
, &theGlobals
);
597 error
= theGlobals
.error
; /* get the result */
599 if ( !theGlobals
.bailout
)
601 /* Copy comment from source to destination directory. */
602 /* Ignore the result because we really don't care if it worked or not. */
603 (void) DTCopyComment(srcVRefNum
, srcDirID
, NULL
, dstVRefNum
, dstDirID
, NULL
);
605 /* Copy the File Manager attributes */
606 error
= CopyFileMgrAttributes(srcVRefNum
, srcDirID
, NULL
,
607 dstVRefNum
, dstDirID
, NULL
, true);
609 /* handle any errors from CopyFileMgrAttributes */
610 if ( (error
!= noErr
) && (copyErrHandler
!= NULL
) )
612 theGlobals
.bailout
= CallCopyErrProc(copyErrHandler
, error
, copyDirFMAttributesOp
,
613 srcVRefNum
, srcDirID
, NULL
,
614 dstVRefNum
, dstDirID
, NULL
);
619 /* Get rid of the copy buffer if we allocated it. */
622 DisposePtr((Ptr
)copyBufferPtr
);
628 /*****************************************************************************/
630 pascal OSErr
DirectoryCopy(short srcVRefNum
,
632 ConstStr255Param srcName
,
635 ConstStr255Param dstName
,
636 ConstStr255Param copyName
,
640 CopyErrProcPtr copyErrHandler
)
642 return ( FilteredDirectoryCopy(srcVRefNum
, srcDirID
, srcName
,
643 dstVRefNum
, dstDirID
, dstName
,
645 copyBufferPtr
, copyBufferSize
, preflight
,
646 copyErrHandler
, NULL
) );
649 /*****************************************************************************/
651 pascal OSErr
FSpFilteredDirectoryCopy(const FSSpec
*srcSpec
,
652 const FSSpec
*dstSpec
,
653 ConstStr255Param copyName
,
657 CopyErrProcPtr copyErrHandler
,
658 CopyFilterProcPtr copyFilterProc
)
660 return ( FilteredDirectoryCopy(srcSpec
->vRefNum
, srcSpec
->parID
, srcSpec
->name
,
661 dstSpec
->vRefNum
, dstSpec
->parID
, dstSpec
->name
,
663 copyBufferPtr
, copyBufferSize
, preflight
,
664 copyErrHandler
, copyFilterProc
) );
667 /*****************************************************************************/
669 pascal OSErr
FSpDirectoryCopy(const FSSpec
*srcSpec
,
670 const FSSpec
*dstSpec
,
671 ConstStr255Param copyName
,
675 CopyErrProcPtr copyErrHandler
)
677 return ( FilteredDirectoryCopy(srcSpec
->vRefNum
, srcSpec
->parID
, srcSpec
->name
,
678 dstSpec
->vRefNum
, dstSpec
->parID
, dstSpec
->name
,
680 copyBufferPtr
, copyBufferSize
, preflight
,
681 copyErrHandler
, NULL
) );
684 /*****************************************************************************/