2005/5/9

     
 

Process.cpp

artefaktur
// -*- mode:C++; tab-width:2; c-basic-offset:2; indent-tabs-mode:nil -*-
//
// Copyright (C) 2000-2005 by Roger Rene Kommer / artefaktur, Kassel, Germany.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public License (LGPL).
// 
//
// 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
// License ACDK-FreeLicense document enclosed in the distribution
// for more for more details.
// This file is part of the Artefaktur Component Development Kit:
//                         ACDK
//
// Please refer to
// - http://www.acdk.de
// - http://www.artefaktur.com
// - http://acdk.sourceforge.net
// for more information.
//
// $Header: /cvsroot/acdk/acdk/acdk_core/src/acdk/lang/Process.cpp,v 1.53 2005/04/25 23:03:34 kommer Exp $


#include <acdk.h>
#include "Thread.h"  // needed for Thread::sleep()
#include "Process.h"
#include "System.h"  // needed for Process::_run()
#include "Integer.h"
#include "sys/core_tick.h"
#include "../util/SysDate.h"

#include <stdlib.h>

#if defined(ACDK_OS_LINUX) || defined(ACDK_OS_CYGWIN32) || defined(ACDK_OS_SOLARIS) || defined(ACDK_OS_BSD) || defined(ACDK_OS_DARWIN)
# include <sys/types.h>
# include <sys/wait.h>
# include <signal.h>
# include <errno.h>
# include <unistd.h>
#endif
/FONT>

#include "ObjectArrayImpl.h"
#include "IllegalThreadStateException.h"

#include <acdk/io/FileReader.h>
#include <acdk/io/FileWriter.h>
#include <acdk/io/FileDescriptor.h>
#include <acdk/io/IOException.h>
#include <acdk/util/StringTokenizer.h>
#if defined(ACDK_OS_UNIX) || defined(ACDK_OS_CYGWIN32)
#include <acdk/util/StringTokenizer.h>
#include <acdk/util/NoSuchElementException.h>
#include <acdk/io/File.h>
#include <acdk/io/FileReader.h>
#endif // defined(ACDK_OS_UNIX)
#include <acdk/io/StreamTokenizer.h>
#include <acdk/io/StringReader.h>

#include "SystemIntern.h"

#if defined(ACDK_OS_WIN32) && !defined(ACDK_OS_CYGWIN32)
#include <tlhelp32.h>

#endif 

