Starting/stopping a launchd agent for all users with GUI sessions
agent, daemon, launchd, launchdagent, macos
Solution
Using `launchctl bsexec` is correct, but you need to launch a wrapper tool which drops UID to the target user before running the 'real' agent executable. Oh, and it's probably better to look for `loginwindow` processes, as those are the leaders of the login sessions (though `launchd` is very likely to work too).
Problem
I need to be able to start/stop a per-session GUI agent from a root level daemon. Similar issues are discussed here, here and here. What I want to be able to do is basically ``` for num in `ps ax | grep [s]bin/launchd | cut -c 1-5`; do if [ $num -ne 1 ]; then sudo launchctl bsexec $num launchctl (un)load -S Aqua /Library/LaunchAgents/com.mycompany.mydaemon.plist; fi; done ``` but this only starts/stops one instance and it runs as root in the current GUI session. If I leave the sudo off there start I get ``` task_for_pid() (os/kern) failure Couldn't switch to new bootstrap port: (ipc/send) invalid port right ``` I've tried messing around with a variety of other permutations of bsexec (including calling a secondary script from bsexec with the load/unload command), but I can never get the instance to start as anything other than root and never in another GUI session. I also tried messing around with `su - <user> ...` and `sudo -u <user> ...`, but had no luck there either (as many people have discussed in the above linked articles and elsewhere). Does anybody have any thoughts? EDIT: I tried doing this with a wrapper tool as suggested below by Graham Lee, but I get the following error: ``` launch_msg(): Socket is not connected ``` This is the command line command, wrapper, and script I'm using (501 is the userid and 63093 the pid of launchd for another user logged in to the system): Command line: ``` sudo launchctl bsexec 63093 /path/TestSetUIDAndExecuteTool 501 /path/LoadBillingDialogAgent ``` Wrapper: ``` #import <Foundation/Foundation.h> int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; if (argc != 3) { NSLog(@"Tool called with improper arguments"); return -1; } int uid = [[NSString stringWithUTF8String:argv[1]] intValue]; // TODO: REMOVE NSLog(@"Setting uid to |%i|", uid); setuid(uid); // TODO: REMOVE char *command = (char *)argv[2]; NSLog(@"Executing command |%s|", command); system(command); [pool drain]; return 0; } ``` Script: ``` /bin/launchctl load -S Aqua /Library/LaunchAgents/com.company.agent.plist ```