2 **      Apple Macintosh Developer Technical Support 
   4 **      FileCopy: A robust, general purpose file copy routine. 
   6 **      by Jim Luther, Apple Developer Technical Support Emeritus 
  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 
  35 /*****************************************************************************/ 
  39 /*      The deny-mode privileges to use when opening the source and destination files. */ 
  43         srcCopyMode 
= dmRdDenyWr
, 
  44         dstCopyMode 
= dmWrDenyRdWr
 
  47 /*      The largest (16K) and smallest (.5K) copy buffer to use if the caller doesn't supply  
  48 **      their own copy buffer. */ 
  52         bigCopyBuffSize  
= 0x00004000, 
  53         minCopyBuffSize  
= 0x00000200 
  56 /*****************************************************************************/ 
  58 /* static prototypes */ 
  60 static  OSErr   
GetDestinationDirInfo(short vRefNum
, 
  62                                                                           ConstStr255Param name
, 
  66 /*      GetDestinationDirInfo tells us if the destination is a directory, it's 
  67         directory ID, and if it's an AppleShare drop box (write privileges only -- 
  68         no read or search privileges). 
  69         vRefNum         input:  Volume specification. 
  70         dirID           input:  Directory ID. 
  71         name            input:  Pointer to object name, or nil when dirID 
  72                                                 specifies a directory that's the object. 
  73         theDirID        output: If the object is a file, then its parent directory 
  74                                                 ID. If the object is a directory, then its ID. 
  75         isDirectory     output: True if object is a directory; false if 
  77         isDropBox       output: True if directory is an AppleShare drop box. 
  80 static  OSErr   
CheckForForks(short vRefNum
, 
  82                                                           ConstStr255Param name
, 
  84                                                           Boolean 
*hasResourceFork
); 
  85 /*      CheckForForks tells us if there is a data or resource fork to copy. 
  86         vRefNum         input:  Volume specification of the file's current 
  88         dirID           input:  Directory ID of the file's current location. 
  89         name            input:  The name of the file. 
  92 static  OSErr   
PreflightFileCopySpace(short srcVRefNum
, 
  94                                                                            ConstStr255Param srcName
, 
  95                                                                            ConstStr255Param dstVolName
, 
  98 /*      PreflightFileCopySpace determines if there's enough space on a 
  99         volume to copy the specified file to that volume. 
 100         Note: The results of this routine are not perfect. For example if the 
 101         volume's catalog or extents overflow file grows when the new file is 
 102         created, more allocation blocks may be needed beyond those needed for 
 103         the file's data and resource forks. 
 105         srcVRefNum              input:  Volume specification of the file's current 
 107         srcDirID                input:  Directory ID of the file's current location. 
 108         srcName                 input:  The name of the file. 
 109         dstVolName              input:  A pointer to the name of the volume where 
 110                                                         the file will be copied or NULL. 
 111         dstVRefNum              input:  Volume specification indicating the volume 
 112                                                         where the file will be copied. 
 113         spaceOK                 output: true if there's enough space on the volume for 
 114                                                         the file's data and resource forks. 
 117 /*****************************************************************************/ 
 119 static  OSErr   
GetDestinationDirInfo(short vRefNum
, 
 121                                                                           ConstStr255Param name
, 
 123                                                                           Boolean 
*isDirectory
, 
 129         pb
.dirInfo
.ioACUser 
= 0;        /* ioACUser used to be filler2, clear it before calling GetCatInfo */ 
 130         error 
= GetCatInfoNoName(vRefNum
, dirID
, name
, &pb
); 
 131         *theDirID 
= pb
.dirInfo
.ioDrDirID
; 
 132         *isDirectory 
= (pb
.dirInfo
.ioFlAttrib 
& ioDirMask
) != 0; 
 133         /* see if access priviledges are make changes, not see folder, and not see files (drop box) */ 
 134         *isDropBox 
= ((pb
.dirInfo
.ioACUser 
& 0x07) == 0x03); 
 139 /*****************************************************************************/ 
 141 static  OSErr   
CheckForForks(short vRefNum
, 
 143                                                           ConstStr255Param name
, 
 144                                                           Boolean 
*hasDataFork
, 
 145                                                           Boolean 
*hasResourceFork
) 
 150         pb
.fileParam
.ioNamePtr 
= (StringPtr
)name
; 
 151         pb
.fileParam
.ioVRefNum 
= vRefNum
; 
 152         pb
.fileParam
.ioFVersNum 
= 0; 
 153         pb
.fileParam
.ioDirID 
= dirID
; 
 154         pb
.fileParam
.ioFDirIndex 
= 0; 
 155         error 
= PBHGetFInfoSync(&pb
); 
 156         *hasDataFork 
= (pb
.fileParam
.ioFlLgLen 
!= 0); 
 157         *hasResourceFork 
= (pb
.fileParam
.ioFlRLgLen 
!= 0); 
 162 /*****************************************************************************/ 
 166 static  OSErr   
PreflightFileCopySpace(short srcVRefNum
, 
 168                                                                            ConstStr255Param srcName
, 
 169                                                                            ConstStr255Param dstVolName
, 
 175         unsigned long dstFreeBlocks
; 
 176         unsigned long dstBlksPerAllocBlk
; 
 177         unsigned long srcDataBlks
; 
 178         unsigned long srcResourceBlks
; 
 180         error 
= XGetVolumeInfoNoName(dstVolName
, dstVRefNum
, &pb
.xPB
); 
 181         if ( error 
== noErr 
) 
 183                 /* get allocation block size (always multiple of 512) and divide by 512 
 184                   to get number of 512-byte blocks per allocation block */ 
 185                 dstBlksPerAllocBlk 
= ((unsigned long)pb
.xPB
.ioVAlBlkSiz 
>> 9); 
 187                 /* Convert freeBytes to free disk blocks (512-byte blocks) */ 
 188                 // dstFreeBlocks = (pb.xPB.ioVFreeBytes.hi << 23) + (pb.xPB.ioVFreeBytes.lo >> 9); 
 189                 dstFreeBlocks 
= pb
.xPB
.ioVFreeBytes 
>> 9 ; 
 191                 /* Now, get the size of the file's data resource forks */ 
 192                 pb
.hPB
.fileParam
.ioNamePtr 
= (StringPtr
)srcName
; 
 193                 pb
.hPB
.fileParam
.ioVRefNum 
= srcVRefNum
; 
 194                 pb
.hPB
.fileParam
.ioFVersNum 
= 0; 
 195                 pb
.hPB
.fileParam
.ioDirID 
= srcDirID
; 
 196                 pb
.hPB
.fileParam
.ioFDirIndex 
= 0; 
 197                 error 
= PBHGetFInfoSync(&pb
.hPB
); 
 198                 if ( error 
== noErr 
) 
 200                         /* Since space on Mac OS disks is always allocated in allocation blocks, */ 
 201                         /* this code takes into account rounding up to the end of an allocation block. */ 
 203                         /* get number of 512-byte blocks needed for data fork */ 
 204                         if ( ((unsigned long)pb
.hPB
.fileParam
.ioFlLgLen 
& 0x000001ff) != 0 ) 
 206                                 srcDataBlks 
= ((unsigned long)pb
.hPB
.fileParam
.ioFlLgLen 
>> 9) + 1; 
 210                                 srcDataBlks 
= (unsigned long)pb
.hPB
.fileParam
.ioFlLgLen 
>> 9; 
 213                         /* now, calculate number of new allocation blocks needed */ 
 214                         if ( srcDataBlks 
% dstBlksPerAllocBlk 
) 
 216                                 srcDataBlks 
= (srcDataBlks 
/ dstBlksPerAllocBlk
) + 1; 
 220                                 srcDataBlks 
/= dstBlksPerAllocBlk
; 
 223                         /* get number of 512-byte blocks needed for resource fork */ 
 224                         if ( ((unsigned long)pb
.hPB
.fileParam
.ioFlRLgLen 
& 0x000001ff) != 0 ) 
 226                                 srcResourceBlks 
= ((unsigned long)pb
.hPB
.fileParam
.ioFlRLgLen 
>> 9) + 1; 
 230                                 srcResourceBlks 
= (unsigned long)pb
.hPB
.fileParam
.ioFlRLgLen 
>> 9; 
 233                         /* now, calculate number of new allocation blocks needed */ 
 234                         if ( srcResourceBlks 
% dstBlksPerAllocBlk 
) 
 236                                 srcResourceBlks 
= (srcResourceBlks 
/ dstBlksPerAllocBlk
) + 1; 
 240                                 srcResourceBlks 
/= dstBlksPerAllocBlk
; 
 243                         /* Is there enough room on the destination volume for the source file? */ 
 244                         *spaceOK 
= ( ((srcDataBlks 
+ srcResourceBlks
) * dstBlksPerAllocBlk
) <= dstFreeBlocks 
); 
 251 /*****************************************************************************/ 
 253 pascal  OSErr   
FileCopy(short srcVRefNum
, 
 255                                                  ConstStr255Param srcName
, 
 258                                                  ConstStr255Param dstPathname
, 
 259                                                  ConstStr255Param copyName
, 
 266         short   srcRefNum 
= 0,                  /* 0 when source data and resource fork are closed  */ 
 267                         dstDataRefNum 
= 0,              /* 0 when destination data fork is closed */ 
 268                         dstRsrcRefNum 
= 0;              /* 0 when destination resource fork is closed */ 
 270         Str63   dstName
;                                /* The filename of the destination. It might be the 
 271                                                                         ** source filename, it might be a new name... */ 
 273         GetVolParmsInfoBuffer infoBuffer
; /* Where PBGetVolParms dumps its info */ 
 274         long    srcServerAdr
;                   /* AppleTalk server address of source (if any) */ 
 276         Boolean dstCreated 
= false,             /* true when destination file has been created */ 
 277                         ourCopyBuffer 
= false,  /* true if we had to allocate the copy buffer */ 
 278                         isDirectory
,                    /* true if destination is really a directory */ 
 279                         isDropBox
;                              /* true if destination is an AppleShare drop box */ 
 284         Boolean spaceOK
;                                /* true if there's enough room to copy the file to the destination volume */ 
 287         Boolean hasResourceFork
; 
 289         /* Preflight for size */ 
 292                 err 
= PreflightFileCopySpace(srcVRefNum
, srcDirID
, srcName
, 
 293                                                                          dstPathname
, dstVRefNum
, &spaceOK
); 
 301                         return ( dskFulErr 
); 
 305         /* get the destination's real dirID and make sure it really is a directory */ 
 306         err 
= GetDestinationDirInfo(dstVRefNum
, dstDirID
, dstPathname
, 
 307                                                                 &dstDirID
, &isDirectory
, &isDropBox
); 
 318         /* get the destination's real vRefNum */ 
 319         err 
= DetermineVRefNum(dstPathname
, dstVRefNum
, &dstVRefNum
); 
 325         /* See if PBHCopyFile can be used.  Using PBHCopyFile saves time by letting the file server 
 326         ** copy the file if the source and destination locations are on the same file server. */ 
 327         tempLong 
= sizeof(infoBuffer
); 
 328         err 
= HGetVolParms(srcName
, srcVRefNum
, &infoBuffer
, &tempLong
); 
 329         if ( (err 
!= noErr
) && (err 
!= paramErr
) ) 
 334         if ( (err 
!= paramErr
) && hasCopyFile(infoBuffer
) ) 
 336                 /* The source volume supports PBHCopyFile. */ 
 337                 srcServerAdr 
= infoBuffer
.vMServerAdr
; 
 339                 /* Now, see if the destination volume is on the same file server. */ 
 340                 tempLong 
= sizeof(infoBuffer
); 
 341                 err 
= HGetVolParms(NULL
, dstVRefNum
, &infoBuffer
, &tempLong
); 
 342                 if ( (err 
!= noErr
) && (err 
!= paramErr
) ) 
 346                 if ( (err 
!= paramErr
) && (srcServerAdr 
== infoBuffer
.vMServerAdr
) ) 
 348                         /* Source and Dest are on same server and PBHCopyFile is supported. Copy with CopyFile. */ 
 349                         err 
= HCopyFile(srcVRefNum
, srcDirID
, srcName
, dstVRefNum
, dstDirID
, NULL
, copyName
); 
 355                         /* AppleShare's CopyFile clears the isAlias bit, so I still need to attempt to copy 
 356                            the File's attributes to attempt to get things right. */ 
 357                         if ( copyName 
!= NULL 
)                         /* Did caller supply copy file name? */ 
 359                                 /* Yes, use the caller supplied copy file name. */ 
 360                                 (void) CopyFileMgrAttributes(srcVRefNum
, srcDirID
, srcName
, 
 361                                                                                          dstVRefNum
, dstDirID
, copyName
, true); 
 365                                 /* They didn't, so get the source file name and use it. */ 
 366                                 if ( GetFilenameFromPathname(srcName
, dstName
) == noErr 
) 
 369                                         (void) CopyFileMgrAttributes(srcVRefNum
, srcDirID
, srcName
, 
 370                                                                                                  dstVRefNum
, dstDirID
, dstName
, true); 
 377         /* If we're here, then PBHCopyFile couldn't be used so we have to copy the file by hand. */ 
 379         /* Make sure a copy buffer is allocated. */ 
 380         if ( copyBufferPtr 
== NULL 
) 
 382                 /* The caller didn't supply a copy buffer so grab one from the application heap. 
 383                 ** Try to get a big copy buffer, if we can't, try for a 512-byte buffer. 
 384                 ** If 512 bytes aren't available, we're in trouble. */ 
 385                 copyBufferSize 
= bigCopyBuffSize
; 
 386                 copyBufferPtr 
= NewPtr(copyBufferSize
); 
 387                 if ( copyBufferPtr 
== NULL 
) 
 389                         copyBufferSize 
= minCopyBuffSize
; 
 390                         copyBufferPtr 
= NewPtr(copyBufferSize
); 
 391                         if ( copyBufferPtr 
== NULL 
) 
 393                                 return ( memFullErr 
); 
 396                 ourCopyBuffer 
= true; 
 399         /* Open the source data fork. */ 
 400         err 
= HOpenAware(srcVRefNum
, srcDirID
, srcName
, srcCopyMode
, &srcRefNum
); 
 404         /* Once a file is opened, we have to exit via ErrorExit to make sure things are cleaned up */ 
 406         /* See if the copy will be renamed. */ 
 407         if ( copyName 
!= NULL 
)                         /* Did caller supply copy file name? */ 
 408                 BlockMoveData(copyName
, dstName
, copyName
[0] + 1);      /* Yes, use the caller supplied copy file name. */ 
 410         {       /* They didn't, so get the source file name and use it. */ 
 411                 err 
= GetFileLocation(srcRefNum
, &tempInt
, &tempLong
, dstName
); 
 418         /* Create the destination file. */ 
 419         err 
= HCreateMinimum(dstVRefNum
, dstDirID
, dstName
); 
 424         dstCreated 
= true;      /* After creating the destination file, any 
 425                                                 ** error conditions should delete the destination file */ 
 427         /* An AppleShare dropbox folder is a folder for which the user has the Make Changes 
 428         ** privilege (write access), but not See Files (read access) and See Folders (search access). 
 429         ** Copying a file into an AppleShare dropbox presents some special problems. Here are the 
 430         ** rules we have to follow to copy a file into a dropbox: 
 431         ** ¥ File attributes can be changed only when both forks of a file are empty. 
 432         ** ¥ DeskTop Manager comments can be added to a file only when both forks of a file  
 434         ** ¥ A fork can be opened for write access only when both forks of a file are empty. 
 435         ** So, with those rules to live with, we'll do those operations now while both forks 
 440                 /* We only set the file attributes now if the file is being copied into a 
 441                 ** drop box. In all other cases, it is better to set the attributes last 
 442                 ** so that if FileCopy is modified to give up time to other processes 
 443                 ** periodicly, the Finder won't try to read any bundle information (because 
 444                 ** the bundle-bit will still be clear) from a partially copied file. If the 
 445                 ** copy is into a drop box, we have to set the attributes now, but since the 
 446                 ** destination forks are opened with write/deny-read/deny-write permissions, 
 447                 ** any Finder that might see the file in the drop box won't be able to open 
 448                 ** its resource fork until the resource fork is closed. 
 450                 ** Note: if you do modify FileCopy to give up time to other processes, don't 
 451                 ** give up time between the time the destination file is created (above) and 
 452                 ** the time both forks are opened (below). That way, you stand the best chance 
 453                 ** of making sure the Finder doesn't read a partially copied resource fork. 
 455                 /* Copy attributes but don't lock the destination. */ 
 456                 err 
= CopyFileMgrAttributes(srcVRefNum
, srcDirID
, srcName
, 
 457                                                                         dstVRefNum
, dstDirID
, dstName
, false); 
 464         /* Attempt to copy the comments while both forks are empty. 
 465         ** Ignore the result because we really don't care if it worked or not. */ 
 466         (void) DTCopyComment(srcVRefNum
, srcDirID
, srcName
, dstVRefNum
, dstDirID
, dstName
); 
 468         /* See which forks we need to copy. By doing this, we won't create a data or resource fork 
 469         ** for the destination unless it's really needed (some foreign file systems such as 
 470         ** the ProDOS File System and Macintosh PC Exchange have to create additional disk 
 471         ** structures to support resource forks). */ 
 472         err 
= CheckForForks(srcVRefNum
, srcDirID
, srcName
, &hasDataFork
, &hasResourceFork
); 
 480                 /* Open the destination data fork. */ 
 481                 err 
= HOpenAware(dstVRefNum
, dstDirID
, dstName
, dstCopyMode
, &dstDataRefNum
); 
 488         if ( hasResourceFork 
) 
 490                 /* Open the destination resource fork. */ 
 491                 err 
= HOpenRFAware(dstVRefNum
, dstDirID
, dstName
, dstCopyMode
, &dstRsrcRefNum
); 
 500                 /* Copy the data fork. */ 
 501                 err 
= CopyFork(srcRefNum
, dstDataRefNum
, copyBufferPtr
, copyBufferSize
); 
 507                 /* Close both data forks and clear reference numbers. */ 
 508                 (void) FSClose(srcRefNum
); 
 509                 (void) FSClose(dstDataRefNum
); 
 510                 srcRefNum 
= dstDataRefNum 
= 0; 
 514                 /* Close the source data fork since it was opened earlier */ 
 515                 (void) FSClose(srcRefNum
); 
 519         if ( hasResourceFork 
) 
 521                 /* Open the source resource fork. */ 
 522                 err 
= HOpenRFAware(srcVRefNum
, srcDirID
, srcName
, srcCopyMode
, &srcRefNum
); 
 528                 /* Copy the resource fork. */ 
 529                 err 
= CopyFork(srcRefNum
, dstRsrcRefNum
, copyBufferPtr
, copyBufferSize
); 
 535                 /* Close both resource forks and clear reference numbers. */ 
 536                 (void) FSClose(srcRefNum
); 
 537                 (void) FSClose(dstRsrcRefNum
); 
 538                 srcRefNum 
= dstRsrcRefNum 
= 0; 
 541         /* Get rid of the copy buffer if we allocated it. */ 
 544                 DisposePtr((Ptr
)copyBufferPtr
); 
 547         /* Attempt to copy attributes again to set mod date.  Copy lock condition this time 
 548         ** since we're done with the copy operation.  This operation will fail if we're copying 
 549         ** into an AppleShare dropbox, so we don't check for error conditions. */ 
 550         CopyFileMgrAttributes(srcVRefNum
, srcDirID
, srcName
, 
 551                                                         dstVRefNum
, dstDirID
, dstName
, true); 
 553         /* Hey, we did it! */ 
 557         if ( srcRefNum 
!= 0 ) 
 559                 (void) FSClose(srcRefNum
);              /* Close the source file */ 
 561         if ( dstDataRefNum 
!= 0 ) 
 563                 (void) FSClose(dstDataRefNum
);  /* Close the destination file data fork */ 
 565         if ( dstRsrcRefNum 
!= 0 ) 
 567                 (void) FSClose(dstRsrcRefNum
);  /* Close the destination file resource fork */ 
 571                 (void) HDelete(dstVRefNum
, dstDirID
, dstName
);  /* Delete dest file.  This may fail if the file  
 572                                                                                                    is in a "drop folder" */ 
 574         if ( ourCopyBuffer 
)    /* dispose of any memory we allocated */ 
 576                 DisposePtr((Ptr
)copyBufferPtr
); 
 582 /*****************************************************************************/ 
 584 pascal  OSErr   
FSpFileCopy(const FSSpec 
*srcSpec
, 
 585                                                         const FSSpec 
*dstSpec
, 
 586                                                         ConstStr255Param copyName
, 
 591         return ( FileCopy(srcSpec
->vRefNum
, srcSpec
->parID
, srcSpec
->name
, 
 592                                          dstSpec
->vRefNum
, dstSpec
->parID
, dstSpec
->name
, 
 593                                          copyName
, copyBufferPtr
, copyBufferSize
, preflight
) ); 
 596 /*****************************************************************************/