How do I run a binary from a Cocoa app? - objective-c

I'm making a simple launcher for a binary so I can have it appear in my launchpad.
My initial thought was that running system("./myProgram"); would be sufficient, but it doesn't appear to actually do anything as the terminal instance it runs doesn't stay open after running the command, immediately shutting down whatever other tasks the program did.
So my question is, is there a way for me to do this that keeps it open indefinitely?
Edit: I want my launcher to close immediately after launching the program, so it would be less than ideal to rely on something that requires it to stay open
Edit: the following all work, but only when run from xcode, when running it stand-alone it doesn't launch the program at all
system("open /Applications/Utilities/Terminal.app myProgram");
system("open myProgram");
system("/bin/sh -c ./myProgram&");
system("./myProgram&");
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath: #"/bin/bash"];
[task setArguments: #[#"-c", #"./myProgram"]];
[task launch];
NSTask does not give any errors, and it doesn't throw any exceptions either when the app runs
Literally every other aspect of the program works, it just won't launch, and it won't say why
Based on all the "feedback" here's what I got so far. And it still doesn't work unless I provide an absolute path (which is no good in case I want to move it later)
//
// AppDelegate.m
// DFLauncher
//
// Created by Electric Coffee on 11/02/15.
// Copyright (c) 2015 Electric Coffee. All rights reserved.
//
#import "AppDelegate.h"
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#end
NSString *CURRENT_DIR;
NSString *FILE_PATH;
NSString *INIT_PATH = #"/data/init/init.txt";
NSString *VOLUME_ON = #"[SOUND:YES]";
NSString *VOLUME_OFF = #"[SOUND:NO]";
BOOL contains(NSString *a, NSString *b) {
return [a rangeOfString: b].location != NSNotFound;
}
NSData *replaceString(NSString *fileContents, NSString *from, NSString *to) {
return [[fileContents stringByReplacingOccurrencesOfString: from withString: to]
dataUsingEncoding: NSUTF8StringEncoding];
}
#implementation AppDelegate
- (void)applicationDidFinishLaunching: (NSNotification *)aNotification {
CURRENT_DIR = [[NSFileManager new] currentDirectoryPath]; //[[NSBundle mainBundle] bundlePath];
//NSLog(#"%#", CURRENT_DIR);
FILE_PATH = [CURRENT_DIR stringByAppendingString: INIT_PATH];
_fileContents = [NSString stringWithContentsOfFile: FILE_PATH
encoding: NSUTF8StringEncoding
error: NULL];
if (contains(_fileContents, VOLUME_OFF))
[_toggleMute setState: YES];
if (contains(_fileContents, VOLUME_ON))
[_toggleMute setState: NO];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
}
- (IBAction)playButtonClick: (id)sender {
//system("open /Applications/Utilities/Terminal.app df"); // doesn't quite work
//system("open /Applications/df_osx/df");
//system("/bin/sh -c /Applications/df_osx/df&");
//system("/Applications/df_osx/df&");
NSString *gamePath = [CURRENT_DIR stringByAppendingString: #"/df&"];
NSTask *task = [NSTask new];
[task setLaunchPath: #"/bin/bash"];
[task setArguments: #[#"-c", gamePath]];
NSError *error = task.standardError;
[task launch];
[NSAlert alertWithError: error];
//[NSApp terminate: self];
}
- (IBAction)folderButtonClick: (id)sender {
system("open .");
}
- (IBAction)quitButtonClick: (id)sender {
[NSApp terminate: self];
}
- (IBAction)mute: (id)sender {
NSData *result;
NSFileManager *fm = [NSFileManager defaultManager];
if ([sender state] == NSOffState)
result = replaceString(_fileContents, VOLUME_OFF, VOLUME_ON);
else
result = replaceString(_fileContents, VOLUME_ON, VOLUME_OFF);
[fm createFileAtPath: FILE_PATH contents: result attributes: nil];
}
#end

Hacky solution that works (but isn't elegant at all)
I had to replace
FILE_PATH = [CURRENT_DIR stringByAppendingString: INIT_PATH];
With
CURRENT_DIR = [[[[NSBundle mainBundle] bundlePath] stringByDeletingPathExtension] stringByDeletingLastPathComponent];
To get the correct path for the file, but it works now.

Related

echo Does Not Work with NSTask and readInBackgroundAndNotify

I have the following Obj-C code and its log output. Can anyone tell me why I'm not getting any output from the NSFileHandle?
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[self performSelectorInBackground:#selector(startTask:) withObject:nil];
}
- (void) startTask: (id) sender
{
NSPipe *pipe = [[NSPipe alloc] init];
NSFileHandle *fh = pipe.fileHandleForReading;
[fh readInBackgroundAndNotify];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(output:) name:NSFileHandleReadCompletionNotification object:fh];
NSTask *echoTask = [[NSTask alloc] init];
echoTask.standardOutput = pipe;
echoTask.standardError = [[NSPipe alloc] init];
echoTask.launchPath = #"/bin/echo";
echoTask.arguments = #[#"hello world!"];
NSLog(#"launching...");
[echoTask launch];
[echoTask waitUntilExit];
NSLog(#"finished.");
}
- (void) output:(NSNotification *)notification
{
NSFileHandle *fh = notification.object;
NSLog(#"fh: %#", fh);
NSString *output = [[NSString alloc] initWithData:[fh readDataToEndOfFile] encoding:NSUTF8StringEncoding];
NSLog(#"output: '%#'", output);
}
#end
log:
2014-12-16 10:19:58.154 SubProcess2[14893:704393] launching...
2014-12-16 10:19:58.165 SubProcess2[14893:704393] fh: <NSConcreteFileHandle: 0x6080000e9e80>
2014-12-16 10:19:58.165 SubProcess2[14893:704393] output: ''
2014-12-16 10:19:58.166 SubProcess2[14893:704393] finished.
If I do it synchronously or using the approach in https://stackoverflow.com/a/16274541/1015200 I could get it to work.
Any other techniques and variations(such as launching task without performSelectorInBackground) have failed.
I really want to see if I can get it to work using the notification.
So if I can get any help that'd be great.
The data that has already been read is passed to the notification in the userInfo dictionary under the key NSFileHandleNotificationDataItem, you should be accessing that and not attempting to read further data. E.g. something like:
- (void) output:(NSNotification *)notification
{
NSString *output = [[NSString alloc]
initWithData:notification.userInfo[NSFileHandleNotificationDataItem]
encoding:NSUTF8StringEncoding];
NSLog(#"output: '%#'", output);
}
HTH

NSTask for SSH using PTY

I'm trying to write an app that will programmatically log in to a remote device using SSH much like an expect script (I know I can use expect but I would like to do this in Obj-c).
I have researched a lot on this and know that I need to use a pty. The code I have works fine for telnet but I can't seem to get ssh to work. It seems as though SSH is not using the pty to ask for the password. When I execute the following code I see the device asking for the password, but I don't see my NSLog output.
I'm very new to this and probably over my head, but I'd really appreciate anyone who can help me get this working.
#import <Foundation/Foundation.h>
#import <util.h>
#interface NSTask (PTY)
- (NSFileHandle *)masterSideOfPTYOrError:(NSError **)error;
#end
#implementation NSTask (PTY)
- (NSFileHandle *)masterSideOfPTYOrError:(NSError *__autoreleasing *)error {
int fdMaster, fdSlave;
int rc = openpty(&fdMaster, &fdSlave, NULL, NULL, NULL);
if (rc != 0) {
if (error) {
*error = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil];
}
return NULL;
}
fcntl(fdMaster, F_SETFD, FD_CLOEXEC);
fcntl(fdSlave, F_SETFD, FD_CLOEXEC);
NSFileHandle *masterHandle = [[NSFileHandle alloc] initWithFileDescriptor:fdMaster closeOnDealloc:YES];
NSFileHandle *slaveHandle = [[NSFileHandle alloc] initWithFileDescriptor:fdSlave closeOnDealloc:YES];
self.standardInput = slaveHandle;
self.standardOutput = slaveHandle;
return masterHandle;
}
#end
int main(int argc, const char * argv[])
{
#autoreleasepool {
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:#"/usr/bin/ssh"];
[task setArguments:#[#"user#192.168.1.1"]];
NSError *error;
NSFileHandle *masterHandle = [task masterSideOfPTYOrError:&error];
if (!masterHandle) {
NSLog(#"error: could not set up PTY for task: %#", error);
exit(0);
}
[task launch];
[masterHandle waitForDataInBackgroundAndNotify];
NSMutableString *buff = [[NSMutableString alloc] init];
[[NSNotificationCenter defaultCenter] addObserverForName:NSFileHandleDataAvailableNotification
object:masterHandle queue:nil
usingBlock:^(NSNotification *note)
{
NSData *outData = [masterHandle availableData];
NSString *outStr = [[NSString alloc] initWithData:outData encoding:NSUTF8StringEncoding];
[buff appendString:outStr];
NSLog(#"output: %#", outStr);
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"sername:"
options:NSRegularExpressionCaseInsensitive
error:nil];
NSTextCheckingResult *match = [regex firstMatchInString:buff
options:0
range:NSMakeRange(0, [buff length])];
if (match) {
NSLog(#"got a match!!");
[buff setString:#""];
[masterHandle writeData:[#"bhughes\n" dataUsingEncoding:NSUTF8StringEncoding]];
}
NSLog(#"Exiting function.\n");
[masterHandle waitForDataInBackgroundAndNotify];
}];
[task waitUntilExit];
NSLog(#"Program complete.\n");
}
return 0;
}
As far as I know, NSTask does not support pty ability. Working with something like ssh requires interactive pty device context.
The simplest way to do this is forkpty, but this forks process itself, so it cannot be used with NSTask.
Finally I wrote a wrapper class that manages forkpty. That forks a child process and calls forkpty and execve.
Here's my implementation: https://github.com/eonil/PseudoTeletypewriter.Swift
You can read/write using single master device file handle.
I confirmed that sudo is working, and I believe ssh also should work fine.

Void between delegates not working - Objective-C Mac OSX

ECHOAppDelegate.m:
- (void)charlieInputTextHandler:(NSString *)theMessage {
if (jarvisSecondTimeCheck1 == TRUE) {
NSRunAlertPanel(#"ECHO", theMessage, #"", #"", #"");
NSData *sendData1 = [theMessage dataUsingEncoding:NSUTF8StringEncoding];
[[inputPipe1 fileHandleForWriting] writeData:sendData1];
NSData *sendReturn1 = [#"\r" dataUsingEncoding:NSUTF8StringEncoding];
[[inputPipe1 fileHandleForWriting] writeData:sendReturn1];
[ContentsTextField1 insertText:theMessage];
[ContentsTextField1 insertText:#"\r"];
} else {
NSRunAlertPanel(#"ECHO", #"The task is not running; therefore, you cannot send DATA to JARVIS.", #"", #"", #"");
}
}
ChatController.m:
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message
{
ECHOAppDelegate *echo = [[ECHOAppDelegate alloc] init];
[echo charlieInputTextHandler:[message stringValue]];
if(![jid isEqual:[message from]]) return;
if([message isChatMessageWithBody])
{
NSString *messageStr = [[message elementForName:#"body"] stringValue];
NSString *paragraph = [NSString stringWithFormat:#"%#\n\n", messageStr];
NSMutableParagraphStyle *mps = [[[NSMutableParagraphStyle alloc] init] autorelease];
[mps setAlignment:NSLeftTextAlignment];
NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithCapacity:2];
[attributes setObject:mps forKey:NSParagraphStyleAttributeName];
[attributes setObject:[NSColor colorWithCalibratedRed:250 green:250 blue:250 alpha:1] forKey:NSBackgroundColorAttributeName];
NSAttributedString *as = [[NSAttributedString alloc] initWithString:paragraph attributes:attributes];
[as autorelease];
[[messageView textStorage] appendAttributedString:as];
}
}
Ok, for some reason jarvisSecondTimeCheck1 (a bool global variable) returns FALSE even though that I know 900% that it's true because I clarified that in applicationDidFinishLaunching.
And the other part of the code:
NSData *sendData1 = [theMessage dataUsingEncoding:NSUTF8StringEncoding];
[[inputPipe1 fileHandleForWriting] writeData:sendData1];
NSData *sendReturn1 = [#"\r" dataUsingEncoding:NSUTF8StringEncoding];
[[inputPipe1 fileHandleForWriting] writeData:sendReturn1];
[ContentsTextField1 insertText:theMessage];
[ContentsTextField1 insertText:#"\r"];
Does not work either. But again, I know this works. Is it because I'm triggering charlieInputTextHandler from another delegate?
Thanks!
You're missing a hell of a lot of relevant code. At a rough guess, I'd say this is a big clue as to what's going wrong:
ECHOAppDelegate *echo = [[ECHOAppDelegate alloc] init];
You shouldn't ever need to instantiate your app delegate more than once. I'd expect something like the following instead:
ECHOAppDelegate *echo = (ECHOAppDelegate *)[UIApplication sharedApplication].delegate;
I'm assuming you're setting jarvisSecondTimeCheck1 on your original instance and expecting it to be set on any other instance you instantiate. This isn't how objects work. I strongly recommend reading the iOS Application Programming Guide section on the app delegate and Learning Objective-C: A Primer.
Sounds like one of your pointers is nil. Sending a message to a nil object will give you nil, which could explain why you're not seeing the results you expect. So check all your pointers and IBOutlet variables to ensure they are set correctly. And check any assumptions too!

Relaunching a cocoa app

I have an application that checks its command line parameters and stores values in persistent stores. One of those is a password that I don't want sticking around for people to see with 'ps' and friends. The approach I'm currently looking at is to, after I've stored the values I need, relaunch the process without the command line parameters. My naive approach is this, where args[0] is the path to the application:
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:[args objectAtIndex:0]];
[task launch];
[task release];
[NSApp terminate:nil];
The child is run. However, when my app is terminated the child doesn't seem to orphan but gets stuck. Am I just way off on this one?
More info: So it seems that when I call [NSApp terminate:nil] the NSTask that was launched gets stuck, but if I just exit() then it works fine. However, I'm concerned that things that are open (keychain, plist, etc.) will be in a bad state if I do that.
And note that lots of example code out there is about some watchdog-like process that restarts a separate process when needed. I'm trying to restart the current process that's already running from within that same process.
There are plenty of examples on the web, but this one (also below) looks like it has all the code you need. There are more detailed explanations out there, as well.
// gcc -Wall -arch i386 -arch ppc -mmacosx-version-min=10.4 -Os -framework AppKit -o relaunch relaunch.m
#import <AppKit/AppKit.h>
#interface TerminationListener : NSObject
{
const char *executablePath;
pid_t parentProcessId;
}
- (void) relaunch;
#end
#implementation TerminationListener
- (id) initWithExecutablePath:(const char *)execPath parentProcessId:(pid_t)ppid
{
self = [super init];
if (self != nil) {
executablePath = execPath;
parentProcessId = ppid;
// This adds the input source required by the run loop
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:#selector(applicationDidTerminate:) name:NSWorkspaceDidTerminateApplicationNotification object:nil];
if (getppid() == 1) {
// ppid is launchd (1) => parent terminated already
[self relaunch];
}
}
return self;
}
- (void) applicationDidTerminate:(NSNotification *)notification
{
if (parentProcessId == [[[notification userInfo] valueForKey:#"NSApplicationProcessIdentifier"] intValue]) {
// parent just terminated
[self relaunch];
}
}
- (void) relaunch
{
[[NSWorkspace sharedWorkspace] launchApplication:[NSString stringWithUTF8String:executablePath]];
exit(0);
}
#end
int main (int argc, const char * argv[])
{
if (argc != 3) return EXIT_FAILURE;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[[[TerminationListener alloc] initWithExecutablePath:argv[1] parentProcessId:atoi(argv[2])] autorelease];
[[NSApplication sharedApplication] run];
[pool release];
return EXIT_SUCCESS;
}
I know its a bit late to answer but this answer may help others. Here is a cool trick that can help you.
By using the terminal command, just open your application as a new instance and terminate the current instance.
This is how it is done:
....
NSString* path = [[NSBundle mainBundle] bundlePath];
NSString* cmd = [NSString stringWithFormat:#"open -n %#", path];
[self runCommand:cmd];
exit(0);
}
/// temrinal function
-(NSString*)runCommand:(NSString*)commandToRun;
{
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: #"/bin/sh"];
NSArray *arguments = [NSArray arrayWithObjects:
#"-c" ,
[NSString stringWithFormat:#"%#", commandToRun],
nil];
NSLog(#"run command: %#",commandToRun);
[task setArguments: arguments];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
NSData *data;
data = [file readDataToEndOfFile];
NSString *output;
output = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
return output;
}
Create an external process that launches yours when it terminates. Then terminate. Launching Cocoa programs with NSTask doesn't work quite right.
For anyone who still wants to use NSTask to relaunch,I found one possible way: Please DO NOT set NSPipe of the NSTask,because the NSTask will terminate the app itself,once the app terminated,the NSTask that started might get stuck there.
At least for me,after I removed the NSPipe settings,my app relaunch successfully.
The following is what I do:
1. Write a command line tool which has 3 parameters: app bundle id,app full path,please note that in this command line you need to terminate the app and wait for a while to make sure it is really terminated before launch the new one,I keep checking app.isTerminated and sleep(1) if it's not terminated.
Launch the Command line tool in app using NSTask,and set the parameters accorddingly,Don't use NSPipe,simply create NSTask and launch
The app relaunches now

Error while checking if a file exists

I'm trying to write to a plist file using writeToFile, before I write I check whether the file exists.
This is the code:
#import "WindowController.h"
#implementation WindowController
#synthesize contacts;
NSString *filePath;
NSFileManager *fileManager;
- (IBAction)addContactAction:(id)sender {
NSDictionary *dict =[NSDictionary dictionaryWithObjectsAndKeys:
[txtFirstName stringValue], #"firstName",
[txtLastName stringValue], #"lastName",
[txtPhoneNumber stringValue], #"phoneNumber",
nil];
[arrayContacts addObject:dict];
[self updateFile];
}
- (void)awakeFromNib {
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
filePath = [rootPath stringByAppendingPathComponent:#"Contacts.plist"];
fileManager = [NSFileManager defaultManager];
contacts = [[NSMutableArray alloc] init];
if ([fileManager fileExistsAtPath:filePath]) {
NSMutableArray *contactsFile = [[NSMutableArray alloc] initWithContentsOfFile:filePath];
for (id contact in contactsFile) {
[arrayContacts addObject:contact];
}
}
}
- (void) updateFile {
if ( ![fileManager fileExistsAtPath:filePath] || [fileManager isWritableFileAtPath:filePath]) {
[[arrayContacts arrangedObjects] writeToFile:filePath atomically:YES];
}
}
#end
When the addContactAction is executed I don't get any error but the program halts and it brings me to the debugger. When I press continue in the debugger I get:
Program received signal: “EXC_BAD_ACCESS”.
But that's probably not important.
PS: I'm new to mac programming and I don't know what else to try since I don't get an error message that tells me what's going wrong.
The path to the file is:
/Users/andre/Documents/Contacts.plist
I earlier tried this(with the same result), but I read that you can only write to the documents folder:
/Users/andre/Desktop/NN/NSTableView/build/Debug/NSTableView.app/Contents/Resources/Contacts.plist
Does anyone have an idea or even an explanation why this happens?
First, I think you shouldn't instantiate an NSFileManager object. Instead you use the default file manager, like this:
[[NSFileManager defaultManager] fileExistsAtPath: filePath];
Then, could you specify at which line the program is breaking into the debugger?
You are setting filePath with the stringByAppendingPathComponent: method. That method returns an autoreleased object. (Autoreleased object is used after it has been (automatically) released, which could cause the bad access error.)
I think changing
[rootPath stringByAppendingPathComponent:#"Contacts.plist"];
into
[[rootPath stringByAppendingPathComponent:#"Contacts.plist"] retain];
will solve your troubles.