Use a relative path with NSTask Arguments in Objective-C - objective-c

I have virtually no programing experience beyond shell scripting, but I'm in a situation where I need to call a binary with launchd and because of security changes in Catilina, it looks as though it cannot be a AppleScript .app, Automator .app, or a Platypus .app. All three of those call some sort of GUI element and none will work as at startup.
I've cobbled together a snippet of code that compiles and works to call the script I need. I've bundled it a .app, signed it, and it works. But I'd very much like to call the script relative to the binary.
How can I call the equivalent of
[task setArguments:#[ #"../Resources/script" ]];
instead of
[task setArguments:#[ #"/Full/Path/To/script" ]];
Below is the entirety of the main.m
#import <Foundation/Foundation.h>
enter code here
int main(int argc, const char * argv[]) {
#autoreleasepool {
// insert code here...
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:#"/bin/zsh"];
[task setArguments:#[ #"/Full/Path/To/script" ]];
[task launch];
}
return 0;
}
As an aside, I know there are much better ways to do this, but my goal os not to have an award winning app, but to simply bridge a specific problem. Many thanks.

You get the current directory with NSFileManager
NSString* path = [[NSFileManager defaultManager] currentDirectoryPath];
The first argument in argv is the path to the binary
Either
NSString *path = [NSString stringWithUTF8String:argv[0]];
or
NSString *path = [[NSProcessInfo processInfo] arguments][0];

You should use NSBundle for this. In particular, you should use:
[[NSBundle mainBundle] pathForResource:#"script" ofType:nil]
as in:
[task setArguments:#[ [[NSBundle mainBundle] pathForResource:#"script" ofType:nil] ]];

Related

NSFileManager or NSTask moving filetypes

I've been struggling to find a solution to do what should be a very simple task. I need to move a certain type of file (all zip files in this case) into another directory. I've tried NSTask and NSFileManager but have come up empty. I can move one at a time, but I would like to move them in one shot, at the same time.
- (void)copyFilesTo :(NSString*)thisPath {
NSFileManager *manager = [NSFileManager defaultManager];
NSDirectoryEnumerator *direnum = [manager enumeratorAtPath:thisPath];
NSString *filename = nil;
while ((filename = [direnum nextObject] )) {
if ([filename hasSuffix:#".zip"]) {
[fileManager copyItemAtPath:thisPath toPath:newPath];
}
}
}
FAILED - files copied = zeroooo
- (void)copyFilesMaybe :(NSString*)thisPath {
newPath = [newPath stringByAppendingPathComponent:fileName];
task = [[NSTask alloc] init];
[task setLaunchPath: #"/usr/bin/find"];
[task waitUntilExit];
NSArray *arguments;
arguments = [NSArray arrayWithObjects: thisPath, #"-name", #"*.zip", #"-exec", #"cp", #"-f", #"{}", newPath, #"\\", #";", nil];
[task setArguments: arguments];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
}
Same sad result, no files copied. What the heck am I doing wrong?
In the first case, you aren't using filename in your copy call. You need to construct a full path to the file by combining filename with thisPath and attempting to copy that. Also, the method is -copyItemAtPath:toPath:error:. You left off the last parameter. Try:
NSError* error;
if (![fileManager copyItemAtPath:[thisPath stringByAppendingPathComponent:filename] toPath:newPath error:&error])
// handle error (at least log error)
In the second case, I think your arguments array is wrong. I'm not sure why it includes #"\\". I suspect because at a shell you have to escape the semicolon with a backslash (\;). However, the need to escape the semicolon is because the shell would otherwise interpret it and not pass it to find. Since you're not using a shell, you don't need to do that. (Also, if you did need to escape it, it shouldn't be a separate element of the arguments array. It would be in the same element as the semicolon, like #"\\;".)
Also, are you sure the task has completed? You show the launch but you don't show observing or waiting for its termination. Given that you've set a pipe for its output, you have to read from that pipe to be sure that the subprocess isn't getting stuck writing to it.
I'm not sure why you're calling -waitUntilExit before launching the task. That may be harmless, though.

New Homebrew Gui mac os project, learning objective-c

I am trying to build a Gui for homebrew on mac , with objective-c, but when i try to see the installed packages with the following code it return empty but if i try other command like update it gives me the result, I tried the same with java and the same error occurs.
Git page: feel free to help the project, the code might have a lot of errors I am new to objective-c.
NSTask *task;
task=[[NSTask alloc]init];
[task setLaunchPath:#"/Users/rogeriop062/homebrew/bin/brew"];
NSArray *arguments;
arguments = [NSArray arrayWithObjects:#"list",nil];
[task setArguments: arguments];
NSPipe *pipe;
pipe =[NSPipe pipe];
[task setStandardOutput:pipe];
NSFileHandle *file;
file=[pipe fileHandleForReading];
[task launch];
NSMutableData *data=[NSMutableData dataWithCapacity:1000];
while ([task isRunning]) {
[data appendData:[file readDataToEndOfFile]];
}
[data appendData:[file readDataToEndOfFile]];
NSString *string;
string =[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"result: %#\n",string);
That's an interesting project; good for you! Homebrew works very nicely on my Mac. I can't see anything wrong with your code. I pasted your code into a test routine on my iMac and it worked perfectly. It listed the programs that I had installed with brew. One per line, which I didn't expect, but it worked. Sorry.
You could also try this. Delete everything in your method from[task launch] to the end, and replace it with this:
task.terminationHandler = ^(NSTask *blockTask) {
NSMutableData *data=[NSMutableData dataWithCapacity:1000];
[data appendData:[file readDataToEndOfFile]];
NSString * string =[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"result: %#\n",string);
};
[task launch];
and that produces the same results as your code on my machine - it works, I'm afraid - but it will not take compute time waiting for the result.

NSAppleScript and unresponsive interface - Objective-C

I want to run some lines of applescript code from within my application. The standard way is to use the NSAppleScript class. However, because that code might take a few minutes to complete, I have to use a separate thread or the interface will stop. The big problem is, as it says here, the NSAppleScript class can ONLY be run on the main thread.
So, if I run the code on a separate thread, my app crashes; If I run it on the main thread, it stops. Any ideas?
Also, I considered using NSTask and the osascript command, but I saw somewhere (can't find the link) that osascript doesn't support user input such as dialog boxes and stuff. I'm not sure if that's true, but if it is then osascript is not a solution.
So I ended up writing a helper for this, launched via NSTask. Here is the code, in case anyone's interested:
For the launcher:
NSArray* args = [NSArray arrayWithObject: <<AS code here>>];
task = [NSTask new];
[task setLaunchPath: [[NSBundle mainBundle] pathForResource: #"ASHelper" ofType: #""]];
[task setArguments: args];
[task launch];
And the helper:
int main(int argc, const char * argv[])
{
#autoreleasepool {
NSString* source = [NSString stringWithCString: argv[1] length: strlen(argv[1])];
NSAppleScript* as = [[NSAppleScript alloc] initWithSource: source];
[as executeAndReturnError: nil];
}
return 0;
}

Objective-C, NSTask won't touch files

I have the following code (below) in my app, which simply tries to use NSTask to touch one file with the directory time/date stamp. It works just fine in an app which only accesses one directory all the time, however, it doesn't with another that frequently changes directories to access some of it's data. When I check the currentfile and currentpath they both show the correct paths. I've expired every possibility I can think of; any help would be great appreciated — thank you.
- (void)someMethod:(NSString *)currentfile {
NSFileManager *filemanager = [[NSFileManager alloc] init];
if ([filemanager changeCurrentDirectoryPath: #"/"] == NO)
NSLog (#"Cannot change directory.\n");
NSString *currentpath = [filemanager currentDirectoryPath];
NSLog (#"Current directory is %#", currentpath);
[filemanager release];
NSArray*arguments = [NSArray arrayWithObjects:#"-r",currentpath,currentfile,nil];
[self touchFiles:arguments];
return;
}
- (void)touchFiles:(NSArray *)arguments {
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:#"/usr/bin/touch"];
[task setArguments:arguments];
[task launch];
[task release];
return;
}
You can change a file's modification time without using NSTask. Use -[NSURL setResourceValue:forKey:error:] with the key NSURLContentModificationDateKey.
As to why your use of NSTask and touch is failing, perhaps you don't have permissions to modify the file's modification time. Check the console log to see if any error was reported from touch or redirect the task's standard error output to someplace else and check that.

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