NSFileWrapper data loss bug in Foundation on macOS Tahoe 26.4.1

There appears to be a data loss bug in NSFileWrapper on macOS 26.4. It may have been around longer but I just never noticed ...

So I write a RTFD file wrapper:

NSFileWrapper *rtfd = [attributedString RTFDFileWrapperFromRange:NSMakeRange(0, attributedString.length) documentAttributes:@{NSDocumentTypeDocumentAttribute:NSRTFDTextDocumentType}];

Now IF I use -writeToURL:options:originalContentsURL:error: without using the NSFileWrapperWritingAtomic option and I pass in an existing URL, the followings happens:

-The method returns NO and populates the NSError with NSFileWriteFileExistsError, as expected. This is what I want.

-BUT the existing file is nuked. It just disappears. Foundation kills it. Poof.

Another thing I gotta workaround. Getting pretty ridiculous, I must say. Just my lucky day I guess. It would be wonderful if I could work on my own features and fixing my own bugs.

Answered by DTS Engineer in 884222022

I wouldn’t characterise this as a bug. If you set the atomic flag then the file wrapper will either work or, if something goes wrong, leave the destination unchanged. The system has to jump through extra hoops to provide this guarantee.

OTOH, if you don’t set that flag then you’re telling the system that you don’t care about the state destination in the error case. The system is free to choose whatever writing techniques that it finds convenient. This is a valid choice if, for example, you’re writing to a temporary file, but it doesn’t look like that’s the case for you.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I wouldn’t characterise this as a bug. If you set the atomic flag then the file wrapper will either work or, if something goes wrong, leave the destination unchanged. The system has to jump through extra hoops to provide this guarantee.

OTOH, if you don’t set that flag then you’re telling the system that you don’t care about the state destination in the error case. The system is free to choose whatever writing techniques that it finds convenient. This is a valid choice if, for example, you’re writing to a temporary file, but it doesn’t look like that’s the case for you.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

OTOH, if you don’t set that flag then you’re telling the system that you don’t care about the state destination in the error case. The system is free to choose whatever writing techniques that it finds convenient.

From the perspective of an app developer, it certainly is interesting for a save operation to fail with NSFileWriteFileExistsError while simultaneously deleting the existing file because the system finds it convenient! If you're going to chuck the file anyway is there really a point of failing after that?

In any case this was a breaking change (it definitely wasn't always this way) and wasn't documented (as far as I know). But I guess my thread here helps serve that purpose and I was just "lucky" before.

you’re writing to a temporary file

Given how it works now, if you don't 100% know you own the path you have no choice but to write a temp since if an error occurs the system reserves the right to remove existing files at its convenience. That behavior probably should be documented IMO.

So it sounds like a long shot I guess but I filed FB22492490 anyway because I don't really understand the point of failing with NSFileWriteFileExistsError if the existing file isn't going to be protected. Also suggest in the FB that documentation should be added.

Is there some specific reason you’re trying to avoid setting the atomic flag?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Well when I first wrote this code (probably ~8 years ago or so), using the atomic flag with RTFD file wrapper would overwrite an existing file. Without the atomic flag, the operation would fail if the file exists but the existing file would remain (which is my desired behavior). At some point this changed and now not specifying the atomic flag, does not overwrite an existing file as I already mentioned but for some reason fails while also discarding the existing file. I cannot think of a situation where that behavior makes any sense or is useful other than to make it easy to write code that causes accidental data loss.

If overwriting is always desired (atomic or not) then an existing file shouldn't ever be an error reason I don't think. I guess I'm grumpy about the behavior changing unannounced in a way that impacts the behavior of apps. If the plan is to keep it this way going forward I think a big bold note should be added to the documentation "if you try to save to a URL that already exists, the operation will fail and the file will disappear").

In any case I've modified my code to write to a temp file first, and then try to move it afterwards. I guess you could argue that this is a better design and I'd agree. Perhaps I should have always done it this but the way I had it wasn't "wrong" until the rug got pulled.

NSFileWrapper data loss bug in Foundation on macOS Tahoe 26.4.1
 
 
Q