libzypp: zypp-core/ExternalProgram.cc Source File

libzypp 17.38.8

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
27#include <zypp-core/AutoDispose.h>
28#include <zypp-core/base/Logger.h>
29#include <zypp-core/base/String.h>
30#include <zypp-core/base/Gettext.h>
31#include <zypp-core/ExternalProgram.h>
32#include <zypp-core/base/CleanerThread_p.h>
33
34#include <zypp-core/ng/base/private/linuxhelpers_p.h>
35#include <zypp-core/ng/io/private/forkspawnengine_p.h>
36
37using std::endl;
38
39#undef ZYPP_BASE_LOGGER_LOGGROUP
40#define ZYPP_BASE_LOGGER_LOGGROUP "zypp::exec"
41
42namespace zypp {
43
44 ExternalProgram::ExternalProgram()
45 {}
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
64 ExternalProgram::ExternalProgram( const Arguments & argv,
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
83 ExternalProgram::ExternalProgram( const Arguments & argv,
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
125 ExternalProgram::ExternalProgram( const char *binpath,
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
138 ExternalProgram::ExternalProgram( const char *binpath,
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
152 ExternalProgram::~ExternalProgram()
153 { }
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 {
173 _backend = zyppng::AbstractSpawnEngine::createDefaultEngine();
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;
339 ExternalProgram::close();
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
357 ExternalProgram::close()
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
425 ExternalProgram::kill()
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
438 bool ExternalProgram::kill(int sig)
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
451 ExternalProgram::running()
452 {
453 if ( !_backend ) return false;
454 return _backend->isRunning();
455 }
456
457 Pathname ExternalProgram::chroot() const
458 {
459 if ( !_backend ) return Pathname();
460 return _backend->chroot();
461 }
462
463
464 pid_t ExternalProgram::getpid()
465 {
466 if ( !running() )
467 return -1;
468 return _backend->pid();
469 }
470
471 const std::string &ExternalProgram::command() const
472 {
473 if ( !_backend ) {
474 static std::string empty;
475 return empty;
476 }
477 return _backend->executedCommand();
478 }
479
480 const std::string &ExternalProgram::execError() const
481 {
482 if ( !_backend ) {
483 static std::string empty;
484 return empty;
485 }
486 return _backend->execError();
487 }
488
489 // origfd will be accessible as newfd and closed (unless they were equal)
490 void ExternalProgram::renumber_fd (int origfd, int newfd)
491 {
492 return zyppng::renumberFd( origfd, newfd );
493 }
494
495 std::ostream & ExternalProgram::operator>>( std::ostream & out_r )
496 {
497 setBlocking( true );
498 for ( std::string line = receiveLine(); line.length(); line = receiveLine() )
499 out_r << line;
500 return out_r;
501 }
502
504 //
505 // class ExternalProgramWithStderr
506 //
508
509 namespace externalprogram
510 {
511 EarlyPipe::EarlyPipe()
512 {
513 _fds[R] = _fds[W] = -1;
514#ifdef HAVE_PIPE2
515 ::pipe2( _fds, O_NONBLOCK );
516#else
517 ::pipe( _fds );
518 ::fcntl(_fds[R], F_SETFD, O_NONBLOCK );
519 ::fcntl(_fds[W], F_SETFD, O_NONBLOCK );
520#endif
521 _stderr = ::fdopen( _fds[R], "r" );
522 }
523
524 EarlyPipe::~EarlyPipe()
525 {
526 closeW();
527 if ( _stderr )
528 ::fclose( _stderr );
529 }
530 } // namespace externalprogram
531
532 bool ExternalProgramWithStderr::stderrGetUpTo( std::string & retval_r, const char delim_r, bool returnDelim_r )
533 {
534 if ( ! _stderr )
535 return false;
536 if ( delim_r && ! _buffer.empty() )
537 {
538 // check for delim already in buffer
539 std::string::size_type pos( _buffer.find( delim_r ) );
540 if ( pos != std::string::npos )
541 {
542 retval_r = _buffer.substr( 0, returnDelim_r ? pos+1 : pos );
543 _buffer.erase( 0, pos+1 );
544 return true;
545 }
546 }
547 ::clearerr( _stderr );
548 do {
549 int ch = fgetc( _stderr );
550 if ( ch != EOF )
551 {
552 if ( ch != delim_r || ! delim_r )
553 _buffer.push_back( ch );
554 else
555 {
556 if ( returnDelim_r )
557 _buffer.push_back( delim_r );
558 break;
559 }
560 }
561 else if ( ::feof( _stderr ) )
562 {
563 if ( _buffer.empty() )
564 return false;
565 break;
566 }
567 else if ( errno != EINTR )
568 return false;
569 } while ( true );
570 // HERE: we left after readig at least one char (\n)
571 retval_r.swap( _buffer );
572 _buffer.clear();
573 return true;
574 }
575
576
577} // namespace zypp
AutoDispose.h
CleanerThread_p.h
ExternalProgram.h
Gettext.h
_
#define _(MSG)
Definition Gettext.h:39
Logger.h
DBG
#define DBG
Definition Logger.h:102
ERR
#define ERR
Definition Logger.h:105
String.h
GPollFD
struct _GPollFD GPollFD
Definition ZYppImpl.h:26
zypp::AutoDispose::resetDispose
void resetDispose()
Set no dispose function.
Definition AutoDispose.h:171
zypp::Errno
Convenience errno wrapper.
Definition Errno.h:26
zypp::ExternalProgramWithStderr::stderrGetUpTo
bool stderrGetUpTo(std::string &retval_r, const char delim_r, bool returnDelim_r=false)
Read data up to delim_r from stderr (nonblocking).
Definition ExternalProgram.cc:532
zypp::ExternalProgramWithStderr::_buffer
std::string _buffer
Definition ExternalProgram.h:310
zypp::ExternalProgram::ExternalProgram
ExternalProgram()
Start an external program by giving the arguments as an arry of char *pointers.
Definition ExternalProgram.cc:44
zypp::ExternalProgram::close
int close() override
Wait for the progamm to complete.
Definition ExternalProgram.cc:357
zypp::ExternalProgram::command
const std::string & command() const
The command we're executing.
Definition ExternalProgram.cc:471
zypp::ExternalProgram::operator>>
std::ostream & operator>>(std::ostream &out_r)
Redirect all command output to an ostream.
Definition ExternalProgram.cc:495
zypp::ExternalProgram::start_program
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)
Definition ExternalProgram.cc:157
zypp::ExternalProgram::Arguments
std::vector< std::string > Arguments
Definition ExternalProgram.h:68
zypp::ExternalProgram::~ExternalProgram
~ExternalProgram() override
Definition ExternalProgram.cc:152
zypp::ExternalProgram::renumber_fd
static void renumber_fd(int origfd, int newfd)
origfd will be accessible as newfd and closed (unless they were equal)
Definition ExternalProgram.cc:490
zypp::ExternalProgram::chroot
Pathname chroot() const
Return the configured chroot path or empty.
Definition ExternalProgram.cc:457
zypp::ExternalProgram::Environment
std::map< std::string, std::string > Environment
For passing additional environment variables to set.
Definition ExternalProgram.h:84
zypp::ExternalProgram::kill
bool kill()
Kill the program.
Definition ExternalProgram.cc:425
zypp::ExternalProgram::getpid
pid_t getpid()
return pid
Definition ExternalProgram.cc:464
zypp::ExternalProgram::execError
const std::string & execError() const
Some detail telling why the execution failed, if it failed.
Definition ExternalProgram.cc:480
zypp::ExternalProgram::running
bool running()
Return whether program is running.
Definition ExternalProgram.cc:451
zypp::ExternalProgram::Stderr_Disposition
Stderr_Disposition
Define symbols for different policies on the handling of stderr.
Definition ExternalProgram.h:74
zypp::ExternalProgram::Stderr_To_FileDesc
@ Stderr_To_FileDesc
Definition ExternalProgram.h:78
zypp::ExternalProgram::Discard_Stderr
@ Discard_Stderr
Definition ExternalProgram.h:76
zypp::ExternalProgram::Stderr_To_Stdout
@ Stderr_To_Stdout
Definition ExternalProgram.h:77
zypp::ExternalProgram::Normal_Stderr
@ Normal_Stderr
Definition ExternalProgram.h:75
zypp::ExternalProgram::_backend
std::unique_ptr< zyppng::AbstractSpawnEngine > _backend
Definition ExternalProgram.h:235
zypp::externalprogram::ExternalDataSource::linebuffer_size
size_t linebuffer_size
Definition ExternalDataSource.h:35
zypp::externalprogram::ExternalDataSource::inputfile
FILE * inputfile
Definition ExternalDataSource.h:30
zypp::externalprogram::ExternalDataSource::setBlocking
void setBlocking(bool mode)
Set the blocking mode of the input stream.
Definition ExternalDataSource.cc:110
zypp::externalprogram::ExternalDataSource::outputfile
FILE * outputfile
Definition ExternalDataSource.h:31
zypp::externalprogram::ExternalDataSource::inputFile
FILE * inputFile() const
Return the input stream.
Definition ExternalDataSource.h:118
zypp::externalprogram::ExternalDataSource::receiveLine
std::string receiveLine()
Read one line from the input stream.
Definition ExternalDataSource.cc:115
zypp::externalprogram::ExternalDataSource::linebuffer
char * linebuffer
Definition ExternalDataSource.h:34
zypp::filesystem::Pathname
Pathname.
Definition Pathname.h:48
zypp::filesystem::Pathname::c_str
const char * c_str() const
String representation.
Definition Pathname.h:113
zyppng::AbstractSpawnEngine::createDefaultEngine
static std::unique_ptr< zyppng::AbstractSpawnEngine > createDefaultEngine()
Definition abstractspawnengine.cc:64
zyppng::ForkSpawnEngine
Definition forkspawnengine_p.h:27
zyppng::ForkSpawnEngine::setUsePty
void setUsePty(const bool set=true)
Definition forkspawnengine.cc:465
forkspawnengine_p.h
linuxhelpers_p.h
zypp::externalprogram
Definition ExternalDataSource.cc:33
zypp::str::form
std::string form(const char *format,...) __attribute__((format(printf
Printf style construction of std::string.
Definition String.cc:39
zypp
Easy-to use interface to the ZYPP dependency resolver.
Definition CodePitfalls.doc:2
zyppng::eintrSafeCall
auto eintrSafeCall(Fun &&function, Args &&... args)
Definition linuxhelpers_p.h:50
zyppng::renumberFd
void renumberFd(int origfd, int newfd)
Definition linuxhelpers.cc:50
zypp::AutoFD
AutoDispose<int> calling close
Definition AutoDispose.h:310
zypp::externalprogram::EarlyPipe::~EarlyPipe
~EarlyPipe()
Definition ExternalProgram.cc:524
zypp::externalprogram::EarlyPipe::closeW
void closeW()
Definition ExternalProgram.h:259
zypp::externalprogram::EarlyPipe::_fds
int _fds[2]
Definition ExternalProgram.h:263
zypp::externalprogram::EarlyPipe::EarlyPipe
EarlyPipe()
Definition ExternalProgram.cc:511
zypp::externalprogram::EarlyPipe::_stderr
FILE * _stderr
Definition ExternalProgram.h:262
zypp::externalprogram::EarlyPipe::R
@ R
Definition ExternalProgram.h:256
zypp::externalprogram::EarlyPipe::W
@ W
Definition ExternalProgram.h:256