/* * Copyright (C) 2007-2014 Xagasoft, All rights reserved. * * This file is part of the libbu++ library and is released under the * terms of the license contained in the file LICENSE. */ #include "bu/process.h" #include #include #ifndef WIN32 #include #include #include #include #include #include #else #include #include #include #endif #include "bu/config.h" Bu::Process::ProcData::ProcData() : #ifdef WIN32 hStdIn( NULL ), hStdOut( NULL ), hStdErr( NULL ) #else iStdIn( -1 ), iStdOut( -1 ), iStdErr( -1 ) #endif { } Bu::Process::Process( Flags eFlags, const char *sName, char *const argv[] ) : iProcStatus( 0 ), bBlocking( true ), bStdOutEos( true ), bStdErrEos( true ) { gexec( eFlags, sName, argv ); } Bu::Process::Process( Flags eFlags, const char *sName, const char *argv, ...) : iProcStatus( 0 ), bBlocking( true ), bStdOutEos( true ), bStdErrEos( true ) { int iCnt = 0; va_list ap; va_start( ap, argv ); for(; va_arg( ap, const char *); iCnt++ ) { } va_end( ap ); char const **list = new char const *[iCnt+2]; va_start( ap, argv ); list[0] = argv; for( int j = 1; j <= iCnt; j++ ) { list[j] = va_arg( ap, const char *); } list[iCnt+1] = NULL; va_end( ap ); gexec( eFlags, sName, (char *const *)list ); delete[] list; } Bu::Process::Process( Flags eFlags, const Bu::Process::Options &opt, const char *sName, char *const argv[] ) : iProcStatus( 0 ), bBlocking( true ), bStdOutEos( true ), bStdErrEos( true ), opt( opt ) { gexec( eFlags, sName, argv ); } Bu::Process::Process( Flags eFlags, const Bu::Process::Options &opt, const char *sName, const char *argv, ...) : iProcStatus( 0 ), bBlocking( true ), bStdOutEos( true ), bStdErrEos( true ), opt( opt ) { int iCnt = 0; va_list ap; va_start( ap, argv ); for(; va_arg( ap, const char *); iCnt++ ) { } va_end( ap ); char const **list = new char const *[iCnt+2]; va_start( ap, argv ); list[0] = argv; for( int j = 1; j <= iCnt; j++ ) { list[j] = va_arg( ap, const char *); } list[iCnt+1] = NULL; va_end( ap ); gexec( eFlags, sName, (char *const *)list ); delete[] list; } Bu::Process::~Process() { close(); } void Bu::Process::wait() { close(); } void Bu::Process::gexec( Flags eFlags, const char *sName, char *const argv[] ) { #ifdef WIN32 bStdErrEos = true; SECURITY_ATTRIBUTES saAttr; HANDLE hChildStd_IN_Rd; HANDLE hChildStd_IN_Wr; HANDLE hChildStd_OUT_Rd; HANDLE hChildStd_OUT_Wr; PROCESS_INFORMATION piProcInfo; saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; // Create a pipe for the child process's STDOUT. if ( ! CreatePipe( &hChildStd_OUT_Rd, &hChildStd_OUT_Wr, &saAttr, 0) ) throw "StdoutRd CreatePipe"; // Ensure the read handle to the pipe for STDOUT is not inherited. if ( ! SetHandleInformation( hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0) ) throw "Stdout SetHandleInformation"; // Create a pipe for the child process's STDIN. if (! CreatePipe( &hChildStd_IN_Rd, &hChildStd_IN_Wr, &saAttr, 0)) throw "Stdin CreatePipe"; // Ensure the write handle to the pipe for STDIN is not inherited. if ( ! SetHandleInformation( hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0) ) throw "Stdin SetHandleInformation"; //TCHAR szCmdline[] = TEXT( sName ); //PROCESS_INFORMATION pd.piProcInfo; STARTUPINFO siStartInfo; BOOL bSuccess = FALSE; // Set up members of the PROCESS_INFORMATION structure. ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) ); // Set up members of the STARTUPINFO structure. // This structure specifies the STDIN and STDOUT handles for redirection. ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) ); siStartInfo.cb = sizeof(STARTUPINFO); siStartInfo.hStdError = hChildStd_OUT_Wr; siStartInfo.hStdOutput = hChildStd_OUT_Wr; siStartInfo.hStdInput = hChildStd_IN_Rd; siStartInfo.dwFlags |= STARTF_USESTDHANDLES; Bu::String sCmd; for( char *const *arg = argv; *arg; arg++ ) { if( arg != argv ) sCmd += " "; sCmd += *arg; } // Create the child process. bSuccess = CreateProcessA( sName, //sName, //module name sCmd.getStr(), //(TCHAR *) sName, //szCmdline, // command line NULL, // process security attributes NULL, // primary thread security attributes TRUE, // handles are inherited CREATE_NO_WINDOW, // creation flags NULL, // use parent's environment NULL, // use parent's current directory &siStartInfo, // STARTUPINFO pointer &piProcInfo ); // receives PROCESS_INFORMATION // If an error occurs, exit the application. if ( ! bSuccess ) { throw Bu::ExceptionBase("Error spawning child process."); return; } else { // Close handles to the child process and its primary thread. // Some applications might keep these handles to monitor the status // of the child process, for example. //CloseHandle(pData->pd.piProcInfo.hProcess); CloseHandle(piProcInfo.hThread); pd.hProcess = piProcInfo.hProcess; // Close the ends we can't use CloseHandle( hChildStd_OUT_Wr ); CloseHandle( hChildStd_IN_Rd ); pd.hStdIn = hChildStd_IN_Wr; pd.hStdOut = hChildStd_OUT_Rd; } #else int iaStdIn[2]; int iaStdOut[2]; int iaStdErr[2]; pipe( iaStdIn ); if( eFlags & StdOut ) { pipe( iaStdOut ); pd.iStdOut = iaStdOut[0]; bStdOutEos = false; } if( eFlags & StdErr ) { pipe( iaStdErr ); pd.iStdErr = iaStdErr[0]; bStdErrEos = false; } pd.iStdIn = iaStdIn[1]; // fcntl( pd.iStdOut, F_SETFL, fcntl( pd.iStdOut, F_GETFL, 0 )|O_NONBLOCK ); pd.iPid = fork(); if( pd.iPid == 0 ) { ::close( iaStdIn[1] ); dup2( iaStdIn[0], 0 ); if( eFlags & StdOut ) { ::close( iaStdOut[0] ); dup2( iaStdOut[1], 1 ); } if( eFlags & StdErr ) { ::close( iaStdErr[0] ); dup2( iaStdErr[1], 2 ); } if( (opt.eFlags&Options::SetGid) ) { setgid( opt.iGid ); } if( (opt.eFlags&Options::SetUid) ) { setuid( opt.iUid ); } execvp( sName, argv ); throw Bu::ExceptionBase("Hey, execvp failed!"); } ::close( iaStdIn[0] ); if( eFlags & StdOut ) ::close( iaStdOut[1] ); if( eFlags & StdErr ) ::close( iaStdErr[1] ); #endif } void Bu::Process::close() { #ifdef WIN32 if( pd.hStdIn ) { CloseHandle( pd.hStdIn ); CloseHandle( pd.hStdOut ); pd.hStdIn = pd.hStdOut = pd.hStdErr = NULL; if( !TerminateProcess(pd.hProcess, 1) ) { throw Bu::ExceptionBase("Error closing process."); } GetExitCodeProcess( pd.hProcess, (PDWORD)&iProcStatus ); CloseHandle( pd.hProcess ); pd.hProcess = NULL; } #else if( pd.iPid ) { if( pd.iStdIn > -1 ) ::close( pd.iStdIn ); if( pd.iStdOut > -1 ) ::close( pd.iStdOut ); if( pd.iStdErr > -1 ) ::close( pd.iStdErr ); waitpid( pd.iPid, &iProcStatus, 0 ); pd.iPid = 0; } #endif } void Bu::Process::closeStdIn() { #ifdef WIN32 CloseHandle( pd.hStdIn ); pd.hStdIn = NULL; #else ::close( pd.iStdIn ); pd.iStdIn = -1; #endif } void Bu::Process::closeStdOut() { #ifdef WIN32 CloseHandle( pd.hStdOut ); pd.hStdOut = NULL; #else ::close( pd.iStdOut ); pd.iStdOut = -1; #endif } Bu::size Bu::Process::read( void *pBuf, Bu::size nBytes ) { #ifdef WIN32 DWORD dwRead; DWORD lExitCode; BOOL bSuccess = FALSE; DWORD dwLen = (DWORD) nBytes; bSuccess = ReadFile( pd.hStdOut, (char *) pBuf, dwLen, &dwRead, NULL); // if( dwRead < dwLen ) { bSuccess = GetExitCodeProcess( pd.hProcess, &lExitCode ); if( lExitCode != STILL_ACTIVE ) { bStdOutEos = true; } } return (int32_t) dwRead; #else if( bStdOutEos ) return 0; fd_set rfds; FD_ZERO( &rfds ); FD_SET( pd.iStdOut, &rfds ); struct timeval tv = {0, 0}; if( ::bu_select( pd.iStdOut+1, &rfds, NULL, NULL, &tv ) < 0 ) throw Bu::ExceptionBase( strerror( errno ) ); if( FD_ISSET( pd.iStdOut, &rfds ) || bBlocking ) { Bu::size nRead = TEMP_FAILURE_RETRY( ::read( pd.iStdOut, pBuf, nBytes ) ); if( nRead == 0 ) { bStdOutEos = true; checkClose(); return 0; } if( nRead < 0 ) { if( errno == EAGAIN ) return 0; throw Bu::ExceptionBase( strerror( errno ) ); } return nRead; } return 0; #endif } Bu::size Bu::Process::readErr( void *pBuf, Bu::size nBytes ) { #ifdef WIN32 if( !pd.hStdErr ) return 0; DWORD dwRead; BOOL bSuccess = FALSE; DWORD dwLen = (DWORD) nBytes; bSuccess = ReadFile( pd.hStdErr, (char *) pBuf, dwLen, &dwRead, NULL); return (int32_t) dwRead; #else if( bStdErrEos ) return 0; fd_set rfds; FD_ZERO( &rfds ); FD_SET( pd.iStdErr, &rfds ); struct timeval tv = {0, 0}; if( ::bu_select( pd.iStdErr+1, &rfds, NULL, NULL, &tv ) < 0 ) throw Bu::ExceptionBase( strerror( errno ) ); if( FD_ISSET( pd.iStdErr, &rfds ) || bBlocking ) { Bu::size nRead = TEMP_FAILURE_RETRY( ::read( pd.iStdErr, pBuf, nBytes ) ); if( nRead == 0 ) { bStdErrEos = true; checkClose(); return 0; } if( nRead < 0 ) { if( errno == EAGAIN ) return 0; throw Bu::ExceptionBase( strerror( errno ) ); } return nRead; } return 0; #endif } Bu::size Bu::Process::write( const void *pBuf, Bu::size nBytes ) { #ifdef WIN32 DWORD dwWritten; BOOL bSuccess = FALSE; DWORD dwLen = (DWORD) nBytes; bSuccess = WriteFile( pd.hStdIn, (const char *) pBuf, dwLen, &dwWritten, NULL ); FlushFileBuffers( pd.hStdIn ); return (int32_t) dwWritten; #else return TEMP_FAILURE_RETRY( ::write( pd.iStdIn, pBuf, nBytes ) ); #endif } Bu::size Bu::Process::tell() { return 0; } void Bu::Process::seek( Bu::size ) { } void Bu::Process::setPos( Bu::size ) { } void Bu::Process::setPosEnd( Bu::size ) { } bool Bu::Process::isEos() { #ifdef WIN32 return (pd.hProcess == NULL); #else return (pd.iPid == 0); #endif } bool Bu::Process::isOpen() { return !isEos(); } void Bu::Process::flush() { } bool Bu::Process::canRead() { return true; } bool Bu::Process::canWrite() { return true; } bool Bu::Process::isReadable() { return true; } bool Bu::Process::isWritable() { return true; } bool Bu::Process::isSeekable() { return false; } bool Bu::Process::isBlocking() { return true; } void Bu::Process::setBlocking( bool bBlocking ) { #ifdef WIN32 #else if( bBlocking ) { if( !bStdOutEos ) fcntl( pd.iStdOut, F_SETFL, fcntl(pd.iStdOut,F_GETFL,0 )&(~O_NONBLOCK) ); if( !bStdErrEos ) fcntl( pd.iStdErr, F_SETFL, fcntl(pd.iStdErr,F_GETFL,0 )&(~O_NONBLOCK) ); } else { if( !bStdOutEos ) fcntl( pd.iStdOut, F_SETFL, fcntl( pd.iStdOut, F_GETFL, 0 )|O_NONBLOCK ); if( !bStdErrEos ) fcntl( pd.iStdErr, F_SETFL, fcntl( pd.iStdErr, F_GETFL, 0 )|O_NONBLOCK ); } this->bBlocking = bBlocking; #endif } void Bu::Process::setSize( Bu::size ) { } Bu::size Bu::Process::getBlockSize() const { return 0; } Bu::size Bu::Process::getSize() const { return 0; } Bu::String Bu::Process::getLocation() const { return ""; } void Bu::Process::select( bool &bStdOut, bool &bStdErr ) { #ifdef WIN32 #else fd_set rfds; FD_ZERO( &rfds ); if( !bStdOutEos ) FD_SET( pd.iStdOut, &rfds ); if( !bStdErrEos ) FD_SET( pd.iStdErr, &rfds ); if( ::bu_select( pd.iStdErr+1, &rfds, NULL, NULL, NULL ) < 0 ) throw Bu::ExceptionBase( strerror( errno ) ); if( FD_ISSET( pd.iStdOut, &rfds ) ) bStdOut = true; else bStdOut = false; if( FD_ISSET( pd.iStdErr, &rfds ) ) bStdErr = true; else bStdErr = false; #endif } bool Bu::Process::isRunning() { #ifdef WIN32 DWORD lExitCode; GetExitCodeProcess( pd.hProcess, &lExitCode ); if( lExitCode != STILL_ACTIVE ) checkClose(); #else if( waitpid( pd.iPid, NULL, WNOHANG ) == pd.iPid ) checkClose(); #endif return isOpen(); } void Bu::Process::ignoreStdErr() { #ifdef WIN32 #else if( pd.iStdErr == -1 ) return; ::close( pd.iStdErr ); pd.iStdErr = -1; bStdErrEos = true; #endif } pid_t Bu::Process::getPid() { #ifdef WIN32 return 0; #else return pd.iPid; #endif } bool Bu::Process::childExited() { #ifdef WIN32 return pd.hProcess != NULL; #else return WIFEXITED( iProcStatus ); #endif } int Bu::Process::childExitStatus() { #ifdef WIN32 return iProcStatus; #else return WEXITSTATUS( iProcStatus ); #endif } bool Bu::Process::childSignaled() { #ifdef WIN32 return false; #else return WIFSIGNALED( iProcStatus ); #endif } int Bu::Process::childSignal() { #ifdef WIN32 return 0; #else return WTERMSIG( iProcStatus ); #endif } bool Bu::Process::childCoreDumped() { #ifdef WIN32 return false; #else return WCOREDUMP( iProcStatus ); #endif } void Bu::Process::checkClose() { if( bStdOutEos && bStdErrEos ) { close(); } }