I'm using NSUserUnixTask to run an unsandboxed NSTask on my sandboxed app. However, my code hangs on calls to [NSFileHandle readDataToEndOfFile]. If I remove those calls it works perfectly. If I replace [NSFileHandle readDataToEndOfFile] with [NSFileHandle availableData] it hangs as well.
Here is the code:
NSUserUnixTask* unixTask = [[NSUserUnixTask alloc] initWithURL: [NSURL fileURLWithPath: path] error: nil];
// Create error file handle
NSFileHandle* errorFH = [NSFileHandle fileHandleWithStandardError];
[unixTask setStandardError: errorFH];
// Create output file handle
NSFileHandle* outputFH = [NSFileHandle fileHandleWithStandardOutput];
[unixTask setStandardOutput: outputFH];
// Run task with termination handler
[unixTask executeWithArguments: nil completionHandler: ^(NSError* error2) {
// Save output
NSString* output = [[NSString alloc] initWithData: [outputFH readDataToEndOfFile] encoding: NSUTF8StringEncoding];
if ([output length]) { // <-- Execution never reaches this line when the block is called
NSLog(#"%#", output);
}
// Read error 1
NSString* error1 = [[NSString alloc] initWithData: [errorFH readDataToEndOfFile] encoding: NSUTF8StringEncoding];
if ([error1 length]) {
NSLog(#"%#", error1);
}
// Read error 2
if (error2) {
NSLog(#"%#", error2);
}
}];
Nothing is logged on Xcode's console, but if I open Console.app I see this line:
Warning: Exception caught during decoding of received reply to message 'executeScript:interpreter:arguments:standardInput:standardOutput:standardError::', dropping incoming message and calling failure block.
Exception: *** -[NSConcreteFileHandle readDataOfLength:]: Bad file descriptor
Hmmm, how can I solve this? Before I had my app sandboxed I used NSPipe in conjunction with NSTask and everything worked, but now NSUserUnixTask requires the use of NSFileHandle instead and I reckon my problem is simply because I'm not using it correctly. But still I have no idea what I'm doing wrong.
It seems to me that you have to use NSPipe to read stderr and stdout from NSUserUnixTask in the same way it is done with NSTask.
I have tested it with a "/bin/ls" task and it produced the expected output:
NSPipe *outPipe = [NSPipe pipe];
NSPipe *errPipe = [NSPipe pipe];
NSString *path = #"/bin/ls";
NSUserUnixTask *unixTask = [[NSUserUnixTask alloc] initWithURL: [NSURL fileURLWithPath: path] error: nil];
[unixTask setStandardOutput:[outPipe fileHandleForWriting]];
[unixTask setStandardError:[errPipe fileHandleForWriting]];
[unixTask executeWithArguments:nil completionHandler:^(NSError *error) {
NSString *output = [[NSString alloc] initWithData: [[outPipe fileHandleForReading] readDataToEndOfFile] encoding: NSUTF8StringEncoding];
NSLog(#"stdout: %#", output);
NSString *error1 = [[NSString alloc] initWithData: [[errPipe fileHandleForReading] readDataToEndOfFile] encoding: NSUTF8StringEncoding];
NSLog(#"stderr: %#", error1);
}];
Related
I am new developing with Objective-C and Cocoa.
I need to run/stop/restart Apache and Mysql from a Xcode/Cocoa App and I don't know which is the best practise: Using NSTask, using NSAppleScript or what should be used?
Anyway I think that I need admin privileges for that.
Maybe somebody can help me how or where to get starting with this using best practices.
Below it's little of what I already tried:
I also saw this tutorial: http://www.michaelvobrien.com/blog/2009/07/authorizationexecutewithprivileges-a-simple-example/ but the AuthorizationExecuteWithPrivileges method is deprecated from OS 10.6.
I tried several methods that I find on stackoverflow.com but without any result.
One of the ways I tried is with the following method that I have in a TasksController class:
- (BOOL) runProcessAsAdministrator:(NSString*)scriptPath
withArguments:(NSArray *)arguments
output:(NSString **)output
errorDescription:(NSString **)errorDescription {
NSLog(#"runProcessAsAdministrator called!");
NSString * allArgs = [arguments componentsJoinedByString:#" "];
NSString * fullScript = [NSString stringWithFormat:#"%# %#", scriptPath, allArgs];
NSDictionary *errorInfo = [NSDictionary new];
NSString *script = [NSString stringWithFormat:#"do shell script \"sudo %#\" with administrator privileges", fullScript];
NSLog(#"shell script path: %#",script);
NSAppleScript *appleScript = [[NSAppleScript new] initWithSource:script];
NSAppleEventDescriptor * eventResult = [appleScript executeAndReturnError:&errorInfo];
// Check errorInfo
if (! eventResult)
{
// Describe common errors
*errorDescription = nil;
if ([errorInfo valueForKey:NSAppleScriptErrorNumber])
{
NSNumber * errorNumber = (NSNumber *)[errorInfo valueForKey:NSAppleScriptErrorNumber];
if ([errorNumber intValue] == -128)
*errorDescription = #"The administrator password is required to do this.";
}
// Set error message from provided message
if (*errorDescription == nil)
{
if ([errorInfo valueForKey:NSAppleScriptErrorMessage])
*errorDescription = (NSString *)[errorInfo valueForKey:NSAppleScriptErrorMessage];
}
return NO;
}
else
{
// Set output to the AppleScript's output
*output = [eventResult stringValue];
return YES;
}
}
Called as following:
TasksController *tasks = [[TasksController alloc] init];
NSString * output = nil;
NSString * processErrorDescription = nil;
BOOL success = [tasks runProcessAsAdministrator:#"/Applications/MyAPP/Library/bin/httpd"
withArguments:[NSArray arrayWithObjects:#"-f /Applications/MyAPP/conf/apache/MyAPP-httpd.conf", #"-k start", nil]
output:&output
errorDescription:&processErrorDescription
];
if (!success) // Process failed to run
{
// ...look at errorDescription
[_lblStatus setStringValue:#" Apache could not be started!"];
[_txtLogs setStringValue:processErrorDescription];
NSLog(#"%#",output);
NSLog(#"%#",processErrorDescription);
}
else
{
// ...process output
[_lblStatus setStringValue:#" Hurray Apache running!!"];
[_txtLogs setStringValue:output];
}
When I tried the above method the app is popup a small window asking for admin password but after introducing pass it's try to run and I get the following message in console:
dyld: DYLD_ environment variables being ignored because main executable (/usr/libexec/security_authtrampoline) is setuid or setgid
I also tried using the following methods but without success:
-(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;
}
-(void) runScript:(NSString*)scriptName
{
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: #"/bin/sh"];
NSArray *arguments;
NSString* newpath = [NSString stringWithFormat:#"'%#'/%#",[[NSBundle mainBundle] resourcePath], scriptName];
NSLog(#"shell script path: %#",newpath);
arguments = [NSArray arrayWithObjects:newpath, 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 (#"script returned:\n%#", string);
}
For this last method I have ShellScript folder in my Supporting Files folder that contain scripts like startMysql.sh, startApache.sh ...
startMysql.sh example:
# /bin/sh
/Applications/MyAPP/Library/bin/mysqld_safe --port=8889 --socket=/Applications/MyAPP/tmp/mysql/mysql.sock --lower_case_table_names=0 --pid-file=/Applications/MyAPP/tmp/mysql/mysql.pid --log-error=/Applications/MyAPP/logs/mysql_error_log &
Thanks in advance!
I'm new to objective-C, so please forgive me if I'm missing something. But we all have to start somewhere :)
I have a snippet of code I got from another open source project that executes a command and passes the result to another method. What I need to do is listen for each new line printed to stdout and do something with each line.
The code snippet I'm working with is the following:
NSMutableArray *args = [NSMutableArray array];
NSString *input = [details valueForKey:#"input"];
for (NSString *i in [input componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]) {
[args addObject:i];
}
NSTask *scriptTask = [NSTask new];
NSPipe *outputPipe = [NSPipe pipe];
if ([_NSFileManager() isExecutableFileAtPath:scriptPath] == NO) {
NSArray *chmodArguments = #[#"+x", scriptPath];
NSTask *chmod = [NSTask launchedTaskWithLaunchPath:#"/bin/chmod" arguments:chmodArguments];
[chmod waitUntilExit];
}
[scriptTask setStandardOutput:outputPipe];
[scriptTask setLaunchPath:scriptPath];
[scriptTask setArguments:args];
NSFileHandle *filehandle = [outputPipe fileHandleForReading];
[scriptTask launch];
[scriptTask waitUntilExit];
NSData *outputData = [filehandle readDataToEndOfFile];
NSString *outputString = [NSString stringWithData:outputData encoding:NSUTF8StringEncoding];
if (NSObjectIsNotEmpty(outputString)) {
[self.world.iomt inputText:outputString command:IRCPrivateCommandIndex("privmsg")];
}
So, rather than waiting for the process to complete then doing something with the result, I need to wait for each new line that gets printed by the command to stdout.
My background is mostly in web dev, so I guess if you were using Node.js and event emitters my aim would look similar to the following:
task = new Task("ls");
task.addListener("newline", function(data) {
somethingElse("sendmsg", data);
});
task.run();
Hopefully you understand what I'm tying to achieve. Thanks!
What you can do is add an observer to the NSFileHandle to notify you when it reads something.
example:
[fileHandler readInBackgroundAndNotify];
//Need to set NSTask output pipes
[self.task setStandardOutput: outputPipe];
[self.task setStandardError: outputPipe];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(readFromTask:) name:NSFileHandleReadCompletionNotification object:fileHandler];
-(void)readFromTask:(NSNotification *)aNotifcation;
{
NSData *data = [[aNotifcation userInfo] objectForKey: NSFileHandleNotificationDataItem];
if (data != 0) {
NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
// Do something with the text
[text release];
[[aNotifcation object] readInBackgroundAndNotify];
}
}
Then there's a notififcation you can add to the task to indicate when its completed so you can do clean up.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(taskEnded:) name:NSTaskDidTerminateNotification object:task];
- (void)taskEnded:(NSNotification *) aNotifcation
{
NSData *data = [[aNotifcation userInfo] objectForKey: NSFileHandleNotificationDataItem];
if (data != 0) {
NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[text release];
}
[self.task release];
}
So from your code, you could remove:
[scriptTask waitUntilExit];
And move the data handling into the notifications.
NSData *outputData = [filehandle readDataToEndOfFile];
NSString *outputString = [NSString stringWithData:outputData encoding:NSUTF8StringEncoding];
if (NSObjectIsNotEmpty(outputString)) {
[self.world.iomt inputText:outputString command:IRCPrivateCommandIndex("privmsg")];
}
That's a rough idea, there's a good post at
http://www.cocoabuilder.com/archive/cocoa/306145-nstask-oddity-with-getting-stdout.html
I am trying to run a simple shell command that runs and returns text rather quickly inside of a loop of indeterminate size at compile time that is generated by an NSArray. In scripting languages like perl, I would be able to do something like this:
for(i=0;i<=$myinputarraysize;i++){
$output[i]=`/my/task $inputarray[i]`;
}
This would build a new array for me from the expected output of my task. In Obj-C this seems to be much more difficult and a bit confusing to me. Right now my loop looks like this:
for(int i=0; i<[inputarray count]; i++){
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath:nsdchat];
NSArray *args;
args = [NSArray arrayWithObjects:#"/my/task", [inputarray objectAtIndex:i], nil];
[task setArguments:args];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput:pipe];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
NSData *data;
data = [file readDataToEndOfFile];
NSString *desc;
desc = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
desc = [string stringByReplacingOccurrencesOfString:#"\n" withString:#""];
[descriptions insertObject:desc atIndex:i];
[task release];
[args release];
[pipe release];
[file release];
[data release];
}
My goal is to fill descriptions (an NSMutableArray) with the output from my task (which I know is always a string and always ends in a newline that I want to strip out). It seems I'm missing something about memory releasing as when I run this and NSLog the output, I get the same result for the entire count of the loop.
Is there any easier or more way to loop through simple tasks like this? Am I overcomplicating it for myself?
First of all, there's no need to do most of that work inside the loop. task, pipe and file all look like they could be handled outside the loop. You should also consider using Objective-C 2.0 dot syntax and fast enumeration to cut things down a little.
More significantly:
NSArray *args;
args = [NSArray arrayWithObjects:#"/my/task", [inputarray objectAtIndex:i], nil];
[task setArguments:args];
This says that the first argument passed to the executable at path ndschat is /my/task. Which doesn't seem to match your PERL usage. Probably you want just:
NSArray *args;
args = [NSArray arrayWithObject:[inputarray objectAtIndex:i]];
[task setArguments:args];
Or, with the style comments taken into account:
for(NSString *argument in inputarray)
{
...
task.arguments = [NSArray arrayWithObject:argument];
...
}
EDIT: you're also releasing a lot of objects you don't own, which as well as adding heft to your code is a memory management error possibly leading to a crash. So, to cut the whole thing down and correct that fault:
for(NSString *argument in inputarray)
{
NSTask *task = [[NSTask alloc] init]; // you now own this
task.launchPath = nsdchat;
NSPipe *pipe = [NSPipe pipe]; // you don't own this
task.standardOutput = pipe;
NSFileHandle *file = [pipe fileHandleForReading]; // you also don't own this
task.arguments = [NSArray arrayWithObject:argument];
[task launch];
[task waitUntilExit]; // you should wait until the task is done
NSData *data = [file readDataToEndOfFile]; // this is another thing
// you don't own. Note also that
// readDataToEndOfFile advances
// the current read pointer, so
// it should be fine to do this
// successively
NSString *desc;
desc = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]
autorelease];
// you don't own this because
// of the autorelease; you
// don't want to own it since
// the next line will throw
// it away
desc = [string stringByReplacingOccurrencesOfString:#"\n" withString:#""];
// you don't own this
[descriptions addObject:desc];
[task release];
}
Using below code, I am getting this error:
NSTask: Task create for path '/Users/media/Library/Developer/Xcode/DerivedData/Manager-gnixeptcszdoubbnfcncumzypwge/Build/Products/Debug/lib' failed: 22, "Invalid argument". Terminating temporary process.
I am not getting whats the issue behind this.
- (void)startProcessingVideo {
NSLog(#"Processing Video Low Resolution");
NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
// NSTimeInterval is defined as double
NSNumber *timeStampObj = [NSNumber numberWithInt:timeStamp];
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterNoStyle];
NSString *convertNumber = [formatter stringForObjectValue:timeStampObj];
NSLog(#"timeStampObj:: %#", convertNumber);
NSString *fileNameNumber = [convertNumber stringByAppendingString:[self genRandStringLength:8]];
NSLog(#"fileNameNumber:::: %#", fileNameNumber);
NSString *aString = [[NSString stringWithFormat:#"%#%#%#", thumbnailDirPath,#"/Mobile" ,fileNameNumber] retain];
//NSString *string = [aString stringByAppendingString:#"_H"];
fileNameMP4Mobile = [aString stringByAppendingString:#".mp4"];
NSLog(#"string:::: %#", aString);
[lblMessage setStringValue:#"Started!"];
NSTask *task= [NSTask new];
[task setLaunchPath:ffmpegPresetLaunchPath];
[task setArguments:[NSArray arrayWithObjects:
#"-y",
#"-i",
inputFilePath,// #"1.wmv",
#"-vcodec",
#"libx264",
#"-fpre",
#"libx264-fast.ffpreset",
#"-b",
#"500k",
#"-s",
#"640*480",
#"-r",
#"30",
#"-aspect",
#"16:9",
#"-ab",
#"128k",
#"-ac",
#"2",
#"-ar",
#"44100",
fileNameMP4Mobile,nil]];
[task setCurrentDirectoryPath:thumbnailDirPath];
NSPipe *outputPipe = [NSPipe pipe];
[task setStandardInput:[NSPipe pipe]];
[task setStandardOutput:outputPipe];
[task launch];
[task waitUntilExit];
[task release];
NSData *outputData = [[outputPipe fileHandleForReading] readDataToEndOfFile];
NSString *outputString = [[[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding] autorelease];
[lblMessage setStringValue:#"Video Generated!"];
[self startProcessingVideoHighResolution];
}
value for ffmpegPresetLaunchPath is:
ffmpegPresetLaunchPath = [[NSString stringWithFormat:#"%#%#", currentDir, #"/lib/"] retain];
Value in NSLOG:
ffmpegPresetLaunchPath::: /Users/media/Library/Developer/Xcode/DerivedData/FileManager-gnixeptcszdoubbnfcncumzypwge/Build/Products/Debug/lib/
Please help resolving this issue.
You are not providing the correct file to execute; you are passing its path (presumably).
You probably need something like:
ffmpegPresetLaunchPath = [[NSString stringWithFormat:#"%#%#", currentDir, #"/lib/ffmpeg"] retain];
Note: It's clear than error 22 is the errno value EINVAL, however there is no reference to it on the exec() manpage or execve() manpage., so I don't know how you're expected to determine the cause from just the error code.
I'm a beginner developer. I'and stopped with this error about:
Clang LLVM 1.0 Error
Expected ':'
line: [pipe fileHandleForReading availableData]
Can anyone help me? Thanks in advance.
- (NSInteger)sizeOfItemAtPath:(NSString*)path {
BOOL isdir;
[[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isdir];
if (isdir) {
NSPipe *pipe = [NSPipe pipe];
NSTask *t = [[[NSTask alloc] init] autorelease];
[t setLaunchPath:#"/usr/bin/du"];
[t setArguments:[NSArray arrayWithObjects:#"-k", #"-d", #"0", path, nil]];
[t setStandardOutput:pipe];
[t setStandardError:[NSPipe pipe]];
[t launch];
[t waitUntilExit];
NSString *sizeString = [[[NSString alloc] initWithData:[[pipe fileHandleForReading availableData] encoding:NSASCIIStringEncoding] autorelease];
sizeString = [[sizeString componentsSeparatedByString:#" "] objectAtIndex:0];
BOOL bytes;
bytes = [sizeString longLongValue]*1024;
}
else {
BOOL bytes;
bytes = [[[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil] fileSize];
}
BOOL bytes;
return bytes;
}
You are missing a ]: it must be
[[pipe fileHandleForReading] availableData]
The whole line needs to look like this:
NSString *sizeString = [[[NSString alloc] initWithData:[[pipe fileHandleForReading] availableData] encoding:NSASCIIStringEncoding] autorelease];
Also, your method will return garbage. That is because you've defined bytes three times: once in the if branch, once in the else branch and once in the enclosing method body. The return value will be taken from the last one, but this one is initialized. Not only that, but you're using the wrong type: it must be a NSInteger bytes;, not BOOL bytes;. You need to put the definition at the start of the method and remove all other definitions, the variable may exist only once.
Try this:
[[pipe fileHandleForReading] availableData]