libzypp 17.31.23
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 fd_set rfds;
379 FD_ZERO( &rfds );
380 FD_SET( inputfileFd, &rfds );
381
382 /* Wait up to 1 seconds. */
383 struct timeval tv;
384 tv.tv_sec = (delay < 0 ? 1 : 0);
385 tv.tv_usec = (delay < 0 ? 0 : delay*100000);
386 if ( delay >= 0 && ++delay > 9 )
387 delay = -1;
388 int retval = select( inputfileFd+1, &rfds, NULL, NULL, &tv );
389
390 if ( retval == -1 )
391 {
392 if ( errno != EINTR ) {
393 ERR << "select error: " << strerror(errno) << endl;
394 break;
395 }
396 }
397 else if ( retval )
398 {
399 // Data is available now.
400 static size_t linebuffer_size = 0; // static because getline allocs
401 static char * linebuffer = 0; // and reallocs if buffer is too small
402 getline( &linebuffer, &linebuffer_size, inputfile );
403 // ::feof check is important as select returns
404 // positive if the file was closed.
405 if ( ::feof( inputfile ) )
406 break;
407 clearerr( inputfile );
408 }
409 else
410 {
411 // No data within time.
412 if ( ! _backend->isRunning() )
413 break;
414 }
415 } while ( true );
416 }
417
418 // wait for the process to end)
419 _backend->isRunning( true );
420 }
421
422 ExternalDataSource::close();
423 return _backend->exitStatus();
424 }
425
426 bool
428 {
429 if ( _backend && _backend->isRunning() )
430 {
431 if ( ::kill( _backend->pid(), SIGKILL) == -1 ) {
432 ERR << "Failed to kill PID " << _backend->pid() << " with error: " << Errno() << std::endl;
433 return false;
434 }
435 close();
436 }
437 return true;
438 }
439
441 {
442 if ( _backend && _backend->isRunning() )
443 {
444 if ( ::kill( _backend->pid(), sig ) == -1 ) {
445 ERR << "Failed to kill PID " << _backend->pid() << " with error: " << Errno() << std::endl;
446 return false;
447 }
448 }
449 return true;
450 }
451
452 bool
454 {
455 if ( !_backend ) return false;
456 return _backend->isRunning();
457 }
458
460 {
461 if ( !running() )
462 return -1;
463 return _backend->pid();
464 }
465
466 const std::string &ExternalProgram::command() const
467 {
468 if ( !_backend ) {
469 static std::string empty;
470 return empty;
471 }
472 return _backend->executedCommand();
473 }
474
475 const std::string &ExternalProgram::execError() const
476 {
477 if ( !_backend ) {
478 static std::string empty;
479 return empty;
480 }
481 return _backend->execError();
482 }
483
484 // origfd will be accessible as newfd and closed (unless they were equal)
485 void ExternalProgram::renumber_fd (int origfd, int newfd)
486 {
487 return zyppng::renumberFd( origfd, newfd );
488 }
489
490 std::ostream & ExternalProgram::operator>>( std::ostream & out_r )
491 {
492 setBlocking( true );
493 for ( std::string line = receiveLine(); line.length(); line = receiveLine() )
494 out_r << line;
495 return out_r;
496 }
497
499 //
500 // class ExternalProgramWithStderr
501 //
503
504 namespace externalprogram
505 {
507 {
508 _fds[R] = _fds[W] = -1;
509#ifdef HAVE_PIPE2
510 ::pipe2( _fds, O_NONBLOCK );
511#else
512 ::pipe( _fds );
513 ::fcntl(_fds[R], F_SETFD, O_NONBLOCK );
514 ::fcntl(_fds[W], F_SETFD, O_NONBLOCK );
515#endif
516 _stderr = ::fdopen( _fds[R], "r" );
517 }
518
520 {
521 closeW();
522 if ( _stderr )
523 ::fclose( _stderr );
524 }
525 } // namespace externalprogram
526
527 bool ExternalProgramWithStderr::stderrGetUpTo( std::string & retval_r, const char delim_r, bool returnDelim_r )
528 {
529 if ( ! _stderr )
530 return false;
531 if ( delim_r && ! _buffer.empty() )
532 {
533 // check for delim already in buffer
534 std::string::size_type pos( _buffer.find( delim_r ) );
535 if ( pos != std::string::npos )
536 {
537 retval_r = _buffer.substr( 0, returnDelim_r ? pos+1 : pos );
538 _buffer.erase( 0, pos+1 );
539 return true;
540 }
541 }
542 ::clearerr( _stderr );
543 do {
544 int ch = fgetc( _stderr );
545 if ( ch != EOF )
546 {
547 if ( ch != delim_r || ! delim_r )
548 _buffer.push_back( ch );
549 else
550 {
551 if ( returnDelim_r )
552 _buffer.push_back( delim_r );
553 break;
554 }
555 }
556 else if ( ::feof( _stderr ) )
557 {
558 if ( _buffer.empty() )
559 return false;
560 break;
561 }
562 else if ( errno != EINTR )
563 return false;
564 } while ( true );
565 // HERE: we left after readig at least one char (\n)
566 retval_r.swap( _buffer );
567 _buffer.clear();
568 return true;
569 }
570
571
572} // 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