OS X Yosemite: Starting a launchAgent from a launchDaemon in user context - objective-c

I have a launchdaemon that is successfully loaded post installation of an app. I want the launchdaemon to load the launchagent in the user context. How can this be achieved on 10.10? Is the following way accurate and approved by Apple.
sudo launchctl bsexec "$PID" sudo -u "$USER" launchctl load /Library/LaunchAgents/pathto.plist
Concerns:
Is the above way Apple approved? I hope it doesn't get break with OS upgrades?
Also for confirmation, how can launchdaemon get the values of the PID and USER. Would running the following command from the launchdaemon in root context fetch the accurate logged in user: /usr/bin/who | /usr/bin/awk '/console/{print $1;exit}'
Any help will be appreciable.

This is what I mostly do. I am not sure whether this is an approved and accepted coding by Apple.
I use the command given below to get logged n user name. If multiple users are logged in it gives the name of the active user, I mean the active console user.
stat -f%Su /dev/console
LaunchAgent can be loaded from LaunchDaemon using su -l. You just need the logged in user name as parameter.
NSString *userName;
NSTask *task = [[NSTask alloc]init];
NSPipe *pipe = [NSPipe pipe];
[task setLaunchPath:#"/usr/bin/stat"];
[task setArguments:[NSArray arrayWithObjects:#"-f%Su", #"/dev/console", nil]];
[task setStandardOutput:pipe];
[task launch];
[task waitUntilExit];
userName = [[NSString alloc] initWithData:[[pipe fileHandleForReading] readDataToEndOfFile]
encoding:NSUTF8StringEncoding];
NSString *loggedInUserName = [userName stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSString *loadScript = [NSString stringWithFormat: #"su -l %# -c \"/bin/launchctl load /Library/LaunchAgents/com.abc.appname.plist\"", loggedInUserName];
system([loadScript UTF8String]);
A word of caution. I have seen this causing problems problems in LaunchAgent accessing UI (anything related to WindowServer) when we have set RunAtLoad as YES in OS versions 10.9 and below. I have never faced this in 10.10.

Related

Executing terminal command in objective C with Dynamic path

May be my question is a repeated question but I searched a lot of it on the internet and can't find the appropriate and helpful solution.
I want to run the terminal command 'mv' for moving a folder from the root '~/' to another folder but its not working command is running properly using the NSTask library but I think the path is not correct my xcode compiler doest find '~/Desktop',
Sample Code:
NSTask *task = [[NSTask alloc]init];
[task setLaunchPath:#"/bin/mv"];
[task setArguments:#[ #"~/Desktop/Script",#"~/Desktop/Script2"]];
[task launch];
and it gives error:
mv: rename ~/Desktop/Script to ~/Desktop/Script2: No such file or directory
I think the '~/' is not working and xcode cant find the file,
please help
Thanks in advance
The ~ directory is not expanded as you have suspected.
You should use NSHomeDirectory() and concatenate.

Different $PATH from Console and Cocoa?

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.

Run "launchctl" command under specific user using NSTask in OS X

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"

How to open a jar file in a Cocoa App

I am trying to execute a jar in my Cocoa app. My research tells me I should use NSTask. However, I am only able to get system() to work. E.g.:
// This works
system("cd /path/to ; /usr/bin/java -jar file.jar");
But this doesn't work:
// This does not work
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:#"/usr/bin/java"];
[task setArguments:[NSArray arrayWithObjects: #"-jar", #"/path/to/test.jar", nil] ];
[task launch];
I get a class not found Java exception when I run my app because it cannot find its dependencies.
There is no general error in your code, I tried it out myself.
I assume that you have defined a CLASSPATH environment variable in your .profile, .bashrc or whatever, that is required to execute the Java file.
system() uses the shell to execute your command, and therefore the CLASSPATH is inherited by the java command.
NSTask on the other hand does not use the shell, so java would not know about the CLASSPATH from your profile.
The solution would be to add #"-cp", #"<your class path>" to the arguments.
UPDATE
As it turned out in the discussion, the problem in this case was not the class path, but the current working directory. Adding
[task setCurrentDirectoryPath:#"/path/to"]
solved the issue.

Problems with NsTask to execute terminal command

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... :-)