macoscocoansfilemanagermacos-carbonapfs

What is the Cocoa method for doing the Carbon FSExchangeObjectsCompat call?


There was this great function in the old MoreFilesX, FSExchangeObjectsCompat, that "exchanges the data between two files". It was typically used as part of a safe-save approach, where a temp file was written out, then FSExchangeObjectsCompat was called to exchange the newly-saved temp file with the old "original" file. It preserved all the metadata, privileges, etc.

I'm seeing a failure with this function on High Sierra, on APFS volumes, which never failed on HFS+ volumes. Not a big surprise -- many of those calls are deprecated.

But what is the Cocoa NSFileManager method of doing the same thing?


Solution

  • You can do something similar using lower-level functions. Here's code I wrote to be used with a pre-10.12 SDK. You can make it somewhat simpler if you compile against the 10.12 SDK or later, and even simpler if you have a deployment target that is 10.12 or later.

    #ifndef RENAME_SWAP
    #define RENAME_SWAP    0x00000002
    #endif
    
    /*!
        @function   ExchangeFiles
    
        @abstract   Given full paths to two files on the same volume,
                    swap their contents.
    
        @discussion This is often part of a safe-save strategy.
    
        @param      inOldFile   Full path to a file.
        @param      inNewFile   Full path to a file.
        @result     0 if all went well, -1 otherwise.
    */
    int ExchangeFiles( const char* inOldFile, const char* inNewFile )
    {
        int result = -1;
        static dispatch_once_t sOnce = 0;
        static renameFuncType sRenameFunc = NULL;
        // Try to get a function pointer to renamex_np, which is available in OS 10.12 and later.
        dispatch_once( &sOnce,
            ^{
                sRenameFunc = (renameFuncType) dlsym( RTLD_DEFAULT, "renamex_np" );
            });
    
        // renamex_np is only available on OS 10.12 and later, and does not work on HFS+ volumes
        // but does work on APFS volumes.  Being the latest and greatest, we try it first.
        if (sRenameFunc != NULL)
        {
            result = (*sRenameFunc)( inOldFile, inNewFile, RENAME_SWAP );
        }
    
        if (result != 0)
        {
            // exchangedata is an older function that works on HFS+ but not APFS.
            result = exchangedata( inOldFile, inNewFile, 0 );
        }
    
        if (result != 0)
        {
            // Neither function worked, we must go old school.
            std::string nameTemplate( inOldFile );
            nameTemplate += "-swapXXXX";
            // Make a mutable copy of the template
            std::vector<char>   workPath( nameTemplate.size() + 1 );
            memcpy( &workPath[0], nameTemplate.c_str(), nameTemplate.size() + 1 );
            mktemp( &workPath[0] );
            std::string tempPath( &workPath[0] );
    
            // Make the old file have a temporary name
            result = rename( inOldFile, tempPath.c_str() );
    
            // Put the new file data under the old name.
            if (result == 0)
            {
                result = rename( inNewFile, inOldFile );
            }
    
            // Put the old data under the new name.
            if (result == 0)
            {
                result = rename( tempPath.c_str(), inNewFile );
            }
        }
    
        return result;
    }