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