libzypp 17.31.28
ExternalProgram.cc
Go to the documentation of this file.
1/*---------------------------------------------------------------------\
2| ____ _ __ __ ___ |
3| |__ / \ / / . \ . \ |
4| / / \ V /| _/ _/ |
5| / /__ | | | | | | |
6| /_____||_| |_| |_| |
7| |
8\---------------------------------------------------------------------*/
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>
33
34#include <zypp-core/zyppng/base/private/linuxhelpers_p.h>
35#include <zypp-core/zyppng/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
45 {}
46
47
48 ExternalProgram::ExternalProgram( 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_( i, argv.begin(), argv.end() )
74 {
75 argvp[c] = i->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_( i, argv.begin(), argv.end() )
94 {
95 argvp[c] = i->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
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, slave_tty; // fds for pair of ttys
240
241 // Create pair of ttys
242 DBG << "Using ttys for communication with " << argv[0] << endl;
243 if (openpty (&master_tty, &slave_tty, 0, 0, 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 = g_poll( &rfd, 1, timeout );
390
391 if ( retval == -1 )
392 {
393 if ( errno != EINTR ) {
394 ERR << "select error: " << strerror(errno) << endl;
395 break;
396 }
397 }
398 else if ( retval )
399 {
400 // Data is available now.
401 static size_t linebuffer_size = 0; // static because getline allocs
402 static char * linebuffer = 0; // and reallocs if buffer is too small
403 getline( &linebuffer, &linebuffer_size, inputfile );
404 // ::feof check is important as select returns
405 // positive if the file was closed.
406 if ( ::feof( inputfile ) )
407 break;
408 clearerr( inputfile );
409 }
410 else
411 {
412 // No data within time.
413 if ( ! _backend->isRunning() )
414 break;
415 }
416 } while ( true );
417 }
418
419 // wait for the process to end)
420 _backend->isRunning( true );
421 }
422
423 ExternalDataSource::close();
424 return _backend->exitStatus();
425 }
426
427 bool
429 {
430 if ( _backend && _backend->isRunning() )
431 {
432 if ( ::kill( _backend->pid(), SIGKILL) == -1 ) {
433 ERR << "Failed to kill PID " << _backend->pid() << " with error: " << Errno() << std::endl;
434 return false;
435 }
436 close();
437 }
438 return true;
439 }
440
442 {
443 if ( _backend && _backend->isRunning() )
444 {
445 if ( ::kill( _backend->pid(), sig ) == -1 ) {
446 ERR << "Failed to kill PID " << _backend->pid() << " with error: " << Errno() << std::endl;
447 return false;
448 }
449 }
450 return true;
451 }
452
453 bool
455 {
456 if ( !_backend ) return false;
457 return _backend->isRunning();
458 }
459
461 {
462 if ( !running() )
463 return -1;
464 return _backend->pid();
465 }
466
467 const std::string &ExternalProgram::command() const
468 {
469 if ( !_backend ) {
470 static std::string empty;
471 return empty;
472 }
473 return _backend->executedCommand();
474 }
475
476 const std::string &ExternalProgram::execError() const
477 {
478 if ( !_backend ) {
479 static std::string empty;
480 return empty;
481 }
482 return _backend->execError();
483 }
484
485 // origfd will be accessible as newfd and closed (unless they were equal)
486 void ExternalProgram::renumber_fd (int origfd, int newfd)
487 {
488 return zyppng::renumberFd( origfd, newfd );
489 }
490
491 std::ostream & ExternalProgram::operator>>( std::ostream & out_r )
492 {
493 setBlocking( true );
494 for ( std::string line = receiveLine(); line.length(); line = receiveLine() )
495 out_r << line;
496 return out_r;
497 }
498
500 //
501 // class ExternalProgramWithStderr
502 //
504
505 namespace externalprogram
506 {
508 {
509 _fds[R] = _fds[W] = -1;
510#ifdef HAVE_PIPE2
511 ::pipe2( _fds, O_NONBLOCK );
512#else
513 ::pipe( _fds );
514 ::fcntl(_fds[R], F_SETFD, O_NONBLOCK );
515 ::fcntl(_fds[W], F_SETFD, O_NONBLOCK );
516#endif
517 _stderr = ::fdopen( _fds[R], "r" );
518 }
519
521 {
522 closeW();
523 if ( _stderr )
524 ::fclose( _stderr );
525 }
526 } // namespace externalprogram
527
528 bool ExternalProgramWithStderr::stderrGetUpTo( std::string & retval_r, const char delim_r, bool returnDelim_r )
529 {
530 if ( ! _stderr )
531 return false;
532 if ( delim_r && ! _buffer.empty() )
533 {
534 // check for delim already in buffer
535 std::string::size_type pos( _buffer.find( delim_r ) );
536 if ( pos != std::string::npos )
537 {
538 retval_r = _buffer.substr( 0, returnDelim_r ? pos+1 : pos );
539 _buffer.erase( 0, pos+1 );
540 return true;
541 }
542 }
543 ::clearerr( _stderr );
544 do {
545 int ch = fgetc( _stderr );
546 if ( ch != EOF )
547 {
548 if ( ch != delim_r || ! delim_r )
549 _buffer.push_back( ch );
550 else
551 {
552 if ( returnDelim_r )
553 _buffer.push_back( delim_r );
554 break;
555 }
556 }
557 else if ( ::feof( _stderr ) )
558 {
559 if ( _buffer.empty() )
560 return false;
561 break;
562 }
563 else if ( errno != EINTR )
564 return false;
565 } while ( true );
566 // HERE: we left after readig at least one char (\n)
567 retval_r.swap( _buffer );
568 _buffer.clear();
569 return true;
570 }
571
572
573} // namespace zypp
void resetDispose()
Set no dispose function.
Definition: AutoDispose.h:180
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.
const std::string & command() const
The command we're executing.
std::ostream & operator>>(std::ostream &out_r)
Redirect all command output to an ostream.
std::map< std::string, std::string > Environment
For passing additional environment variables to set.
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)
static void renumber_fd(int origfd, int newfd)
origfd will be accessible as newfd and closed (unless they were equal)
std::vector< std::string > Arguments
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.
int close()
Wait for the progamm to complete.
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:110
std::string form(const char *format,...) __attribute__((format(printf
Printf style construction of std::string.
Definition: String.cc:36
Easy-to use interface to the ZYPP dependency resolver.
Definition: CodePitfalls.doc:2
AutoDispose<int> calling ::close
Definition: AutoDispose.h:302
#define for_(IT, BEG, END)
Convenient for-loops using iterator.
Definition: Easy.h:28
#define _(MSG)
Definition: Gettext.h:37
#define DBG
Definition: Logger.h:95
#define ERR
Definition: Logger.h:98