/*
* Copyright (c) 2008, 2009, 2010, 2011, 2012
* Jonathan Schleifer <js@webkeks.org>
*
* All rights reserved.
*
* This file is part of ObjFW. It may be distributed under the terms of the
* Q Public License 1.0, which can be found in the file LICENSE.QPL included in
* the packaging of this file.
*
* Alternatively, it may be distributed under the terms of the GNU General
* Public License, either version 2 or 3, which can be found in the file
* LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
* file.
*/
#include "config.h"
#include <stdlib.h>
#ifndef _WIN32
# include <unistd.h>
# include <sys/wait.h>
#endif
#import "OFProcess.h"
#import "OFString.h"
#import "OFArray.h"
#import "OFAutoreleasePool.h"
#import "OFInitializationFailedException.h"
#import "OFReadFailedException.h"
#import "OFWriteFailedException.h"
#ifdef _WIN32
# include <windows.h>
#endif
@implementation OFProcess
+ processWithProgram: (OFString*)program
{
return [[[self alloc] initWithProgram: program] autorelease];
}
+ processWithProgram: (OFString*)program
arguments: (OFArray*)arguments
{
return [[[self alloc] initWithProgram: program
arguments: arguments] autorelease];
}
+ processWithProgram: (OFString*)program
programName: (OFString*)programName
arguments: (OFArray*)arguments
{
return [[[self alloc] initWithProgram: program
programName: programName
arguments: arguments] autorelease];
}
- initWithProgram: (OFString*)program
{
return [self initWithProgram: program
programName: program
arguments: nil];
}
- initWithProgram: (OFString*)program
arguments: (OFArray*)arguments
{
return [self initWithProgram: program
programName: program
arguments: arguments];
}
- initWithProgram: (OFString*)program
programName: (OFString*)programName
arguments: (OFArray*)arguments
{
self = [super init];
@try {
#ifndef _WIN32
if (pipe(readPipe) != 0 || pipe(writePipe) != 0)
@throw [OFInitializationFailedException
exceptionWithClass: isa];
switch ((pid = fork())) {
case 0:;
OFString **cArray = [arguments cArray];
size_t i, count = [arguments count];
char **argv = alloca((count + 2) * sizeof(char*));
argv[0] = (char*)[programName cStringWithEncoding:
OF_STRING_ENCODING_NATIVE];
for (i = 0; i < count; i++)
argv[i + 1] = (char*)[cArray[i]
cStringWithEncoding:
OF_STRING_ENCODING_NATIVE];
argv[i + 1] = NULL;
close(readPipe[0]);
close(writePipe[1]);
dup2(writePipe[0], 0);
dup2(readPipe[1], 1);
execvp([program cStringWithEncoding:
OF_STRING_ENCODING_NATIVE], argv);
@throw [OFInitializationFailedException
exceptionWithClass: isa];
case -1:
@throw [OFInitializationFailedException
exceptionWithClass: isa];
default:
close(readPipe[1]);
close(writePipe[0]);
break;
}
#else
SECURITY_ATTRIBUTES sa;
PROCESS_INFORMATION pi;
STARTUPINFO si;
OFAutoreleasePool *pool;
OFMutableString *argumentsString;
OFEnumerator *enumerator;
OFString *argument;
char *argumentsCString;
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
if (!CreatePipe(&readPipe[0], &readPipe[1], &sa, 0))
@throw [OFInitializationFailedException
exceptionWithClass: isa];
if (!SetHandleInformation(readPipe[0], HANDLE_FLAG_INHERIT, 0))
@throw [OFInitializationFailedException
exceptionWithClass: isa];
if (!CreatePipe(&writePipe[0], &writePipe[1], &sa, 0))
@throw [OFInitializationFailedException
exceptionWithClass: isa];
if (!SetHandleInformation(writePipe[1], HANDLE_FLAG_INHERIT, 0))
@throw [OFInitializationFailedException
exceptionWithClass: isa];
memset(&pi, 0, sizeof(pi));
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.hStdInput = writePipe[0];
si.hStdOutput = readPipe[1];
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
si.dwFlags |= STARTF_USESTDHANDLES;
pool = [[OFAutoreleasePool alloc] init];
argumentsString =
[OFMutableString stringWithString: programName];
[argumentsString replaceOccurrencesOfString: @"\\\""
withString: @"\\\\\""];
[argumentsString replaceOccurrencesOfString: @"\""
withString: @"\\\""];
if ([argumentsString containsString: @" "]) {
[argumentsString prependString: @"\""];
[argumentsString appendString: @"\""];
}
enumerator = [arguments objectEnumerator];
while ((argument = [enumerator nextObject]) != nil) {
OFMutableString *tmp =
[[argument mutableCopy] autorelease];
BOOL containsSpaces = [tmp containsString: @" "];
[argumentsString appendString: @" "];
if (containsSpaces)
[argumentsString appendString: @"\""];
[tmp replaceOccurrencesOfString: @"\\\""
withString: @"\\\\\""];
[tmp replaceOccurrencesOfString: @"\""
withString: @"\\\""];;
[argumentsString appendString: tmp];
if (containsSpaces)
[argumentsString appendString: @"\""];
}
argumentsCString = strdup([argumentsString
cStringWithEncoding: OF_STRING_ENCODING_NATIVE]);
@try {
if (!CreateProcess([program cStringWithEncoding:
OF_STRING_ENCODING_NATIVE], argumentsCString, NULL,
NULL, TRUE, 0, NULL, NULL, &si, &pi))
@throw [OFInitializationFailedException
exceptionWithClass: isa];
} @finally {
free(argumentsString);
}
[pool release];
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(readPipe[1]);
CloseHandle(writePipe[0]);
#endif
} @catch (id e) {
[self release];
@throw e;
}
return self;
}
- (BOOL)_isAtEndOfStream
{
#ifndef _WIN32
if (readPipe[0] == -1)
#else
if (readPipe[0] == NULL)
#endif
return YES;
return atEndOfStream;
}
- (size_t)_readNBytes: (size_t)length
intoBuffer: (void*)buffer
{
#ifndef _WIN32
ssize_t ret;
#else
DWORD ret;
#endif
#ifndef _WIN32
if (readPipe[0] == -1 || atEndOfStream ||
(ret = read(readPipe[0], buffer, length)) < 0) {
#else
if (readPipe[0] == NULL || atEndOfStream ||
!ReadFile(readPipe[0], buffer, length, &ret, NULL)) {
if (GetLastError() == ERROR_BROKEN_PIPE) {
atEndOfStream = YES;
return 0;
}
#endif
@throw [OFReadFailedException exceptionWithClass: isa
stream: self
requestedLength: length];
}
if (ret == 0)
atEndOfStream = YES;
return ret;
}
- (void)_writeNBytes: (size_t)length
fromBuffer: (const void*)buffer
{
#ifndef _WIN32
if (writePipe[1] == -1 || atEndOfStream ||
write(writePipe[1], buffer, length) < length)
#else
DWORD ret;
if (writePipe[1] == NULL || atEndOfStream ||
!WriteFile(writePipe[1], buffer, length, &ret, NULL) ||
ret < length)
#endif
@throw [OFWriteFailedException exceptionWithClass: isa
stream: self
requestedLength: length];
}
- (void)dealloc
{
[self close];
[super dealloc];
}
/*
* FIXME: Add -[fileDescriptor]. The problem is that we have two FDs, which is
* not yet supported by OFStreamObserver. This has to be split into one
* FD for reading and one for writing.
*/
- (void)closeForWriting
{
#ifndef _WIN32
if (writePipe[1] != -1)
close(writePipe[1]);
writePipe[1] = -1;
#else
if (writePipe[1] != NULL)
CloseHandle(writePipe[1]);
writePipe[1] = NULL;
#endif
}
- (void)close
{
#ifndef _WIN32
if (readPipe[0] != -1)
close(readPipe[0]);
if (writePipe[1] != -1)
close(writePipe[1]);
if (pid != -1)
waitpid(pid, &status, WNOHANG);
pid = -1;
readPipe[0] = -1;
writePipe[1] = -1;
#else
if (readPipe[0] != NULL)
CloseHandle(readPipe[0]);
if (writePipe[1] != NULL)
CloseHandle(writePipe[1]);
readPipe[0] = NULL;
writePipe[1] = NULL;
#endif
}
@end