Index: configure.ac ================================================================== --- configure.ac +++ configure.ac @@ -1559,29 +1559,29 @@ (!defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR) egrep_cpp_yes #endif ], [ AC_MSG_RESULT(yes) - have_processes="yes" + have_subprocesses="yes" ], [ AC_MSG_RESULT(no) - have_processes="no" + have_subprocesses="no" ]) ;; mingw*) - have_processes="yes" + have_subprocesses="yes" ;; msdosdjgpp*) - have_processes="no" + have_subprocesses="no" ;; *) AC_HEADER_SYS_WAIT AC_CHECK_FUNCS(kill) AC_CHECK_FUNCS(posix_spawnp, [ AS_IF([test x"$ac_cv_func_kill" = x"yes"], [ - have_processes="yes" + have_subprocesses="yes" AC_CHECK_HEADERS(spawn.h) ]) ], [ AC_CHECK_FUNCS([vfork dup2 execvp _exit], [ @@ -1589,21 +1589,21 @@ -a x"$ac_cv_func_pipe" = x"yes" \ -a x"$ac_cv_func_dup2" = x"yes" \ -a x"$ac_cv_func_execvp" = x"yes" \ -a x"$ac_cv_func_kill" = x"yes" \ -a x"$ac_cv_func__exit" = x"yes"], [ - have_processes="yes" + have_subprocesses="yes" ]) ], [ break ]) ]) ;; esac -AS_IF([test x"$have_processes" = x"yes"], [ - AC_SUBST(OF_PROCESS_M, "OFProcess.m") - AC_DEFINE(OF_HAVE_PROCESSES, 1, [Whether we have processes]) +AS_IF([test x"$have_subprocesses" = x"yes"], [ + AC_SUBST(OF_SUBPROCESS_M, "OFSubprocess.m") + AC_DEFINE(OF_HAVE_SUBPROCESSES, 1, [Whether we have subprocesses]) ]) AC_CHECK_HEADERS_ONCE([complex.h sys/ioctl.h sys/ttycom.h]) AC_CHECK_FUNCS(isatty) Index: extra.mk.in ================================================================== --- extra.mk.in +++ extra.mk.in @@ -35,12 +35,12 @@ LIBOBJFWRT_DEP_LVL2 = @LIBOBJFWRT_DEP_LVL2@ LIBOBJFW_DEP = @LIBOBJFW_DEP@ LIBOBJFW_DEP_LVL2 = @LIBOBJFW_DEP_LVL2@ LINKLIB = @LINKLIB@ LOOKUP_ASM_A = @LOOKUP_ASM_A@ -LOOKUP_ASM_LIB_A = @LOOKUP_ASM_LIB_A@ LOOKUP_ASM_AMIGALIB_A = @LOOKUP_ASM_AMIGALIB_A@ +LOOKUP_ASM_LIB_A = @LOOKUP_ASM_LIB_A@ MAP_LDFLAGS = @MAP_LDFLAGS@ OFARC = @OFARC@ OFDNS = @OFDNS@ OFHASH = @OFHASH@ OFHTTP = @OFHTTP@ @@ -48,13 +48,13 @@ OF_BLOCK_TESTS_M = @OF_BLOCK_TESTS_M@ OF_EPOLL_KERNEL_EVENT_OBSERVER_M = @OF_EPOLL_KERNEL_EVENT_OBSERVER_M@ OF_HTTP_CLIENT_TESTS_M = @OF_HTTP_CLIENT_TESTS_M@ OF_KQUEUE_KERNEL_EVENT_OBSERVER_M = @OF_KQUEUE_KERNEL_EVENT_OBSERVER_M@ OF_POLL_KERNEL_EVENT_OBSERVER_M = @OF_POLL_KERNEL_EVENT_OBSERVER_M@ -OF_PROCESS_M = @OF_PROCESS_M@ OF_SCTP_SOCKET_M = @OF_SCTP_SOCKET_M@ OF_SELECT_KERNEL_EVENT_OBSERVER_M = @OF_SELECT_KERNEL_EVENT_OBSERVER_M@ +OF_SUBPROCESS_M = @OF_SUBPROCESS_M@ REEXPORT_RUNTIME = @REEXPORT_RUNTIME@ REEXPORT_RUNTIME_FRAMEWORK = @REEXPORT_RUNTIME_FRAMEWORK@ RUNTIME = @RUNTIME@ RUNTIME_ARC_TESTS_M = @RUNTIME_ARC_TESTS_M@ RUNTIME_AUTORELEASE_M = @RUNTIME_AUTORELEASE_M@ Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -65,11 +65,10 @@ OFObject.m \ OFObject+KeyValueCoding.m \ OFObject+Serialization.m \ OFOptionsParser.m \ OFPair.m \ - ${OF_PROCESS_M} \ OFRIPEMD160Hash.m \ OFRunLoop.m \ OFSandbox.m \ OFSecureData.m \ OFSeekableStream.m \ @@ -90,10 +89,11 @@ OFString+PropertyListParsing.m \ OFString+Serialization.m \ OFString+URLEncoding.m \ OFString+XMLEscaping.m \ OFString+XMLUnescaping.m \ + ${OF_SUBUPROCESS_M} \ OFSystemInfo.m \ OFTarArchive.m \ OFTarArchiveEntry.m \ OFThread.m \ OFTimer.m \ DELETED src/OFProcess.h Index: src/OFProcess.h ================================================================== --- src/OFProcess.h +++ src/OFProcess.h @@ -1,207 +0,0 @@ -/* - * Copyright (c) 2008-2021 Jonathan Schleifer - * - * 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. - */ - -#ifndef __STDC_LIMIT_MACROS -# define __STDC_LIMIT_MACROS -#endif -#ifndef __STDC_CONSTANT_MACROS -# define __STDC_CONSTANT_MACROS -#endif - -#include "objfw-defs.h" - -#ifdef OF_HAVE_SYS_TYPES_H -# include -#endif - -#import "OFStream.h" -#import "OFKernelEventObserver.h" -#import "OFString.h" - -#ifdef OF_WINDOWS -# include -#endif - -OF_ASSUME_NONNULL_BEGIN - -@class OFArray OF_GENERIC(ObjectType); -@class OFDictionary OF_GENERIC(KeyType, ObjectType); - -/** - * @class OFProcess OFProcess.h ObjFW/OFProcess.h - * - * @brief A class for stream-like communication with a newly created process. - */ -OF_SUBCLASSING_RESTRICTED -@interface OFProcess: OFStream -#ifndef OF_WINDOWS - -#endif -{ -#ifndef OF_WINDOWS - pid_t _pid; - int _readPipe[2], _writePipe[2]; -#else - HANDLE _process, _readPipe[2], _writePipe[2]; -#endif - int _status; - bool _atEndOfStream; -} - -/** - * @brief Creates a new OFProcess with the specified program and invokes the - * program. - * - * @param program The program to execute. If it does not start with a slash, the - * search path specified in PATH is used. - * @return A new, autoreleased OFProcess. - */ -+ (instancetype)processWithProgram: (OFString *)program; - -/** - * @brief Creates a new OFProcess with the specified program and arguments and - * invokes the program. - * - * @param program The program to execute. If it does not start with a slash, the - * search path specified in PATH is used. - * @param arguments The arguments to pass to the program, or `nil` - * @return A new, autoreleased OFProcess. - */ -+ (instancetype) - processWithProgram: (OFString *)program - arguments: (nullable OFArray OF_GENERIC(OFString *) *)arguments; - -/** - * @brief Creates a new OFProcess with the specified program, program name and - * arguments and invokes the program. - * - * @param program The program to execute. If it does not start with a slash, the - * search path specified in PATH is used. - * @param programName The program name for the program to invoke (argv[0]). - * Usually, this is equal to program. - * @param arguments The arguments to pass to the program, or `nil` - * @return A new, autoreleased OFProcess. - */ -+ (instancetype) - processWithProgram: (OFString *)program - programName: (OFString *)programName - arguments: (nullable OFArray OF_GENERIC(OFString *) *)arguments; - -/** - * @brief Creates a new OFProcess with the specified program, program name, - * arguments and environment and invokes the program. - * - * @param program The program to execute. If it does not start with a slash, the - * search path specified in PATH is used. - * @param programName The program name for the program to invoke (argv[0]). - * Usually, this is equal to program. - * @param arguments The arguments to pass to the program, or `nil` - * @param environment The environment to pass to the program, or `nil`. If it - * is not `nil`, the passed dictionary will be used to - * override the environment. If you want to add to the - * existing environment, you need to get the existing - * environment first, copy it, modify it and then pass it. - * @return A new, autoreleased OFProcess. - */ -+ (instancetype) - processWithProgram: (OFString *)program - programName: (OFString *)programName - arguments: (nullable OFArray OF_GENERIC(OFString *) *)arguments - environment: (nullable OFDictionary - OF_GENERIC(OFString *, OFString *) *)environment; - -- (instancetype)init OF_UNAVAILABLE; - -/** - * @brief Initializes an already allocated OFProcess with the specified program - * and invokes the program. - * - * @param program The program to execute. If it does not start with a slash, the - * search path specified in PATH is used. - * @return An initialized OFProcess. - */ -- (instancetype)initWithProgram: (OFString *)program; - -/** - * @brief Initializes an already allocated OFProcess with the specified program - * and arguments and invokes the program. - * - * @param program The program to execute. If it does not start with a slash, the - * search path specified in PATH is used. - * @param arguments The arguments to pass to the program, or `nil` - * @return An initialized OFProcess. - */ -- (instancetype) - initWithProgram: (OFString *)program - arguments: (nullable OFArray OF_GENERIC(OFString *) *)arguments; - -/** - * @brief Initializes an already allocated OFProcess with the specified program, - * program name and arguments and invokes the program. - * - * @param program The program to execute. If it does not start with a slash, the - * search path specified in PATH is used. - * @param programName The program name for the program to invoke (argv[0]). - * Usually, this is equal to program. - * @param arguments The arguments to pass to the program, or `nil` - * @return An initialized OFProcess. - */ -- (instancetype) - initWithProgram: (OFString *)program - programName: (OFString *)programName - arguments: (nullable OFArray OF_GENERIC(OFString *) *)arguments; - -/** - * @brief Initializes an already allocated OFProcess with the specified program, - * program name, arguments and environment and invokes the program. - * - * @param program The program to execute. If it does not start with a slash, the - * search path specified in PATH is used. - * @param programName The program name for the program to invoke (argv[0]). - * Usually, this is equal to program. - * @param arguments The arguments to pass to the program, or `nil` - * @param environment The environment to pass to the program, or `nil`. If it - * is not `nil`, the passed dictionary will be used to - * override the environment. If you want to add to the - * existing environment, you need to get the existing - * environment first, copy it, modify it and then pass it. - * @return An initialized OFProcess. - */ -- (instancetype) - initWithProgram: (OFString *)program - programName: (OFString *)programName - arguments: (nullable OFArray OF_GENERIC(OFString *) *)arguments - environment: (nullable OFDictionary - OF_GENERIC(OFString *, OFString *) *)environment - OF_DESIGNATED_INITIALIZER; - -/** - * @brief Closes the write direction of the process. - * - * This method needs to be called for some programs before data can be read, - * since some programs don't start processing before the write direction is - * closed. - */ -- (void)closeForWriting; - -/** - * @brief Waits for the process to terminate and returns the exit status. - * - * If the process has already exited, this returns the exit status immediately. - */ -- (int)waitForTermination; -@end - -OF_ASSUME_NONNULL_END DELETED src/OFProcess.m Index: src/OFProcess.m ================================================================== --- src/OFProcess.m +++ src/OFProcess.m @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2008-2021 Jonathan Schleifer - * - * 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 "platform.h" - -#ifdef OF_WINDOWS -# include "platform/windows/OFProcess.m" -#else -# include "platform/posix/OFProcess.m" -#endif ADDED src/OFSubprocess.h Index: src/OFSubprocess.h ================================================================== --- src/OFSubprocess.h +++ src/OFSubprocess.h @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2008-2021 Jonathan Schleifer + * + * 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. + */ + +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS +#endif +#ifndef __STDC_CONSTANT_MACROS +# define __STDC_CONSTANT_MACROS +#endif + +#include "objfw-defs.h" + +#ifdef OF_HAVE_SYS_TYPES_H +# include +#endif + +#import "OFStream.h" +#import "OFKernelEventObserver.h" +#import "OFString.h" + +#ifdef OF_WINDOWS +# include +#endif + +OF_ASSUME_NONNULL_BEGIN + +@class OFArray OF_GENERIC(ObjectType); +@class OFDictionary OF_GENERIC(KeyType, ObjectType); + +/** + * @class OFSubprocess OFSubprocess.h ObjFW/OFSubprocess.h + * + * @brief A class for stream-like communication with a newly created subprocess. + */ +OF_SUBCLASSING_RESTRICTED +@interface OFSubprocess: OFStream +#ifndef OF_WINDOWS + +#endif +{ +#ifndef OF_WINDOWS + pid_t _pid; + int _readPipe[2], _writePipe[2]; +#else + HANDLE _handle, _readPipe[2], _writePipe[2]; +#endif + int _status; + bool _atEndOfStream; +} + +/** + * @brief Creates a new OFSubprocess with the specified program and invokes the + * program. + * + * @param program The program to execute. If it does not start with a slash, the + * search path specified in PATH is used. + * @return A new, autoreleased OFSubprocess. + */ ++ (instancetype)subprocessWithProgram: (OFString *)program; + +/** + * @brief Creates a new OFSubprocess with the specified program and arguments + * and invokes the program. + * + * @param program The program to execute. If it does not start with a slash, the + * search path specified in PATH is used. + * @param arguments The arguments to pass to the program, or `nil` + * @return A new, autoreleased OFSubprocess. + */ ++ (instancetype) + subProcessWithProgram: (OFString *)program + arguments: (nullable OFArray OF_GENERIC(OFString *) *)arguments; + +/** + * @brief Creates a new OFSubprocess with the specified program, program name + * and arguments and invokes the program. + * + * @param program The program to execute. If it does not start with a slash, the + * search path specified in PATH is used. + * @param programName The program name for the program to invoke (argv[0]). + * Usually, this is equal to program. + * @param arguments The arguments to pass to the program, or `nil` + * @return A new, autoreleased OFSubprocess. + */ ++ (instancetype) + subprocessWithProgram: (OFString *)program + programName: (OFString *)programName + arguments: (nullable OFArray OF_GENERIC(OFString *) *)arguments; + +/** + * @brief Creates a new OFSubprocess with the specified program, program name, + * arguments and environment and invokes the program. + * + * @param program The program to execute. If it does not start with a slash, the + * search path specified in PATH is used. + * @param programName The program name for the program to invoke (argv[0]). + * Usually, this is equal to program. + * @param arguments The arguments to pass to the program, or `nil` + * @param environment The environment to pass to the program, or `nil`. If it + * is not `nil`, the passed dictionary will be used to + * override the environment. If you want to add to the + * existing environment, you need to get the existing + * environment first, copy it, modify it and then pass it. + * @return A new, autoreleased OFSubprocess. + */ ++ (instancetype) + subprocessWithProgram: (OFString *)program + programName: (OFString *)programName + arguments: (nullable OFArray OF_GENERIC(OFString *) *)arguments + environment: (nullable OFDictionary + OF_GENERIC(OFString *, OFString *) *)environment; + +- (instancetype)init OF_UNAVAILABLE; + +/** + * @brief Initializes an already allocated OFSubprocess with the specified + * program and invokes the program. + * + * @param program The program to execute. If it does not start with a slash, the + * search path specified in PATH is used. + * @return An initialized OFSubprocess. + */ +- (instancetype)initWithProgram: (OFString *)program; + +/** + * @brief Initializes an already allocated OFSubprocess with the specified + * program and arguments and invokes the program. + * + * @param program The program to execute. If it does not start with a slash, the + * search path specified in PATH is used. + * @param arguments The arguments to pass to the program, or `nil` + * @return An initialized OFSubprocess. + */ +- (instancetype) + initWithProgram: (OFString *)program + arguments: (nullable OFArray OF_GENERIC(OFString *) *)arguments; + +/** + * @brief Initializes an already allocated OFSubprocess with the specified + * program, program name and arguments and invokes the program. + * + * @param program The program to execute. If it does not start with a slash, the + * search path specified in PATH is used. + * @param programName The program name for the program to invoke (argv[0]). + * Usually, this is equal to program. + * @param arguments The arguments to pass to the program, or `nil` + * @return An initialized OFSubprocess. + */ +- (instancetype) + initWithProgram: (OFString *)program + programName: (OFString *)programName + arguments: (nullable OFArray OF_GENERIC(OFString *) *)arguments; + +/** + * @brief Initializes an already allocated OFSubprocess with the specified + * program, program name, arguments and environment and invokes the + * program. + * + * @param program The program to execute. If it does not start with a slash, the + * search path specified in PATH is used. + * @param programName The program name for the program to invoke (argv[0]). + * Usually, this is equal to program. + * @param arguments The arguments to pass to the program, or `nil` + * @param environment The environment to pass to the program, or `nil`. If it + * is not `nil`, the passed dictionary will be used to + * override the environment. If you want to add to the + * existing environment, you need to get the existing + * environment first, copy it, modify it and then pass it. + * @return An initialized OFSubprocess. + */ +- (instancetype) + initWithProgram: (OFString *)program + programName: (OFString *)programName + arguments: (nullable OFArray OF_GENERIC(OFString *) *)arguments + environment: (nullable OFDictionary + OF_GENERIC(OFString *, OFString *) *)environment + OF_DESIGNATED_INITIALIZER; + +/** + * @brief Closes the write direction of the subprocess. + * + * This method needs to be called for some programs before data can be read, + * since some programs don't start processing before the write direction is + * closed. + */ +- (void)closeForWriting; + +/** + * @brief Waits for the subprocess to terminate and returns the exit status. + * + * If the subprocess has already exited, this returns the exit status + * immediately. + */ +- (int)waitForTermination; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFSubprocess.m Index: src/OFSubprocess.m ================================================================== --- src/OFSubprocess.m +++ src/OFSubprocess.m @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2008-2021 Jonathan Schleifer + * + * 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 "platform.h" + +#ifdef OF_WINDOWS +# include "platform/windows/OFSubprocess.m" +#else +# include "platform/posix/OFSubprocess.m" +#endif Index: src/ObjFW.h ================================================================== --- src/ObjFW.h +++ src/ObjFW.h @@ -96,12 +96,12 @@ # import "OFHTTPRequest.h" # import "OFHTTPResponse.h" # import "OFHTTPServer.h" #endif -#ifdef OF_HAVE_PROCESSES -# import "OFProcess.h" +#ifdef OF_HAVE_SUBPROCESSES +# import "OFSubprocess.h" #endif #import "OFCryptoHash.h" #import "OFMD5Hash.h" #import "OFRIPEMD160Hash.h" DELETED src/platform/posix/OFProcess.m Index: src/platform/posix/OFProcess.m ================================================================== --- src/platform/posix/OFProcess.m +++ src/platform/posix/OFProcess.m @@ -1,403 +0,0 @@ -/* - * Copyright (c) 2008-2021 Jonathan Schleifer - * - * 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 -#include - -#include - -#ifdef HAVE_SYS_WAIT_H -# include -#endif - -#include "unistd_wrapper.h" -#ifdef HAVE_SPAWN_H -# include -#endif - -#import "OFProcess.h" -#import "OFString.h" -#import "OFArray.h" -#import "OFDictionary.h" -#import "OFLocale.h" - -#import "OFInitializationFailedException.h" -#import "OFNotOpenException.h" -#import "OFOutOfRangeException.h" -#import "OFReadFailedException.h" -#import "OFWriteFailedException.h" - -#ifndef HAVE_POSIX_SPAWNP -extern char **environ; -#endif - -@interface OFProcess () -- (void)of_getArgv: (char ***)argv - forProgramName: (OFString *)programName - andArguments: (OFArray *)arguments; -- (char **)of_environmentForDictionary: (OFDictionary *)dictionary; -@end - -@implementation OFProcess -+ (instancetype)processWithProgram: (OFString *)program -{ - return [[[self alloc] initWithProgram: program] autorelease]; -} - -+ (instancetype)processWithProgram: (OFString *)program - arguments: (OFArray *)arguments -{ - return [[[self alloc] initWithProgram: program - arguments: arguments] autorelease]; -} - -+ (instancetype)processWithProgram: (OFString *)program - programName: (OFString *)programName - arguments: (OFArray *)arguments -{ - return [[[self alloc] initWithProgram: program - programName: programName - arguments: arguments] autorelease]; -} - -+ (instancetype)processWithProgram: (OFString *)program - programName: (OFString *)programName - arguments: (OFArray *)arguments - environment: (OFDictionary *)environment -{ - return [[[self alloc] initWithProgram: program - programName: programName - arguments: arguments - environment: environment] autorelease]; -} - -- (instancetype)init -{ - OF_INVALID_INIT_METHOD -} - -- (instancetype)initWithProgram: (OFString *)program -{ - return [self initWithProgram: program - programName: program - arguments: nil - environment: nil]; -} - -- (instancetype)initWithProgram: (OFString *)program - arguments: (OFArray *)arguments -{ - return [self initWithProgram: program - programName: program - arguments: arguments - environment: nil]; -} - -- (instancetype)initWithProgram: (OFString *)program - programName: (OFString *)programName - arguments: (OFArray *)arguments -{ - return [self initWithProgram: program - programName: program - arguments: arguments - environment: nil]; -} - -- (instancetype)initWithProgram: (OFString *)program - programName: (OFString *)programName - arguments: (OFArray *)arguments - environment: (OFDictionary *)environment -{ - self = [super init]; - - @try { - void *pool = objc_autoreleasePoolPush(); - const char *path; - char **argv, **env = NULL; - - _pid = -1; - _readPipe[0] = _writePipe[1] = -1; - - if (pipe(_readPipe) != 0 || pipe(_writePipe) != 0) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; - - path = [program cStringWithEncoding: [OFLocale encoding]]; - [self of_getArgv: &argv - forProgramName: programName - andArguments: arguments]; - - @try { - env = [self of_environmentForDictionary: environment]; -#ifdef HAVE_POSIX_SPAWNP - posix_spawn_file_actions_t actions; - posix_spawnattr_t attr; - - if (posix_spawn_file_actions_init(&actions) != 0) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; - - if (posix_spawnattr_init(&attr) != 0) { - posix_spawn_file_actions_destroy(&actions); - - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; - } - - @try { - if (posix_spawn_file_actions_addclose(&actions, - _readPipe[0]) != 0 || - posix_spawn_file_actions_addclose(&actions, - _writePipe[1]) != 0 || - posix_spawn_file_actions_adddup2(&actions, - _writePipe[0], 0) != 0 || - posix_spawn_file_actions_adddup2(&actions, - _readPipe[1], 1) != 0) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; - -# ifdef POSIX_SPAWN_CLOEXEC_DEFAULT - if (posix_spawnattr_setflags(&attr, - POSIX_SPAWN_CLOEXEC_DEFAULT) != 0) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; -# endif - - if (posix_spawnp(&_pid, path, &actions, &attr, - argv, env) != 0) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; - } @finally { - posix_spawn_file_actions_destroy(&actions); - posix_spawnattr_destroy(&attr); - } -#else - if ((_pid = vfork()) == 0) { - environ = env; - - close(_readPipe[0]); - close(_writePipe[1]); - dup2(_writePipe[0], 0); - dup2(_readPipe[1], 1); - execvp(path, argv); - - _exit(EXIT_FAILURE); - } - - if (_pid == -1) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; -#endif - } @finally { - char **iter; - - close(_readPipe[1]); - close(_writePipe[0]); - free(argv); - - for (iter = env; *iter != NULL; iter++) - free(*iter); - - free(env); - } - - objc_autoreleasePoolPop(pool); - } @catch (id e) { - [self release]; - @throw e; - } - - return self; -} - -- (void)dealloc -{ - if (_readPipe[0] != -1) - [self close]; - - [super dealloc]; -} - -- (void)of_getArgv: (char ***)argv - forProgramName: (OFString *)programName - andArguments: (OFArray *)arguments -{ - OFString *const *objects = arguments.objects; - size_t i, count = arguments.count; - of_string_encoding_t encoding; - - *argv = of_alloc(count + 2, sizeof(char *)); - - encoding = [OFLocale encoding]; - - (*argv)[0] = (char *)[programName cStringWithEncoding: encoding]; - - for (i = 0; i < count; i++) - (*argv)[i + 1] = - (char *)[objects[i] cStringWithEncoding: encoding]; - - (*argv)[i + 1] = NULL; -} - -- (char **)of_environmentForDictionary: (OFDictionary *)environment -{ - char **envp; - size_t count; - of_string_encoding_t encoding; - - if (environment == nil) - return NULL; - - encoding = [OFLocale encoding]; - - count = environment.count; - envp = of_alloc_zeroed(count + 1, sizeof(char *)); - - @try { - OFEnumerator *keyEnumerator = [environment keyEnumerator]; - OFEnumerator *objectEnumerator = [environment objectEnumerator]; - - for (size_t i = 0; i < count; i++) { - OFString *key; - OFString *object; - size_t keyLen, objectLen; - - key = [keyEnumerator nextObject]; - object = [objectEnumerator nextObject]; - - keyLen = [key cStringLengthWithEncoding: encoding]; - objectLen = [object - cStringLengthWithEncoding: encoding]; - - envp[i] = of_alloc(keyLen + objectLen + 2, 1); - - memcpy(envp[i], - [key cStringWithEncoding: encoding], keyLen); - envp[i][keyLen] = '='; - memcpy(envp[i] + keyLen + 1, - [object cStringWithEncoding: encoding], objectLen); - envp[i][keyLen + objectLen + 1] = '\0'; - } - } @catch (id e) { - for (size_t i = 0; i < count; i++) - free(envp[i]); - - free(envp); - - @throw e; - } - - return envp; -} - -- (bool)lowlevelIsAtEndOfStream -{ - if (_readPipe[0] == -1) - @throw [OFNotOpenException exceptionWithObject: self]; - - return _atEndOfStream; -} - -- (size_t)lowlevelReadIntoBuffer: (void *)buffer - length: (size_t)length -{ - ssize_t ret; - - if (_readPipe[0] == -1) - @throw [OFNotOpenException exceptionWithObject: self]; - - if ((ret = read(_readPipe[0], buffer, length)) < 0) - @throw [OFReadFailedException exceptionWithObject: self - requestedLength: length - errNo: errno]; - - if (ret == 0) - _atEndOfStream = true; - - return ret; -} - -- (size_t)lowlevelWriteBuffer: (const void *)buffer - length: (size_t)length -{ - ssize_t bytesWritten; - - if (_writePipe[1] == -1) - @throw [OFNotOpenException exceptionWithObject: self]; - - if (length > SSIZE_MAX) - @throw [OFOutOfRangeException exception]; - - if ((bytesWritten = write(_writePipe[1], buffer, length)) < 0) - @throw [OFWriteFailedException exceptionWithObject: self - requestedLength: length - bytesWritten: 0 - errNo: errno]; - - return (size_t)bytesWritten; -} - -- (int)fileDescriptorForReading -{ - return _readPipe[0]; -} - -- (int)fileDescriptorForWriting -{ - return _writePipe[1]; -} - -- (void)closeForWriting -{ - if (_writePipe[1] != -1) - close(_writePipe[1]); - - _writePipe[1] = -1; -} - -- (void)close -{ - if (_readPipe[0] == -1) - @throw [OFNotOpenException exceptionWithObject: self]; - - [self closeForWriting]; - close(_readPipe[0]); - - if (_pid != -1) { - kill(_pid, SIGTERM); - waitpid(_pid, &_status, WNOHANG); - } - - _pid = -1; - _readPipe[0] = -1; - - [super close]; -} - -- (int)waitForTermination -{ - if (_readPipe[0] == -1) - @throw [OFNotOpenException exceptionWithObject: self]; - - if (_pid != -1) { - waitpid(_pid, &_status, 0); - _pid = -1; - } - - return WEXITSTATUS(_status); -} -@end ADDED src/platform/posix/OFSubprocess.m Index: src/platform/posix/OFSubprocess.m ================================================================== --- src/platform/posix/OFSubprocess.m +++ src/platform/posix/OFSubprocess.m @@ -0,0 +1,403 @@ +/* + * Copyright (c) 2008-2021 Jonathan Schleifer + * + * 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 +#include + +#include + +#ifdef HAVE_SYS_WAIT_H +# include +#endif + +#include "unistd_wrapper.h" +#ifdef HAVE_SPAWN_H +# include +#endif + +#import "OFSubprocess.h" +#import "OFString.h" +#import "OFArray.h" +#import "OFDictionary.h" +#import "OFLocale.h" + +#import "OFInitializationFailedException.h" +#import "OFNotOpenException.h" +#import "OFOutOfRangeException.h" +#import "OFReadFailedException.h" +#import "OFWriteFailedException.h" + +#ifndef HAVE_POSIX_SPAWNP +extern char **environ; +#endif + +@interface OFSubprocess () +- (void)of_getArgv: (char ***)argv + forProgramName: (OFString *)programName + andArguments: (OFArray *)arguments; +- (char **)of_environmentForDictionary: (OFDictionary *)dictionary; +@end + +@implementation OFSubprocess ++ (instancetype)subprocessWithProgram: (OFString *)program +{ + return [[[self alloc] initWithProgram: program] autorelease]; +} + ++ (instancetype)subprocessWithProgram: (OFString *)program + arguments: (OFArray *)arguments +{ + return [[[self alloc] initWithProgram: program + arguments: arguments] autorelease]; +} + ++ (instancetype)subprocessWithProgram: (OFString *)program + programName: (OFString *)programName + arguments: (OFArray *)arguments +{ + return [[[self alloc] initWithProgram: program + programName: programName + arguments: arguments] autorelease]; +} + ++ (instancetype)subprocessWithProgram: (OFString *)program + programName: (OFString *)programName + arguments: (OFArray *)arguments + environment: (OFDictionary *)environment +{ + return [[[self alloc] initWithProgram: program + programName: programName + arguments: arguments + environment: environment] autorelease]; +} + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (instancetype)initWithProgram: (OFString *)program +{ + return [self initWithProgram: program + programName: program + arguments: nil + environment: nil]; +} + +- (instancetype)initWithProgram: (OFString *)program + arguments: (OFArray *)arguments +{ + return [self initWithProgram: program + programName: program + arguments: arguments + environment: nil]; +} + +- (instancetype)initWithProgram: (OFString *)program + programName: (OFString *)programName + arguments: (OFArray *)arguments +{ + return [self initWithProgram: program + programName: program + arguments: arguments + environment: nil]; +} + +- (instancetype)initWithProgram: (OFString *)program + programName: (OFString *)programName + arguments: (OFArray *)arguments + environment: (OFDictionary *)environment +{ + self = [super init]; + + @try { + void *pool = objc_autoreleasePoolPush(); + const char *path; + char **argv, **env = NULL; + + _pid = -1; + _readPipe[0] = _writePipe[1] = -1; + + if (pipe(_readPipe) != 0 || pipe(_writePipe) != 0) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; + + path = [program cStringWithEncoding: [OFLocale encoding]]; + [self of_getArgv: &argv + forProgramName: programName + andArguments: arguments]; + + @try { + env = [self of_environmentForDictionary: environment]; +#ifdef HAVE_POSIX_SPAWNP + posix_spawn_file_actions_t actions; + posix_spawnattr_t attr; + + if (posix_spawn_file_actions_init(&actions) != 0) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; + + if (posix_spawnattr_init(&attr) != 0) { + posix_spawn_file_actions_destroy(&actions); + + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; + } + + @try { + if (posix_spawn_file_actions_addclose(&actions, + _readPipe[0]) != 0 || + posix_spawn_file_actions_addclose(&actions, + _writePipe[1]) != 0 || + posix_spawn_file_actions_adddup2(&actions, + _writePipe[0], 0) != 0 || + posix_spawn_file_actions_adddup2(&actions, + _readPipe[1], 1) != 0) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; + +# ifdef POSIX_SPAWN_CLOEXEC_DEFAULT + if (posix_spawnattr_setflags(&attr, + POSIX_SPAWN_CLOEXEC_DEFAULT) != 0) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; +# endif + + if (posix_spawnp(&_pid, path, &actions, &attr, + argv, env) != 0) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; + } @finally { + posix_spawn_file_actions_destroy(&actions); + posix_spawnattr_destroy(&attr); + } +#else + if ((_pid = vfork()) == 0) { + environ = env; + + close(_readPipe[0]); + close(_writePipe[1]); + dup2(_writePipe[0], 0); + dup2(_readPipe[1], 1); + execvp(path, argv); + + _exit(EXIT_FAILURE); + } + + if (_pid == -1) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; +#endif + } @finally { + char **iter; + + close(_readPipe[1]); + close(_writePipe[0]); + free(argv); + + for (iter = env; *iter != NULL; iter++) + free(*iter); + + free(env); + } + + objc_autoreleasePoolPop(pool); + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + if (_readPipe[0] != -1) + [self close]; + + [super dealloc]; +} + +- (void)of_getArgv: (char ***)argv + forProgramName: (OFString *)programName + andArguments: (OFArray *)arguments +{ + OFString *const *objects = arguments.objects; + size_t i, count = arguments.count; + of_string_encoding_t encoding; + + *argv = of_alloc(count + 2, sizeof(char *)); + + encoding = [OFLocale encoding]; + + (*argv)[0] = (char *)[programName cStringWithEncoding: encoding]; + + for (i = 0; i < count; i++) + (*argv)[i + 1] = + (char *)[objects[i] cStringWithEncoding: encoding]; + + (*argv)[i + 1] = NULL; +} + +- (char **)of_environmentForDictionary: (OFDictionary *)environment +{ + char **envp; + size_t count; + of_string_encoding_t encoding; + + if (environment == nil) + return NULL; + + encoding = [OFLocale encoding]; + + count = environment.count; + envp = of_alloc_zeroed(count + 1, sizeof(char *)); + + @try { + OFEnumerator *keyEnumerator = [environment keyEnumerator]; + OFEnumerator *objectEnumerator = [environment objectEnumerator]; + + for (size_t i = 0; i < count; i++) { + OFString *key; + OFString *object; + size_t keyLen, objectLen; + + key = [keyEnumerator nextObject]; + object = [objectEnumerator nextObject]; + + keyLen = [key cStringLengthWithEncoding: encoding]; + objectLen = [object + cStringLengthWithEncoding: encoding]; + + envp[i] = of_alloc(keyLen + objectLen + 2, 1); + + memcpy(envp[i], + [key cStringWithEncoding: encoding], keyLen); + envp[i][keyLen] = '='; + memcpy(envp[i] + keyLen + 1, + [object cStringWithEncoding: encoding], objectLen); + envp[i][keyLen + objectLen + 1] = '\0'; + } + } @catch (id e) { + for (size_t i = 0; i < count; i++) + free(envp[i]); + + free(envp); + + @throw e; + } + + return envp; +} + +- (bool)lowlevelIsAtEndOfStream +{ + if (_readPipe[0] == -1) + @throw [OFNotOpenException exceptionWithObject: self]; + + return _atEndOfStream; +} + +- (size_t)lowlevelReadIntoBuffer: (void *)buffer + length: (size_t)length +{ + ssize_t ret; + + if (_readPipe[0] == -1) + @throw [OFNotOpenException exceptionWithObject: self]; + + if ((ret = read(_readPipe[0], buffer, length)) < 0) + @throw [OFReadFailedException exceptionWithObject: self + requestedLength: length + errNo: errno]; + + if (ret == 0) + _atEndOfStream = true; + + return ret; +} + +- (size_t)lowlevelWriteBuffer: (const void *)buffer + length: (size_t)length +{ + ssize_t bytesWritten; + + if (_writePipe[1] == -1) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (length > SSIZE_MAX) + @throw [OFOutOfRangeException exception]; + + if ((bytesWritten = write(_writePipe[1], buffer, length)) < 0) + @throw [OFWriteFailedException exceptionWithObject: self + requestedLength: length + bytesWritten: 0 + errNo: errno]; + + return (size_t)bytesWritten; +} + +- (int)fileDescriptorForReading +{ + return _readPipe[0]; +} + +- (int)fileDescriptorForWriting +{ + return _writePipe[1]; +} + +- (void)closeForWriting +{ + if (_writePipe[1] != -1) + close(_writePipe[1]); + + _writePipe[1] = -1; +} + +- (void)close +{ + if (_readPipe[0] == -1) + @throw [OFNotOpenException exceptionWithObject: self]; + + [self closeForWriting]; + close(_readPipe[0]); + + if (_pid != -1) { + kill(_pid, SIGTERM); + waitpid(_pid, &_status, WNOHANG); + } + + _pid = -1; + _readPipe[0] = -1; + + [super close]; +} + +- (int)waitForTermination +{ + if (_readPipe[0] == -1) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (_pid != -1) { + waitpid(_pid, &_status, 0); + _pid = -1; + } + + return WEXITSTATUS(_status); +} +@end DELETED src/platform/windows/OFProcess.m Index: src/platform/windows/OFProcess.m ================================================================== --- src/platform/windows/OFProcess.m +++ src/platform/windows/OFProcess.m @@ -1,419 +0,0 @@ -/* - * Copyright (c) 2008-2021 Jonathan Schleifer - * - * 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 -#include - -#import "OFProcess.h" -#import "OFArray.h" -#import "OFData.h" -#import "OFDictionary.h" -#import "OFLocale.h" -#import "OFString.h" -#import "OFSystemInfo.h" - -#import "OFInitializationFailedException.h" -#import "OFNotOpenException.h" -#import "OFOutOfRangeException.h" -#import "OFReadFailedException.h" -#import "OFWriteFailedException.h" - -#include - -@interface OFProcess () -- (of_char16_t *)of_wideEnvironmentForDictionary: (OFDictionary *)dictionary; -- (char *)of_environmentForDictionary: (OFDictionary *)environment; -@end - -@implementation OFProcess -+ (instancetype)processWithProgram: (OFString *)program -{ - return [[[self alloc] initWithProgram: program] autorelease]; -} - -+ (instancetype)processWithProgram: (OFString *)program - arguments: (OFArray *)arguments -{ - return [[[self alloc] initWithProgram: program - arguments: arguments] autorelease]; -} - -+ (instancetype)processWithProgram: (OFString *)program - programName: (OFString *)programName - arguments: (OFArray *)arguments -{ - return [[[self alloc] initWithProgram: program - programName: programName - arguments: arguments] autorelease]; -} - -+ (instancetype)processWithProgram: (OFString *)program - programName: (OFString *)programName - arguments: (OFArray *)arguments - environment: (OFDictionary *)environment -{ - return [[[self alloc] initWithProgram: program - programName: programName - arguments: arguments - environment: environment] autorelease]; -} - -- (instancetype)init -{ - OF_INVALID_INIT_METHOD -} - -- (instancetype)initWithProgram: (OFString *)program -{ - return [self initWithProgram: program - programName: program - arguments: nil - environment: nil]; -} - -- (instancetype)initWithProgram: (OFString *)program - arguments: (OFArray *)arguments -{ - return [self initWithProgram: program - programName: program - arguments: arguments - environment: nil]; -} - -- (instancetype)initWithProgram: (OFString *)program - programName: (OFString *)programName - arguments: (OFArray *)arguments -{ - return [self initWithProgram: program - programName: program - arguments: arguments - environment: nil]; -} - -- (instancetype)initWithProgram: (OFString *)program - programName: (OFString *)programName - arguments: (OFArray *)arguments - environment: (OFDictionary *)environment -{ - self = [super init]; - - @try { - SECURITY_ATTRIBUTES sa; - PROCESS_INFORMATION pi; - void *pool; - OFMutableString *argumentsString; - - _process = INVALID_HANDLE_VALUE; - _readPipe[0] = _writePipe[1] = NULL; - - sa.nLength = sizeof(sa); - sa.bInheritHandle = TRUE; - sa.lpSecurityDescriptor = NULL; - - if (!CreatePipe(&_readPipe[0], &_readPipe[1], &sa, 0)) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; - - if (!SetHandleInformation(_readPipe[0], HANDLE_FLAG_INHERIT, 0)) - if (GetLastError() != ERROR_CALL_NOT_IMPLEMENTED) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; - - if (!CreatePipe(&_writePipe[0], &_writePipe[1], &sa, 0)) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; - - if (!SetHandleInformation(_writePipe[1], - HANDLE_FLAG_INHERIT, 0)) - if (GetLastError() != ERROR_CALL_NOT_IMPLEMENTED) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; - - memset(&pi, 0, sizeof(pi)); - - pool = objc_autoreleasePoolPush(); - - argumentsString = - [OFMutableString stringWithString: programName]; - [argumentsString replaceOccurrencesOfString: @"\\\"" - withString: @"\\\\\""]; - [argumentsString replaceOccurrencesOfString: @"\"" - withString: @"\\\""]; - - if ([argumentsString containsString: @" "]) { - [argumentsString prependString: @"\""]; - [argumentsString appendString: @"\""]; - } - - for (OFString *argument in arguments) { - 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: @"\""]; - } - - if ([OFSystemInfo isWindowsNT]) { - size_t length; - of_char16_t *argumentsCopy; - STARTUPINFOW si; - - 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; - - length = argumentsString.UTF16StringLength; - argumentsCopy = of_alloc(length + 1, - sizeof(of_char16_t)); - memcpy(argumentsCopy, argumentsString.UTF16String, - (length + 1) * 2); - @try { - if (!CreateProcessW(program.UTF16String, - argumentsCopy, NULL, NULL, TRUE, - CREATE_UNICODE_ENVIRONMENT, - [self of_wideEnvironmentForDictionary: - environment], NULL, &si, &pi)) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; - } @finally { - free(argumentsCopy); - } - } else { - of_string_encoding_t encoding = [OFLocale encoding]; - STARTUPINFO si; - - 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; - - if (!CreateProcessA([program cStringWithEncoding: - encoding], (char *)[argumentsString - cStringWithEncoding: encoding], NULL, NULL, TRUE, 0, - [self of_environmentForDictionary: environment], - NULL, &si, &pi)) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; - } - - objc_autoreleasePoolPop(pool); - - _process = pi.hProcess; - CloseHandle(pi.hThread); - - CloseHandle(_readPipe[1]); - CloseHandle(_writePipe[0]); - } @catch (id e) { - [self release]; - @throw e; - } - - return self; -} - -- (void)dealloc -{ - if (_readPipe[0] != NULL) - [self close]; - - [super dealloc]; -} - -- (of_char16_t *)of_wideEnvironmentForDictionary: (OFDictionary *)environment -{ - OFMutableData *env; - OFEnumerator *keyEnumerator, *objectEnumerator; - OFString *key, *object; - const of_char16_t equal = '='; - const of_char16_t zero[2] = { 0, 0 }; - - if (environment == nil) - return NULL; - - env = [OFMutableData dataWithItemSize: sizeof(of_char16_t)]; - - keyEnumerator = [environment keyEnumerator]; - objectEnumerator = [environment objectEnumerator]; - while ((key = [keyEnumerator nextObject]) != nil && - (object = [objectEnumerator nextObject]) != nil) { - [env addItems: key.UTF16String count: key.UTF16StringLength]; - [env addItems: &equal count: 1]; - [env addItems: object.UTF16String - count: object.UTF16StringLength]; - [env addItems: &zero count: 1]; - } - [env addItems: zero count: 2]; - - return env.mutableItems; -} - -- (char *)of_environmentForDictionary: (OFDictionary *)environment -{ - of_string_encoding_t encoding = [OFLocale encoding]; - OFMutableData *env; - OFEnumerator *keyEnumerator, *objectEnumerator; - OFString *key, *object; - - if (environment == nil) - return NULL; - - env = [OFMutableData data]; - - keyEnumerator = [environment keyEnumerator]; - objectEnumerator = [environment objectEnumerator]; - while ((key = [keyEnumerator nextObject]) != nil && - (object = [objectEnumerator nextObject]) != nil) { - [env addItems: [key cStringWithEncoding: encoding] - count: [key cStringLengthWithEncoding: encoding]]; - [env addItems: "=" count: 1]; - [env addItems: [object cStringWithEncoding: encoding] - count: [object cStringLengthWithEncoding: encoding]]; - [env addItems: "" count: 1]; - } - [env addItems: "\0" count: 2]; - - return env.mutableItems; -} - -- (bool)lowlevelIsAtEndOfStream -{ - if (_readPipe[0] == NULL) - @throw [OFNotOpenException exceptionWithObject: self]; - - return _atEndOfStream; -} - -- (size_t)lowlevelReadIntoBuffer: (void *)buffer length: (size_t)length -{ - DWORD ret; - - if (length > UINT32_MAX) - @throw [OFOutOfRangeException exception]; - - if (_readPipe[0] == NULL) - @throw [OFNotOpenException exceptionWithObject: self]; - - if (!ReadFile(_readPipe[0], buffer, (DWORD)length, &ret, NULL)) { - if (GetLastError() == ERROR_BROKEN_PIPE) { - _atEndOfStream = true; - return 0; - } - - @throw [OFReadFailedException exceptionWithObject: self - requestedLength: length - errNo: EIO]; - } - - if (ret == 0) - _atEndOfStream = true; - - return ret; -} - -- (size_t)lowlevelWriteBuffer: (const void *)buffer length: (size_t)length -{ - DWORD bytesWritten; - - if (length > UINT32_MAX) - @throw [OFOutOfRangeException exception]; - - if (_writePipe[1] == NULL) - @throw [OFNotOpenException exceptionWithObject: self]; - - if (!WriteFile(_writePipe[1], buffer, (DWORD)length, &bytesWritten, - NULL)) { - int errNo = EIO; - - if (GetLastError() == ERROR_BROKEN_PIPE) - errNo = EPIPE; - - @throw [OFWriteFailedException exceptionWithObject: self - requestedLength: length - bytesWritten: 0 - errNo: errNo]; - } - - return (size_t)bytesWritten; -} - -- (void)closeForWriting -{ - if (_writePipe[1] != NULL) - CloseHandle(_writePipe[1]); - - _writePipe[1] = NULL; -} - -- (void)close -{ - if (_readPipe[0] == NULL) - @throw [OFNotOpenException exceptionWithObject: self]; - - [self closeForWriting]; - CloseHandle(_readPipe[0]); - - if (_process != INVALID_HANDLE_VALUE) { - TerminateProcess(_process, 0); - CloseHandle(_process); - } - - _process = INVALID_HANDLE_VALUE; - _readPipe[0] = NULL; - - [super close]; -} - -- (int)waitForTermination -{ - if (_readPipe[0] == NULL) - @throw [OFNotOpenException exceptionWithObject: self]; - - if (_process != INVALID_HANDLE_VALUE) { - DWORD exitCode; - - WaitForSingleObject(_process, INFINITE); - - if (GetExitCodeProcess(_process, &exitCode)) - _status = exitCode; - else - _status = GetLastError(); - - CloseHandle(_process); - _process = INVALID_HANDLE_VALUE; - } - - return _status; -} -@end ADDED src/platform/windows/OFSubprocess.m Index: src/platform/windows/OFSubprocess.m ================================================================== --- src/platform/windows/OFSubprocess.m +++ src/platform/windows/OFSubprocess.m @@ -0,0 +1,419 @@ +/* + * Copyright (c) 2008-2021 Jonathan Schleifer + * + * 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 +#include + +#import "OFSubprocess.h" +#import "OFArray.h" +#import "OFData.h" +#import "OFDictionary.h" +#import "OFLocale.h" +#import "OFString.h" +#import "OFSystemInfo.h" + +#import "OFInitializationFailedException.h" +#import "OFNotOpenException.h" +#import "OFOutOfRangeException.h" +#import "OFReadFailedException.h" +#import "OFWriteFailedException.h" + +#include + +@interface OFSubprocess () +- (of_char16_t *)of_wideEnvironmentForDictionary: (OFDictionary *)dictionary; +- (char *)of_environmentForDictionary: (OFDictionary *)environment; +@end + +@implementation OFSubprocess ++ (instancetype)subprocessWithProgram: (OFString *)program +{ + return [[[self alloc] initWithProgram: program] autorelease]; +} + ++ (instancetype)subprocessWithProgram: (OFString *)program + arguments: (OFArray *)arguments +{ + return [[[self alloc] initWithProgram: program + arguments: arguments] autorelease]; +} + ++ (instancetype)subprocessWithProgram: (OFString *)program + programName: (OFString *)programName + arguments: (OFArray *)arguments +{ + return [[[self alloc] initWithProgram: program + programName: programName + arguments: arguments] autorelease]; +} + ++ (instancetype)subprocessWithProgram: (OFString *)program + programName: (OFString *)programName + arguments: (OFArray *)arguments + environment: (OFDictionary *)environment +{ + return [[[self alloc] initWithProgram: program + programName: programName + arguments: arguments + environment: environment] autorelease]; +} + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (instancetype)initWithProgram: (OFString *)program +{ + return [self initWithProgram: program + programName: program + arguments: nil + environment: nil]; +} + +- (instancetype)initWithProgram: (OFString *)program + arguments: (OFArray *)arguments +{ + return [self initWithProgram: program + programName: program + arguments: arguments + environment: nil]; +} + +- (instancetype)initWithProgram: (OFString *)program + programName: (OFString *)programName + arguments: (OFArray *)arguments +{ + return [self initWithProgram: program + programName: program + arguments: arguments + environment: nil]; +} + +- (instancetype)initWithProgram: (OFString *)program + programName: (OFString *)programName + arguments: (OFArray *)arguments + environment: (OFDictionary *)environment +{ + self = [super init]; + + @try { + SECURITY_ATTRIBUTES sa; + PROCESS_INFORMATION pi; + void *pool; + OFMutableString *argumentsString; + + _handle = INVALID_HANDLE_VALUE; + _readPipe[0] = _writePipe[1] = NULL; + + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + sa.lpSecurityDescriptor = NULL; + + if (!CreatePipe(&_readPipe[0], &_readPipe[1], &sa, 0)) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; + + if (!SetHandleInformation(_readPipe[0], HANDLE_FLAG_INHERIT, 0)) + if (GetLastError() != ERROR_CALL_NOT_IMPLEMENTED) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; + + if (!CreatePipe(&_writePipe[0], &_writePipe[1], &sa, 0)) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; + + if (!SetHandleInformation(_writePipe[1], + HANDLE_FLAG_INHERIT, 0)) + if (GetLastError() != ERROR_CALL_NOT_IMPLEMENTED) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; + + memset(&pi, 0, sizeof(pi)); + + pool = objc_autoreleasePoolPush(); + + argumentsString = + [OFMutableString stringWithString: programName]; + [argumentsString replaceOccurrencesOfString: @"\\\"" + withString: @"\\\\\""]; + [argumentsString replaceOccurrencesOfString: @"\"" + withString: @"\\\""]; + + if ([argumentsString containsString: @" "]) { + [argumentsString prependString: @"\""]; + [argumentsString appendString: @"\""]; + } + + for (OFString *argument in arguments) { + 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: @"\""]; + } + + if ([OFSystemInfo isWindowsNT]) { + size_t length; + of_char16_t *argumentsCopy; + STARTUPINFOW si; + + 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; + + length = argumentsString.UTF16StringLength; + argumentsCopy = of_alloc(length + 1, + sizeof(of_char16_t)); + memcpy(argumentsCopy, argumentsString.UTF16String, + (length + 1) * 2); + @try { + if (!CreateProcessW(program.UTF16String, + argumentsCopy, NULL, NULL, TRUE, + CREATE_UNICODE_ENVIRONMENT, + [self of_wideEnvironmentForDictionary: + environment], NULL, &si, &pi)) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; + } @finally { + free(argumentsCopy); + } + } else { + of_string_encoding_t encoding = [OFLocale encoding]; + STARTUPINFO si; + + 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; + + if (!CreateProcessA([program cStringWithEncoding: + encoding], (char *)[argumentsString + cStringWithEncoding: encoding], NULL, NULL, TRUE, 0, + [self of_environmentForDictionary: environment], + NULL, &si, &pi)) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; + } + + objc_autoreleasePoolPop(pool); + + _handle = pi.hProcess; + CloseHandle(pi.hThread); + + CloseHandle(_readPipe[1]); + CloseHandle(_writePipe[0]); + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + if (_readPipe[0] != NULL) + [self close]; + + [super dealloc]; +} + +- (of_char16_t *)of_wideEnvironmentForDictionary: (OFDictionary *)environment +{ + OFMutableData *env; + OFEnumerator *keyEnumerator, *objectEnumerator; + OFString *key, *object; + const of_char16_t equal = '='; + const of_char16_t zero[2] = { 0, 0 }; + + if (environment == nil) + return NULL; + + env = [OFMutableData dataWithItemSize: sizeof(of_char16_t)]; + + keyEnumerator = [environment keyEnumerator]; + objectEnumerator = [environment objectEnumerator]; + while ((key = [keyEnumerator nextObject]) != nil && + (object = [objectEnumerator nextObject]) != nil) { + [env addItems: key.UTF16String count: key.UTF16StringLength]; + [env addItems: &equal count: 1]; + [env addItems: object.UTF16String + count: object.UTF16StringLength]; + [env addItems: &zero count: 1]; + } + [env addItems: zero count: 2]; + + return env.mutableItems; +} + +- (char *)of_environmentForDictionary: (OFDictionary *)environment +{ + of_string_encoding_t encoding = [OFLocale encoding]; + OFMutableData *env; + OFEnumerator *keyEnumerator, *objectEnumerator; + OFString *key, *object; + + if (environment == nil) + return NULL; + + env = [OFMutableData data]; + + keyEnumerator = [environment keyEnumerator]; + objectEnumerator = [environment objectEnumerator]; + while ((key = [keyEnumerator nextObject]) != nil && + (object = [objectEnumerator nextObject]) != nil) { + [env addItems: [key cStringWithEncoding: encoding] + count: [key cStringLengthWithEncoding: encoding]]; + [env addItems: "=" count: 1]; + [env addItems: [object cStringWithEncoding: encoding] + count: [object cStringLengthWithEncoding: encoding]]; + [env addItems: "" count: 1]; + } + [env addItems: "\0" count: 2]; + + return env.mutableItems; +} + +- (bool)lowlevelIsAtEndOfStream +{ + if (_readPipe[0] == NULL) + @throw [OFNotOpenException exceptionWithObject: self]; + + return _atEndOfStream; +} + +- (size_t)lowlevelReadIntoBuffer: (void *)buffer length: (size_t)length +{ + DWORD ret; + + if (length > UINT32_MAX) + @throw [OFOutOfRangeException exception]; + + if (_readPipe[0] == NULL) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (!ReadFile(_readPipe[0], buffer, (DWORD)length, &ret, NULL)) { + if (GetLastError() == ERROR_BROKEN_PIPE) { + _atEndOfStream = true; + return 0; + } + + @throw [OFReadFailedException exceptionWithObject: self + requestedLength: length + errNo: EIO]; + } + + if (ret == 0) + _atEndOfStream = true; + + return ret; +} + +- (size_t)lowlevelWriteBuffer: (const void *)buffer length: (size_t)length +{ + DWORD bytesWritten; + + if (length > UINT32_MAX) + @throw [OFOutOfRangeException exception]; + + if (_writePipe[1] == NULL) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (!WriteFile(_writePipe[1], buffer, (DWORD)length, &bytesWritten, + NULL)) { + int errNo = EIO; + + if (GetLastError() == ERROR_BROKEN_PIPE) + errNo = EPIPE; + + @throw [OFWriteFailedException exceptionWithObject: self + requestedLength: length + bytesWritten: 0 + errNo: errNo]; + } + + return (size_t)bytesWritten; +} + +- (void)closeForWriting +{ + if (_writePipe[1] != NULL) + CloseHandle(_writePipe[1]); + + _writePipe[1] = NULL; +} + +- (void)close +{ + if (_readPipe[0] == NULL) + @throw [OFNotOpenException exceptionWithObject: self]; + + [self closeForWriting]; + CloseHandle(_readPipe[0]); + + if (_handle != INVALID_HANDLE_VALUE) { + TerminateProcess(_handle, 0); + CloseHandle(_handle); + } + + _handle = INVALID_HANDLE_VALUE; + _readPipe[0] = NULL; + + [super close]; +} + +- (int)waitForTermination +{ + if (_readPipe[0] == NULL) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (_handle != INVALID_HANDLE_VALUE) { + DWORD exitCode; + + WaitForSingleObject(_handle, INFINITE); + + if (GetExitCodeProcess(_handle, &exitCode)) + _status = exitCode; + else + _status = GetLastError(); + + CloseHandle(_handle); + _handle = INVALID_HANDLE_VALUE; + } + + return _status; +} +@end