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. 
  27 #define __COMPILINGMOREFILES 
  34 /*****************************************************************************/ 
  38 /*      The deny-mode privileges to use when opening the source and destination files. */ 
  42         srcCopyMode 
= dmRdDenyWr
, 
  43         dstCopyMode 
= dmWrDenyRdWr
 
  46 /*      The largest (16K) and smallest (.5K) copy buffer to use if the caller doesn't supply  
  47 **      their own copy buffer. */ 
  51         bigCopyBuffSize  
= 0x00004000, 
  52         minCopyBuffSize  
= 0x00000200 
  55 /*****************************************************************************/ 
  57 /* static prototypes */ 
  59 static  OSErr   
GetDestinationDirInfo(short vRefNum
, 
  61                                                                           ConstStr255Param name
, 
  65 /*      GetDestinationDirInfo tells us if the destination is a directory, it's 
  66         directory ID, and if it's an AppleShare drop box (write privileges only -- 
  67         no read or search privileges). 
  68         vRefNum         input:  Volume specification. 
  69         dirID           input:  Directory ID. 
  70         name            input:  Pointer to object name, or nil when dirID 
  71                                                 specifies a directory that's the object. 
  72         theDirID        output: If the object is a file, then its parent directory 
  73                                                 ID. If the object is a directory, then its ID. 
  74         isDirectory     output: True if object is a directory; false if 
  76         isDropBox       output: True if directory is an AppleShare drop box. 
  79 static  OSErr   
CheckForForks(short vRefNum
, 
  81                                                           ConstStr255Param name
, 
  83                                                           Boolean 
*hasResourceFork
); 
  84 /*      CheckForForks tells us if there is a data or resource fork to copy. 
  85         vRefNum         input:  Volume specification of the file's current 
  87         dirID           input:  Directory ID of the file's current location. 
  88         name            input:  The name of the file. 
  91 static  OSErr   
PreflightFileCopySpace(short srcVRefNum
, 
  93                                                                            ConstStr255Param srcName
, 
  94                                                                            ConstStr255Param dstVolName
, 
  97 /*      PreflightFileCopySpace determines if there's enough space on a 
  98         volume to copy the specified file to that volume. 
  99         Note: The results of this routine are not perfect. For example if the 
 100         volume's catalog or extents overflow file grows when the new file is 
 101         created, more allocation blocks may be needed beyond those needed for 
 102         the file's data and resource forks. 
 104         srcVRefNum              input:  Volume specification of the file's current 
 106         srcDirID                input:  Directory ID of the file's current location. 
 107         srcName                 input:  The name of the file. 
 108         dstVolName              input:  A pointer to the name of the volume where 
 109                                                         the file will be copied or NULL. 
 110         dstVRefNum              input:  Volume specification indicating the volume 
 111                                                         where the file will be copied. 
 112         spaceOK                 output: true if there's enough space on the volume for 
 113                                                         the file's data and resource forks. 
 116 /*****************************************************************************/ 
 118 static  OSErr   
GetDestinationDirInfo(short vRefNum
, 
 120                                                                           ConstStr255Param name
, 
 122                                                                           Boolean 
*isDirectory
, 
 128         pb
.dirInfo
.ioACUser 
= 0;        /* ioACUser used to be filler2, clear it before calling GetCatInfo */ 
 129         error 
= GetCatInfoNoName(vRefNum
, dirID
, name
, &pb
); 
 130         *theDirID 
= pb
.dirInfo
.ioDrDirID
; 
 131         *isDirectory 
= (pb
.dirInfo
.ioFlAttrib 
& ioDirMask
) != 0; 
 132         /* see if access priviledges are make changes, not see folder, and not see files (drop box) */ 
 133         *isDropBox 
= ((pb
.dirInfo
.ioACUser 
& 0x07) == 0x03); 
 138 /*****************************************************************************/ 
 140 static  OSErr   
CheckForForks(short vRefNum
, 
 142                                                           ConstStr255Param name
, 
 143                                                           Boolean 
*hasDataFork
, 
 144                                                           Boolean 
*hasResourceFork
) 
 149         pb
.fileParam
.ioNamePtr 
= (StringPtr
)name
; 
 150         pb
.fileParam
.ioVRefNum 
= vRefNum
; 
 151         pb
.fileParam
.ioFVersNum 
= 0; 
 152         pb
.fileParam
.ioDirID 
= dirID
; 
 153         pb
.fileParam
.ioFDirIndex 
= 0; 
 154         error 
= PBHGetFInfoSync(&pb
); 
 155         *hasDataFork 
= (pb
.fileParam
.ioFlLgLen 
!= 0); 
 156         *hasResourceFork 
= (pb
.fileParam
.ioFlRLgLen 
!= 0); 
 161 /*****************************************************************************/ 
 163 static  OSErr   
PreflightFileCopySpace(short srcVRefNum
, 
 165                                                                            ConstStr255Param srcName
, 
 166                                                                            ConstStr255Param dstVolName
, 
 172         unsigned long dstFreeBlocks
; 
 173         unsigned long dstBlksPerAllocBlk
; 
 174         unsigned long srcDataBlks
; 
 175         unsigned long srcResourceBlks
; 
 177         error 
= XGetVolumeInfoNoName(dstVolName
, dstVRefNum
, &pb
.xPB
); 
 178         if ( error 
== noErr 
) 
 180                 /* get allocation block size (always multiple of 512) and divide by 512 
 181                   to get number of 512-byte blocks per allocation block */ 
 182                 dstBlksPerAllocBlk 
= ((unsigned long)pb
.xPB
.ioVAlBlkSiz 
>> 9); 
 184                 /* Convert freeBytes to free disk blocks (512-byte blocks) */ 
 185                 dstFreeBlocks 
= (pb
.xPB
.ioVFreeBytes
.hi 
<< 23) + (pb
.xPB
.ioVFreeBytes
.lo 
>> 9); 
 187                 /* Now, get the size of the file's data resource forks */ 
 188                 pb
.hPB
.fileParam
.ioNamePtr 
= (StringPtr
)srcName
; 
 189                 pb
.hPB
.fileParam
.ioVRefNum 
= srcVRefNum
; 
 190                 pb
.hPB
.fileParam
.ioFVersNum 
= 0; 
 191                 pb
.hPB
.fileParam
.ioDirID 
= srcDirID
; 
 192                 pb
.hPB
.fileParam
.ioFDirIndex 
= 0; 
 193                 error 
= PBHGetFInfoSync(&pb
.hPB
); 
 194                 if ( error 
== noErr 
) 
 196                         /* Since space on Mac OS disks is always allocated in allocation blocks, */ 
 197                         /* this code takes into account rounding up to the end of an allocation block. */ 
 199                         /* get number of 512-byte blocks needed for data fork */ 
 200                         if ( ((unsigned long)pb
.hPB
.fileParam
.ioFlLgLen 
& 0x000001ff) != 0 ) 
 202                                 srcDataBlks 
= ((unsigned long)pb
.hPB
.fileParam
.ioFlLgLen 
>> 9) + 1; 
 206                                 srcDataBlks 
= (unsigned long)pb
.hPB
.fileParam
.ioFlLgLen 
>> 9; 
 209                         /* now, calculate number of new allocation blocks needed */ 
 210                         if ( srcDataBlks 
% dstBlksPerAllocBlk 
) 
 212                                 srcDataBlks 
= (srcDataBlks 
/ dstBlksPerAllocBlk
) + 1; 
 216                                 srcDataBlks 
/= dstBlksPerAllocBlk
; 
 219                         /* get number of 512-byte blocks needed for resource fork */ 
 220                         if ( ((unsigned long)pb
.hPB
.fileParam
.ioFlRLgLen 
& 0x000001ff) != 0 ) 
 222                                 srcResourceBlks 
= ((unsigned long)pb
.hPB
.fileParam
.ioFlRLgLen 
>> 9) + 1; 
 226                                 srcResourceBlks 
= (unsigned long)pb
.hPB
.fileParam
.ioFlRLgLen 
>> 9; 
 229                         /* now, calculate number of new allocation blocks needed */ 
 230                         if ( srcResourceBlks 
% dstBlksPerAllocBlk 
) 
 232                                 srcResourceBlks 
= (srcResourceBlks 
/ dstBlksPerAllocBlk
) + 1; 
 236                                 srcResourceBlks 
/= dstBlksPerAllocBlk
; 
 239                         /* Is there enough room on the destination volume for the source file? */ 
 240                         *spaceOK 
= ( ((srcDataBlks 
+ srcResourceBlks
) * dstBlksPerAllocBlk
) <= dstFreeBlocks 
); 
 247 /*****************************************************************************/ 
 249 pascal  OSErr   
FileCopy(short srcVRefNum
, 
 251                                                  ConstStr255Param srcName
, 
 254                                                  ConstStr255Param dstPathname
, 
 255                                                  ConstStr255Param copyName
, 
 262         short   srcRefNum 
= 0,                  /* 0 when source data and resource fork are closed  */ 
 263                         dstDataRefNum 
= 0,              /* 0 when destination data fork is closed */ 
 264                         dstRsrcRefNum 
= 0;              /* 0 when destination resource fork is closed */ 
 266         Str63   dstName
;                                /* The filename of the destination. It might be the 
 267                                                                         ** source filename, it might be a new name... */ 
 269         GetVolParmsInfoBuffer infoBuffer
; /* Where PBGetVolParms dumps its info */ 
 270         long    srcServerAdr
;                   /* AppleTalk server address of source (if any) */ 
 272         Boolean dstCreated 
= false,             /* true when destination file has been created */ 
 273                         ourCopyBuffer 
= false,  /* true if we had to allocate the copy buffer */ 
 274                         isDirectory
,                    /* true if destination is really a directory */ 
 275                         isDropBox
;                              /* true if destination is an AppleShare drop box */ 
 280         Boolean spaceOK
;                                /* true if there's enough room to copy the file to the destination volume */ 
 283         Boolean hasResourceFork
; 
 285         /* Preflight for size */ 
 288                 err 
= PreflightFileCopySpace(srcVRefNum
, srcDirID
, srcName
, 
 289                                                                          dstPathname
, dstVRefNum
, &spaceOK
); 
 297                         return ( dskFulErr 
); 
 301         /* get the destination's real dirID and make sure it really is a directory */ 
 302         err 
= GetDestinationDirInfo(dstVRefNum
, dstDirID
, dstPathname
, 
 303                                                                 &dstDirID
, &isDirectory
, &isDropBox
); 
 314         /* get the destination's real vRefNum */ 
 315         err 
= DetermineVRefNum(dstPathname
, dstVRefNum
, &dstVRefNum
); 
 321         /* See if PBHCopyFile can be used.  Using PBHCopyFile saves time by letting the file server 
 322         ** copy the file if the source and destination locations are on the same file server. */ 
 323         tempLong 
= sizeof(infoBuffer
); 
 324         err 
= HGetVolParms(srcName
, srcVRefNum
, &infoBuffer
, &tempLong
); 
 325         if ( (err 
!= noErr
) && (err 
!= paramErr
) ) 
 330         if ( (err 
!= paramErr
) && hasCopyFile(infoBuffer
) ) 
 332                 /* The source volume supports PBHCopyFile. */ 
 333                 srcServerAdr 
= infoBuffer
.vMServerAdr
; 
 335                 /* Now, see if the destination volume is on the same file server. */ 
 336                 tempLong 
= sizeof(infoBuffer
); 
 337                 err 
= HGetVolParms(NULL
, dstVRefNum
, &infoBuffer
, &tempLong
); 
 338                 if ( (err 
!= noErr
) && (err 
!= paramErr
) ) 
 342                 if ( (err 
!= paramErr
) && (srcServerAdr 
== infoBuffer
.vMServerAdr
) ) 
 344                         /* Source and Dest are on same server and PBHCopyFile is supported. Copy with CopyFile. */ 
 345                         err 
= HCopyFile(srcVRefNum
, srcDirID
, srcName
, dstVRefNum
, dstDirID
, NULL
, copyName
); 
 351                         /* AppleShare's CopyFile clears the isAlias bit, so I still need to attempt to copy 
 352                            the File's attributes to attempt to get things right. */ 
 353                         if ( copyName 
!= NULL 
)                         /* Did caller supply copy file name? */ 
 355                                 /* Yes, use the caller supplied copy file name. */ 
 356                                 (void) CopyFileMgrAttributes(srcVRefNum
, srcDirID
, srcName
, 
 357                                                                                          dstVRefNum
, dstDirID
, copyName
, true); 
 361                                 /* They didn't, so get the source file name and use it. */ 
 362                                 if ( GetFilenameFromPathname(srcName
, dstName
) == noErr 
) 
 365                                         (void) CopyFileMgrAttributes(srcVRefNum
, srcDirID
, srcName
, 
 366                                                                                                  dstVRefNum
, dstDirID
, dstName
, true); 
 373         /* If we're here, then PBHCopyFile couldn't be used so we have to copy the file by hand. */ 
 375         /* Make sure a copy buffer is allocated. */ 
 376         if ( copyBufferPtr 
== NULL 
) 
 378                 /* The caller didn't supply a copy buffer so grab one from the application heap. 
 379                 ** Try to get a big copy buffer, if we can't, try for a 512-byte buffer. 
 380                 ** If 512 bytes aren't available, we're in trouble. */ 
 381                 copyBufferSize 
= bigCopyBuffSize
; 
 382                 copyBufferPtr 
= NewPtr(copyBufferSize
); 
 383                 if ( copyBufferPtr 
== NULL 
) 
 385                         copyBufferSize 
= minCopyBuffSize
; 
 386                         copyBufferPtr 
= NewPtr(copyBufferSize
); 
 387                         if ( copyBufferPtr 
== NULL 
) 
 389                                 return ( memFullErr 
); 
 392                 ourCopyBuffer 
= true; 
 395         /* Open the source data fork. */ 
 396         err 
= HOpenAware(srcVRefNum
, srcDirID
, srcName
, srcCopyMode
, &srcRefNum
); 
 400         /* Once a file is opened, we have to exit via ErrorExit to make sure things are cleaned up */ 
 402         /* See if the copy will be renamed. */ 
 403         if ( copyName 
!= NULL 
)                         /* Did caller supply copy file name? */ 
 404                 BlockMoveData(copyName
, dstName
, copyName
[0] + 1);      /* Yes, use the caller supplied copy file name. */ 
 406         {       /* They didn't, so get the source file name and use it. */ 
 407                 err 
= GetFileLocation(srcRefNum
, &tempInt
, &tempLong
, dstName
); 
 414         /* Create the destination file. */ 
 415         err 
= HCreateMinimum(dstVRefNum
, dstDirID
, dstName
); 
 420         dstCreated 
= true;      /* After creating the destination file, any 
 421                                                 ** error conditions should delete the destination file */ 
 423         /* An AppleShare dropbox folder is a folder for which the user has the Make Changes 
 424         ** privilege (write access), but not See Files (read access) and See Folders (search access). 
 425         ** Copying a file into an AppleShare dropbox presents some special problems. Here are the 
 426         ** rules we have to follow to copy a file into a dropbox: 
 427         ** ¥ File attributes can be changed only when both forks of a file are empty. 
 428         ** ¥ DeskTop Manager comments can be added to a file only when both forks of a file  
 430         ** ¥ A fork can be opened for write access only when both forks of a file are empty. 
 431         ** So, with those rules to live with, we'll do those operations now while both forks 
 436                 /* We only set the file attributes now if the file is being copied into a 
 437                 ** drop box. In all other cases, it is better to set the attributes last 
 438                 ** so that if FileCopy is modified to give up time to other processes 
 439                 ** periodicly, the Finder won't try to read any bundle information (because 
 440                 ** the bundle-bit will still be clear) from a partially copied file. If the 
 441                 ** copy is into a drop box, we have to set the attributes now, but since the 
 442                 ** destination forks are opened with write/deny-read/deny-write permissions, 
 443                 ** any Finder that might see the file in the drop box won't be able to open 
 444                 ** its resource fork until the resource fork is closed. 
 446                 ** Note: if you do modify FileCopy to give up time to other processes, don't 
 447                 ** give up time between the time the destination file is created (above) and 
 448                 ** the time both forks are opened (below). That way, you stand the best chance 
 449                 ** of making sure the Finder doesn't read a partially copied resource fork. 
 451                 /* Copy attributes but don't lock the destination. */ 
 452                 err 
= CopyFileMgrAttributes(srcVRefNum
, srcDirID
, srcName
, 
 453                                                                         dstVRefNum
, dstDirID
, dstName
, false); 
 460         /* Attempt to copy the comments while both forks are empty. 
 461         ** Ignore the result because we really don't care if it worked or not. */ 
 462         (void) DTCopyComment(srcVRefNum
, srcDirID
, srcName
, dstVRefNum
, dstDirID
, dstName
); 
 464         /* See which forks we need to copy. By doing this, we won't create a data or resource fork 
 465         ** for the destination unless it's really needed (some foreign file systems such as 
 466         ** the ProDOS File System and Macintosh PC Exchange have to create additional disk 
 467         ** structures to support resource forks). */ 
 468         err 
= CheckForForks(srcVRefNum
, srcDirID
, srcName
, &hasDataFork
, &hasResourceFork
); 
 476                 /* Open the destination data fork. */ 
 477                 err 
= HOpenAware(dstVRefNum
, dstDirID
, dstName
, dstCopyMode
, &dstDataRefNum
); 
 484         if ( hasResourceFork 
) 
 486                 /* Open the destination resource fork. */ 
 487                 err 
= HOpenRFAware(dstVRefNum
, dstDirID
, dstName
, dstCopyMode
, &dstRsrcRefNum
); 
 496                 /* Copy the data fork. */ 
 497                 err 
= CopyFork(srcRefNum
, dstDataRefNum
, copyBufferPtr
, copyBufferSize
); 
 503                 /* Close both data forks and clear reference numbers. */ 
 504                 (void) FSClose(srcRefNum
); 
 505                 (void) FSClose(dstDataRefNum
); 
 506                 srcRefNum 
= dstDataRefNum 
= 0; 
 510                 /* Close the source data fork since it was opened earlier */ 
 511                 (void) FSClose(srcRefNum
); 
 515         if ( hasResourceFork 
) 
 517                 /* Open the source resource fork. */ 
 518                 err 
= HOpenRFAware(srcVRefNum
, srcDirID
, srcName
, srcCopyMode
, &srcRefNum
); 
 524                 /* Copy the resource fork. */ 
 525                 err 
= CopyFork(srcRefNum
, dstRsrcRefNum
, copyBufferPtr
, copyBufferSize
); 
 531                 /* Close both resource forks and clear reference numbers. */ 
 532                 (void) FSClose(srcRefNum
); 
 533                 (void) FSClose(dstRsrcRefNum
); 
 534                 srcRefNum 
= dstRsrcRefNum 
= 0; 
 537         /* Get rid of the copy buffer if we allocated it. */ 
 540                 DisposePtr((Ptr
)copyBufferPtr
); 
 543         /* Attempt to copy attributes again to set mod date.  Copy lock condition this time 
 544         ** since we're done with the copy operation.  This operation will fail if we're copying 
 545         ** into an AppleShare dropbox, so we don't check for error conditions. */ 
 546         CopyFileMgrAttributes(srcVRefNum
, srcDirID
, srcName
, 
 547                                                         dstVRefNum
, dstDirID
, dstName
, true); 
 549         /* Hey, we did it! */ 
 553         if ( srcRefNum 
!= 0 ) 
 555                 (void) FSClose(srcRefNum
);              /* Close the source file */ 
 557         if ( dstDataRefNum 
!= 0 ) 
 559                 (void) FSClose(dstDataRefNum
);  /* Close the destination file data fork */ 
 561         if ( dstRsrcRefNum 
!= 0 ) 
 563                 (void) FSClose(dstRsrcRefNum
);  /* Close the destination file resource fork */ 
 567                 (void) HDelete(dstVRefNum
, dstDirID
, dstName
);  /* Delete dest file.  This may fail if the file  
 568                                                                                                    is in a "drop folder" */ 
 570         if ( ourCopyBuffer 
)    /* dispose of any memory we allocated */ 
 572                 DisposePtr((Ptr
)copyBufferPtr
); 
 578 /*****************************************************************************/ 
 580 pascal  OSErr   
FSpFileCopy(const FSSpec 
*srcSpec
, 
 581                                                         const FSSpec 
*dstSpec
, 
 582                                                         ConstStr255Param copyName
, 
 587         return ( FileCopy(srcSpec
->vRefNum
, srcSpec
->parID
, srcSpec
->name
, 
 588                                          dstSpec
->vRefNum
, dstSpec
->parID
, dstSpec
->name
, 
 589                                          copyName
, copyBufferPtr
, copyBufferSize
, preflight
) ); 
 592 /*****************************************************************************/