namespace acdk {
namespace lang {

/*
#if defined(ACDK_MINGW)

int _open_osfhandle ( long osfhandle, int flags )
{
  return -1;
}
long _get_osfhandle( int filehandle )
{
  return 0;
}

#endif //defined(ACDK_MINGW)
*/

//#define LOCAL_DEBUG
#ifdef LOCAL_DEBUG
#define DOUT(strexpr) \
do { \
  StringBuffer sb; \
  sb << strexpr; \
  System::out->println(sb.toString()); \
} while (false)
#else
/FONT>
#define DOUT(strexpr)
#endif
/FONT>

Process_PipeReader::Process_PipeReader(IN(::acdk::io::RFileDescriptor) fd, bool dupl)
: ::acdk::io::FileReader(fd, dupl)
{
}

USING_CLASS(acdk::io::, FileDescriptor);
USING_CLASS(acdk::io::, IOException);
USING_CLASS(acdk::io::, Reader);
USING_CLASS(acdk::io::, Writer);
USING_CLASS(acdk::io::, FileReader);
USING_CLASS(acdk::io::, FileWriter);

//using namespace ::acdk::util;
String::iterator consumeWs(String::iterator it, String::iterator end)
{
  while (it < end)
  {
    if (*it != ' ' && *it != '\t')
    {
      --it;
      return it;
    }
    ++it;
  }
  return it;
}

RStringArray
parseCommand(IN(RString) command, IN(RStringArray) _args, IN(RStringArray) _envp)
{
  acdk::io::StringReader cin(command);
  acdk::io::StreamTokenizer tin(&cin);
  tin.readCxxIdentifier(false);
  tin.readOperator(false);
  tin.readNumberAsString(true);
  tin.wantWhiteSpace(true);
  tin.parseCCComments(false);
  int tk;
  StringBuffer sb;
  while ((tk = tin.nextToken()) != acdk::io::StreamTokenizer::TT_EOF)
  {
    if (tk == acdk::io::StreamTokenizer::TT_WS)
    {
      _args->append(sb.toString());
      sb.reset();
    }
    else
    {
      sb.append(tin.sval);
    }
  }
  if (sb.length() > 0)
    _args->append(sb.toString());
  return _args;
}

/* old
RStringArray _parseCommand(IN(RString) command, bool expand,
                          IN(RStringArray) _args, IN(RStringArray) _envp)
{
  if (expand == true)
  {
    sys::coreout << "parseCommand expand = true" << sys::eofl;
    int argind = 0;
    int envind = 0;
    int n;
    bool quote = false;
    bool escape = false;
    bool wsp = false;
    bool isenv = false;
    bool envable = true;
    int start = 0;
    StringBuffer sb;
    for (n = 0; n < command->length(); n++)
    {
      char c = command->charAt(n);
      if (escape == true)
      {
        escape = false;
        sb.append(c);
        continue;
      }
      if ((quote == true) && (c == '"')) {
        sb.append(c);
        quote = false;
        continue;
      }
      switch (c) {
        case '"':
          envable = false;
          quote = true;
          break;
        case '\\':
          envable = false;
          escape = true;
          break;
        case ' ':
        case '\t':
          if (sb.length() == 0)
            break;
          if (isenv == true) {
            _envp->resize(envind + 1);
            _envp->set(envind++, sb.toString());
            isenv = false;
            envable = true;
          } else {
            _args->resize(argind + 1);
            _args->set(argind++, sb.toString());
            envable = false;
          }
          sb.reset();
          break;
        case '=':
          if (envable == true) {
            isenv = true;
          }
          // fall through
        default:
          sb.append(c);
          break;
      }
    }
    _args->resize(argind + 1);
    _args->set(argind++, sb.toString());
    _args->resize(argind); // needed, because ensureCapacity() may have expanded it too much.
    _envp->resize(envind);
    sb = Nil;
  }
  else
{
    bool inDoubleQuote = false;
    bool inSingleQuote = false;
    bool nextQuoted = false;
    StringBuffer sb;
    sys::coreout << command->c_str() << sys::eofl;
    for (String::iterator it = command->begin(); it < command->end(); ++it)
    {
      if (*it == '\\')
      {
        nextQuoted = true;
        //sb.append(*it);
        continue;
      }
      else if (*it == '"')
      {
        if (nextQuoted == true)
        {
          sb.append(*it);
        }
        else if (inSingleQuote == true)
        {
          sb.append(*it);
        }
        else if (inDoubleQuote == true)
        {
          _args->append(sb.toString());
          sb.reset();
          inDoubleQuote = false;
        } else
          inDoubleQuote = true;
      }
      else if (*it == '\'')
      {
        if (nextQuoted == true)
        {
          sb.append(*it);
        }
        else if (inDoubleQuote == true)
        {
          sb.append(*it);
        }
        else if (inSingleQuote == true)
        {
          _args->append(sb.toString());
          sb.reset();
          inSingleQuote = false;
        } else
          inSingleQuote = true;
      }
      else if (*it == ' ' || *it == '\t')
      {
        if (inSingleQuote == true || inDoubleQuote == true)
        {
          sb.append(*it);
        }
        else
        {
          _args->append(sb.toString());
          sb.reset();
          it = consumeWs(it, command->end());
        }
      }
      else
      {
        sb.append(*it);
      }
      nextQuoted = false;
    }
    if (sb.length() > 0)
      _args->append(sb.toString());
  }
  return _args;
}
*/

Process::Process(IN(RString) command, IN(RString) workdir)
: _command(command)
, _workDir(workdir)
, _status(ProcessNotStarted)
{
  _args = new StringArray(0);
  _envp = new StringArray(0);
  parseCommand(command, _args, _envp);
    _run();
}

Process::Process(IN(RString) command, IN(RStringArray) envp, IN(RString) workdir)
: _command(command)
, _workDir(workdir)
, _status(ProcessNotStarted)
{
  _args = new StringArray(0);
  _envp = envp;
  parseCommand(command, _args, _envp);
  _run();
}

Process::Process(IN(RStringArray) cmdarray, IN(RStringArray) envp, IN(RString) workdir)
: _workDir(workdir)
, _status(ProcessNotStarted)
{
  _args = cmdarray;
  _envp = envp;

  _run();
}

Process::Process(IN(RStringArray) cmdarray, IN(RString) workdir)
: _workDir(workdir)
, _status(ProcessNotStarted)
{
  _args = cmdarray;
  _envp = new StringArray(0);
  _run();
}

RString
Process::getCommandLine()
{
  if (_command != Nil)
    return _command;
  StringBuffer sb;
  for (int i = 0; i < _args->length(); ++i)
  {
    if (i > 0)
      sb << " ";
    RString s = _args[i];
    if (s->indexOf(' ') != -1 ||
        s->indexOf('\t') != -1 ||
        s->indexOf('\"') != -1 ||
        s->indexOf('\'') != -1)
    {
      if (s->indexOf('\"') != -1)
        sb << "\"" << s << "\"";
      else
        sb << "\'" << s << "\'";
    }
    else
      sb << s;
  }
  _command = sb.toString();
  return _command;
}

//virtual
void
Process::destroy()
{
  DOUT("Process::destroy()");
 if (_status == ProcessAlreadyDestroyed)
   return;

#if defined(ACDK_OS_WIN32) && !defined(ACDK_OS_CYGWIN32)
  TerminateProcess(_processInfo.hProcess, ERROR_PROCESS_ABORTED);
#else
/FONT>
  
  int n;
  kill(_processId, SIGTERM);  // maybe we should sent SIGKILL immediately
  for (n = 0; n < 10; n++) 
  {
    if (isRunning() == false)
      return;
    Thread::sleep(20);
  }
  kill(_processId, SIGKILL);  // note: SIGINT is catchable!!
  DOUT("Process::destroyed");
#endif
/FONT>
  _status = ProcessAlreadyDestroyed;
}

Process::~Process() 
{
  DOUT("Process::~Process()");
  if (_status == ProcessStillRunning)
    waitFor();

  _stdinWriter = Nil;
  _stdoutReader = Nil;
  _stderrReader = Nil;

  _args = Nil;

  if (_envp) {
    _envp = Nil;
  }
}

int
Process_PipeReader::available()
{
#if defined(ACDK_OS_WIN32) && !defined(ACDK_OS_CYGWIN32)
  DWORD avail;
  if (PeekNamedPipe((HANDLE)(_get_osfhandle(getFD()->c_fd())), NULL, NULL, NULL, &avail, NULL) == FALSE)
  {
    DOUT("PeekNamedPipe failed: " << System::getLastError());
    return 0;
  }
  return avail;

#elif defined(ACDK_OS_UNIX) || defined(ACDK_OS_CYGWIN32)
  struct stat st;

  if ((fstat(getFD()->c_fd(), &st)) < 0)
  {
    DOUT("Process_PipeReader(): fstat failed");
    return 0;
  }
  return st.st_size;
#else // ACDK_OS_*

#error pipes currently not supported on this platform/OS.
#endif // ACDK_OS_*
}

int
Process_PipeReader::read()
{
  return FileReader::read();
}

RString
Process_PipeReader::readLine()
{
  return FileReader::readLine();
}


int
Process_PipeReader::read(IN(RbyteArray) buffer, int offset, int len)
{
  if (len == -1)
    len = buffer->length() - offset;
  //if (available() >= len)
  return FileReader::read(buffer, offset, len);
  //return -1;
}

int
Process_PipeReader::read(byte* buffer, int offset, int len)
{
  return FileReader::read(buffer, offset, len);
}



void
Process::_run()
{

  RFileDescriptor iFD, oFD, eFD;
  _exitCode = -1;

#if defined(ACDK_OS_WIN32) && !defined(ACDK_OS_CYGWIN32)

  HANDLE inh[2], outh[2], errh[2], tmph;
  int fd;
  bool retval;
  StringBuffer cbuf("");
  ZeroMemory(&_startupInfo, sizeof(_STARTUPINFOW));
  _startupInfo.cb = sizeof(_STARTUPINFOW);
  _startupInfo.lpReserved = NULL;
  _startupInfo.lpTitle = NULL;
  _startupInfo.lpDesktop = NULL;
  _startupInfo.dwFlags = STARTF_USESTDHANDLES;
  _startupInfo.lpReserved2 = NULL;
  _startupInfo.cbReserved2 = 0;

  SECURITY_ATTRIBUTES sa;
  ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
  sa.nLength = sizeof(SECURITY_ATTRIBUTES);
  sa.lpSecurityDescriptor = NULL;
  sa.bInheritHandle = FALSE;


  // Create Stdin
  retval = CreatePipe(&(inh[0]), &(inh[1]), &sa, 0);
  if (retval == 0)
    THROW1(IOException, "CreatePipe");
  retval = DuplicateHandle(GetCurrentProcess(), inh[0], GetCurrentProcess(), &tmph, 0,
                           TRUE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
  if (retval == 0)
    THROW1(IOException, "DuplicatePipe");

  iFD = new FileDescriptor(_open_osfhandle((long)(inh[1]), 0), O_WRONLY);
  _startupInfo.hStdInput = tmph;

  // Create Stdout
  retval = CreatePipe(&(outh[0]), &(outh[1]), &sa, 0);
  if (retval == 0)
    THROW1(IOException, "CreatePipe");
  retval = DuplicateHandle(GetCurrentProcess(), outh[1], GetCurrentProcess(), &tmph, 0,
                                    TRUE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
  if (retval == 0)
    THROW1(IOException, "DuplicatePipe");
  oFD = new FileDescriptor(_open_osfhandle((long)(outh[0]), 0), O_RDONLY);
  _startupInfo.hStdOutput = tmph;

  // Create Stderr
  retval = CreatePipe(&(errh[0]), &(errh[1]), &sa, 0);
  if (retval == 0)
    THROW1(IOException, "CreatePipe");
  retval = DuplicateHandle(GetCurrentProcess(), errh[1], GetCurrentProcess(), &tmph, 0,
                          TRUE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
  if (retval == 0)
    THROW1(IOException, "DuplicatePipe");

  eFD = new FileDescriptor(_open_osfhandle((long)(errh[0]), 0), O_RDONLY);
  _startupInfo.hStdError = tmph;
  /*
  char *wsp, *enviro = 0;
  int i = 0;
  while(_argv[i] != 0)  {
    if ((wsp = strpbrk(const_cast<char *>(_argv[i]), " \t\r\n")) != NULL)
      cbuf.append('"');
    cbuf.append(_argv[i]);
    if (wsp != NULL)
      cbuf.append('"');
    cbuf.append(" ");
    i++;
  }
  */

  // Build up Environmental Block
  UcEnvBlockHolder envblock(_envp);

  const native_char* workdirptr = 0;
  if (_workDir != Nil)
  {
    _workDir = _workDir->convertToNative();
    workdirptr = _workDir->native_c_str();
  }
  /*
  char *cmd = const_cast<char*>(cbuf.c_str());
  int cmdlength = strlen(cmd);
  */
  RString cmdline = getCommandLine();
  cmdline = cmdline->convertToNative();

  native_char* cmdptr = const_cast<native_char*>(cmdline->native_c_str());
  retval = CreateProcessW(NULL, reinterpret_cast<LPWSTR>(cmdptr), NULL, NULL, true, 0/*DETACHED_PROCESS*/ | CREATE_UNICODE_ENVIRONMENT , envblock.block, 
						  (LPCWSTR)workdirptr, &_startupInfo, &_processInfo);
  if (retval == 0)
  {
    THROW1(IOException, RString("Cannot create process: ") + getCommandLine());
  }
  CloseHandle(_startupInfo.hStdOutput);
  CloseHandle(_startupInfo.hStdError);

#elif defined(ACDK_OS_UNIX) || defined(ACDK_OS_CYGWIN32)

  int n, cnt;
  ArgumentHolder tempargs(_args);
  ArgumentHolder tempenvp(_envp);
  char** _argv = tempargs.getArgv();
  char** _env = (tempenvp.getArgc() > 0)? tempenvp.getArgv() : 0;

  int infd[2], outfd[2], errfd[2], tmpfd;
  int fakefd[2];
  int retval;
  RString msg;
  RStringBuffer path = new StringBuffer;
  if (_args[0]->indexOf('/') == -1) {
    ::acdk::util::RStringTokenizer st = new ::acdk::util::StringTokenizer(System::getProperty("PATH"), RString(":"));
    RString pe;
    ::acdk::io::RFile tf;
    while (st->hasNext() == true)
    {
      if ((pe = st->nextToken()) == Nil)
      {
        // shouldn't happen after hasNext()
        RString msg = RString("error while parsing \"") + System::getProperty("PATH") + "\"";
        THROW1(IOException, msg);
      }
      path->set(pe);
      if ((pe->length() > 0) && (pe->charAt(pe->length() - 1) != '/'))
        path->append("/");
      path->append(_args[0]);
      tf = new ::acdk::io::File(path->toString());
      if (tf->exists())
        break;
      tf = Nil;
      pe = Nil;
    }
    tf = Nil;
    st = Nil;
    if (pe == Nil) {
      path->set(_args[0]);
    }
    pe = Nil;
  } else {
    path->set(_args[0]);
  }

  retval = pipe(infd);
  if (retval < 0)
    THROW1(IOException, "pipe fd0");
  retval = pipe(outfd);
  if (retval < 0)
    THROW1(IOException, "pipe fd1");
  retval = pipe(errfd);
  if (retval < 0)
    THROW1(IOException, "pipe fd2");
  retval = pipe(fakefd);
  if (retval < 0)
    THROW1(IOException, "pipe fd3");


  // WARNING: we should never throw an exception from client or the results are unexpectable
  switch (_processId = fork()) {
    case -1:  // fork failed.
      THROW1(IOException, "create new process failed");
      break;
    case 0: // am child
      ::close(infd[1]);
      ::close(outfd[0]);
      ::close(errfd[0]);
      ::close(fakefd[0]);
      for (int n = 0; n < 32; n++)
        signal(n, SIG_DFL);
      if ((::dup2(infd[0], 0)) != 0)
      {
        msg = RString("dup2 of in-pipe failed: errno=") + Integer::toString(errno) + "\n";
        write(errfd[1], msg->c_str(), msg->length());
        break;
      }
      if ((::dup2(outfd[1], 1)) != 1) {
        msg = RString("dup2 of out-pipe failed: errno=") + Integer::toString(errno) + "\n";
        write(errfd[1], msg->c_str(), msg->length());
        break;
      }
      if ((::dup2(errfd[1], 2)) != 2) {
        msg = RString("dup2 of err-pipe failed: errno=") + Integer::toString(errno) + "\n";
        write(errfd[1], msg->c_str(), msg->length());
        break;
      }
      ::close(infd[0]);
      ::close(outfd[1]);
      ::close(errfd[1]);
      if ((::fcntl(fakefd[1], F_SETFD, FD_CLOEXEC) < 0) || (::fcntl(fakefd[1], F_GETFD) != FD_CLOEXEC))
        THROW1(IOException, "closeonexec failed.");


       if (_workDir != Nil)
          chdir(_workDir->c_str());

        // execv() is declared as execv(const char *, char * const *), so we need const_cast<>()
        // note: exec only returns, if it fails.
       /*
       sys::coreout << "EXEC: " << path->c_str() << sys::eofl;
       for (const char** ptr = (const char**)_argv; *ptr != 0; ++ptr)
          sys::coreout << "arg: " <<  *ptr << sys::eofl;
       */
       if (_env)
      {
        //sys::coreout << "execve" << sys::eofl;
        execve(path->c_str(), const_cast<char * const *>(_argv), const_cast<char * const *>(_env));
      } else {
        /*
        for (const char** ptr = (const char**)environ; *ptr != 0; ++ptr)
          sys::coreout << "env: " <<  *ptr << sys::eofl;
        for (const char** ptr = (const char**)_argv; *ptr != 0; ++ptr)
          sys::coreout << "arg: " <<  *ptr << sys::eofl;
        */
        execv(path->c_str(), const_cast<char * const *>(_argv));
      }
      ::close(fakefd[1]);
      ::fcntl(2, F_SETFL, O_NONBLOCK);
      msg = RString("execv of \"") + path->toString() + "\" failed: errno=" + Integer::toString(errno) + "\n";
      ::write(2, msg->c_str(), msg->length());
      break;
    default:  // am parent
      DOUT("start process pid: " << _processId);
      ::close(infd[0]);
      ::close(outfd[1]);
      ::close(errfd[1]);
      ::close(fakefd[1]);
      iFD = new FileDescriptor(infd[1], O_WRONLY);
      oFD = new FileDescriptor(outfd[0], O_RDONLY);
      eFD = new FileDescriptor(errfd[0], O_RDONLY);
      break;
  }
  if (_processId == 0) { // only meaningful for failed client, which should not reach this point.
    ::close(infd[0]);
    ::close(outfd[0]);
    ::close(errfd[0]);
    ::close(fakefd[0]);
    ::close(infd[1]);
    ::close(outfd[1]);
    ::close(errfd[1]);
    ::close(fakefd[1]);
    exit(127);
  }
  try {
    int loop;
    int dummy;
    ::read(fakefd[0], &dummy, 1);	// simply wait for close of other end.
    ::close(fakefd[0]);
#if !defined(ACDK_OS_BSD)
    for (loop = 0; loop < 20; loop++) 
    {
        // wait a moment, so the child may exit after failed exec().
      Thread::sleep(250);
      if ((isRunning() == true) || (_exitCode != -1))
        break;
      DOUT("process is not startup. sleep a while");
    }
    if ((loop >= 20) || (_exitCode == 127)) 
    {
      THROW1(IOException, SBSTR("Process  didn't started up; exitCode: " << _exitCode));
    }
#endif 
  } catch (RRuntimeException ex) {
    // waitpid failed somehow...
    THROW1(IOException, "something awful happened while creating new Process.");
  }
#else // ACDK_OS_*
#error currently no process creation available for this platform/OS
#endif
/FONT>
  _status = ProcessStillRunning;
  _stdinWriter  = new FileWriter(iFD, true);
  _stdoutReader = new Process_PipeReader(oFD, true);
  _stderrReader = new Process_PipeReader(eFD, true);
  ::close(iFD->c_fd());
  ::close(oFD->c_fd());
  ::close(eFD->c_fd());
}

//virtual
RReader
Process::getErrorStream()
{
  return (RReader)_stderrReader;
}


//virtual
RWriter
Process::getInputStream()
{
  return (RWriter)_stdinWriter;
}

//virtual
RReader
Process::getOutputStream()
{
  return (RReader)_stdoutReader;
}


//virtual
int
Process::waitFor(int timeOutMs)
{
  if (_exitCode != -1)
    return _exitCode;

#if defined(ACDK_OS_WIN32) && !defined(ACDK_OS_CYGWIN32)
  int ret;
  do {
    if (timeOutMs == -1)
      timeOutMs = INFINITE;
    ret = WaitForSingleObject(_processInfo.hProcess, timeOutMs);
    if (WAIT_OBJECT_0 == ret) 
    {
      DWORD ex;
      if ((GetExitCodeProcess(_processInfo.hProcess, &ex)) == 0)
        THROW1(IllegalThreadStateException, "GetExitCodeProcess Failed");
      if (ex== STILL_ACTIVE)
        continue;
      _exitCode = ex;
      _status = ProcessFinished;
      return _exitCode;
    }
    else if (WAIT_TIMEOUT == ret)
    {
      return ProcessStillRunning;
    }
  } while(ret == WAIT_OBJECT_0);
  THROW1(IOException, RString("WaitForSingleObject failed with ") + ret);
#elif defined(ACDK_OS_UNIX) || defined(ACDK_OS_CYGWIN32)
  int status;
  pid_t pid;
  DOUT("waitpid1 ...");
  jlong start = acdk::util::SysDate().getTime();
  jlong now = start;
  //sys::tick_t start = sys::core_tick::now();
  bool finished = false;
  int waitPidOptions = 0;
  if (timeOutMs != -1)
    waitPidOptions = WNOHANG;

  do {
    if ((pid = waitpid(_processId, &status, waitPidOptions)) != _processId && timeOutMs == -1)
    {
      RString msg = SBSTR("waitpid failed, got " << (int)pid << " instead of "  << (int)_processId);
      if (pid < 0)
        msg = msg + " errno is " + Integer::toString(errno);
      DOUT(msg);
      THROW1(IllegalThreadStateException, msg);
    }
    DOUT("waitpid status: " << status << "; _processId: " << (int)_processId << "; pid returned: " << (int)pid);
    if (timeOutMs != -1)
    {
#if defined(ACDK_OS_SOLARIS)
      if (pid == _processId && WIFEXITED(status) != 0)
#else
/FONT>
      if (WIFEXITED(status) != 0)
#endif
/FONT>
      {
        finished = true;
        break;
      }
      DOUT("Process::waitFor sleep 100");
      Thread::sleep(100);
    }
    now = acdk::util::SysDate().getTime();
    //int ms = sys::core_tick::millisecsDiff(start, sys::core_tick::now());
    /*
      DOUT("clock: " << (jlong)sys::core_tick::now() << "; sysTime: " << acdk::util::SysDate().getTime() 
         << "; Timeout: " << timeOutMs << "; now: " << ms << "; clockpermillisec: " 
         << int(CLOCKS_PER_MILLISECONDS) << "; clock per sec: " << int(CLOCKS_PER_SEC));
    */
  } while (timeOutMs != -1 && ((now - start) < timeOutMs));

  DOUT("waitpid returned: " << status);
#if defined(ACDK_OS_SOLARIS)
  if (pid == _processId && WIFEXITED(status) != 0)
#else
/FONT>
  if (WIFEXITED(status) != 0)
#endif
/FONT>
  {
    _exitCode = WEXITSTATUS(status);
    _status = ProcessFinished;
  }
  else if (WIFSIGNALED(status) && finished == true)
  {
    _exitCode = 128 | WTERMSIG(status);  // this is typical for shells
    _status = ProcessFinished;
  }
  else
    return ProcessStillRunning;

#else // ACDK_OS_*
#error currently no process creation available for this platform/OS
#endif
/FONT>
  return _exitCode;
}

bool
Process::isRunning()
{
#if defined(ACDK_OS_WIN32) && !defined(ACDK_OS_CYGWIN32)
  DWORD ex;
  GetExitCodeProcess(_processInfo.hProcess, &ex);
  if (ex == STILL_ACTIVE)
    return true;
#elif defined(ACDK_OS_UNIX) || defined(ACDK_OS_CYGWIN32)
  int killret;
  if ((killret = kill(_processId, 0)) == 0) 
  {
    int status = waitpid(_processId, &status, WNOHANG);
    DOUT("waitpid(" << _processId << ") returned: " << status);
    switch (status) 
    {
      case -1:
        THROW1(RuntimeException, "waitpid failed");
        break;
      case 0:  // isn't a zombie...
        break;
      default:  // was a zombie, save it's exit-status
        if (WIFEXITED(status))
          _exitCode = WEXITSTATUS(status);
        else if (WIFSIGNALED(status))
          _exitCode = 128 | WTERMSIG(status);  // this is typical for shells
        else
          _exitCode = 128;
/* maybe these will be implemented some day */
#if 0
        if (WIFSIGNALED(status))
          _termSig = WTERMSIG(status);
        if (WIFSTOPPED(status))
          _stopSig = WSTOPSIG(status);
#endif
/FONT>
        return false;
    }
    return true;
  }
  else
  {
    DOUT("Process::isRunning() kill returned: " << strerror(errno));
  }
#else // ACDK_OS_*
#error currently no process creation available for this platform/OS
#endif
/FONT>
  return false;
}


//static
int
Process::getProcessId()
{
#if defined(ACDK_OS_WIN32) && !defined(ACDK_OS_CYGWIN32)
  return (int)GetCurrentProcessId();
#elif defined(ACDK_OS_UNIX) || defined(ACDK_OS_CYGWIN32)
  return (int)getpid();
#else // ACDK_OS_*
#error currently no process creation available for this platform/OS
#endif
/FONT>
}

#if defined(ACDK_OS_WIN32) && !defined(ACDK_OS_CYGWIN32)
HINSTANCE
getKernel32()
{
  static HINSTANCE kernel32 = NULL;
  if (kernel32 != NULL)
    return kernel32;
  kernel32 = LoadLibraryA("kernel32.dll");
  return kernel32;
    
}
typedef WINBASEAPI HANDLE WINAPI OpenThreadFunc(DWORD dwDesiredAccess, BOOL bInheridedHandle, DWORD dwThreadID);
HANDLE openThread(DWORD dwDesiredAccess, BOOL bInheridedHandle, DWORD dwThreadID)
{
  HINSTANCE kernel32 = getKernel32();
    if (kernel32 == NULL)
      return NULL;

  static FARPROC openthread = NULL;
  if (openthread == NULL)
    openthread = GetProcAddress(kernel32, "OpenThread");
    if (openthread == NULL)
      return NULL;
  return ((OpenThreadFunc*)(void*)openthread)(dwDesiredAccess, bInheridedHandle, dwThreadID);
}
typedef WINBASEAPI BOOL WINAPI Thread32FirstFunc(HANDLE hSnapshot, LPTHREADENTRY32 lpte);
BOOL thread32First(HANDLE hSnapshot, LPTHREADENTRY32 lpte)
{
  HINSTANCE kernel32 = getKernel32();
  if (kernel32 == NULL)
    return NULL;
  static FARPROC thread32First = NULL;
  if (thread32First == NULL)
    thread32First = GetProcAddress(kernel32, "Thread32First");
  if (thread32First == NULL)
    return NULL;
  return ((Thread32FirstFunc*)(void*)thread32First)(hSnapshot, lpte);
  
}


BOOL thread32Next(HANDLE hSnapshot, LPTHREADENTRY32 lpte)
{
  HINSTANCE kernel32 = getKernel32();
  if (kernel32 == NULL)
    return NULL;
  static FARPROC thread32Next = NULL;
  if (thread32Next == NULL)
    thread32Next = GetProcAddress(kernel32, "Thread32Next");
  if (thread32Next == NULL)
    return NULL;
  return ((Thread32FirstFunc*)(void*)thread32Next)(hSnapshot, lpte);
  
}

typedef WINBASEAPI HANDLE WINAPI CreateToolhelp32SnapshotFunc(DWORD dwFlags, DWORD th32ProcessID);
HANDLE createToolhelp32Snapshot(DWORD dwFlags, DWORD th32ProcessID)
{
  HINSTANCE kernel32 = getKernel32();
  if (kernel32 == NULL)
    return NULL;
  static FARPROC createToolhelp32Snapshot = NULL;
  if (createToolhelp32Snapshot == NULL)
    createToolhelp32Snapshot = GetProcAddress(kernel32, "CreateToolhelp32Snapshot"); 
  if (createToolhelp32Snapshot == NULL)
    return NULL;
  return ((CreateToolhelp32SnapshotFunc*)(void*)createToolhelp32Snapshot)(dwFlags, th32ProcessID);
}

bool suspendResumeWin32Process(DWORD pid, bool resume)
{
  HANDLE hThreadSnap = createToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); 
  if (hThreadSnap == INVALID_HANDLE_VALUE) 
    return false; 
  THREADENTRY32 te32 = { 0 }; 
  te32.dwSize = sizeof(THREADENTRY32); 

  if (thread32First(hThreadSnap, &te32) == FALSE) 
    return false;
  
  do { 
    if (te32.th32OwnerProcessID == pid) 
    {
      HANDLE hThread = openThread(THREAD_SUSPEND_RESUME, FALSE, te32.th32ThreadID);
			if (resume == true)
		    ResumeThread(hThread);
      else
			  SuspendThread(hThread);
		  CloseHandle(hThread);
    } 
  } while (thread32Next(hThreadSnap, &te32) == TRUE); 
  CloseHandle (hThreadSnap); 
  return true; 
}
#endif //defined(ACDK_OS_WIN32) && !defined(ACDK_OS_CYGWIN32)


void 
Process::suspend()
{
#if defined(ACDK_OS_WIN32) && !defined(ACDK_OS_CYGWIN32)
  suspendResumeWin32Process(_processInfo.dwProcessId, false);
#else
/FONT>
  int ret = kill(_processId, SIGSTOP);
#endif
/FONT>
}

void 
Process::resume()
{
#if defined(ACDK_OS_WIN32) && !defined(ACDK_OS_CYGWIN32)
  suspendResumeWin32Process(_processInfo.dwProcessId, true);
#else
/FONT>
  int ret = kill(_processId, SIGCONT);
#endif
/FONT>
   
}

} // lang
} // acdk