Programmatic interface to launchctl and some other questions, OS X 10.5
Hello I am writing a Cocoa application, that installs and launches an agent (and, if needed - stops that agent, and uninstalls it). This agent is supposed to watch for a launch of another certain application, and if it encounters such an event, it launches some process itself. I am wondering, how could i programmatically register (launchctl's load operation), start (launchctl's start), stop and unload the agent, doing these operations from my Cocoa application? The target operating systems are Mac OS X 10.5 and 10.6. Having searched through the Internet i have found ServiceManagement.h (that allows, as i understand, installing application into /Library/LaunchAgents, even if the application that called SMJobBless/SMJobSumbin was non-root). I am not very "familiar" with OS X ideas. As i understand, when a user doubleclicks my Cocoa application's bundle, the application is launched with non-root privileges, which means i can't "launchctl load" my agent into /Library/LaunchAgents - i can only do that with /Users/username/Library/LaunchAgents directory. But unfortunately this ServiceManagement API is supported only since OS X 10.6 Also there's a /usr/include/launch.h file, which provides some kind of API, but i've no idea how to use it, and i am not sure if it gives any means to load/start/stop/unload agents. Maybe i could use it somehow? Most suggestions about programmatical management of agents are about calling "[NSTask launchProcess:@"launchctl {operation} {label}"]". If there is no other better choice, i'd take it, but i am a little confused about privileges - i won't be able to install an agent to /Library/LaunchAgents, will I?. I'm sorry if i'm saying nonsense, but if i call "[NSTask launchProcess:@"sudo launchctl {operation} {label}"]" will it count as a launch from root, or this trick works only if its done in a terminal? how can i determine, if my agent is running or not, from my Cocoa application? and how could i determine if it's registered (loaded, in terms of launchctl) or not? Calling [NSTask launchProcess:@"launchctl list | grep com.mycompany.myagent"] and then parsing the returned string seems wrong to me :/. Is it really the only choice? I was also searching for the info on how to launch a process, that is a console one (Gui-less). Most suggestions are about using launchd instead of fork/exec-ing it. How is it done usually? Should i necessarily create and save that *.plist to the /Users/username/Library/LaunchAgents/ directory and then call that "[NSTask launchProcess:@"sudo launchctl {operation} {label}"]"?, first with "launchctl load" and second with "launchctl start"? OS X 10.6's ServiceManagement.h API seems to support launching of processes without first copying plist to the one of the "agents'" directories, but again it's only OS X 10.6. Sorry for so many questions :) And Thanks a lot for the response!
On 2010 Oct 25, at 18:01, eveningnick eveningnick wrote:
As i understand, when a user doubleclicks my Cocoa application's bundle, the application is launched with non-root privileges, which means i can't "launchctl load" my agent into /Library/LaunchAgents - i can only do that with /Users/username/Library/LaunchAgents directory.
That is correct.
Also there's a /usr/include/launch.h file, which provides some kind of API, but i've no idea how to use it, and i am not sure if it gives any means to load/start/stop/unload agents. Maybe i could use it somehow?
Anyone know what he's talking about?
Most suggestions about programmatical management of agents are about calling "[NSTask launchProcess:@"launchctl {operation} {label}"]". If there is no other better choice, i'd take it
That's how I do it. It is sad, and a chore.
but i am a little confused about privileges - i won't be able to install an agent to /Library/LaunchAgents, will I?.
That is correct. You'd have to write a Privileged Helper Tool based on Better Authorization Sample to do that, which is a *really* big chore. You might find it a little easier if you use my framework… http://sheepsystems.com/sourceCode/authTasksCocoa.html
I'm sorry if i'm saying nonsense, but if i call "[NSTask launchProcess:@"sudo launchctl {operation} {label}"]" will it count as a launch from root, or this trick works only if its done in a terminal?
Nice try :) But that sudo won't work because there is no UI for the user to type in their credential. Sorry, you need a Privileged Helper Tool.
how can i determine, if my agent is running or not, from my Cocoa application? and how could i determine if it's registered (loaded, in terms of launchctl) or not? Calling [NSTask launchProcess:@"launchctl list | grep com.mycompany.myagent"] and then parsing the returned string seems wrong to me :/. Is it really the only choice?
That's the only choice that I know of. Again, this is quite sad.
I was also searching for the info on how to launch a process, that is a console one (Gui-less). Most suggestions are about using launchd instead of fork/exec-ing it. How is it done usually?
Well, yes. But launchd is *normally* used in this way, to launch gui-less tools. The path to your tool is the first element in the ProgramArguments array. If you wanted to launch an *app* with launchd, you'd need to use /usr/bin/open.
Should i necessarily create and save that *.plist to the /Users/username/Library/LaunchAgents/ directory and then call that "[NSTask launchProcess:@"sudo launchctl {operation} {label}"]"?, first with "launchctl load" and second with "launchctl start"?
Yes, but again, that sudo ain't going to work, but if you are launching an agent from ~/Library/LaunchAgents, you don't need sudo. Also, I use "launchctl load" but I've never even heard of "launchctl start". Sounds like it might be the same as putting RunAtLoad into the plist? That's what I do. You should appreciate that to use system-domain agents (/Library/LaunchAgents) instead of user-domain agents (~/Library/LaunchAgents), you are entering the realm of security and privileges, and greatly complicating your life. However, if you really need to do this, address questions in these areas to apple-cdsa@lists.apple.com.
On 26 Oct 2010, at 03:55, Jerry Krinock wrote:
On 2010 Oct 25, at 18:01, eveningnick eveningnick wrote:
Also there's a /usr/include/launch.h file, which provides some kind of API, but i've no idea how to use it, and i am not sure if it gives any means to load/start/stop/unload agents. Maybe i could use it somehow?
Anyone know what he's talking about?
The routines in <launch.h> provide necessary services for launchd jobs (daemons and agents). Specifically, a launchd job uses this API to check in with launchd. See SampleD for an example of this. <http://developer.apple.com/library/mac/#samplecode/SampleD/> If you need to manipulate the launchd state, you should use Service Management (if it's available and does what you want) or simply fork/exec launchctl. And yes, it is both sad and a chore )-:
how can i determine, if my agent is running or not, from my Cocoa application? and how could i determine if it's registered (loaded, in terms of launchctl) or not? Calling [NSTask launchProcess:@"launchctl list | grep com.mycompany.myagent"] and then parsing the returned string seems wrong to me :/. Is it really the only choice?
That's the only choice that I know of. Again, this is quite sad.
The approach I usually use here (for example, this is what BAS shows) is to have my launchd job provide a service, and then try to connect to that service. If the connection works, the job must be installed properly. If the connection fails, some corrective action is necessary. S+E -- Quinn "The Eskimo!" <http://www.apple.com/developer/> Apple Developer Relations, Developer Technical Support, Core OS/Hardware
Thank you for the answer!
If you need to manipulate the launchd state, you should use Service Management (if it's available and does what you want) or simply fork/exec launchctl. And yes, it is both sad and a chore )-:
The approach I usually use here (for example, this is what BAS shows) is to have my launchd job provide a service, and then try to connect to that service. If the connection works, the job must be installed properly. If the connection fails, some corrective action is necessary.
Could you tell a little more about what kind of services do you provide usually and how do you respond to them? Is it something like AppleEvents? Or some kind of user-signals?
Well, yes. But launchd is *normally* used in this way, to launch gui-less tools. The path to your tool is the first element in the ProgramArguments array. If you wanted to launch an *app* with launchd, you'd need to use /usr/bin/open.
But how should it be done? Do i need to save a plist file to /users/myuser/library/launchagents? or can i have it dynamically? As i understand for now, to correctly launch a GUI-less tool (for example, ls), i need: 1) generate plist file (using NSDictionary, for example - and correctly fill ProgramArguments field there) 2) save that plist file somewhere on disc (for example, some kind of tmp folder) 3) call fork/exec with params launchctl load "a/path/to/my/saved/plist/folder" 4) call fork/exec with params launchctl start "my.justloaded.nongui.app.label" 5) if i want to terminate that application, i have to fork/exec launchctl unload "my.justloaded.nongui.app.label" 6) then i have to delete that temporary plist file (at what moment of program execution?) Is it the only right way? Sounds like a bit of overhead.. Could you comment these steps? Thanks again!
On 26 Oct 2010, at 12:52, eveningnick eveningnick wrote:
Could you tell a little more about what kind of services do you provide usually and how do you respond to them? Is it something like AppleEvents? Or some kind of user-signals?
For a launchd daemon, I usual communicate with the daemon via a UNIX domain socket. So, in the client application, I just create a UNIX domain socket and try to connect it to the daemon's socket path. If that succeeds, I know that the daemon is running. For an agent, you can do similar things but you have to use the "SecureSocketWithKey" property to ensure that each agent has its own unique socket path (although there are some wacky edge cases for the first install case). See <x-man-page://5/launchd.plist> for details. Alternatively, you can register a Mach port using the "MachServices" property. This works nicely for both the daemon and the agent case, but Mach IPC is kinda ugly in my experience. * * * It would really help if you could explain your high-level goal in high-level terms. Right now I think we're getting bogged down in the details, without a clear understanding of the overall problem. Specifically: o Are you trying to install your agent for a specific user, or for all users? o Once installed, how does your agent get uninstalled? o If you are trying to install your agent for all users, do you care about the fast user switched case? That is, when there are multiple users logged in via fast user switching, do you care whether users other than the one installing your software get the agent straight away? o What are you trying to do with daemons? o Do your agents (and daemons for that matter) have any command and control interface? If so, what are you planning to use for that? S+E -- Quinn "The Eskimo!" <http://www.apple.com/developer/> Apple Developer Relations, Developer Technical Support, Core OS/Hardware
Uff, i'm terribly sorry, i have sent the mail not to the mailing list, but to you privately. Thought to resend it here, maybe it'll help someone in future as well :) Hello Quinn Thanks for the answer!
It would really help if you could explain your high-level goal in high-level terms. Right now I think we're getting bogged down in the details, without a clear understanding of the overall problem. Specifically:
o Are you trying to install your agent for a specific user, or for all users?
Now i narrowed down my goals to just installing an agent for a specific user, it is supposed to eliminate a lot of troubles
o Once installed, how does your agent get uninstalled?
there's an another, Control application. It executes "launchctl load", "launchctl start" and copies plist file to "/users/username/library/launcagents/" when user presses "Start the agent" checkbox, and "launchctl unload", "rm /users/username/library/launcagents/com.mycompany.myagent.plish" when user deactivates the checkbox. Though i am also wondering about things: 1) what if my agent binary is moved to trash bin, while the agent is still loaded and started (up and running)? 2) what if the agent is "installed" (or, rather, loaded in launchd terminology) with the param "RunAtLoad:TRUE", and the user deletes the binary? Will launchd just skip my agent, when the system is started? Or will it delete my agent's plist as a "false one"? Or.. what will happen?
o If you are trying to install your agent for all users, do you care about the fast user switched case? That is, when there are multiple users logged in via fast user switching, do you care whether users other than the one installing your software get the agent straight away?
o What are you trying to do with daemons?
an agent watches for process starts in a carbon event loop, and if a process with a specific name is started, the agent launched the third process (a plugin). Maybe it's not the best implementation, but launchd unfortunately doesn't provide services to start an application on the event "Another application has started".
o Do your agents (and daemons for that matter) have any command and control interface? If so, what are you planning to use for that?
no, the agent is very simple. Perhaps i will add MachPort IPC functionality as you have suggested, to identify that the agent is loaded from the control application I am wondering, if from the launchctl sources (i've heard they are available) i could extract the code to load/start/unload/list the agents, instead of calling the "launchctl" terminal application with fork/exec and waiting in the pipe for their result (and parsing that result). What i mean is - of course, launchctl and launchd can have changed with time, and my application will become invalid, but so can the launchctl's console output, that i'm parsing. And, for example, when i expect the text "12345 1 com.mycompany.myagent" to be returned with "launchctl list | grep", i may get something different and think "Oh well, my agent is not running yet, let's start it again" (this is just an example). Why is this solution considered to be better than calling API's directly? P.S. Could you please look at this question too, if you have some time? :)
But how should it be done? Do i need to save a plist file to /users/myuser/library/launchagents? or can i have it dynamically? As i understand for now, to correctly launch a GUI-less tool (for example, ls), i need: 1) generate plist file (using NSDictionary, for example - and correctly fill ProgramArguments field there) 2) save that plist file somewhere on disc (for example, some kind of tmp folder) 3) call fork/exec with params launchctl load "a/path/to/my/saved/plist/folder" 4) call fork/exec with params launchctl start "my.justloaded.nongui.app.label" 5) if i want to terminate that application, i have to fork/exec launchctl unload "my.justloaded.nongui.app.label" 6) then i have to delete that temporary plist file (at what moment of program execution?)
Is it the only right way? Sounds like a bit of overhead.. Could you comment these steps?
Regards, George
On Oct 26, 2010, at 5:49 AM, eveningnick eveningnick wrote:
I am wondering, if from the launchctl sources (i've heard they are available) i could extract the code to load/start/unload/list the agents, instead of calling the "launchctl" terminal application with fork/exec and waiting in the pipe for their result (and parsing that result). What i mean is - of course, launchctl and launchd can have changed with time, and my application will become invalid, but so can the launchctl's console output, that i'm parsing. And, for example, when i expect the text "12345 1 com.mycompany.myagent" to be returned with "launchctl list | grep", i may get something different and think "Oh well, my agent is not running yet, let's start it again" (this is just an example). Why is this solution considered to be better than calling API's directly?
Perhaps these will be of use? http://code.google.com/p/google-toolbox-for-mac/source/browse/trunk/Foundati... http://code.google.com/p/google-toolbox-for-mac/source/browse/trunk/Foundati... Cheers, Dave
On Oct 26, 2010, at 2:51 PM, Dave MacLachlan wrote:
On Oct 26, 2010, at 5:49 AM, eveningnick eveningnick wrote:
I am wondering, if from the launchctl sources (i've heard they are available) i could extract the code to load/start/unload/list the agents, instead of calling the "launchctl" terminal application with fork/exec and waiting in the pipe for their result (and parsing that result). What i mean is - of course, launchctl and launchd can have changed with time, and my application will become invalid, but so can the launchctl's console output, that i'm parsing. And, for example, when i expect the text "12345 1 com.mycompany.myagent" to be returned with "launchctl list | grep", i may get something different and think "Oh well, my agent is not running yet, let's start it again" (this is just an example). Why is this solution considered to be better than calling API's directly?
Perhaps these will be of use?
http://code.google.com/p/google-toolbox-for-mac/source/browse/trunk/Foundati... http://code.google.com/p/google-toolbox-for-mac/source/browse/trunk/Foundati...
Be careful when using this. It will not work for submitting jobs which have Socket services. -- Damien Sorresso BSD Engineering Apple Inc.
On Oct 26, 2010, at 3:05 PM, Damien Sorresso wrote:
On Oct 26, 2010, at 2:51 PM, Dave MacLachlan wrote:
On Oct 26, 2010, at 5:49 AM, eveningnick eveningnick wrote:
I am wondering, if from the launchctl sources (i've heard they are available) i could extract the code to load/start/unload/list the agents, instead of calling the "launchctl" terminal application with fork/exec and waiting in the pipe for their result (and parsing that result). What i mean is - of course, launchctl and launchd can have changed with time, and my application will become invalid, but so can the launchctl's console output, that i'm parsing. And, for example, when i expect the text "12345 1 com.mycompany.myagent" to be returned with "launchctl list | grep", i may get something different and think "Oh well, my agent is not running yet, let's start it again" (this is just an example). Why is this solution considered to be better than calling API's directly?
Perhaps these will be of use?
http://code.google.com/p/google-toolbox-for-mac/source/browse/trunk/Foundati... http://code.google.com/p/google-toolbox-for-mac/source/browse/trunk/Foundati...
Be careful when using this. It will not work for submitting jobs which have Socket services.
Damien, could you expand on this comment a bit so I can fix it appropriately? Cheers, Dave
On Oct 26, 2010, at 3:05 PM, Damien Sorresso wrote:
On Oct 26, 2010, at 2:51 PM, Dave MacLachlan wrote:
On Oct 26, 2010, at 5:49 AM, eveningnick eveningnick wrote:
I am wondering, if from the launchctl sources (i've heard they are available) i could extract the code to load/start/unload/list the agents, instead of calling the "launchctl" terminal application with fork/exec and waiting in the pipe for their result (and parsing that result). What i mean is - of course, launchctl and launchd can have changed with time, and my application will become invalid, but so can the launchctl's console output, that i'm parsing. And, for example, when i expect the text "12345 1 com.mycompany.myagent" to be returned with "launchctl list | grep", i may get something different and think "Oh well, my agent is not running yet, let's start it again" (this is just an example). Why is this solution considered to be better than calling API's directly?
Perhaps these will be of use?
http://code.google.com/p/google-toolbox-for-mac/source/browse/trunk/Foundati... http://code.google.com/p/google-toolbox-for-mac/source/browse/trunk/Foundati...
Be careful when using this. It will not work for submitting jobs which have Socket services.
Code should be fixed now. Thanks for pointing out the problem Damien. Cheers, Dave
participants (5)
-
Damien Sorresso
-
Dave MacLachlan
-
eveningnick eveningnick
-
Jerry Krinock
-
Quinn "The Eskimo!"