E-MailRelay
gnewprocess_win32.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2023 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 "gnewprocess.h"
23#include "gexception.h"
24#include "gstr.h"
25#include "gpath.h"
26#include "gtest.h"
27#include "gbuffer.h"
28#include "glog.h"
29#include <sstream>
30#include <sys/types.h>
31#include <sys/stat.h>
32#include <process.h>
33#include <io.h>
34#include <algorithm> // std::swap()
35#include <utility> // std::swap()
36#include <array>
37
38namespace G
39{
40 namespace NewProcessWindowsImp
41 {
42 class Pipe ;
43 class AttributeList ;
44 class StartupInfo ;
45 }
46}
47
48class G::NewProcessWindowsImp::Pipe
49{
50public:
51 Pipe() ;
52 ~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 ;
56 void close() ;
57
58public:
59 Pipe( const Pipe & ) = delete ;
60 Pipe( Pipe && ) = delete ;
61 Pipe & operator=( const Pipe & ) = delete ;
62 Pipe & operator=( Pipe && ) = delete ;
63
64private:
65 static void create( HANDLE & read , HANDLE & write ) ;
66 static void uninherited( HANDLE h ) ;
67
68private:
69 HANDLE m_read ;
70 HANDLE m_write ;
71} ;
72
73class G::NewProcessImp
74{
75public:
76 using Pipe = NewProcessWindowsImp::Pipe ;
77 using AttributeList = NewProcessWindowsImp::AttributeList ;
78 using StartupInfo = NewProcessWindowsImp::StartupInfo ;
79 using Fd = NewProcess::Fd ;
80
81 NewProcessImp( const Path & , const StringArray & , const NewProcess::Config & ) ;
82 // Constructor. Spawns the new process.
83
84 ~NewProcessImp() ;
85 // Destructor. Kills the process if it is still running.
86
87 NewProcessWaitable & waitable() noexcept ;
88 // Returns a reference to the Waitable sub-object to allow
89 // the caller to wait for the process to finish.
90
91 void kill() noexcept ;
92 // Tries to kill the spawned process.
93
94 int id() const noexcept ;
95 // Returns the process id.
96
97 static bool valid( HANDLE h ) noexcept ;
98 // Returns true if a valid handle.
99
100public:
101 NewProcessImp( const NewProcessImp & ) = delete ;
102 NewProcessImp( NewProcessImp && ) = delete ;
103 NewProcessImp & operator=( const NewProcessImp & ) = delete ;
104 NewProcessImp & operator=( NewProcessImp && ) = delete ;
105
106private:
107 static std::pair<std::string,std::string> commandLine( std::string exe , StringArray args ) ;
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() ;
116
117private:
118 HANDLE m_hprocess ;
119 DWORD m_pid ;
120 bool m_killed ;
121 Pipe m_pipe ;
122 NewProcessWaitable m_waitable ;
123} ;
124
125#if GCONFIG_HAVE_WINDOWS_STARTUP_INFO_EX
126class G::NewProcessWindowsImp::AttributeList
127{
128public:
129 G_EXCEPTION( Error , tx("AttributeList error") ) ;
130 using pointer_type = LPPROC_THREAD_ATTRIBUTE_LIST ;
131 explicit AttributeList( HANDLE ) ;
132 ~AttributeList() ;
133 pointer_type ptr() ;
134
135public:
136 AttributeList( const AttributeList & ) = delete ;
137 AttributeList( AttributeList && ) = delete ;
138 AttributeList & operator=( const AttributeList & ) = delete ;
139 AttributeList & operator=( AttributeList && ) = delete ;
140
141private:
142 void resize() ;
143 void init( DWORD ) ;
144 void add( HANDLE ) ;
145 void cleanup() noexcept ;
146
147private:
148 G::Buffer<char> m_buffer ; // (char since PROC_THREAD_ATTRIBUTE_LIST is not a defined type)
149 HANDLE m_handle {NULL} ;
150 pointer_type m_ptr {NULL} ;
151} ;
152#else
153class G::NewProcessWindowsImp::AttributeList
154{
155public:
156 using pointer_type = void* ;
157 explicit AttributeList( HANDLE ) {}
158 pointer_type ptr() { return nullptr ; }
159} ;
160#endif
161
162class G::NewProcessWindowsImp::StartupInfo
163{
164public:
165 StartupInfo( AttributeList & , HANDLE hstdout , HANDLE hstdin ) ;
166 LPSTARTUPINFOA ptr() ;
167 DWORD flags() const ;
168
169private:
170 #if GCONFIG_HAVE_WINDOWS_STARTUP_INFO_EX
171 STARTUPINFOEXA m_startup_info ;
172 #else
173 STARTUPINFOA m_startup_info ;
174 #endif
175 LPSTARTUPINFOA m_ptr ;
176 DWORD m_flags ;
177} ;
178
179// ==
180
181G::NewProcess::NewProcess( const Path & exe , const StringArray & args , const Config & config ) :
182 m_imp(std::make_unique<NewProcessImp>(exe,args,config))
183{
184}
185
187= default ;
188
190{
191 return m_imp->waitable() ;
192}
193
194int G::NewProcess::id() const noexcept
195{
196 return m_imp->id() ;
197}
198
199bool G::NewProcessImp::valid( HANDLE h ) noexcept
200{
201 return h != HNULL && h != INVALID_HANDLE_VALUE ;
202}
203
204void G::NewProcess::kill( bool yield ) noexcept
205{
206 m_imp->kill() ;
207 if( yield )
208 {
209 G::threading::yield() ;
210 SleepEx( 0 , FALSE ) ;
211 }
212}
213
214// ==
215
216G::NewProcessImp::NewProcessImp( const Path & exe , const StringArray & args , const NewProcess::Config & config ) :
217 m_hprocess(0) ,
218 m_killed(false) ,
219 m_waitable(HNULL,HNULL,0)
220{
221 G_DEBUG( "G::NewProcessImp::ctor: exe=[" << exe << "] args=[" << Str::join("],[",args) << "]" ) ;
222
223 // only support Fd::devnull() and Fd::pipe() here
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() ) )
228 {
229 throw NewProcess::Error( "invalid parameters" ) ;
230 }
231
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() ) ;
236
237 m_hprocess = pair.first ;
238 m_pid = pair.second ;
239
240 m_pipe.close() ; // close write end, now used by child process
241 m_waitable.assign( m_hprocess , m_pipe.hread() , 0 ) ;
242}
243
244void G::NewProcessImp::kill() noexcept
245{
246 if( !m_killed && valid(m_hprocess) )
247 TerminateProcess( m_hprocess , 127 ) ;
248 m_killed = true ;
249}
250
251G::NewProcessImp::~NewProcessImp()
252{
253 if( m_hprocess != HNULL )
254 CloseHandle( m_hprocess ) ;
255}
256
257G::NewProcessWaitable & G::NewProcessImp::waitable() noexcept
258{
259 return m_waitable ;
260}
261
262int G::NewProcessImp::id() const noexcept
263{
264 return static_cast<int>(m_pid) ;
265}
266
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 )
269{
270 G_DEBUG( "G::NewProcessImp::createProcess: exe=[" << exe << "] command-line=[" << command_line << "]" ) ;
271
272 // redirect stdout or stderr onto the read end of our pipe
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 ) ;
277
278 BOOL inherit = TRUE ;
279 DWORD flags = startup_info.flags() ;
280 LPVOID envp = env.empty() ? nullptr : static_cast<LPVOID>(const_cast<char*>(env.ptr())) ;
281 LPCSTR cwd = cd ;
282 SECURITY_ATTRIBUTES * process_attributes = nullptr ;
283 SECURITY_ATTRIBUTES * thread_attributes = nullptr ;
284
285 PROCESS_INFORMATION info {} ;
286
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 ) ;
291
292 if( rc == 0 || !valid(info.hProcess) )
293 {
294 DWORD e = GetLastError() ;
295 std::ostringstream ss ;
296 ss << "error " << e << ": [" << exe << "] [" << command_line << "]" ;
297 throw NewProcess::CreateProcessError( ss.str() ) ;
298 }
299
300 G_DEBUG( "G::NewProcessImp::createProcess: process-id=" << info.dwProcessId ) ;
301 G_DEBUG( "G::NewProcessImp::createProcess: thread-id=" << info.dwThreadId ) ;
302
303 CloseHandle( info.hThread ) ;
304 return { info.hProcess , info.dwProcessId } ;
305}
306
307std::pair<std::string,std::string> G::NewProcessImp::commandLine( std::string exe , StringArray args )
308{
309 // there is no correct way to do this because every target program
310 // will parse its command-line differently -- quotes, spaces and
311 // empty arguments are best avoided
312
313 // in this implementation all quotes are deleted(!) unless
314 // an exe, executable paths with a space are quoted, empty
315 // arguments and arguments with a space are quoted (unless
316 // a batch file that has been quoted)
317
318 if( isSimplyQuoted(exe) )
319 dequote( exe ) ;
320
321 for( auto & arg : args )
322 dequote( arg ) ;
323
324 std::string type = Str::lower( G::Path(exe).extension() ) ;
325 if( type == "exe" || type == "bat" )
326 {
327 // we can run CreateProcess() directly -- but note
328 // that CreateProcess() with a batch file runs
329 // "cmd.exe /c" internally
330 }
331 else
332 {
333 args.insert( args.begin() , exe ) ;
334 args.insert( args.begin() , "//B" ) ;
335 args.insert( args.begin() , "//nologo" ) ;
336 exe = cscript() ;
337 }
338
339 std::string command_line = isSpaced(exe) ? withQuotes(exe) : exe ;
340 for( auto & arg : args )
341 {
342 if( ( arg.empty() || isSpaced(arg) ) && isSpaced(exe) && type == "bat" )
343 {
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) ; // this fails >-: cmd /c "a b.bat" "c d"
346 }
347 else if( arg.empty() || isSpaced(arg) )
348 {
349 command_line.append(1U,' ').append(withQuotes(arg)) ;
350 }
351 else
352 {
353 command_line.append(1U,' ').append(arg) ;
354 }
355 }
356 return { exe , command_line } ;
357}
358
359void G::NewProcessImp::dequote( std::string & s )
360{
361 if( isSimplyQuoted(s) )
362 {
363 s = s.substr( 1U , s.length()-2U ) ;
364 }
365 else if( s.find( '\"' ) != std::string::npos )
366 {
367 G::Str::removeAll( s , '\"' ) ;
368 G_WARNING_ONCE( "G::NewProcessImp::dequote: quotes removed when building command-line" ) ;
369 }
370}
371
372bool G::NewProcessImp::isSimplyQuoted( const std::string & s )
373{
374 static constexpr char q = '\"' ;
375 return
376 s.length() > 1U && s.at(0U) == q && s.at(s.length()-1U) == q &&
377 s.find(q,1U) == (s.length()-1U) ;
378}
379
380bool G::NewProcessImp::isSpaced( const std::string & s )
381{
382 return s.find(' ') != std::string::npos ;
383}
384
385std::string G::NewProcessImp::withQuotes( const std::string & s )
386{
387 return std::string(1U,'\"').append(s).append(1U,'\"') ;
388}
389
390std::string G::NewProcessImp::windowsPath()
391{
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 ) ;
398}
399
400std::string G::NewProcessImp::cscript()
401{
402 return windowsPath().append("\\system32\\cscript.exe") ;
403}
404
405// ==
406
407#if GCONFIG_HAVE_WINDOWS_STARTUP_INFO_EX
408G::NewProcessWindowsImp::AttributeList::AttributeList( HANDLE h )
409{
410 resize() ;
411 init( h ? 1 : 0 ) ;
412 add( h ) ;
413}
414
415G::NewProcessWindowsImp::AttributeList::~AttributeList()
416{
417 cleanup() ;
418}
419
420void G::NewProcessWindowsImp::AttributeList::resize()
421{
422 SIZE_T buffer_size = 0 ;
423 InitializeProcThreadAttributeList( NULL , 1 , 0 , &buffer_size ) ;
424 if( buffer_size == 0 || buffer_size > 100000 )
425 throw Error() ;
426 m_buffer.resize( buffer_size ) ;
427}
428
429void G::NewProcessWindowsImp::AttributeList::init( DWORD attribute_count )
430{
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 ) ;
434 if( !ok )
435 throw Error() ;
436 m_ptr = ptr ;
437 G_ASSERT( m_buffer.size() >= buffer_size ) ;
438}
439
440void G::NewProcessWindowsImp::AttributeList::add( HANDLE h )
441{
442 if( h )
443 {
444 m_handle = h ;
445 BOOL ok = UpdateProcThreadAttribute( m_ptr , 0 ,
446 PROC_THREAD_ATTRIBUTE_HANDLE_LIST , &m_handle , sizeof(m_handle) , NULL , NULL ) ;
447 if( !ok )
448 {
449 cleanup() ;
450 throw Error() ;
451 }
452 }
453}
454
455void G::NewProcessWindowsImp::AttributeList::cleanup() noexcept
456{
457 if( m_ptr )
458 DeleteProcThreadAttributeList( m_ptr ) ;
459 m_ptr = NULL ;
460}
461
462G::NewProcessWindowsImp::AttributeList::pointer_type G::NewProcessWindowsImp::AttributeList::ptr()
463{
464 return m_ptr ;
465}
466#endif
467
468// ==
469
470G::NewProcessWindowsImp::StartupInfo::StartupInfo( AttributeList & attribute_list , HANDLE hstdout , HANDLE hstderr )
471{
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 ;
483 #else
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 ;
494 #endif
495}
496
497LPSTARTUPINFOA G::NewProcessWindowsImp::StartupInfo::ptr()
498{
499 return m_ptr ;
500}
501
502DWORD G::NewProcessWindowsImp::StartupInfo::flags() const
503{
504 return m_flags ;
505}
506
507// ==
508
509G::NewProcessWindowsImp::Pipe::Pipe() :
510 m_read(HNULL) ,
511 m_write(HNULL)
512{
513 create( m_read , m_write ) ;
514 uninherited( m_read ) ;
515}
516
517G::NewProcessWindowsImp::Pipe::~Pipe()
518{
519 if( m_read != HNULL ) CloseHandle( m_read ) ;
520 if( m_write != HNULL ) CloseHandle( m_write ) ;
521}
522
523void G::NewProcessWindowsImp::Pipe::create( HANDLE & h_read , HANDLE & h_write )
524{
525 SECURITY_ATTRIBUTES attributes {} ;
526 attributes.nLength = sizeof(attributes) ;
527 attributes.lpSecurityDescriptor = nullptr ;
528 attributes.bInheritHandle = TRUE ;
529
530 h_read = HNULL ;
531 h_write = HNULL ;
532 DWORD buffer_size_hint = 0 ;
533 BOOL rc = CreatePipe( &h_read , &h_write , &attributes , buffer_size_hint ) ;
534 if( rc == 0 )
535 {
536 DWORD error = GetLastError() ;
537 G_ERROR( "G::NewProcessWindowsImp::Pipe::create: pipe error: create: " << error ) ;
538 throw NewProcess::PipeError( "create" ) ;
539 }
540}
541
542void G::NewProcessWindowsImp::Pipe::uninherited( HANDLE h )
543{
544 if( ! SetHandleInformation( h , HANDLE_FLAG_INHERIT , 0 ) )
545 {
546 DWORD error = GetLastError() ;
547 CloseHandle( h ) ;
548 G_ERROR( "G::NewProcessWindowsImp::Pipe::uninherited: uninherited error " << error ) ;
549 throw NewProcess::PipeError( "uninherited" ) ;
550 }
551}
552
553HANDLE G::NewProcessWindowsImp::Pipe::hwrite() const
554{
555 return m_write ;
556}
557
558HANDLE G::NewProcessWindowsImp::Pipe::hread() const
559{
560 return m_read ;
561}
562
563void G::NewProcessWindowsImp::Pipe::close()
564{
565 if( m_write != HNULL )
566 CloseHandle( m_write ) ;
567 m_write = HNULL ;
568}
569
570std::size_t G::NewProcessWindowsImp::Pipe::read( HANDLE hread , char * buffer , std::size_t buffer_size_in ) noexcept
571{
572 // (worker thread - keep it simple)
573 if( hread == HNULL ) return 0U ;
574 DWORD buffer_size = static_cast<DWORD>(buffer_size_in) ;
575 DWORD nread = 0U ;
576 BOOL ok = ReadFile( hread , buffer , buffer_size , &nread , nullptr ) ;
577 //DWORD error = GetLastError() ;
578 nread = ok ? std::min( nread , buffer_size ) : DWORD(0) ;
579 return static_cast<std::size_t>(nread) ;
580}
581
582// ==
583
585 m_hprocess(HNULL),
586 m_hpipe(HNULL) ,
587 m_pid(0),
588 m_rc(0),
589 m_status(0),
590 m_error(0) ,
591 m_test_mode(G::Test::enabled("waitpid-slow"))
592{
593}
594
595G::NewProcessWaitable::NewProcessWaitable( HANDLE hprocess , HANDLE hpipe , int ) :
596 m_buffer(1024U) ,
597 m_hprocess(hprocess),
598 m_hpipe(hpipe),
599 m_pid(0),
600 m_rc(0),
601 m_status(0),
602 m_error(0) ,
603 m_test_mode(G::Test::enabled("waitpid-slow"))
604{
605}
606
607void G::NewProcessWaitable::assign( HANDLE hprocess , HANDLE hpipe , int )
608{
609 m_buffer.resize( 1024U ) ;
610 m_data_size = 0U ;
611 m_hprocess = hprocess ;
612 m_hpipe = hpipe ;
613 m_pid = 0 ;
614 m_rc = 0 ;
615 m_status = 0 ;
616 m_error = 0 ;
617}
618
619void G::NewProcessWaitable::waitp( std::promise<std::pair<int,std::string>> p ) noexcept
620{
621 try
622 {
623 wait() ;
624 p.set_value( std::make_pair(get(),output()) ) ;
625 }
626 catch(...)
627 {
628 try { p.set_exception( std::current_exception() ) ; } catch(...) {}
629 }
630}
631
633{
634 // (worker thread - keep it simple)
635 m_data_size = 0U ;
636 m_error = 0 ;
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() ;
642 for(;;)
643 {
644 HANDLE handles[2] ;
645 DWORD nhandles = 0 ;
646 if( NewProcessImp::valid(m_hprocess) )
647 handles[nhandles++] = m_hprocess ;
648 if( m_hpipe != HNULL )
649 handles[nhandles++] = m_hpipe ;
650 if( nhandles == 0 )
651 break ;
652
653 // wait on both handles to avoid the pipe-writer from blocking if the pipe fills
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 )
657 {
658 DWORD exit_code = 127 ;
659 GetExitCodeProcess( m_hprocess , &exit_code ) ;
660 m_status = static_cast<int>(exit_code) ;
661 m_hprocess = HNULL ;
662 }
663 else if( h == m_hpipe && m_hpipe )
664 {
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 )
668 {
669 read_p += nread ;
670 space -= nread ;
671 m_data_size += nread ;
672 }
673 if( nread == 0U )
674 m_hpipe = HNULL ;
675 }
676 else
677 {
678 m_error = 1 ;
679 break ;
680 }
681 }
682 if( m_test_mode )
683 Sleep( 10000U ) ;
684 return *this ;
685}
686
688{
689 if( m_error )
690 throw NewProcess::WaitError() ;
691 return m_status ;
692}
693
694int G::NewProcessWaitable::get( std::nothrow_t , int ec ) const noexcept
695{
696 return m_error ? ec : m_status ;
697}
698
699std::string G::NewProcessWaitable::output() const
700{
701 return m_buffer.size() ? std::string(&m_buffer[0],m_data_size) : std::string() ;
702}
703
Holds the parameters and future results of a waitpid() system call.
Definition: gnewprocess.h:198
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.
~NewProcess()
Destructor.
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.
Definition: gpath.h:73
static void removeAll(std::string &, char)
Removes all occurrences of the character from the string. See also only().
Definition: gstr.cpp:262
A static interface for enabling test features at run-time.
Definition: gtest.h:50
bool enabled() noexcept
Returns true if pop code is built in.
Low-level classes.
Definition: garg.h:30
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstringarray.h:30
constexpr const char * tx(const char *p)
A briefer alternative to G::gettext_noop().
Definition: ggettext.h:84
STL namespace.