/*
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 1, or (at your option)
** any later version.
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
//#define DEBUG 1
/*
* Author : Jens Miltner <jum@mac.com> --- January 2004
*/
#include "cvs.h"
#include "mac_copy_file.h"
#include "error.h"
#include <CoreServices/CoreServices.h>
#include <stddef.h>
#include <unistd.h>
#include <set>
#ifndef DEBUG
# define DEBUG 0
#endif
namespace {
#if DEBUG
inline void _myverify_noerr(OSStatus err, const char *expr, const char* function, const char *file, int line)
{
if ( err != noErr ) {
fprintf(stderr, "### %s != noErr in function %s (Error: %d)\n (file %s; line %d)\n", expr, function, err, file, line);
fflush(stderr);
}
}
inline void _mycheck(bool b, const char *expr, const char *function, const char *file, int line)
{
if ( !b ) {
fprintf(stderr, "### %s failed in function %s\n (file %s; line %d)\n", expr, function, file, line);
fflush(stderr);
}
}
#define myverify_noerr(x) _myverify_noerr((x), #x, __FUNCTION__, __FILE__, __LINE__)
#define mycheck_noerr(x) myverify_noerr(x)
#define mycheck(x) _mycheck((x), #x, __FUNCTION__, __FILE__, __LINE__)
#define myverify(x) mycheck(x)
#else
#define mycheck_noerr(x)
#define mycheck(x)
#define myverify(x) do { (x); } while (0)
#define myverify_noerr(x) myverify(x)
#endif
#pragma mark ----- Tunable Parameters -----
/* The following constants control the behavior of the copy engine. */
enum { /* BufferSizeForVolSpeed */
/* kDefaultCopyBufferSize = 2L * 1024 * 1024,*/ /* 2MB, Fast but not very responsive. */
kDefaultCopyBufferSize = 256L * 1024, /* 256kB, Slower but can still use machine. */
kMaximumCopyBufferSize = 2L * 1024 * 1024,
kMinimumCopyBufferSize = 1024
};
enum {
/* for use with PBHGetDirAccess in IsDropBox */
kPrivilegesMask = kioACAccessUserWriteMask | kioACAccessUserReadMask | kioACAccessUserSearchMask,
/* for use with FSGetCatalogInfo and FSPermissionInfo->mode */
/* from sys/stat.h... note -- sys/stat.h definitions are in octal */
/* */
/* You can use these values to adjust the users/groups permissions */
/* on a file/folder with FSSetCatalogInfo and extracting the */
/* kFSCatInfoPermissions field. See code below for examples */
kRWXUserAccessMask = 0x01C0,
kReadAccessUser = 0x0100,
kWriteAccessUser = 0x0080,
kExecuteAccessUser = 0x0040,
kRWXGroupAccessMask = 0x0038,
kReadAccessGroup = 0x0020,
kWriteAccessGroup = 0x0010,
kExecuteAccessGroup = 0x0008,
kRWXOtherAccessMask = 0x0007,
kReadAccessOther = 0x0004,
kWriteAccessOther = 0x0002,
kExecuteAccessOther = 0x0001,
kDropFolderValue = kWriteAccessOther | kExecuteAccessOther
};
struct ForkInfo
{
HFSUniStr255 forkName;
SInt64 forkSize;
ForkInfo() {}
ForkInfo(const ForkInfo& inRhs)
{
this->operator = (inRhs);
}
ForkInfo& operator = (const ForkInfo& inRhs)
{
BlockMoveData(inRhs.forkName.unicode, forkName.unicode, inRhs.forkName.length * sizeof(UniChar));
forkName.length = inRhs.forkName.length;
forkSize = inRhs.forkSize;
return *this;
}
bool operator < (const ForkInfo& inRhs) const
{
for ( UInt16 i(0); i < forkName.length; ++i ) {
if ( i >= inRhs.forkName.length ) return false;
if ( forkName.unicode[i] < inRhs.forkName.unicode[i] ) return true;
if ( forkName.unicode[i] > inRhs.forkName.unicode[i] ) return false;
}
return forkName.length < inRhs.forkName.length;
}
};
static OSErr FSGetVRefNum( const FSRef *ref,
FSVolumeRefNum *vRefNum)
{
FSCatalogInfo catalogInfo;
OSErr err = ( ref != NULL && vRefNum != NULL ) ? OSErr(noErr) : OSErr(paramErr);
if( err == noErr ) /* get the volume refNum from the FSRef */
err = FSGetCatalogInfo(ref, kFSCatInfoVolume, &catalogInfo, NULL, NULL, NULL);
if( err == noErr )
*vRefNum = catalogInfo.volume;
mycheck_noerr( err );
return err;
}
static OSErr FSGetVolParms( FSVolumeRefNum volRefNum,
UInt32 bufferSize,
GetVolParmsInfoBuffer *volParmsInfo,
UInt32 *actualInfoSize) /* Can Be NULL */
{
HParamBlockRec pb;
OSErr err = ( volParmsInfo != NULL ) ? OSErr(noErr) : OSErr(paramErr);
if( err == noErr )
{
pb.ioParam.ioNamePtr = NULL;
pb.ioParam.ioVRefNum = volRefNum;
pb.ioParam.ioBuffer = (Ptr)volParmsInfo;
pb.ioParam.ioReqCount = (SInt32)bufferSize;
err = PBHGetVolParmsSync(&pb);
}
/* return number of bytes the file system returned in volParmsInfo buffer */
if( err == noErr && actualInfoSize != NULL)
*actualInfoSize = (UInt32)pb.ioParam.ioActCount;
mycheck_noerr( err );
return ( err );
}
const char* ForkNameAsCString(const HFSUniStr255& forkName)
{
static char s[256];
CFStringRef cfstr(CFStringCreateWithCharactersNoCopy(NULL, forkName.unicode, forkName.length, kCFAllocatorNull));
if ( cfstr ) {
if ( !CFStringGetCString(cfstr, s, sizeof(s), kCFStringEncodingUTF8) ) sprintf(s, "<failure 1>");
CFRelease(cfstr);
} else sprintf(s, "<failure 2>");
return s;
}
const char* FSRef2Path(const FSRef& inRef)
{
static char path[PATH_MAX];
OSStatus err;
err = FSRefMakePath(&inRef, (UInt8*)path, sizeof(path));
if ( err != noErr ) sprintf(path, "<error %d while converting FSRef>", err);
return path;
}
OSStatus CollectForks(const FSRef& inFile, std::set<ForkInfo>& outForkList)
{
CatPositionRec iterator;
OSStatus err(noErr);
#if DEBUG
printf("CollectForks for '%s'\n", FSRef2Path(inFile));
#endif
/* need to initialize the iterator before using it */
iterator.initialize = 0;
/* Iterate over the list of forks */
while ( err == noErr ) {
ForkInfo forkInfo;
err = FSIterateForks( &inFile, &iterator, &forkInfo.forkName, &forkInfo.forkSize, NULL );
if ( err == noErr ) {
#if DEBUG
printf("found fork: '%s', fork size: %qu\n", ForkNameAsCString(forkInfo.forkName), forkInfo.forkSize);
#endif
outForkList.insert(forkInfo);
}
}
return ( err == errFSNoMoreItems ? noErr : err );
}
/* Writes the fork from the source, references by srcRefNum, to the destination fork */
/* references by destRefNum */
OSStatus WriteFork( SInt16 srcRefNum,
SInt16 destRefNum,
SInt64 forkSize,
UInt32 bufferSize,
void* buffer,
bool truncateFork,
bool copyingToLocalVolume)
{
UInt64 bytesRemaining;
UInt64 bytesToReadThisTime;
UInt64 bytesToWriteThisTime;
OSStatus err;
/* Here we create space for the entire fork on the destination volume. */
/* FSAllocateFork has the right semantics on both traditional Mac OS */
/* and Mac OS X. On traditional Mac OS it will allocate space for the */
/* file in one hit without any other special action. On Mac OS X, */
/* FSAllocateFork is preferable to FSSetForkSize because it prevents */
/* the system from zero filling the bytes that were added to the end */
/* of the fork (which would be waste because we're about to write over */
/* those bytes anyway. */
err = FSAllocateFork(destRefNum, kFSAllocNoRoundUpMask, fsFromStart, 0, forkSize, NULL);
/* Copy the file from the source to the destination in chunks of */
/* no more than params->copyBufferSize bytes. This is fairly */
/* boring code except for the bytesToReadThisTime/bytesToWriteThisTime */
/* distinction. On the last chunk, we round bytesToWriteThisTime */
/* up to the next 512 byte boundary and then, after we exit the loop, */
/* we set the file's EOF back to the real location (if the fork size */
/* is not a multiple of 512 bytes). */
/* */
/* This technique works around a 'bug' in the traditional Mac OS File Manager, */
/* where the File Manager will put the last 512-byte block of a large write into */
/* the cache (even if we specifically request no caching) if that block is not */
/* full. If the block goes into the cache it will eventually have to be */
/* flushed, which causes sub-optimal disk performance. */
/* */
/* This is only done if the destination volume is local. For a network */
/* volume, it's better to just write the last bytes directly. */
/* */
/* This is extreme over-optimization given the other limits of this */
/* sample, but I will hopefully get to the other limits eventually. */
bytesRemaining = forkSize;
while( err == noErr && bytesRemaining != 0 )
{
if( bytesRemaining > bufferSize )
{
bytesToReadThisTime = bufferSize;
bytesToWriteThisTime = bytesToReadThisTime;
}
else
{
bytesToReadThisTime = bytesRemaining;
bytesToWriteThisTime = ( copyingToLocalVolume ) ?
( (bytesRemaining + 0x01FF ) & ~0x01FF ) : bytesRemaining;
}
err = FSReadFork( srcRefNum, fsAtMark + noCacheMask, 0, bytesToReadThisTime, buffer, NULL );
if( err == noErr )
err = FSWriteFork( destRefNum, fsAtMark + noCacheMask, 0, bytesToWriteThisTime, buffer, NULL );
if( err == noErr )
bytesRemaining -= bytesToReadThisTime;
}
if (err == noErr && (truncateFork || (copyingToLocalVolume && ( forkSize & 0x01FF ) != 0)) )
err = FSSetForkSize( destRefNum, fsFromStart, forkSize );
return err;
}
OSStatus CopyForks(const FSRef& inSrcFile,
const FSRef& inDestFile,
const std::set<ForkInfo>& inSourceForks,
const std::set<ForkInfo>& inDestForks,
UInt32 inBufferSize,
void* inBuffer)
{
OSStatus err(noErr);
std::set<ForkInfo> forksToRemove(inDestForks);
// copy all of the source file's forks first
for ( std::set<ForkInfo>::const_iterator i(inSourceForks.begin()); i != inSourceForks.end() && err == noErr; ++i ) {
std::set<ForkInfo>::iterator foundDestFork(forksToRemove.find(*i));
bool existingFork(foundDestFork != forksToRemove.end());
TRACE(1, "processing fork: '%s', size = %qu bytes (%s fork)", ForkNameAsCString(i->forkName), i->forkSize, existingFork ? "existing" : "new");
if ( !existingFork ) {
// fork doesn't already exist in destination file -> create it
err = FSCreateFork(&inDestFile, i->forkName.length, i->forkName.unicode);
}
if ( err == noErr && ( i->forkSize > 0 || foundDestFork != forksToRemove.end() ) ) {
SInt16 srcRefNum(0), destRefNum(0);
/* Open the destination fork */
err = FSOpenFork(&inDestFile, i->forkName.length, i->forkName.unicode, fsWrPerm, &destRefNum);
if ( err == noErr ) {
if ( i->forkSize > 0 ) {
/* Open the source fork */
err = FSOpenFork(&inSrcFile, i->forkName.length, i->forkName.unicode, fsRdPerm, &srcRefNum);
if( err == noErr ) { /* Write the fork to disk */
TRACE(1, "%s fork %s from source file.", existingFork ? "Overwriting" : "Copying", ForkNameAsCString(i->forkName));
err = WriteFork( srcRefNum, destRefNum, i->forkSize, inBufferSize, inBuffer, existingFork, true );
}
} else {
// new fork size is 0, but fork exists in destination file -> make sure to truncate it
TRACE(1, "Truncating existing fork %s to 0 bytes.", ForkNameAsCString(i->forkName));
err = FSSetForkSize(destRefNum, fsFromStart, 0);
}
}
if( destRefNum != 0 ) /* Close the destination fork */
myverify_noerr( FSCloseFork( destRefNum ) );
if( srcRefNum != 0 ) /* Close the source fork */
myverify_noerr( FSCloseFork( srcRefNum ) );
}
if ( foundDestFork != forksToRemove.end() ) {
// fork already existed in destination file -> remove from list of forks to remove
forksToRemove.erase(foundDestFork);
}
}
// then remove all untouched forks in the destination file
for ( std::set<ForkInfo>::const_iterator i(forksToRemove.begin()); i != forksToRemove.end() && err == noErr; ++i ) {
TRACE(1, "Removing existing fork %s, since it does not exist in the source file.", ForkNameAsCString(i->forkName));
err = FSDeleteFork(&inDestFile, i->forkName.length, i->forkName.unicode);
}
return err;
}
/* Calculate an appropriate copy buffer size based on the volumes */
/* rated speed. Our target is to use a buffer that takes 0.25 */
/* seconds to fill. This is necessary because the volume might be */
/* mounted over a very slow link (like ARA), and if we do a 256 KB */
/* read over an ARA link we'll block the File Manager queue for */
/* so long that other clients (who might have innocently just */
/* called PBGetCatInfoSync) will block for a noticeable amount of time. */
/* */
/* Note that volumeBytesPerSecond might be 0, in which case we assume */
/* some default value. */
static UInt32 BufferSizeForVolSpeed(UInt32 volumeBytesPerSecond)
{
ByteCount bufferSize;
if (volumeBytesPerSecond == 0)
bufferSize = kDefaultCopyBufferSize;
else
{ /* We want to issue a single read that takes 0.25 of a second, */
/* so devide the bytes per second by 4. */
bufferSize = volumeBytesPerSecond / 4;
}
/* Round bufferSize down to 512 byte boundary. */
bufferSize &= ~0x01FF;
/* Clip to sensible limits. */
if (bufferSize < kMinimumCopyBufferSize)
bufferSize = kMinimumCopyBufferSize;
else if (bufferSize > kMaximumCopyBufferSize)
bufferSize = kMaximumCopyBufferSize;
return bufferSize;
}
/* This routine calculates the appropriate buffer size for */
/* the given vRefNum. It's a simple composition of FSGetVolParms */
/* BufferSizeForVolSpeed. */
static UInt32 BufferSizeForVol(FSVolumeRefNum vRefNum)
{
GetVolParmsInfoBuffer volParms;
ByteCount volumeBytesPerSecond = 0;
UInt32 actualSize;
OSErr err;
err = FSGetVolParms( vRefNum, sizeof(volParms), &volParms, &actualSize );
if( err == noErr )
{
/* Version 1 of the GetVolParmsInfoBuffer included the vMAttrib */
/* field, so we don't really need to test actualSize. A noErr */
/* result indicates that we have the info we need. This is */
/* just a paranoia check. */
mycheck(actualSize >= offsetof(GetVolParmsInfoBuffer, vMVolumeGrade));
/* On the other hand, vMVolumeGrade was not introduced until */
/* version 2 of the GetVolParmsInfoBuffer, so we have to explicitly */
/* test whether we got a useful value. */
if( ( actualSize >= offsetof(GetVolParmsInfoBuffer, vMForeignPrivID) ) &&
( volParms.vMVolumeGrade <= 0 ) )
{
volumeBytesPerSecond = -volParms.vMVolumeGrade;
}
}
mycheck_noerr( err );
return BufferSizeForVolSpeed(volumeBytesPerSecond);
}
#pragma mark ----- Calculate Buffer Size -----
/* Calculates the optimal buffer size for both the source */
/* and destination volumes */
static OSErr CalculateBufferSize( const FSRef& source,
const FSRef& destDir,
ByteCount& outBufferSize )
{
FSVolumeRefNum sourceVRefNum,
destVRefNum;
ByteCount tmpBufferSize = 0;
OSErr err(noErr);
if( err == noErr )
err = FSGetVRefNum( &source, &sourceVRefNum );
if( err == noErr )
err = FSGetVRefNum( &destDir, &destVRefNum);
if( err == noErr)
{
tmpBufferSize = BufferSizeForVol(sourceVRefNum);
if (destVRefNum != sourceVRefNum)
{
ByteCount tmp = BufferSizeForVol(destVRefNum);
if (tmp < tmpBufferSize)
tmpBufferSize = tmp;
}
}
outBufferSize = tmpBufferSize;
mycheck_noerr( err );
return err;
}
#pragma mark ----- CopyFile -----
/* Create a copy of the file, copying all available forks.
Will overwrite existing files. Behavior is similar to 'regular' unix copy:
/verbatim
creat(dest);
open(src);
while ( !eof ) {
read(src);
write(dest);
}
close(src);
close(dest);
\endverbatim
There's a reason we don't use the 'standard' FSCopyObject for Mac OS: this function has a
different behavior when it comes to replacing existing files, especially with regards to
handling situations where the target file is read-only. In that case, the behavior of FSCopyObject
is just not close enough to the default file copying mechanism in cvs to provide the same
experience to the user as in a standard unix distribution of cvs...
*/
OSStatus CopyFile(const FSRef& source,
const FSRef& destDir,
FSRef *outFileRef,
const HFSUniStr255* newName)
{
OSStatus err(noErr);
HFSUniStr255 newFileName;
FSCatalogInfo sourceCatInfo;
FSPermissionInfo originalPermissions;
OSType originalFileType;
UInt16 originalNodeFlags;
std::set<ForkInfo> sourceForks, destForks;
FSRef dest;
FSSpec tmpSpec;
bool isSymLink;
bool destFileExists(false);
void* buffer = NULL;
UInt32 bufferSize;
err = CalculateBufferSize( source, destDir, bufferSize);
if ( err == noErr ) {
buffer = malloc(bufferSize);
if ( buffer == NULL ) err = memFullErr;
}
if( err == noErr ) { /* get needed info about the source file */
if ( newName ) BlockMoveData(newName, &newFileName, sizeof(newFileName));
err = FSGetCatalogInfo( &source, kFSCatInfoSettableInfo, &sourceCatInfo, newName ? NULL : &newFileName, NULL, NULL );
}
// collect information about the forks to copy and replace
err = CollectForks(source, sourceForks);
if ( err == noErr ) {
if ( FSMakeFSRefUnicode(&destDir, newFileName.length, newFileName.unicode, kTextEncodingUnknown, &dest) == noErr ) {
// destination file exists -> collect forks, so we known which one already exist
err = CollectForks(dest, destForks);
destFileExists = true;
}
}
/* Clear the "inited" bit so that the Finder positions the icon for us. */
((FInfo *)(sourceCatInfo.finderInfo))->fdFlags &= ~kHasBeenInited;
// now copy all data & metadata
/* Remember to clear the file's type, so the Finder doesn't */
/* look at the file until we're done. */
originalFileType = ((FInfo *) &sourceCatInfo.finderInfo)->fdType;
((FInfo *) &sourceCatInfo.finderInfo)->fdType = kFirstMagicBusyFiletype;
/* Remember and clear the file's locked status, so that we can */
/* actually write the forks we're about to create. */
originalNodeFlags = sourceCatInfo.nodeFlags;
sourceCatInfo.nodeFlags &= ~kFSNodeLockedMask;
/* figure out if we should get the FSSpec to the new file or not */
/* If we need it for symlinks */
isSymLink = ( originalFileType == 'slnk' && ((FInfo *) &sourceCatInfo.finderInfo)->fdCreator == 'rhap' );
/* we need to have user level read/write/execute access to the file we are */
/* going to create otherwise FSCreateFileUnicode will return */
/* -5000 (afpAccessDenied), and the FSRef returned will be invalid, yet */
/* the file is created (size 0k)... bug? */
originalPermissions = *((FSPermissionInfo*)sourceCatInfo.permissions);
((FSPermissionInfo*)sourceCatInfo.permissions)->mode |= kRWXUserAccessMask;
if( err == noErr && !destFileExists ) /* attempt to create the new file */
err = FSCreateFileUnicode(&destDir, newFileName.length, newFileName.unicode, kFSCatInfoSettableInfo, &sourceCatInfo, &dest, isSymLink ? &tmpSpec : NULL );
if( err == noErr ) /* Copy the forks over to the new file */
err = CopyForks(source, dest, sourceForks, destForks, bufferSize, buffer);
/* Restore the original file type, creation and modification dates, */
/* locked status and permissions. */
/* This is one of the places where we need to handle drop */
/* folders as a special case because this FSSetCatalogInfo will fail for */
/* an item in a drop folder, so we don't even attempt it. */
if ( err == noErr )
{
((FInfo *) &sourceCatInfo.finderInfo)->fdType = originalFileType;
sourceCatInfo.nodeFlags = originalNodeFlags;
*((FSPermissionInfo*)sourceCatInfo.permissions) = originalPermissions;
/* 2796751, FSSetCatalogInfo returns -36 when setting the Finder Info */
/* for a symlink. To workaround this, when the file is a */
/* symlink (slnk/rhap) we will finish the copy in two steps. First */
/* setting everything but the Finder Info on the file, then calling */
/* FSpSetFInfo to set the Finder Info for the file. I would rather use */
/* an FSRef function to set the Finder Info, but FSSetCatalogInfo is */
/* the only one... catch-22... */
/* */
/* The Carbon File Manager always sets the type/creator of a symlink to */
/* slnk/rhap if the file is a symlink we do the two step, if it isn't */
/* we use FSSetCatalogInfo to do all the work. */
if ( isSymLink )
{ /* Its a symlink */
/* set all the info, except the Finder info */
err = FSSetCatalogInfo(&dest, kFSCatInfoNodeFlags | kFSCatInfoPermissions, &sourceCatInfo);
if ( err == noErr ) /* set the Finder Info to that file */
err = FSpSetFInfo( &tmpSpec, ((FInfo *) &sourceCatInfo.finderInfo) );
}
else { /* its a regular file */
err = FSSetCatalogInfo(&dest, kFSCatInfoNodeFlags | kFSCatInfoFinderInfo | kFSCatInfoPermissions, &sourceCatInfo);
}
}
/* If we created the file and the copy failed, try to clean up by */
/* deleting the file we created. We do this because, while it's */
/* possible for the copy to fail halfway through and the File Manager */
/* doesn't really clean up that well in that case, we *really* don't want */
/* any half-created files being left around. */
/* if the file already existed, we don't want to delete it */
if( err == noErr ) {
/* if everything was fine, then return the new file Spec/Ref */
if( outFileRef != NULL ) *outFileRef = dest;
}
else if ( !destFileExists ) myverify_noerr(FSDeleteObject(&dest));
if ( buffer ) free(buffer);
return err;
}
OSStatus _split_path(const char* path, FSRef *outParentFolder, HFSUniStr255 *outName)
{
char tmpPath[ PATH_MAX ],
*tmpNamePtr;
OSErr err;
/* Get local copy of incoming path */
strcpy( tmpPath, (char*)path );
/* Get the name of the object from the given path */
/* Find the last / and change it to a '\0' so */
/* tmpPath is a path to the parent directory of the */
/* object and tmpNamePtr is the name */
tmpNamePtr = strrchr( tmpPath, '/' );
if ( tmpNamePtr == NULL )
getcwd(tmpPath, sizeof(tmpPath));
else
{
if( *(tmpNamePtr + 1) == '\0' )
{ /* in case the last character in the path is a / */
*tmpNamePtr = '\0';
tmpNamePtr = strrchr( tmpPath, '/' );
}
*tmpNamePtr = '\0';
tmpNamePtr++;
}
/* Get the FSRef to the parent directory */
err = FSPathMakeRef( (unsigned char*)tmpPath, outParentFolder, NULL );
if( err == noErr )
{ /* Convert the name to an HFSUniStr255 */
CFStringRef tmpStringRef = CFStringCreateWithCString( kCFAllocatorDefault, tmpNamePtr ? tmpNamePtr : path, kCFStringEncodingUTF8 );
if( tmpStringRef != NULL )
{
outName->length = CFStringGetLength(tmpStringRef);
if ( outName->length > sizeof(outName->unicode)/sizeof(UniChar) )
err = bdNamErr;
if ( err == noErr ) CFStringGetCharacters(tmpStringRef, CFRangeMake(0, outName->length), outName->unicode);
CFRelease( tmpStringRef );
}
else err = coreFoundationUnknownErr;
}
return err;
}
struct CarbonWDFixup
{
short savedVRefNum;
long savedDirID;
CarbonWDFixup()
{
HGetVol(NULL, &savedVRefNum, &savedDirID);
char* wd = getcwd(NULL, 0);
if ( wd ) {
FSRef wdref;
if ( FSPathMakeRef((const UInt8*)wd, &wdref, NULL) == noErr ) {
FSCatalogInfo catInfo;
if ( FSGetCatalogInfo(&wdref, kFSCatInfoNodeID|kFSCatInfoVolume, &catInfo, NULL, NULL, NULL) == noErr ) {
HSetVol(NULL, catInfo.volume, catInfo.nodeID);
}
}
free(wd);
}
}
~CarbonWDFixup()
{
HSetVol(NULL, savedVRefNum, savedDirID);
}
};
} // end of anonymous namespace
#if DEBUG
const char* GetCarbonWDPath()
{
static char path[16384];
short vRefNum;
long dirID;
OSStatus err;
err = HGetVol(NULL, &vRefNum, &dirID);
if ( err == noErr ) {
FSSpec fspec = { vRefNum, dirID, {0} };
FSRef fref;
err = FSpMakeFSRef(&fspec, &fref);
if ( err == noErr ) {
err = FSRefMakePath(&fref, (UInt8*)path, sizeof(path));
if ( err ) sprintf(path, "<error %d getting path>", err);
} else sprintf(path, "<error %d creating FSRef>", err);
} else sprintf(path, "<error %d>", err);
return path;
}
#endif
int mac_copy_file(const char* from, const char* to, int force_overwrite, int must_exist)
{
OSStatus err;
FSRef fromRef, toParentRef;
HFSUniStr255 toName;
//CarbonWDFixup fixCarbonWD;
TRACE(1, "mac_copy_file(%s, %s, %d, %d)", from, to, force_overwrite, must_exist);
#if DEBUG
TRACE(1, "Carbon Working Directory = %s", GetCarbonWDPath());
TRACE(1, "getcwd() = %s", getcwd(NULL, 0));
#endif
// get source FSRef
err = FSPathMakeRef( (unsigned char*) from, &fromRef, NULL );
if ( err ) {
if ( must_exist )
error (1, 0, "cannot access %s for copying: error %d", from, (int)err);
else
return -1;
}
// and destination FSRef (i.e. target parent folder) & name
err = _split_path(to, &toParentRef, &toName);
if ( err ) error (1, 0, "bad target %s for copying: error %d", to, (int)err);
// remove file before attempting to copy if force-overwriting, since FSCopyObject may not do this properly
if (force_overwrite && CVS_UNLINK(to) && !existence_error (errno))
error (1, errno, "unable to remove %s", to);
// now copy all forks
err = CopyFile(fromRef, toParentRef, NULL, &toName);
if ( err ) error (1, 0, "error while trying to copy file %s to %s: error %d", from, to, (int)err);
}
syntax highlighted by Code2HTML, v. 0.9.1