Saturday, October 07, 2006

LaunchServices From a root Daemon?

At the end of this post I mention that I'd like to find a way to start a process in the console user's session, from a root process in the startup item context. I do not know of a documented way to do this, but to be clear, there is currently (at least as of 10.4.8) a way to do it simply using LaunchServices.

LaunchServices is the recommended way to launch an application on OS X. When you double-click an item in the dock, it's launched using LaunchServices. When you double-click on a PDF file, it is LaunchServices that figures out that Preview.app is the best candidate to handle that file type (because you certainly don't have Adobe Acrobat installed), it opens Preview.app and tells it to open said file.

LaunchServices is a sub-framework the ApplicationServices umbrella framework, which is not daemon safe according to TN2083. "Daemon safe" means that daemon processes running in the root bootstrap context (aka startup item context) are allowed to link with and use the framework. One reason a framework would not be daemon safe, is if it uses the WindowServer process.

The WindowServer is not only in charge of managing windows on screen, it's also intimately involved with process management (with some help from the loginwindow process). As the matter a fact, when you launch an application it will ultimately be the WindowServer that does a fork() and exec() to start your process. You can use ps to see that the WindowServer is indeed the parent for most of your processes.

$ ps jaxww | grep WindowServe[r]
windowse 59 1 59 .../CoreGraphics.framework/Resources/WindowServer -daemon
$ ps jx | awk '{if ($3 == 59) print}'
jgm 132 59 ... /System/Library/CoreServices/Dock.app/Contents/MacOS/Dock -psn_0_393217
jgm 134 59 ... /System/Library/CoreServices/SystemUIServer.app/Contents/MacOS/SystemUIServer -psn_0_524289
jgm 136 59 ... /System/Library/CoreServices/Finder.app/Contents/MacOS/Finder -psn_0_655361
jgm 139 59 ... /Applications/Google Notifier.app/Contents/MacOS/Google Notifier -psn_0_786433
jgm 197 59 ... /Applications/Utilities/Terminal.app/Contents/MacOS/Terminal -psn_0_1572865
jgm 257 59 ... /Applications/Safari.app/Contents/MacOS/Safari -psn_0_2490369

(Note that if you look at the Parent Process field in Activity Monitor, it will incorrectly report the Dock as the parent of processes that were launched by clicking their dock icon. This is incorrect, and can be verified by look at the output with ps.)

OK, back on topic. So we know the WindowServer is important to launching processes. Actually when we launch a process in typical Cocoa fashion using the NSWorkspace class, LaunchServices is invoked behind the scenes to send a message to the WindowServer via mach messages, requesting that the WindowServer fork() up a new process in the console user's session. So in order to use LaunchServices (or NSWorkspace), we need to be able to communicate with the WindowServer, which is the reason why LaunchServices (and therefore ApplicationServices and therefore Cocoa) is not "daemon safe".

However, as TN2083 points out, there is a "global window server" reference available in the startup item context. This window server reference appears to be an artifact from the past as Quinn explains in the Technote:

The reasons for this non-obvious behavior are lost in the depths of history. However, the fact that this works at all is pretty much irrelevant because there are important caveats that prevent it from being truly useful.

But the fact remains that there is a reference, and we can see it with the Bootstrap dump command.
$ sudo /usr/libexec/StartupItemContext ~/bin/BootstrapDump  | grep Window
"com.apple.windowserver" by "/System/Library/.../Resources/WindowServer"


However, the only users allowed to connect to the WindowServer process are root and the console user. (The console user is the "current" user in a fast-user-switched environment. The console user is the owner of the /dev/console device.) But if we're running as root we should be able to connect to the window server using standard LaunchServices calls. Let's try.

$ cat -n launch.m
1 #import <Cocoa/Cocoa.h>
2 int main(void) {
3 [[NSWorkspace sharedWorkspace]
4 launchApplication:@"TextEdit"];
5 return 0;
6 }
$ gcc -o launch launch.m -framework Cocoa
$ sudo /usr/libexec/StartupItemContext ./launch

This will start TextEdit in my current user session. We can use Bootstrap dump again to verify that the TextEdit process is indeed running in my session, but it is. The command /usr/bin/open links against Cocoa, and works the same way, so I can simply type sudo /usr/libexec/StartupItemContext /usr/bin/open -a TextEdit to do the same thing.

As I mentioned above, only the console user and root are allowed to connect to the WindowServer process, so if your daemon is running as nobody (for example), it won't be able to do this.

$ sudo /usr/libexec/StartupItemContext /usr/bin/sudo -u nobody -s
$ open -a TextEdit
kCGErrorRangeCheck : Window Server communications from outside of session allowed for root and console user only
INIT_Processeses(), could not establish the default connection to the WindowServer.Abort trap

(The first command above starts a shell running as user nobody in the startup item context.)

So, we can see that from the startup item context, as root, we are able to launch a process in the console user's session, simply using normal LaunchServices calls. But note that this is undocumented behavior, and it is not guaranteed to work at any point in the future. It merely worked in this example.

6 comments:

Anonymous said...

"So in order to use LaunchServices (or NSWorkspace), we need to be able to communicate with the WindowServer, which ..."

Is this also true for the use of LSUIElement or LSBackgroundOnly? (May be not exactly on-topic. Just curious.)

http://developer.apple.com/documentation/Carbon/Conceptual/LaunchServicesConcepts/index.html

Greg said...

First off, I'm not sure, but I'll venture a guess anyway. Given that LaunchServices will start a process in a user's session, I'd think that any applications would work since they're actually running in a user session and would have access to all the services available to that user. A quick test with the Google Notifier, which is an LSUIElement, seems to work:
sudo /usr/libexec/StartupItemContext /usr/bin/open -a "Google Notifier"

Anonymous said...

There is a pretty informative overview on Execution Context Summary in Daemons and Agents at

http://developer.apple.com/technotes/tn2005/tn2083.html#SECCONTEXTCROSSREF

Maybe a future improvement of launchd agents will do the trick, maybe in some way combined with man StartupItemContext or man service.

Anyway, informative read!

Emanuele "∞" Vulcano said...

The global window server connection trick is what allows ssh session to use open to run applications and osascript to control them (I do it all the time for MAMP or similar).

Anyway, the blessed, Apple-guaranteed-to-always-work way of doing things is having an agent running in the session that communicates in an Apple-sanctioned way (ie UNIX-domain sockets) with the daemon. The agent should then do the launching.

sternr said...

"The WindowServer is not only in charge of managing windows on screen, it's also intimately involved with process management"
... "You can use ps to see that the WindowServer is indeed the parent for most of your processes"
Hmmm a strong deja vu for Explorer.exe on a different OS... ;)

admin said...

I am Very thank full the owner of this blog. Becouse of this blog is very imformative for me.. And I ask u some thiing You make more this type blog where we can get more knowledge.
thanks you very high work..
www.r10.net küresel ısınmaya hayır seo yarışması
www.r10.net küresel ısınmaya hayır seo yarışması
www.r10.net küresel ısınmaya hayır seo yarışması