1 //####COPYRIGHTBEGIN####
3 // ----------------------------------------------------------------------------
4 // Copyright (C) 1998, 1999, 2000 Red Hat, Inc.
6 // This program is part of the eCos host tools.
8 // This program is free software; you can redistribute it and/or modify it
9 // under the terms of the GNU General Public License as published by the Free
10 // Software Foundation; either version 2 of the License, or (at your option)
13 // This program is distributed in the hope that it will be useful, but WITHOUT
14 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
18 // You should have received a copy of the GNU General Public License along with
19 // this program; if not, write to the Free Software Foundation, Inc.,
20 // 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 // ----------------------------------------------------------------------------
24 //####COPYRIGHTEND####
25 //===========================================================================
26 //===========================================================================
27 //#####DESCRIPTIONBEGIN####
34 // Description: This is the implementation of the class which allows for spawning subprocesses
42 //####DESCRIPTIONEND####
44 //===========================================================================
45 #include "eCosTrace.h"
46 #include "Subprocess.h"
50 HINSTANCE CSubprocess::hInstLib1 = VER_PLATFORM_WIN32_NT==CSubprocess::GetPlatform()?LoadLibrary(_T("PSAPI.DLL")):LoadLibrary(_T("Kernel32.DLL")) ;
51 HINSTANCE CSubprocess::hInstLib2 = VER_PLATFORM_WIN32_NT==CSubprocess::GetPlatform()?LoadLibrary(_T("NTDLL.DLL")):NULL;
55 //#define CloseHandle(x) TRACE(_T("CSubprocess::CloseHandle %x\n"),x);::CloseHandle(x)
57 const unsigned int CSubprocess::PROCESS_KILL_EXIT_CODE=0xCCFFCCFF;
59 CSubprocess::CSubprocess(bool bAutoDelete):
60 m_pfnContinue(DefaultContinuationFunc),
61 m_pContinuationFuncParam(0),
62 m_bAutoDelete(bAutoDelete),
63 m_bThreadTerminated(true),
76 CSubprocess::~CSubprocess()
79 if(!CeCosThreadUtils::WaitFor(m_bThreadTerminated,1000)){
81 CeCosThreadUtils::WaitFor(m_bThreadTerminated);
85 CloseHandle(m_hProcess);
90 bool CSubprocess::Run(LogFunc *pfnLog,void * pLogparam, LPCTSTR pszCmd,bool bBlock/*=true*/)
93 if(!m_bThreadTerminated){
97 m_pLogparam=pLogparam;
99 // UNIX does it from the thread func. WIN32 could too, but it's nice to know at the time
100 // of calling run whether the process is successfully created.
102 // Normally done in the dtor
103 CloseHandle(m_hProcess);
105 rc=CreateProcess(pszCmd);
113 // When using RunThread, the manipulation of this Boolean is taken care of.
114 // Here we must do it ourselves.
115 m_bThreadTerminated=false;
117 m_bThreadTerminated=true;
119 CeCosThreadUtils::RunThread(SThreadFunc,this,&m_bThreadTerminated,String::SFormat(_T("subprocess %d read"),m_idProcess));
127 bool CSubprocess::CreateProcess(LPCTSTR pszCmdline)
130 STARTUPINFO si; // For CreateProcess call
131 HANDLE hrPipe,hwPipe,hwPipe2,m_hrPipeTemp,m_hwPipeTemp;
132 // Create the anonymous pipe
134 SECURITY_ATTRIBUTES saPipe; // Security for anonymous pipe
135 saPipe.nLength = sizeof(SECURITY_ATTRIBUTES);
136 saPipe.lpSecurityDescriptor = NULL;
137 saPipe.bInheritHandle = true;
139 ::CreatePipe(&m_hrPipeTemp,&hwPipe,&saPipe,10240);
141 // In most cases you can get away with using the same anonymous
142 // pipe write handle for both the child's standard output and
143 // standard error, but this may cause problems if the child app
144 // explicitly closes one of its standard output or error handles. If
145 // that happens, the anonymous pipe will close, since the child's
146 // standard output and error handles are really the same handle. The
147 // child won't be able to write to the other write handle since the
148 // pipe is now gone, and parent reads from the pipe will return
149 // ERROR_BROKEN_PIPE and child output will be lost. To solve this
150 // problem, simply duplicate the write end of the pipe to create
151 // another distinct, separate handle to the write end of the pipe.
152 // One pipe write handle will serve as standard out, the other as
153 // standard error. Now *both* write handles must be closed before the
154 // write end of the pipe actually closes.
156 ::DuplicateHandle(::GetCurrentProcess(), // Source process
157 hwPipe, // Handle to duplicate
158 ::GetCurrentProcess(), // Destination process
159 &hwPipe2, // New handle, used as stderr by child
160 0, // New access flags - ignored since DUPLICATE_SAME_ACCESS
161 true, // It's inheritable
162 DUPLICATE_SAME_ACCESS);
164 ::CreatePipe(&hrPipe,&m_hwPipeTemp,&saPipe,10240);
167 // Create new output read handle and the input write handles, setting
168 // the Properties to FALSE. Otherwise, the child inherits the
169 // properties and, as a result, non-closeable handles to the pipes
171 DuplicateHandle(GetCurrentProcess(),m_hrPipeTemp,
173 &m_hrPipe, // Address of new handle.
174 0,FALSE, // Make it uninheritable.
175 DUPLICATE_SAME_ACCESS);
177 DuplicateHandle(GetCurrentProcess(),m_hwPipeTemp,
179 &m_hwPipe, // Address of new handle.
180 0,FALSE, // Make it uninheritable.
181 DUPLICATE_SAME_ACCESS);
183 // Close inheritable copies of the handles we do not want to be inherited:
184 CloseHandle(m_hrPipeTemp);
185 CloseHandle(m_hwPipeTemp);
188 memset(&si, 0, sizeof(si));
191 si.hStdOutput = hwPipe;
192 si.hStdError = hwPipe2;
193 si.hStdInput = hrPipe;
194 si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
195 si.wShowWindow = SW_SHOW;
198 if(m_strDir.empty()){
199 pszDir=NULL; // current directory
204 PROCESS_INFORMATION pi;
205 String strCmd(pszCmdline);
208 if(!m_strPath.empty()){
209 int nSize=GetEnvironmentVariable(_T("PATH"), NULL, 0);
211 GetEnvironmentVariable(_T("PATH"),strOrigpath.GetBuffer(nSize),nSize);
212 strOrigpath.ReleaseBuffer();
213 SetEnvironmentVariable(_T("PATH"),m_strPath);
217 bool rc=(TRUE==::CreateProcess(NULL,strCmd.GetBuffer(),NULL,NULL,true,DETACHED_PROCESS|CREATE_NEW_PROCESS_GROUP,NULL,pszDir,&si,&pi));
219 if(!m_strPath.empty()){
220 SetEnvironmentVariable(_T("PATH"),strOrigpath);
223 m_nErr=GetLastError();
225 strCmd.ReleaseBuffer();
228 m_idProcess=pi.dwProcessId;
229 m_hProcess=pi.hProcess;
231 Output(String::SFormat(_T("*** Process %d created \"%s\"\n"),m_idProcess,pszCmdline));
233 TRACE(String::SFormat(_T("Process %d created \"%s\"\n"),m_idProcess,pszCmdline));
234 m_nExitCode=STILL_ACTIVE;
235 CloseHandle(pi.hThread);
239 Output(String::SFormat(_T("*** Failed to create process \"%s\" %s\n"),pszCmdline,(LPCTSTR)ErrorString()));
241 TRACE(String::SFormat(_T("Failed to create process \"%s\" %s\n"),pszCmdline,(LPCTSTR)ErrorString()));
242 m_nExitCode=GetLastError();
243 CloseHandle(m_hrPipe);m_hrPipe=INVALID_HANDLE_VALUE;
244 CloseHandle(m_hwPipe);m_hwPipe=INVALID_HANDLE_VALUE;
249 CloseHandle(hwPipe2);
255 void CSubprocess::ThreadFunc()
258 TRACE(_T("Reading from process %d\n"),m_idProcess);
264 while (!m_bKillThread && m_pfnContinue(m_pContinuationFuncParam) && ::PeekNamedPipe(m_hrPipe, NULL, 0, 0, &dwAvail, NULL)){
265 //TRACE(_T("P%d\n"),dwAvail);
267 dwAvail=MIN(dwAvail,80); // Read a maximum of 80 characters at a time
269 char *buf=new char[dwAvail+1];
270 //TRACE(_T("R%d\n"),dwAvail);
271 if(!::ReadFile(m_hrPipe, buf, dwAvail, &dwRead, NULL)){
272 TRACE(_T("ReadFile returns false\n"));
277 Output(String::CStrToUnicodeStr(buf));
280 else if (!ProcessAlive())
282 TRACE(_T("m_bThreadTerminated=%d\n"),m_bThreadTerminated);
285 // Fix for hanging in an endless loop under Windows ME, by Bill Diehls <billabloke@yahoo.com>
286 else if (::GetExitCodeProcess(m_hProcess, &dwExitCode) && dwExitCode!=STILL_ACTIVE)
292 CeCosThreadUtils::Sleep(250);
296 ::GetExitCodeProcess(m_hProcess, &dwExitCode);
297 m_nExitCode=dwExitCode;
303 str=_T("still alive");
305 str+=_T(" - requested to stop reading");
308 case PROCESS_KILL_EXIT_CODE:
312 str.Format(_T("terminated rc=%d"),dwExitCode);
315 TRACE(_T("Finished reading from process %d (%s)\n"),m_idProcess,(LPCTSTR)str);
318 CloseHandle(m_hrPipe);m_hrPipe=INVALID_HANDLE_VALUE;
319 CloseHandle(m_hwPipe);m_hwPipe=INVALID_HANDLE_VALUE;
322 m_bThreadTerminated=true; // or else the dtor will block
329 bool CSubprocess::CreateProcess(LPCTSTR pszCmdline)
332 int fdchild=-1; // the file descriptor for the child (slave) half of the pseudo-tty pair
334 // Get a free /dev/ptyp0 (master) and /dev/ttyp0 (slave) tty pair
335 String strMasterTty,strChildTty;
336 for(unsigned int c=0;c<64;c++){
337 strMasterTty.Format("/dev/pty%c%x",'p'+c/16,c%16);
339 m_tty=open(strMasterTty, O_RDWR | O_NOCTTY);
341 strChildTty.Format("/dev/tty%c%x",'p'+c/16,c%16);
343 fdchild = open(strChildTty, O_RDWR);
348 VTRACE("opened %s - fd=%d\n",(LPCTSTR)strMasterTty,m_tty);
355 ERROR(_T("Failed to get a pty\n"));
359 TRACE(_T("Master pty %s (fd %d) slave pty %s (fd %d)\n"),(LPCTSTR)strMasterTty,m_tty,(LPCTSTR)strChildTty,fdchild);
363 switch (m_idProcess) {
366 TRACE(_T("Failed to create process - %s\n"),strerror(errno));
370 // Process is created (we're the child)
372 // Close all descriptors except the slave side of the pseudo-terminal
373 for (int fd = 0; fd < (int) sysconf(_SC_OPEN_MAX); fd++) {
386 if(!m_strDir.empty()){
387 if(0!=chdir(m_strDir)){
389 fprintf(stderr,_T("*** Failed to change directory to %s\n"),(LPCTSTR)m_strDir);
395 fprintf(stderr,_T("*** Process %d created \"%s\"\n"),m_idProcess,pszCmdline);
399 int argc=String(pszCmdline).Chop(ar,_TCHAR(' '),true);
400 TCHAR **argv=new TCHAR *[1+argc];
401 for(int i=0;i<argc;i++){
402 argv[i]=new TCHAR[1+strlen(ar[i])];
403 strcpy(argv[i],ar[i]);
406 if(!m_strPath.empty()){
407 _tputenv(String::SFormat(_T("PATH=%s"),(LPCTSTR)m_strPath));
409 _texecvp(argv[0], argv);
412 fprintf(stderr,"exec error - %s\n",strerror(errno));
416 // Process is created (we're the parent)
417 TRACE(_T("Closing fd %d\n"),fdchild);
419 TRACE(_T("Forked to create process %s - process id <%d>\n"), pszCmdline, m_idProcess);
422 return 0!=m_idProcess;
425 void CSubprocess::ThreadFunc()
427 if(!CreateProcess(m_strCmd)){
428 ERROR(_T("Failed to create process for %s\n"),(LPCTSTR)m_strCmd);
430 fcntl(m_tty,F_SETFL,O_NONBLOCK);
434 rc=read(m_tty, buf, sizeof(buf)-1);
441 CeCosThreadUtils::Sleep(250);
450 buf[rc]=_TCHAR('\0');
451 Output(String::CStrToUnicodeStr(buf));
454 } while(!m_bKillThread && m_pfnContinue(m_pContinuationFuncParam));
456 TRACE(_T("Closing fd %d\n"),m_tty);
459 switch(waitpid(m_idProcess,&m_nExitCode,WNOHANG));
468 void CSubprocess::Output (LPCTSTR psz)
470 m_pfnLogfunc(m_pLogparam,psz);
473 void CSubprocess::Send(LPCTSTR str)
475 char *psz=String(str).GetCString();
476 int nToWrite=strlen(psz);
481 if(!::WriteFile(m_hwPipe,psz,nToWrite,&dwWritten,0)){
485 int dwWritten = write(m_tty, c, nToWrite);
490 nToWrite-=(int)dwWritten;
492 } while (nToWrite>0);
493 //::FlushFileBuffers(m_hwPipe);
497 bool CSubprocess::Kill(bool bRecurse)
499 TRACE(_T("CSubprocess::Kill pid %d recurse=%d\n"),m_idProcess,bRecurse);
502 if(m_idProcess && -1!=m_idProcess){
503 // Start of with the easy one:
505 // Need to gather this information before we orphan our grandchildren:
512 TRACE(_T("Terminate process %s\n"),(LPCTSTR)Name(m_idProcess));
513 rc=(TRUE==::TerminateProcess(m_hProcess,PROCESS_KILL_EXIT_CODE));
514 // dtor's (or subsequent Run's) responsibility to close the handle
518 rc=(0==kill(m_idProcess,SIGTERM));
520 waitpid(m_idProcess,&status,WNOHANG);
524 // kill process *and* its children
525 // FIXME: needs to be top-down
526 for(int i=0;i<(signed)arPinfo.size();i++){
527 if(arPinfo[i].IsChildOf(m_idProcess)){
531 const String strName(Name(arPinfo[i].PID));
532 if(_tcsstr(strName,_T("eCosTest")) || _tcsstr(strName,_T("cmd.EXE")) || _tcsstr(strName,_T("CMD.EXE")) || arPinfo[i].PID==(signed)GetCurrentProcessId()){
536 HANDLE hProcess=::OpenProcess(PROCESS_TERMINATE,false,arPinfo[i].PID);
538 TRACE(_T("Terminate process %s\n"),(LPCTSTR)Name(arPinfo[i].PID));
539 rc&=(TRUE==::TerminateProcess(hProcess,PROCESS_KILL_EXIT_CODE));
540 CloseHandle(hProcess);
545 rc&=(0==kill(arPinfo[i].PID,SIGTERM));
547 waitpid(arPinfo[i].PID,&status,WNOHANG);
556 Time CSubprocess::CpuTime(bool bRecurse) const
559 // kill process *and* its children
560 // FIXME: needs to be top-down
563 __int64 ftCreation,ftExit,ftKernel,ftUser;
564 if(m_hProcess && ::GetProcessTimes (m_hProcess,(FILETIME *)&ftCreation,(FILETIME *)&ftExit,(FILETIME *)&ftKernel,(FILETIME *)&ftUser)){
565 t+=Time((ftKernel+ftUser)/10000);
571 if(m_idProcess && -1!=m_idProcess){
572 for(int i=0;i<(signed)arPinfo.size();i++){
573 if(arPinfo[i].IsChildOf(m_idProcess)){
582 for(int i=0;i<(signed)arPinfo.size();i++){
583 if(arPinfo[i].PID==m_idProcess || arPinfo[i].IsChildOf(m_idProcess)){
592 bool CSubprocess::PSExtract(CSubprocess::PInfoArray &arPinfo)
597 switch(GetPlatform()) {
598 case VER_PLATFORM_WIN32_NT:
601 // Get procedure addresses.
602 static BOOL (WINAPI *lpfEnumProcesses)( DWORD *, DWORD cb, DWORD * ) = (BOOL(WINAPI *)(DWORD *,DWORD,DWORD*))GetProcAddress( hInstLib1, "EnumProcesses" ) ;
603 if( lpfEnumProcesses) {
607 static DWORD (WINAPI *lpfNtQueryInformationProcess)( HANDLE, int, void *, DWORD, LPDWORD ) =
608 (DWORD(WINAPI *)(HANDLE, int, void *, DWORD, LPDWORD)) GetProcAddress( hInstLib2,"NtQueryInformationProcess" ) ;
610 if(lpfNtQueryInformationProcess){
613 DWORD *arPids = NULL ;
616 arPids=new DWORD[dwMaxPids];
617 } while(lpfEnumProcesses(arPids, dwMaxPids, &dwPidSize) && dwPidSize/sizeof(DWORD)==dwMaxPids) ;
619 if(dwPidSize/sizeof(DWORD)<dwMaxPids){
621 for( DWORD dwIndex = 0 ; (signed)dwIndex < dwPidSize/sizeof(DWORD); dwIndex++ ) {
622 // Regardless of OpenProcess success or failure, we
623 // still call the enum func with the ProcID.
624 DWORD pid=arPids[dwIndex];
625 HANDLE hProcess=::OpenProcess( PROCESS_QUERY_INFORMATION, FALSE, pid );
628 DWORD ExitStatus; // receives process termination status
629 DWORD PebBaseAddress; // receives process environment block address
630 DWORD AffinityMask; // receives process affinity mask
631 DWORD BasePriority; // receives process priority class
632 ULONG UniqueProcessId; // receives process identifier
633 ULONG InheritedFromUniqueProcessId; // receives parent process identifier
635 memset( &pbi, 0, sizeof(pbi));
637 __int64 ftCreation,ftExit,ftKernel,ftUser;
638 if(lpfNtQueryInformationProcess(hProcess, 0 /*ProcessBasicInformation*/, &pbi, sizeof(pbi), &retLen)>=0 &&
639 TRUE==::GetProcessTimes (hProcess,(FILETIME *)&ftCreation,(FILETIME *)&ftExit,(FILETIME *)&ftKernel,(FILETIME *)&ftUser)){
640 // The second test is important. It excludes orphaned processes who appear to have been adopted by virtue of a new
641 // process having been created with the same ID as their original parent.
644 p.PPID=pbi.InheritedFromUniqueProcessId;
645 p.tCreation=ftCreation;
646 p.tCpu=Time((ftKernel+ftUser)/10000);
647 arPinfo.push_back(p);
650 CloseHandle(hProcess);
661 case VER_PLATFORM_WIN32_WINDOWS:
665 static HANDLE (WINAPI *lpfCreateToolhelp32Snapshot)(DWORD,DWORD)=
666 (HANDLE(WINAPI *)(DWORD,DWORD))GetProcAddress( hInstLib1,"CreateToolhelp32Snapshot" ) ;
667 static BOOL (WINAPI *lpfProcess32First)(HANDLE,LPPROCESSENTRY32)=
668 (BOOL(WINAPI *)(HANDLE,LPPROCESSENTRY32))GetProcAddress( hInstLib1, "Process32First" ) ;
669 static BOOL (WINAPI *lpfProcess32Next)(HANDLE,LPPROCESSENTRY32)=
670 (BOOL(WINAPI *)(HANDLE,LPPROCESSENTRY32))GetProcAddress( hInstLib1, "Process32Next" ) ;
671 if( lpfProcess32Next && lpfProcess32First && lpfCreateToolhelp32Snapshot) {
673 // Get a handle to a Toolhelp snapshot of the systems
675 HANDLE hSnapShot = lpfCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) ;
676 if(INVALID_HANDLE_VALUE != hSnapShot) {
677 // Get the first process' information.
678 PROCESSENTRY32 procentry;
679 procentry.dwSize = sizeof(PROCESSENTRY32) ;
680 if(lpfProcess32First( hSnapShot, &procentry )){
684 p.PID=procentry.th32ProcessID;
685 p.PPID=procentry.th32ParentProcessID;
686 arPinfo.push_back(p);
687 } while(lpfProcess32Next( hSnapShot, &procentry ));
689 CloseHandle(hSnapShot);
701 ERROR(_T("Couldn't get process information!\n"));
708 bool CSubprocess::PSExtract(CSubprocess::PInfoArray &arPinfo)
712 FILE *f=popen("ps -l",_T("r") MODE_TEXT);
715 while(fgets(buf,sizeof(buf)-1,f)){
718 // Output is in the form
719 // F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
720 //100 S 490 877 876 0 70 0 - 368 wait4 pts/0 00:00:00 bash
721 int F,UID,C,PRI,NI,SZ,HH,MM,SS;
722 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));
724 p.tCpu=1000*(SS+60*(60*HH+MM));
725 arPinfo.push_back(p);
729 for(i=0;i<(signed)arPinfo.size();i++){
730 int pid=arPinfo[i].PPID;
731 arPinfo[i].pParent=0;
732 for(int j=0;j<(signed)arPinfo.size();j++){
733 if(i!=j && arPinfo[j].PID==pid){
734 arPinfo[i].pParent=&arPinfo[j];
740 ERROR(_T("Failed to run ps -l\n"));
747 void CSubprocess::SetParents(CSubprocess::PInfoArray &arPinfo)
750 for(i=0;i<(signed)arPinfo.size();i++){
753 for(int j=0;j<(signed)arPinfo.size();j++){
754 if(arPinfo[j].PID==p.PPID
756 && arPinfo[j].tCreation<p.tCreation
760 arPinfo[i].pParent=&arPinfo[j];
766 // Check for circularity
767 bool bCircularity=false;
768 for(i=0;i<(signed)arPinfo.size();i++){
769 PInfo *p=&arPinfo[i];
770 for(int j=0;j<(signed)arPinfo.size() && p;j++){
773 // If all is well, p should be NULL here. Otherwise we have a loop.
775 // Make sure it can't foul things up:
776 arPinfo[i].pParent=0;
782 ERROR(_T("!!! Circularly linked process list at index %d\n"),i);
783 for(int k=0;k<(signed)arPinfo.size();k++){
784 const PInfo &p=arPinfo[k];
785 ERROR(_T("%d: %s ppid=%4d\n"),k,(LPCTSTR)Name(p.PID),p.PPID);
790 bool CSubprocess::PInfo::IsChildOf(int pid) const
792 for(PInfo *p=pParent;p && p!=this;p=p->pParent) { // guard against circular linkage
800 const String CSubprocess::Name(int pid)
802 String str(String::SFormat(_T("id=%d"),pid));
805 if(VER_PLATFORM_WIN32_NT==GetPlatform() && hInstLib1){
806 static BOOL (WINAPI *lpfEnumProcessModules)( HANDLE, HMODULE *, DWORD, LPDWORD ) =
807 (BOOL(WINAPI *)(HANDLE, HMODULE *, DWORD, LPDWORD)) GetProcAddress( hInstLib1,"EnumProcessModules" ) ;
808 static DWORD (WINAPI *lpfGetModuleFileNameEx)( HANDLE, HMODULE, LPTSTR, DWORD )=
809 (DWORD (WINAPI *)(HANDLE, HMODULE,LPTSTR, DWORD )) GetProcAddress( hInstLib1,"GetModuleFileNameExA" ) ;
810 if( lpfEnumProcessModules && lpfGetModuleFileNameEx ) {
811 HANDLE hProcess=::OpenProcess(PROCESS_ALL_ACCESS,false,pid);
815 if(lpfEnumProcessModules( hProcess, &hMod, sizeof(HMODULE), &dwSize ) ){
816 // Get Full pathname:
817 TCHAR buf[1+MAX_PATH];
818 lpfGetModuleFileNameEx( hProcess, hMod, buf, MAX_PATH);
822 CloseHandle(hProcess);
832 DWORD CSubprocess::GetPlatform()
835 osver.dwOSVersionInfoSize = sizeof( osver ) ;
836 return GetVersionEx( &osver ) ? osver.dwPlatformId : (DWORD)-1;
840 bool CSubprocess::ProcessAlive()
842 return !m_bThreadTerminated;
845 void CSubprocess::CloseInput()
848 CloseHandle(m_hwPipe);m_hwPipe=INVALID_HANDLE_VALUE;
854 bool CSubprocess::Wait(Duration dTimeout)
856 return CeCosThreadUtils::WaitFor(m_bThreadTerminated,dTimeout);
859 const String CSubprocess::ErrorString() const
864 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
867 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
874 return strerror(errno);