libzypp 17.28.8
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
322 inputfile = fdopen( childStdoutParentFd, "r" );
323 childStdoutParentFd.resetDispose();
324 outputfile = fdopen( childStdinParentFd, "w" );
325 childStdinParentFd.resetDispose();
326
327 if (!inputfile || !outputfile)
328 {
329 ERR << "Cannot create streams to external program " << argv[0] << endl;
331 }
332 } else {
333 // Fork failed, exit code and status was set by backend
334 return;
335 }
336 }
337
338 int
340 {
341 if ( !_backend ) {
342 ExternalDataSource::close();
343 return -1;
344 }
345
346 if ( _backend->isRunning() )
347 {
348 if ( inputFile() )
349 {
350 // Discard any output instead of closing the pipe,
351 // but watch out for the command exiting while some
352 // subprocess keeps the filedescriptor open.
353 setBlocking( false );
354 FILE * inputfile = inputFile();
355 int inputfileFd = ::fileno( inputfile );
356 long delay = 0;
357 do
358 {
359 /* Watch inputFile to see when it has input. */
360 fd_set rfds;
361 FD_ZERO( &rfds );
362 FD_SET( inputfileFd, &rfds );
363
364 /* Wait up to 1 seconds. */
365 struct timeval tv;
366 tv.tv_sec = (delay < 0 ? 1 : 0);
367 tv.tv_usec = (delay < 0 ? 0 : delay*100000);
368 if ( delay >= 0 && ++delay > 9 )
369 delay = -1;
370 int retval = select( inputfileFd+1, &rfds, NULL, NULL, &tv );
371
372 if ( retval == -1 )
373 {
374 if ( errno != EINTR ) {
375 ERR << "select error: " << strerror(errno) << endl;
376 break;
377 }
378 }
379 else if ( retval )
380 {
381 // Data is available now.
382 static size_t linebuffer_size = 0; // static because getline allocs
383 static char * linebuffer = 0; // and reallocs if buffer is too small
385 // ::feof check is important as select returns
386 // positive if the file was closed.
387 if ( ::feof( inputfile ) )
388 break;
389 clearerr( inputfile );
390 }
391 else
392 {
393 // No data within time.
394 if ( ! _backend->isRunning() )
395 break;
396 }
397 } while ( true );
398 }
399
400 // wait for the process to end)
401 _backend->isRunning( true );
402 }
403
404 ExternalDataSource::close();
405 return _backend->exitStatus();
406 }
407
408 bool
410 {
411 if ( _backend && _backend->isRunning() )
412 {
413 ::kill( _backend->pid(), SIGKILL);
414 close();
415 }
416 return true;
417 }
418
420 {
421 if ( _backend && _backend->isRunning() )
422 {
423 ::kill( _backend->pid(), sig );
424 }
425 return true;
426 }
427
428 bool
430 {
431 if ( !_backend ) return false;
432 return _backend->isRunning();
433 }
434
436 {
437 if ( !running() )
438 return -1;
439 return _backend->pid();
440 }
441
442 const std::string &ExternalProgram::command() const
443 {
444 if ( !_backend ) {
445 static std::string empty;
446 return empty;
447 }
448 return _backend->executedCommand();
449 }
450
451 const std::string &ExternalProgram::execError() const
452 {
453 if ( !_backend ) {
454 static std::string empty;
455 return empty;
456 }
457 return _backend->execError();
458 }
459
460 // origfd will be accessible as newfd and closed (unless they were equal)
461 void ExternalProgram::renumber_fd (int origfd, int newfd)
462 {
463 return zyppng::renumberFd( origfd, newfd );
464 }
465
466 std::ostream & ExternalProgram::operator>>( std::ostream & out_r )
467 {
468 setBlocking( true );
469 for ( std::string line = receiveLine(); line.length(); line = receiveLine() )
470 out_r << line;
471 return out_r;
472 }
473
475 //
476 // class ExternalProgramWithStderr
477 //
479
480 namespace externalprogram
481 {
483 {
484 _fds[R] = _fds[W] = -1;
485#ifdef HAVE_PIPE2
486 ::pipe2( _fds, O_NONBLOCK );
487#else
488 ::pipe( _fds );
489 ::fcntl(_fds[R], F_SETFD, O_NONBLOCK );
490 ::fcntl(_fds[W], F_SETFD, O_NONBLOCK );
491#endif
492 _stderr = ::fdopen( _fds[R], "r" );
493 }
494
496 {
497 closeW();
498 if ( _stderr )
499 ::fclose( _stderr );
500 }
501 } // namespace externalprogram
502
503 bool ExternalProgramWithStderr::stderrGetUpTo( std::string & retval_r, const char delim_r, bool returnDelim_r )
504 {
505 if ( ! _stderr )
506 return false;
507 if ( delim_r && ! _buffer.empty() )
508 {
509 // check for delim already in buffer
510 std::string::size_type pos( _buffer.find( delim_r ) );
511 if ( pos != std::string::npos )
512 {
513 retval_r = _buffer.substr( 0, returnDelim_r ? pos+1 : pos );
514 _buffer.erase( 0, pos+1 );
515 return true;
516 }
517 }
518 ::clearerr( _stderr );
519 do {
520 int ch = fgetc( _stderr );
521 if ( ch != EOF )
522 {
523 if ( ch != delim_r || ! delim_r )
524 _buffer.push_back( ch );
525 else
526 {
527 if ( returnDelim_r )
528 _buffer.push_back( delim_r );
529 break;
530 }
531 }
532 else if ( ::feof( _stderr ) )
533 {
534 if ( _buffer.empty() )
535 return false;
536 break;
537 }
538 else if ( errno != EINTR )
539 return false;
540 } while ( true );
541 // HERE: we left after readig at least one char (\n)
542 retval_r.swap( _buffer );
543 _buffer.clear();
544 return true;
545 }
546
547
548} // namespace zypp
void resetDispose()
Set no dispose function.
Definition: AutoDispose.h:164
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 getline(std::istream &str)
Read one line from stream.
Definition: IOStream.cc:33
SolvableIdType size_type
Definition: PoolMember.h:126
std::string strerror(int errno_r)
Return string describing the error_r code.
Definition: String.cc:53
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:283
#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