// -*- c++ -*- /* * Jakelib2 - General purpose C++ library * Copyright (C) 2001 Florian Wolff (florian@donuz.de) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * $Id: Process.jlc,v 1.19 2006-02-18 11:56:47 florian Exp $ */ #include "jakelib2.h" #include "jakelib2/lang/Process.h" #include "jakelib2/lang/System.h" #include "jakelib2/io/PipeInputStream.h" #include "jakelib2/io/PipeOutputStream.h" #include using namespace jakelib::lang; using namespace jakelib::io; JAKELIB_IMPLEMENT_CLASS("jakelib.lang.Process", Process, Object) #ifdef HAVE_SCHED_H # include #endif #ifdef HAVE_ERRNO_H # include #endif #if defined(HAVE_STDARG_H) || defined(JAKELIB_WIN32API) # include #endif #ifdef HAVE_SYS_WAIT_H # include #endif #ifdef HAVE_SIGNAL_H # include #endif #include /*****************************************************************************\ * Process | *****************************************************************************/ Process::Process(String* cmdline, ...) { init1(); #if defined(JAKELIB_WIN32API) ZeroMemory(&procInfo, sizeof(PROCESS_INFORMATION)); SECURITY_ATTRIBUTES secAttr; secAttr.nLength = sizeof(SECURITY_ATTRIBUTES); secAttr.bInheritHandle = TRUE; secAttr.lpSecurityDescriptor = NULL; // Create pipes for stdin, stdout and stderr: HANDLE childInPipe, childOutPipe, childErrPipe; if (!CreatePipe(&inPipe, &childOutPipe, &secAttr, 0)) { throw new IOException(`"CreatePipe failed for stdin: "` .. System::explainErrorCode(GetLastError()) .. JAKELIB_AT2("jakelib.lang.Process.Process")); } if (!CreatePipe(&errPipe, &childErrPipe, &secAttr, 0)) { throw new IOException(`"CreatePipe failed for stderr: "` .. System::explainErrorCode(GetLastError()) .. JAKELIB_AT2("jakelib.lang.Process.Process")); } if (!CreatePipe(&childInPipe, &outPipe, &secAttr, 0)) { throw new IOException(`"CreatePipe failed for stdout: "` .. System::explainErrorCode(GetLastError()) .. JAKELIB_AT2("jakelib.lang.Process.Process")); } // Duplicate write end of StdOut pipe : HANDLE dupHandle; if (!DuplicateHandle(GetCurrentProcess(), outPipe, GetCurrentProcess(), &dupHandle, 0, FALSE, DUPLICATE_SAME_ACCESS)) { throw new IOException(`"DuplicateHandle failed: "` .. System::explainErrorCode(GetLastError()) .. JAKELIB_AT2("jakelib.lang.Process.Process")); } CloseHandle(outPipe); outPipe = dupHandle; // Build up one command line string: va_list ptr; va_start(ptr, cmdline); StringBuffer* args = new StringBuffer(); String* s; args->append(cmdline)->append(' '); while ((s = va_arg(ptr, String*)) != null) { args->append(s)->append(' '); } // Build up Startup-Info structure: STARTUPINFO startInfo; ZeroMemory(&startInfo, sizeof(STARTUPINFO)); startInfo.cb = sizeof(STARTUPINFO); startInfo.wShowWindow = SW_SHOW; startInfo.dwFlags = STARTF_USESTDHANDLES; startInfo.hStdInput = childInPipe; startInfo.hStdOutput = childOutPipe; startInfo.hStdError = childErrPipe; // Actually create the new process: if (!CreateProcess(NULL, args->toString()->latin1(), NULL, NULL, TRUE, 0, 0, 0, &startInfo, &procInfo)) { throw new IOException(System::explainErrorCode(GetLastError()) .. `":"` .. System::eol .. args .. JAKELIB_AT2("jakelib.lang.Process.Process")); } // Close children's end of all pipes: CloseHandle(childInPipe); CloseHandle(childOutPipe); CloseHandle(childErrPipe); #else int stdoutPipes[2], stdinPipes[2], stderrPipes[2]; pid_t pid; if (pipe(stdoutPipes) != 0) { throw new IOException(`"Cannot create pipe"` .. JAKELIB_AT2("jakelib.lang.Process.Process")); } if (pipe(stdinPipes) != 0) { throw new IOException(`"Cannot create pipe"` .. JAKELIB_AT2("jakelib.lang.Process.Process")); } if (pipe(stderrPipes) != 0) { throw new IOException(`"Cannot create pipe"` .. JAKELIB_AT2("jakelib.lang.Process.Process")); } childPid = fork(); if (childPid < 0) { throw new IOException(`"Unable to fork new process"` .. JAKELIB_AT2("jakelib.lang.Process.Process")); } if (childPid == 0) { // Child process: // Count the number of arguments given: va_list ptr; va_start(ptr, cmdline); String* s; int num = 0; while ((s = va_arg(ptr, String*)) != null) num++; // Put all arguments in an array - element 0 is the program name, last element is null: char** argv = (char**) malloc(sizeof(char*) * (num +2)); num = 0; va_start(ptr, cmdline); argv[num++] = JAKELIB_LATIN1((String*) cmdline); while ((s = va_arg(ptr, String*)) != null) argv[num++] = JAKELIB_LATIN1(s); argv[num] = null; dup2(stdoutPipes[1], fileno(stdout)); dup2(stderrPipes[1], fileno(stderr)); dup2(stdinPipes[0], fileno(stdin)); execvp(JAKELIB_LATIN1(cmdline), argv); int e = errno; fprintf(stderr, "Cannot exec: %s - %s", JAKELIB_LATIN1(cmdline), strerror(e)); exit(2); } else { // Parent process: inPipe = stdoutPipes[0]; outPipe = stdinPipes[1]; errPipe = stderrPipes[0]; close(stdinPipes[0]); close(stdoutPipes[1]); close(stderrPipes[1]); // FIXME: Throw Exception if execvp failed! } #endif } Process::~Process() { #if defined(JAKELIB_WIN32API) if (procInfo.hProcess != INVALID_HANDLE_VALUE) { CloseHandle(procInfo.hProcess); CloseHandle(procInfo.hThread); } #endif } /*****************************************************************************\ * init1 | *****************************************************************************/ void Process::init1() { inputStream = null; outputStream = null; errorStream = null; terminated = false; _exitValue = 255; } /*****************************************************************************\ * getInputStream | *****************************************************************************/ InputStream* Process::getInputStream() { if (inputStream == null) { inputStream = new PipeInputStream(inPipe); inputStream; } return inputStream; } /*****************************************************************************\ * getErrorStream | *****************************************************************************/ InputStream* Process::getErrorStream() { if (errorStream == null) { errorStream = new PipeInputStream(errPipe); errorStream; } return errorStream; } /*****************************************************************************\ * getOutputStream | *****************************************************************************/ jakelib::io::OutputStream* Process::getOutputStream() { if (outputStream == null) { outputStream = new PipeOutputStream(outPipe); outputStream; } return outputStream; } /*****************************************************************************\ * destroy | *****************************************************************************/ void Process::destroy() { #if defined(JAKELIB_WIN32API) TerminateProcess(procInfo.hProcess, -1); CloseHandle(procInfo.hProcess); CloseHandle(procInfo.hThread); procInfo.hProcess = INVALID_HANDLE_VALUE; procInfo.hThread = INVALID_HANDLE_VALUE; #else kill(childPid, SIGKILL); #endif } /*****************************************************************************\ * waitFor | *****************************************************************************/ void Process::waitFor() { #if defined(JAKELIB_WIN32API) WaitForSingleObject(procInfo.hProcess, INFINITE); #else // FIXME: error handling waitpid(childPid, &_exitValue, 0); terminated = true; #endif } /*****************************************************************************\ * exitValue | *****************************************************************************/ int Process::exitValue() { if (terminated) { return _exitValue; } #if defined(JAKELIB_WIN32API) if (!GetExitCodeProcess(procInfo.hProcess, &_exitValue)) { throw new IOException(`"Cannot determine exit code: #"` .. (jlong) GetLastError() .. JAKELIB_AT2("jakelib.lang.Process.exitValue")); } if (_exitValue == STILL_ACTIVE) { throw new IllegalThreadStateException(`"Subprocess has not yet terminated"` .. JAKELIB_AT2("jakelib.lang.Process.exitValue")); } #else if (waitpid(childPid, &_exitValue, WNOHANG) <= 0) { throw new IllegalThreadStateException(`"Subprocess has not yet terminated"` .. JAKELIB_AT2("jakelib.lang.Process.exitValue")); if (WIFEXITED(_exitValue)) { _exitValue = WEXITSTATUS(_exitValue); } else if (WIFSTOPPED(_exitValue)) { throw new IOException(`"Process was stopped: #"` .. _exitValue .. JAKELIB_AT2("jakelib.lang.Process.exitValue")); } else { throw new IOException(`"Process aborted abnormally: #"` .. _exitValue .. JAKELIB_AT2("jakelib.lang.Process.exitValue")); } _exitValue = _exitValue & 0xff; } #endif terminated = true; return _exitValue; }