2 ** Apple Macintosh Developer Technical Support
4 ** DirectoryCopy: A robust, general purpose directory copy routine.
6 ** by Jim Luther, Apple Developer Technical Support Emeritus
8 ** File: DirectoryCopy.c
10 ** Copyright © 1992-1998 Apple Computer, Inc.
11 ** All rights reserved.
13 ** You may incorporate this sample code into your applications without
14 ** restriction, though the sample code has been provided "AS IS" and the
15 ** responsibility for its operation is 100% yours. However, what you are
16 ** not permitted to do is to redistribute the source as "DSC Sample Code"
17 ** after having made changes. If you're going to re-distribute the source,
18 ** we require that you make it clear in the source that the code was
19 ** descended from Apple Sample Code, but that you've made changes.
28 #define __COMPILINGMOREFILES
36 /*****************************************************************************/
42 dirCopyBigCopyBuffSize
= 0x00004000,
43 dirCopyMinCopyBuffSize
= 0x00000200
47 /*****************************************************************************/
49 /* local data structures */
51 /* The EnumerateGlobals structure is used to minimize the amount of
52 ** stack space used when recursively calling CopyLevel and to hold
53 ** global information that might be needed at any time. */
55 #if PRAGMA_STRUCT_ALIGN
56 #pragma options align=mac68k
57 #elif PRAGMA_STRUCT_PACKPUSH
59 #elif PRAGMA_STRUCT_PACK
63 struct EnumerateGlobals
65 Ptr copyBuffer
; /* pointer to buffer used for file copy operations */
66 long bufferSize
; /* the size of the copy buffer */
67 CopyErrProcPtr errorHandler
; /* pointer to error handling function */
68 CopyFilterProcPtr copyFilterProc
; /* pointer to filter function */
69 OSErr error
; /* temporary holder of results - saves 2 bytes of stack each level */
70 Boolean bailout
; /* set to true to by error handling function if fatal error */
71 short destinationVRefNum
; /* the destination vRefNum */
72 Str63 itemName
; /* the name of the current item */
73 CInfoPBRec myCPB
; /* the parameter block used for PBGetCatInfo calls */
75 #if PRAGMA_STRUCT_ALIGN
76 #pragma options align=reset
77 #elif PRAGMA_STRUCT_PACKPUSH
79 #elif PRAGMA_STRUCT_PACK
83 typedef struct EnumerateGlobals EnumerateGlobals
;
84 typedef EnumerateGlobals
*EnumerateGlobalsPtr
;
87 /* The PreflightGlobals structure is used to minimize the amount of
88 ** stack space used when recursively calling GetLevelSize and to hold
89 ** global information that might be needed at any time. */
91 #if PRAGMA_STRUCT_ALIGN
92 #pragma options align=mac68k
93 #elif PRAGMA_STRUCT_PACKPUSH
95 #elif PRAGMA_STRUCT_PACK
99 struct PreflightGlobals
101 OSErr result
; /* temporary holder of results - saves 2 bytes of stack each level */
102 Str63 itemName
; /* the name of the current item */
103 CInfoPBRec myCPB
; /* the parameter block used for PBGetCatInfo calls */
105 unsigned long dstBlksPerAllocBlk
; /* the number of 512 byte blocks per allocation block on destination */
107 unsigned long allocBlksNeeded
; /* the total number of allocation blocks needed */
109 unsigned long tempBlocks
; /* temporary storage for calculations (save some stack space) */
110 CopyFilterProcPtr copyFilterProc
; /* pointer to filter function */
112 #if PRAGMA_STRUCT_ALIGN
113 #pragma options align=reset
114 #elif PRAGMA_STRUCT_PACKPUSH
116 #elif PRAGMA_STRUCT_PACK
120 typedef struct PreflightGlobals PreflightGlobals
;
121 typedef PreflightGlobals
*PreflightGlobalsPtr
;
123 /*****************************************************************************/
125 /* static prototypes */
127 static void GetLevelSize(long currentDirID
,
128 PreflightGlobals
*theGlobals
);
130 static OSErr
PreflightDirectoryCopySpace(short srcVRefNum
,
133 CopyFilterProcPtr copyFilterProc
,
136 static void CopyLevel(long sourceDirID
,
138 EnumerateGlobals
*theGlobals
);
140 /*****************************************************************************/
142 static void GetLevelSize(long currentDirID
,
143 PreflightGlobals
*theGlobals
)
149 theGlobals
->myCPB
.dirInfo
.ioFDirIndex
= index
;
150 theGlobals
->myCPB
.dirInfo
.ioDrDirID
= currentDirID
; /* we need to do this every time */
151 /* through, since GetCatInfo */
152 /* returns ioFlNum in this field */
153 theGlobals
->result
= PBGetCatInfoSync(&theGlobals
->myCPB
);
154 if ( theGlobals
->result
== noErr
)
156 if ( (theGlobals
->copyFilterProc
== NULL
) ||
157 CallCopyFilterProc(theGlobals
->copyFilterProc
, &theGlobals
->myCPB
) ) /* filter if filter proc was supplied */
159 /* Either there's no filter proc OR the filter proc says to use this item */
160 if ( (theGlobals
->myCPB
.dirInfo
.ioFlAttrib
& ioDirMask
) != 0 )
162 /* we have a directory */
164 GetLevelSize(theGlobals
->myCPB
.dirInfo
.ioDrDirID
, theGlobals
); /* recurse */
165 theGlobals
->result
= noErr
; /* clear error return on way back */
169 /* We have a file - add its allocation blocks to allocBlksNeeded. */
170 /* Since space on Mac OS disks is always allocated in allocation blocks, */
171 /* this takes into account rounding up to the end of an allocation block. */
173 /* get number of 512-byte blocks needed for data fork */
174 if ( ((unsigned long)theGlobals
->myCPB
.hFileInfo
.ioFlLgLen
& 0x000001ff) != 0 )
176 theGlobals
->tempBlocks
= ((unsigned long)theGlobals
->myCPB
.hFileInfo
.ioFlLgLen
>> 9) + 1;
180 theGlobals
->tempBlocks
= (unsigned long)theGlobals
->myCPB
.hFileInfo
.ioFlLgLen
>> 9;
182 /* now, calculate number of new allocation blocks needed for the data fork and add it to the total */
183 if ( theGlobals
->tempBlocks
% theGlobals
->dstBlksPerAllocBlk
)
185 theGlobals
->allocBlksNeeded
+= (theGlobals
->tempBlocks
/ theGlobals
->dstBlksPerAllocBlk
) + 1;
189 theGlobals
->allocBlksNeeded
+= theGlobals
->tempBlocks
/ theGlobals
->dstBlksPerAllocBlk
;
192 /* get number of 512-byte blocks needed for resource fork */
193 if ( ((unsigned long)theGlobals
->myCPB
.hFileInfo
.ioFlRLgLen
& 0x000001ff) != 0 )
195 theGlobals
->tempBlocks
= ((unsigned long)theGlobals
->myCPB
.hFileInfo
.ioFlRLgLen
>> 9) + 1;
199 theGlobals
->tempBlocks
= (unsigned long)theGlobals
->myCPB
.hFileInfo
.ioFlRLgLen
>> 9;
201 /* now, calculate number of new allocation blocks needed for the resource fork and add it to the total */
202 if ( theGlobals
->tempBlocks
% theGlobals
->dstBlksPerAllocBlk
)
204 theGlobals
->allocBlksNeeded
+= (theGlobals
->tempBlocks
/ theGlobals
->dstBlksPerAllocBlk
) + 1;
208 theGlobals
->allocBlksNeeded
+= theGlobals
->tempBlocks
/ theGlobals
->dstBlksPerAllocBlk
;
214 } while ( theGlobals
->result
== noErr
);
220 /*****************************************************************************/
222 static OSErr
PreflightDirectoryCopySpace(short srcVRefNum
,
225 CopyFilterProcPtr copyFilterProc
,
230 unsigned long dstFreeBlocks
;
231 PreflightGlobals theGlobals
;
233 error
= XGetVolumeInfoNoName(NULL
, dstVRefNum
, &pb
);
234 if ( error
== noErr
)
236 /* Convert freeBytes to free disk blocks (512-byte blocks) */
237 // dstFreeBlocks = (pb.ioVFreeBytes.hi << 23) + (pb.ioVFreeBytes.lo >> 9);
238 dstFreeBlocks
= pb
.ioVFreeBytes
>> 9 ;
240 /* get allocation block size (always multiple of 512) and divide by 512
241 to get number of 512-byte blocks per allocation block */
242 theGlobals
.dstBlksPerAllocBlk
= ((unsigned long)pb
.ioVAlBlkSiz
>> 9);
244 theGlobals
.allocBlksNeeded
= 0;
246 theGlobals
.myCPB
.dirInfo
.ioNamePtr
= theGlobals
.itemName
;
247 theGlobals
.myCPB
.dirInfo
.ioVRefNum
= srcVRefNum
;
249 theGlobals
.copyFilterProc
= copyFilterProc
;
251 GetLevelSize(srcDirID
, &theGlobals
);
253 /* Is there enough room on the destination volume for the source file? */
254 /* Note: This will work because the largest number of disk blocks supported */
255 /* on a 2TB volume is 0xffffffff and (allocBlksNeeded * dstBlksPerAllocBlk) */
256 /* will always be less than 0xffffffff. */
257 *spaceOK
= ((theGlobals
.allocBlksNeeded
* theGlobals
.dstBlksPerAllocBlk
) <= dstFreeBlocks
);
263 /*****************************************************************************/
265 static void CopyLevel(long sourceDirID
,
267 EnumerateGlobals
*theGlobals
)
269 long currentSrcDirID
;
275 /* Get next source item at the current directory level */
277 theGlobals
->myCPB
.dirInfo
.ioFDirIndex
= index
;
278 theGlobals
->myCPB
.dirInfo
.ioDrDirID
= sourceDirID
;
279 theGlobals
->error
= PBGetCatInfoSync(&theGlobals
->myCPB
);
281 if ( theGlobals
->error
== noErr
)
283 if ( (theGlobals
->copyFilterProc
== NULL
) ||
284 CallCopyFilterProc(theGlobals
->copyFilterProc
, &theGlobals
->myCPB
) ) /* filter if filter proc was supplied */
286 /* Either there's no filter proc OR the filter proc says to use this item */
288 /* We have an item. Is it a file or directory? */
289 if ( (theGlobals
->myCPB
.hFileInfo
.ioFlAttrib
& ioDirMask
) != 0 )
291 /* We have a directory */
293 /* Create a new directory at the destination. No errors allowed! */
294 theGlobals
->error
= DirCreate(theGlobals
->destinationVRefNum
, dstDirID
, theGlobals
->itemName
, &newDirID
);
295 if ( theGlobals
->error
== noErr
)
297 /* Save the current source directory ID where we can get it when we come back
298 ** from recursion land. */
299 currentSrcDirID
= theGlobals
->myCPB
.dirInfo
.ioDrDirID
;
301 /* Dive again (copy the directory level we just found below this one) */
302 CopyLevel(theGlobals
->myCPB
.dirInfo
.ioDrDirID
, newDirID
, theGlobals
);
304 if ( !theGlobals
->bailout
)
306 /* Copy comment from old to new directory. */
307 /* Ignore the result because we really don't care if it worked or not. */
308 (void) DTCopyComment(theGlobals
->myCPB
.dirInfo
.ioVRefNum
, currentSrcDirID
, NULL
, theGlobals
->destinationVRefNum
, newDirID
, NULL
);
310 /* Copy directory attributes (dates, etc.) to newDirID. */
311 /* No errors allowed */
312 theGlobals
->error
= CopyFileMgrAttributes(theGlobals
->myCPB
.dirInfo
.ioVRefNum
, currentSrcDirID
, NULL
, theGlobals
->destinationVRefNum
, newDirID
, NULL
, true);
314 /* handle any errors from CopyFileMgrAttributes */
315 if ( theGlobals
->error
!= noErr
)
317 if ( theGlobals
->errorHandler
!= NULL
)
319 theGlobals
->bailout
= CallCopyErrProc(theGlobals
->errorHandler
, theGlobals
->error
, copyDirFMAttributesOp
,
320 theGlobals
->myCPB
.dirInfo
.ioVRefNum
, currentSrcDirID
, NULL
,
321 theGlobals
->destinationVRefNum
, newDirID
, NULL
);
325 /* If you don't handle the errors with an error handler, */
326 /* then the copy stops here. */
327 theGlobals
->bailout
= true;
332 else /* error handling for DirCreate */
334 if ( theGlobals
->errorHandler
!= NULL
)
336 theGlobals
->bailout
= CallCopyErrProc(theGlobals
->errorHandler
, theGlobals
->error
, dirCreateOp
,
337 theGlobals
->myCPB
.dirInfo
.ioVRefNum
, currentSrcDirID
, NULL
,
338 theGlobals
->destinationVRefNum
, dstDirID
, theGlobals
->itemName
);
342 /* If you don't handle the errors with an error handler, */
343 /* then the copy stops here. */
344 theGlobals
->bailout
= true;
348 if ( !theGlobals
->bailout
)
350 /* clear error return on way back if we aren't bailing out */
351 theGlobals
->error
= noErr
;
356 /* We have a file, so copy it */
358 theGlobals
->error
= FileCopy(theGlobals
->myCPB
.hFileInfo
.ioVRefNum
,
359 theGlobals
->myCPB
.hFileInfo
.ioFlParID
,
360 theGlobals
->itemName
,
361 theGlobals
->destinationVRefNum
,
365 theGlobals
->copyBuffer
,
366 theGlobals
->bufferSize
,
369 /* handle any errors from FileCopy */
370 if ( theGlobals
->error
!= noErr
)
372 if ( theGlobals
->errorHandler
!= NULL
)
374 theGlobals
->bailout
= CallCopyErrProc(theGlobals
->errorHandler
, theGlobals
->error
, fileCopyOp
,
375 theGlobals
->myCPB
.hFileInfo
.ioVRefNum
, theGlobals
->myCPB
.hFileInfo
.ioFlParID
, theGlobals
->itemName
,
376 theGlobals
->destinationVRefNum
, dstDirID
, NULL
);
377 if ( !theGlobals
->bailout
)
379 /* If the CopyErrProc handled the problem, clear the error here */
380 theGlobals
->error
= noErr
;
385 /* If you don't handle the errors with an error handler, */
386 /* then the copy stops here. */
387 theGlobals
->bailout
= true;
394 { /* error handling for PBGetCatInfo */
395 /* it's normal to get a fnfErr when indexing; that only means you've hit the end of the directory */
396 if ( theGlobals
->error
!= fnfErr
)
398 if ( theGlobals
->errorHandler
!= NULL
)
400 theGlobals
->bailout
= CallCopyErrProc(theGlobals
->errorHandler
, theGlobals
->error
, getNextItemOp
,
401 theGlobals
->myCPB
.dirInfo
.ioVRefNum
, sourceDirID
, NULL
, 0, 0, NULL
);
402 if ( !theGlobals
->bailout
)
404 /* If the CopyErrProc handled the problem, clear the error here */
405 theGlobals
->error
= noErr
;
410 /* If you don't handle the errors with an error handler, */
411 /* then the copy stops here. */
412 theGlobals
->bailout
= true;
416 ++index
; /* prepare to get next item */
417 } while ( (theGlobals
->error
== noErr
) && (!theGlobals
->bailout
) ); /* time to fall back a level? */
420 /*****************************************************************************/
422 pascal OSErr
FilteredDirectoryCopy(short srcVRefNum
,
424 ConstStr255Param srcName
,
427 ConstStr255Param dstName
,
431 CopyErrProcPtr copyErrHandler
,
432 CopyFilterProcPtr copyFilterProc
)
434 EnumerateGlobals theGlobals
;
437 Boolean ourCopyBuffer
= false;
438 Str63 srcDirName
, oldDiskName
;
441 /* Make sure a copy buffer is allocated. */
442 if ( copyBufferPtr
== NULL
)
444 /* The caller didn't supply a copy buffer so grab one from the application heap.
445 ** Try to get a big copy buffer, if we can't, try for a 512-byte buffer.
446 ** If 512 bytes aren't available, we're in trouble. */
447 copyBufferSize
= dirCopyBigCopyBuffSize
;
448 copyBufferPtr
= NewPtr(copyBufferSize
);
449 if ( copyBufferPtr
== NULL
)
451 copyBufferSize
= dirCopyMinCopyBuffSize
;
452 copyBufferPtr
= NewPtr(copyBufferSize
);
453 if ( copyBufferPtr
== NULL
)
455 return ( memFullErr
);
458 ourCopyBuffer
= true;
461 /* Get the real dirID where we're copying from and make sure it is a directory. */
462 error
= GetDirectoryID(srcVRefNum
, srcDirID
, srcName
, &srcDirID
, &isDirectory
);
463 if ( error
!= noErr
)
473 /* Special case destination if it is the root parent directory. */
474 /* Since you can't create the root directory, this is needed if */
475 /* you want to copy a directory's content to a disk's root directory. */
476 if ( (dstDirID
== fsRtParID
) && (dstName
== NULL
) )
478 dstDirID
= fsRtParID
;
484 /* Get the real dirID where we're going to put the copy and make sure it is a directory. */
485 error
= GetDirectoryID(dstVRefNum
, dstDirID
, dstName
, &dstDirID
, &isDirectory
);
486 if ( error
!= noErr
)
497 /* Get the real vRefNum of both the source and destination */
498 error
= DetermineVRefNum(srcName
, srcVRefNum
, &srcVRefNum
);
499 if ( error
!= noErr
)
503 error
= DetermineVRefNum(dstName
, dstVRefNum
, &dstVRefNum
);
504 if ( error
!= noErr
)
511 error
= PreflightDirectoryCopySpace(srcVRefNum
, srcDirID
, dstVRefNum
, copyFilterProc
, &spaceOK
);
512 if ( error
!= noErr
)
518 error
= dskFulErr
; /* not enough room on destination */
523 /* Create the new directory in the destination directory with the */
524 /* same name as the source directory. */
525 error
= GetDirName(srcVRefNum
, srcDirID
, srcDirName
);
526 if ( error
!= noErr
)
531 /* Again, special case destination if the destination is the */
532 /* root parent directory. This time, we'll rename the disk to */
533 /* the source directory name. */
534 if ( dstDirID
== fsRtParID
)
536 /* Get the current name of the destination disk */
537 error
= GetDirName(dstVRefNum
, fsRtDirID
, oldDiskName
);
538 if ( error
== noErr
)
540 /* Shorten the name if it's too long to be the volume name */
541 TruncPString(srcDirName
, srcDirName
, 27);
543 /* Rename the disk */
544 error
= HRename(dstVRefNum
, fsRtParID
, oldDiskName
, srcDirName
);
545 /* and copy to the root directory */
546 dstDirID
= fsRtDirID
;
551 error
= DirCreate(dstVRefNum
, dstDirID
, srcDirName
, &dstDirID
);
553 if ( error
!= noErr
)
555 /* handle any errors from DirCreate */
556 if ( copyErrHandler
!= NULL
)
558 if ( CallCopyErrProc(copyErrHandler
, error
, dirCreateOp
,
559 srcVRefNum
, srcDirID
, NULL
,
560 dstVRefNum
, dstDirID
, srcDirName
) )
566 /* If the CopyErrProc handled the problem, clear the error here */
573 /* If you don't handle the errors with an error handler, */
574 /* then the copy stops here. */
579 /* dstDirID is now the newly created directory! */
581 /* Set up the globals we need to access from the recursive routine. */
582 theGlobals
.copyBuffer
= (Ptr
)copyBufferPtr
;
583 theGlobals
.bufferSize
= copyBufferSize
;
584 theGlobals
.destinationVRefNum
= dstVRefNum
; /* so we can get to it always */
585 theGlobals
.myCPB
.hFileInfo
.ioNamePtr
= (StringPtr
)&theGlobals
.itemName
;
586 theGlobals
.myCPB
.hFileInfo
.ioVRefNum
= srcVRefNum
;
587 theGlobals
.errorHandler
= copyErrHandler
;
588 theGlobals
.bailout
= false;
589 theGlobals
.copyFilterProc
= copyFilterProc
;
591 /* Here we go into recursion land... */
592 CopyLevel(srcDirID
, dstDirID
, &theGlobals
);
593 error
= theGlobals
.error
; /* get the result */
595 if ( !theGlobals
.bailout
)
597 /* Copy comment from source to destination directory. */
598 /* Ignore the result because we really don't care if it worked or not. */
599 (void) DTCopyComment(srcVRefNum
, srcDirID
, NULL
, dstVRefNum
, dstDirID
, NULL
);
601 /* Copy the File Manager attributes */
602 error
= CopyFileMgrAttributes(srcVRefNum
, srcDirID
, NULL
,
603 dstVRefNum
, dstDirID
, NULL
, true);
605 /* handle any errors from CopyFileMgrAttributes */
606 if ( (error
!= noErr
) && (copyErrHandler
!= NULL
) )
608 theGlobals
.bailout
= CallCopyErrProc(copyErrHandler
, error
, copyDirFMAttributesOp
,
609 srcVRefNum
, srcDirID
, NULL
,
610 dstVRefNum
, dstDirID
, NULL
);
615 /* Get rid of the copy buffer if we allocated it. */
618 DisposePtr((Ptr
)copyBufferPtr
);
624 /*****************************************************************************/
626 pascal OSErr
DirectoryCopy(short srcVRefNum
,
628 ConstStr255Param srcName
,
631 ConstStr255Param dstName
,
635 CopyErrProcPtr copyErrHandler
)
637 return ( FilteredDirectoryCopy(srcVRefNum
, srcDirID
, srcName
,
638 dstVRefNum
, dstDirID
, dstName
,
639 copyBufferPtr
, copyBufferSize
, preflight
,
640 copyErrHandler
, NULL
) );
643 /*****************************************************************************/
645 pascal OSErr
FSpFilteredDirectoryCopy(const FSSpec
*srcSpec
,
646 const FSSpec
*dstSpec
,
650 CopyErrProcPtr copyErrHandler
,
651 CopyFilterProcPtr copyFilterProc
)
653 return ( FilteredDirectoryCopy(srcSpec
->vRefNum
, srcSpec
->parID
, srcSpec
->name
,
654 dstSpec
->vRefNum
, dstSpec
->parID
, dstSpec
->name
,
655 copyBufferPtr
, copyBufferSize
, preflight
,
656 copyErrHandler
, copyFilterProc
) );
659 /*****************************************************************************/
661 pascal OSErr
FSpDirectoryCopy(const FSSpec
*srcSpec
,
662 const FSSpec
*dstSpec
,
666 CopyErrProcPtr copyErrHandler
)
668 return ( FilteredDirectoryCopy(srcSpec
->vRefNum
, srcSpec
->parID
, srcSpec
->name
,
669 dstSpec
->vRefNum
, dstSpec
->parID
, dstSpec
->name
,
670 copyBufferPtr
, copyBufferSize
, preflight
,
671 copyErrHandler
, NULL
) );
674 /*****************************************************************************/