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_ALIGN_SUPPORTED
56 #pragma options align=mac68k
58 struct EnumerateGlobals
60 Ptr copyBuffer
; /* pointer to buffer used for file copy operations */
61 long bufferSize
; /* the size of the copy buffer */
62 CopyErrProcPtr errorHandler
; /* pointer to error handling function */
63 CopyFilterProcPtr copyFilterProc
; /* pointer to filter function */
64 OSErr error
; /* temporary holder of results - saves 2 bytes of stack each level */
65 Boolean bailout
; /* set to true to by error handling function if fatal error */
66 short destinationVRefNum
; /* the destination vRefNum */
67 Str63 itemName
; /* the name of the current item */
68 CInfoPBRec myCPB
; /* the parameter block used for PBGetCatInfo calls */
70 #if PRAGMA_ALIGN_SUPPORTED
71 #pragma options align=reset
74 typedef struct EnumerateGlobals EnumerateGlobals
;
75 typedef EnumerateGlobals
*EnumerateGlobalsPtr
;
78 /* The PreflightGlobals structure is used to minimize the amount of
79 ** stack space used when recursively calling GetLevelSize and to hold
80 ** global information that might be needed at any time. */
82 #if PRAGMA_ALIGN_SUPPORTED
83 #pragma options align=mac68k
85 struct PreflightGlobals
87 OSErr result
; /* temporary holder of results - saves 2 bytes of stack each level */
88 Str63 itemName
; /* the name of the current item */
89 CInfoPBRec myCPB
; /* the parameter block used for PBGetCatInfo calls */
91 unsigned long dstBlksPerAllocBlk
; /* the number of 512 byte blocks per allocation block on destination */
93 unsigned long allocBlksNeeded
; /* the total number of allocation blocks needed */
95 unsigned long tempBlocks
; /* temporary storage for calculations (save some stack space) */
96 CopyFilterProcPtr copyFilterProc
; /* pointer to filter function */
98 #if PRAGMA_ALIGN_SUPPORTED
99 #pragma options align=reset
102 typedef struct PreflightGlobals PreflightGlobals
;
103 typedef PreflightGlobals
*PreflightGlobalsPtr
;
105 /*****************************************************************************/
107 /* static prototypes */
109 static void GetLevelSize(long currentDirID
,
110 PreflightGlobals
*theGlobals
);
112 static OSErr
PreflightDirectoryCopySpace(short srcVRefNum
,
115 CopyFilterProcPtr copyFilterProc
,
118 static void CopyLevel(long sourceDirID
,
120 EnumerateGlobals
*theGlobals
);
122 /*****************************************************************************/
124 static void GetLevelSize(long currentDirID
,
125 PreflightGlobals
*theGlobals
)
131 theGlobals
->myCPB
.dirInfo
.ioFDirIndex
= index
;
132 theGlobals
->myCPB
.dirInfo
.ioDrDirID
= currentDirID
; /* we need to do this every time */
133 /* through, since GetCatInfo */
134 /* returns ioFlNum in this field */
135 theGlobals
->result
= PBGetCatInfoSync(&theGlobals
->myCPB
);
136 if ( theGlobals
->result
== noErr
)
138 if ( (theGlobals
->copyFilterProc
== NULL
) ||
139 CallCopyFilterProc(theGlobals
->copyFilterProc
, &theGlobals
->myCPB
) ) /* filter if filter proc was supplied */
141 /* Either there's no filter proc OR the filter proc says to use this item */
142 if ( (theGlobals
->myCPB
.dirInfo
.ioFlAttrib
& ioDirMask
) != 0 )
144 /* we have a directory */
146 GetLevelSize(theGlobals
->myCPB
.dirInfo
.ioDrDirID
, theGlobals
); /* recurse */
147 theGlobals
->result
= noErr
; /* clear error return on way back */
151 /* We have a file - add its allocation blocks to allocBlksNeeded. */
152 /* Since space on Mac OS disks is always allocated in allocation blocks, */
153 /* this takes into account rounding up to the end of an allocation block. */
155 /* get number of 512-byte blocks needed for data fork */
156 if ( ((unsigned long)theGlobals
->myCPB
.hFileInfo
.ioFlLgLen
& 0x000001ff) != 0 )
158 theGlobals
->tempBlocks
= ((unsigned long)theGlobals
->myCPB
.hFileInfo
.ioFlLgLen
>> 9) + 1;
162 theGlobals
->tempBlocks
= (unsigned long)theGlobals
->myCPB
.hFileInfo
.ioFlLgLen
>> 9;
164 /* now, calculate number of new allocation blocks needed for the data fork and add it to the total */
165 if ( theGlobals
->tempBlocks
% theGlobals
->dstBlksPerAllocBlk
)
167 theGlobals
->allocBlksNeeded
+= (theGlobals
->tempBlocks
/ theGlobals
->dstBlksPerAllocBlk
) + 1;
171 theGlobals
->allocBlksNeeded
+= theGlobals
->tempBlocks
/ theGlobals
->dstBlksPerAllocBlk
;
174 /* get number of 512-byte blocks needed for resource fork */
175 if ( ((unsigned long)theGlobals
->myCPB
.hFileInfo
.ioFlRLgLen
& 0x000001ff) != 0 )
177 theGlobals
->tempBlocks
= ((unsigned long)theGlobals
->myCPB
.hFileInfo
.ioFlRLgLen
>> 9) + 1;
181 theGlobals
->tempBlocks
= (unsigned long)theGlobals
->myCPB
.hFileInfo
.ioFlRLgLen
>> 9;
183 /* now, calculate number of new allocation blocks needed for the resource fork and add it to the total */
184 if ( theGlobals
->tempBlocks
% theGlobals
->dstBlksPerAllocBlk
)
186 theGlobals
->allocBlksNeeded
+= (theGlobals
->tempBlocks
/ theGlobals
->dstBlksPerAllocBlk
) + 1;
190 theGlobals
->allocBlksNeeded
+= theGlobals
->tempBlocks
/ theGlobals
->dstBlksPerAllocBlk
;
196 } while ( theGlobals
->result
== noErr
);
199 /*****************************************************************************/
201 static OSErr
PreflightDirectoryCopySpace(short srcVRefNum
,
204 CopyFilterProcPtr copyFilterProc
,
209 unsigned long dstFreeBlocks
;
210 PreflightGlobals theGlobals
;
212 error
= XGetVolumeInfoNoName(NULL
, dstVRefNum
, &pb
);
213 if ( error
== noErr
)
215 /* Convert freeBytes to free disk blocks (512-byte blocks) */
216 dstFreeBlocks
= (pb
.ioVFreeBytes
.hi
<< 23) + (pb
.ioVFreeBytes
.lo
>> 9);
218 /* get allocation block size (always multiple of 512) and divide by 512
219 to get number of 512-byte blocks per allocation block */
220 theGlobals
.dstBlksPerAllocBlk
= ((unsigned long)pb
.ioVAlBlkSiz
>> 9);
222 theGlobals
.allocBlksNeeded
= 0;
224 theGlobals
.myCPB
.dirInfo
.ioNamePtr
= theGlobals
.itemName
;
225 theGlobals
.myCPB
.dirInfo
.ioVRefNum
= srcVRefNum
;
227 theGlobals
.copyFilterProc
= copyFilterProc
;
229 GetLevelSize(srcDirID
, &theGlobals
);
231 /* Is there enough room on the destination volume for the source file? */
232 /* Note: This will work because the largest number of disk blocks supported */
233 /* on a 2TB volume is 0xffffffff and (allocBlksNeeded * dstBlksPerAllocBlk) */
234 /* will always be less than 0xffffffff. */
235 *spaceOK
= ((theGlobals
.allocBlksNeeded
* theGlobals
.dstBlksPerAllocBlk
) <= dstFreeBlocks
);
241 /*****************************************************************************/
243 static void CopyLevel(long sourceDirID
,
245 EnumerateGlobals
*theGlobals
)
247 long currentSrcDirID
;
253 /* Get next source item at the current directory level */
255 theGlobals
->myCPB
.dirInfo
.ioFDirIndex
= index
;
256 theGlobals
->myCPB
.dirInfo
.ioDrDirID
= sourceDirID
;
257 theGlobals
->error
= PBGetCatInfoSync(&theGlobals
->myCPB
);
259 if ( theGlobals
->error
== noErr
)
261 if ( (theGlobals
->copyFilterProc
== NULL
) ||
262 CallCopyFilterProc(theGlobals
->copyFilterProc
, &theGlobals
->myCPB
) ) /* filter if filter proc was supplied */
264 /* Either there's no filter proc OR the filter proc says to use this item */
266 /* We have an item. Is it a file or directory? */
267 if ( (theGlobals
->myCPB
.hFileInfo
.ioFlAttrib
& ioDirMask
) != 0 )
269 /* We have a directory */
271 /* Create a new directory at the destination. No errors allowed! */
272 theGlobals
->error
= DirCreate(theGlobals
->destinationVRefNum
, dstDirID
, theGlobals
->itemName
, &newDirID
);
273 if ( theGlobals
->error
== noErr
)
275 /* Save the current source directory ID where we can get it when we come back
276 ** from recursion land. */
277 currentSrcDirID
= theGlobals
->myCPB
.dirInfo
.ioDrDirID
;
279 /* Dive again (copy the directory level we just found below this one) */
280 CopyLevel(theGlobals
->myCPB
.dirInfo
.ioDrDirID
, newDirID
, theGlobals
);
282 if ( !theGlobals
->bailout
)
284 /* Copy comment from old to new directory. */
285 /* Ignore the result because we really don't care if it worked or not. */
286 (void) DTCopyComment(theGlobals
->myCPB
.dirInfo
.ioVRefNum
, currentSrcDirID
, NULL
, theGlobals
->destinationVRefNum
, newDirID
, NULL
);
288 /* Copy directory attributes (dates, etc.) to newDirID. */
289 /* No errors allowed */
290 theGlobals
->error
= CopyFileMgrAttributes(theGlobals
->myCPB
.dirInfo
.ioVRefNum
, currentSrcDirID
, NULL
, theGlobals
->destinationVRefNum
, newDirID
, NULL
, true);
292 /* handle any errors from CopyFileMgrAttributes */
293 if ( theGlobals
->error
!= noErr
)
295 if ( theGlobals
->errorHandler
!= NULL
)
297 theGlobals
->bailout
= CallCopyErrProc(theGlobals
->errorHandler
, theGlobals
->error
, copyDirFMAttributesOp
,
298 theGlobals
->myCPB
.dirInfo
.ioVRefNum
, currentSrcDirID
, NULL
,
299 theGlobals
->destinationVRefNum
, newDirID
, NULL
);
303 /* If you don't handle the errors with an error handler, */
304 /* then the copy stops here. */
305 theGlobals
->bailout
= true;
310 else /* error handling for DirCreate */
312 if ( theGlobals
->errorHandler
!= NULL
)
314 theGlobals
->bailout
= CallCopyErrProc(theGlobals
->errorHandler
, theGlobals
->error
, dirCreateOp
,
315 theGlobals
->myCPB
.dirInfo
.ioVRefNum
, currentSrcDirID
, NULL
,
316 theGlobals
->destinationVRefNum
, dstDirID
, theGlobals
->itemName
);
320 /* If you don't handle the errors with an error handler, */
321 /* then the copy stops here. */
322 theGlobals
->bailout
= true;
326 if ( !theGlobals
->bailout
)
328 /* clear error return on way back if we aren't bailing out */
329 theGlobals
->error
= noErr
;
334 /* We have a file, so copy it */
336 theGlobals
->error
= FileCopy(theGlobals
->myCPB
.hFileInfo
.ioVRefNum
,
337 theGlobals
->myCPB
.hFileInfo
.ioFlParID
,
338 theGlobals
->itemName
,
339 theGlobals
->destinationVRefNum
,
343 theGlobals
->copyBuffer
,
344 theGlobals
->bufferSize
,
347 /* handle any errors from FileCopy */
348 if ( theGlobals
->error
!= noErr
)
350 if ( theGlobals
->errorHandler
!= NULL
)
352 theGlobals
->bailout
= CallCopyErrProc(theGlobals
->errorHandler
, theGlobals
->error
, fileCopyOp
,
353 theGlobals
->myCPB
.hFileInfo
.ioVRefNum
, theGlobals
->myCPB
.hFileInfo
.ioFlParID
, theGlobals
->itemName
,
354 theGlobals
->destinationVRefNum
, dstDirID
, NULL
);
355 if ( !theGlobals
->bailout
)
357 /* If the CopyErrProc handled the problem, clear the error here */
358 theGlobals
->error
= noErr
;
363 /* If you don't handle the errors with an error handler, */
364 /* then the copy stops here. */
365 theGlobals
->bailout
= true;
372 { /* error handling for PBGetCatInfo */
373 /* it's normal to get a fnfErr when indexing; that only means you've hit the end of the directory */
374 if ( theGlobals
->error
!= fnfErr
)
376 if ( theGlobals
->errorHandler
!= NULL
)
378 theGlobals
->bailout
= CallCopyErrProc(theGlobals
->errorHandler
, theGlobals
->error
, getNextItemOp
,
379 theGlobals
->myCPB
.dirInfo
.ioVRefNum
, sourceDirID
, NULL
, 0, 0, NULL
);
380 if ( !theGlobals
->bailout
)
382 /* If the CopyErrProc handled the problem, clear the error here */
383 theGlobals
->error
= noErr
;
388 /* If you don't handle the errors with an error handler, */
389 /* then the copy stops here. */
390 theGlobals
->bailout
= true;
394 ++index
; /* prepare to get next item */
395 } while ( (theGlobals
->error
== noErr
) && (!theGlobals
->bailout
) ); /* time to fall back a level? */
398 /*****************************************************************************/
400 pascal OSErr
FilteredDirectoryCopy(short srcVRefNum
,
402 ConstStr255Param srcName
,
405 ConstStr255Param dstName
,
409 CopyErrProcPtr copyErrHandler
,
410 CopyFilterProcPtr copyFilterProc
)
412 EnumerateGlobals theGlobals
;
415 Boolean ourCopyBuffer
= false;
416 Str63 srcDirName
, oldDiskName
;
419 /* Make sure a copy buffer is allocated. */
420 if ( copyBufferPtr
== NULL
)
422 /* The caller didn't supply a copy buffer so grab one from the application heap.
423 ** Try to get a big copy buffer, if we can't, try for a 512-byte buffer.
424 ** If 512 bytes aren't available, we're in trouble. */
425 copyBufferSize
= dirCopyBigCopyBuffSize
;
426 copyBufferPtr
= NewPtr(copyBufferSize
);
427 if ( copyBufferPtr
== NULL
)
429 copyBufferSize
= dirCopyMinCopyBuffSize
;
430 copyBufferPtr
= NewPtr(copyBufferSize
);
431 if ( copyBufferPtr
== NULL
)
433 return ( memFullErr
);
436 ourCopyBuffer
= true;
439 /* Get the real dirID where we're copying from and make sure it is a directory. */
440 error
= GetDirectoryID(srcVRefNum
, srcDirID
, srcName
, &srcDirID
, &isDirectory
);
441 if ( error
!= noErr
)
451 /* Special case destination if it is the root parent directory. */
452 /* Since you can't create the root directory, this is needed if */
453 /* you want to copy a directory's content to a disk's root directory. */
454 if ( (dstDirID
== fsRtParID
) && (dstName
== NULL
) )
456 dstDirID
= fsRtParID
;
462 /* Get the real dirID where we're going to put the copy and make sure it is a directory. */
463 error
= GetDirectoryID(dstVRefNum
, dstDirID
, dstName
, &dstDirID
, &isDirectory
);
464 if ( error
!= noErr
)
475 /* Get the real vRefNum of both the source and destination */
476 error
= DetermineVRefNum(srcName
, srcVRefNum
, &srcVRefNum
);
477 if ( error
!= noErr
)
481 error
= DetermineVRefNum(dstName
, dstVRefNum
, &dstVRefNum
);
482 if ( error
!= noErr
)
489 error
= PreflightDirectoryCopySpace(srcVRefNum
, srcDirID
, dstVRefNum
, copyFilterProc
, &spaceOK
);
490 if ( error
!= noErr
)
496 error
= dskFulErr
; /* not enough room on destination */
501 /* Create the new directory in the destination directory with the */
502 /* same name as the source directory. */
503 error
= GetDirName(srcVRefNum
, srcDirID
, srcDirName
);
504 if ( error
!= noErr
)
509 /* Again, special case destination if the destination is the */
510 /* root parent directory. This time, we'll rename the disk to */
511 /* the source directory name. */
512 if ( dstDirID
== fsRtParID
)
514 /* Get the current name of the destination disk */
515 error
= GetDirName(dstVRefNum
, fsRtDirID
, oldDiskName
);
516 if ( error
== noErr
)
518 /* Shorten the name if it's too long to be the volume name */
519 TruncPString(srcDirName
, srcDirName
, 27);
521 /* Rename the disk */
522 error
= HRename(dstVRefNum
, fsRtParID
, oldDiskName
, srcDirName
);
523 /* and copy to the root directory */
524 dstDirID
= fsRtDirID
;
529 error
= DirCreate(dstVRefNum
, dstDirID
, srcDirName
, &dstDirID
);
531 if ( error
!= noErr
)
533 /* handle any errors from DirCreate */
534 if ( copyErrHandler
!= NULL
)
536 if ( CallCopyErrProc(copyErrHandler
, error
, dirCreateOp
,
537 srcVRefNum
, srcDirID
, NULL
,
538 dstVRefNum
, dstDirID
, srcDirName
) )
544 /* If the CopyErrProc handled the problem, clear the error here */
551 /* If you don't handle the errors with an error handler, */
552 /* then the copy stops here. */
557 /* dstDirID is now the newly created directory! */
559 /* Set up the globals we need to access from the recursive routine. */
560 theGlobals
.copyBuffer
= (Ptr
)copyBufferPtr
;
561 theGlobals
.bufferSize
= copyBufferSize
;
562 theGlobals
.destinationVRefNum
= dstVRefNum
; /* so we can get to it always */
563 theGlobals
.myCPB
.hFileInfo
.ioNamePtr
= (StringPtr
)&theGlobals
.itemName
;
564 theGlobals
.myCPB
.hFileInfo
.ioVRefNum
= srcVRefNum
;
565 theGlobals
.errorHandler
= copyErrHandler
;
566 theGlobals
.bailout
= false;
567 theGlobals
.copyFilterProc
= copyFilterProc
;
569 /* Here we go into recursion land... */
570 CopyLevel(srcDirID
, dstDirID
, &theGlobals
);
571 error
= theGlobals
.error
; /* get the result */
573 if ( !theGlobals
.bailout
)
575 /* Copy comment from source to destination directory. */
576 /* Ignore the result because we really don't care if it worked or not. */
577 (void) DTCopyComment(srcVRefNum
, srcDirID
, NULL
, dstVRefNum
, dstDirID
, NULL
);
579 /* Copy the File Manager attributes */
580 error
= CopyFileMgrAttributes(srcVRefNum
, srcDirID
, NULL
,
581 dstVRefNum
, dstDirID
, NULL
, true);
583 /* handle any errors from CopyFileMgrAttributes */
584 if ( (error
!= noErr
) && (copyErrHandler
!= NULL
) )
586 theGlobals
.bailout
= CallCopyErrProc(copyErrHandler
, error
, copyDirFMAttributesOp
,
587 srcVRefNum
, srcDirID
, NULL
,
588 dstVRefNum
, dstDirID
, NULL
);
593 /* Get rid of the copy buffer if we allocated it. */
596 DisposePtr((Ptr
)copyBufferPtr
);
602 /*****************************************************************************/
604 pascal OSErr
DirectoryCopy(short srcVRefNum
,
606 ConstStr255Param srcName
,
609 ConstStr255Param dstName
,
613 CopyErrProcPtr copyErrHandler
)
615 return ( FilteredDirectoryCopy(srcVRefNum
, srcDirID
, srcName
,
616 dstVRefNum
, dstDirID
, dstName
,
617 copyBufferPtr
, copyBufferSize
, preflight
,
618 copyErrHandler
, NULL
) );
621 /*****************************************************************************/
623 pascal OSErr
FSpFilteredDirectoryCopy(const FSSpec
*srcSpec
,
624 const FSSpec
*dstSpec
,
628 CopyErrProcPtr copyErrHandler
,
629 CopyFilterProcPtr copyFilterProc
)
631 return ( FilteredDirectoryCopy(srcSpec
->vRefNum
, srcSpec
->parID
, srcSpec
->name
,
632 dstSpec
->vRefNum
, dstSpec
->parID
, dstSpec
->name
,
633 copyBufferPtr
, copyBufferSize
, preflight
,
634 copyErrHandler
, copyFilterProc
) );
637 /*****************************************************************************/
639 pascal OSErr
FSpDirectoryCopy(const FSSpec
*srcSpec
,
640 const FSSpec
*dstSpec
,
644 CopyErrProcPtr copyErrHandler
)
646 return ( FilteredDirectoryCopy(srcSpec
->vRefNum
, srcSpec
->parID
, srcSpec
->name
,
647 dstSpec
->vRefNum
, dstSpec
->parID
, dstSpec
->name
,
648 copyBufferPtr
, copyBufferSize
, preflight
,
649 copyErrHandler
, NULL
) );
652 /*****************************************************************************/