Normally in the terminal I would execute this command to communicate with a serial USB device.
echo -e '\xFF\x01\x01' > /dev/cu.usbserial-A8003YzT
I'm trying to do this from within a cocoa app using NStask, but I'm getting no love for some reason.
Heres my code:
- (IBAction) doCommand:(id)sender{
{
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath:#"/bin/echo"];
[task setArguments:
[NSArray arrayWithObjects:#"-e '\\xFF\\x01\\x01' > /dev/cu.usbserial-A8003YzT", nil]];
[task launch];
[task release];
}}
I know the code is essentially working, as I've executed other terminal commands with the same script.....not sure why I can't get the echo to fire....perhaps I'm missing somthing simple?
Thanks for any help
You're trying to send '\xFF\x01\x01' > /dev/cu.usbserial-A8003YzT as the first argument to echo, but that's not what happens when you run that command from a shell prompt. Only the first, quoted part is sent as an argument to echo. Bash interprets the > itself, captures the output from the echo command, and redirects it to the indicated file - in this case, a file that represents a usb device.
If you want to run an NSTask that will interpret a shell command with redirects, pipes, and such, you'll need to use /bin/sh as the launch path, and -c, your shell command as arguments.
Alternatively, you could skip the NSTask altogether, and simply open an NSFileHandle to the device file, then send the three-byte sequence you want to send it. Echo is handy for interactive debugging stuff like this in a terminal, but launching an external task in your app, just to write three bytes to a file, is pretty drastic overkill... :-)
Related
I am programming an advanced Executable (binary) Analyser Application that can do many operations on Mach-O binaries. One of these functions is a simple launch operation with can simply execute the executable and log its output.
I recently found out the when terminal launches executable files, it detects when the executable runs its own NSTask inside its code.
eg.
Terminal is executing my application and is also showing (in the red circle) that my application is currently running an NSTask of the "file" command:
What I want to figure out is how I can make my app do this to other executable files. I want my app to launch an Executable and when the executable runs a shell script (NSTask), My app will detect it and log it.
eg.
An Executable has the following code:
int main(int argc, const char * argv[]) {
NSTask *task = [NSTask new];
task.launchPath = #"/usr/bin/file";
task.arguments = #[#"--mime", #"--brief", #"/path/to/file"];
task.standardOutput = [NSPipe new];
[task launch];
return 0;
}
When I execute this in Terminal, once the line
[task launch];
is executed, Terminal will display the sub process name in the titlebar:
I Want my app to execute an Executable just like terminal and when an executable runs its own NSTask, I want my app to detect that and log it.
So when my app runs this executable. I want my app to detect when the "file" task is executed in the executable.
I Hope this isn't to difficult to Understand. I also have a feeling I will have to dig deeper than NSTask to include this process.
Thanks.
OK, I must be missing something very simple but here's what:
If I echo $PATH in the terminal, I'm getting /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/drkameleon which is correct
Now if I run an NSTask and try executing a simple bash script (/usr/bin/env bash myscript.sh) to echo $PATH, it prints /Applications/Xcode.app/Contents/Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin
(I've even tried with getenv, or print the entire [[NSProcessInfo processInfo] environment] dictionary, but the PATH variable is simply... wrong)
What's going on? How can I have access to the real $PATH as seen in the terminal?
When executing a command via NSTask, not your bash, zshell or whatever kind of shell you are using, is started. Hence the PATH (and other environment variables) are different from your environment variables when echoing them in the terminal.
Reason: NSTask uses fork() and exec() for command execution.
IMHO, there are two possible solutions for the problem.
1. You could set the wanted PATH via the setEnvironment:method of NSTask
Here is some untested example code, which should do the trick:
NSTask *task = //Configure your task
NSDictionary* env = [task environment];
NSString* currentPATH = env["PATH"];
NSString* yourPathExtension = #"/your/path";
env["PATH"] = (currentPATH != nil) ? [yourPath stringByAppendingFormat:#":%#", currentPATH] : yourPathExtension;
[task setEnvironment:env];
This adds :<old-PATH> after the current PATH from NSTask.
2. You could start /bin/bash within NSTask
task setLaunchPath:#"/bin/bash"];
NSArray *args = [NSArray arrayWithObjects:#"-l",
#"-c",
#"<your command here>",
nil];
[task setArguments: args];
This task will start /bin/bash with the PATH configured via ~/.bashrc etc. and execute the command within the bash.
Pro: Your command has all the usual environment variables of your bash
Con: You rely on the users PATH variable which can be quite different from yours which may lead to unexpected behavior.
Which solution is the best one for you depends on your use case. In your provided example, both approaches should work fine.
In general, according to the NSTask and NSProcessInfo documentation, the environment variables are equal to the variables of the process from which the application has been started. Hence you might solve your problem when starting your application from the bash.
My application is launching under root and I need to be able to unload processes using NSTask and launchctl
Here is a code I do:
NSPipe *pipe = [NSPipe pipe];
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath: #"/bin/launchctl"];
[task setCurrentDirectoryPath:#"/"];
[task setStandardError:pipe];
NSLog(#"/bin/launchctl unload %#", plistAutostartLocation);
NSArray *arguments;
arguments = [NSArray arrayWithObjects: enableCommand, plistAutostartLocation, nil];
[task setArguments: arguments];
NSFileHandle * read = [pipe fileHandleForReading];
[task launch];
[task waitUntilExit];
If process need to be unload is launched under "root" then it unloads successfully if not the fails.
The question is how to run "launchctl" under specific user (e.g. "myusername")?
Edit:
In terminal if I want to run some command under specific user I do next and it works well:
su - myusername -c "ls /Users/myusername"
But when I try to run "launchctl" under specific user it fails:
su - myusername -c "launchctl load /Library/LaunchAgents/com.google.keystone.agent.plist"
It says: "nothing found to load"
The failing of your last command is related to bootstrap namespaces, you are trying to load the agent in the wrong bootstrap namespace. The system is creating two different bootstrap namespaces, excerpt from Apple documentation:
It's worth noting the distinction between GUI and non-GUI per-session
bootstrap namespaces. A GUI per-session bootstrap namespace is
instantiated by the GUI infrastructure (loginwindow and WindowServer)
when a user logs in via the GUI. A non-GUI per-session bootstrap
namespace is created when a user logs in via SSH. While there is no
fundamental difference between these namespaces, GUI-based services,
like the Dock, only register themselves in GUI per-session bootstrap
namespaces.
The su command is not launched in the same bootsrap namespace used by launchd.
There are two ways to run a command like the ones you want to run in the correct bootstrap namespace:
10.10 and above: use launchctl asuser. This will run any command as it was launched by the specified <myusername>. It is worth to mention that you must run this as root, otherwise the command will fail. Your last command should be run like this:
launchctl asuser <myusername> launchctl load "/Library/LaunchAgents/com.google.keystone.agent.plist"
10.10 and below (since 10.11, SIP blocks this method): use launchctl bsexec. This requires a process id to retrieve the correct namespace in which to run another command. Also, you have to change the UID of the command manually, like this:
launctl bsexec <pid> su -u <myusername> launchctl load "/Library/LaunchAgents/com.google.keystone.agent.plist"
My app uses NSTask to execute shell scripts, one of those scripts launches an X11 app (specifically meld).
I would have expected this to work:
#!/bin/bash
source ~/.profile # setup $PATH/etc
meld .
But it fails with:
gtk.icon_theme_get_default().append_search_path(meld.paths.icon_dir())
Traceback (most recent call last):
File "/usr/local/bin/meld", line 132, in <module>
gtk.icon_theme_get_default().append_search_path(meld.paths.icon_dir())
AttributeError: 'NoneType' object has no attribute 'append_search_path'
As a proof of concept I changed the script to this, which works perfectly:
#!/usr/bin/ruby
exec 'osascript -e \'tell app "Terminal" to do script "meld ' + Dir.pwd + '" in front window\''
Does anyone what is causing the problem? Here is my code for executing the shell script:
NSTask *task = [[NSTask alloc] init];
task.launchPath = self.scriptURL.path;
task.standardOutput = [NSPipe pipe];
task.currentDirectoryPath = workingDirectoryURL.path;
[task launch];
X11 apps learn the display server's address using the DISPLAY environment variable.
On OS X, the DISPLAY value is randomized for security reasons, so you can't hard-code it, as you noticed. Instead, there is a launchd agent that tells launchd to set DISPLAY when it starts a process.
Somewhere between launchd and meld, the value of DISPLAY is being dropped or overwritten. Your job is to find out where.
Make sure the launchd agent is running. Run launchctl list and look for org.macosforge.xquartz.startx. Since meld is working from Terminal.app this part is probably correct.
Make sure DISPLAY is set in your app. It should be listed in [NSProcessInfo processInfo].environment.
Make sure DISPLAY is set in the NSTask you launch. Try running /usr/bin/env with an NSTask, and make sure DISPLAY appears in its output.
Make sure DISPLAY is set inside the script you run. Try echo $DISPLAY before and after sourcing ~/.profile. (Sometimes the .profile itself overwrites DISPLAY to an incorrect value because that's sometimes the right thing to do on other operating systems.)
EDIT: this is the code I used to copy DISPLAY from my GUI app to the shell script:
NSTask *task = ...
task.environment = [NSProcessInfo processInfo].environment;
[task.launch];
Change your script to spew the environment variables and compare "working" to "not-working".
There are subtle differences between how shells are initialized across the different execution models. Almost assuredly, the environment is the source of your issues.
Take a look at man launchctl
launchctl submit ... -p /usr/local/bin/meld -- .
to have launchd launch the job; that will set certain environment variables for you that are part of the per-user session.
Basically I've passed a script in the terminal through /bin/bash "[path]" or /bin/sh "[path]", and I've passed commands in a terminal through /bin/bash -c "[command]" or /bin/sh -c "[command]". Both ways work properly. But in my Cocoa App, when I try to do the exact same thing with NSTask (using /bin/bash or /bin/sh), the app seems to never respond. It is as if the program is stuck inside of bash or sh. I've tried to do this with and without waitUntilExit, and I've also tried to use terminate. Still no luck. Has anyone else had this issue or have a clue as to why this is happening?
Thanks!
P.S. I'm not on my work computer right now, but, if needed, I can provide code later.
Ok. So I found the solution for anyone else that might need it. Basically, there is a problem is the standardInput. The problem only shows up in the Xcode console and is not an error/bug with your app. The fix is to add the following line (basically set the standardInput to something random):
[task setStandardInput: [NSPipe pipe]];
In the solution above, task is the variable name of the NSTask being used.
Also see:
http://www.cocoadev.com/index.pl?HowToPipeCommandsWithNSTask
[proc setLaunchPath:#"/bin/bash"];
[proc setArguments:[NSArray arrayWithObjects: #"-c", #"/usr/sbin/netstat -rn | /usr/bin/grep default", nil]];