42 namespace NewProcessWindowsImp
48 HANDLE hread()
const ;
49 HANDLE hwrite()
const ;
50 static std::size_t read( HANDLE read ,
char * buffer , std::size_t buffer_size ) noexcept ;
53 static void create( HANDLE & read , HANDLE & write ) ;
54 static void uninherited( HANDLE h ) ;
58 #if GCONFIG_HAVE_WINDOWS_STARTUP_INFO_EX
61 G_EXCEPTION( Error ,
tx(
"AttributeList error") )
62 using pointer_type = LPPROC_THREAD_ATTRIBUTE_LIST ;
63 explicit AttributeList( const
std::array<HANDLE,4U> & ) ;
67 void cleanup() noexcept ;
68 G::Buffer<
char> m_buffer ;
69 std::array<HANDLE,4U> m_handles ;
70 pointer_type m_ptr {NULL} ;
75 using pointer_type =
void* ;
76 explicit AttributeList(
const std::array<HANDLE,4U> & ) {}
77 pointer_type ptr() {
return nullptr ; }
82 nowide::STARTUPINFO_REAL_type m_startup_info ;
83 nowide::STARTUPINFO_BASE_type * m_ptr ;
85 StartupInfo( AttributeList & attribute_list , HANDLE hstdin , HANDLE hstdout , HANDLE hstderr )
87 #if GCONFIG_HAVE_WINDOWS_STARTUP_INFO_EX
88 nowide::STARTUPINFO_REAL_type zero {} ;
89 m_startup_info = zero ;
90 m_startup_info.StartupInfo.cb =
sizeof(m_startup_info) ;
91 m_startup_info.StartupInfo.dwFlags = STARTF_USESTDHANDLES ;
92 m_startup_info.StartupInfo.hStdInput = hstdin ;
93 m_startup_info.StartupInfo.hStdOutput = hstdout ;
94 m_startup_info.StartupInfo.hStdError = hstderr ;
95 m_startup_info.lpAttributeList = attribute_list.ptr() ;
96 m_ptr =
reinterpret_cast<nowide::STARTUPINFO_BASE_type*
>(&m_startup_info) ;
97 m_flags = CREATE_NO_WINDOW | nowide::STARTUPINFO_flags ;
99 GDEF_IGNORE_PARAMS( attribute_list ) ;
100 nowide::STARTUPINFO_BASE_type zero {} ;
101 m_startup_info = zero ;
102 m_startup_info.cb =
sizeof(m_startup_info) ;
103 m_startup_info.dwFlags = STARTF_USESTDHANDLES ;
104 m_startup_info.hStdInput = hstdin ;
105 m_startup_info.hStdOutput = hstdout ;
106 m_startup_info.hStdError = hstderr ;
107 m_ptr = &m_startup_info ;
108 m_flags = CREATE_NO_WINDOW | nowide::STARTUPINFO_flags ;
112 HANDLE fdhandle(
int fd ) ;
113 void closeHandle( HANDLE h )
noexcept
121class G::NewProcessImp
124 using Pipe = NewProcessWindowsImp::Pipe ;
125 using AttributeList = NewProcessWindowsImp::AttributeList ;
126 using StartupInfo = NewProcessWindowsImp::StartupInfo ;
127 using Fd = NewProcess::Fd ;
129 NewProcessImp(
const Path & ,
const StringArray & ,
const NewProcess::Config & ) ;
135 NewProcessWaitable & waitable() noexcept ;
139 void kill() noexcept ;
142 int id() const noexcept ;
145 static
bool valid( HANDLE h ) noexcept ;
149 NewProcessImp( const NewProcessImp & ) = delete ;
150 NewProcessImp( NewProcessImp && ) = delete ;
151 NewProcessImp & operator=( const NewProcessImp & ) = delete ;
152 NewProcessImp & operator=( NewProcessImp && ) = delete ;
156 static
std::pair<HANDLE,DWORD> createProcessImp( const
std::
string & exe ,
157 const
std::
string & command_line , const Environment & ,
158 HANDLE hpipe , HANDLE keep_handle_1 , HANDLE keep_handle_2 ,
159 Fd fd_stdin , Fd fd_stdout , Fd fd_stderr ,
bool with_cd , const Path & cd ) ;
160 static
void dequote(
std::
string & ) ;
161 static
std::
string withQuotes( const
std::
string & ) ;
162 static
bool isSpaced( const
std::
string & ) ;
163 static
bool isSimplyQuoted( const
std::
string & ) ;
164 static
std::
string windowsPath() ;
165 static
std::
string cscript() ;
166 static
std::
string powershell() ;
169 NewProcess::Config m_config ;
174 NewProcessWaitable m_waitable ;
178G::NewProcess::NewProcess( const Path & exe , const
StringArray & args , const Config & config ) :
179 m_imp(
std::make_unique<NewProcessImp>(exe,args,config))
188 return m_imp->waitable() ;
196bool G::NewProcessImp::valid( HANDLE h )
noexcept
198 return h != HNULL && h != INVALID_HANDLE_VALUE ;
206 G::threading::yield() ;
207 SleepEx( 0 , FALSE ) ;
213G::NewProcessImp::NewProcessImp(
const Path & exe ,
const StringArray & args ,
const NewProcess::Config & config ) :
217 m_waitable(HNULL,HNULL,0)
219 G_DEBUG(
"G::NewProcessImp::ctor: exe=[" << exe <<
"] args=[" << Str::join(
"],[",args) <<
"]" ) ;
221 bool one_pipe = config.stdout == Fd::pipe() || config.stderr == Fd::pipe() ;
222 bool stdin_ok = config.stdin.m_null || config.stdin.m_fd >= 0 ;
223 if( !one_pipe || !stdin_ok )
224 throw NewProcess::Error(
"invalid parameters" ) ;
226 auto command_line_pair = commandLine( exe.str() , args ) ;
228 std::pair<HANDLE,DWORD> pair = createProcessImp( command_line_pair.first , command_line_pair.second ,
229 config.env , m_pipe.hwrite() , config.keep_handle_1 , config.keep_handle_2 ,
230 config.stdin , config.stdout , config.stderr , !config.cd.empty() , config.cd ) ;
232 if( !valid(pair.first) )
234 DWORD e = pair.second ;
236 if( m_config.exec_error_format_fn )
238 s = (config.exec_error_format_fn)(config.exec_error_format,e) ;
240 else if( !m_config.exec_error_format.empty() )
242 s = m_config.exec_error_format ;
248 s =
"error " + std::to_string(e) ;
250 throw NewProcess::CreateProcessError( s ) ;
253 m_hprocess = pair.first ;
254 m_pid = pair.second ;
257 m_waitable.assign( m_hprocess , m_pipe.hread() , 0 ) ;
260void G::NewProcessImp::kill() noexcept
262 if( !m_killed && valid(m_hprocess) )
263 TerminateProcess( m_hprocess , 127 ) ;
267G::NewProcessImp::~NewProcessImp()
269 namespace imp = NewProcessWindowsImp ;
270 imp::closeHandle( m_hprocess ) ;
278int G::NewProcessImp::id() const noexcept
280 return static_cast<int>(m_pid) ;
283std::pair<HANDLE,DWORD> G::NewProcessImp::createProcessImp(
const std::string & exe ,
284 const std::string & command_line ,
const Environment & env ,
285 HANDLE hpipe , HANDLE keep_handle_1 , HANDLE keep_handle_2 ,
286 Fd fd_stdin , Fd fd_stdout , Fd fd_stderr ,
bool with_cd ,
const Path & cd_path )
288 namespace imp = NewProcessWindowsImp ;
289 G_DEBUG(
"G::NewProcessImp::createProcessImp: exe=[" << exe <<
"] command-line=[" << command_line <<
"]" ) ;
291 HANDLE hstdin = INVALID_HANDLE_VALUE ;
292 if( fd_stdin.m_fd >= 0 )
293 hstdin = imp::fdhandle( fd_stdin.m_fd ) ;
295 HANDLE hstdout = INVALID_HANDLE_VALUE ;
296 if( fd_stdout == Fd::pipe() )
298 else if( fd_stdout.m_fd >= 0 )
299 hstdout = imp::fdhandle( fd_stdout.m_fd ) ;
301 HANDLE hstderr = INVALID_HANDLE_VALUE ;
302 if( fd_stderr == Fd::pipe() )
304 else if( fd_stderr.m_fd >= 0 )
305 hstderr = imp::fdhandle( fd_stderr.m_fd ) ;
308 imp::AttributeList attribute_list({ hstdin , hpipe , keep_handle_1 , keep_handle_2 }) ;
309 imp::StartupInfo startup_info( attribute_list , hstdin , hstdout , hstderr ) ;
311 std::string env_char_block = env.block() ;
314 PROCESS_INFORMATION info {} ;
317 BOOL rc = nowide::createProcess( exe , command_line ,
318 env.empty() ?
nullptr : env_char_block.data() ,
319 env.empty() ?
nullptr : env_wchar_block.data() ,
320 with_cd ? &cd_path :
nullptr ,
321 startup_info.m_flags , startup_info.m_ptr ,
324 if( rc == 0 || !valid(info.hProcess) )
327 G_DEBUG(
"G::NewProcessImp::createProcessImp: error=" << e ) ;
328 imp::closeHandle( info.hThread ) ;
329 return { info.hProcess , e } ;
333 imp::closeHandle( info.hThread ) ;
334 G_DEBUG(
"G::NewProcessImp::createProcessImp: process-id=" << info.dwProcessId ) ;
335 G_DEBUG(
"G::NewProcessImp::createProcessImp: thread-id=" << info.dwThreadId ) ;
336 return { info.hProcess , info.dwProcessId } ;
340std::pair<std::string,std::string> G::NewProcessImp::commandLine( std::string exe ,
StringArray args )
351 if( isSimplyQuoted(exe) )
354 for(
auto & arg : args )
357 std::string type = Str::lower(
G::Path(exe).extension() ) ;
358 if( type ==
"exe" || type ==
"bat" )
364 else if( type ==
"ps1" )
366 args.insert( args.begin() , exe ) ;
367 args.insert( args.begin() ,
"-File" ) ;
368 args.insert( args.begin() ,
"-NoLogo" ) ;
373 args.insert( args.begin() , exe ) ;
374 args.insert( args.begin() ,
"//B" ) ;
375 args.insert( args.begin() ,
"//nologo" ) ;
379 std::string command_line = isSpaced(exe) ? withQuotes(exe) : exe ;
380 for(
auto & arg : args )
382 if( ( arg.empty() || isSpaced(arg) ) && isSpaced(exe) && type ==
"bat" )
384 G_WARNING_ONCE(
"G::NewProcessImp::commandLine: batch file path contains a space so arguments cannot be quoted" ) ;
385 command_line.append(1U,
' ').append(arg) ;
387 else if( arg.empty() || isSpaced(arg) )
389 command_line.append(1U,
' ').append(withQuotes(arg)) ;
393 command_line.append(1U,
' ').append(arg) ;
396 return { exe , command_line } ;
399void G::NewProcessImp::dequote( std::string & s )
401 if( isSimplyQuoted(s) )
403 s = s.substr( 1U , s.length()-2U ) ;
405 else if( s.find(
'\"' ) != std::string::npos )
408 G_WARNING_ONCE(
"G::NewProcessImp::dequote: quotes removed when building command-line" ) ;
412bool G::NewProcessImp::isSimplyQuoted(
const std::string & s )
414 static constexpr char q =
'\"' ;
416 s.length() > 1U && s.at(0U) == q && s.at(s.length()-1U) == q &&
417 s.find(q,1U) == (s.length()-1U) ;
420bool G::NewProcessImp::isSpaced(
const std::string & s )
422 return s.find(
' ') != std::string::npos ;
425std::string G::NewProcessImp::withQuotes(
const std::string & s )
427 return std::string(1U,
'\"').append(s).append(1U,
'\"') ;
430std::string G::NewProcessImp::windowsPath()
432 std::string result = nowide::windowsPath() ;
434 throw NewProcess::SystemError(
"GetWindowsDirectory failed" ) ;
438std::string G::NewProcessImp::cscript()
440 return windowsPath().append(
"\\system32\\cscript.exe") ;
443std::string G::NewProcessImp::powershell()
445 return windowsPath().append(
"\\System32\\WindowsPowerShell\\v1.0\\powershell.exe") ;
450#if GCONFIG_HAVE_WINDOWS_STARTUP_INFO_EX
451G::NewProcessWindowsImp::AttributeList::AttributeList(
const std::array<HANDLE,4U> & handles_in ) :
452 m_handles(handles_in)
454 auto end = std::partition( m_handles.begin() , m_handles.end() ,
455 [](HANDLE h){return h!=0 && h!=INVALID_HANDLE_VALUE;} ) ;
456 std::size_t handles_size = std::distance( m_handles.begin() , end ) ;
459 SIZE_T buffer_size = 0 ;
460 InitializeProcThreadAttributeList( NULL , 1 , 0 , &buffer_size ) ;
461 if( buffer_size == 0 || buffer_size > 100000 )
463 m_buffer.resize( buffer_size ) ;
465 auto ptr =
reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST
>(m_buffer.data()) ;
466 BOOL ok = InitializeProcThreadAttributeList( ptr , 1 , 0 , &buffer_size ) ;
467 if( !ok || buffer_size != m_buffer.size() )
470 ok = UpdateProcThreadAttribute( ptr , 0 , PROC_THREAD_ATTRIBUTE_HANDLE_LIST ,
471 m_handles.data() , handles_size *
sizeof(HANDLE) , NULL , NULL ) ;
481G::NewProcessWindowsImp::AttributeList::~AttributeList()
486void G::NewProcessWindowsImp::AttributeList::cleanup() noexcept
489 DeleteProcThreadAttributeList( m_ptr ) ;
493G::NewProcessWindowsImp::AttributeList::pointer_type G::NewProcessWindowsImp::AttributeList::ptr()
501G::NewProcessWindowsImp::Pipe::Pipe() :
505 create( m_read , m_write ) ;
506 uninherited( m_read ) ;
509G::NewProcessWindowsImp::Pipe::~Pipe()
511 closeHandle( m_read ) ;
512 closeHandle( m_write ) ;
515void G::NewProcessWindowsImp::Pipe::create( HANDLE & h_read , HANDLE & h_write )
517 SECURITY_ATTRIBUTES attributes {} ;
518 attributes.nLength =
sizeof(attributes) ;
519 attributes.lpSecurityDescriptor = nullptr ;
520 attributes.bInheritHandle = TRUE ;
524 DWORD buffer_size_hint = 0 ;
525 BOOL rc = CreatePipe( &h_read , &h_write , &attributes , buffer_size_hint ) ;
528 DWORD error = GetLastError() ;
529 G_ERROR(
"G::NewProcessWindowsImp::Pipe::create: pipe error: create: " << error ) ;
530 throw NewProcess::PipeError(
"create" ) ;
534void G::NewProcessWindowsImp::Pipe::uninherited( HANDLE h )
536 if( ! SetHandleInformation( h , HANDLE_FLAG_INHERIT , 0 ) )
538 DWORD error = GetLastError() ;
540 G_ERROR(
"G::NewProcessWindowsImp::Pipe::uninherited: uninherited error " << error ) ;
541 throw NewProcess::PipeError(
"uninherited" ) ;
545HANDLE G::NewProcessWindowsImp::Pipe::hwrite()
const
550HANDLE G::NewProcessWindowsImp::Pipe::hread()
const
555void G::NewProcessWindowsImp::Pipe::close()
557 closeHandle( m_write ) ;
561std::size_t G::NewProcessWindowsImp::Pipe::read( HANDLE hread ,
char * buffer , std::size_t buffer_size_in )
noexcept
564 if( hread == HNULL )
return 0U ;
565 DWORD buffer_size =
static_cast<DWORD
>(buffer_size_in) ;
567 BOOL ok = ReadFile( hread , buffer , buffer_size , &nread ,
nullptr ) ;
569 nread = ok ? std::min( nread , buffer_size ) : DWORD(0) ;
570 return static_cast<std::size_t
>(nread) ;
582 m_test_mode(
G::Test::
enabled(
"waitpid-slow"))
588 m_hprocess(hprocess),
600 m_buffer.resize( 1024U * 4U ) ;
602 m_hprocess = hprocess ;
615 p.set_value( std::make_pair(get(),output()) ) ;
619 try { p.set_exception( std::current_exception() ) ; }
catch(...) {}
628 std::array<char,64U> discard_buffer {} ;
629 char * discard = discard_buffer.data() ;
630 std::size_t discard_size = discard_buffer.size() ;
631 char * read_p = m_buffer.data() ;
632 std::size_t space = m_buffer.size() ;
637 if( NewProcessImp::valid(m_hprocess) )
638 handles[nhandles++] = m_hprocess ;
639 if( m_hpipe != HNULL )
640 handles[nhandles++] = m_hpipe ;
645 DWORD rc = WaitForMultipleObjects( nhandles , handles , FALSE , INFINITE ) ;
646 HANDLE h = rc == WAIT_OBJECT_0 ? handles[0] : (rc==(WAIT_OBJECT_0+1)?handles[1]:HNULL) ;
647 if( h == m_hprocess && m_hprocess )
649 DWORD exit_code = 127 ;
650 GetExitCodeProcess( m_hprocess , &exit_code ) ;
651 m_status =
static_cast<int>(exit_code) ;
654 else if( h == m_hpipe && m_hpipe )
656 using Pipe = NewProcessWindowsImp::Pipe ;
657 std::size_t nread = Pipe::read( m_hpipe , space?read_p:discard , space?space:discard_size ) ;
658 if( space && nread <= space )
662 m_data_size += nread ;
681 throw NewProcess::WaitError() ;
687 return m_error ? ec : m_status ;
692 return m_buffer.empty() ? std::string() :
std::string(m_buffer.data(),m_data_size) ;
697HANDLE G::NewProcessWindowsImp::fdhandle(
int fd )
701 return INVALID_HANDLE_VALUE ;
702 intptr_t h = _get_osfhandle( fd ) ;
703 if( h == -1 || h == -2 )
704 return INVALID_HANDLE_VALUE ;
705 return reinterpret_cast<HANDLE
>(h) ;
static std::wstring widen(std::string_view)
Widens from UTF-8 to UTF-16/UCS-4 wstring.
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...
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().
static unsigned int replaceAll(std::string &s, std::string_view from, std::string_view to)
Does a global replace on string 's', replacing all occurrences of sub-string 'from' with 'to'.
A static interface for enabling test features at run-time.
Contains inline functions that convert to and from UTF-8 strings in order to call wide-character "W()...
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) noexcept
A briefer alternative to G::gettext_noop().