Send value from NSTextField to constantly running NSTask - objective-c

There have been a couple of NSTask related questions, but after paging through them I still have no idea what to do.
I'm writing a frontend for a java server in Cocoa, launched by java -Xmx1024M -Xms1024M -jar server.jar nogui (I've left out the nogui arg in my current code so as not to fill up my computer with unnecessary orphaned instances of server).
My current code correctly runs the .jar file; now I need a way to capture (and parse) output and send input to the process.
server = [[NSTask alloc] init];
pipe = [NSPipe pipe];
NSArray *args = [NSArray arrayWithObjects:#"-Xms1024M",
#"-Xmx1024M",
#"-jar",
#"server.jar",
nil];
[server setLaunchPath:#"/usr/bin/java"];
[server setCurrentDirectoryPath:#"MyApp.app/Contents/Resources/"];
[server setArguments:args];
[server setStandardOutput:pipe];
[server setStandardInput:pipe];
[server launch];
I've read up on NSPipe and NSTask and everything, but I can't seem an answer geared towards my problem:
Live, parsed (RegEx?) output to NSTextView or NSTableView.
Input from NSTextField
EDIT: Or should I use launchd? How would I do that?

You need to make two pipes: one for the task's standard input, and another for the task's standard output. What you're doing right now connects the task's output to its own input.
Something like this:
#interface ServerController : NSObject
#property (strong) NSFileHandle *standardInput;
#property (strong) NSFileHandle *standardOutput;
#end
#implementation ServerController
...
- (void)launchServer {
NSPipe *standardInputPipe = [NSPipe pipe];
self.standardInput = standardInputPipe.fileHandleForWriting;
NSPipe *standardOutputPipe = [NSPipe pipe];
self.standardOutput = standardOutputPipe.fileHandleForReading;
...
server.standardInput = standardInputPipe;
server.standardOutput = standardOutputPipe;
[server launch];
}
...
Now you can write to the server by sending the writeData: message to the standardInput property of the ServerController instance. To read from the server, you'll want to use either readInBackgroundAndNotify or readabilityHandler on the standardOutput property.

Related

Run user defined command with NSTask

I would like to execute a terminal command specified by the user. For example, the user might write killall "TextEdit" or say "Hello world!" in a text field, and I want to execute that command.
NSTask is the way to go, except I have two problems with it:
First: the arguments. Right now I'm doing this:
NSArray* args = [commandString componentsSeparatedByString: #" "];
[task setArguments: [args subarrayWithRange: NSMakeRange(1, [args count] - 1)]]; // First one is the command name
Is this the way to do it? I don't think I've had problems with this yet, but I doesn't look like it's safe. Imagine this: the user writes killall 'Address Book' but the command receives as arguments 'Address and Book'?? That doesn't work. So, what should I do instead? How can I safely parse the arguments?
Second: the launch path. It's much more user-friendly to only have to write the name of the command, instead of the complete path to it. So I want to support that, which means finding out programmatically the full path for a command having only it's name. For that I wrote a category on NSTask like this:
+ (NSString*)completePathForExec: (NSString*)exec
{
NSTask* task = [[NSTask alloc] init];
NSPipe* pipe = [[NSPipe alloc] init];
NSArray* args = [NSArray arrayWithObject: exec];
[task setLaunchPath: #"/usr/bin/which"];
[task setArguments: args];
[task setStandardOutput: pipe];
[task setStandardError: pipe];
[task launch];
[task waitUntilExit];
NSFileHandle* file = [pipe fileHandleForReading];
NSString* result = [[NSString alloc] initWithData: [file readDataToEndOfFile] encoding: NSASCIIStringEncoding];
if ([result length]) {
if ([result hasSuffix: #"\n"]) { result = [result substringWithRange: NSMakeRange(0, [result length] - 1)]; }
return result;
}
else { return exec; }
}
This seems to works well. However, how can I be sure that this path: /usr/bin/which will always work? I mean: will it work on 10.6, 10.7, 10.8, etc? I think I had a problem once where the path to a shell command changed with the system version, and you can never be too careful.
If the path is guaranteed to stay the same, then this isn't a problem. If it changes, then how can I know the 'path to the path-finder'?
It'll be far easier for you to not re-invent the command line parsing wheel. But, of course, going down the route of executing arbitrary user entered code is a security nightmare (tempered by the fact that the user has access to the system and, thus, could probably just run Terminal directly).
Specifically, have NSTask wrap an invocation of one of the shells with the command line option to have it execute an arbitrary string.
sh -c "ls -alF"
This would allow you to pass the path to sh as your launch path, which is in a fixed location on every system. The #"-c" argument tells sh to parse the next argument as a script and, of course, the next argument is whatever the user entered.
Note, this will also give the user the ability to pipe stuff, too.

Write to NSTasks standard input after launch

I am currently trying to wrap my head around the hole NSTask, NSPipe, NSFileHandle business. So I thought I write a little tool, which can compile and run C code. I also wanted to be able to redirect my stdout and stdin to a text view.
Here is what I got so far.
I used code from this post to redirect my stdio: What is the best way to redirect stdout to NSTextView in Cocoa?
NSPipe *inputPipe = [NSPipe pipe];
// redirect stdin to input pipe file handle
dup2([[inputPipe fileHandleForReading] fileDescriptor], STDIN_FILENO);
// curInputHandle is an instance variable of type NSFileHandle
curInputHandle = [inputPipe fileHandleForWriting];
NSPipe *outputPipe = [NSPipe pipe];
NSFileHandle *readHandle = [outputPipe fileHandleForReading];
[readHandle waitForDataInBackgroundAndNotify];
// redirect stdout to output pipe file handle
dup2([[outputPipe fileHandleForWriting] fileDescriptor], STDOUT_FILENO);
// Instead of writing to curInputHandle here I would like to do it later
// when my C program hits a scanf
[curInputHandle writeData:[#"123" dataUsingEncoding:NSUTF8StringEncoding]];
NSTask *runTask = [[[NSTask alloc] init] autorelease];
[runTask setLaunchPath:target]; // target was declared earlier
[runTask setArguments:[NSArray array]];
[runTask launch];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:#selector(stdoutDataAvailable:) name:NSFileHandleReadCompletionNotification object:readHandle];
And here the stdoutDataAvailable method
- (void)stdoutDataAvailable:(NSNotification *)notification
{
NSFileHandle *handle = (NSFileHandle *)[notification object];
NSString *str = [[NSString alloc] initWithData:[handle availableData] encoding:NSUTF8StringEncoding];
[handle waitForDataInBackgroundAndNotify];
// consoleView is an NSTextView
[self.consoleView setString:[[self.consoleView string] stringByAppendingFormat:#"Output:\n%#", str]];
}
This Program is working just fine. It is running the C program printing the stdout to my text view and reading "123" from my inputPipe. Like indicated in my comment above I would like to provide the input while the task is running once it is needed.
So there are two questions now.
Is there a way to get a notification as soon as somebody tries to read data from my inputPipe?
If the answer to 1 is no, is there a different approach I can try? Maybe using a class other than NSTask?
Any help, sample code, links to other resources are highly appreciated!
I'm not sure whether you can detect a "pull" on an NSPipe. I do have a vague sense that polling for write-availability with select() or using kqueue to look for I/O availability events on the underlying file descriptor of your NSFileHandle might do the trick, but I'm not very familiar with using those facilities in this way.
Do you have to support arbitrary C programs, or is it a special daemon or something you've developed?
If it's your own program, you could watch for requests for feedback on outputPipe, or just blast input onto the inputPipe as you find out what it is you want to send, and let the C program consume it when it's ready; if it's somebody else's code, you may be able to hook scanf and friends using a link-time method (since it's code you're compiling) like the one described in Appendix A-4 of:
http://www.cs.umd.edu/Library/TRs/CS-TR-4585/CS-TR-4585.pdf
The gist of it is to make a .dylib with your custom I/O functions (which may send some sigil to your app indicating that they need input), link that into the built program, set an environment variable (DYLD_BIND_AT_LAUNCH=YES) for the launched task, and run it. Once you've got those hooks in, you can provide whatever conveniences you want for your host program.

How do I get something similar to Tail -f using NSTask

I need to read the last added line to a log file, in realtime, and capture that line being added.
Something similar to Tail -f.
So my first attempt was to use Tail -f using NSTask.
I can't see any output using the code below:
NSTask *server = [[NSTask alloc] init];
[server setLaunchPath:#"/usr/bin/tail"];
[server setArguments:[NSArray arrayWithObjects:#"-f", #"/path/to/my/LogFile.txt",nil]];
NSPipe *outputPipe = [NSPipe pipe];
[server setStandardInput:[NSPipe pipe]];
[server setStandardOutput:outputPipe];
[server launch];
[server waitUntilExit];
[server release];
NSData *outputData = [[outputPipe fileHandleForReading] readDataToEndOfFile];
NSString *outputString = [[[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding] autorelease];
NSLog (#"Output \n%#", outputString);
I can see the output as expected when using:
[server setLaunchPath:#"/bin/ls"];
How can i capture the output of that tail NSTask?
Is there any alternative to this method, where I can open a stream to file and each time a line is added, output it on screen? (basic logging functionality)
This is a little tricky to do your way, as readDataToEndOfFile will wait until tail closes the output stream before returning, but tail -f never closes the output stream (stdout). However, this is actually pretty simple to do with basic C I/O code, so I whipped up a simple FileTailer class that you can check out. It's not anything fancy, but it should show you how it's done. Here're the sources for FileTailer.h, FileTailer.m, and a test driver.
The meat of the class is pretty simple. You pass it a block, and it reads a character from the stream (if possible) and passes it to the block; if EOF has been reached, it waits a number of seconds (determined by refresh) and then tries to read the stream again.
- (void)readIndefinitely:(void (^)(int ch))action
{
long pos = 0L;
int ch = 0;
while (1) {
fseek(in, pos, SEEK_SET);
int ch = fgetc(in);
pos = ftell(in);
if (ch != EOF) {
action(ch);
} else {
[NSThread sleepForTimeInterval:refresh];
}
}
}
You can call it pretty simply, like this:
FileTailer *tail = [[[FileTailer alloc] initWithStream:stdin refreshPeriod:3.0] autorelease];
[tail readIndefinitely:^ void (int ch) { printf("%c", ch); }];
(Caveat: I wrote the FileTailer class pretty fast, so it's kind of ugly right now and should be cleaned up a bit, but it should serve as a decent example on how to read a file indefinitely, à la tail -f.)
Here's a way to use "tail -f logfile" via NSTask in Objective-C:
asynctask.m -- sample code that shows how to implement asynchronous stdin, stdout & stderr streams for processing data with NSTask
...
Being a GUI-less application (i.e. a Foundation-based command line tool), asynctask.m runs an NSRunLoop manually
to enable the use of asynchronous "waitForDataInBackgroundAndNotify" notifications. In addition, asynctask.m
uses pthread_create(3) and pthread_detach(3) for writing more than 64 KB to the stdin of an NSTask.
Source code of asynctask.m available at: http://www.cocoadev.com/index.pl?NSPipe

Is there an Instruments API?

Is it possible to setup an Instruments run programmatically from my code? For instance, I'd like to structure my code something like this where startTrace might setup a specific probe for the current thread and start recording while stopTrace would stop recording. I would be writing the content of those routines using the Instruments API that is the subject of this question.
-(void)myInterestingMethod
{
[self startTrace];
// do something interesting and performance critical
[self stopTrace];
}
If the above isn't available, is setting up my own DTrace probe a viable alternative?
Doesn't look like there's anything straight-forward, but there is an instruments command-line tool. Here's some quick+dirty code that will invoke it and sample CPU usage for the calling process
static void sampleMe() {
// instruments -t '/Developer/Applications/Instruments.app/Contents/Resources/templates/CPU Sampler.tracetemplate' -p 26838 -l 5000
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:#"/usr/bin/instruments"];
[task setArguments:[NSArray arrayWithObjects:
#"-t",
#"/Developer/Applications/Instruments.app/Contents/Resources/templates/CPU Sampler.tracetemplate",
#"-p",
[NSString stringWithFormat:#"%ld", getpid()],
#"-l",
#"5000",
nil]];
[task setCurrentDirectoryPath:NSHomeDirectory()];
[task setStandardInput:[NSPipe pipe]];
[task setStandardOutput:[NSPipe pipe]];
[task setStandardError:[NSPipe pipe]];
[task launch];
// purposely leak everything since I can't be bothered to figure out lifetimes
}
After invocation a file named instrumentscli0.trace will be in your home directory.
Update: Instruments 4.0 offers DTSendSignalFlag in the DTPerformanceSession for iOS apps.

Registering for a NSFileHandleReadToEndOfFileCompletionNotification

I'm trying to figure out how to make this piece of code from a previous question work, but I'm stuck on the part on how to 'register' a NSFileHandleReadToEndOfFileCompletionNotification.
This is my code:
NSTask *topTask = [NSTask new];
[topTask setLaunchPath:#"/usr/bin/top"];
[topTask setArguments:[NSArray arrayWithObjects:#"-s", #"1", #"-l", #"3600", #"-stats", #"pid,cpu,time,command", nil]];
NSPipe *outputPipe = [NSPipe pipe];
[topTask setStandardOutput:outputPipe];
[topTask launch];
... which runs fine until I add this:
[[outputPipe fileHandleForReading] readToEndOfFileInBackgroundAndNotify];
... which causes the program to freeze. And when I add this:
NSString *outputString = [[[NSString alloc] initWithData:[[notification userInfo] objectForKey:NSFileHandleNotificationDataItem] encoding:NSUTF8StringEncoding] autorelease];
... the code doesn't compile and I get the !warning
'notification' undeclared.
Any assistance on this matter earns copious amounts of gratitude on my behalf.
Running top with those parameters in the command line will cause it to continually print out stats and it will never write EOF. That's why -readToEndOfFileInBackgroundAndNotify runs forever.
It sounds like you may just want to read the first iteration and then kill the task.
As for the error, it sounds like you simply don't have a variable named notification in the method containing that line.