40 namespace NewProcessWindowsImp
48class G::NewProcessWindowsImp::Pipe
53 HANDLE hread()
const ;
54 HANDLE hwrite()
const ;
55 static std::size_t read( HANDLE read ,
char * buffer , std::size_t buffer_size ) noexcept ;
59 Pipe(
const Pipe & ) = delete ;
60 Pipe( Pipe && ) = delete ;
61 Pipe & operator=(
const Pipe & ) = delete ;
62 Pipe & operator=( Pipe && ) = delete ;
65 static void create( HANDLE & read , HANDLE & write ) ;
66 static void uninherited( HANDLE h ) ;
76 using Pipe = NewProcessWindowsImp::Pipe ;
77 using AttributeList = NewProcessWindowsImp::AttributeList ;
78 using StartupInfo = NewProcessWindowsImp::StartupInfo ;
79 using Fd = NewProcess::Fd ;
81 NewProcessImp(
const Path & ,
const StringArray & ,
const NewProcess::Config & ) ;
87 NewProcessWaitable & waitable() noexcept ;
91 void kill() noexcept ;
94 int id() const noexcept ;
97 static
bool valid( HANDLE h ) noexcept ;
101 NewProcessImp( const NewProcessImp & ) = delete ;
102 NewProcessImp( NewProcessImp && ) = delete ;
103 NewProcessImp & operator=( const NewProcessImp & ) = delete ;
104 NewProcessImp & operator=( NewProcessImp && ) = delete ;
108 static
std::pair<HANDLE,DWORD> createProcess( const
std::
string & exe , const
std::
string & command_line ,
109 const Environment & , HANDLE hpipe , Fd fd_stdout , Fd fd_stderr , const
char * cd ) ;
110 static
void dequote(
std::
string & ) ;
111 static
std::
string withQuotes( const
std::
string & ) ;
112 static
bool isSpaced( const
std::
string & ) ;
113 static
bool isSimplyQuoted( const
std::
string & ) ;
114 static
std::
string windowsPath() ;
115 static
std::
string cscript() ;
122 NewProcessWaitable m_waitable ;
125#if GCONFIG_HAVE_WINDOWS_STARTUP_INFO_EX
126class G::NewProcessWindowsImp::AttributeList
129 G_EXCEPTION( Error ,
tx(
"AttributeList error") ) ;
130 using pointer_type = LPPROC_THREAD_ATTRIBUTE_LIST ;
131 explicit AttributeList( HANDLE ) ;
136 AttributeList(
const AttributeList & ) = delete ;
137 AttributeList( AttributeList && ) = delete ;
138 AttributeList & operator=(
const AttributeList & ) = delete ;
139 AttributeList & operator=( AttributeList && ) = delete ;
145 void cleanup() noexcept ;
148 G::Buffer<
char> m_buffer ;
149 HANDLE m_handle {NULL} ;
150 pointer_type m_ptr {NULL} ;
153class G::NewProcessWindowsImp::AttributeList
156 using pointer_type =
void* ;
157 explicit AttributeList( HANDLE ) {}
158 pointer_type ptr() {
return nullptr ; }
162class G::NewProcessWindowsImp::StartupInfo
165 StartupInfo( AttributeList & , HANDLE hstdout , HANDLE hstdin ) ;
166 LPSTARTUPINFOA ptr() ;
167 DWORD flags()
const ;
170 #if GCONFIG_HAVE_WINDOWS_STARTUP_INFO_EX
171 STARTUPINFOEXA m_startup_info ;
173 STARTUPINFOA m_startup_info ;
175 LPSTARTUPINFOA m_ptr ;
182 m_imp(
std::make_unique<NewProcessImp>(exe,args,config))
191 return m_imp->waitable() ;
199bool G::NewProcessImp::valid( HANDLE h )
noexcept
201 return h != HNULL && h != INVALID_HANDLE_VALUE ;
209 G::threading::yield() ;
210 SleepEx( 0 , FALSE ) ;
216G::NewProcessImp::NewProcessImp(
const Path & exe ,
const StringArray & args ,
const NewProcess::Config & config ) :
219 m_waitable(HNULL,HNULL,0)
221 G_DEBUG(
"G::NewProcessImp::ctor: exe=[" << exe <<
"] args=[" << Str::join(
"],[",args) <<
"]" ) ;
224 if( config.stdin != Fd::devnull() ||
225 ( config.stdout != Fd::devnull() && config.stdout != Fd::pipe() ) ||
226 ( config.stderr != Fd::devnull() && config.stderr != Fd::pipe() ) ||
227 ( config.stdout == Fd::pipe() && config.stderr == Fd::pipe() ) )
229 throw NewProcess::Error(
"invalid parameters" ) ;
232 auto command_line_pair = commandLine( exe.str() , args ) ;
233 std::pair<HANDLE,DWORD> pair = createProcess( command_line_pair.first , command_line_pair.second ,
234 config.env , m_pipe.hwrite() , config.stdout , config.stderr ,
235 config.cd.empty() ?
nullptr : config.cd.cstr() ) ;
237 m_hprocess = pair.first ;
238 m_pid = pair.second ;
241 m_waitable.assign( m_hprocess , m_pipe.hread() , 0 ) ;
244void G::NewProcessImp::kill() noexcept
246 if( !m_killed && valid(m_hprocess) )
247 TerminateProcess( m_hprocess , 127 ) ;
251G::NewProcessImp::~NewProcessImp()
253 if( m_hprocess != HNULL )
254 CloseHandle( m_hprocess ) ;
262int G::NewProcessImp::id() const noexcept
264 return static_cast<int>(m_pid) ;
267std::pair<HANDLE,DWORD> G::NewProcessImp::createProcess(
const std::string & exe ,
const std::string & command_line ,
268 const Environment & env , HANDLE hpipe , Fd fd_stdout , Fd fd_stderr ,
const char * cd )
270 G_DEBUG(
"G::NewProcessImp::createProcess: exe=[" << exe <<
"] command-line=[" << command_line <<
"]" ) ;
273 AttributeList attribute_list( hpipe ) ;
274 StartupInfo startup_info( attribute_list ,
275 fd_stdout == Fd::pipe() ? hpipe : INVALID_HANDLE_VALUE ,
276 fd_stderr == Fd::pipe() ? hpipe : INVALID_HANDLE_VALUE ) ;
278 BOOL inherit = TRUE ;
279 DWORD flags = startup_info.flags() ;
280 LPVOID envp = env.empty() ? nullptr :
static_cast<LPVOID
>(
const_cast<char*
>(env.ptr())) ;
282 SECURITY_ATTRIBUTES * process_attributes = nullptr ;
283 SECURITY_ATTRIBUTES * thread_attributes = nullptr ;
285 PROCESS_INFORMATION info {} ;
287 BOOL rc = CreateProcessA( exe.c_str() ,
288 const_cast<char*
>(command_line.c_str()) ,
289 process_attributes , thread_attributes , inherit ,
290 flags , envp , cwd , startup_info.ptr() , &info ) ;
292 if( rc == 0 || !valid(info.hProcess) )
294 DWORD e = GetLastError() ;
295 std::ostringstream ss ;
296 ss <<
"error " << e <<
": [" << exe <<
"] [" << command_line <<
"]" ;
297 throw NewProcess::CreateProcessError( ss.str() ) ;
300 G_DEBUG(
"G::NewProcessImp::createProcess: process-id=" << info.dwProcessId ) ;
301 G_DEBUG(
"G::NewProcessImp::createProcess: thread-id=" << info.dwThreadId ) ;
303 CloseHandle( info.hThread ) ;
304 return { info.hProcess , info.dwProcessId } ;
307std::pair<std::string,std::string> G::NewProcessImp::commandLine( std::string exe ,
StringArray args )
318 if( isSimplyQuoted(exe) )
321 for(
auto & arg : args )
324 std::string type = Str::lower(
G::Path(exe).extension() ) ;
325 if( type ==
"exe" || type ==
"bat" )
333 args.insert( args.begin() , exe ) ;
334 args.insert( args.begin() ,
"//B" ) ;
335 args.insert( args.begin() ,
"//nologo" ) ;
339 std::string command_line = isSpaced(exe) ? withQuotes(exe) : exe ;
340 for(
auto & arg : args )
342 if( ( arg.empty() || isSpaced(arg) ) && isSpaced(exe) && type ==
"bat" )
344 G_WARNING_ONCE(
"G::NetProcessImp::commandLine: batch file path contains a space so arguments cannot be quoted" ) ;
345 command_line.append(1U,
' ').append(arg) ;
347 else if( arg.empty() || isSpaced(arg) )
349 command_line.append(1U,
' ').append(withQuotes(arg)) ;
353 command_line.append(1U,
' ').append(arg) ;
356 return { exe , command_line } ;
359void G::NewProcessImp::dequote( std::string & s )
361 if( isSimplyQuoted(s) )
363 s = s.substr( 1U , s.length()-2U ) ;
365 else if( s.find(
'\"' ) != std::string::npos )
368 G_WARNING_ONCE(
"G::NewProcessImp::dequote: quotes removed when building command-line" ) ;
372bool G::NewProcessImp::isSimplyQuoted(
const std::string & s )
374 static constexpr char q =
'\"' ;
376 s.length() > 1U && s.at(0U) == q && s.at(s.length()-1U) == q &&
377 s.find(q,1U) == (s.length()-1U) ;
380bool G::NewProcessImp::isSpaced(
const std::string & s )
382 return s.find(
' ') != std::string::npos ;
385std::string G::NewProcessImp::withQuotes(
const std::string & s )
387 return std::string(1U,
'\"').append(s).append(1U,
'\"') ;
390std::string G::NewProcessImp::windowsPath()
392 std::vector<char> buffer( MAX_PATH+1 ) ;
393 buffer.at(0) =
'\0' ;
394 unsigned int n = ::GetWindowsDirectoryA( &buffer[0] , MAX_PATH ) ;
395 if( n == 0 || n > MAX_PATH )
396 throw NewProcess::SystemError(
"GetWindowsDirectoryA failed" ) ;
397 return std::string( &buffer[0] , n ) ;
400std::string G::NewProcessImp::cscript()
402 return windowsPath().append(
"\\system32\\cscript.exe") ;
407#if GCONFIG_HAVE_WINDOWS_STARTUP_INFO_EX
408G::NewProcessWindowsImp::AttributeList::AttributeList( HANDLE h )
415G::NewProcessWindowsImp::AttributeList::~AttributeList()
420void G::NewProcessWindowsImp::AttributeList::resize()
422 SIZE_T buffer_size = 0 ;
423 InitializeProcThreadAttributeList( NULL , 1 , 0 , &buffer_size ) ;
424 if( buffer_size == 0 || buffer_size > 100000 )
426 m_buffer.resize( buffer_size ) ;
429void G::NewProcessWindowsImp::AttributeList::init( DWORD attribute_count )
431 SIZE_T buffer_size = m_buffer.size() ;
432 auto ptr =
reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST
>(m_buffer.data()) ;
433 BOOL ok = InitializeProcThreadAttributeList( ptr , attribute_count , 0 , &buffer_size ) ;
437 G_ASSERT( m_buffer.size() >= buffer_size ) ;
440void G::NewProcessWindowsImp::AttributeList::add( HANDLE h )
445 BOOL ok = UpdateProcThreadAttribute( m_ptr , 0 ,
446 PROC_THREAD_ATTRIBUTE_HANDLE_LIST , &m_handle ,
sizeof(m_handle) , NULL , NULL ) ;
455void G::NewProcessWindowsImp::AttributeList::cleanup() noexcept
458 DeleteProcThreadAttributeList( m_ptr ) ;
462G::NewProcessWindowsImp::AttributeList::pointer_type G::NewProcessWindowsImp::AttributeList::ptr()
470G::NewProcessWindowsImp::StartupInfo::StartupInfo( AttributeList & attribute_list , HANDLE hstdout , HANDLE hstderr )
472 #if GCONFIG_HAVE_WINDOWS_STARTUP_INFO_EX
473 STARTUPINFOEXA zero {} ;
474 m_startup_info = zero ;
475 m_startup_info.StartupInfo.cb =
sizeof( STARTUPINFOEXA ) ;
476 m_startup_info.StartupInfo.dwFlags = STARTF_USESTDHANDLES ;
477 m_startup_info.StartupInfo.hStdInput = INVALID_HANDLE_VALUE ;
478 m_startup_info.StartupInfo.hStdOutput = hstdout ;
479 m_startup_info.StartupInfo.hStdError = hstderr ;
480 m_startup_info.lpAttributeList = attribute_list.ptr() ;
481 m_ptr =
reinterpret_cast<LPSTARTUPINFOA
>(&m_startup_info) ;
482 m_flags = CREATE_NO_WINDOW | EXTENDED_STARTUPINFO_PRESENT ;
484 GDEF_IGNORE_PARAMS( attribute_list ) ;
485 STARTUPINFOA zero {} ;
486 m_startup_info = zero ;
487 m_startup_info.cb =
sizeof( STARTUPINFOA ) ;
488 m_startup_info.dwFlags = STARTF_USESTDHANDLES ;
489 m_startup_info.hStdInput = INVALID_HANDLE_VALUE ;
490 m_startup_info.hStdOutput = hstdout ;
491 m_startup_info.hStdError = hstderr ;
492 m_ptr = &m_startup_info ;
493 m_flags = CREATE_NO_WINDOW ;
497LPSTARTUPINFOA G::NewProcessWindowsImp::StartupInfo::ptr()
502DWORD G::NewProcessWindowsImp::StartupInfo::flags()
const
509G::NewProcessWindowsImp::Pipe::Pipe() :
513 create( m_read , m_write ) ;
514 uninherited( m_read ) ;
517G::NewProcessWindowsImp::Pipe::~Pipe()
519 if( m_read != HNULL ) CloseHandle( m_read ) ;
520 if( m_write != HNULL ) CloseHandle( m_write ) ;
523void G::NewProcessWindowsImp::Pipe::create( HANDLE & h_read , HANDLE & h_write )
525 SECURITY_ATTRIBUTES attributes {} ;
526 attributes.nLength =
sizeof(attributes) ;
527 attributes.lpSecurityDescriptor = nullptr ;
528 attributes.bInheritHandle = TRUE ;
532 DWORD buffer_size_hint = 0 ;
533 BOOL rc = CreatePipe( &h_read , &h_write , &attributes , buffer_size_hint ) ;
536 DWORD error = GetLastError() ;
537 G_ERROR(
"G::NewProcessWindowsImp::Pipe::create: pipe error: create: " << error ) ;
538 throw NewProcess::PipeError(
"create" ) ;
542void G::NewProcessWindowsImp::Pipe::uninherited( HANDLE h )
544 if( ! SetHandleInformation( h , HANDLE_FLAG_INHERIT , 0 ) )
546 DWORD error = GetLastError() ;
548 G_ERROR(
"G::NewProcessWindowsImp::Pipe::uninherited: uninherited error " << error ) ;
549 throw NewProcess::PipeError(
"uninherited" ) ;
553HANDLE G::NewProcessWindowsImp::Pipe::hwrite()
const
558HANDLE G::NewProcessWindowsImp::Pipe::hread()
const
563void G::NewProcessWindowsImp::Pipe::close()
565 if( m_write != HNULL )
566 CloseHandle( m_write ) ;
570std::size_t G::NewProcessWindowsImp::Pipe::read( HANDLE hread ,
char * buffer , std::size_t buffer_size_in )
noexcept
573 if( hread == HNULL )
return 0U ;
574 DWORD buffer_size =
static_cast<DWORD
>(buffer_size_in) ;
576 BOOL ok = ReadFile( hread , buffer , buffer_size , &nread ,
nullptr ) ;
578 nread = ok ? std::min( nread , buffer_size ) : DWORD(0) ;
579 return static_cast<std::size_t
>(nread) ;
591 m_test_mode(
G::Test::
enabled(
"waitpid-slow"))
597 m_hprocess(hprocess),
609 m_buffer.resize( 1024U ) ;
611 m_hprocess = hprocess ;
624 p.set_value( std::make_pair(get(),output()) ) ;
628 try { p.set_exception( std::current_exception() ) ; }
catch(...) {}
637 std::array<char,64U> discard_buffer {} ;
638 char * discard = &discard_buffer[0] ;
639 std::size_t discard_size = discard_buffer.size() ;
640 char * read_p = &m_buffer[0] ;
641 std::size_t space = m_buffer.size() ;
646 if( NewProcessImp::valid(m_hprocess) )
647 handles[nhandles++] = m_hprocess ;
648 if( m_hpipe != HNULL )
649 handles[nhandles++] = m_hpipe ;
654 DWORD rc = WaitForMultipleObjects( nhandles , handles , FALSE , INFINITE ) ;
655 HANDLE h = rc == WAIT_OBJECT_0 ? handles[0] : (rc==(WAIT_OBJECT_0+1)?handles[1]:HNULL) ;
656 if( h == m_hprocess && m_hprocess )
658 DWORD exit_code = 127 ;
659 GetExitCodeProcess( m_hprocess , &exit_code ) ;
660 m_status =
static_cast<int>(exit_code) ;
663 else if( h == m_hpipe && m_hpipe )
665 using Pipe = NewProcessWindowsImp::Pipe ;
666 std::size_t nread = Pipe::read( m_hpipe , space?read_p:discard , space?space:discard_size ) ;
667 if( space && nread <= space )
671 m_data_size += nread ;
690 throw NewProcess::WaitError() ;
696 return m_error ? ec : m_status ;
701 return m_buffer.size() ? std::string(&m_buffer[0],m_data_size) :
std::string() ;
Holds the parameters and future results of a waitpid() system call.
int get() const
Returns the result of the wait() as either the process exit code or as a thrown exception.
void waitp(std::promise< std::pair< int, std::string > >) noexcept
Calls wait() and then sets the given promise with the get() and output() values or an exception:
NewProcessWaitable()
Default constructor for an object where wait() does nothing and get() returns zero.
std::string output() const
Returns the first bit of child-process output.
NewProcessWaitable & wait()
Waits for the process identified by the constructor parameter to exit.
void assign(pid_t pid, int fd)
Reinitialises as if constructed with the given proces-id and file descriptor.
int id() const noexcept
Returns the process id.
void kill(bool yield=false) noexcept
Tries to kill the spawned process and optionally yield to a thread that might be waiting on it.
NewProcessWaitable & waitable() noexcept
Returns a reference to the Waitable sub-object so that the caller can wait for the child process to e...
NewProcess(const Path &exe, const G::StringArray &args, const Config &)
Constructor.
A Path object represents a file system path.
static void removeAll(std::string &, char)
Removes all occurrences of the character from the string. See also only().
A static interface for enabling test features at run-time.
bool enabled() noexcept
Returns true if pop code is built in.
std::vector< std::string > StringArray
A std::vector of std::strings.
constexpr const char * tx(const char *p)
A briefer alternative to G::gettext_noop().