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