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 |
|
---|
64 | enum { // 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 |
|
---|
71 | enum { // CalculateForksToCopy
|
---|
72 | kExpectedForkCount = 10 // Number of non-classic forks we expect.
|
---|
73 | }; // (i.e. non resource/data forks)
|
---|
74 |
|
---|
75 | enum { // CheckForDestInsideSource
|
---|
76 | errFSDestInsideSource = -1234
|
---|
77 | };
|
---|
78 |
|
---|
79 | enum {
|
---|
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
|
---|
115 | struct CopyParams {
|
---|
116 | UTCDateTime magicBusyCreateDate;
|
---|
117 | void *copyBuffer;
|
---|
118 | ByteCount copyBufferSize;
|
---|
119 | Boolean copyingToDropFolder;
|
---|
120 | Boolean copyingToLocalVolume;
|
---|
121 | };
|
---|
122 | typedef 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
|
---|
127 | struct FilterParams {
|
---|
128 | FSCatalogInfoBitmap whichInfo;
|
---|
129 | CopyObjectFilterProcPtr filterProcPtr;
|
---|
130 | FSSpec fileSpec;
|
---|
131 | FSSpec *fileSpecPtr;
|
---|
132 | HFSUniStr255 fileName;
|
---|
133 | HFSUniStr255 *fileNamePtr;
|
---|
134 | void *yourDataPtr;
|
---|
135 | };
|
---|
136 | typedef 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...
|
---|
144 | struct ForkTracker {
|
---|
145 | HFSUniStr255 forkName;
|
---|
146 | SInt64 forkSize;
|
---|
147 | SInt16 forkDestRefNum;
|
---|
148 | };
|
---|
149 | typedef struct ForkTracker ForkTracker;
|
---|
150 | typedef ForkTracker *ForkTrackerPtr;
|
---|
151 |
|
---|
152 | // The FSCopyObjectGlobals data structure holds information needed to do
|
---|
153 | // the recursive copy of a directory.
|
---|
154 | struct 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 | };
|
---|
168 | typedef struct FSCopyObjectGlobals FSCopyObjectGlobals;
|
---|
169 |
|
---|
170 | // The FSDeleteObjectGlobals data structure holds information needed to
|
---|
171 | // recursively delete a directory
|
---|
172 | struct FSDeleteObjectGlobals
|
---|
173 | {
|
---|
174 | FSCatalogInfo catalogInfo; /* FSCatalogInfo */
|
---|
175 | ItemCount actualObjects; /* number of objects returned */
|
---|
176 | OSErr result; /* result */
|
---|
177 | };
|
---|
178 | typedef struct FSDeleteObjectGlobals FSDeleteObjectGlobals;
|
---|
179 |
|
---|
180 | #pragma mark ----- Local Prototypes -----
|
---|
181 |
|
---|
182 | static 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 |
|
---|
189 | static 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 |
|
---|
196 | static 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 |
|
---|
203 | static 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 |
|
---|
210 | static 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 |
|
---|
218 | static OSErr FSCopyFolderLevel( FSCopyObjectGlobals *theGlobals, const HFSUniStr255 *destName );
|
---|
219 |
|
---|
220 | static OSErr CheckForDestInsideSource( const FSRef *source,
|
---|
221 | const FSRef *destDir);
|
---|
222 |
|
---|
223 | static OSErr CopyItemsForks( const FSRef *source,
|
---|
224 | const FSRef *dest,
|
---|
225 | CopyParams *params);
|
---|
226 |
|
---|
227 | static OSErr OpenAllForks( const FSRef *dest,
|
---|
228 | const ForkTrackerPtr dataFork,
|
---|
229 | const ForkTrackerPtr rsrcFork,
|
---|
230 | ForkTrackerPtr otherForks,
|
---|
231 | ItemCount otherForksCount);
|
---|
232 |
|
---|
233 | static OSErr CopyFork( const FSRef *source,
|
---|
234 | const FSRef *dest,
|
---|
235 | const ForkTrackerPtr sourceFork,
|
---|
236 | const CopyParams *params);
|
---|
237 |
|
---|
238 | static OSErr CloseAllForks( SInt16 dataRefNum,
|
---|
239 | SInt16 rsrcRefNum,
|
---|
240 | ForkTrackerPtr otherForks,
|
---|
241 | ItemCount otherForksCount);
|
---|
242 |
|
---|
243 | static OSErr CalculateForksToCopy( const FSRef *source,
|
---|
244 | const ForkTrackerPtr dataFork,
|
---|
245 | const ForkTrackerPtr rsrcFork,
|
---|
246 | ForkTrackerPtr *otherForksParam,
|
---|
247 | ItemCount *otherForksCountParam);
|
---|
248 |
|
---|
249 | static OSErr CalculateBufferSize( const FSRef *source,
|
---|
250 | const FSRef *destDir,
|
---|
251 | ByteCount * bufferSize);
|
---|
252 |
|
---|
253 | static ByteCount BufferSizeForThisVolume(FSVolumeRefNum vRefNum);
|
---|
254 |
|
---|
255 | static ByteCount BufferSizeForThisVolumeSpeed(UInt32 volumeBytesPerSecond);
|
---|
256 |
|
---|
257 | static OSErr IsDropBox( const FSRef* source,
|
---|
258 | Boolean *isDropBox);
|
---|
259 |
|
---|
260 | static OSErr GetMagicBusyCreationDate( UTCDateTime *date );
|
---|
261 |
|
---|
262 | static Boolean CompareHFSUniStr255(const HFSUniStr255 *lhs,
|
---|
263 | const HFSUniStr255 *rhs);
|
---|
264 |
|
---|
265 | static OSErr FSGetVRefNum( const FSRef *ref,
|
---|
266 | FSVolumeRefNum *vRefNum);
|
---|
267 |
|
---|
268 | static OSErr FSGetVolParms( FSVolumeRefNum volRefNum,
|
---|
269 | UInt32 bufferSize,
|
---|
270 | GetVolParmsInfoBuffer *volParmsInfo,
|
---|
271 | UInt32 *actualInfoSize); /* Can Be NULL */
|
---|
272 |
|
---|
273 | static OSErr UnicodeNameGetHFSName( UniCharCount nameLength,
|
---|
274 | const UniChar *name,
|
---|
275 | TextEncoding textEncodingHint,
|
---|
276 | Boolean isVolumeName,
|
---|
277 | Str31 hfsName);
|
---|
278 |
|
---|
279 | static OSErr FSMakeFSRef( FSVolumeRefNum volRefNum,
|
---|
280 | SInt32 dirID,
|
---|
281 | ConstStr255Param name,
|
---|
282 | FSRef *ref);
|
---|
283 |
|
---|
284 | static OSErr FSDeleteFolder( const FSRef *container );
|
---|
285 |
|
---|
286 | static 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.
|
---|
298 | OSErr 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, ©Params.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( ©Params.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, ©Params.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, ©Params, &filterParams, maxLevels, newObject );
|
---|
384 | }
|
---|
385 | else // no
|
---|
386 | osErr = FSCopyFile(source, destDir, destNamePtr, ©Params, &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 |
|
---|
402 | OSErr 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 |
|
---|
447 | OSErr 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 |
|
---|
474 | OSErr 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 | //
|
---|
532 | OSErr 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 |
|
---|
621 | OSErr 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 |
|
---|
663 | OSErr 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 | //
|
---|
822 | OSErr 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 |
|
---|
865 | OSErr 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.
|
---|
937 | OSErr 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.
|
---|
990 | OSErr 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.
|
---|
1121 | OSErr 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.
|
---|
1170 | OSErr 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 |
|
---|
1279 | OSErr 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.
|
---|
1315 | ByteCount 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.
|
---|
1360 | ByteCount 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 |
|
---|
1388 | OSErr 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 |
|
---|
1417 | OSErr 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 |
|
---|
1434 | void 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
|
---|
1493 | OSErr 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!).
|
---|
1551 | OSErr 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
|
---|
1568 | Boolean 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 |
|
---|
1576 | OSErr 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 |
|
---|
1593 | OSErr 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 |
|
---|
1620 | OSErr 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 |
|
---|
1689 | OSErr 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 | }
|
---|