VirtualBox

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

Last change on this file since 549 was 1, checked in by vboxsync, 55 years ago

import

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette