cocoaserializationplistaclhfs+

"writeToFile:atomically:" Fails on File with Certain ACL


If I have a writable file with an ACL-rule of deny delete, any [plistableObject writeFoFile:undeletableFile atomically:YES] call returns NO, whereas non-atomic writes succeed.

I know, that the atomic write means that a temporary file is written and — if written successfully — eventually renamed. This particular implication of it feels odd, though.
So I wonder, is this due to...

  1. lack of a direct 'rename' in HFS+,
  2. a deficiency in the implementation of -[NS(Array|Dictionary|Data|String) writeToFile:atomically:] or
  3. a deficiency in the implementation of ACLs in Mac OS X?

Thanks in Advance

Daniel


Original question:
I've found this odd behavior the other day on a Mac I restored from a backup:
Most applications were unable to persist their preferences — especially Mail.app which warned with an error message, suggesting that it was unable to write to ~/Library/Preferences.

Digging deeper, I found that — somehow — most plists had an ACL with the directive group:everyone deny delete in place; ditching this rule saved the day.

I suspected NSArray|NSDictionary|NSWhatHaveYou's writeToFile:atomically: to be responsible* for this behavior and — sure enough — the test-tool I wrote only succeeds when passed NO as the second argument if the file exists and has such an ACL in place...

(* where by "responsible" I only mean the not-writing-part; the ACL situation was something else entirely)

So I wonder:

Is this a bug or a feature?

While — technically — this method writes a file and upon completion renames it, from a user perspective it is not deleting anything...

If it's a bug:
Should it be filed against NSArray and friends or against the implementation of ACLs?

Any thoughts much appreciated!

Cheers

Daniel


Solution

  • After some digging in the HFS source I've come to the conclusion that there is no such thing as a filesystem-native rename function in Mac OS.

    Instead, it is implemented as (pseudocode)

    link!
    successful:
       return 0
    // otherwise
    unlink!
    successful:
      link!
      return error_code
    // otherwise
    return error_code
    

    So this behavior is to be expected :-(

    That said, I don't know enough about either filesystems or low-level programming to decide whether the creation of a native rename would be worth the hassle implementing it.

    I strongly feel it would be the right thing to do so, but...


    Edit

    As jfortman pointed out, an atomic rename would actually be possible and pretty straight-forward through use of the following sequence:

    1. write to tempfile
    2. exchangedata( path_to_tempfile, path_to_destination_file, options ) (by the way: the manpage states that this function is around since Darwin 1.3.1/Mac OS X 10.0...)
    3. delete tempfile