libzypp 17.37.17
ExternalProgram.cc
Go to the documentation of this file.
1/*---------------------------------------------------------------------\
2| ____ _ __ __ ___ |
3| |__ / \ / / . \ . \ |
4| / / \ V /| _/ _/ |
5| / /__ | | | | | | |
6| /_____||_| |_| |_| |
7| |
8\---------------------------------------------------------------------*/
11
12#define _GNU_SOURCE 1 // for ::getline
13
14#include <signal.h>
15#include <errno.h>
16#include <unistd.h>
17#include <sys/wait.h>
18#include <fcntl.h>
19#include <pty.h> // openpty
20#include <stdlib.h> // setenv
21#include <sys/prctl.h> // prctl(), PR_SET_PDEATHSIG
22
23#include <cstring> // strsignal
24#include <iostream>
25#include <sstream>
26
33
36
37using std::endl;
38
39#undef ZYPP_BASE_LOGGER_LOGGROUP
40#define ZYPP_BASE_LOGGER_LOGGROUP "zypp::exec"
41
42namespace zypp {
43
46
47
48 ExternalProgram::ExternalProgram( const std::string& commandline,
49 Stderr_Disposition stderr_disp,
50 bool use_pty,
51 int stderr_fd,
52 bool default_locale,
53 const Pathname & root )
54 {
55 const char *argv[4];
56 argv[0] = "/bin/sh";
57 argv[1] = "-c";
58 argv[2] = commandline.c_str();
59 argv[3] = 0;
60
61 start_program( argv, Environment(), stderr_disp, stderr_fd, default_locale, root.c_str(), false, false, use_pty );
62 }
63
65 Stderr_Disposition stderr_disp,
66 bool use_pty,
67 int stderr_fd,
68 bool default_locale,
69 const Pathname & root )
70 {
71 const char * argvp[argv.size() + 1];
72 unsigned c = 0;
73 for ( const std::string & arg : argv )
74 {
75 argvp[c] = arg.c_str();
76 ++c;
77 }
78 argvp[c] = 0;
79
80 start_program( argvp, Environment(), stderr_disp, stderr_fd, default_locale, root.c_str(), false, false, use_pty );
81 }
82
84 const Environment & environment,
85 Stderr_Disposition stderr_disp,
86 bool use_pty,
87 int stderr_fd,
88 bool default_locale,
89 const Pathname & root )
90 {
91 const char * argvp[argv.size() + 1];
92 unsigned c = 0;
93 for ( const std::string & arg : argv )
94 {
95 argvp[c] = arg.c_str();
96 ++c;
97 }
98 argvp[c] = 0;
99
100 start_program( argvp, environment, stderr_disp, stderr_fd, default_locale, root.c_str(), false, false, use_pty );
101 }
102
103 ExternalProgram::ExternalProgram( const char *const *argv,
104 Stderr_Disposition stderr_disp,
105 bool use_pty,
106 int stderr_fd,
107 bool default_locale,
108 const Pathname & root )
109 {
110 start_program( argv, Environment(), stderr_disp, stderr_fd, default_locale, root.c_str(), false, false, use_pty );
111 }
112
113 ExternalProgram::ExternalProgram( const char *const * argv,
114 const Environment & environment,
115 Stderr_Disposition stderr_disp,
116 bool use_pty,
117 int stderr_fd,
118 bool default_locale,
119 const Pathname & root )
120 {
121 start_program( argv, environment, stderr_disp, stderr_fd, default_locale, root.c_str(), false, false, use_pty );
122 }
123
124
126 const char *const *argv_1,
127 bool use_pty )
128 {
129 int i = 0;
130 while (argv_1[i++])
131 ;
132 const char *argv[i + 1];
133 argv[0] = binpath;
134 memcpy( &argv[1], argv_1, (i - 1) * sizeof (char *) );
135 start_program( argv, Environment(), Normal_Stderr, 1, false, NULL, false, false, use_pty );
136 }
137
139 const char *const *argv_1,
140 const Environment & environment,
141 bool use_pty )
142 {
143 int i = 0;
144 while (argv_1[i++])
145 ;
146 const char *argv[i + 1];
147 argv[0] = binpath;
148 memcpy( &argv[1], argv_1, (i - 1) * sizeof (char *) );
149 start_program( argv, environment, Normal_Stderr, 1, false, NULL, false, false, use_pty );
150 }
151
154
155
156
157 void ExternalProgram::start_program( const char *const *argv,
158 const Environment & environment,
159 Stderr_Disposition stderr_disp,
160 int stderr_fd,
161 bool default_locale,
162 const char * root , bool switch_pgid, bool die_with_parent , bool usePty )
163 {
164 if ( _backend )
165 return;
166
167 // usePty is only supported by the forking backend
168 if ( usePty ) {
169 DBG << "usePty was set, forcing the ForkSpawnEngine to start external processes" << std::endl;
170 _backend = std::make_unique<zyppng::ForkSpawnEngine>();
171 static_cast<zyppng::ForkSpawnEngine&>(*_backend).setUsePty( true );
172 } else {
174 }
175
176 // retrieve options at beginning of arglist
177 const char * redirectStdin = nullptr; // <[file]
178 const char * redirectStdout = nullptr; // >[file]
179 const char * chdirTo = nullptr; // #/[path]
180
181 if ( root )
182 {
183 if ( root[0] == '\0' )
184 {
185 root = nullptr; // ignore empty root
186 }
187 else if ( root[0] == '/' && root[1] == '\0' )
188 {
189 // If root is '/' do not chroot, but chdir to '/'
190 // unless arglist defines another dir.
191 chdirTo = "/";
192 root = nullptr;
193 }
194 }
195
196 for ( bool strip = false; argv[0] != nullptr; ++argv )
197 {
198 strip = false;
199 switch ( argv[0][0] )
200 {
201 case '<':
202 strip = true;
203 redirectStdin = argv[0]+1;
204 if ( *redirectStdin == '\0' )
205 redirectStdin = "/dev/null";
206 break;
207
208 case '>':
209 strip = true;
210 redirectStdout = argv[0]+1;
211 if ( *redirectStdout == '\0' )
212 redirectStdout = "/dev/null";
213 break;
214
215 case '#':
216 strip = true;
217 if ( argv[0][1] == '/' ) // #/[path]
218 chdirTo = argv[0]+1;
219 break;
220 }
221 if ( ! strip )
222 break;
223 }
224
225 // those are the FDs that the new process will receive
226 // AutoFD will take care of closing them on our side
227 zypp::AutoFD stdinFd = -1;
228 zypp::AutoFD stdoutFd = -1;
229 zypp::AutoFD stderrFd = -1;
230
231 // those are the fds we will keep, we put them into autofds in case
232 // we need to return early without actually spawning the new process
233 zypp::AutoFD childStdinParentFd = -1;
234 zypp::AutoFD childStdoutParentFd = -1;
235
236 if ( usePty )
237 {
238
239 int master_tty = 0, slave_tty = 0; // fds for pair of ttys
240
241 // Create pair of ttys
242 DBG << "Using ttys for communication with " << argv[0] << endl;
243 if ( zyppng::eintrSafeCall( ::openpty, &master_tty, &slave_tty, (char *)0, (termios *)0, (winsize *)0) != 0)
244 {
245 _backend->setExecError( str::form( _("Can't open pty (%s)."), strerror(errno) ) );
246 _backend->setExitStatus( 126 );
247 ERR << _backend->execError() << endl;
248 return;
249 }
250
251 stdinFd = slave_tty;
252 stdoutFd = slave_tty;
253 childStdinParentFd = master_tty;
254 childStdoutParentFd = master_tty;
255 }
256 else
257 {
258 if ( redirectStdin ) {
259 stdinFd = open( redirectStdin, O_RDONLY );
260 } else {
261 int to_external[2];
262 if ( pipe (to_external) != 0 )
263 {
264 _backend->setExecError( str::form( _("Can't open pipe (%s)."), strerror(errno) ) );
265 _backend->setExitStatus( 126 );
266 ERR << _backend->execError() << endl;
267 return;
268 }
269 stdinFd = to_external[0];
270 childStdinParentFd = to_external[1];
271 }
272
273 if ( redirectStdout ) {
274 stdoutFd = open( redirectStdout, O_WRONLY|O_CREAT|O_APPEND, 0600 );
275 } else {
276
277 int from_external[2];
278 // Create pair of pipes
279 if ( pipe (from_external) != 0 )
280 {
281 _backend->setExecError( str::form( _("Can't open pipe (%s)."), strerror(errno) ) );
282 _backend->setExitStatus( 126 );
283 ERR << _backend->execError() << endl;
284 return;
285 }
286 stdoutFd = from_external[1];
287 childStdoutParentFd = from_external[0];
288 }
289 }
290
291 // Handle stderr
292 if (stderr_disp == Discard_Stderr)
293 {
294 stderrFd = open("/dev/null", O_WRONLY);
295 }
296 else if (stderr_disp == Stderr_To_Stdout)
297 {
298 stderrFd = *stdoutFd;
299 //no double close
300 stderrFd.resetDispose();
301 }
302 else if (stderr_disp == Stderr_To_FileDesc)
303 {
304 // Note: We don't have to close anything regarding stderr_fd.
305 // Our caller is responsible for that.
306 stderrFd = stderr_fd;
307 stderrFd.resetDispose();
308 }
309
310 if ( root )
311 _backend->setChroot( root );
312 if ( chdirTo )
313 _backend->setWorkingDirectory( chdirTo );
314
315 _backend->setDieWithParent( die_with_parent );
316 _backend->setSwitchPgid( switch_pgid );
317 _backend->setEnvironment( environment );
318 _backend->setUseDefaultLocale( default_locale );
319
320 if ( _backend->start( argv, stdinFd, stdoutFd, stderrFd ) ) {
321 bool connected = true;
322 if ( childStdoutParentFd != -1 ) {
323 inputfile = fdopen( childStdoutParentFd, "r" );
324 if ( inputfile )
325 childStdoutParentFd.resetDispose();
326 else
327 connected = false;
328 }
329 if ( childStdinParentFd != -1 ) {
330 outputfile = fdopen( childStdinParentFd, "w" );
331 if ( outputfile )
332 childStdinParentFd.resetDispose();
333 else
334 connected = false;
335 }
336 if ( not connected )
337 {
338 ERR << "Cannot create streams to external program " << argv[0] << endl;
340 }
341 } else {
342 // Fork failed, exit code and status was set by backend
343 return;
344 }
345 }
346
347 bool ExternalProgram::waitForExit(std::optional<uint64_t> timeout)
348 {
349 if ( !_backend ) {
350 // no backend means no running progress, return true
351 return true;
352 }
353 return _backend->waitForExit( timeout );
354 }
355
356 int
358 {
359 if ( !_backend ) {
360 ExternalDataSource::close();
361 return -1;
362 }
363
364 if ( _backend->isRunning() )
365 {
366 if ( inputFile() )
367 {
368 // Discard any output instead of closing the pipe,
369 // but watch out for the command exiting while some
370 // subprocess keeps the filedescriptor open.
371 setBlocking( false );
372 FILE * inputfile = inputFile();
373 int inputfileFd = ::fileno( inputfile );
374 long delay = 0;
375 do
376 {
377 /* Watch inputFile to see when it has input. */
378 GPollFD rfd;
379 rfd.fd = inputfileFd;
380 rfd.events = G_IO_IN | G_IO_HUP | G_IO_ERR;
381 rfd.revents = 0;
382
383 // each try increases our wait by 0.1 seconds, until we have
384 // 1 sec max timout.
385 gint timeout = delay < 0 ? 1000 : delay*100;
386 if ( delay >= 0 && ++delay > 9 )
387 delay = -1;
388
389 int retval = zyppng::eintrSafeCall( g_poll, &rfd, 1, timeout );
390 if ( retval == -1 )
391 {
392 ERR << "select error: " << strerror(errno) << endl;
393 break;
394 }
395 else if ( retval )
396 {
397 // Data is available now.
398 static size_t linebuffer_size = 0; // static because getline allocs
399 static char * linebuffer = 0; // and reallocs if buffer is too small
400 getline( &linebuffer, &linebuffer_size, inputfile );
401 // ::feof check is important as select returns
402 // positive if the file was closed.
403 if ( ::feof( inputfile ) )
404 break;
405 clearerr( inputfile );
406 }
407 else
408 {
409 // No data within time.
410 if ( ! _backend->isRunning() )
411 break;
412 }
413 } while ( true );
414 }
415
416 // wait for the process to end)
417 _backend->isRunning( true );
418 }
419
420 ExternalDataSource::close();
421 return _backend->exitStatus();
422 }
423
424 bool
426 {
427 if ( _backend && _backend->isRunning() )
428 {
429 if ( ::kill( _backend->pid(), SIGKILL) == -1 ) {
430 ERR << "Failed to kill PID " << _backend->pid() << " with error: " << Errno() << std::endl;
431 return false;
432 }
433 close();
434 }
435 return true;
436 }
437
439 {
440 if ( _backend && _backend->isRunning() )
441 {
442 if ( ::kill( _backend->pid(), sig ) == -1 ) {
443 ERR << "Failed to kill PID " << _backend->pid() << " with error: " << Errno() << std::endl;
444 return false;
445 }
446 }
447 return true;
448 }
449
450 bool
452 {
453 if ( !_backend ) return false;
454 return _backend->isRunning();
455 }
456
458 {
459 if ( !running() )
460 return -1;
461 return _backend->pid();
462 }
463
464 const std::string &ExternalProgram::command() const
465 {
466 if ( !_backend ) {
467 static std::string empty;
468 return empty;
469 }
470 return _backend->executedCommand();
471 }
472
473 const std::string &ExternalProgram::execError() const
474 {
475 if ( !_backend ) {
476 static std::string empty;
477 return empty;
478 }
479 return _backend->execError();
480 }
481
482 // origfd will be accessible as newfd and closed (unless they were equal)
483 void ExternalProgram::renumber_fd (int origfd, int newfd)
484 {
485 return zyppng::renumberFd( origfd, newfd );
486 }
487
488 std::ostream & ExternalProgram::operator>>( std::ostream & out_r )
489 {
490 setBlocking( true );
491 for ( std::string line = receiveLine(); line.length(); line = receiveLine() )
492 out_r << line;
493 return out_r;
494 }
495
497 //
498 // class ExternalProgramWithStderr
499 //
501
502 namespace externalprogram
503 {
505 {
506 _fds[R] = _fds[W] = -1;
507#ifdef HAVE_PIPE2
508 ::pipe2( _fds, O_NONBLOCK );
509#else
510 ::pipe( _fds );
511 ::fcntl(_fds[R], F_SETFD, O_NONBLOCK );
512 ::fcntl(_fds[W], F_SETFD, O_NONBLOCK );
513#endif
514 _stderr = ::fdopen( _fds[R], "r" );
515 }
516
518 {
519 closeW();
520 if ( _stderr )
521 ::fclose( _stderr );
522 }
523 } // namespace externalprogram
524
525 bool ExternalProgramWithStderr::stderrGetUpTo( std::string & retval_r, const char delim_r, bool returnDelim_r )
526 {
527 if ( ! _stderr )
528 return false;
529 if ( delim_r && ! _buffer.empty() )
530 {
531 // check for delim already in buffer
532 std::string::size_type pos( _buffer.find( delim_r ) );
533 if ( pos != std::string::npos )
534 {
535 retval_r = _buffer.substr( 0, returnDelim_r ? pos+1 : pos );
536 _buffer.erase( 0, pos+1 );
537 return true;
538 }
539 }
540 ::clearerr( _stderr );
541 do {
542 int ch = fgetc( _stderr );
543 if ( ch != EOF )
544 {
545 if ( ch != delim_r || ! delim_r )
546 _buffer.push_back( ch );
547 else
548 {
549 if ( returnDelim_r )
550 _buffer.push_back( delim_r );
551 break;
552 }
553 }
554 else if ( ::feof( _stderr ) )
555 {
556 if ( _buffer.empty() )
557 return false;
558 break;
559 }
560 else if ( errno != EINTR )
561 return false;
562 } while ( true );
563 // HERE: we left after readig at least one char (\n)
564 retval_r.swap( _buffer );
565 _buffer.clear();
566 return true;
567 }
568
569
570} // namespace zypp
struct _GPollFD GPollFD
Definition ZYppImpl.h:26
void resetDispose()
Set no dispose function.
Convenience errno wrapper.
Definition Errno.h:26
bool stderrGetUpTo(std::string &retval_r, const char delim_r, bool returnDelim_r=false)
Read data up to delim_r from stderr (nonblocking).
ExternalProgram()
Start an external program by giving the arguments as an arry of char *pointers.
int close() override
Wait for the progamm to complete.
const std::string & command() const
The command we're executing.
std::ostream & operator>>(std::ostream &out_r)
Redirect all command output to an ostream.
void start_program(const char *const *argv, const Environment &environment, Stderr_Disposition stderr_disp=Normal_Stderr, int stderr_fd=-1, bool default_locale=false, const char *root=NULL, bool switch_pgid=false, bool die_with_parent=false, bool usePty=false)
std::vector< std::string > Arguments
static void renumber_fd(int origfd, int newfd)
origfd will be accessible as newfd and closed (unless they were equal)
std::map< std::string, std::string > Environment
For passing additional environment variables to set.
bool kill()
Kill the program.
pid_t getpid()
return pid
const std::string & execError() const
Some detail telling why the execution failed, if it failed.
bool running()
Return whether program is running.
Stderr_Disposition
Define symbols for different policies on the handling of stderr.
std::unique_ptr< zyppng::AbstractSpawnEngine > _backend
void setBlocking(bool mode)
Set the blocking mode of the input stream.
FILE * inputFile() const
Return the input stream.
std::string receiveLine()
Read one line from the input stream.
const char * c_str() const
String representation.
Definition Pathname.h:112
static std::unique_ptr< zyppng::AbstractSpawnEngine > createDefaultEngine()
void setUsePty(const bool set=true)
std::string form(const char *format,...) __attribute__((format(printf
Printf style construction of std::string.
Definition String.cc:39
Easy-to use interface to the ZYPP dependency resolver.
auto eintrSafeCall(Fun &&function, Args &&... args)
void renumberFd(int origfd, int newfd)
AutoDispose<int> calling close
#define _(MSG)
Definition Gettext.h:39
#define DBG
Definition Logger.h:99
#define ERR
Definition Logger.h:102