libzypp 17.25.7
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/base/Logger.h>
28#include <zypp/base/String.h>
29#include <zypp/base/Gettext.h>
32
33using std::endl;
34
35#undef ZYPP_BASE_LOGGER_LOGGROUP
36#define ZYPP_BASE_LOGGER_LOGGROUP "zypp::exec"
37
38namespace zypp {
39
41 : use_pty (false)
42 , pid( -1 )
43 {}
44
45
46 ExternalProgram::ExternalProgram( std::string commandline,
47 Stderr_Disposition stderr_disp,
48 bool use_pty,
49 int stderr_fd,
50 bool default_locale,
51 const Pathname & root )
52 : use_pty (use_pty)
53 , pid( -1 )
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() );
62 }
63
64
66 Stderr_Disposition stderr_disp,
67 bool use_pty,
68 int stderr_fd,
69 bool default_locale,
70 const Pathname & root )
71 : use_pty (use_pty)
72 , pid( -1 )
73 {
74 const char * argvp[argv.size() + 1];
75 unsigned c = 0;
76 for_( i, argv.begin(), argv.end() )
77 {
78 argvp[c] = i->c_str();
79 ++c;
80 }
81 argvp[c] = 0;
82
83 start_program( argvp, Environment(), stderr_disp, stderr_fd, default_locale, root.c_str() );
84 }
85
86
88 const Environment & environment,
89 Stderr_Disposition stderr_disp,
90 bool use_pty,
91 int stderr_fd,
92 bool default_locale,
93 const Pathname & root )
94 : use_pty (use_pty)
95 , pid( -1 )
96 {
97 const char * argvp[argv.size() + 1];
98 unsigned c = 0;
99 for_( i, argv.begin(), argv.end() )
100 {
101 argvp[c] = i->c_str();
102 ++c;
103 }
104 argvp[c] = 0;
105
106 start_program( argvp, environment, stderr_disp, stderr_fd, default_locale, root.c_str() );
107 }
108
109
110
111 ExternalProgram::ExternalProgram( const char *const *argv,
112 Stderr_Disposition stderr_disp,
113 bool use_pty,
114 int stderr_fd,
115 bool default_locale,
116 const Pathname & root )
117 : use_pty (use_pty)
118 , pid( -1 )
119 {
120 start_program( argv, Environment(), stderr_disp, stderr_fd, default_locale, root.c_str() );
121 }
122
123
124 ExternalProgram::ExternalProgram( const char *const * argv,
125 const Environment & environment,
126 Stderr_Disposition stderr_disp,
127 bool use_pty,
128 int stderr_fd,
129 bool default_locale,
130 const Pathname & root )
131 : use_pty (use_pty)
132 , pid( -1 )
133 {
134 start_program( argv, environment, stderr_disp, stderr_fd, default_locale, root.c_str() );
135 }
136
137
139 const char *const *argv_1,
140 bool use_pty )
141 : use_pty (use_pty)
142 , pid( -1 )
143 {
144 int i = 0;
145 while (argv_1[i++])
146 ;
147 const char *argv[i + 1];
148 argv[0] = binpath;
149 memcpy( &argv[1], argv_1, (i - 1) * sizeof (char *) );
150 start_program( argv, Environment() );
151 }
152
153
155 const char *const *argv_1,
156 const Environment & environment,
157 bool use_pty )
158 : use_pty (use_pty)
159 , pid( -1 )
160 {
161 int i = 0;
162 while (argv_1[i++])
163 ;
164 const char *argv[i + 1];
165 argv[0] = binpath;
166 memcpy( &argv[1], argv_1, (i - 1) * sizeof (char *) );
167 start_program( argv, environment );
168 }
169
170
172 {
173 if ( running() ) {
174 // we got destructed while the external process is still alive
175 // make sure the zombie is cleaned up once it exits
177 }
178 }
179
180
181
182 void ExternalProgram::start_program(const char *const *argv,
183 const Environment & environment,
184 Stderr_Disposition stderr_disp,
185 int stderr_fd,
186 bool default_locale,
187 const char * root , bool switch_pgid, bool die_with_parent )
188 {
189 pid = -1;
190 _exitStatus = 0;
191 int to_external[2], from_external[2]; // fds for pair of pipes
192 int master_tty, slave_tty; // fds for pair of ttys
193
194 // retrieve options at beginning of arglist
195 const char * redirectStdin = nullptr; // <[file]
196 const char * redirectStdout = nullptr; // >[file]
197 const char * chdirTo = nullptr; // #/[path]
198
199 if ( root )
200 {
201 if ( root[0] == '\0' )
202 {
203 root = nullptr; // ignore empty root
204 }
205 else if ( root[0] == '/' && root[1] == '\0' )
206 {
207 // If root is '/' do not chroot, but chdir to '/'
208 // unless arglist defines another dir.
209 chdirTo = "/";
210 root = nullptr;
211 }
212 }
213
214 for ( bool strip = false; argv[0]; ++argv )
215 {
216 strip = false;
217 switch ( argv[0][0] )
218 {
219 case '<':
220 strip = true;
221 redirectStdin = argv[0]+1;
222 if ( *redirectStdin == '\0' )
223 redirectStdin = "/dev/null";
224 break;
225
226 case '>':
227 strip = true;
228 redirectStdout = argv[0]+1;
229 if ( *redirectStdout == '\0' )
230 redirectStdout = "/dev/null";
231 break;
232
233 case '#':
234 strip = true;
235 if ( argv[0][1] == '/' ) // #/[path]
236 chdirTo = argv[0]+1;
237 break;
238 }
239 if ( ! strip )
240 break;
241 }
242
243 // do not remove the single quotes around every argument, copy&paste of
244 // command to shell will not work otherwise!
245 {
246 std::stringstream cmdstr;
247 for (int i = 0; argv[i]; i++)
248 {
249 if (i>0) cmdstr << ' ';
250 cmdstr << '\'';
251 cmdstr << argv[i];
252 cmdstr << '\'';
253 }
254 if ( redirectStdin )
255 cmdstr << " < '" << redirectStdin << "'";
256 if ( redirectStdout )
257 cmdstr << " > '" << redirectStdout << "'";
258 _command = cmdstr.str();
259 }
260 DBG << "Executing" << (default_locale?"[C] ":" ") << _command << endl;
261
262
263 if (use_pty)
264 {
265 // Create pair of ttys
266 DBG << "Using ttys for communication with " << argv[0] << endl;
267 if (openpty (&master_tty, &slave_tty, 0, 0, 0) != 0)
268 {
269 _execError = str::form( _("Can't open pty (%s)."), strerror(errno) );
270 _exitStatus = 126;
271 ERR << _execError << endl;
272 return;
273 }
274 }
275 else
276 {
277 // Create pair of pipes
278 if (pipe (to_external) != 0 || pipe (from_external) != 0)
279 {
280 _execError = str::form( _("Can't open pipe (%s)."), strerror(errno) );
281 _exitStatus = 126;
282 ERR << _execError << endl;
283 return;
284 }
285 }
286
287 pid_t ppid_before_fork = ::getpid();
288
289 // Create module process
290 if ((pid = fork()) == 0)
291 {
293 // Don't write to the logfile after fork!
295 if (use_pty)
296 {
297 setsid();
298 if(slave_tty != 1)
299 dup2 (slave_tty, 1); // set new stdout
300 renumber_fd (slave_tty, 0); // set new stdin
301 ::close(master_tty); // Belongs to father process
302
303 // We currently have no controlling terminal (due to setsid).
304 // The first open call will also set the new ctty (due to historical
305 // unix guru knowledge ;-) )
306
307 char name[512];
308 ttyname_r(slave_tty, name, sizeof(name));
309 ::close(open(name, O_RDONLY));
310 }
311 else
312 {
313 if ( switch_pgid )
314 setpgid( 0, 0);
315 renumber_fd (to_external[0], 0); // set new stdin
316 ::close(from_external[0]); // Belongs to father process
317
318 renumber_fd (from_external[1], 1); // set new stdout
319 ::close(to_external [1]); // Belongs to father process
320 }
321
322 if ( redirectStdin )
323 {
324 ::close( 0 );
325 int inp_fd = open( redirectStdin, O_RDONLY );
326 dup2( inp_fd, 0 );
327 }
328
329 if ( redirectStdout )
330 {
331 ::close( 1 );
332 int inp_fd = open( redirectStdout, O_WRONLY|O_CREAT|O_APPEND, 0600 );
333 dup2( inp_fd, 1 );
334 }
335
336 // Handle stderr
337 if (stderr_disp == Discard_Stderr)
338 {
339 int null_fd = open("/dev/null", O_WRONLY);
340 dup2(null_fd, 2);
341 ::close(null_fd);
342 }
343 else if (stderr_disp == Stderr_To_Stdout)
344 {
345 dup2(1, 2);
346 }
347 else if (stderr_disp == Stderr_To_FileDesc)
348 {
349 // Note: We don't have to close anything regarding stderr_fd.
350 // Our caller is responsible for that.
351 dup2 (stderr_fd, 2);
352 }
353
354 for ( Environment::const_iterator it = environment.begin(); it != environment.end(); ++it ) {
355 setenv( it->first.c_str(), it->second.c_str(), 1 );
356 }
357
358 if(default_locale)
359 setenv("LC_ALL","C",1);
360
361 if(root)
362 {
363 if(chroot(root) == -1)
364 {
365 _execError = str::form( _("Can't chroot to '%s' (%s)."), root, strerror(errno) );
366 std::cerr << _execError << endl;// After fork log on stderr too
367 _exit (128); // No sense in returning! I am forked away!!
368 }
369 if ( ! chdirTo )
370 chdirTo = "/";
371 }
372
373 if ( chdirTo && chdir( chdirTo ) == -1 )
374 {
375 _execError = root ? str::form( _("Can't chdir to '%s' inside chroot '%s' (%s)."), chdirTo, root, strerror(errno) )
376 : str::form( _("Can't chdir to '%s' (%s)."), chdirTo, strerror(errno) );
377 std::cerr << _execError << endl;// After fork log on stderr too
378 _exit (128); // No sense in returning! I am forked away!!
379 }
380
381 // close all filedesctiptors above stderr
382 for ( int i = ::getdtablesize() - 1; i > 2; --i ) {
383 ::close( i );
384 }
385
386 if ( die_with_parent ) {
387 // process dies with us
388 int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
389 if (r == -1) {
390 //ignore if it did not work, worst case the process lives on after the parent dies
391 std::cerr << "Failed to set PR_SET_PDEATHSIG" << endl;// After fork log on stderr too
392 }
393
394 // test in case the original parent exited just
395 // before the prctl() call
396 pid_t ppidNow = getppid();
397 if (ppidNow != ppid_before_fork) {
398 std::cerr << "PPID changed from "<<ppid_before_fork<<" to "<< ppidNow << endl;// After fork log on stderr too
399 _exit(128);
400 }
401 }
402
403 execvp(argv[0], const_cast<char *const *>(argv));
404 // don't want to get here
405 _execError = str::form( _("Can't exec '%s' (%s)."), argv[0], strerror(errno) );
406 std::cerr << _execError << endl;// After fork log on stderr too
407 _exit (129); // No sense in returning! I am forked away!!
409 }
410
411 else if (pid == -1) // Fork failed, close everything.
412 {
413 _execError = str::form( _("Can't fork (%s)."), strerror(errno) );
414 _exitStatus = 127;
415 ERR << _execError << endl;
416
417 if (use_pty) {
418 ::close(master_tty);
419 ::close(slave_tty);
420 }
421 else {
422 ::close(to_external[0]);
423 ::close(to_external[1]);
424 ::close(from_external[0]);
425 ::close(from_external[1]);
426 }
427 }
428
429 else {
430 if (use_pty)
431 {
432 ::close(slave_tty); // belongs to child process
433 inputfile = fdopen(master_tty, "r");
434 outputfile = fdopen(master_tty, "w");
435 }
436 else
437 {
438 ::close(to_external[0]); // belongs to child process
439 ::close(from_external[1]); // belongs to child process
440 inputfile = fdopen(from_external[0], "r");
441 outputfile = fdopen(to_external[1], "w");
442 }
443
444 DBG << "pid " << pid << " launched" << endl;
445
446 if (!inputfile || !outputfile)
447 {
448 ERR << "Cannot create streams to external program " << argv[0] << endl;
449 close();
450 }
451 }
452 }
453
454
455 int
457 {
458 if (pid > 0)
459 {
460 if ( inputFile() )
461 {
462 // Discard any output instead of closing the pipe,
463 // but watch out for the command exiting while some
464 // subprocess keeps the filedescriptor open.
465 setBlocking( false );
466 FILE * inputfile = inputFile();
467 int inputfileFd = ::fileno( inputfile );
468 long delay = 0;
469 do
470 {
471 /* Watch inputFile to see when it has input. */
472 fd_set rfds;
473 FD_ZERO( &rfds );
474 FD_SET( inputfileFd, &rfds );
475
476 /* Wait up to 1 seconds. */
477 struct timeval tv;
478 tv.tv_sec = (delay < 0 ? 1 : 0);
479 tv.tv_usec = (delay < 0 ? 0 : delay*100000);
480 if ( delay >= 0 && ++delay > 9 )
481 delay = -1;
482 int retval = select( inputfileFd+1, &rfds, NULL, NULL, &tv );
483
484 if ( retval == -1 )
485 {
486 if ( errno != EINTR ) {
487 ERR << "select error: " << strerror(errno) << endl;
488 break;
489 }
490 }
491 else if ( retval )
492 {
493 // Data is available now.
494 static size_t linebuffer_size = 0; // static because getline allocs
495 static char * linebuffer = 0; // and reallocs if buffer is too small
497 // ::feof check is important as select returns
498 // positive if the file was closed.
499 if ( ::feof( inputfile ) )
500 break;
501 clearerr( inputfile );
502 }
503 else
504 {
505 // No data within time.
506 if ( ! running() )
507 break;
508 }
509 } while ( true );
510 }
511
512 if ( pid > 0 ) // bsc#1109877: must re-check! running() in the loop above may have already waited.
513 {
514 // Wait for child to exit
515 int ret;
516 int status = 0;
517 do
518 {
519 ret = waitpid(pid, &status, 0);
520 }
521 while (ret == -1 && errno == EINTR);
522
523 if (ret != -1)
524 {
525 _exitStatus = checkStatus( status );
526 }
527 pid = -1;
528 }
529 }
530
531 return _exitStatus;
532 }
533
534
536 {
537 if (WIFEXITED (status))
538 {
539 status = WEXITSTATUS (status);
540 if(status)
541 {
542 DBG << "Pid " << pid << " exited with status " << status << endl;
543 _execError = str::form( _("Command exited with status %d."), status );
544 }
545 else
546 {
547 // if 'launch' is logged, completion should be logged,
548 // even if successful.
549 DBG << "Pid " << pid << " successfully completed" << endl;
550 _execError.clear(); // empty if running or successfully completed
551 }
552 }
553 else if (WIFSIGNALED (status))
554 {
555 status = WTERMSIG (status);
556 WAR << "Pid " << pid << " was killed by signal " << status
557 << " (" << strsignal(status);
558 if (WCOREDUMP (status))
559 {
560 WAR << ", core dumped";
561 }
562 WAR << ")" << endl;
563 _execError = str::form( _("Command was killed by signal %d (%s)."), status, strsignal(status) );
564 status+=128;
565 }
566 else {
567 ERR << "Pid " << pid << " exited with unknown error" << endl;
568 _execError = _("Command exited with unknown error.");
569 }
570
571 return status;
572 }
573
574 bool
576 {
577 if (pid > 0)
578 {
579 ::kill(pid, SIGKILL);
580 close();
581 }
582 return true;
583 }
584
586 {
587 if (pid > 0)
588 {
589 ::kill(pid, sig);
590 }
591 return true;
592 }
593
594 bool
596 {
597 if ( pid < 0 ) return false;
598
599 int status = 0;
600 int p = waitpid( pid, &status, WNOHANG );
601 switch ( p )
602 {
603 case -1:
604 ERR << "waitpid( " << pid << ") returned error '" << strerror(errno) << "'" << endl;
605 return false;
606 break;
607 case 0:
608 return true; // still running
609 break;
610 }
611
612 // Here: completed...
613 _exitStatus = checkStatus( status );
614 pid = -1;
615 return false;
616 }
617
618 // origfd will be accessible as newfd and closed (unless they were equal)
619 void ExternalProgram::renumber_fd (int origfd, int newfd)
620 {
621 // It may happen that origfd is already the one we want
622 // (Although in our circumstances, that would mean somebody has closed
623 // our stdin or stdout... weird but has appened to Cray, #49797)
624 if (origfd != newfd)
625 {
626 dup2 (origfd, newfd);
627 ::close (origfd);
628 }
629 }
630
631 std::ostream & ExternalProgram::operator>>( std::ostream & out_r )
632 {
633 setBlocking( true );
634 for ( std::string line = receiveLine(); line.length(); line = receiveLine() )
635 out_r << line;
636 return out_r;
637 }
638
640 //
641 // class ExternalProgramWithStderr
642 //
644
645 namespace externalprogram
646 {
648 {
649 _fds[R] = _fds[W] = -1;
650#ifdef HAVE_PIPE2
651 ::pipe2( _fds, O_NONBLOCK );
652#else
653 ::pipe( _fds );
654 ::fcntl(_fds[R], F_SETFD, O_NONBLOCK );
655 ::fcntl(_fds[W], F_SETFD, O_NONBLOCK );
656#endif
657 _stderr = ::fdopen( _fds[R], "r" );
658 }
659
661 {
662 closeW();
663 if ( _stderr )
664 ::fclose( _stderr );
665 }
666 } // namespace externalprogram
667
668 bool ExternalProgramWithStderr::stderrGetUpTo( std::string & retval_r, const char delim_r, bool returnDelim_r )
669 {
670 if ( ! _stderr )
671 return false;
672 if ( delim_r && ! _buffer.empty() )
673 {
674 // check for delim already in buffer
675 std::string::size_type pos( _buffer.find( delim_r ) );
676 if ( pos != std::string::npos )
677 {
678 retval_r = _buffer.substr( 0, returnDelim_r ? pos+1 : pos );
679 _buffer.erase( 0, pos+1 );
680 return true;
681 }
682 }
683 ::clearerr( _stderr );
684 do {
685 int ch = fgetc( _stderr );
686 if ( ch != EOF )
687 {
688 if ( ch != delim_r || ! delim_r )
689 _buffer.push_back( ch );
690 else
691 {
692 if ( returnDelim_r )
693 _buffer.push_back( delim_r );
694 break;
695 }
696 }
697 else if ( ::feof( _stderr ) )
698 {
699 if ( _buffer.empty() )
700 return false;
701 break;
702 }
703 else if ( errno != EINTR )
704 return false;
705 } while ( true );
706 // HERE: we left after readig at least one char (\n)
707 retval_r.swap( _buffer );
708 _buffer.clear();
709 return true;
710 }
711
712
713} // namespace zypp
This file contains private API, it will change without notice.
#define for_(IT, BEG, END)
Convenient for-loops using iterator.
Definition: Easy.h:28
Interface to gettext.
#define _(MSG)
Definition: Gettext.h:37
#define DBG
Definition: Logger.h:78
#define ERR
Definition: Logger.h:81
#define WAR
Definition: Logger.h:80
static void watchPID(pid_t pid_r)
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.
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.
std::string _execError
Remember execution errors like failed fork/exec.
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
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 kill()
Kill the program.
std::string _command
Store the command we're executing.
pid_t getpid()
return pid
bool running()
Return whether program is running.
int close()
Wait for the progamm to complete.
bool use_pty
Set to true, if a pair of ttys is used for communication instead of a pair of pipes.
Stderr_Disposition
Define symbols for different policies on the handling of stderr.
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