Re: Load launchd job immediately, without restart - how?
Re: Load launchd job immediately, without restart - how?
- Subject: Re: Load launchd job immediately, without restart - how?
- From: Dimitris Roilidis <email@hidden>
- Date: Mon, 29 Sep 2008 16:23:13 +0300
(Oops, hit reply and forgot to change the address to cocoa-dev)
Thanks for the tip! This looks like what I had in mind, I'll give it a
spin.
Regarding the "original" route I mentioned, there is a programmatic
way to interface directly with launchd. Look in /usr/include/launch.h;
there's also some Apple sample code (http://developer.apple.com/samplecode/SampleD/
). However, after looking through the header and the sample, it seems
like an awful lot of trouble - and code - for such a minor task
(loading/unloading a job).
I find it very strange that it is so easy to register a launchd job,
yet so difficult to actually load it.
As an aside, I found Lingon (http://lingon.sourceforge.net/) to be
extremely useful when working with launchd plists.
D. Roilidis
On Sep 29, 2008, at 2:58 PM, Jerry Krinock wrote:
On 2008 Sep, 28, at 7:49, Dimitris Roilidis wrote:
I have a PrefPane that acts as a ui for a command line utility
(helper). According to the launchd docs, ...To load the helper
immediately ... the alternative possibly involves a script to call
launchctl
Well, I wouldn't call it an "alternative". I believe it is the only
way to do it. If anyone knows a better way, please let us know.
but how would this work
Unfortunately, it looks like a job for NSTask. Try the function
SSYLoadLaunchdJob given below and let me know if it works. I'll
need to do the same thing soon.
and more importantly, how would it be integrated into the PrefPane
code?
Insert the call to SSYLoadLaunchctlJob() any time after you have
written your plist file.
the sporadic comments google turned up on the subject say it's not
worth the trouble.
I'd like to read those comments if you could find the links in your
browser history.
// *** Declarations with HeaderDoc comments
/*!
@function SSYLoadLaunchdJob
@abstract Invokes the system's launchctl command to "load" a
launchd job
@discussion This function blocks until the task completes.
@param plistPath The full path to the .plist file defining the
job,
in one of the LaunchAgents directories.
@result The result of the launchctl command which was returned
by the system
*/
int SSYLoadLaunchdJob(NSString* plistPath) ;
/*!
@function SSYDoShellTask()
@abstract A wrapper around NSTask to launch a command-line process
@discussion Only use this function after you have searched far and
wide for a Cocoa, CoreFoundation,
Carbon, or any built-in API to do what you want to do. That is
because this function will spawn
another process which often leads to trouble. Use it sparingly.
Examine the return value,
stdOut_p and stdErr_p and write code to recover from errors.
@param command The command, not including its arguments. A
full path to the desired tool
is recommended. Example: @"/bin/launchctl"
@param arguments The array of arguments which should be passed
with the command. Each element
of the array should be an NSString, one of the space-separated
"words" that you would type on the
command line if you were performing this task via Terminal.app. For
example, to perform the task
/bin/launchctl -load /Users/me/LaunchAgents/MyTask.plist
The 'command' would be @"/bin/launchctl/" and the 'arguments' would
be an array of two strings,
@"-load" and @"/Users/me/LaunchAgents/MyTask.plist" in that order.
If the command does not use
a space between its argument "letter" and its text, for example "-
oPath/To/Output", this would
be entered as a single string element in 'arguments'.
If the command has no arguments, pass nil. Arguments can be very
tricky. For example, I have
never found a way to pass in pipe redirects. I tried this
suggestion once:
http://www.cocoabuilder.com/archive/message/cocoa/2005/2/24/129019
but could not get it to work.
@param inDirectory The working directory in which the command
will be launched. You
may pass nil. In that case, the tasks's current directory is
inherited from this process, which,
for applications, appears to be the root level of the startup
drive. Run command "pwd" if you
need to be sure. To avoid problems, I'd say never pass inDirectory
= nil unless you're giving
a full path in 'command'.
@param stdInData The stdin data to be passed to the command.
If nil, the tasks's standard
input is inherited from this process. I suppose that could be
interesting.
@param stdOutData_p If you want the stdout from the task, pass
an NSData*. On output it
will point to an NSData object containing the stdout. Otherwise,
pass NULL. In that case
the stdout location is inherited from the calling process and will
not be returned.
@param stdErrData_p If you want the stderr from the task, pass
an NSData*. On output it
will point to an NSData object containing the stderr. Otherwise,
pass NULL. In that case
the stderr location is inherited from the calling process and will
not be returned.
@param waitUntilExit If YES, this function blocks until the
task completes or aborts
and exits. If NO, this function will return immediately.
@result If waitUntilExit is YES, returns whatever the task
returns. If waitUntilExit is NO,
returns -999.
*/
int SSYDoShellTask(NSString* command,
NSArray* arguments,
NSString* inDirectory,
NSData* stdInData,
NSData** stdOutData_p,
NSData** stdErrData_p,
BOOL waitUntilExit) ;
// *** Implementations
int SSYLoadLaunchdJob(NSString* plistPath) {
NSArray* arguments = [NSArray arrayWithObjects:@"load",
plistPath, nil] ;
return SSYDoShellTask(@"/bin/launchctl/",
arguments,
nil,
nil,
NULL,
NULL,
YES) ;
}
int SSYDoShellTask(NSString* command,
NSArray* arguments,
NSString*inDirectory,
NSData* stdInData,
NSData** stdOutData_p,
NSData** stdErrData_p,
BOOL waitUntilExit) {
int taskResult = -999 ;
NSData* stdOutData = nil ;
NSData* stdErrData = nil ;
NSTask* task;
NSPipe* pipeStdIn = nil ;
NSPipe* pipeStdOut = nil ;
NSPipe* pipeStdErr = nil ;
NSFileHandle* fileStdIn = nil ;
NSFileHandle* fileStdOut = nil ;
NSFileHandle* fileStdErr = nil ;
// Each of the three NSFileHandles we are going to create
requires creation of an NSPipe,
// which according to documentation -fileHandleForReading is
released "automatically"
// when the NSPipe is released. Actually, I find that it is
autoreleased when the
// current autorelease pool is released, which is a little
different.
// To conserve system resources, therefore, we use a local pool
here.
// For more info,
// http://www.cocoabuilder.com/archive/message/cocoa/2002/11/30/51122
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init] ;
task = [[NSTask alloc] init] ;
[task setLaunchPath:command] ;
if (inDirectory)
[task setCurrentDirectoryPath: inDirectory] ;
if (arguments != nil)
[task setArguments: arguments] ;
if (stdInData)
{
pipeStdIn = [[NSPipe alloc] init] ;
fileStdIn = [pipeStdIn fileHandleForWriting] ;
[task setStandardInput:pipeStdIn] ;
}
if (stdOutData_p)
{
pipeStdOut = [[NSPipe alloc] init] ;
fileStdOut = [pipeStdOut fileHandleForReading] ;
[task setStandardOutput:pipeStdOut ] ;
}
if (stdErrData_p)
{
pipeStdErr = [[NSPipe alloc] init] ;
fileStdErr = [pipeStdErr fileHandleForReading] ;
[task setStandardError:pipeStdErr ] ;
}
NS_DURING
[task launch] ;
if ( [task isRunning] )
{
// Note: The following won't execute if no stdInData, since
fileStdIn will be nil
[fileStdIn writeData:stdInData] ;
[fileStdIn closeFile] ;
}
if (waitUntilExit)
{
if (stdOutData_p) {
stdOutData = [fileStdOut readDataToEndOfFile] ;
*stdOutData_p = stdOutData ;
}
if (stdErrData_p) {
stdErrData = [fileStdErr readDataToEndOfFile] ;
[stdErrData retain] ; // We retain this here so it
doesn't go away with local autorelease
*stdErrData_p = stdErrData ;
}
[task waitUntilExit] ;
taskResult = [task terminationStatus] ;
}
NS_HANDLER
// It would be nice to log [task terminationStatus] here, but
that launches another
// task, which will also fail if too many processes are
running, so don't do that.
NSLog(@"SSYDoShellTask: NSTask %@ %@ failed.", command,
arguments);
NSString* caption = [NSString stringWithFormat:@"%@: %@ %@",
[NSString localize:@"failed"],
command,
arguments ] ;
NSString* msg = [NSString localizeFormat:@"insufficient_",
[NSString localize:@"resources"]] ;
NSRunCriticalAlertPanel(caption, msg, [NSString
localize:@"quit"], nil, nil) ;
[NSApp terminate:nil] ;
NS_ENDHANDLER
[pipeStdIn release] ;
[pipeStdOut release] ;
[pipeStdErr release] ;
[task release] ;
if (gLogging >= 0) {
NSString* stdOutString = [[NSString alloc]
initWithData:stdOutData encoding:NSUTF8StringEncoding] ;
[stdOutString release] ;
}
// Retain autoreleased objects which we're going to need after
releasing the local pool
[stdOutData retain] ;
[stdErrData retain] ;
[pool release] ;
// After thus autoreleasing the locals, we autorelease the things
which will be needed by the caller
// By Obj-C convention, objects returned by reference are not
"alloced or copied", so we return them autoreleased.
[stdOutData autorelease] ;
[stdErrData autorelease] ;
return taskResult ;
}
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden