VirtualBox

source: vbox/trunk/src/libs/xpcom18a4/xpcom/MoreFiles/FSCopyObject.c@ 4837

Last change on this file since 4837 was 4481, checked in by vboxsync, 17 years ago

remove Apple disclaimer as permitted by original disclaimer and make it MPL/GPL

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 61.5 KB
Line 
1/*
2 File: FSCopyObject.c
3
4 Contains: A Copy/Delete Files/Folders engine which uses the HFS+ API's.
5 This code is a combination of MoreFilesX and MPFileCopy
6 with some added features. This code will run on OS 9.1 and up
7 and 10.1.x (Classic and Carbon)
8
9*/
10
11/* ***** BEGIN LICENSE BLOCK *****
12 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
13 *
14 * The contents of this file are subject to the Mozilla Public License Version
15 * 1.1 (the "License"); you may not use this file except in compliance with
16 * the License. You may obtain a copy of the License at
17 * http://www.mozilla.org/MPL/
18 *
19 * Software distributed under the License is distributed on an "AS IS" basis,
20 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
21 * for the specific language governing rights and limitations under the
22 * License.
23 *
24 * The Original Code is Mozilla Communicator client code, released
25 * March 31, 1998.
26 *
27 * The Initial Developer of the Original Code is
28 * Netscape Communications Corporation.
29 * Portions created by the Initial Developer are Copyright (C) 2000
30 * the Initial Developer. All Rights Reserved.
31 *
32 * Contributor(s):
33 *
34 * Alternatively, the contents of this file may be used under the terms of
35 * either of the GNU General Public License Version 2 or later (the "GPL"),
36 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
37 * in which case the provisions of the GPL or the LGPL are applicable instead
38 * of those above. If you wish to allow use of your version of this file only
39 * under the terms of either the GPL or the LGPL, and not to allow others to
40 * use your version of this file under the terms of the MPL, indicate your
41 * decision by deleting the provisions above and replace them with the notice
42 * and other provisions required by the GPL or the LGPL. If you do not delete
43 * the provisions above, a recipient may use your version of this file under
44 * the terms of any one of the MPL, the GPL or the LGPL.
45 *
46 * ***** END LICENSE BLOCK ***** */
47
48
49// Modified 2006-01-23 - added this comment.
50
51#include "FSCopyObject.h"
52#include <UnicodeConverter.h>
53#include <stddef.h>
54#include <string.h>
55
56/*
57
58*/
59
60#pragma mark ----- Tunable Parameters -----
61
62// The following constants control the behavior of the copy engine.
63
64enum { // BufferSizeForThisVolumeSpeed
65// kDefaultCopyBufferSize = 2L * 1024 * 1024, // Fast be not very responsive.
66 kDefaultCopyBufferSize = 256L * 1024, // Slower, but can still use machine.
67 kMaximumCopyBufferSize = 2L * 1024 * 1024,
68 kMinimumCopyBufferSize = 1024
69};
70
71enum { // CalculateForksToCopy
72 kExpectedForkCount = 10 // Number of non-classic forks we expect.
73}; // (i.e. non resource/data forks)
74
75enum { // CheckForDestInsideSource
76 errFSDestInsideSource = -1234
77};
78
79enum {
80 // for use with PBHGetDirAccess in IsDropBox
81 kPrivilegesMask = kioACAccessUserWriteMask | kioACAccessUserReadMask | kioACAccessUserSearchMask,
82
83 // for use with FSGetCatalogInfo and FSPermissionInfo->mode
84 // from sys/stat.h... note -- sys/stat.h definitions are in octal
85 //
86 // You can use these values to adjust the users/groups permissions
87 // on a file/folder with FSSetCatalogInfo and extracting the
88 // kFSCatInfoPermissions field. See code below for examples
89 kRWXUserAccessMask = 0x01C0,
90 kReadAccessUser = 0x0100,
91 kWriteAccessUser = 0x0080,
92 kExecuteAccessUser = 0x0040,
93
94 kRWXGroupAccessMask = 0x0038,
95 kReadAccessGroup = 0x0020,
96 kWriteAccessGroup = 0x0010,
97 kExecuteAccessGroup = 0x0008,
98
99 kRWXOtherAccessMask = 0x0007,
100 kReadAccessOther = 0x0004,
101 kWriteAccessOther = 0x0002,
102 kExecuteAccessOther = 0x0001,
103
104 kDropFolderValue = kWriteAccessOther | kExecuteAccessOther
105};
106
107#pragma mark ----- Struct Definitions -----
108
109#define VolHasCopyFile(volParms) \
110 (((volParms)->vMAttrib & (1L << bHasCopyFile)) != 0)
111
112 // The CopyParams data structure holds the copy buffer used
113 // when copying the forks over, as well as special case
114 // info on the destination
115struct CopyParams {
116 UTCDateTime magicBusyCreateDate;
117 void *copyBuffer;
118 ByteCount copyBufferSize;
119 Boolean copyingToDropFolder;
120 Boolean copyingToLocalVolume;
121};
122typedef struct CopyParams CopyParams;
123
124 // The FilterParams data structure holds the date and info
125 // that the caller wants passed into the Filter Proc, as well
126 // as the Filter Proc Pointer itself
127struct FilterParams {
128 FSCatalogInfoBitmap whichInfo;
129 CopyObjectFilterProcPtr filterProcPtr;
130 FSSpec fileSpec;
131 FSSpec *fileSpecPtr;
132 HFSUniStr255 fileName;
133 HFSUniStr255 *fileNamePtr;
134 void *yourDataPtr;
135};
136typedef struct FilterParams FilterParams;
137
138 // The ForkTracker data structure holds information about a specific fork,
139 // specifically the name and the refnum. We use this to build a list of
140 // all the forks before we start copying them. We need to do this because,
141 // if we're copying into a drop folder, we must open all the forks before
142 // we start copying data into any of them.
143 // Plus it's a convenient way to keep track of all the forks...
144struct ForkTracker {
145 HFSUniStr255 forkName;
146 SInt64 forkSize;
147 SInt16 forkDestRefNum;
148};
149typedef struct ForkTracker ForkTracker;
150typedef ForkTracker *ForkTrackerPtr;
151
152 // The FSCopyObjectGlobals data structure holds information needed to do
153 // the recursive copy of a directory.
154struct FSCopyObjectGlobals
155{
156 FSCatalogInfo catalogInfo;
157 FSRef ref; /* FSRef to the source file/folder*/
158 FSRef destRef; /* FSRef to the destination directory */
159 CopyParams *copyParams; /* pointer to info needed to do the copy */
160 FilterParams *filterParams; /* pointer to info needed for the optional filter proc */
161 ItemCount maxLevels; /* maximum levels to iterate through */
162 ItemCount currentLevel; /* the current level FSCopyFolderLevel is on */
163 Boolean quitFlag; /* set to true if filter wants to kill interation */
164 Boolean containerChanged; /* temporary - set to true if the current container changed during iteration */
165 OSErr result; /* result */
166 ItemCount actualObjects; /* number of objects returned */
167};
168typedef struct FSCopyObjectGlobals FSCopyObjectGlobals;
169
170 // The FSDeleteObjectGlobals data structure holds information needed to
171 // recursively delete a directory
172struct FSDeleteObjectGlobals
173{
174 FSCatalogInfo catalogInfo; /* FSCatalogInfo */
175 ItemCount actualObjects; /* number of objects returned */
176 OSErr result; /* result */
177};
178typedef struct FSDeleteObjectGlobals FSDeleteObjectGlobals;
179
180#pragma mark ----- Local Prototypes -----
181
182static OSErr FSCopyFile( const FSRef *source,
183 const FSRef *destDir,
184 const HFSUniStr255 *destName, /* can be NULL (no rename during copy) */
185 CopyParams *copyParams,
186 FilterParams *filterParams,
187 FSRef *newFile); /* can be NULL */
188
189static OSErr CopyFile( const FSRef *source,
190 FSCatalogInfo *sourceCatInfo,
191 const FSRef *destDir,
192 const HFSUniStr255 *destName,
193 CopyParams *copyParams,
194 FSRef *newRef); /* can be NULL */
195
196static OSErr FSUsePBHCopyFile( const FSRef *srcFileRef,
197 const FSRef *dstDirectoryRef,
198 UniCharCount nameLength,
199 const UniChar *copyName, /* can be NULL (no rename during copy) */
200 TextEncoding textEncodingHint,
201 FSRef *newRef); /* can be NULL */
202
203static OSErr DoCopyFile( const FSRef *source,
204 FSCatalogInfo *sourceCatInfo,
205 const FSRef *destDir,
206 const HFSUniStr255 *destName,
207 CopyParams *params,
208 FSRef *newRef); /* can be NULL */
209
210static OSErr FSCopyFolder( const FSRef *source,
211 const FSRef *destDir,
212 const HFSUniStr255 *destName, /* can be NULL (no rename during copy) */
213 CopyParams* copyParams,
214 FilterParams *filterParams,
215 ItemCount maxLevels,
216 FSRef* newDir); /* can be NULL */
217
218static OSErr FSCopyFolderLevel( FSCopyObjectGlobals *theGlobals, const HFSUniStr255 *destName );
219
220static OSErr CheckForDestInsideSource( const FSRef *source,
221 const FSRef *destDir);
222
223static OSErr CopyItemsForks( const FSRef *source,
224 const FSRef *dest,
225 CopyParams *params);
226
227static OSErr OpenAllForks( const FSRef *dest,
228 const ForkTrackerPtr dataFork,
229 const ForkTrackerPtr rsrcFork,
230 ForkTrackerPtr otherForks,
231 ItemCount otherForksCount);
232
233static OSErr CopyFork( const FSRef *source,
234 const FSRef *dest,
235 const ForkTrackerPtr sourceFork,
236 const CopyParams *params);
237
238static OSErr CloseAllForks( SInt16 dataRefNum,
239 SInt16 rsrcRefNum,
240 ForkTrackerPtr otherForks,
241 ItemCount otherForksCount);
242
243static OSErr CalculateForksToCopy( const FSRef *source,
244 const ForkTrackerPtr dataFork,
245 const ForkTrackerPtr rsrcFork,
246 ForkTrackerPtr *otherForksParam,
247 ItemCount *otherForksCountParam);
248
249static OSErr CalculateBufferSize( const FSRef *source,
250 const FSRef *destDir,
251 ByteCount * bufferSize);
252
253static ByteCount BufferSizeForThisVolume(FSVolumeRefNum vRefNum);
254
255static ByteCount BufferSizeForThisVolumeSpeed(UInt32 volumeBytesPerSecond);
256
257static OSErr IsDropBox( const FSRef* source,
258 Boolean *isDropBox);
259
260static OSErr GetMagicBusyCreationDate( UTCDateTime *date );
261
262static Boolean CompareHFSUniStr255(const HFSUniStr255 *lhs,
263 const HFSUniStr255 *rhs);
264
265static OSErr FSGetVRefNum( const FSRef *ref,
266 FSVolumeRefNum *vRefNum);
267
268static OSErr FSGetVolParms( FSVolumeRefNum volRefNum,
269 UInt32 bufferSize,
270 GetVolParmsInfoBuffer *volParmsInfo,
271 UInt32 *actualInfoSize); /* Can Be NULL */
272
273static OSErr UnicodeNameGetHFSName( UniCharCount nameLength,
274 const UniChar *name,
275 TextEncoding textEncodingHint,
276 Boolean isVolumeName,
277 Str31 hfsName);
278
279static OSErr FSMakeFSRef( FSVolumeRefNum volRefNum,
280 SInt32 dirID,
281 ConstStr255Param name,
282 FSRef *ref);
283
284static OSErr FSDeleteFolder( const FSRef *container );
285
286static void FSDeleteFolderLevel( const FSRef *container,
287 FSDeleteObjectGlobals *theGlobals);
288
289/*****************************************************************************/
290/*****************************************************************************/
291/*****************************************************************************/
292
293#pragma mark ----- Copy Objects -----
294
295 // This routine acts as the top level of the copy engine. It exists
296 // to a) present a nicer API than the various recursive routines, and
297 // b) minimise the local variables in the recursive routines.
298OSErr FSCopyObject( const FSRef *source,
299 const FSRef *destDir,
300 UniCharCount nameLength,
301 const UniChar *copyName, // can be NULL (no rename during copy)
302 ItemCount maxLevels,
303 FSCatalogInfoBitmap whichInfo,
304 Boolean wantFSSpec,
305 Boolean wantName,
306 CopyObjectFilterProcPtr filterProcPtr, // can be NULL
307 void *yourDataPtr, // can be NULL
308 FSRef *newObject) // can be NULL
309{
310 CopyParams copyParams;
311 FilterParams filterParams;
312 HFSUniStr255 destName;
313 HFSUniStr255 *destNamePtr;
314 Boolean isDirectory;
315 OSErr osErr = ( source != NULL && destDir != NULL ) ? noErr : paramErr;
316
317 if (copyName)
318 {
319 if (nameLength <= 255)
320 {
321 BlockMoveData(copyName, destName.unicode, nameLength * sizeof(UniChar));
322 destName.length = nameLength;
323 destNamePtr = &destName;
324 }
325 else
326 osErr = paramErr;
327 }
328 else
329 destNamePtr = NULL;
330
331 // we want the settable info no matter what the user asked for
332 filterParams.whichInfo = whichInfo | kFSCatInfoSettableInfo;
333 filterParams.filterProcPtr = filterProcPtr;
334 filterParams.fileSpecPtr = ( wantFSSpec ) ? &filterParams.fileSpec : NULL;
335 filterParams.fileNamePtr = ( wantName ) ? &filterParams.fileName : NULL;
336 filterParams.yourDataPtr = yourDataPtr;
337
338 // Calculate the optimal buffer size to copy the forks over
339 // and create the buffer
340 if( osErr == noErr )
341 osErr = CalculateBufferSize( source, destDir, &copyParams.copyBufferSize);
342
343 if( osErr == noErr )
344 {
345 copyParams.copyBuffer = NewPtr( copyParams.copyBufferSize );
346 if( copyParams.copyBuffer == NULL )
347 osErr = memFullErr;
348 }
349
350 if( osErr == noErr )
351 osErr = GetMagicBusyCreationDate( &copyParams.magicBusyCreateDate );
352
353 if( osErr == noErr ) // figure out if source is a file or folder
354 { // if it is on a local volume,
355 // if destination is a drop box
356 GetVolParmsInfoBuffer volParms;
357 FSCatalogInfo tmpCatInfo;
358 FSVolumeRefNum destVRefNum;
359
360 // to figure out if the souce is a folder or directory
361 osErr = FSGetCatalogInfo(source, kFSCatInfoNodeFlags, &tmpCatInfo, NULL, NULL, NULL);
362 if( osErr == noErr )
363 {
364 isDirectory = ((tmpCatInfo.nodeFlags & kFSNodeIsDirectoryMask) != 0);
365 // are we copying to a drop folder?
366 osErr = IsDropBox( destDir, &copyParams.copyingToDropFolder );
367 }
368 if( osErr == noErr )
369 osErr = FSGetVRefNum(destDir, &destVRefNum);
370 if( osErr == noErr )
371 osErr = FSGetVolParms( destVRefNum, sizeof(volParms), &volParms, NULL );
372 if( osErr == noErr ) // volParms.vMServerAdr is non-zero for remote volumes
373 copyParams.copyingToLocalVolume = (volParms.vMServerAdr == 0);
374 }
375
376 // now copy the file/folder...
377 if( osErr == noErr )
378 { // is it a folder?
379 if ( isDirectory )
380 { // yes
381 osErr = CheckForDestInsideSource(source, destDir);
382 if( osErr == noErr )
383 osErr = FSCopyFolder( source, destDir, destNamePtr, &copyParams, &filterParams, maxLevels, newObject );
384 }
385 else // no
386 osErr = FSCopyFile(source, destDir, destNamePtr, &copyParams, &filterParams, newObject);
387 }
388
389 // Clean up for space and safety... Who me?
390 if( copyParams.copyBuffer != NULL )
391 DisposePtr((char*)copyParams.copyBuffer);
392
393 mycheck_noerr( osErr ); // put up debug assert in debug builds
394
395 return osErr;
396}
397
398/*****************************************************************************/
399
400#pragma mark ----- Copy Files -----
401
402OSErr FSCopyFile( const FSRef *source,
403 const FSRef *destDir,
404 const HFSUniStr255 *destName,
405 CopyParams *copyParams,
406 FilterParams *filterParams,
407 FSRef *newFile)
408{
409 FSCatalogInfo sourceCatInfo;
410 FSRef tmpRef;
411 OSErr osErr = ( source != NULL && destDir != NULL &&
412 copyParams != NULL && filterParams != NULL ) ? noErr : paramErr;
413
414 // get needed info about the source file
415 if ( osErr == noErr )
416 {
417 if (destName)
418 {
419 osErr = FSGetCatalogInfo(source, filterParams->whichInfo, &sourceCatInfo, NULL, NULL, NULL);
420 filterParams->fileName = *destName;
421 }
422 else
423 osErr = FSGetCatalogInfo(source, filterParams->whichInfo, &sourceCatInfo, &filterParams->fileName, NULL, NULL);
424 }
425 if( osErr == noErr )
426 osErr = CopyFile(source, &sourceCatInfo, destDir, &filterParams->fileName, copyParams, &tmpRef);
427
428 // Call the IterateFilterProc _after_ the new file was created
429 // even if an error occured
430 if( filterParams->filterProcPtr != NULL )
431 {
432 (void) CallCopyObjectFilterProc(filterParams->filterProcPtr, false, 0, osErr, &sourceCatInfo,
433 &tmpRef, filterParams->fileSpecPtr,
434 filterParams->fileNamePtr, filterParams->yourDataPtr);
435 }
436
437 if( osErr == noErr && newFile != NULL )
438 *newFile = tmpRef;
439
440 mycheck_noerr(osErr); // put up debug assert in debug builds
441
442 return osErr;
443}
444
445/*****************************************************************************/
446
447OSErr CopyFile( const FSRef *source,
448 FSCatalogInfo *sourceCatInfo,
449 const FSRef *destDir,
450 ConstHFSUniStr255Param destName,
451 CopyParams *params,
452 FSRef* newFile)
453{
454 OSErr osErr = paramErr;
455
456 // Clear the "inited" bit so that the Finder positions the icon for us.
457 ((FInfo *)(sourceCatInfo->finderInfo))->fdFlags &= ~kHasBeenInited;
458
459 // if the destination is on a remote volume, try to use PBHCopyFile
460 if( params->copyingToLocalVolume == 0 )
461 osErr = FSUsePBHCopyFile( source, destDir, 0, NULL, kTextEncodingUnknown, newFile );
462
463 // if PBHCopyFile didn't work or not supported,
464 if( osErr != noErr ) // then try old school file transfer
465 osErr = DoCopyFile( source, sourceCatInfo, destDir, destName, params, newFile );
466
467 mycheck_noerr(osErr); // put up debug assert in debug builds
468
469 return osErr;
470}
471
472/*****************************************************************************/
473
474OSErr FSUsePBHCopyFile( const FSRef *srcFileRef,
475 const FSRef *dstDirectoryRef,
476 UniCharCount nameLength,
477 const UniChar *copyName, /* can be NULL (no rename during copy) */
478 TextEncoding textEncodingHint,
479 FSRef *newRef) /* can be NULL */
480{
481 FSSpec srcFileSpec;
482 FSCatalogInfo catalogInfo;
483 GetVolParmsInfoBuffer volParmsInfo;
484 HParamBlockRec pb;
485 Str31 hfsName;
486 OSErr osErr;
487
488 // get source FSSpec from source FSRef
489 osErr = FSGetCatalogInfo(srcFileRef, kFSCatInfoNone, NULL, NULL, &srcFileSpec, NULL);
490 if( osErr == noErr ) // Make sure the volume supports CopyFile
491 osErr = FSGetVolParms( srcFileSpec.vRefNum, sizeof(GetVolParmsInfoBuffer), &volParmsInfo, NULL);
492 if( osErr == noErr )
493 osErr = VolHasCopyFile(&volParmsInfo) ? noErr : paramErr;
494 if( osErr == noErr ) // get the destination vRefNum and dirID
495 osErr = FSGetCatalogInfo(dstDirectoryRef, kFSCatInfoVolume | kFSCatInfoNodeID, &catalogInfo, NULL, NULL, NULL);
496 if( osErr == noErr ) // gather all the info needed
497 {
498 pb.copyParam.ioVRefNum = srcFileSpec.vRefNum;
499 pb.copyParam.ioDirID = srcFileSpec.parID;
500 pb.copyParam.ioNamePtr = (StringPtr)srcFileSpec.name;
501 pb.copyParam.ioDstVRefNum = catalogInfo.volume;
502 pb.copyParam.ioNewDirID = (long)catalogInfo.nodeID;
503 pb.copyParam.ioNewName = NULL;
504 if( copyName != NULL )
505 osErr = UnicodeNameGetHFSName(nameLength, copyName, textEncodingHint, false, hfsName);
506 pb.copyParam.ioCopyName = ( copyName != NULL && osErr == noErr ) ? hfsName : NULL;
507 }
508 if( osErr == noErr ) // tell the server to copy the object
509 osErr = PBHCopyFileSync(&pb);
510
511 if( osErr == noErr && newRef != NULL )
512 {
513 myverify_noerr(FSMakeFSRef(pb.copyParam.ioDstVRefNum, pb.copyParam.ioNewDirID,
514 pb.copyParam.ioCopyName, newRef));
515 }
516
517 if( osErr != paramErr ) // returning paramErr is ok, it means PBHCopyFileSync was not supported
518 mycheck_noerr(osErr); // put up debug assert in debug builds
519
520 return osErr;
521}
522
523/*****************************************************************************/
524
525 // Copies a file referenced by source to the directory referenced by
526 // destDir. destName is the name the file should be given in the
527 // destination directory. sourceCatInfo is the catalogue info of
528 // the file, which is passed in as an optimization (we could get it
529 // by doing a FSGetCatalogInfo but the caller has already done that
530 // so we might as well take advantage of that).
531 //
532OSErr DoCopyFile( const FSRef *source,
533 FSCatalogInfo *sourceCatInfo,
534 const FSRef *destDir,
535 ConstHFSUniStr255Param destName,
536 CopyParams *params,
537 FSRef *newRef)
538{
539 FSRef dest;
540 FSPermissionInfo originalPermissions;
541 UTCDateTime originalCreateDate;
542 OSType originalFileType;
543 UInt16 originalNodeFlags;
544 OSErr osErr;
545
546 // If we're copying to a drop folder, we won't be able to reset this
547 // information once the copy is done, so we don't mess it up in
548 // the first place. We still clear the locked bit though; items dropped
549 // into a drop folder always become unlocked.
550 if (!params->copyingToDropFolder)
551 {
552 // Remember to clear the file's type, so the Finder doesn't
553 // look at the file until we're done.
554 originalFileType = ((FInfo *) &sourceCatInfo->finderInfo)->fdType;
555 ((FInfo *) &sourceCatInfo->finderInfo)->fdType = kFirstMagicBusyFiletype;
556
557 // Remember and clear the file's locked status, so that we can
558 // actually write the forks we're about to create.
559 originalNodeFlags = sourceCatInfo->nodeFlags;
560
561 // Set the file's creation date to kMagicBusyCreationDate,
562 // remembering the old value for restoration later.
563 originalCreateDate = sourceCatInfo->createDate;
564 sourceCatInfo->createDate = params->magicBusyCreateDate;
565 }
566 sourceCatInfo->nodeFlags &= ~kFSNodeLockedMask;
567
568 // we need to have user level read/write/execute access to the file we are going to create
569 // otherwise FSCreateFileUnicode will return -5000 (afpAccessDenied),
570 // and the FSRef returned will be invalid, yet the file is created (size 0k)... bug?
571 originalPermissions = *((FSPermissionInfo*)sourceCatInfo->permissions);
572 ((FSPermissionInfo*)sourceCatInfo->permissions)->mode |= kRWXUserAccessMask;
573
574 // Classic only supports 9.1 and higher, so we don't have to worry about 2397324
575 osErr = FSCreateFileUnicode(destDir, destName->length, destName->unicode, kFSCatInfoSettableInfo, sourceCatInfo, &dest, NULL);
576 if( osErr == noErr ) // Copy the forks over to the new file
577 osErr = CopyItemsForks(source, &dest, params);
578
579 // Restore the original file type, creation and modification dates,
580 // locked status and permissions.
581 // This is one of the places where we need to handle drop
582 // folders as a special case because this FSSetCatalogInfo will fail for
583 // an item in a drop folder, so we don't even attempt it.
584 if (osErr == noErr && !params->copyingToDropFolder)
585 {
586 ((FInfo *) &sourceCatInfo->finderInfo)->fdType = originalFileType;
587 sourceCatInfo->createDate = originalCreateDate;
588 sourceCatInfo->nodeFlags = originalNodeFlags;
589 *((FSPermissionInfo*)sourceCatInfo->permissions) = originalPermissions;
590
591 osErr = FSSetCatalogInfo(&dest, kFSCatInfoSettableInfo, sourceCatInfo);
592 }
593
594 // If we created the file and the copy failed, try to clean up by
595 // deleting the file we created. We do this because, while it's
596 // possible for the copy to fail halfway through and the File Manager
597 // doesn't really clean up that well, we *really* don't wan't
598 // any half-created files being left around.
599 // if the file already existed, we don't want to delete it
600 //
601 // Note that there are cases where the assert can fire which are not
602 // errors (for example, if the destination is in a drop folder) but
603 // I'll leave it in anyway because I'm interested in discovering those
604 // cases. Note that, if this fires and we're running MP, current versions
605 // of MacsBug won't catch the exception and the MP task will terminate
606 // with a kMPTaskAbortedErr error.
607 if (osErr != noErr && osErr != dupFNErr )
608 myverify_noerr( FSDeleteObjects(&dest) );
609 else if( newRef != NULL ) // if everything was fine, then return the new file
610 *newRef = dest;
611
612 mycheck_noerr(osErr); // put up debug assert in debug builds
613
614 return osErr;
615}
616
617/*****************************************************************************/
618
619#pragma mark ----- Copy Folders -----
620
621OSErr FSCopyFolder( const FSRef *source, const FSRef *destDir, const HFSUniStr255 *destName,
622 CopyParams* copyParams, FilterParams *filterParams, ItemCount maxLevels, FSRef* newDir)
623{
624 FSCopyObjectGlobals theGlobals;
625
626 theGlobals.ref = *source;
627 theGlobals.destRef = *destDir;
628 theGlobals.copyParams = copyParams;
629 theGlobals.filterParams = filterParams;
630 theGlobals.maxLevels = maxLevels;
631 theGlobals.currentLevel = 0;
632 theGlobals.quitFlag = false;
633 theGlobals.containerChanged = false;
634 theGlobals.result = ( source != NULL && destDir != NULL &&
635 copyParams != NULL && filterParams != NULL ) ?
636 noErr : paramErr;
637 theGlobals.actualObjects = 0;
638
639 // here we go into recursion land...
640 if( theGlobals.result == noErr )
641 theGlobals.result = FSCopyFolderLevel(&theGlobals, destName);
642
643 if( theGlobals.result == noErr && newDir != NULL)
644 *newDir = theGlobals.ref;
645
646 // Call the IterateFilterProc _after_ the new folder is created
647 // even if we failed...
648 if( filterParams->filterProcPtr != NULL )
649 {
650 (void) CallCopyObjectFilterProc(filterParams->filterProcPtr, theGlobals.containerChanged,
651 theGlobals.currentLevel, theGlobals.result, &theGlobals.catalogInfo,
652 &theGlobals.ref, filterParams->fileSpecPtr,
653 filterParams->fileNamePtr, filterParams->yourDataPtr);
654 }
655
656 mycheck_noerr(theGlobals.result); // put up debug assert in debug builds
657
658 return ( theGlobals.result );
659}
660
661/*****************************************************************************/
662
663OSErr FSCopyFolderLevel( FSCopyObjectGlobals *theGlobals, const HFSUniStr255 *destName )
664{
665 // If maxLevels is zero, we aren't checking levels
666 // If currentLevel < maxLevels, look at this level
667 if ( (theGlobals->maxLevels == 0) ||
668 (theGlobals->currentLevel < theGlobals->maxLevels) )
669 {
670 FSRef newDirRef;
671 UTCDateTime originalCreateDate;
672 FSPermissionInfo originalPermissions;
673 FSIterator iterator;
674 FilterParams *filterPtr = theGlobals->filterParams;
675
676 // get the info we need on the source file...
677 theGlobals->result = FSGetCatalogInfo( &theGlobals->ref, filterPtr->whichInfo,
678 &theGlobals->catalogInfo, &filterPtr->fileName,
679 NULL, NULL);
680
681 if (theGlobals->currentLevel == 0 && destName)
682 filterPtr->fileName = *destName;
683
684 // Clear the "inited" bit so that the Finder positions the icon for us.
685 ((FInfo *)(theGlobals->catalogInfo.finderInfo))->fdFlags &= ~kHasBeenInited;
686
687 // Set the folder's creation date to kMagicBusyCreationDate
688 // so that the Finder doesn't mess with the folder while
689 // it's copying. We remember the old value for restoration
690 // later. We only do this if we're not copying to a drop
691 // folder, because if we are copying to a drop folder we don't
692 // have the opportunity to reset the information at the end of
693 // this routine.
694 if ( theGlobals->result == noErr && !theGlobals->copyParams->copyingToDropFolder)
695 {
696 originalCreateDate = theGlobals->catalogInfo.createDate;
697 theGlobals->catalogInfo.createDate = theGlobals->copyParams->magicBusyCreateDate;
698 }
699
700 // we need to have user level read/write/execute access to the folder we are going to create,
701 // otherwise FSCreateDirectoryUnicode will return -5000 (afpAccessDenied),
702 // and the FSRef returned will be invalid, yet the folder is created... bug?
703 originalPermissions = *((FSPermissionInfo*)theGlobals->catalogInfo.permissions);
704 ((FSPermissionInfo*)theGlobals->catalogInfo.permissions)->mode |= kRWXUserAccessMask;
705
706 // create the new directory
707 if( theGlobals->result == noErr )
708 {
709 theGlobals->result = FSCreateDirectoryUnicode( &theGlobals->destRef, filterPtr->fileName.length,
710 filterPtr->fileName.unicode, kFSCatInfoSettableInfo,
711 &theGlobals->catalogInfo, &newDirRef,
712 &filterPtr->fileSpec, NULL);
713 }
714
715 ++theGlobals->currentLevel; // setup to go to the next level
716
717 // With the new APIs, folders can have forks as well as files. Before
718 // we start copying items in the folder, we must copy over the forks
719 if( theGlobals->result == noErr )
720 theGlobals->result = CopyItemsForks(&theGlobals->ref, &newDirRef, theGlobals->copyParams);
721 if( theGlobals->result == noErr ) // Open FSIterator for flat access to theGlobals->ref
722 theGlobals->result = FSOpenIterator(&theGlobals->ref, kFSIterateFlat, &iterator);
723 if( theGlobals->result == noErr )
724 {
725 OSErr osErr;
726
727 // Call FSGetCatalogInfoBulk in loop to get all items in the container
728 do
729 {
730 theGlobals->result = FSGetCatalogInfoBulk( iterator, 1, &theGlobals->actualObjects,
731 &theGlobals->containerChanged, filterPtr->whichInfo,
732 &theGlobals->catalogInfo, &theGlobals->ref,
733 filterPtr->fileSpecPtr, &filterPtr->fileName);
734 if ( ( (theGlobals->result == noErr) || (theGlobals->result == errFSNoMoreItems) ) &&
735 ( theGlobals->actualObjects != 0 ) )
736 {
737 // Any errors in here will be passed to the filter proc
738 // we don't want an error in here to prematurely
739 // cancel the recursive copy, leaving a half filled directory
740
741 // is the new object a directory?
742 if ( (theGlobals->catalogInfo.nodeFlags & kFSNodeIsDirectoryMask) != 0 )
743 { // yes
744 theGlobals->destRef = newDirRef;
745 osErr = FSCopyFolderLevel(theGlobals, NULL);
746 theGlobals->result = noErr; // don't want one silly mistake to kill the party...
747 }
748 else // no
749 {
750 osErr = CopyFile( &theGlobals->ref, &theGlobals->catalogInfo,
751 &newDirRef, &filterPtr->fileName,
752 theGlobals->copyParams, &theGlobals->ref);
753 }
754
755 // Call the filter proc _after_ the file/folder was created completly
756 if( filterPtr->filterProcPtr != NULL && !theGlobals->quitFlag )
757 {
758 theGlobals->quitFlag = CallCopyObjectFilterProc(filterPtr->filterProcPtr,
759 theGlobals->containerChanged, theGlobals->currentLevel,
760 osErr, &theGlobals->catalogInfo,
761 &theGlobals->ref, filterPtr->fileSpecPtr,
762 filterPtr->fileNamePtr, filterPtr->yourDataPtr);
763 }
764 }
765 } while ( ( theGlobals->result == noErr ) && ( !theGlobals->quitFlag ) );
766
767 // Close the FSIterator (closing an open iterator should never fail)
768 (void) FSCloseIterator(iterator);
769 }
770
771 // errFSNoMoreItems is OK - it only means we hit the end of this level
772 // afpAccessDenied is OK, too - it only means we cannot see inside a directory
773 if ( (theGlobals->result == errFSNoMoreItems) || (theGlobals->result == afpAccessDenied) )
774 theGlobals->result = noErr;
775
776 // store away the name, and an FSSpec and FSRef of the new directory
777 // for use in filter proc one level up...
778 if( theGlobals->result == noErr )
779 {
780 theGlobals->ref = newDirRef;
781 theGlobals->result = FSGetCatalogInfo(&newDirRef, kFSCatInfoNone, NULL,
782 &filterPtr->fileName, &filterPtr->fileSpec, NULL);
783 }
784
785 // Return to previous level as we leave
786 --theGlobals->currentLevel;
787
788 // Reset the modification dates and permissions, except when copying to a drop folder
789 // where this won't work.
790 if (theGlobals->result == noErr && ! theGlobals->copyParams->copyingToDropFolder)
791 {
792 theGlobals->catalogInfo.createDate = originalCreateDate;
793 *((FSPermissionInfo*)theGlobals->catalogInfo.permissions) = originalPermissions;
794 theGlobals->result = FSSetCatalogInfo(&newDirRef, kFSCatInfoCreateDate
795 | kFSCatInfoAttrMod
796 | kFSCatInfoContentMod
797 | kFSCatInfoPermissions, &theGlobals->catalogInfo);
798 }
799
800 // If we created the folder and the copy failed, try to clean up by
801 // deleting the folder we created. We do this because, while it's
802 // possible for the copy to fail halfway through and the File Manager
803 // doesn't really clean up that well, we *really* don't wan't any
804 // half-created files/folders being left around.
805 // if the file already existed, we don't want to delete it
806 if( theGlobals->result != noErr && theGlobals->result != dupFNErr )
807 myverify_noerr( FSDeleteObjects(&newDirRef) );
808 }
809
810 mycheck_noerr( theGlobals->result ); // put up debug assert in debug builds
811
812 return theGlobals->result;
813}
814
815/*****************************************************************************/
816
817 // Determines whether the destination directory is equal to the source
818 // item, or whether it's nested inside the source item. Returns a
819 // errFSDestInsideSource if that's the case. We do this to prevent
820 // endless recursion while copying.
821 //
822OSErr CheckForDestInsideSource(const FSRef *source, const FSRef *destDir)
823{
824 FSRef thisDir = *destDir;
825 FSCatalogInfo thisDirInfo;
826 Boolean done = false;
827 OSErr osErr;
828
829 do
830 {
831 osErr = FSCompareFSRefs(source, &thisDir);
832 if (osErr == noErr)
833 osErr = errFSDestInsideSource;
834 else if (osErr == diffVolErr)
835 {
836 osErr = noErr;
837 done = true;
838 }
839 else if (osErr == errFSRefsDifferent)
840 {
841 // This is somewhat tricky. We can ask for the parent of thisDir
842 // by setting the parentRef parameter to FSGetCatalogInfo but, if
843 // thisDir is the volume's FSRef, this will give us back junk.
844 // So we also ask for the parent's dir ID to be returned in the
845 // FSCatalogInfo record, and then check that against the node
846 // ID of the root's parent (ie 1). If we match that, we've made
847 // it to the top of the hierarchy without hitting source, so
848 // we leave with no error.
849
850 osErr = FSGetCatalogInfo(&thisDir, kFSCatInfoParentDirID, &thisDirInfo, NULL, NULL, &thisDir);
851 if( ( osErr == noErr ) && ( thisDirInfo.parentDirID == fsRtParID ) )
852 done = true;
853 }
854 } while ( osErr == noErr && ! done );
855
856 mycheck_noerr( osErr ); // put up debug assert in debug builds
857
858 return osErr;
859}
860
861/*****************************************************************************/
862
863#pragma mark ----- Copy Forks -----
864
865OSErr CopyItemsForks(const FSRef *source, const FSRef *dest, CopyParams *params)
866{
867 ForkTracker dataFork,
868 rsrcFork;
869 ForkTrackerPtr otherForks;
870 ItemCount otherForksCount,
871 thisForkIndex;
872 OSErr osErr;
873
874 dataFork.forkDestRefNum = 0;
875 rsrcFork.forkDestRefNum = 0;
876 otherForks = NULL;
877 otherForksCount = 0;
878
879 // Get the constant names for the resource and data fork, which
880 // we're going to need inside the copy engine.
881 osErr = FSGetDataForkName(&dataFork.forkName);
882 if( osErr == noErr )
883 osErr = FSGetResourceForkName(&rsrcFork.forkName);
884 if( osErr == noErr ) // First determine the list of forks that the source has.
885 osErr = CalculateForksToCopy(source, &dataFork, &rsrcFork, &otherForks, &otherForksCount);
886 if (osErr == noErr)
887 {
888 // If we're copying into a drop folder, open up all of those forks.
889 // We have to do this because, once we've starting writing to a fork
890 // in a drop folder, we can't open any more forks.
891 //
892 // We only do this if we're copying into a drop folder in order
893 // to conserve FCBs in the more common, non-drop folder case.
894
895 if (params->copyingToDropFolder)
896 osErr = OpenAllForks(dest, &dataFork, &rsrcFork, otherForks, otherForksCount);
897
898 // Copy each fork.
899 if (osErr == noErr && (dataFork.forkSize != 0)) // copy data fork
900 osErr = CopyFork(source, dest, &dataFork, params);
901 if (osErr == noErr && (rsrcFork.forkSize != 0)) // copy resource fork
902 osErr = CopyFork(source, dest, &rsrcFork, params);
903 if (osErr == noErr) { // copy other forks
904 for (thisForkIndex = 0; thisForkIndex < otherForksCount && osErr == noErr; thisForkIndex++)
905 osErr = CopyFork(source,dest, &otherForks[thisForkIndex], params);
906 }
907
908 // Close any forks that might be left open. Note that we have to call
909 // this regardless of an error. Also note that this only closes forks
910 // that were opened by OpenAllForks. If we're not copying into a drop
911 // folder, the forks are opened and closed by CopyFork.
912 {
913 OSErr osErr2 = CloseAllForks(dataFork.forkDestRefNum, rsrcFork.forkDestRefNum, otherForks, otherForksCount);
914 mycheck_noerr(osErr2);
915 if (osErr == noErr)
916 osErr = osErr2;
917 }
918 }
919
920 // Clean up.
921 if (otherForks != NULL)
922 DisposePtr((char*)otherForks);
923
924 mycheck_noerr( osErr ); // put up debug assert in debug builds
925
926 return osErr;
927}
928
929/*****************************************************************************/
930
931 // Open all the forks of the file. We need to do this when we're copying
932 // into a drop folder, where you must open all the forks before starting
933 // to write to any of them.
934 //
935 // IMPORTANT: If it fails, this routine won't close forks that opened successfully.
936 // You must call CloseAllForks regardless of whether this routine returns an error.
937OSErr OpenAllForks( const FSRef *dest,
938 const ForkTrackerPtr dataFork,
939 const ForkTrackerPtr rsrcFork,
940 ForkTrackerPtr otherForks,
941 ItemCount otherForksCount)
942{
943 ItemCount thisForkIndex;
944 OSErr osErr = noErr;
945
946 // Open the resource and data forks as a special case, if they exist in this object
947 if (dataFork->forkSize != 0) // Data fork never needs to be created, so I don't have to FSCreateFork it here.
948 osErr = FSOpenFork(dest, dataFork->forkName.length, dataFork->forkName.unicode, fsWrPerm, &dataFork->forkDestRefNum);
949 if (osErr == noErr && rsrcFork->forkSize != 0) // Resource fork never needs to be created, so I don't have to FSCreateFork it here.
950 osErr = FSOpenFork(dest, rsrcFork->forkName.length, rsrcFork->forkName.unicode, fsWrPerm, &rsrcFork->forkDestRefNum);
951
952 if (osErr == noErr && otherForks != NULL && otherForksCount > 0) // Open the other forks.
953 {
954 for (thisForkIndex = 0; thisForkIndex < otherForksCount && osErr == noErr; thisForkIndex++)
955 {
956 // Create the fork. Swallow afpAccessDenied because this operation
957 // causes the external file system compatibility shim in Mac OS 9 to
958 // generate a GetCatInfo request to the AppleShare external file system,
959 // which in turn causes an AFP GetFileDirParms request on the wire,
960 // which the AFP server bounces with afpAccessDenied because the file
961 // is in a drop folder. As there's no native support for non-classic
962 // forks in current AFP, there's no way I can decide how I should
963 // handle this in a non-test case. So I just swallow the error and
964 // hope that when native AFP support arrives, the right thing will happen.
965 osErr = FSCreateFork(dest, otherForks[thisForkIndex].forkName.length, otherForks[thisForkIndex].forkName.unicode);
966 if (osErr == noErr || osErr == afpAccessDenied)
967 osErr = noErr;
968
969 // Previously I avoided opening up the fork if the fork if the
970 // length was empty, but that confused CopyFork into thinking
971 // this wasn't a drop folder copy, so I decided to simply avoid
972 // this trivial optimization. In drop folders, we always open
973 // all forks.
974 if (osErr == noErr)
975 osErr = FSOpenFork(dest, otherForks[thisForkIndex].forkName.length, otherForks[thisForkIndex].forkName.unicode, fsWrPerm, &otherForks[thisForkIndex].forkDestRefNum);
976 }
977 }
978
979 mycheck_noerr( osErr ); // put up debug assert in debug builds
980
981 return osErr;
982}
983
984/*****************************************************************************/
985
986 // Copies the fork whose name is forkName from source to dest.
987 // A refnum for the destination fork may be supplied in forkDestRefNum.
988 // If forkDestRefNum is 0, we must open the destination fork ourselves,
989 // otherwise it has been opened for us and we shouldn't close it.
990OSErr CopyFork( const FSRef *source, const FSRef *dest, const ForkTrackerPtr sourceFork, const CopyParams *params)
991{
992 UInt64 bytesRemaining;
993 UInt64 bytesToReadThisTime;
994 UInt64 bytesToWriteThisTime;
995 SInt16 sourceRef;
996 SInt16 destRef;
997 OSErr osErr = noErr;
998 OSErr osErr2 = noErr;
999
1000 // If we haven't been passed in a sourceFork->forkDestRefNum (which basically
1001 // means we're copying into a non-drop folder), create the destination
1002 // fork. We have to do this regardless of whether sourceFork->forkSize is
1003 // 0, because we want to preserve empty forks.
1004 if (sourceFork->forkDestRefNum == 0)
1005 {
1006 osErr = FSCreateFork(dest, sourceFork->forkName.length, sourceFork->forkName.unicode);
1007
1008 // Mac OS 9.0 has a bug (in the AppleShare external file system,
1009 // I think) [2410374] that causes FSCreateFork to return an errFSForkExists
1010 // error even though the fork is empty. The following code swallows
1011 // the error (which is harmless) in that case.
1012 if (osErr == errFSForkExists && !params->copyingToLocalVolume)
1013 osErr = noErr;
1014 }
1015
1016 // The remainder of this code only applies if there is actual data
1017 // in the source fork.
1018
1019 if (osErr == noErr && sourceFork->forkSize != 0) {
1020
1021 // Prepare for failure.
1022
1023 sourceRef = 0;
1024 destRef = 0;
1025
1026 // Open up the destination fork, if we're asked to, otherwise
1027 // just use the passed in sourceFork->forkDestRefNum.
1028 if( sourceFork->forkDestRefNum == 0 )
1029 osErr = FSOpenFork(dest, sourceFork->forkName.length, sourceFork->forkName.unicode, fsWrPerm, &destRef);
1030 else
1031 destRef = sourceFork->forkDestRefNum;
1032
1033 // Open up the source fork.
1034 if (osErr == noErr)
1035 osErr = FSOpenFork(source, sourceFork->forkName.length, sourceFork->forkName.unicode, fsRdPerm, &sourceRef);
1036
1037 // Here we create space for the entire fork on the destination volume.
1038 // FSAllocateFork has the right semantics on both traditional Mac OS
1039 // and Mac OS X. On traditional Mac OS it will allocate space for the
1040 // file in one hit without any other special action. On Mac OS X,
1041 // FSAllocateFork is preferable to FSSetForkSize because it prevents
1042 // the system from zero filling the bytes that were added to the end
1043 // of the fork (which would be waste becasue we're about to write over
1044 // those bytes anyway.
1045 if( osErr == noErr )
1046 osErr = FSAllocateFork(destRef, kFSAllocNoRoundUpMask, fsFromStart, 0, sourceFork->forkSize, NULL);
1047
1048 // Copy the file from the source to the destination in chunks of
1049 // no more than params->copyBufferSize bytes. This is fairly
1050 // boring code except for the bytesToReadThisTime/bytesToWriteThisTime
1051 // distinction. On the last chunk, we round bytesToWriteThisTime
1052 // up to the next 512 byte boundary and then, after we exit the loop,
1053 // we set the file's EOF back to the real location (if the fork size
1054 // is not a multiple of 512 bytes).
1055 //
1056 // This technique works around a 'bug' in the traditional Mac OS File Manager,
1057 // where the File Manager will put the last 512-byte block of a large write into
1058 // the cache (even if we specifically request no caching) if that block is not
1059 // full. If the block goes into the cache it will eventually have to be
1060 // flushed, which causes sub-optimal disk performance.
1061 //
1062 // This is only done if the destination volume is local. For a network
1063 // volume, it's better to just write the last bytes directly.
1064 //
1065 // This is extreme over-optimization given the other limits of this
1066 // sample, but I will hopefully get to the other limits eventually.
1067 bytesRemaining = sourceFork->forkSize;
1068 while (osErr == noErr && bytesRemaining != 0)
1069 {
1070 if (bytesRemaining > params->copyBufferSize)
1071 {
1072 bytesToReadThisTime = params->copyBufferSize;
1073 bytesToWriteThisTime = bytesToReadThisTime;
1074 }
1075 else
1076 {
1077 bytesToReadThisTime = bytesRemaining;
1078 bytesToWriteThisTime = (params->copyingToLocalVolume) ?
1079 (bytesRemaining + 0x01FF) & ~0x01FF :
1080 bytesRemaining;
1081 }
1082
1083 osErr = FSReadFork(sourceRef, fsAtMark + noCacheMask, 0, bytesToReadThisTime, params->copyBuffer, NULL);
1084 if (osErr == noErr)
1085 osErr = FSWriteFork(destRef, fsAtMark + noCacheMask, 0, bytesToWriteThisTime, params->copyBuffer, NULL);
1086 if (osErr == noErr)
1087 bytesRemaining -= bytesToReadThisTime;
1088 }
1089
1090 if (osErr == noErr && (params->copyingToLocalVolume && ((sourceFork->forkSize & 0x01FF) != 0)) )
1091 osErr = FSSetForkSize(destRef, fsFromStart, sourceFork->forkSize);
1092
1093 // Clean up.
1094 if (sourceRef != 0)
1095 {
1096 osErr2 = FSCloseFork(sourceRef);
1097 mycheck_noerr(osErr2);
1098 if (osErr == noErr)
1099 osErr = osErr2;
1100 }
1101
1102 // Only close destRef if we were asked to open it (ie sourceFork->forkDestRefNum == 0) and
1103 // we actually managed to open it (ie destRef != 0).
1104 if (sourceFork->forkDestRefNum == 0 && destRef != 0)
1105 {
1106 osErr2 = FSCloseFork(destRef);
1107 mycheck_noerr(osErr2);
1108 if (osErr == noErr)
1109 osErr = osErr2;
1110 }
1111 }
1112
1113 mycheck_noerr( osErr ); // put up debug assert in debug builds
1114
1115 return osErr;
1116}
1117
1118/*****************************************************************************/
1119
1120 // Close all the forks that might have been opened by OpenAllForks.
1121OSErr CloseAllForks(SInt16 dataRefNum, SInt16 rsrcRefNum, ForkTrackerPtr otherForks, ItemCount otherForksCount)
1122{
1123 ItemCount thisForkIndex;
1124 OSErr osErr = noErr,
1125 osErr2;
1126
1127 if (dataRefNum != 0)
1128 {
1129 osErr2 = FSCloseFork(dataRefNum);
1130 mycheck_noerr(osErr2);
1131 if (osErr == noErr)
1132 osErr = osErr2;
1133 }
1134 if (rsrcRefNum != 0)
1135 {
1136 osErr2 = FSCloseFork(rsrcRefNum);
1137 mycheck_noerr(osErr2);
1138 if (osErr == noErr)
1139 osErr = osErr2;
1140 }
1141 if( otherForks != NULL && otherForksCount > 0 )
1142 {
1143 for (thisForkIndex = 0; thisForkIndex < otherForksCount; thisForkIndex++)
1144 {
1145 if (otherForks[thisForkIndex].forkDestRefNum != 0)
1146 {
1147 osErr2 = FSCloseFork(otherForks[thisForkIndex].forkDestRefNum);
1148 mycheck_noerr(osErr2);
1149 if (osErr == noErr)
1150 osErr = osErr2;
1151 }
1152 }
1153 }
1154
1155 mycheck_noerr( osErr ); // put up debug assert in debug builds
1156
1157 return osErr;
1158}
1159
1160/*****************************************************************************/
1161
1162 // This routine determines the list of forks that a file has.
1163 // dataFork is populated if the file has a data fork.
1164 // rsrcFork is populated if the file has a resource fork.
1165 // otherForksParam is set to point to a memory block allocated with
1166 // NewPtr if the file has forks beyond the resource and data
1167 // forks. You must free that block with DisposePtr. otherForksCountParam
1168 // is set to the number of forks in the otherForksParam
1169 // array. This count does *not* include the resource and data forks.
1170OSErr CalculateForksToCopy( const FSRef *source,
1171 const ForkTrackerPtr dataFork,
1172 const ForkTrackerPtr rsrcFork,
1173 ForkTrackerPtr *otherForksParam,
1174 ItemCount *otherForksCountParam)
1175{
1176 Boolean done;
1177 CatPositionRec iterator;
1178 HFSUniStr255 thisForkName;
1179 SInt64 thisForkSize;
1180 ForkTrackerPtr otherForks;
1181 ItemCount otherForksCount;
1182 ItemCount otherForksMemoryBlockCount;
1183 OSErr osErr = ( (source != NULL) && (dataFork != NULL) &&
1184 (rsrcFork != NULL) && (otherForksParam != NULL) &&
1185 (otherForksCountParam != NULL) ) ?
1186 noErr : paramErr;
1187
1188 dataFork->forkSize = 0;
1189 rsrcFork->forkSize = 0;
1190 otherForks = NULL;
1191 otherForksCount = 0;
1192 iterator.initialize = 0;
1193 done = false;
1194
1195 // Iterate through the list of forks, processing each fork name in turn.
1196 while (osErr == noErr && ! done)
1197 {
1198 osErr = FSIterateForks(source, &iterator, &thisForkName, &thisForkSize, NULL);
1199 if (osErr == errFSNoMoreItems)
1200 {
1201 osErr = noErr;
1202 done = true;
1203 }
1204 else if (osErr == noErr)
1205 {
1206 if ( CompareHFSUniStr255(&thisForkName, &dataFork->forkName) )
1207 dataFork->forkSize = thisForkSize;
1208 else if ( CompareHFSUniStr255(&thisForkName, &rsrcFork->forkName) )
1209 rsrcFork->forkSize = thisForkSize;
1210 else
1211 {
1212 // We've found a fork other than the resource and data forks.
1213 // We have to add it to the otherForks array. But the array
1214 // a) may not have been created yet, and b) may not contain
1215 // enough elements to hold the new fork.
1216
1217 if (otherForks == NULL) // The array hasn't been allocated yet, allocate it.
1218 {
1219 otherForksMemoryBlockCount = kExpectedForkCount;
1220 otherForks = ( ForkTracker* ) NewPtr( sizeof(ForkTracker) * kExpectedForkCount );
1221 if (otherForks == NULL)
1222 osErr = memFullErr;
1223 }
1224 else if (otherForksCount == otherForksMemoryBlockCount)
1225 { // If the array doesn't contain enough elements, grow it.
1226 ForkTrackerPtr newOtherForks;
1227
1228 newOtherForks = (ForkTracker*)NewPtr(sizeof(ForkTracker) * (otherForksCount + kExpectedForkCount));
1229 if( newOtherForks != NULL)
1230 {
1231 BlockMoveData(otherForks, newOtherForks, sizeof(ForkTracker) * otherForksCount);
1232 otherForksMemoryBlockCount += kExpectedForkCount;
1233 DisposePtr((char*)otherForks);
1234 otherForks = newOtherForks;
1235 }
1236 else
1237 osErr = memFullErr;
1238 }
1239
1240 // If we have no error, we know we have space in the otherForks
1241 // array to place the new fork. Put it there and increment the
1242 // count of forks.
1243
1244 if (osErr == noErr)
1245 {
1246 BlockMoveData(&thisForkName, &otherForks[otherForksCount].forkName, sizeof(thisForkName));
1247 otherForks[otherForksCount].forkSize = thisForkSize;
1248 otherForks[otherForksCount].forkDestRefNum = 0;
1249 ++otherForksCount;
1250 }
1251 }
1252 }
1253 }
1254
1255 // Clean up.
1256
1257 if (osErr != noErr)
1258 {
1259 if (otherForks != NULL)
1260 {
1261 DisposePtr((char*)otherForks);
1262 otherForks = NULL;
1263 }
1264 otherForksCount = 0;
1265 }
1266
1267 *otherForksParam = otherForks;
1268 *otherForksCountParam = otherForksCount;
1269
1270 mycheck_noerr( osErr ); // put up debug assert in debug builds
1271
1272 return osErr;
1273}
1274
1275/*****************************************************************************/
1276
1277#pragma mark ----- Calculate Buffer Size -----
1278
1279OSErr CalculateBufferSize( const FSRef *source, const FSRef *destDir,
1280 ByteCount * bufferSize )
1281{
1282 FSVolumeRefNum sourceVRefNum,
1283 destVRefNum;
1284 ByteCount tmpBufferSize = 0;
1285 OSErr osErr = ( source != NULL && destDir != NULL && bufferSize != NULL ) ?
1286 noErr : paramErr;
1287
1288 if( osErr == noErr )
1289 osErr = FSGetVRefNum( source, &sourceVRefNum );
1290 if( osErr == noErr )
1291 osErr = FSGetVRefNum( destDir, &destVRefNum);
1292 if( osErr == noErr)
1293 {
1294 tmpBufferSize = BufferSizeForThisVolume(sourceVRefNum);
1295 if (destVRefNum != sourceVRefNum)
1296 {
1297 ByteCount tmp = BufferSizeForThisVolume(destVRefNum);
1298 if (tmp < tmpBufferSize)
1299 tmpBufferSize = tmp;
1300 }
1301 }
1302
1303 *bufferSize = tmpBufferSize;
1304
1305 mycheck_noerr( osErr ); // put up debug assert in debug builds
1306
1307 return osErr;
1308}
1309
1310/*****************************************************************************/
1311
1312 // This routine calculates the appropriate buffer size for
1313 // the given vRefNum. It's a simple composition of FSGetVolParms
1314 // BufferSizeForThisVolumeSpeed.
1315ByteCount BufferSizeForThisVolume(FSVolumeRefNum vRefNum)
1316{
1317 GetVolParmsInfoBuffer volParms;
1318 ByteCount volumeBytesPerSecond = 0;
1319 UInt32 actualSize;
1320 OSErr osErr;
1321
1322 osErr = FSGetVolParms( vRefNum, sizeof(volParms), &volParms, &actualSize );
1323 if( osErr == noErr )
1324 {
1325 // Version 1 of the GetVolParmsInfoBuffer included the vMAttrib
1326 // field, so we don't really need to test actualSize. A noErr
1327 // result indicates that we have the info we need. This is
1328 // just a paranoia check.
1329
1330 mycheck(actualSize >= offsetof(GetVolParmsInfoBuffer, vMVolumeGrade));
1331
1332 // On the other hand, vMVolumeGrade was not introduced until
1333 // version 2 of the GetVolParmsInfoBuffer, so we have to explicitly
1334 // test whether we got a useful value.
1335
1336 if( ( actualSize >= offsetof(GetVolParmsInfoBuffer, vMForeignPrivID) ) &&
1337 ( volParms.vMVolumeGrade <= 0 ) )
1338 {
1339 volumeBytesPerSecond = -volParms.vMVolumeGrade;
1340 }
1341 }
1342
1343 mycheck_noerr( osErr ); // put up debug assert in debug builds
1344
1345 return BufferSizeForThisVolumeSpeed(volumeBytesPerSecond);
1346}
1347
1348/*****************************************************************************/
1349
1350 // Calculate an appropriate copy buffer size based on the volumes
1351 // rated speed. Our target is to use a buffer that takes 0.25
1352 // seconds to fill. This is necessary because the volume might be
1353 // mounted over a very slow link (like ARA), and if we do a 256 KB
1354 // read over an ARA link we'll block the File Manager queue for
1355 // so long that other clients (who might have innocently just
1356 // called PBGetCatInfoSync) will block for a noticeable amount of time.
1357 //
1358 // Note that volumeBytesPerSecond might be 0, in which case we assume
1359 // some default value.
1360ByteCount BufferSizeForThisVolumeSpeed(UInt32 volumeBytesPerSecond)
1361{
1362 ByteCount bufferSize;
1363
1364 if (volumeBytesPerSecond == 0)
1365 bufferSize = kDefaultCopyBufferSize;
1366 else
1367 { // We want to issue a single read that takes 0.25 of a second,
1368 // so devide the bytes per second by 4.
1369 bufferSize = volumeBytesPerSecond / 4;
1370 }
1371
1372 // Round bufferSize down to 512 byte boundary.
1373 bufferSize &= ~0x01FF;
1374
1375 // Clip to sensible limits.
1376 if (bufferSize < kMinimumCopyBufferSize)
1377 bufferSize = kMinimumCopyBufferSize;
1378 else if (bufferSize > kMaximumCopyBufferSize)
1379 bufferSize = kMaximumCopyBufferSize;
1380
1381 return bufferSize;
1382}
1383
1384/*****************************************************************************/
1385
1386#pragma mark ----- Delete Objects -----
1387
1388OSErr FSDeleteObjects( const FSRef *source )
1389{
1390 FSCatalogInfo catalogInfo;
1391 OSErr osErr = ( source != NULL ) ? noErr : paramErr;
1392
1393 // get nodeFlags for container
1394 if( osErr == noErr )
1395 osErr = FSGetCatalogInfo(source, kFSCatInfoNodeFlags, &catalogInfo, NULL, NULL,NULL);
1396 if( osErr == noErr && (catalogInfo.nodeFlags & kFSNodeIsDirectoryMask) != 0 )
1397 { // its a directory, so delete its contents before we delete it
1398 osErr = FSDeleteFolder(source);
1399 }
1400 if( osErr == noErr && (catalogInfo.nodeFlags & kFSNodeLockedMask) != 0 ) // is object locked?
1401 { // then attempt to unlock the object (ignore osErr since FSDeleteObject will set it correctly)
1402 catalogInfo.nodeFlags &= ~kFSNodeLockedMask;
1403 (void) FSSetCatalogInfo(source, kFSCatInfoNodeFlags, &catalogInfo);
1404 }
1405 if( osErr == noErr ) // delete the object (if it was a directory it is now empty, so we can delete it)
1406 osErr = FSDeleteObject(source);
1407
1408 mycheck_noerr( osErr );
1409
1410 return ( osErr );
1411}
1412
1413/*****************************************************************************/
1414
1415#pragma mark ----- Delete Folders -----
1416
1417OSErr FSDeleteFolder( const FSRef *container )
1418{
1419 FSDeleteObjectGlobals theGlobals;
1420
1421 theGlobals.result = ( container != NULL ) ? noErr : paramErr;
1422
1423 // delete container's contents
1424 if( theGlobals.result == noErr )
1425 FSDeleteFolderLevel(container, &theGlobals);
1426
1427 mycheck_noerr( theGlobals.result );
1428
1429 return ( theGlobals.result );
1430}
1431
1432/*****************************************************************************/
1433
1434void FSDeleteFolderLevel( const FSRef *container,
1435 FSDeleteObjectGlobals *theGlobals)
1436{
1437 FSIterator iterator;
1438 FSRef itemToDelete;
1439 UInt16 nodeFlags;
1440
1441 // Open FSIterator for flat access and give delete optimization hint
1442 theGlobals->result = FSOpenIterator(container, kFSIterateFlat + kFSIterateDelete, &iterator);
1443 if ( theGlobals->result == noErr )
1444 {
1445 do // delete the contents of the directory
1446 {
1447 // get 1 item to delete
1448 theGlobals->result = FSGetCatalogInfoBulk( iterator, 1, &theGlobals->actualObjects,
1449 NULL, kFSCatInfoNodeFlags, &theGlobals->catalogInfo,
1450 &itemToDelete, NULL, NULL);
1451 if ( (theGlobals->result == noErr) && (theGlobals->actualObjects == 1) )
1452 {
1453 // save node flags in local in case we have to recurse */
1454 nodeFlags = theGlobals->catalogInfo.nodeFlags;
1455
1456 // is it a directory?
1457 if ( (nodeFlags & kFSNodeIsDirectoryMask) != 0 )
1458 { // yes -- delete its contents before attempting to delete it */
1459 FSDeleteFolderLevel(&itemToDelete, theGlobals);
1460 }
1461 if ( theGlobals->result == noErr) // are we still OK to delete?
1462 {
1463 if ( (nodeFlags & kFSNodeLockedMask) != 0 ) // is item locked?
1464 { // then attempt to unlock it (ignore result since FSDeleteObject will set it correctly)
1465 theGlobals->catalogInfo.nodeFlags = nodeFlags & ~kFSNodeLockedMask;
1466 (void) FSSetCatalogInfo(&itemToDelete, kFSCatInfoNodeFlags, &theGlobals->catalogInfo);
1467 }
1468 // delete the item
1469 theGlobals->result = FSDeleteObject(&itemToDelete);
1470 }
1471 }
1472 } while ( theGlobals->result == noErr );
1473
1474 // we found the end of the items normally, so return noErr
1475 if ( theGlobals->result == errFSNoMoreItems )
1476 theGlobals->result = noErr;
1477
1478 // close the FSIterator (closing an open iterator should never fail)
1479 myverify_noerr(FSCloseIterator(iterator));
1480 }
1481
1482 mycheck_noerr( theGlobals->result );
1483
1484 return;
1485}
1486
1487/*****************************************************************************/
1488
1489#pragma mark ----- Utilities -----
1490
1491 // Figures out if the given directory is a drop box or not
1492 // if it is, the Copy Engine will behave slightly differently
1493OSErr IsDropBox(const FSRef* source, Boolean *isDropBox)
1494{
1495 FSCatalogInfo tmpCatInfo;
1496 FSSpec sourceSpec;
1497 Boolean isDrop = false;
1498 OSErr osErr;
1499
1500 // get info about the destination, and an FSSpec to it for PBHGetDirAccess
1501 osErr = FSGetCatalogInfo(source, kFSCatInfoNodeFlags | kFSCatInfoPermissions, &tmpCatInfo, NULL, &sourceSpec, NULL);
1502 if( osErr == noErr ) // make sure the source is a directory
1503 osErr = ((tmpCatInfo.nodeFlags & kFSNodeIsDirectoryMask) != 0) ? noErr : errFSNotAFolder;
1504 if( osErr == noErr )
1505 {
1506 HParamBlockRec hPB;
1507
1508 BlockZero(&hPB, sizeof( HParamBlockRec ));
1509
1510 hPB.accessParam.ioNamePtr = sourceSpec.name;
1511 hPB.accessParam.ioVRefNum = sourceSpec.vRefNum;
1512 hPB.accessParam.ioDirID = sourceSpec.parID;
1513
1514 // This is the official way (reads: the way X Finder does it) to figure
1515 // out the current users access privileges to a given directory
1516 osErr = PBHGetDirAccessSync(&hPB);
1517 if( osErr == noErr ) // its a drop folder if the current user only has write access
1518 isDrop = (hPB.accessParam.ioACAccess & kPrivilegesMask) == kioACAccessUserWriteMask;
1519 else if ( osErr == paramErr )
1520 {
1521 // There is a bug (2908703) in the Classic File System (not OS 9.x or Carbon)
1522 // on 10.1.x where PBHGetDirAccessSync sometimes returns paramErr even when the
1523 // data passed in is correct. This is a workaround/hack for that problem,
1524 // but is not as accurate.
1525 // Basically, if "Everyone" has only Write/Search access then its a drop folder
1526 // that is the most common case when its a drop folder
1527 FSPermissionInfo *tmpPerm = (FSPermissionInfo *)tmpCatInfo.permissions;
1528 isDrop = ((tmpPerm->mode & kRWXOtherAccessMask) == kDropFolderValue);
1529 osErr = noErr;
1530 }
1531 }
1532
1533 *isDropBox = isDrop;
1534
1535 mycheck_noerr( osErr );
1536
1537 return osErr;
1538}
1539
1540 // The copy engine is going to set each item's creation date
1541 // to kMagicBusyCreationDate while it's copying the item.
1542 // But kMagicBusyCreationDate is an old-style 32-bit date/time,
1543 // while the HFS Plus APIs use the new 64-bit date/time. So
1544 // we have to call a happy UTC utilities routine to convert from
1545 // the local time kMagicBusyCreationDate to a UTCDateTime
1546 // gMagicBusyCreationDate, which the File Manager will store
1547 // on disk and which the Finder we read back using the old
1548 // APIs, whereupon the File Manager will convert it back
1549 // to local time (and hopefully get the kMagicBusyCreationDate
1550 // back!).
1551OSErr GetMagicBusyCreationDate( UTCDateTime *date )
1552{
1553 UTCDateTime tmpDate = {0,0,0};
1554 OSErr osErr = ( date != NULL ) ? noErr : paramErr;
1555
1556 if( osErr == noErr )
1557 osErr = ConvertLocalTimeToUTC(kMagicBusyCreationDate, &tmpDate.lowSeconds);
1558 if( osErr == noErr )
1559 *date = tmpDate;
1560
1561 mycheck_noerr( osErr ); // put up debug assert in debug builds
1562
1563 return osErr;
1564}
1565
1566 // compares two HFSUniStr255 for equality
1567 // return true if they are identical, false if not
1568Boolean CompareHFSUniStr255(const HFSUniStr255 *lhs, const HFSUniStr255 *rhs)
1569{
1570 return (lhs->length == rhs->length)
1571 && (memcmp(lhs->unicode, rhs->unicode, lhs->length * sizeof(UniChar)) == 0);
1572}
1573
1574/*****************************************************************************/
1575
1576OSErr FSGetVRefNum(const FSRef *ref, FSVolumeRefNum *vRefNum)
1577{
1578 FSCatalogInfo catalogInfo;
1579 OSErr osErr = ( ref != NULL && vRefNum != NULL ) ? noErr : paramErr;
1580
1581 if( osErr == noErr ) /* get the volume refNum from the FSRef */
1582 osErr = FSGetCatalogInfo(ref, kFSCatInfoVolume, &catalogInfo, NULL, NULL, NULL);
1583 if( osErr == noErr )
1584 *vRefNum = catalogInfo.volume;
1585
1586 mycheck_noerr( osErr );
1587
1588 return osErr;
1589}
1590
1591/*****************************************************************************/
1592
1593OSErr FSGetVolParms( FSVolumeRefNum volRefNum,
1594 UInt32 bufferSize,
1595 GetVolParmsInfoBuffer *volParmsInfo,
1596 UInt32 *actualInfoSize) /* Can Be NULL */
1597{
1598 HParamBlockRec pb;
1599 OSErr osErr = ( volParmsInfo != NULL ) ? noErr : paramErr;
1600
1601 if( osErr == noErr )
1602 {
1603 pb.ioParam.ioNamePtr = NULL;
1604 pb.ioParam.ioVRefNum = volRefNum;
1605 pb.ioParam.ioBuffer = (Ptr)volParmsInfo;
1606 pb.ioParam.ioReqCount = (SInt32)bufferSize;
1607 osErr = PBHGetVolParmsSync(&pb);
1608 }
1609 /* return number of bytes the file system returned in volParmsInfo buffer */
1610 if( osErr == noErr && actualInfoSize != NULL)
1611 *actualInfoSize = (UInt32)pb.ioParam.ioActCount;
1612
1613 mycheck_noerr( osErr ); // put up debug assert in debug builds
1614
1615 return ( osErr );
1616}
1617
1618/*****************************************************************************/
1619
1620OSErr UnicodeNameGetHFSName( UniCharCount nameLength,
1621 const UniChar *name,
1622 TextEncoding textEncodingHint,
1623 Boolean isVolumeName,
1624 Str31 hfsName)
1625{
1626 UnicodeMapping uMapping;
1627 UnicodeToTextInfo utInfo;
1628 ByteCount unicodeByteLength;
1629 ByteCount unicodeBytesConverted;
1630 ByteCount actualPascalBytes;
1631 OSErr osErr = (hfsName != NULL && name != NULL ) ? noErr : paramErr;
1632
1633 // make sure output is valid in case we get errors or there's nothing to convert
1634 hfsName[0] = 0;
1635
1636 unicodeByteLength = nameLength * sizeof(UniChar);
1637 if ( unicodeByteLength == 0 )
1638 osErr = noErr; /* do nothing */
1639 else
1640 {
1641 // if textEncodingHint is kTextEncodingUnknown, get a "default" textEncodingHint
1642 if ( kTextEncodingUnknown == textEncodingHint )
1643 {
1644 ScriptCode script;
1645 RegionCode region;
1646
1647 script = (ScriptCode)GetScriptManagerVariable(smSysScript);
1648 region = (RegionCode)GetScriptManagerVariable(smRegionCode);
1649 osErr = UpgradeScriptInfoToTextEncoding(script, kTextLanguageDontCare,
1650 region, NULL, &textEncodingHint );
1651 if ( osErr == paramErr )
1652 { // ok, ignore the region and try again
1653 osErr = UpgradeScriptInfoToTextEncoding(script, kTextLanguageDontCare,
1654 kTextRegionDontCare, NULL,
1655 &textEncodingHint );
1656 }
1657 if ( osErr != noErr ) // ok... try something
1658 textEncodingHint = kTextEncodingMacRoman;
1659 }
1660
1661 uMapping.unicodeEncoding = CreateTextEncoding( kTextEncodingUnicodeV2_0,
1662 kUnicodeCanonicalDecompVariant,
1663 kUnicode16BitFormat);
1664 uMapping.otherEncoding = GetTextEncodingBase(textEncodingHint);
1665 uMapping.mappingVersion = kUnicodeUseHFSPlusMapping;
1666
1667 osErr = CreateUnicodeToTextInfo(&uMapping, &utInfo);
1668 if( osErr == noErr )
1669 {
1670 osErr = ConvertFromUnicodeToText( utInfo, unicodeByteLength, name, kUnicodeLooseMappingsMask,
1671 0, NULL, 0, NULL, /* offsetCounts & offsetArrays */
1672 isVolumeName ? kHFSMaxVolumeNameChars : kHFSMaxFileNameChars,
1673 &unicodeBytesConverted, &actualPascalBytes, &hfsName[1]);
1674 }
1675 if( osErr == noErr )
1676 hfsName[0] = actualPascalBytes;
1677
1678 // verify the result in debug builds -- there's really not anything you can do if it fails
1679 myverify_noerr(DisposeUnicodeToTextInfo(&utInfo));
1680 }
1681
1682 mycheck_noerr( osErr ); // put up debug assert in debug builds
1683
1684 return ( osErr );
1685}
1686
1687/*****************************************************************************/
1688
1689OSErr FSMakeFSRef( FSVolumeRefNum volRefNum,
1690 SInt32 dirID,
1691 ConstStr255Param name,
1692 FSRef *ref)
1693{
1694 FSRefParam pb;
1695 OSErr osErr = ( ref != NULL ) ? noErr : paramErr;
1696
1697 if( osErr == noErr )
1698 {
1699 pb.ioVRefNum = volRefNum;
1700 pb.ioDirID = dirID;
1701 pb.ioNamePtr = (StringPtr)name;
1702 pb.newRef = ref;
1703 osErr = PBMakeFSRefSync(&pb);
1704 }
1705
1706 mycheck_noerr( osErr ); // put up debug assert in debug builds
1707
1708 return ( osErr );
1709}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use