Keep a NSTask Session Running - Cocoa - objective-c

I run a simple grep command in my Cocoa app like so:
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: #"/usr/bin/grep"];
NSArray *arguments;
arguments = [NSArray arrayWithObjects: #"foo", #"bar.txt", nil];
[task setArguments: arguments];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
NSData *data;
data = [file readDataToEndOfFile];
NSString *string;
string = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog (#"grep returned:\n%#", string);
[string release];
[task release];
However, I am somewhat curious to know how commands entered through terminal, which don't give out an output and are not executed promptly unless exited with something like Control + C can be run with this technique. Something like running java -jar server.jar where it keeps running until quit out of the session. How would I do something like that where the session is not automatically ended once the command has been launched?
Would I just need to comment out the part where it releases the NSTask? Any suggestions would be nice!

When using NSTask with an underlying program that doesn’t exit immediately:
You shouldn’t use -[NSFileHandle readDataToEndOfFile] since there is no end of file. Instead, you should use -[NSFileHandle readInBackgroundAndNotify] to read the standard output pipe of that task in the background, and be notified when data is available;
You should use -[NSTask release] only when you’ve determined that the task shouldn’t run any more. In that case, prior to releasing the task, you should send its standard input the command that causes the underlying program to exit (e.g. the characters equivalent to control-d), or send it -terminate or -interrupt.
You shouldn’t use -waitUntilExit unless you’ve spawned a secondary thread to deal with that task.

Related

NSTask - Respond to input request from OpenSSL

It seems i'm having some trouble understanding NSTask in Cocoa. The application that I want to launch is openSSL. Currently, I'm able to send the information (launch path, arguments etc.) and I can get a response as well using NSPipe. What I need to be able to do though is to respond to the input requests that the application asks for. With the following code, I can send and read a response from a file:
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: launchPath];
[task setArguments: arguments];
[task setCurrentDirectoryPath:dir];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
NSData *data;
data = [file readDataToEndOfFile];
NSString *response = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
Once I launch the NSTask, I'm expected to provide things like the Domain Name, Country etc. The reason for my question is because I need to be able to generate a Certificate Signing Request with openSSL and send it, along with some other data, over to a server. The code above is not broken, I'm just not sure how I can go about sending over that input.
Also, if anyone has used some sort of openSSL implementation with Cocoa/ObjC and feels that it would be a better option than using NSTask, I'm completely open to that as well.
Thanks in Advance.
Not that anybody is looking at this, but if you are and didn't already know the answer, I found the solution but ended up using a different method. Instead of sending additional input, I would just pass the -subj parameter. However, the solution to what I was originally asking is as follows:
NSTask *task = [[NSTask alloc] init];
NSString *tmpdir=NSTemporaryDirectory();
[task setCurrentDirectoryPath:tmpdir];
[task setLaunchPath:#"/usr/bin/openssl"];
NSArray *sslarguments=#[#"req",#"-nodes",#"-newkey",#"rsa:2048",#"-keyout",#"myserver.key",#"-out",#"server.csr"];
[task setArguments:sslarguments];
NSPipe * in = [NSPipe pipe];
[task setStandardInput:in];
NSData *data=[#"GB\nYorks\n\nYork\SimuplanSL\nIT\nsomeone#simuplan.com" dataUsingEncoding:NSUTF8StringEncoding];
[task launch];
[[in fileHandleForWriting] writeData:data];
[task waitUntilExit];
I just had to write to a file in a temporary directory and feed it through the input pipe.

Executing a command from Objective C

I want to execute a command from objective C (Cocoa framework). The command I am trying is as below. I tried with NSTask but it says "launch path not accessible" when I execute it.
sudo ifconfig en0 down
My code is:
- (void)testme {
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: #"sudo ifconfig en0 down"];
NSArray *arguments;
arguments = [NSArray arrayWithObjects: #"foo", #"bar.txt", nil];
[task setArguments: arguments];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
NSData *data;
data = [file readDataToEndOfFile];
NSString *string;
string = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog (#"command returned:\n%#", string);
[string release];
[task release];
}
sudo ifconfig en0 down is not a sensible launch path. The correct launch path for this command would be /usr/sbin/sudo.
Once that is done, you still need to pass the correct arguments to setArguments:. foo and bar.txt look like example code that you copied without reading.
MORE IMPORTANTLY, THOUGH, running sudo from NSTask will not work. You will need to use Authorization Services to launch a privileged command.
You need to specify the full executable path and you should specify the arguments as the arguments, not along with the launch path. NSTask ain't a shell, it internally uses syscalls (execv(), I guess) to invoke the command.
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:#"/usr/bin/sudo"];
NSArray *arguments = #[#"ifconfig", #"en0", #"down"];
[task setArguments:arguments];

How do I have an NSTextField constantly update its value based on the output of a command line argument?

I am trying to create a small rsync program in Cbjective-C. It presently accesses the terminal command line through an NSTask and reads the command line's output to a string that is displayed in a NSTextField; however, when I use this small program on a very large file (around 8 gb) it does not display the output until after the RSYNC is complete. I want the NSTextField to continually update while the process is running. I have the following code and am looking for ideas!:
-(IBAction)sync:(id)sender
{
NSString *sourcePath = self.source.stringValue;
NSString *destinationPath = self.destination.stringValue;
NSLog(#"The source is %#. The destination is %#.", sourcePath, destinationPath);
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath:#"/usr/bin/rsync"];
NSArray *arguments;
arguments = [NSArray arrayWithObjects: #"-rptWav", #"--progress", sourcePath, destinationPath, nil];
[task setArguments: arguments];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
// [task setStandardInput:[NSPipe pipe]];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
NSData *data;
data = [file readDataToEndOfFile];
while ([task isRunning])
{
NSString *readString;
readString = [[NSString alloc] initWithData: data encoding:NSUTF8StringEncoding];
textView.string = readString;
NSLog(#"grep returned:\n%#", readString);
}
}
OK, the issue is with the way in which you are reading the data from the pipe. You are using:
NSData *data = [file readDataToEndOfFile];
Which will read everything written by the child process in one go, up until the pipe closes (when the child process has terminated).
What you need to do is read a character at-at-time and reconstruct the output line. You also want to use non-blocking mode, so that your main UI thread isn't interrupted when there is no data to read (better still, this should be done in a background thread so that the main UI thread remains completely uninterrupted).

Execute SSH Command on remote Linux Machine using Native Mac App. (Obj-C)

I have a Mac Native app written with Xcode. I want to execute some SSH command using that application on remote servers and get the result back to user.
Is there any library/Framework exist for that? Is that possible?
You will want to use the NSTask class to execute an ssh command.
The following code was adapted from the answer to this question.
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: #"/usr/bin/ssh"]; // Tell the task to execute the ssh command
[task setArguments: [NSArray arrayWithObjects: #"<user>:<hostname>", #"<command>"]]; // Set the arguments for ssh to contain only your command. If other configuration is necessary, see the ssh(1) man page.
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
NSFileHandle *file;
file = [pipe fileHandleForReading]; // This file handle is a reference to the output of the ssh command
[task launch];
NSData *data;
data = [file readDataToEndOfFile];
NSString *string;
string = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding]; // This string now contains the entire output of the ssh command.

Call shell script with argument in Mac application

I'm having trouble calling a shell script which takes an argument from my Cocoa application for Mac.
I have created the shell script, and put it in the app's local repository. It is called SCRIPT. It takes one argument which is a URL address.
I call the script as follows but nothing happens, no errors or messages, just the script stops after doing nothing.
NSString *address = [_addressField stringValue];
NSString *resPath = [[NSBundle mainBundle] resourcePath];
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: [NSString stringWithFormat:#"%#/SCRIPT", resPath]];
NSArray *arguments;
arguments = [NSArray arrayWithObjects: address, nil];
[task setArguments: arguments];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
NSData *data;
data = [file readDataToEndOfFile];
NSString *status;
status = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog (#"%#", status);
Thanks in advance everyone
The Objective-C code seams to work fine for me if i try with /bin/echo as launch path. So i guess the problem is with the script. Can you include the script in your question? note that the environment when running from a Cocoa application is probably quite different from when running in a interactive shell.
Could it be a permissions issue? Try invoking /bin/sh and setting the script path as the first argument.