E-MailRelay

gnewprocess_win32.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2024 Graeme Walker <graeme_walker@users.sourceforge.net>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16// ===
17///
18/// \file gnewprocess_win32.cpp
19///
20
21#include "gdef.h"
22#include "gnowide.h"
23#include "gnewprocess.h"
24#include "gexception.h"
25#include "gstr.h"
26#include "gpath.h"
27#include "gtest.h"
28#include "gbuffer.h"
29#include "gconvert.h"
30#include "glog.h"
31#include <sstream>
32#include <sys/types.h>
33#include <sys/stat.h>
34#include <process.h>
35#include <io.h>
36#include <algorithm>
37#include <utility>
38#include <array>
39
40 namespace G
41{
42 namespace NewProcessWindowsImp
43 {
44 struct Pipe
45 {
46 Pipe() ;
47 ~Pipe() ;
48 HANDLE hread() const ;
49 HANDLE hwrite() const ;
50 static std::size_t read( HANDLE read , char * buffer , std::size_t buffer_size ) noexcept ;
51 void close() ;
52 private:
53 static void create( HANDLE & read , HANDLE & write ) ;
54 static void uninherited( HANDLE h ) ;
55 HANDLE m_read ;
56 HANDLE m_write ;
57 } ;
58 #if GCONFIG_HAVE_WINDOWS_STARTUP_INFO_EX
59 struct AttributeList
60 {
61 G_EXCEPTION( Error , tx("AttributeList error") )
62 using pointer_type = LPPROC_THREAD_ATTRIBUTE_LIST ;
63 explicit AttributeList( const std::array<HANDLE,4U> & ) ;
64 ~AttributeList() ;
65 pointer_type ptr() ;
66 private:
67 void cleanup() noexcept ;
68 G::Buffer<char> m_buffer ;
69 std::array<HANDLE,4U> m_handles ;
70 pointer_type m_ptr {NULL} ;
71 } ;
72 #else
73 struct AttributeList
74 {
75 using pointer_type = void* ;
76 explicit AttributeList( const std::array<HANDLE,4U> & ) {}
77 pointer_type ptr() { return nullptr ; }
78 } ;
79 #endif
80 struct StartupInfo
81 {
82 nowide::STARTUPINFO_REAL_type m_startup_info ;
83 nowide::STARTUPINFO_BASE_type * m_ptr ;
84 DWORD m_flags ;
85 StartupInfo( AttributeList & attribute_list , HANDLE hstdin , HANDLE hstdout , HANDLE hstderr )
86 {
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 ;
98 #else
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 ;
109 #endif
110 }
111 } ;
112 HANDLE fdhandle( int fd ) ;
113 void closeHandle( HANDLE h ) noexcept
114 {
115 if( h != HNULL )
116 CloseHandle( h ) ;
117 }
118 }
119}
120
121 class G::NewProcessImp
122{
123 public:
124 using Pipe = NewProcessWindowsImp::Pipe ;
125 using AttributeList = NewProcessWindowsImp::AttributeList ;
126 using StartupInfo = NewProcessWindowsImp::StartupInfo ;
127 using Fd = NewProcess::Fd ;
128
129 NewProcessImp( const Path & , const StringArray & , const NewProcess::Config & ) ;
130 // Constructor. Spawns the new process.
131
132 ~NewProcessImp() ;
133 // Destructor. Kills the process if it is still running.
134
135 NewProcessWaitable & waitable() noexcept ;
136 // Returns a reference to the Waitable sub-object to allow
137 // the caller to wait for the process to finish.
138
139 void kill() noexcept ;
140 // Tries to kill the spawned process.
141
142 int id() const noexcept ;
143 // Returns the process id.
144
145 static bool valid( HANDLE h ) noexcept ;
146 // Returns true if a valid handle.
147
148public:
149 NewProcessImp( const NewProcessImp & ) = delete ;
150 NewProcessImp( NewProcessImp && ) = delete ;
151 NewProcessImp & operator=( const NewProcessImp & ) = delete ;
152 NewProcessImp & operator=( NewProcessImp && ) = delete ;
153
154private:
155 static std::pair<std::string,std::string> commandLine( std::string exe , StringArray args ) ;
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() ;
167
168private:
169 NewProcess::Config m_config ;
170 HANDLE m_hprocess ;
171 DWORD m_pid ;
172 bool m_killed ;
173 Pipe m_pipe ;
174 NewProcessWaitable m_waitable ;
175} ;
176
177
178 G::NewProcess::NewProcess( const Path & exe , const StringArray & args , const Config & config ) :
179 m_imp(std::make_unique<NewProcessImp>(exe,args,config))
180{
181}
182
183 G::NewProcess::~NewProcess()
184= default ;
185
186 G::NewProcessWaitable & G::NewProcess::waitable() noexcept
187{
188 return m_imp->waitable() ;
189}
190
191 int G::NewProcess::id() const noexcept
192{
193 return m_imp->id() ;
194}
195
196 bool G::NewProcessImp::valid( HANDLE h ) noexcept
197{
198 return h != HNULL && h != INVALID_HANDLE_VALUE ;
199}
200
201 void G::NewProcess::kill( bool yield ) noexcept
202{
203 m_imp->kill() ;
204 if( yield )
205 {
206 G::threading::yield() ;
207 SleepEx( 0 , FALSE ) ;
208 }
209}
210
211// ==
212
213G::NewProcessImp::NewProcessImp( const Path & exe , const StringArray & args , const NewProcess::Config & config ) :
214 m_config(config) ,
215 m_hprocess(0) ,
216 m_killed(false) ,
217 m_waitable(HNULL,HNULL,0)
218{
219 G_DEBUG( "G::NewProcessImp::ctor: exe=[" << exe << "] args=[" << Str::join("],[",args) << "]" ) ;
220
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" ) ;
225
226 auto command_line_pair = commandLine( exe.str() , args ) ;
227
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 ) ;
231
232 if( !valid(pair.first) )
233 {
234 DWORD e = pair.second ;
235 std::string s ;
236 if( m_config.exec_error_format_fn )
237 {
238 s = (config.exec_error_format_fn)(config.exec_error_format,e) ;
239 }
240 else if( !m_config.exec_error_format.empty() )
241 {
242 s = m_config.exec_error_format ;
243 G::Str::replaceAll( s , "__""errno""__" , std::to_string(e) ) ;
244 G::Str::replaceAll( s , "__""strerror""__" , Process::errorMessage(e) ) ;
245 }
246 else
247 {
248 s = "error " + std::to_string(e) ;
249 }
250 throw NewProcess::CreateProcessError( s ) ;
251 }
252
253 m_hprocess = pair.first ;
254 m_pid = pair.second ;
255
256 m_pipe.close() ; // close write end, now used by child process
257 m_waitable.assign( m_hprocess , m_pipe.hread() , 0 ) ;
258}
259
260 void G::NewProcessImp::kill() noexcept
261{
262 if( !m_killed && valid(m_hprocess) )
263 TerminateProcess( m_hprocess , 127 ) ;
264 m_killed = true ;
265}
266
267G::NewProcessImp::~NewProcessImp()
268{
269 namespace imp = NewProcessWindowsImp ;
270 imp::closeHandle( m_hprocess ) ;
271}
272
273 G::NewProcessWaitable & G::NewProcessImp::waitable() noexcept
274{
275 return m_waitable ;
276}
277
278 int G::NewProcessImp::id() const noexcept
279{
280 return static_cast<int>(m_pid) ;
281}
282
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 )
287{
288 namespace imp = NewProcessWindowsImp ;
289 G_DEBUG( "G::NewProcessImp::createProcessImp: exe=[" << exe << "] command-line=[" << command_line << "]" ) ;
290
291 HANDLE hstdin = INVALID_HANDLE_VALUE ;
292 if( fd_stdin.m_fd >= 0 )
293 hstdin = imp::fdhandle( fd_stdin.m_fd ) ;
294
295 HANDLE hstdout = INVALID_HANDLE_VALUE ;
296 if( fd_stdout == Fd::pipe() )
297 hstdout = hpipe ;
298 else if( fd_stdout.m_fd >= 0 )
299 hstdout = imp::fdhandle( fd_stdout.m_fd ) ;
300
301 HANDLE hstderr = INVALID_HANDLE_VALUE ;
302 if( fd_stderr == Fd::pipe() )
303 hstderr = hpipe ;
304 else if( fd_stderr.m_fd >= 0 )
305 hstderr = imp::fdhandle( fd_stderr.m_fd ) ;
306
307 // redirect stdout or stderr onto the read end of our pipe
308 imp::AttributeList attribute_list({ hstdin , hpipe , keep_handle_1 , keep_handle_2 }) ;
309 imp::StartupInfo startup_info( attribute_list , hstdin , hstdout , hstderr ) ;
310
311 std::string env_char_block = env.block() ;
312 std::wstring env_wchar_block = env.block( &G::Convert::widen ) ;
313
314 PROCESS_INFORMATION info {} ;
315 DWORD e = 0 ;
316
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 ,
322 &info ) ;
323
324 if( rc == 0 || !valid(info.hProcess) )
325 {
326 e = GetLastError() ;
327 G_DEBUG( "G::NewProcessImp::createProcessImp: error=" << e ) ;
328 imp::closeHandle( info.hThread ) ;
329 return { info.hProcess , e } ;
330 }
331 else
332 {
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 } ;
337 }
338}
339
340std::pair<std::string,std::string> G::NewProcessImp::commandLine( std::string exe , StringArray args )
341{
342 // there is no correct way to do this because every target program
343 // will parse its command-line differently -- quotes, spaces and
344 // empty arguments are best avoided
345
346 // in this implementation: all quotes are deleted(!) unless
347 // an exe; executable paths with a space are quoted; empty
348 // arguments and arguments with a space are quoted (unless
349 // a batch file that has been quoted)
350
351 if( isSimplyQuoted(exe) )
352 dequote( exe ) ;
353
354 for( auto & arg : args )
355 dequote( arg ) ;
356
357 std::string type = Str::lower( G::Path(exe).extension() ) ;
358 if( type == "exe" || type == "bat" )
359 {
360 // we can run CreateProcess() directly -- but note
361 // that CreateProcess() with a batch file runs
362 // "cmd.exe /c" internally
363 }
364 else if( type == "ps1" )
365 {
366 args.insert( args.begin() , exe ) ;
367 args.insert( args.begin() , "-File" ) ;
368 args.insert( args.begin() , "-NoLogo" ) ;
369 exe = powershell() ;
370 }
371 else
372 {
373 args.insert( args.begin() , exe ) ;
374 args.insert( args.begin() , "//B" ) ;
375 args.insert( args.begin() , "//nologo" ) ;
376 exe = cscript() ;
377 }
378
379 std::string command_line = isSpaced(exe) ? withQuotes(exe) : exe ;
380 for( auto & arg : args )
381 {
382 if( ( arg.empty() || isSpaced(arg) ) && isSpaced(exe) && type == "bat" )
383 {
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) ; // this fails >-: cmd /c "a b.bat" "c d"
386 }
387 else if( arg.empty() || isSpaced(arg) )
388 {
389 command_line.append(1U,' ').append(withQuotes(arg)) ;
390 }
391 else
392 {
393 command_line.append(1U,' ').append(arg) ;
394 }
395 }
396 return { exe , command_line } ;
397}
398
399 void G::NewProcessImp::dequote( std::string & s )
400{
401 if( isSimplyQuoted(s) )
402 {
403 s = s.substr( 1U , s.length()-2U ) ;
404 }
405 else if( s.find( '\"' ) != std::string::npos )
406 {
407 G::Str::removeAll( s , '\"' ) ;
408 G_WARNING_ONCE( "G::NewProcessImp::dequote: quotes removed when building command-line" ) ;
409 }
410}
411
412 bool G::NewProcessImp::isSimplyQuoted( const std::string & s )
413{
414 static constexpr char q = '\"' ;
415 return
416 s.length() > 1U && s.at(0U) == q && s.at(s.length()-1U) == q &&
417 s.find(q,1U) == (s.length()-1U) ;
418}
419
420 bool G::NewProcessImp::isSpaced( const std::string & s )
421{
422 return s.find(' ') != std::string::npos ;
423}
424
425std::string G::NewProcessImp::withQuotes( const std::string & s )
426{
427 return std::string(1U,'\"').append(s).append(1U,'\"') ;
428}
429
430std::string G::NewProcessImp::windowsPath()
431{
432 std::string result = nowide::windowsPath() ;
433 if( result.empty() )
434 throw NewProcess::SystemError( "GetWindowsDirectory failed" ) ;
435 return result ;
436}
437
438std::string G::NewProcessImp::cscript()
439{
440 return windowsPath().append("\\system32\\cscript.exe") ;
441}
442
443std::string G::NewProcessImp::powershell()
444{
445 return windowsPath().append("\\System32\\WindowsPowerShell\\v1.0\\powershell.exe") ;
446}
447
448// ==
449
450#if GCONFIG_HAVE_WINDOWS_STARTUP_INFO_EX
451G::NewProcessWindowsImp::AttributeList::AttributeList( const std::array<HANDLE,4U> & handles_in ) :
452 m_handles(handles_in)
453{
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 ) ;
457 if( handles_size )
458 {
459 SIZE_T buffer_size = 0 ;
460 InitializeProcThreadAttributeList( NULL , 1 , 0 , &buffer_size ) ;
461 if( buffer_size == 0 || buffer_size > 100000 )
462 throw Error() ;
463 m_buffer.resize( buffer_size ) ;
464
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() )
468 throw Error() ;
469
470 ok = UpdateProcThreadAttribute( ptr , 0 , PROC_THREAD_ATTRIBUTE_HANDLE_LIST ,
471 m_handles.data() , handles_size * sizeof(HANDLE) , NULL , NULL ) ;
472 if( !ok )
473 {
474 cleanup() ;
475 throw Error() ;
476 }
477 m_ptr = ptr ;
478 }
479}
480
481G::NewProcessWindowsImp::AttributeList::~AttributeList()
482{
483 cleanup() ;
484}
485
486 void G::NewProcessWindowsImp::AttributeList::cleanup() noexcept
487{
488 if( m_ptr )
489 DeleteProcThreadAttributeList( m_ptr ) ;
490 m_ptr = NULL ;
491}
492
493G::NewProcessWindowsImp::AttributeList::pointer_type G::NewProcessWindowsImp::AttributeList::ptr()
494{
495 return m_ptr ;
496}
497#endif
498
499// ==
500
501G::NewProcessWindowsImp::Pipe::Pipe() :
502 m_read(HNULL) ,
503 m_write(HNULL)
504{
505 create( m_read , m_write ) ;
506 uninherited( m_read ) ;
507}
508
509G::NewProcessWindowsImp::Pipe::~Pipe()
510{
511 closeHandle( m_read ) ;
512 closeHandle( m_write ) ;
513}
514
515 void G::NewProcessWindowsImp::Pipe::create( HANDLE & h_read , HANDLE & h_write )
516{
517 SECURITY_ATTRIBUTES attributes {} ;
518 attributes.nLength = sizeof(attributes) ;
519 attributes.lpSecurityDescriptor = nullptr ;
520 attributes.bInheritHandle = TRUE ;
521
522 h_read = HNULL ;
523 h_write = HNULL ;
524 DWORD buffer_size_hint = 0 ;
525 BOOL rc = CreatePipe( &h_read , &h_write , &attributes , buffer_size_hint ) ;
526 if( rc == 0 )
527 {
528 DWORD error = GetLastError() ;
529 G_ERROR( "G::NewProcessWindowsImp::Pipe::create: pipe error: create: " << error ) ;
530 throw NewProcess::PipeError( "create" ) ;
531 }
532}
533
534 void G::NewProcessWindowsImp::Pipe::uninherited( HANDLE h )
535{
536 if( ! SetHandleInformation( h , HANDLE_FLAG_INHERIT , 0 ) )
537 {
538 DWORD error = GetLastError() ;
539 closeHandle( h ) ;
540 G_ERROR( "G::NewProcessWindowsImp::Pipe::uninherited: uninherited error " << error ) ;
541 throw NewProcess::PipeError( "uninherited" ) ;
542 }
543}
544
545HANDLE G::NewProcessWindowsImp::Pipe::hwrite() const
546{
547 return m_write ;
548}
549
550HANDLE G::NewProcessWindowsImp::Pipe::hread() const
551{
552 return m_read ;
553}
554
555 void G::NewProcessWindowsImp::Pipe::close()
556{
557 closeHandle( m_write ) ;
558 m_write = HNULL ;
559}
560
561std::size_t G::NewProcessWindowsImp::Pipe::read( HANDLE hread , char * buffer , std::size_t buffer_size_in ) noexcept
562{
563 // (worker thread - keep it simple)
564 if( hread == HNULL ) return 0U ;
565 DWORD buffer_size = static_cast<DWORD>(buffer_size_in) ;
566 DWORD nread = 0U ;
567 BOOL ok = ReadFile( hread , buffer , buffer_size , &nread , nullptr ) ;
568 //DWORD error = GetLastError() ;
569 nread = ok ? std::min( nread , buffer_size ) : DWORD(0) ;
570 return static_cast<std::size_t>(nread) ;
571}
572
573// ==
574
575 G::NewProcessWaitable::NewProcessWaitable() :
576 m_hprocess(HNULL),
577 m_hpipe(HNULL) ,
578 m_pid(0),
579 m_rc(0),
580 m_status(0),
581 m_error(0) ,
582 m_test_mode(G::Test::enabled("waitpid-slow"))
583{
584}
585
586 G::NewProcessWaitable::NewProcessWaitable( HANDLE hprocess , HANDLE hpipe , int ) :
587 m_buffer(1024U*4U) ,
588 m_hprocess(hprocess),
589 m_hpipe(hpipe),
590 m_pid(0),
591 m_rc(0),
592 m_status(0),
593 m_error(0) ,
594 m_test_mode(G::Test::enabled("waitpid-slow"))
595{
596}
597
598 void G::NewProcessWaitable::assign( HANDLE hprocess , HANDLE hpipe , int )
599{
600 m_buffer.resize( 1024U * 4U ) ;
601 m_data_size = 0U ;
602 m_hprocess = hprocess ;
603 m_hpipe = hpipe ;
604 m_pid = 0 ;
605 m_rc = 0 ;
606 m_status = 0 ;
607 m_error = 0 ;
608}
609
610 void G::NewProcessWaitable::waitp( std::promise<std::pair<int,std::string>> p ) noexcept
611{
612 try
613 {
614 wait() ;
615 p.set_value( std::make_pair(get(),output()) ) ;
616 }
617 catch(...)
618 {
619 try { p.set_exception( std::current_exception() ) ; } catch(...) {}
620 }
621}
622
623 G::NewProcessWaitable & G::NewProcessWaitable::wait()
624{
625 // (worker thread - keep it simple)
626 m_data_size = 0U ;
627 m_error = 0 ;
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() ;
633 for(;;)
634 {
635 HANDLE handles[2] ;
636 DWORD nhandles = 0 ;
637 if( NewProcessImp::valid(m_hprocess) )
638 handles[nhandles++] = m_hprocess ;
639 if( m_hpipe != HNULL )
640 handles[nhandles++] = m_hpipe ;
641 if( nhandles == 0 )
642 break ;
643
644 // wait on both handles to avoid the pipe-writer from blocking if the pipe fills
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 )
648 {
649 DWORD exit_code = 127 ;
650 GetExitCodeProcess( m_hprocess , &exit_code ) ;
651 m_status = static_cast<int>(exit_code) ;
652 m_hprocess = HNULL ;
653 }
654 else if( h == m_hpipe && m_hpipe )
655 {
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 )
659 {
660 read_p += nread ;
661 space -= nread ;
662 m_data_size += nread ;
663 }
664 if( nread == 0U )
665 m_hpipe = HNULL ;
666 }
667 else
668 {
669 m_error = 1 ;
670 break ;
671 }
672 }
673 if( m_test_mode )
674 Sleep( 10000U ) ;
675 return *this ;
676}
677
678 int G::NewProcessWaitable::get() const
679{
680 if( m_error )
681 throw NewProcess::WaitError() ;
682 return m_status ;
683}
684
685 int G::NewProcessWaitable::get( std::nothrow_t , int ec ) const noexcept
686{
687 return m_error ? ec : m_status ;
688}
689
690std::string G::NewProcessWaitable::output() const
691{
692 return m_buffer.empty() ? std::string() : std::string(m_buffer.data(),m_data_size) ;
693}
694
695// ==
696
697HANDLE G::NewProcessWindowsImp::fdhandle( int fd )
698{
699 // beware "parameter validation" -- maybe use _set_thread_local_invalid_parameter_handler()
700 if( fd < 0 )
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) ;
706}
707
G::Convert::widen
static std::wstring widen(std::string_view)
Widens from UTF-8 to UTF-16/UCS-4 wstring.
Definition: gconvert.cpp:38
G::NewProcessWaitable
Holds the parameters and future results of a waitpid() system call.
Definition: gnewprocess.h:204
G::NewProcessWaitable::get
int get() const
Returns the result of the wait() as either the process exit code or as a thrown exception.
Definition: gnewprocess_unix.cpp:486
G::NewProcessWaitable::waitp
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:
Definition: gnewprocess_unix.cpp:415
G::NewProcessWaitable::NewProcessWaitable
NewProcessWaitable()
Default constructor for an object where wait() does nothing and get() returns zero.
Definition: gnewprocess_unix.cpp:386
G::NewProcessWaitable::output
std::string output() const
Returns the first bit of child-process output.
Definition: gnewprocess_unix.cpp:535
G::NewProcessWaitable::wait
NewProcessWaitable & wait()
Waits for the process identified by the constructor parameter to exit.
Definition: gnewprocess_unix.cpp:430
G::NewProcessWaitable::assign
void assign(pid_t pid, int fd)
Reinitialises as if constructed with the given proces-id and file descriptor.
Definition: gnewprocess_unix.cpp:401
G::NewProcess::id
int id() const noexcept
Returns the process id.
Definition: gnewprocess_unix.cpp:126
G::NewProcess::kill
void kill(bool yield=false) noexcept
Tries to kill the spawned process and optionally yield to a thread that might be waiting on it.
Definition: gnewprocess_unix.cpp:131
G::NewProcess::~NewProcess
~NewProcess()
Destructor.
G::NewProcess::waitable
NewProcessWaitable & waitable() noexcept
Returns a reference to the Waitable sub-object so that the caller can wait for the child process to e...
Definition: gnewprocess_unix.cpp:116
G::Path
A Path object represents a file system path.
Definition: gpath.h:82
G::Str::removeAll
static void removeAll(std::string &, char)
Removes all occurrences of the character from the string. See also only().
Definition: gstr.cpp:262
G::Str::replaceAll
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'.
Definition: gstr.cpp:247
G::Test
A static interface for enabling test features at run-time.
Definition: gtest.h:50
gbuffer.h
gconvert.h
gexception.h
glog.h
gnewprocess.h
gnowide.h
Contains inline functions that convert to and from UTF-8 strings in order to call wide-character "W()...
gpath.h
gstr.h
gtest.h
GPop::enabled
bool enabled() noexcept
Returns true if pop code is built in.
Definition: gpop_disabled.cpp:25
G
Low-level classes.
Definition: garg.h:36
G::StringArray
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstringarray.h:30
G::tx
constexpr const char * tx(const char *p) noexcept
A briefer alternative to G::gettext_noop().
Definition: ggettext.h:84
std
STL namespace.