00001
00002
00003
00004
00005
00006
00007
00008
00012 #define _GNU_SOURCE 1 // for ::getline
00013
00014 #include <signal.h>
00015 #include <errno.h>
00016 #include <unistd.h>
00017 #include <sys/wait.h>
00018 #include <fcntl.h>
00019 #include <pty.h>
00020 #include <stdlib.h>
00021
00022 #include <cstring>
00023 #include <iostream>
00024 #include <sstream>
00025
00026 #include "zypp/base/Logger.h"
00027 #include "zypp/base/String.h"
00028 #include "zypp/base/Gettext.h"
00029 #include "zypp/ExternalProgram.h"
00030
00031 using namespace std;
00032
00033 namespace zypp {
00034
00035 ExternalProgram::ExternalProgram()
00036 : use_pty (false)
00037 , pid( -1 )
00038 {
00039 }
00040
00041
00042 ExternalProgram::ExternalProgram( std::string commandline,
00043 Stderr_Disposition stderr_disp,
00044 bool use_pty,
00045 int stderr_fd,
00046 bool default_locale,
00047 const Pathname & root )
00048 : use_pty (use_pty)
00049 , pid( -1 )
00050 {
00051 const char *argv[4];
00052 argv[0] = "/bin/sh";
00053 argv[1] = "-c";
00054 argv[2] = commandline.c_str();
00055 argv[3] = 0;
00056
00057 const char* rootdir = NULL;
00058 if(!root.empty() && root != "/")
00059 {
00060 rootdir = root.asString().c_str();
00061 }
00062 Environment environment;
00063 start_program (argv, environment, stderr_disp, stderr_fd, default_locale, rootdir);
00064 }
00065
00066
00067 ExternalProgram::ExternalProgram (const Arguments &argv,
00068 Stderr_Disposition stderr_disp,
00069 bool use_pty, int stderr_fd,
00070 bool default_locale,
00071 const Pathname& root)
00072 : use_pty (use_pty)
00073 , pid( -1 )
00074 {
00075 const char * argvp[argv.size() + 1];
00076 unsigned c = 0;
00077 for_( i, argv.begin(), argv.end() )
00078 {
00079 argvp[c] = i->c_str();
00080 ++c;
00081 }
00082 argvp[c] = 0;
00083
00084 Environment environment;
00085 const char* rootdir = NULL;
00086 if(!root.empty() && root != "/")
00087 {
00088 rootdir = root.asString().c_str();
00089 }
00090 start_program (argvp, environment, stderr_disp, stderr_fd, default_locale, rootdir);
00091 }
00092
00093
00094 ExternalProgram::ExternalProgram (const Arguments &argv,
00095 const Environment & environment,
00096 Stderr_Disposition stderr_disp,
00097 bool use_pty, int stderr_fd,
00098 bool default_locale,
00099 const Pathname& root)
00100 : use_pty (use_pty)
00101 , pid( -1 )
00102 {
00103 const char * argvp[argv.size() + 1];
00104 unsigned c = 0;
00105 for_( i, argv.begin(), argv.end() )
00106 {
00107 argvp[c] = i->c_str();
00108 ++c;
00109 }
00110 argvp[c] = 0;
00111
00112 const char* rootdir = NULL;
00113 if(!root.empty() && root != "/")
00114 {
00115 rootdir = root.asString().c_str();
00116 }
00117 start_program (argvp, environment, stderr_disp, stderr_fd, default_locale, rootdir);
00118
00119 }
00120
00121
00122
00123
00124 ExternalProgram::ExternalProgram( const char *const *argv,
00125 Stderr_Disposition stderr_disp,
00126 bool use_pty,
00127 int stderr_fd,
00128 bool default_locale,
00129 const Pathname & root )
00130 : use_pty (use_pty)
00131 , pid( -1 )
00132 {
00133 const char* rootdir = NULL;
00134 if(!root.empty() && root != "/")
00135 {
00136 rootdir = root.asString().c_str();
00137 }
00138 Environment environment;
00139 start_program (argv, environment, stderr_disp, stderr_fd, default_locale, rootdir);
00140 }
00141
00142
00143 ExternalProgram::ExternalProgram (const char *const *argv, const Environment & environment,
00144 Stderr_Disposition stderr_disp, bool use_pty,
00145 int stderr_fd, bool default_locale,
00146 const Pathname& root)
00147 : use_pty (use_pty)
00148 , pid( -1 )
00149 {
00150 const char* rootdir = NULL;
00151 if(!root.empty() && root != "/")
00152 {
00153 rootdir = root.asString().c_str();
00154 }
00155 start_program (argv, environment, stderr_disp, stderr_fd, default_locale, rootdir);
00156 }
00157
00158
00159 ExternalProgram::ExternalProgram (const char *binpath, const char *const *argv_1,
00160 bool use_pty)
00161 : use_pty (use_pty)
00162 , pid( -1 )
00163 {
00164 int i = 0;
00165 while (argv_1[i++])
00166 ;
00167 const char *argv[i + 1];
00168 argv[0] = binpath;
00169 memcpy (&argv[1], argv_1, (i - 1) * sizeof (char *));
00170 Environment environment;
00171 start_program (argv, environment);
00172 }
00173
00174
00175 ExternalProgram::ExternalProgram (const char *binpath, const char *const *argv_1, const Environment & environment,
00176 bool use_pty)
00177 : use_pty (use_pty)
00178 , pid( -1 )
00179 {
00180 int i = 0;
00181 while (argv_1[i++])
00182 ;
00183 const char *argv[i + 1];
00184 argv[0] = binpath;
00185 memcpy (&argv[1], argv_1, (i - 1) * sizeof (char *));
00186 start_program (argv, environment);
00187 }
00188
00189
00190 ExternalProgram::~ExternalProgram()
00191 {
00192 }
00193
00194
00195 void
00196 ExternalProgram::start_program (const char *const *argv, const Environment & environment,
00197 Stderr_Disposition stderr_disp,
00198 int stderr_fd, bool default_locale, const char* root)
00199 {
00200 pid = -1;
00201 _exitStatus = 0;
00202 int to_external[2], from_external[2];
00203 int master_tty, slave_tty;
00204
00205 const char * redirectStdin = 0;
00206 if ( argv[0] && *argv[0] == '<' )
00207 {
00208 redirectStdin = argv[0]+1;
00209 if ( *redirectStdin == '\0' )
00210 redirectStdin = "/dev/null";
00211 ++argv;
00212 }
00213
00214
00215
00216 {
00217 stringstream cmdstr;
00218 for (int i = 0; argv[i]; i++)
00219 {
00220 if (i>0) cmdstr << ' ';
00221 cmdstr << '\'';
00222 cmdstr << argv[i];
00223 cmdstr << '\'';
00224 }
00225 if ( redirectStdin )
00226 cmdstr << " < '" << redirectStdin << "'";
00227 _command = cmdstr.str();
00228 }
00229 DBG << "Executing " << _command << endl;
00230
00231
00232 if (use_pty)
00233 {
00234
00235 DBG << "Using ttys for communication with " << argv[0] << endl;
00236 if (openpty (&master_tty, &slave_tty, 0, 0, 0) != 0)
00237 {
00238 _execError = str::form( _("Can't open pty (%s)."), strerror(errno) );
00239 _exitStatus = 126;
00240 ERR << _execError << endl;
00241 return;
00242 }
00243 }
00244 else
00245 {
00246
00247 if (pipe (to_external) != 0 || pipe (from_external) != 0)
00248 {
00249 _execError = str::form( _("Can't open pipe (%s)."), strerror(errno) );
00250 _exitStatus = 126;
00251 ERR << _execError << endl;
00252 return;
00253 }
00254 }
00255
00256
00257 if ((pid = fork()) == 0)
00258 {
00260
00262 if (use_pty)
00263 {
00264 setsid();
00265 if(slave_tty != 1)
00266 dup2 (slave_tty, 1);
00267 renumber_fd (slave_tty, 0);
00268 ::close(master_tty);
00269
00270
00271
00272
00273
00274 char name[512];
00275 ttyname_r(slave_tty, name, sizeof(name));
00276 ::close(open(name, O_RDONLY));
00277 }
00278 else
00279 {
00280 renumber_fd (to_external[0], 0);
00281 ::close(from_external[0]);
00282
00283 renumber_fd (from_external[1], 1);
00284 ::close(to_external [1]);
00285 }
00286
00287 if ( redirectStdin )
00288 {
00289 ::close( 0 );
00290 int inp_fd = open( redirectStdin, O_RDONLY );
00291 dup2( inp_fd, 0 );
00292 }
00293
00294
00295 if (stderr_disp == Discard_Stderr)
00296 {
00297 int null_fd = open("/dev/null", O_WRONLY);
00298 dup2(null_fd, 2);
00299 ::close(null_fd);
00300 }
00301 else if (stderr_disp == Stderr_To_Stdout)
00302 {
00303 dup2(1, 2);
00304 }
00305 else if (stderr_disp == Stderr_To_FileDesc)
00306 {
00307
00308
00309 dup2 (stderr_fd, 2);
00310 }
00311
00312 for ( Environment::const_iterator it = environment.begin(); it != environment.end(); ++it ) {
00313 setenv( it->first.c_str(), it->second.c_str(), 1 );
00314 }
00315
00316 if(default_locale)
00317 setenv("LC_ALL","C",1);
00318
00319 if(root)
00320 {
00321 if(chroot(root) == -1)
00322 {
00323 _execError = str::form( _("Can't chroot to '%s' (%s)."), root, strerror(errno) );
00324 std::cerr << _execError << endl;
00325 _exit (128);
00326 }
00327 if(chdir("/") == -1)
00328 {
00329 _execError = str::form( _("Can't chdir to '/' inside chroot (%s)."), strerror(errno) );
00330 std::cerr << _execError << endl;
00331 _exit (128);
00332 }
00333 }
00334
00335
00336 for ( int i = ::getdtablesize() - 1; i > 2; --i ) {
00337 ::close( i );
00338 }
00339
00340 execvp(argv[0], const_cast<char *const *>(argv));
00341
00342 _execError = str::form( _("Can't exec '%s' (%s)."), argv[0], strerror(errno) );
00343 std::cerr << _execError << endl;
00344 _exit (129);
00346 }
00347
00348 else if (pid == -1)
00349 {
00350 _execError = str::form( _("Can't fork (%s)."), strerror(errno) );
00351 _exitStatus = 127;
00352 ERR << _execError << endl;
00353
00354 if (use_pty) {
00355 ::close(master_tty);
00356 ::close(slave_tty);
00357 }
00358 else {
00359 ::close(to_external[0]);
00360 ::close(to_external[1]);
00361 ::close(from_external[0]);
00362 ::close(from_external[1]);
00363 }
00364 }
00365
00366 else {
00367 if (use_pty)
00368 {
00369 ::close(slave_tty);
00370 inputfile = fdopen(master_tty, "r");
00371 outputfile = fdopen(master_tty, "w");
00372 }
00373 else
00374 {
00375 ::close(to_external[0]);
00376 ::close(from_external[1]);
00377 inputfile = fdopen(from_external[0], "r");
00378 outputfile = fdopen(to_external[1], "w");
00379 }
00380
00381 DBG << "pid " << pid << " launched" << endl;
00382
00383 if (!inputfile || !outputfile)
00384 {
00385 ERR << "Cannot create streams to external program " << argv[0] << endl;
00386 close();
00387 }
00388 }
00389 }
00390
00391
00392 int
00393 ExternalProgram::close()
00394 {
00395 if (pid > 0)
00396 {
00397 if ( inputFile() )
00398 {
00399
00400
00401
00402 setBlocking( false );
00403 FILE * inputfile = inputFile();
00404 int inputfileFd = ::fileno( inputfile );
00405 long delay = 0;
00406 do
00407 {
00408
00409 fd_set rfds;
00410 FD_ZERO( &rfds );
00411 FD_SET( inputfileFd, &rfds );
00412
00413
00414 struct timeval tv;
00415 tv.tv_sec = (delay < 0 ? 1 : 0);
00416 tv.tv_usec = (delay < 0 ? 0 : delay*100000);
00417 if ( delay >= 0 && ++delay > 9 )
00418 delay = -1;
00419 int retval = select( inputfileFd+1, &rfds, NULL, NULL, &tv );
00420
00421 if ( retval == -1 )
00422 {
00423 ERR << "select error: " << strerror(errno) << endl;
00424 if ( errno != EINTR )
00425 break;
00426 }
00427 else if ( retval )
00428 {
00429
00430 static size_t linebuffer_size = 0;
00431 static char * linebuffer = 0;
00432 getline( &linebuffer, &linebuffer_size, inputfile );
00433
00434
00435 if ( ::feof( inputfile ) )
00436 break;
00437 clearerr( inputfile );
00438 }
00439 else
00440 {
00441
00442 if ( ! running() )
00443 break;
00444 }
00445 } while ( true );
00446 }
00447
00448
00449 int ret;
00450 int status = 0;
00451 do
00452 {
00453 ret = waitpid(pid, &status, 0);
00454 }
00455 while (ret == -1 && errno == EINTR);
00456
00457 if (ret != -1)
00458 {
00459 _exitStatus = checkStatus( status );
00460 }
00461 pid = -1;
00462 }
00463
00464 return _exitStatus;
00465 }
00466
00467
00468 int ExternalProgram::checkStatus( int status )
00469 {
00470 if (WIFEXITED (status))
00471 {
00472 status = WEXITSTATUS (status);
00473 if(status)
00474 {
00475 DBG << "Pid " << pid << " exited with status " << status << endl;
00476 _execError = str::form( _("Command exited with status %d."), status );
00477 }
00478 else
00479 {
00480
00481
00482 DBG << "Pid " << pid << " successfully completed" << endl;
00483 _execError.clear();
00484 }
00485 }
00486 else if (WIFSIGNALED (status))
00487 {
00488 status = WTERMSIG (status);
00489 WAR << "Pid " << pid << " was killed by signal " << status
00490 << " (" << strsignal(status);
00491 if (WCOREDUMP (status))
00492 {
00493 WAR << ", core dumped";
00494 }
00495 WAR << ")" << endl;
00496 _execError = str::form( _("Command was killed by signal %d (%s)."), status, strsignal(status) );
00497 status+=128;
00498 }
00499 else {
00500 ERR << "Pid " << pid << " exited with unknown error" << endl;
00501 _execError = _("Command exited with unknown error.");
00502 }
00503
00504 return status;
00505 }
00506
00507 bool
00508 ExternalProgram::kill()
00509 {
00510 if (pid > 0)
00511 {
00512 ::kill(pid, SIGKILL);
00513 close();
00514 }
00515 return true;
00516 }
00517
00518
00519 bool
00520 ExternalProgram::running()
00521 {
00522 if ( pid < 0 ) return false;
00523
00524 int status = 0;
00525 int p = waitpid( pid, &status, WNOHANG );
00526 switch ( p )
00527 {
00528 case -1:
00529 ERR << "waitpid( " << pid << ") returned error '" << strerror(errno) << "'" << endl;
00530 return false;
00531 break;
00532 case 0:
00533 return true;
00534 break;
00535 }
00536
00537
00538 _exitStatus = checkStatus( status );
00539 pid = -1;
00540 return false;
00541 }
00542
00543
00544 void ExternalProgram::renumber_fd (int origfd, int newfd)
00545 {
00546
00547
00548
00549 if (origfd != newfd)
00550 {
00551 dup2 (origfd, newfd);
00552 ::close (origfd);
00553 }
00554 }
00555
00556 std::ostream & ExternalProgram::operator>>( std::ostream & out_r )
00557 {
00558 setBlocking( true );
00559 for ( std::string line = receiveLine(); line.length(); line = receiveLine() )
00560 out_r << line;
00561 return out_r;
00562 }
00563
00565
00566
00567
00569
00570 namespace _ExternalProgram
00571 {
00572 EarlyPipe::EarlyPipe()
00573 {
00574 _fds[R] = _fds[W] = -1;
00575 #ifdef HAVE_PIPE2
00576 ::pipe2( _fds, O_NONBLOCK );
00577 #else
00578 ::pipe( _fds );
00579 ::fcntl(_fds[R], F_SETFL, O_NONBLOCK );
00580 ::fcntl(_fds[W], F_SETFL, O_NONBLOCK );
00581 #endif
00582 _stderr = ::fdopen( _fds[R], "r" );
00583 }
00584
00585 EarlyPipe::~EarlyPipe()
00586 {
00587 closeW();
00588 if ( _stderr )
00589 ::fclose( _stderr );
00590 }
00591 }
00592
00593 bool ExternalProgramWithStderr::stderrGetUpTo( std::string & retval_r, const char delim_r, bool returnDelim_r )
00594 {
00595 if ( ! _stderr )
00596 return false;
00597 if ( delim_r && ! _buffer.empty() )
00598 {
00599
00600 std::string::size_type pos( _buffer.find( delim_r ) );
00601 if ( pos != std::string::npos )
00602 {
00603 retval_r = _buffer.substr( 0, returnDelim_r ? pos+1 : pos );
00604 _buffer.erase( 0, pos+1 );
00605 return true;
00606 }
00607 }
00608 ::clearerr( _stderr );
00609 do {
00610 int ch = fgetc( _stderr );
00611 if ( ch != EOF )
00612 {
00613 if ( ch != delim_r || ! delim_r )
00614 _buffer.push_back( ch );
00615 else
00616 {
00617 if ( returnDelim_r )
00618 _buffer.push_back( delim_r );
00619 break;
00620 }
00621 }
00622 else if ( ::feof( _stderr ) )
00623 {
00624 if ( _buffer.empty() )
00625 return false;
00626 break;
00627 }
00628 else if ( errno != EINTR )
00629 return false;
00630 } while ( true );
00631
00632 retval_r.swap( _buffer );
00633 _buffer.clear();
00634 return true;
00635 }
00636
00637
00638 }