]> git.kernelconcepts.de Git - karo-tx-redboot.git/blobdiff - tools/src/tools/Utils/common/Subprocess.cpp
Initial revision
[karo-tx-redboot.git] / tools / src / tools / Utils / common / Subprocess.cpp
diff --git a/tools/src/tools/Utils/common/Subprocess.cpp b/tools/src/tools/Utils/common/Subprocess.cpp
new file mode 100644 (file)
index 0000000..d8c94b2
--- /dev/null
@@ -0,0 +1,877 @@
+//####COPYRIGHTBEGIN####
+//                                                                          
+// ----------------------------------------------------------------------------
+// Copyright (C) 1998, 1999, 2000 Red Hat, Inc.
+//
+// This program is part of the eCos host tools.
+//
+// This program is free software; you can redistribute it and/or modify it 
+// under the terms of the GNU General Public License as published by the Free 
+// Software Foundation; either version 2 of the License, or (at your option) 
+// any later version.
+// 
+// This program 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 General Public License for 
+// more details.
+// 
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 
+// 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+//
+// ----------------------------------------------------------------------------
+//                                                                          
+//####COPYRIGHTEND####
+//===========================================================================
+//===========================================================================
+//#####DESCRIPTIONBEGIN####
+//
+// Author(s):  sdf
+// Contact(s): sdf
+// Date:               1998/08/11
+// Version:            0.01
+// Purpose:    
+// Description:        This is the implementation of the class which allows for spawning subprocesses
+//
+// Requires:   
+// Provides:   
+// See also:    
+// Known bugs: 
+// Usage:      
+//
+//####DESCRIPTIONEND####
+//
+//===========================================================================
+#include "eCosTrace.h"
+#include "Subprocess.h"
+#ifdef _WIN32
+  #include <tlhelp32.h>
+
+  HINSTANCE CSubprocess::hInstLib1 = VER_PLATFORM_WIN32_NT==CSubprocess::GetPlatform()?LoadLibrary(_T("PSAPI.DLL")):LoadLibrary(_T("Kernel32.DLL")) ;
+  HINSTANCE CSubprocess::hInstLib2 = VER_PLATFORM_WIN32_NT==CSubprocess::GetPlatform()?LoadLibrary(_T("NTDLL.DLL")):NULL;
+
+#endif
+
+//#define CloseHandle(x) TRACE(_T("CSubprocess::CloseHandle %x\n"),x);::CloseHandle(x)
+
+const unsigned int CSubprocess::PROCESS_KILL_EXIT_CODE=0xCCFFCCFF;
+
+CSubprocess::CSubprocess(bool bAutoDelete):
+  m_pfnContinue(DefaultContinuationFunc),
+  m_pContinuationFuncParam(0),
+  m_bAutoDelete(bAutoDelete),
+  m_bThreadTerminated(true),
+  m_bVerbose(false),
+  m_nExitCode(-1),
+  m_idProcess(0),
+  m_pLogparam(0),
+  m_pfnLogfunc(0),
+  m_bKillThread(false)
+{
+#ifdef _WIN32
+  m_hProcess=0;
+#endif
+}
+
+CSubprocess::~CSubprocess()
+{
+  Kill();
+  if(!CeCosThreadUtils::WaitFor(m_bThreadTerminated,1000)){
+    m_bKillThread=true;
+    CeCosThreadUtils::WaitFor(m_bThreadTerminated);  
+  }
+#ifdef _WIN32
+  if(m_hProcess){
+    CloseHandle(m_hProcess);
+  }
+#endif
+}
+
+bool CSubprocess::Run(LogFunc *pfnLog,void * pLogparam, LPCTSTR pszCmd,bool bBlock/*=true*/)
+{
+  bool rc;
+  if(!m_bThreadTerminated){
+    rc=false;
+  } else {
+    m_pfnLogfunc=pfnLog;
+    m_pLogparam=pLogparam;
+#ifdef _WIN32 
+    // UNIX does it from the thread func.  WIN32 could too, but it's nice to know at the time 
+    // of calling run whether the process is successfully created.
+    if(m_hProcess){
+      // Normally done in the dtor
+      CloseHandle(m_hProcess);
+    }
+    rc=CreateProcess(pszCmd);
+#else 
+    m_strCmd=pszCmd;
+    rc=true;
+#endif
+    if(rc){
+      m_bKillThread=false;
+      if(bBlock){
+        // When using RunThread, the manipulation of this Boolean is taken care of.
+        // Here we must do it ourselves.
+        m_bThreadTerminated=false;
+        ThreadFunc();
+        m_bThreadTerminated=true;
+      } else {
+        CeCosThreadUtils::RunThread(SThreadFunc,this,&m_bThreadTerminated,String::SFormat(_T("subprocess %d read"),m_idProcess));
+      }
+    }
+  }
+  return rc;
+}
+
+#ifdef _WIN32
+bool CSubprocess::CreateProcess(LPCTSTR pszCmdline)
+{
+  
+  STARTUPINFO   si;                    // For CreateProcess call
+  HANDLE        hrPipe,hwPipe,hwPipe2,m_hrPipeTemp,m_hwPipeTemp;
+  // Create the anonymous pipe
+  
+  SECURITY_ATTRIBUTES saPipe;          // Security for anonymous pipe
+  saPipe.nLength = sizeof(SECURITY_ATTRIBUTES);
+  saPipe.lpSecurityDescriptor = NULL;
+  saPipe.bInheritHandle = true;
+  
+  ::CreatePipe(&m_hrPipeTemp,&hwPipe,&saPipe,10240);
+  
+  // In most cases you can get away with using the same anonymous
+  // pipe write handle for both the child's standard output and
+  // standard error, but this may cause problems if the child app
+  // explicitly closes one of its standard output or error handles. If
+  // that happens, the anonymous pipe will close, since the child's
+  // standard output and error handles are really the same handle. The
+  // child won't be able to write to the other write handle since the
+  // pipe is now gone, and parent reads from the pipe will return
+  // ERROR_BROKEN_PIPE and child output will be lost. To solve this
+  // problem, simply duplicate the write end of the pipe to create
+  // another distinct, separate handle to the write end of the pipe.
+  // One pipe write handle will serve as standard out, the other as
+  // standard error. Now *both* write handles must be closed before the
+  // write end of the pipe actually closes.
+  
+  ::DuplicateHandle(::GetCurrentProcess(),                     // Source process
+    hwPipe,                    // Handle to duplicate
+    ::GetCurrentProcess(),   // Destination process
+    &hwPipe2,              // New handle, used as stderr by child 
+    0,                     // New access flags - ignored since DUPLICATE_SAME_ACCESS
+    true,                  // It's inheritable
+    DUPLICATE_SAME_ACCESS);
+  
+  ::CreatePipe(&hrPipe,&m_hwPipeTemp,&saPipe,10240);
+  
+
+  // Create new output read handle and the input write handles, setting
+  // the Properties to FALSE. Otherwise, the child inherits the
+  // properties and, as a result, non-closeable handles to the pipes
+  // are created.
+  DuplicateHandle(GetCurrentProcess(),m_hrPipeTemp,
+                       GetCurrentProcess(),
+                       &m_hrPipe, // Address of new handle.
+                       0,FALSE, // Make it uninheritable.
+                       DUPLICATE_SAME_ACCESS);
+
+  DuplicateHandle(GetCurrentProcess(),m_hwPipeTemp,
+                       GetCurrentProcess(),
+                       &m_hwPipe, // Address of new handle.
+                       0,FALSE, // Make it uninheritable.
+                       DUPLICATE_SAME_ACCESS);
+
+  // Close inheritable copies of the handles we do not want to be inherited:
+  CloseHandle(m_hrPipeTemp);
+  CloseHandle(m_hwPipeTemp);
+
+
+  memset(&si, 0, sizeof(si));
+  si.cb = sizeof(si);
+  
+  si.hStdOutput = hwPipe;
+  si.hStdError =  hwPipe2;
+  si.hStdInput =  hrPipe;
+  si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
+  si.wShowWindow = SW_SHOW;
+  
+  LPCTSTR pszDir;
+  if(m_strDir.empty()){
+    pszDir=NULL; // current directory
+  } else {
+    pszDir=m_strDir;
+  }
+  
+  PROCESS_INFORMATION pi;
+  String strCmd(pszCmdline);
+
+  String strOrigpath;
+  if(!m_strPath.empty()){
+         int nSize=GetEnvironmentVariable(_T("PATH"), NULL, 0);
+         if(nSize>0){
+      GetEnvironmentVariable(_T("PATH"),strOrigpath.GetBuffer(nSize),nSize);
+      strOrigpath.ReleaseBuffer();
+      SetEnvironmentVariable(_T("PATH"),m_strPath);
+    }
+  }
+
+  bool rc=(TRUE==::CreateProcess(NULL,strCmd.GetBuffer(),NULL,NULL,true,DETACHED_PROCESS|CREATE_NEW_PROCESS_GROUP,NULL,pszDir,&si,&pi));
+
+  if(!m_strPath.empty()){
+    SetEnvironmentVariable(_T("PATH"),strOrigpath);
+  }
+
+  m_nErr=GetLastError();
+
+  strCmd.ReleaseBuffer();
+  
+  if(rc){
+    m_idProcess=pi.dwProcessId;
+    m_hProcess=pi.hProcess;
+    if(m_bVerbose){
+      Output(String::SFormat(_T("*** Process %d created \"%s\"\n"),m_idProcess,pszCmdline));
+    }
+    TRACE(String::SFormat(_T("Process %d created \"%s\"\n"),m_idProcess,pszCmdline));
+    m_nExitCode=STILL_ACTIVE;
+    CloseHandle(pi.hThread);
+  } else {
+    m_idProcess=0;
+    if(m_bVerbose){
+      Output(String::SFormat(_T("*** Failed to create process \"%s\" %s\n"),pszCmdline,(LPCTSTR)ErrorString()));
+    }
+    TRACE(String::SFormat(_T("Failed to create process \"%s\" %s\n"),pszCmdline,(LPCTSTR)ErrorString()));
+    m_nExitCode=GetLastError();
+    CloseHandle(m_hrPipe);m_hrPipe=INVALID_HANDLE_VALUE;
+    CloseHandle(m_hwPipe);m_hwPipe=INVALID_HANDLE_VALUE;
+  }
+  
+  CloseHandle(hrPipe);
+  CloseHandle(hwPipe);
+  CloseHandle(hwPipe2);
+  
+  return rc;
+  
+}
+
+void CSubprocess::ThreadFunc()
+{
+
+  TRACE(_T("Reading from process %d\n"),m_idProcess);
+  
+  DWORD dwAvail;
+  
+  DWORD dwExitCode;
+
+  while (!m_bKillThread && m_pfnContinue(m_pContinuationFuncParam) && ::PeekNamedPipe(m_hrPipe, NULL, 0, 0, &dwAvail, NULL)){
+//TRACE(_T("P%d\n"),dwAvail);
+    if(dwAvail){
+      dwAvail=MIN(dwAvail,80); // Read a maximum of 80 characters at a time
+      DWORD dwRead;
+      char *buf=new char[dwAvail+1];
+//TRACE(_T("R%d\n"),dwAvail);
+      if(!::ReadFile(m_hrPipe, buf, dwAvail, &dwRead, NULL)){
+        TRACE(_T("ReadFile returns false\n"));
+        delete [] buf;
+        break;
+      }
+      buf[dwRead]='\0';
+      Output(String::CStrToUnicodeStr(buf));
+      delete [] buf;
+    }
+    else if (!ProcessAlive())
+    {
+        TRACE(_T("m_bThreadTerminated=%d\n"),m_bThreadTerminated);
+        break;
+    }
+    // Fix for hanging in an endless loop under Windows ME, by Bill Diehls <billabloke@yahoo.com>
+    else if (::GetExitCodeProcess(m_hProcess, &dwExitCode) && dwExitCode!=STILL_ACTIVE) 
+    {
+               break;
+    }
+    else
+    {
+      CeCosThreadUtils::Sleep(250);
+    }
+  }
+
+  ::GetExitCodeProcess(m_hProcess, &dwExitCode);
+  m_nExitCode=dwExitCode;
+  
+#ifdef _DEBUG
+  String str;
+  switch(dwExitCode){
+    case STILL_ACTIVE:
+      str=_T("still alive");
+      if(m_bKillThread){
+        str+=_T(" - requested to stop reading");
+      }
+      break;
+    case PROCESS_KILL_EXIT_CODE:
+      str=_T("killed");
+      break;
+    default:
+      str.Format(_T("terminated rc=%d"),dwExitCode);
+      break;
+  }
+  TRACE(_T("Finished reading from process %d (%s)\n"),m_idProcess,(LPCTSTR)str);
+#endif
+
+  CloseHandle(m_hrPipe);m_hrPipe=INVALID_HANDLE_VALUE;
+  CloseHandle(m_hwPipe);m_hwPipe=INVALID_HANDLE_VALUE;
+  
+  if(m_bAutoDelete){
+    m_bThreadTerminated=true; // or else the dtor will block
+    delete this;
+  }
+}
+
+#else // UNIX
+
+bool CSubprocess::CreateProcess(LPCTSTR pszCmdline)
+{
+  m_idProcess=0;
+  int fdchild=-1; // the file descriptor for the child (slave) half of the pseudo-tty pair
+
+  // Get a free /dev/ptyp0 (master) and /dev/ttyp0 (slave) tty pair
+  String strMasterTty,strChildTty;
+  for(unsigned int c=0;c<64;c++){
+    strMasterTty.Format("/dev/pty%c%x",'p'+c/16,c%16);
+    
+    m_tty=open(strMasterTty, O_RDWR | O_NOCTTY);
+    if (-1!=m_tty) { 
+      strChildTty.Format("/dev/tty%c%x",'p'+c/16,c%16);        
+      
+      fdchild = open(strChildTty, O_RDWR);
+      if (-1==fdchild) {
+        close(m_tty);
+        m_tty=fdchild=-1;
+      } else {
+        VTRACE("opened %s - fd=%d\n",(LPCTSTR)strMasterTty,m_tty);
+        break;
+      }
+    }
+  }
+
+  if(-1==m_tty){
+    ERROR(_T("Failed to get a pty\n"));
+    return false;
+  }  
+  
+  TRACE(_T("Master pty %s (fd %d) slave pty %s (fd %d)\n"),(LPCTSTR)strMasterTty,m_tty,(LPCTSTR)strChildTty,fdchild);
+
+  m_idProcess=fork();
+  
+  switch (m_idProcess) {
+    // Fork failed
+    case -1:
+      TRACE(_T("Failed to create process - %s\n"),strerror(errno));
+      m_idProcess=0;
+      break;
+    case 0:
+      // Process is created (we're the child)
+      {
+        // Close all descriptors except the slave side of the pseudo-terminal
+        for (int fd = 0; fd < (int) sysconf(_SC_OPEN_MAX); fd++) {
+          if(fd!=fdchild){
+            close(fd);
+          }
+        }
+        setsid();
+        
+        dup2(fdchild, 0);
+        dup2(fdchild, 1);
+        dup2(fdchild, 2);
+        
+        close(fdchild);
+
+        if(!m_strDir.empty()){
+          if(0!=chdir(m_strDir)){
+            if(m_bVerbose){
+              fprintf(stderr,_T("*** Failed to change directory to %s\n"),(LPCTSTR)m_strDir);
+            }
+            exit (5);
+          }
+        }
+        if(m_bVerbose){
+          fprintf(stderr,_T("*** Process %d created \"%s\"\n"),m_idProcess,pszCmdline);
+        }
+
+        StringArray ar;
+        int argc=String(pszCmdline).Chop(ar,_TCHAR(' '),true);
+        TCHAR **argv=new TCHAR *[1+argc];
+        for(int i=0;i<argc;i++){
+          argv[i]=new TCHAR[1+strlen(ar[i])];  
+          strcpy(argv[i],ar[i]);
+        }
+        argv[argc]=0;
+        if(!m_strPath.empty()){
+          _tputenv(String::SFormat(_T("PATH=%s"),(LPCTSTR)m_strPath));
+        }
+        _texecvp(argv[0], argv);  
+      }
+
+      fprintf(stderr,"exec error - %s\n",strerror(errno));
+      exit(4);
+
+    default:
+      // Process is created (we're the parent)
+      TRACE(_T("Closing fd %d\n"),fdchild);
+      close(fdchild);
+      TRACE(_T("Forked to create process %s - process id <%d>\n"), pszCmdline, m_idProcess);
+      break;
+  }
+  return 0!=m_idProcess;
+}
+
+void CSubprocess::ThreadFunc()
+{
+  if(!CreateProcess(m_strCmd)){
+    ERROR(_T("Failed to create process for %s\n"),(LPCTSTR)m_strCmd);
+  } else {  
+    fcntl(m_tty,F_SETFL,O_NONBLOCK);
+    int rc;
+    do {
+      TCHAR buf[4096];
+      rc=read(m_tty, buf, sizeof(buf)-1);
+      if(rc>=0){
+        buf[rc]='\0';
+      }
+      switch(rc){
+        case -1:
+          if(EAGAIN==errno){
+            CeCosThreadUtils::Sleep(250);
+          } else {
+              goto Done;
+          }
+          break;  
+        case 0:
+          goto Done;
+          continue;
+        default:
+          buf[rc]=_TCHAR('\0');
+          Output(String::CStrToUnicodeStr(buf));
+          continue;
+      }
+    } while(!m_bKillThread && m_pfnContinue(m_pContinuationFuncParam));
+Done:
+    TRACE(_T("Closing fd %d\n"),m_tty);
+    close (m_tty);
+  
+    switch(waitpid(m_idProcess,&m_nExitCode,WNOHANG));
+  }
+  
+  if(m_bAutoDelete){
+    delete this;
+  }
+}
+#endif
+
+void CSubprocess::Output (LPCTSTR psz)
+{
+  m_pfnLogfunc(m_pLogparam,psz);
+}
+
+void CSubprocess::Send(LPCTSTR str)
+{
+  char *psz=String(str).GetCString();
+  int nToWrite=strlen(psz);
+  const char *c=psz;
+  do {
+#ifdef _WIN32
+    DWORD dwWritten;
+    if(!::WriteFile(m_hwPipe,psz,nToWrite,&dwWritten,0)){
+      break;
+    }
+#else
+    int dwWritten = write(m_tty, c, nToWrite);
+    if(-1==dwWritten){
+      break;
+    } 
+#endif
+    nToWrite-=(int)dwWritten;
+    c+=(int)dwWritten;
+  } while (nToWrite>0);
+  //::FlushFileBuffers(m_hwPipe);
+  delete [] psz;
+}
+
+bool CSubprocess::Kill(bool bRecurse)
+{
+TRACE(_T("CSubprocess::Kill pid %d recurse=%d\n"),m_idProcess,bRecurse);
+  PInfoArray arPinfo;
+  bool rc=false;
+  if(m_idProcess && -1!=m_idProcess){
+    // Start of with the easy one:
+    if(bRecurse) {
+      // Need to gather this information before we orphan our grandchildren:
+      PSExtract(arPinfo);
+    }
+    
+#ifdef _WIN32
+
+    if(m_hProcess){
+      TRACE(_T("Terminate process %s\n"),(LPCTSTR)Name(m_idProcess));
+      rc=(TRUE==::TerminateProcess(m_hProcess,PROCESS_KILL_EXIT_CODE));
+      // dtor's (or subsequent Run's) responsibility to close the handle
+    }
+
+#else
+    rc=(0==kill(m_idProcess,SIGTERM));
+    int status;
+    waitpid(m_idProcess,&status,WNOHANG);
+#endif
+    
+    if(bRecurse) {
+      // kill process *and* its children
+      // FIXME: needs to be top-down
+      for(int i=0;i<(signed)arPinfo.size();i++){
+        if(arPinfo[i].IsChildOf(m_idProcess)){
+      
+#ifdef _WIN32
+          // begin hack
+          const String strName(Name(arPinfo[i].PID));
+          if(_tcsstr(strName,_T("eCosTest")) || _tcsstr(strName,_T("cmd.EXE")) || _tcsstr(strName,_T("CMD.EXE")) || arPinfo[i].PID==(signed)GetCurrentProcessId()){
+            continue;
+          }
+          // end hack
+          HANDLE hProcess=::OpenProcess(PROCESS_TERMINATE,false,arPinfo[i].PID);
+          if(hProcess){
+            TRACE(_T("Terminate process %s\n"),(LPCTSTR)Name(arPinfo[i].PID));
+            rc&=(TRUE==::TerminateProcess(hProcess,PROCESS_KILL_EXIT_CODE));
+            CloseHandle(hProcess);
+          } else {
+            rc=false;
+          }
+#else
+          rc&=(0==kill(arPinfo[i].PID,SIGTERM));
+          int status;
+          waitpid(arPinfo[i].PID,&status,WNOHANG);
+#endif
+        }
+      }
+    }
+  }
+  return rc;
+}
+
+Time CSubprocess::CpuTime(bool bRecurse) const
+{
+  Time t=0;
+  // kill process *and* its children
+  // FIXME: needs to be top-down
+  
+#ifdef _WIN32
+  __int64 ftCreation,ftExit,ftKernel,ftUser;
+  if(m_hProcess && ::GetProcessTimes (m_hProcess,(FILETIME *)&ftCreation,(FILETIME *)&ftExit,(FILETIME *)&ftKernel,(FILETIME *)&ftUser)){
+    t+=Time((ftKernel+ftUser)/10000);
+  }
+
+  if(bRecurse){
+    PInfoArray arPinfo;
+    PSExtract(arPinfo);
+    if(m_idProcess && -1!=m_idProcess){
+      for(int i=0;i<(signed)arPinfo.size();i++){
+        if(arPinfo[i].IsChildOf(m_idProcess)){
+          t+=arPinfo[i].tCpu;
+        }
+      }
+    }
+  }
+#else
+  PInfoArray arPinfo;
+  PSExtract(arPinfo);
+  for(int i=0;i<(signed)arPinfo.size();i++){
+    if(arPinfo[i].PID==m_idProcess || arPinfo[i].IsChildOf(m_idProcess)){
+      t+=arPinfo[i].tCpu;
+    }
+  }
+#endif
+  return t;
+}
+
+#ifdef _WIN32
+bool CSubprocess::PSExtract(CSubprocess::PInfoArray &arPinfo)
+{
+  bool rc=false;
+  arPinfo.clear();
+  // If Windows NT:
+  switch(GetPlatform()) {
+  case VER_PLATFORM_WIN32_NT:
+    if(hInstLib1) {
+      
+      // Get procedure addresses.
+      static BOOL (WINAPI *lpfEnumProcesses)( DWORD *, DWORD cb, DWORD * ) = (BOOL(WINAPI *)(DWORD *,DWORD,DWORD*))GetProcAddress( hInstLib1, "EnumProcesses" ) ;
+      if( lpfEnumProcesses) {
+        
+        if(hInstLib2) {
+          
+          static DWORD (WINAPI *lpfNtQueryInformationProcess)( HANDLE, int, void *, DWORD, LPDWORD ) =
+            (DWORD(WINAPI *)(HANDLE, int, void *, DWORD, LPDWORD)) GetProcAddress( hInstLib2,"NtQueryInformationProcess" ) ;
+          
+          if(lpfNtQueryInformationProcess){
+            DWORD dwMaxPids=256;
+            DWORD dwPidSize;
+            DWORD *arPids = NULL ;
+            do {
+              delete [] arPids;
+              arPids=new DWORD[dwMaxPids];
+            } while(lpfEnumProcesses(arPids, dwMaxPids, &dwPidSize) && dwPidSize/sizeof(DWORD)==dwMaxPids) ;
+            
+            if(dwPidSize/sizeof(DWORD)<dwMaxPids){
+              rc=true;
+              for( DWORD dwIndex = 0 ; (signed)dwIndex < dwPidSize/sizeof(DWORD); dwIndex++ ) {
+                // Regardless of OpenProcess success or failure, we
+                // still call the enum func with the ProcID.
+                DWORD pid=arPids[dwIndex];
+                HANDLE hProcess=::OpenProcess( PROCESS_QUERY_INFORMATION, FALSE, pid ); 
+                if (hProcess ) {
+                  struct {
+                    DWORD ExitStatus; // receives process termination status
+                    DWORD PebBaseAddress; // receives process environment block address
+                    DWORD AffinityMask; // receives process affinity mask
+                    DWORD BasePriority; // receives process priority class
+                    ULONG UniqueProcessId; // receives process identifier
+                    ULONG InheritedFromUniqueProcessId; // receives parent process identifier
+                  } pbi;
+                  memset( &pbi, 0, sizeof(pbi)); 
+                  DWORD retLen; 
+                  __int64 ftCreation,ftExit,ftKernel,ftUser;
+                  if(lpfNtQueryInformationProcess(hProcess, 0 /*ProcessBasicInformation*/, &pbi, sizeof(pbi), &retLen)>=0 &&
+                    TRUE==::GetProcessTimes (hProcess,(FILETIME *)&ftCreation,(FILETIME *)&ftExit,(FILETIME *)&ftKernel,(FILETIME *)&ftUser)){
+                    // The second test is important.  It excludes orphaned processes who appear to have been adopted by virtue of a new
+                    // process having been created with the same ID as their original parent.
+                    PInfo p;
+                    p.PID=pid;
+                    p.PPID=pbi.InheritedFromUniqueProcessId;
+                    p.tCreation=ftCreation;
+                    p.tCpu=Time((ftKernel+ftUser)/10000);
+                    arPinfo.push_back(p);
+                  }
+                  
+                  CloseHandle(hProcess); 
+
+                }
+              }
+            }
+            delete [] arPids;
+          }          
+        }
+      }      
+    }
+    break;
+  case VER_PLATFORM_WIN32_WINDOWS:
+    
+    if( hInstLib1) {
+      
+      static HANDLE (WINAPI *lpfCreateToolhelp32Snapshot)(DWORD,DWORD)=
+        (HANDLE(WINAPI *)(DWORD,DWORD))GetProcAddress( hInstLib1,"CreateToolhelp32Snapshot" ) ;
+      static BOOL (WINAPI *lpfProcess32First)(HANDLE,LPPROCESSENTRY32)=
+        (BOOL(WINAPI *)(HANDLE,LPPROCESSENTRY32))GetProcAddress( hInstLib1, "Process32First" ) ;
+      static BOOL (WINAPI *lpfProcess32Next)(HANDLE,LPPROCESSENTRY32)=
+        (BOOL(WINAPI *)(HANDLE,LPPROCESSENTRY32))GetProcAddress( hInstLib1, "Process32Next" ) ;
+      if( lpfProcess32Next && lpfProcess32First && lpfCreateToolhelp32Snapshot) {
+        
+        // Get a handle to a Toolhelp snapshot of the systems
+        // processes.
+        HANDLE hSnapShot = lpfCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) ;
+        if(INVALID_HANDLE_VALUE != hSnapShot) {
+          // Get the first process' information.
+          PROCESSENTRY32 procentry;
+          procentry.dwSize = sizeof(PROCESSENTRY32) ;
+          if(lpfProcess32First( hSnapShot, &procentry )){
+            rc=true;
+            do {
+              PInfo p;
+              p.PID=procentry.th32ProcessID;
+              p.PPID=procentry.th32ParentProcessID;
+              arPinfo.push_back(p);
+            } while(lpfProcess32Next( hSnapShot, &procentry ));
+          }
+          CloseHandle(hSnapShot);
+        }
+      }
+    }
+    break;
+  default:
+    break;
+  }    
+
+  SetParents(arPinfo);
+
+  if(!rc){
+    ERROR(_T("Couldn't get process information!\n"));
+  }
+  return rc;
+}
+
+#else // UNIX
+
+bool CSubprocess::PSExtract(CSubprocess::PInfoArray &arPinfo)
+{
+  arPinfo.clear();
+  int i;
+  FILE *f=popen("ps -l",_T("r") MODE_TEXT);
+  if(f){
+    char buf[100];
+    while(fgets(buf,sizeof(buf)-1,f)){
+      TCHAR discard[100];
+      PInfo p;
+      // Output is in the form
+      //  F S   UID   PID  PPID  C PRI  NI ADDR    SZ WCHAN  TTY          TIME CMD
+      //100 S   490   877   876  0  70   0    -   368 wait4  pts/0    00:00:00 bash
+      int F,UID,C,PRI,NI,SZ,HH,MM,SS; 
+      bool rc=(15==_stscanf(buf,_T("%d %s %d %d %d %d %d %d %s %d %s %s %d:%d:%d"),&F,discard,&UID,&p.PID,&p.PPID,&C,&PRI,&NI,discard,&SZ,discard,discard,&HH,&MM,&SS));
+      if(rc){
+        p.tCpu=1000*(SS+60*(60*HH+MM));
+        arPinfo.push_back(p);
+      }
+    }
+    pclose(f);
+    for(i=0;i<(signed)arPinfo.size();i++){
+      int pid=arPinfo[i].PPID;
+      arPinfo[i].pParent=0;
+      for(int j=0;j<(signed)arPinfo.size();j++){
+        if(i!=j && arPinfo[j].PID==pid){
+          arPinfo[i].pParent=&arPinfo[j];
+          break;
+        }
+      }
+    }
+  } else {
+    ERROR(_T("Failed to run ps -l\n"));
+  }
+  return true; //FIXME
+}
+
+#endif
+
+void CSubprocess::SetParents(CSubprocess::PInfoArray &arPinfo)
+{
+  int i;
+  for(i=0;i<(signed)arPinfo.size();i++){
+    PInfo &p=arPinfo[i];
+    p.pParent=0;
+    for(int j=0;j<(signed)arPinfo.size();j++){
+      if(arPinfo[j].PID==p.PPID 
+#ifdef _WIN32
+        && arPinfo[j].tCreation<p.tCreation
+#endif
+        )
+      {
+        arPinfo[i].pParent=&arPinfo[j];
+        break;
+      }
+    }
+  }
+
+  // Check for circularity
+  bool bCircularity=false;
+  for(i=0;i<(signed)arPinfo.size();i++){
+    PInfo *p=&arPinfo[i];
+    for(int j=0;j<(signed)arPinfo.size() && p;j++){
+      p=p->pParent;
+    }
+    // If all is well, p should be NULL here.  Otherwise we have a loop.
+    if(p){
+      // Make sure it can't foul things up:
+      arPinfo[i].pParent=0;
+      bCircularity=true;
+    }
+  }
+  
+  if(bCircularity){
+    ERROR(_T("!!! Circularly linked process list at index %d\n"),i);
+    for(int k=0;k<(signed)arPinfo.size();k++){
+      const PInfo &p=arPinfo[k];
+      ERROR(_T("%d: %s ppid=%4d\n"),k,(LPCTSTR)Name(p.PID),p.PPID);
+    }
+  }
+}
+
+bool CSubprocess::PInfo::IsChildOf(int pid) const
+{
+  for(PInfo *p=pParent;p && p!=this;p=p->pParent) { // guard against circular linkage
+    if(p->PID==pid){
+      return true;
+    }
+  }
+  return false;
+}
+
+const String CSubprocess::Name(int pid)
+{
+  String str(String::SFormat(_T("id=%d"),pid));
+#ifdef _DEBUG
+#ifdef _WIN32
+  if(VER_PLATFORM_WIN32_NT==GetPlatform() && hInstLib1){
+    static BOOL (WINAPI *lpfEnumProcessModules)( HANDLE, HMODULE *, DWORD, LPDWORD ) =
+      (BOOL(WINAPI *)(HANDLE, HMODULE *, DWORD, LPDWORD)) GetProcAddress( hInstLib1,"EnumProcessModules" ) ;
+    static DWORD (WINAPI *lpfGetModuleFileNameEx)( HANDLE, HMODULE, LPTSTR, DWORD )=
+      (DWORD (WINAPI *)(HANDLE, HMODULE,LPTSTR, DWORD )) GetProcAddress( hInstLib1,"GetModuleFileNameExA" ) ;
+    if( lpfEnumProcessModules &&  lpfGetModuleFileNameEx ) {
+      HANDLE hProcess=::OpenProcess(PROCESS_ALL_ACCESS,false,pid);
+      if(hProcess) {
+        HMODULE hMod;
+        DWORD dwSize;
+        if(lpfEnumProcessModules( hProcess, &hMod, sizeof(HMODULE), &dwSize ) ){
+          // Get Full pathname:
+          TCHAR buf[1+MAX_PATH];
+          lpfGetModuleFileNameEx( hProcess, hMod, buf, MAX_PATH);
+          str+=_TCHAR(' ');
+          str+=buf;
+        }
+        CloseHandle(hProcess);
+      }
+    }
+  }
+#endif
+#endif
+  return str;
+}
+
+#ifdef _WIN32
+DWORD CSubprocess::GetPlatform()
+{
+  OSVERSIONINFO  osver;
+  osver.dwOSVersionInfoSize = sizeof( osver ) ;
+  return GetVersionEx( &osver ) ? osver.dwPlatformId : (DWORD)-1;
+}
+#endif
+
+bool CSubprocess::ProcessAlive()
+{
+  return !m_bThreadTerminated;
+}
+
+void CSubprocess::CloseInput()
+{
+#ifdef _WIN32
+  CloseHandle(m_hwPipe);m_hwPipe=INVALID_HANDLE_VALUE;
+#else
+  close(m_tty);
+#endif
+}
+
+bool CSubprocess::Wait(Duration dTimeout)
+{
+  return CeCosThreadUtils::WaitFor(m_bThreadTerminated,dTimeout);
+}
+
+const String CSubprocess::ErrorString() const
+{
+#ifdef _WIN32
+  TCHAR *pszMsg;
+  FormatMessage(  
+    FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
+    NULL,
+    m_nErr,
+    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
+    (LPTSTR)&pszMsg,
+    0,
+    NULL 
+    );
+  return pszMsg;
+#else 
+  return strerror(errno);
+#endif
+}
+