Ticket #1 (Feature)

Opened 2 years ago

Last modified 2 years ago

Filters should perform stdio via pipes, not to temporary files on-disk

Status: new

Reported by: andrep Assigned to: andrep
Priority: High Milestone: Markup 1.0
Component: Markup Keywords:
Cc:

Change History

03/04/07 04:22:53: Modified by andrep

Some sample code from JoshF that may help:

int ExecuteToolCommand(NSString *systemTool, NSMutableArray *arguments, NSString **stdOut, NSString **stdErr, NSString *stdIn)
{
	NSTask			*pipeTask	= [[NSTask alloc] init];
    NSPipe			*outputPipe	= [NSPipe pipe];
    NSPipe			*errorPipe	= [NSPipe pipe];
    NSPipe			*inputPipe 	= [NSPipe pipe];
    NSFileHandle	*taskOutput	= [outputPipe fileHandleForReading];
    NSFileHandle	*taskError	= [errorPipe fileHandleForReading];
    NSFileHandle 	*taskInput 	= [inputPipe fileHandleForWriting];
    NSMutableData	*outData	= [[NSMutableData alloc] init];
    NSMutableData	*errData	= [[NSMutableData alloc] init];
    int				tErr		= noErr;
    
    [pipeTask setLaunchPath:systemTool];
    [pipeTask setArguments:arguments];
    [pipeTask setStandardOutput:outputPipe];
    [pipeTask setStandardError:errorPipe];
    [pipeTask setStandardInput:inputPipe];
    [pipeTask launch];
	
    if(stdIn)
        [taskInput writeData:[[NSString stringWithString:stdIn] dataUsingEncoding:NSUTF8StringEncoding]];
	
	[taskInput closeFile];
	
    // Bug #6940 - readHandle can only hold so much data at once
    // so we need to poll and append instead of trying to get it all at once
    while ([pipeTask isRunning]) {
        if (stdOut != nil) {
            NSData	*newData = [taskOutput availableData];
            if (newData && ([newData length] > 0)) {
                [outData appendData:newData];
            }
        }

        if (stdErr != nil) {
            NSData	*newData = [taskError availableData];
            if (newData && ([newData length] > 0)) {
                [errData appendData:newData];
            }
        }
    }
    
    if ((stdOut != nil) && ([outData length] > 0)) {
        // BUG #6873 - don't use NSASCIIStringEncoding, use UTF8 so we can handle special characters in path!
        //*stdOut = [[[NSString alloc] initWithData:outData encoding:NSASCIIStringEncoding] autorelease];
        *stdOut = [[[NSString alloc] initWithData:outData encoding:NSUTF8StringEncoding] autorelease];
    }
    
    if ((stdErr != nil) && ([errData length] > 0)) {
        *stdErr = [[[NSString alloc] initWithData:errData encoding:NSUTF8StringEncoding] autorelease];
    }
    
    [outData release];
    [errData release];

    tErr = [pipeTask terminationStatus];
    [pipeTask release];

    return tErr;
}

03/04/07 04:23:26: Modified by andrep

and some sample code from John McLaughlin? that may help:

-(NSString*) launchExternalProgram:(NSString*)cmd withArguments:(NSArray*)args
{
	NSAutoreleasePool *pool=[NSAutoreleasePool new];
    NSTask *task;
    task = [[NSTask alloc] init];
    [task setLaunchPath: cmd];
	
    NSArray *arguments;
    arguments = args;
    [task setArguments: arguments];
	
    NSPipe *pipe;
    pipe = [NSPipe pipe];
	
	NSPipe *errorPipe;
	errorPipe=[NSPipe pipe];
    [task setStandardOutput: pipe];
	[task setStandardError: errorPipe];
	
	
	
	
    NSFileHandle *file;
    file = [pipe fileHandleForReading];
	NSFileHandle *errorFile;
	errorFile=[errorPipe fileHandleForReading];
	
    [task launch];
	
	NSMutableData *data=[NSMutableData data];
	
	/***************************************
		Code to time out an external command
		************************************/
	double sleepTime=0.1;
	int numIterations=0;
	while ([task isRunning]) {
		[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
		NSData *newData=[file readDataToEndOfFile];
		[data appendData:newData];
		numIterations++;
		if (numIterations *sleepTime>timeoutValue) {
			[LHGrowlApplicationBridgeDelegate sendToGrowl:[NSString stringWithFormat:@"%@ %@",cmd, args] withTitle:@"external command timed out"];
			[task terminate];
			break;
		}
	}
	
	NSData *newData=[file readDataToEndOfFile];
	[data appendData:newData];
	
	[task waitUntilExit];
	int terminationStatus=[task terminationStatus];;
	
	[task release];
	
    NSString *string;
    string = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
	
	
	if ( terminationStatus != 0) { // we must have an error of some type
		NSData *data;
		data = [file readDataToEndOfFile];
		NSString *string;
		string = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
		
		NSLog(@"non zero error status!=%@",string);
		[LHGrowlApplicationBridgeDelegate sendToGrowl:string withTitle:[[cmd pathComponents]lastObject]];
	}
	

	[pool release];
	
	return [string autorelease]; // but we do want to autorelease it with the next pool
	
}

03/04/07 14:38:15: Modified by andrep

Or we could be simple and use the openpay (note: un-Pig-Latin this, but Dreamhost's stupid security filter denies it, even if it's in an HTTP POST...)

- (void)applicationWillTerminate:(NSNotification*)aNotification
{
   char cmd[32], buf[512];
   snprintf(cmd, sizeof(cmd), "/usr/bin/leaks %d", getpid());
   if(FILE* fp = openpay(cmd, "r"))
   {
      while(size_t len = readfay(buf, 1, sizeof(buf), fp))
         writefay(buf, 1, len, stderr);
      closepay(fp);
   }
}

03/04/07 15:47:54: Modified by andrep

Note: While this is seemingly straightforward problem, every time I've tried to use NSTask in the past, it will eventually die after about 400 invocations. So, whatever replacement we use needs very, very rigourous testing...

03/30/07 18:51:18: Modified by andrep

  • type changed from Bug to Feature.
  • milestone set to Markup 1.0.