Home » Learning Curve » Developers Workshop
Core Rot: cfprefsdMore inexplicable expected behaviour. By Joel Bruner.
So, I've been keeping busy, no time for blog posts... However thought you'd find this interesting because while it affects me on the scripting side, it also is at the heart of the APIs that you well know Apple has been constantly fiddling with to the point of uncertainty.
(Maybe I will just post this long rambling thing as a blog post...)
So as of 10.8 writing to a plist with anything but /usr/bin/defaults (at least out here in script-land) is not guaranteed to actually change the plist!
From:
https://developer.apple.com/library/mac/#releasenotes/CoreFoundation/CoreFoundation.html
CFPreferences Plist Files
The on-disk property list files used by CFPreferences have always been a private implementation detail, but in previous releases, directly modifying them has mostly worked (though there are some potential data-loss issues for applications that do so, even on previous systems). In 10.8 and later, the CFPreferences agent process (cfprefsd) will cache information from these files and asynchronously write to them. This means that directly modifying plist files is unlikely to have the expected results (new settings will not necessarily be read, and may even be overwritten). You should use the NSUserDefaults or CFPreferences APIs, or the defaults(1) command, to interact with the preferences system.
[Note: There are several warning lights already there. 'Potential data loss'? 'Mostly worked'? Ed.]
This means PlistBuddy, the only other Apple tool one can use on plists with an even greater degree of control to dig into heavily nested dicts and arrays, is not guaranteed to work anymore!
So the new daemon that is caching these plist values to memory and flushing them out every so often is cfprefsd.
CFPREFSD(8) BSD System Manager's Manual CFPREFSD(8)
NAME
cfprefsd -- defaults server
SYNOPSIS
cfprefsd
DESCRIPTION
cfprefsd provides preferences services for the CFPreferences and NSUserDefaults APIs.
There are no configuration options to cfprefsd manually.
Mac OS X October 25th, 2011 Mac OS X
And it has been an issue for developers too, since last summer:
http://www.cocoabuilder.com/archive/cocoa/317915-cfpreferences-and-mountain-lion.html
I was made aware of this when my loginhooks script that runs though various scripts that do various things at login, one of which is a Dock icon adder, started having the effect of totally obliterating the Dock! Only the last set of icons to be added would survive. The script is addicontodock and it runs as the user logging in, and yes the script uses defaults, yet cfprefsd just murders it. Eliminating the extra logic from addicontodock, I made a script with two lines to test:
/private/etc/hooks/loginhooks/addChess.sh
#!/bin/bash
su $1 -c "defaults write /Users/$1/Library/Preferences/com.apple.dock persistent-apps -array-add '<dict><key>tile-data</key><dict><key>file-data</key><dict><key>_CFURLString</key><string>/Applications/Chess.app</string><key>_CFURLStringType</key><integer>0</integer></dict></dict></dict>'"
# defaults write com.apple.loginwindow LoginHook /private/etc/hooks/loginhooks/addChess.sh
So this runs when you log in. It adds Chess.app to a new temp file, then renames the file to the existing Dock file. It has the effect of killing everything else in the dock:
# fs_usage -w -f filesys | grep -i dock
11:18:47.274882 stat64 /System/Library/LaunchAgents/com.apple.Dock.plist 0.000005 launchctl.3016008
11:18:47.274899 open F=5 (R_____) /System/Library/LaunchAgents/com.apple.Dock.plist 0.000011 launchctl.3016008
11:18:50.615559 lstat64 /Users/test/Library/Preferences/com.apple.dock.plist 0.000039 ls.3016177
11:18:50.616592 lstat_extended64 /Users/test/Library/Preferences/com.apple.dock.plist 0.000009 ls.3016177
11:18:50.616599 listxattr /Users/test/Library/Preferences/com.apple.dock.plist 0.000004 ls.3016177
11:18:50.619907 lstat64 /Users/test/Library/Preferences/com.apple.dock.plist 0.000011 ls.3016178
11:18:50.621076 lstat_extended64 /Users/test/Library/Preferences/com.apple.dock.plist 0.000009 ls.3016178
11:18:50.621082 listxattr /Users/test/Library/Preferences/com.apple.dock.plist 0.000004 ls.3016178
11:18:51.030309 open F=26 (R_____) /Users/test/Library/Preferences/com.apple.dock.plist 0.000025 cfprefsd.3016087
11:18:51.030410 open F=4 (R_____) /Users/test/Library/Preferences/com.apple.dock.plist 0.000018 defaults.3016194
11:18:51.030933 open F=4 (R_____) /Users/test/Library/Preferences/com.apple.dock.plist 0.000007 defaults.3016194
11:18:51.040173 open F=27 (RWC__E) /Users/test/Library/Preferences/com.apple.dock.plist.Ay8KJ7w 0.009082 W cfprefsd.3016087
11:18:51.124549 rename com.apple.dock.plist.Ay8KJ7w 0.042862 W cfprefsd.3016087
So I had previously copied in a new default dock and had ls running in a loop in an ssh shell. Here's the before/after:
-rw------- 1 test staff 4469 Feb 21 11:17 /Users/test/Library/Preferences/com.apple.dock.plist
-rw------- 1 test staff 348 Feb 21 11:18 /Users/test/Library/Preferences/com.apple.dock.plist
$ defaults read com.apple.dock
{
"persistent-apps" = (
{
"tile-data" = {
"file-data" = {
"_CFURLString" = "/Applications/Chess.app";
"_CFURLStringType" = 0;
};
};
}
);
}
Yes, that's the entire contents of the Dock following this, it's not much to look at, later a daemon comes by and adds icon data, changes the _CFURLString (prepending file://localhost/) and adds the Downloads folder, but - woopty do! All your icons are gone!
What seems to be happening is that when this script executes as a loginhook, cfprefsd has not even been initialized for that user (an instance is run for every user including nobody, _lp, mdworker, etc) and is not aware of anything being in the Dock plist because it hasn't cached it to memory yet, so it just adds the array to nothing, and flushes it to disk, done - next!
The workaround is not only to have the script sleep 5-15 seconds for cfprefsd gets its bearings, but to also make a proxy script that calls the script with an '&' so that all the other login processes initialize while the add chess script itself is sleeping!
My master loginhook script actually does run the scripts with an '&', so then all I had to do was add the sleep, but GEEZ is that really necessary?! cfprefsd needs to take the hippocratic oath and if unsure of WTF is going on, then double-check and do no harm!
This is the change:
/private/etc/hooks/loginhooks/addChess.sh
#!/bin/bash
su $1 -c "sleep 5; defaults write /Users/$1/Library/Preferences/com.apple.dock persistent-apps -array-add '<dict><key>tile-data</key><dict><key>file-data</key><dict><key>_CFURLString</key><string>/Applications/Chess.app</string><key>_CFURLStringType</key><integer>0</integer></dict></dict></dict>'"
# defaults write com.apple.loginwindow LoginHook /private/etc/hooks/loginhooks/addChessProxy.sh
/private/etc/hooks/loginhooks/addChessProxy.sh
#!/bin/bash
/private/etc/hooks/loginhooks/addChess.sh $1 &
Anyway, I've just been nibbling away at this, first I found the workaround, because what was essential was trying to figure what's going on, to perhaps avoid and maybe even file a bug report, just for the heck of it.
Yet another workaround I just discovered while writing this, which is probably what Apple will tell me to do if they reply to the bug report, is to make the script a launchd user agent, which is then loaded at login, but after all other user processes are initialized it seems and things are not trashed. So just another example of new middle managers being introduced into the ever expanding bureaucracy of OS X.
PlistBuddy started its life bundled inside iTunes installers where it was called by the postflight scripts. 10.5 saw it being stashed at the unsearched path /usr/libexec, but at least it was there (in the OS). And after getting used to its quirky command line, it became my go to plist tweaker.
But I think it's more of a Apple hobby/quick and dirty tool than anything and probably forgotten by anyone hired in the past few years. Who are probably the same authors of cfprefsd. Also note the man page for defaults and the BUGS section which admits of its limitations, there since its first appearance.
Joel Bruner is a Macintosh Systems Administrator for a marketing conglomerate with 250+ Macs in his charge. His blog is found at http://brunerd.com.
|