]> git.saurik.com Git - wxWidgets.git/blob - src/mac/morefile/FileCopy.cpp
added wxUSE_DEBUG_NEW_ALWAYS to --enable-mem_tracing (quite useless otherwise)
[wxWidgets.git] / src / mac / morefile / FileCopy.cpp
1 /*
2 ** Apple Macintosh Developer Technical Support
3 **
4 ** FileCopy: A robust, general purpose file copy routine.
5 **
6 ** by Jim Luther, Apple Developer Technical Support Emeritus
7 **
8 ** File: FileCopy.c
9 **
10 ** Copyright © 1992-1998 Apple Computer, Inc.
11 ** All rights reserved.
12 **
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.
20 */
21
22 #include <Types.h>
23 #include <Errors.h>
24 #include <Memory.h>
25 #include <Files.h>
26
27 #define __COMPILINGMOREFILES
28
29 #include "MoreFile.h"
30 #include "MoreExtr.h"
31 #include "MoreDesk.h"
32 #include "FileCopy.h"
33
34 /*****************************************************************************/
35
36 /* local constants */
37
38 /* The deny-mode privileges to use when opening the source and destination files. */
39
40 enum
41 {
42 srcCopyMode = dmRdDenyWr,
43 dstCopyMode = dmWrDenyRdWr
44 };
45
46 /* The largest (16K) and smallest (.5K) copy buffer to use if the caller doesn't supply
47 ** their own copy buffer. */
48
49 enum
50 {
51 bigCopyBuffSize = 0x00004000,
52 minCopyBuffSize = 0x00000200
53 };
54
55 /*****************************************************************************/
56
57 /* static prototypes */
58
59 static OSErr GetDestinationDirInfo(short vRefNum,
60 long dirID,
61 ConstStr255Param name,
62 long *theDirID,
63 Boolean *isDirectory,
64 Boolean *isDropBox);
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
75 object is a file.
76 isDropBox output: True if directory is an AppleShare drop box.
77 */
78
79 static OSErr CheckForForks(short vRefNum,
80 long dirID,
81 ConstStr255Param name,
82 Boolean *hasDataFork,
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
86 location.
87 dirID input: Directory ID of the file's current location.
88 name input: The name of the file.
89 */
90
91 static OSErr PreflightFileCopySpace(short srcVRefNum,
92 long srcDirID,
93 ConstStr255Param srcName,
94 ConstStr255Param dstVolName,
95 short dstVRefNum,
96 Boolean *spaceOK);
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.
103
104 srcVRefNum input: Volume specification of the file's current
105 location.
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.
114 */
115
116 /*****************************************************************************/
117
118 static OSErr GetDestinationDirInfo(short vRefNum,
119 long dirID,
120 ConstStr255Param name,
121 long *theDirID,
122 Boolean *isDirectory,
123 Boolean *isDropBox)
124 {
125 CInfoPBRec pb;
126 OSErr error;
127
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);
134
135 return ( error );
136 }
137
138 /*****************************************************************************/
139
140 static OSErr CheckForForks(short vRefNum,
141 long dirID,
142 ConstStr255Param name,
143 Boolean *hasDataFork,
144 Boolean *hasResourceFork)
145 {
146 HParamBlockRec pb;
147 OSErr error;
148
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);
157
158 return ( error );
159 }
160
161 /*****************************************************************************/
162
163 static OSErr PreflightFileCopySpace(short srcVRefNum,
164 long srcDirID,
165 ConstStr255Param srcName,
166 ConstStr255Param dstVolName,
167 short dstVRefNum,
168 Boolean *spaceOK)
169 {
170 UniversalFMPB pb;
171 OSErr error;
172 unsigned long dstFreeBlocks;
173 unsigned long dstBlksPerAllocBlk;
174 unsigned long srcDataBlks;
175 unsigned long srcResourceBlks;
176
177 error = XGetVolumeInfoNoName(dstVolName, dstVRefNum, &pb.xPB);
178 if ( error == noErr )
179 {
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);
183
184 /* Convert freeBytes to free disk blocks (512-byte blocks) */
185 dstFreeBlocks = (pb.xPB.ioVFreeBytes.hi << 23) + (pb.xPB.ioVFreeBytes.lo >> 9);
186
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 )
195 {
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. */
198
199 /* get number of 512-byte blocks needed for data fork */
200 if ( ((unsigned long)pb.hPB.fileParam.ioFlLgLen & 0x000001ff) != 0 )
201 {
202 srcDataBlks = ((unsigned long)pb.hPB.fileParam.ioFlLgLen >> 9) + 1;
203 }
204 else
205 {
206 srcDataBlks = (unsigned long)pb.hPB.fileParam.ioFlLgLen >> 9;
207 }
208
209 /* now, calculate number of new allocation blocks needed */
210 if ( srcDataBlks % dstBlksPerAllocBlk )
211 {
212 srcDataBlks = (srcDataBlks / dstBlksPerAllocBlk) + 1;
213 }
214 else
215 {
216 srcDataBlks /= dstBlksPerAllocBlk;
217 }
218
219 /* get number of 512-byte blocks needed for resource fork */
220 if ( ((unsigned long)pb.hPB.fileParam.ioFlRLgLen & 0x000001ff) != 0 )
221 {
222 srcResourceBlks = ((unsigned long)pb.hPB.fileParam.ioFlRLgLen >> 9) + 1;
223 }
224 else
225 {
226 srcResourceBlks = (unsigned long)pb.hPB.fileParam.ioFlRLgLen >> 9;
227 }
228
229 /* now, calculate number of new allocation blocks needed */
230 if ( srcResourceBlks % dstBlksPerAllocBlk )
231 {
232 srcResourceBlks = (srcResourceBlks / dstBlksPerAllocBlk) + 1;
233 }
234 else
235 {
236 srcResourceBlks /= dstBlksPerAllocBlk;
237 }
238
239 /* Is there enough room on the destination volume for the source file? */
240 *spaceOK = ( ((srcDataBlks + srcResourceBlks) * dstBlksPerAllocBlk) <= dstFreeBlocks );
241 }
242 }
243
244 return ( error );
245 }
246
247 /*****************************************************************************/
248
249 pascal OSErr FileCopy(short srcVRefNum,
250 long srcDirID,
251 ConstStr255Param srcName,
252 short dstVRefNum,
253 long dstDirID,
254 ConstStr255Param dstPathname,
255 ConstStr255Param copyName,
256 void *copyBufferPtr,
257 long copyBufferSize,
258 Boolean preflight)
259 {
260 OSErr err;
261
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 */
265
266 Str63 dstName; /* The filename of the destination. It might be the
267 ** source filename, it might be a new name... */
268
269 GetVolParmsInfoBuffer infoBuffer; /* Where PBGetVolParms dumps its info */
270 long srcServerAdr; /* AppleTalk server address of source (if any) */
271
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 */
276
277 long tempLong;
278 short tempInt;
279
280 Boolean spaceOK; /* true if there's enough room to copy the file to the destination volume */
281
282 Boolean hasDataFork;
283 Boolean hasResourceFork;
284
285 /* Preflight for size */
286 if ( preflight )
287 {
288 err = PreflightFileCopySpace(srcVRefNum, srcDirID, srcName,
289 dstPathname, dstVRefNum, &spaceOK);
290 if ( err != noErr )
291 {
292 return ( err );
293 }
294
295 if ( !spaceOK )
296 {
297 return ( dskFulErr );
298 }
299 }
300
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);
304 if ( err != noErr )
305 {
306 goto ErrorExit;
307 }
308
309 if ( !isDirectory )
310 {
311 return ( dirNFErr );
312 }
313
314 /* get the destination's real vRefNum */
315 err = DetermineVRefNum(dstPathname, dstVRefNum, &dstVRefNum);
316 if ( err != noErr )
317 {
318 goto ErrorExit;
319 }
320
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) )
326 {
327 return ( err );
328 }
329
330 if ( (err != paramErr) && hasCopyFile(infoBuffer) )
331 {
332 /* The source volume supports PBHCopyFile. */
333 srcServerAdr = infoBuffer.vMServerAdr;
334
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) )
339 {
340 return ( err );
341 }
342 if ( (err != paramErr) && (srcServerAdr == infoBuffer.vMServerAdr) )
343 {
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);
346 if ( err != noErr )
347 {
348 return ( err );
349 }
350
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? */
354 {
355 /* Yes, use the caller supplied copy file name. */
356 (void) CopyFileMgrAttributes(srcVRefNum, srcDirID, srcName,
357 dstVRefNum, dstDirID, copyName, true);
358 }
359 else
360 {
361 /* They didn't, so get the source file name and use it. */
362 if ( GetFilenameFromPathname(srcName, dstName) == noErr )
363 {
364 /* */
365 (void) CopyFileMgrAttributes(srcVRefNum, srcDirID, srcName,
366 dstVRefNum, dstDirID, dstName, true);
367 }
368 }
369 return ( err );
370 }
371 }
372
373 /* If we're here, then PBHCopyFile couldn't be used so we have to copy the file by hand. */
374
375 /* Make sure a copy buffer is allocated. */
376 if ( copyBufferPtr == NULL )
377 {
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 )
384 {
385 copyBufferSize = minCopyBuffSize;
386 copyBufferPtr = NewPtr(copyBufferSize);
387 if ( copyBufferPtr == NULL )
388 {
389 return ( memFullErr );
390 }
391 }
392 ourCopyBuffer = true;
393 }
394
395 /* Open the source data fork. */
396 err = HOpenAware(srcVRefNum, srcDirID, srcName, srcCopyMode, &srcRefNum);
397 if ( err != noErr )
398 return ( err );
399
400 /* Once a file is opened, we have to exit via ErrorExit to make sure things are cleaned up */
401
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. */
405 else
406 { /* They didn't, so get the source file name and use it. */
407 err = GetFileLocation(srcRefNum, &tempInt, &tempLong, dstName);
408 if ( err != noErr )
409 {
410 goto ErrorExit;
411 }
412 }
413
414 /* Create the destination file. */
415 err = HCreateMinimum(dstVRefNum, dstDirID, dstName);
416 if ( err != noErr )
417 {
418 goto ErrorExit;
419 }
420 dstCreated = true; /* After creating the destination file, any
421 ** error conditions should delete the destination file */
422
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
429 ** are empty.
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
432 ** are empty. */
433
434 if ( isDropBox )
435 {
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.
445 **
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.
450 */
451 /* Copy attributes but don't lock the destination. */
452 err = CopyFileMgrAttributes(srcVRefNum, srcDirID, srcName,
453 dstVRefNum, dstDirID, dstName, false);
454 if ( err != noErr )
455 {
456 goto ErrorExit;
457 }
458 }
459
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);
463
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);
469 if ( err != noErr )
470 {
471 goto ErrorExit;
472 }
473
474 if ( hasDataFork )
475 {
476 /* Open the destination data fork. */
477 err = HOpenAware(dstVRefNum, dstDirID, dstName, dstCopyMode, &dstDataRefNum);
478 if ( err != noErr )
479 {
480 goto ErrorExit;
481 }
482 }
483
484 if ( hasResourceFork )
485 {
486 /* Open the destination resource fork. */
487 err = HOpenRFAware(dstVRefNum, dstDirID, dstName, dstCopyMode, &dstRsrcRefNum);
488 if ( err != noErr )
489 {
490 goto ErrorExit;
491 }
492 }
493
494 if ( hasDataFork )
495 {
496 /* Copy the data fork. */
497 err = CopyFork(srcRefNum, dstDataRefNum, copyBufferPtr, copyBufferSize);
498 if ( err != noErr )
499 {
500 goto ErrorExit;
501 }
502
503 /* Close both data forks and clear reference numbers. */
504 (void) FSClose(srcRefNum);
505 (void) FSClose(dstDataRefNum);
506 srcRefNum = dstDataRefNum = 0;
507 }
508 else
509 {
510 /* Close the source data fork since it was opened earlier */
511 (void) FSClose(srcRefNum);
512 srcRefNum = 0;
513 }
514
515 if ( hasResourceFork )
516 {
517 /* Open the source resource fork. */
518 err = HOpenRFAware(srcVRefNum, srcDirID, srcName, srcCopyMode, &srcRefNum);
519 if ( err != noErr )
520 {
521 goto ErrorExit;
522 }
523
524 /* Copy the resource fork. */
525 err = CopyFork(srcRefNum, dstRsrcRefNum, copyBufferPtr, copyBufferSize);
526 if ( err != noErr )
527 {
528 goto ErrorExit;
529 }
530
531 /* Close both resource forks and clear reference numbers. */
532 (void) FSClose(srcRefNum);
533 (void) FSClose(dstRsrcRefNum);
534 srcRefNum = dstRsrcRefNum = 0;
535 }
536
537 /* Get rid of the copy buffer if we allocated it. */
538 if ( ourCopyBuffer )
539 {
540 DisposePtr((Ptr)copyBufferPtr);
541 }
542
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);
548
549 /* Hey, we did it! */
550 return ( noErr );
551
552 ErrorExit:
553 if ( srcRefNum != 0 )
554 {
555 (void) FSClose(srcRefNum); /* Close the source file */
556 }
557 if ( dstDataRefNum != 0 )
558 {
559 (void) FSClose(dstDataRefNum); /* Close the destination file data fork */
560 }
561 if ( dstRsrcRefNum != 0 )
562 {
563 (void) FSClose(dstRsrcRefNum); /* Close the destination file resource fork */
564 }
565 if ( dstCreated )
566 {
567 (void) HDelete(dstVRefNum, dstDirID, dstName); /* Delete dest file. This may fail if the file
568 is in a "drop folder" */
569 }
570 if ( ourCopyBuffer ) /* dispose of any memory we allocated */
571 {
572 DisposePtr((Ptr)copyBufferPtr);
573 }
574
575 return ( err );
576 }
577
578 /*****************************************************************************/
579
580 pascal OSErr FSpFileCopy(const FSSpec *srcSpec,
581 const FSSpec *dstSpec,
582 ConstStr255Param copyName,
583 void *copyBufferPtr,
584 long copyBufferSize,
585 Boolean preflight)
586 {
587 return ( FileCopy(srcSpec->vRefNum, srcSpec->parID, srcSpec->name,
588 dstSpec->vRefNum, dstSpec->parID, dstSpec->name,
589 copyName, copyBufferPtr, copyBufferSize, preflight) );
590 }
591
592 /*****************************************************************************/
593