bdunagan

Brian Dunagan

April 18 2010
Communicating with a Privileged Tool

In writing Multicast Ping for the Mac, I needed a two-way communication pipe between the interface (parent process) and the privileged tool (child process) and a clean method for terminating the tool through that pipe. NSTask provides setStandardInput and setStandardOutput for just this reason. However, I cannot launch a privileged tool with NSTask; instead, I have to use AuthorizationExecuteWithPrivileges with its lower-level file handle mechanism.

With a bit of help, I implemented a two-way pipe at a high-level and utilize that pipe to cleanly terminate the child process with a simple EOF signal. Apple’s BetterAuthorizationSample sample project touches on this communication pipe but does so at a low-level and only one-way. Matt Gallagher from Cocoa With Love also uses a pipe in invoking other processes when he creates an Open File Killer app, but the pipe is still only used one direction. Thanks to both those tutorials as well as a post from Caius Theory for helping me along. See my GitHub multicast_ping repo for the code in context.

Interface: setup the pipe to the tool

// Execute the command with privileges from SFAuthorizationView.
FILE *handle;
OSErr processError = AuthorizationExecuteWithPrivileges([[authView authorization] authorizationRef], [helperPath UTF8String], 
														kAuthorizationFlagDefaults, (char *const *)argv, &handle);
free(argv);
if (processError != 0) {
	NSLog(@"helper tool failed (%d)", processError);
	return NO;
}

// Setup the two-way pipe.
helperHandle = [[NSFileHandle alloc] initWithFileDescriptor:fileno(handle)];
[helperHandle waitForDataInBackgroundAndNotify];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleTaskOutput:) name:NSFileHandleDataAvailableNotification object:helperHandle];

Interface: wait for data from the tool

- (void)handleTaskOutput:(NSNotification *)notification {
	// Get the new data.
	NSFileHandle *handle = (NSFileHandle *)[notification object];
	NSData *data = [handle availableData];
	if ([data length] > 0) {
		// Convert the data into a string.
		NSString *dataString = [[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:NSUTF8StringEncoding];
		if ([dataString isEqual:@"port in use"]) {
			// Tool failed.
			[self handleToolFailure];
			[dataString release];
			return;
		}

		// Process the string, expecting "address,port,message".
		NSArray *array = [dataString componentsSeparatedByString:@","];
		[dataString release];
		if ([array count] == 3) {
			// Get attributes.
			NSString *address = [array objectAtIndex:0];
			int port = [[array objectAtIndex:1] intValue];
			NSString *message = [array objectAtIndex:2];

			// Create new message.
			Message *newMessage = [[Message alloc] initWithAddress:address andPort:port andMessage:message];
			[messages addObject:newMessage];
			[newMessage release];
			
			// Update view.
			[listView reloadData];
		}
		
		// Prepare for more data.
		[handle waitForDataInBackgroundAndNotify];
	}
	else {
		// No data means tool failed.
		[self handleToolFailure];
	}
}

Interface: terminate the tool

// Close pipe to terminate the helper tool.
[helperHandle closeFile];
helperHandle = nil;

Tool: send data to the interface

// Write out the new message to the pipe.
NSString *line = [NSString stringWithFormat:@"%@,%d,%@", sentHost, sentPort, receivedMessage];
[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:[line dataUsingEncoding:NSUTF8StringEncoding]];

Tool: listen for terminate from the interface

// Wait for parent process to close the pipe. That will send the EOF signal.
[[NSFileHandle fileHandleWithStandardInput] readDataToEndOfFile];
Marketing Tip: Name Your Downloads Cocoa Tip: Losing UITableView Selection
LinkedIn GitHub Email