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
;
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 if ( theGlobals
->errorHandler
!= NULL
)
330 theGlobals
->bailout
= CallCopyErrProc(theGlobals
->errorHandler
, theGlobals
->error
, dirCreateOp
,
331 theGlobals
->myCPB
.dirInfo
.ioVRefNum
, currentSrcDirID
, NULL
,
332 theGlobals
->destinationVRefNum
, dstDirID
, theGlobals
->itemName
);
336 /* If you don't handle the errors with an error handler, */
337 /* then the copy stops here. */
338 theGlobals
->bailout
= true;
342 if ( !theGlobals
->bailout
)
344 /* clear error return on way back if we aren't bailing out */
345 theGlobals
->error
= noErr
;
350 /* We have a file, so copy it */
352 theGlobals
->error
= FileCopy(theGlobals
->myCPB
.hFileInfo
.ioVRefNum
,
353 theGlobals
->myCPB
.hFileInfo
.ioFlParID
,
354 theGlobals
->itemName
,
355 theGlobals
->destinationVRefNum
,
359 theGlobals
->copyBuffer
,
360 theGlobals
->bufferSize
,
363 /* handle any errors from FileCopy */
364 if ( theGlobals
->error
!= noErr
)
366 if ( theGlobals
->errorHandler
!= NULL
)
368 theGlobals
->bailout
= CallCopyErrProc(theGlobals
->errorHandler
, theGlobals
->error
, fileCopyOp
,
369 theGlobals
->myCPB
.hFileInfo
.ioVRefNum
, theGlobals
->myCPB
.hFileInfo
.ioFlParID
, theGlobals
->itemName
,
370 theGlobals
->destinationVRefNum
, dstDirID
, NULL
);
371 if ( !theGlobals
->bailout
)
373 /* If the CopyErrProc handled the problem, clear the error here */
374 theGlobals
->error
= noErr
;
379 /* If you don't handle the errors with an error handler, */
380 /* then the copy stops here. */
381 theGlobals
->bailout
= true;
388 { /* error handling for PBGetCatInfo */
389 /* it's normal to get a fnfErr when indexing; that only means you've hit the end of the directory */
390 if ( theGlobals
->error
!= fnfErr
)
392 if ( theGlobals
->errorHandler
!= NULL
)
394 theGlobals
->bailout
= CallCopyErrProc(theGlobals
->errorHandler
, theGlobals
->error
, getNextItemOp
,
395 theGlobals
->myCPB
.dirInfo
.ioVRefNum
, sourceDirID
, NULL
, 0, 0, NULL
);
396 if ( !theGlobals
->bailout
)
398 /* If the CopyErrProc handled the problem, clear the error here */
399 theGlobals
->error
= noErr
;
404 /* If you don't handle the errors with an error handler, */
405 /* then the copy stops here. */
406 theGlobals
->bailout
= true;
410 ++index
; /* prepare to get next item */
411 } while ( (theGlobals
->error
== noErr
) && (!theGlobals
->bailout
) ); /* time to fall back a level? */
414 /*****************************************************************************/
416 pascal OSErr
FilteredDirectoryCopy(short srcVRefNum
,
418 ConstStr255Param srcName
,
421 ConstStr255Param dstName
,
422 ConstStr255Param copyName
,
426 CopyErrProcPtr copyErrHandler
,
427 CopyFilterProcPtr copyFilterProc
)
429 EnumerateGlobals theGlobals
;
432 Boolean ourCopyBuffer
= false;
433 Str63 srcDirName
, oldDiskName
;
436 /* Make sure a copy buffer is allocated. */
437 if ( copyBufferPtr
== NULL
)
439 /* The caller didn't supply a copy buffer so grab one from the application heap.
440 ** Try to get a big copy buffer, if we can't, try for a 512-byte buffer.
441 ** If 512 bytes aren't available, we're in trouble. */
442 copyBufferSize
= dirCopyBigCopyBuffSize
;
443 copyBufferPtr
= NewPtr(copyBufferSize
);
444 if ( copyBufferPtr
== NULL
)
446 copyBufferSize
= dirCopyMinCopyBuffSize
;
447 copyBufferPtr
= NewPtr(copyBufferSize
);
448 if ( copyBufferPtr
== NULL
)
450 return ( memFullErr
);
453 ourCopyBuffer
= true;
456 /* Get the real dirID where we're copying from and make sure it is a directory. */
457 error
= GetDirectoryID(srcVRefNum
, srcDirID
, srcName
, &srcDirID
, &isDirectory
);
458 if ( error
!= noErr
)
468 /* Special case destination if it is the root parent directory. */
469 /* Since you can't create the root directory, this is needed if */
470 /* you want to copy a directory's content to a disk's root directory. */
471 if ( (dstDirID
== fsRtParID
) && (dstName
== NULL
) )
473 dstDirID
= fsRtParID
;
479 /* Get the real dirID where we're going to put the copy and make sure it is a directory. */
480 error
= GetDirectoryID(dstVRefNum
, dstDirID
, dstName
, &dstDirID
, &isDirectory
);
481 if ( error
!= noErr
)
492 /* Get the real vRefNum of both the source and destination */
493 error
= DetermineVRefNum(srcName
, srcVRefNum
, &srcVRefNum
);
494 if ( error
!= noErr
)
498 error
= DetermineVRefNum(dstName
, dstVRefNum
, &dstVRefNum
);
499 if ( error
!= noErr
)
506 error
= PreflightDirectoryCopySpace(srcVRefNum
, srcDirID
, dstVRefNum
, copyFilterProc
, &spaceOK
);
507 if ( error
!= noErr
)
513 error
= dskFulErr
; /* not enough room on destination */
518 /* Create the new directory in the destination directory with the */
519 /* same name as the source directory. */
520 error
= GetDirName(srcVRefNum
, srcDirID
, srcDirName
);
521 if ( error
!= noErr
)
526 /* Again, special case destination if the destination is the */
527 /* root parent directory. This time, we'll rename the disk to */
528 /* the source directory name. */
529 if ( dstDirID
== fsRtParID
)
531 /* Get the current name of the destination disk */
532 error
= GetDirName(dstVRefNum
, fsRtDirID
, oldDiskName
);
533 if ( error
== noErr
)
535 /* use the copyName as srcDirName if supplied */
536 if ( copyName
!= NULL
)
538 /* make a copy since copyName is a const input */
539 BlockMoveData(copyName
, srcDirName
, sizeof(Str31
));
541 /* Shorten the name if it's too long to be the volume name */
542 TruncPString(srcDirName
, srcDirName
, 27);
544 /* Rename the disk */
545 error
= HRename(dstVRefNum
, fsRtParID
, oldDiskName
, srcDirName
);
547 /* and copy to the root directory */
548 dstDirID
= fsRtDirID
;
553 /* use the copyName as srcDirName if supplied */
554 error
= DirCreate(dstVRefNum
, dstDirID
, ((copyName
!= NULL
) ? copyName
: srcDirName
), &dstDirID
);
556 if ( error
!= noErr
)
558 /* handle any errors from DirCreate */
559 if ( copyErrHandler
!= NULL
)
561 if ( CallCopyErrProc(copyErrHandler
, error
, dirCreateOp
,
562 srcVRefNum
, srcDirID
, NULL
,
563 dstVRefNum
, dstDirID
, srcDirName
) )
569 /* If the CopyErrProc handled the problem, clear the error here */
576 /* If you don't handle the errors with an error handler, */
577 /* then the copy stops here. */
582 /* dstDirID is now the newly created directory! */
584 /* Set up the globals we need to access from the recursive routine. */
585 theGlobals
.copyBuffer
= (Ptr
)copyBufferPtr
;
586 theGlobals
.bufferSize
= copyBufferSize
;
587 theGlobals
.destinationVRefNum
= dstVRefNum
; /* so we can get to it always */
588 theGlobals
.myCPB
.hFileInfo
.ioNamePtr
= (StringPtr
)&theGlobals
.itemName
;
589 theGlobals
.myCPB
.hFileInfo
.ioVRefNum
= srcVRefNum
;
590 theGlobals
.errorHandler
= copyErrHandler
;
591 theGlobals
.bailout
= false;
592 theGlobals
.copyFilterProc
= copyFilterProc
;
594 /* Here we go into recursion land... */
595 CopyLevel(srcDirID
, dstDirID
, &theGlobals
);
596 error
= theGlobals
.error
; /* get the result */
598 if ( !theGlobals
.bailout
)
600 /* Copy comment from source to destination directory. */
601 /* Ignore the result because we really don't care if it worked or not. */
602 (void) DTCopyComment(srcVRefNum
, srcDirID
, NULL
, dstVRefNum
, dstDirID
, NULL
);
604 /* Copy the File Manager attributes */
605 error
= CopyFileMgrAttributes(srcVRefNum
, srcDirID
, NULL
,
606 dstVRefNum
, dstDirID
, NULL
, true);
608 /* handle any errors from CopyFileMgrAttributes */
609 if ( (error
!= noErr
) && (copyErrHandler
!= NULL
) )
611 theGlobals
.bailout
= CallCopyErrProc(copyErrHandler
, error
, copyDirFMAttributesOp
,
612 srcVRefNum
, srcDirID
, NULL
,
613 dstVRefNum
, dstDirID
, NULL
);
618 /* Get rid of the copy buffer if we allocated it. */
621 DisposePtr((Ptr
)copyBufferPtr
);
627 /*****************************************************************************/
629 pascal OSErr
DirectoryCopy(short srcVRefNum
,
631 ConstStr255Param srcName
,
634 ConstStr255Param dstName
,
635 ConstStr255Param copyName
,
639 CopyErrProcPtr copyErrHandler
)
641 return ( FilteredDirectoryCopy(srcVRefNum
, srcDirID
, srcName
,
642 dstVRefNum
, dstDirID
, dstName
,
644 copyBufferPtr
, copyBufferSize
, preflight
,
645 copyErrHandler
, NULL
) );
648 /*****************************************************************************/
650 pascal OSErr
FSpFilteredDirectoryCopy(const FSSpec
*srcSpec
,
651 const FSSpec
*dstSpec
,
652 ConstStr255Param copyName
,
656 CopyErrProcPtr copyErrHandler
,
657 CopyFilterProcPtr copyFilterProc
)
659 return ( FilteredDirectoryCopy(srcSpec
->vRefNum
, srcSpec
->parID
, srcSpec
->name
,
660 dstSpec
->vRefNum
, dstSpec
->parID
, dstSpec
->name
,
662 copyBufferPtr
, copyBufferSize
, preflight
,
663 copyErrHandler
, copyFilterProc
) );
666 /*****************************************************************************/
668 pascal OSErr
FSpDirectoryCopy(const FSSpec
*srcSpec
,
669 const FSSpec
*dstSpec
,
670 ConstStr255Param copyName
,
674 CopyErrProcPtr copyErrHandler
)
676 return ( FilteredDirectoryCopy(srcSpec
->vRefNum
, srcSpec
->parID
, srcSpec
->name
,
677 dstSpec
->vRefNum
, dstSpec
->parID
, dstSpec
->name
,
679 copyBufferPtr
, copyBufferSize
, preflight
,
680 copyErrHandler
, NULL
) );
683 /*****************************************************************************/