NSData method writeToFile:atomically: isn't atomic

Originator:stevebyan
Number:rdar://13653232 Date Originated:15-Apr-2013 01:00 PM
Status:Open Resolved:
Product:OS X Product Version:10.8.3/12D78
Classification:Crash/Hang/Data Loss Reproducible:Rarely
 
Summary: 
The writeToFile:atomically: method of NSData is no longer atomic. The introduction of versioning seems to have broken it. Just before renaming the temporary file with the new contents to the name of the document, the versioning code renames the original document to the backup name, rather than copying it or creating a hard link. Consequently, a system crash, reset, or power fail after the versioning rename but before the rename of the temp file will leave the filesystem in a state where the document file does not exist. The "atomic" portion of "writeToFile:atomically:" is meant to avoid this state, but it does not.

Steps to Reproduce:
1. Open TextEdit. 
2. Enter some text. 
3. Start dtruss in a Terminal window on the TextEdit process. 
4. Switch back to TextEdit and hit "Cmd-S" to save.
5. After the save is complete, switch to the terminal window and hit Cntrl-C to terminate dtruss.

Expected Results:
I expect the dtruss output to contain the following sequence (clutter omitted, only signification syscalls shown):

open("/private/var/folders/xq/thqw6j0j3xj_99pw3xgk_qxm000gn_/T/com.apple.TextEdit/TemporaryItems/(A Document Being Saved By TextEdit)/foo.txt\0", 0x601, 0x1B6)		 = 8 0
write(0x8, "foobar\nbaz\nexisting text\nfoo\n\n\0", 0x1E)		 = 30 0
fsync(0x8, 0x7FC5A406E240, 0x0)		 = 0 0
close(0x8)		 = 0 0
...
lstat64("/Users/smb/work/projects/SNIA NVM Programming TWG/use cases/safe writes/foo.txt.sb-f0b0dec4-DnB01P\0", 0x10A849570, 0x8E430)		 = -1 Err#2
ln("/Users/smb/work/projects/SNIA NVM Programming TWG/use cases/safe writes/foo.txt\0", "/Users/smb/work/projects/SNIA NVM Programming TWG/use cases/safe writes/foo.txt.sb-f0b0dec4-DnB01P\0")		 = 0 0
rename("/private/var/folders/xq/thqw6j0j3xj_99pw3xgk_qxm000gn_/T/com.apple.TextEdit/TemporaryItems/(A Document Being Saved By TextEdit)/foo.txt\0", "/Users/smb/work/projects/SNIA NVM Programming TWG/use cases/safe writes/foo.txt\0")		 = 0 0


Actual Results:

open("/private/var/folders/xq/thqw6j0j3xj_99pw3xgk_qxm000gn_/T/com.apple.TextEdit/TemporaryItems/(A Document Being Saved By TextEdit)/foo.txt\0", 0x601, 0x1B6)		 = 8 0
write(0x8, "foobar\nbaz\nexisting text\nfoo\n\n\0", 0x1E)		 = 30 0
fsync(0x8, 0x7FC5A406E240, 0x0)		 = 0 0
close(0x8)		 = 0 0
...
lstat64("/Users/smb/work/projects/SNIA NVM Programming TWG/use cases/safe writes/foo.txt.sb-f0b0dec4-DnB01P\0", 0x10A849570, 0x8E430)		 = -1 Err#2
rename("/Users/smb/work/projects/SNIA NVM Programming TWG/use cases/safe writes/foo.txt\0", "/Users/smb/work/projects/SNIA NVM Programming TWG/use cases/safe writes/foo.txt.sb-f0b0dec4-DnB01P\0")		 = 0 0
rename("/private/var/folders/xq/thqw6j0j3xj_99pw3xgk_qxm000gn_/T/com.apple.TextEdit/TemporaryItems/(A Document Being Saved By TextEdit)/foo.txt\0", "/Users/smb/work/projects/SNIA NVM Programming TWG/use cases/safe writes/foo.txt\0")		 = 0 0

Regression:

Notes:
It's also notable that the temp file is not opened with the O_SYNC flag. It's possible that the lazy writer will asynchronously flush the data and get an EIO error on the write to the device. The initial write by the app and the following fsync and close would then return without errors, giving the application the illusion that the temp file was written successfully when it actually was not. The writeToFile:atomically: method would then rename the temp file to replace the current document, even though the temp file contains corrupted data.

Comments


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!