ExternalProgram: add command output redirection to stream.
[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     {
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     {
49       const char *argv[4];
50       argv[0] = "/bin/sh";
51       argv[1] = "-c";
52       argv[2] = commandline.c_str();
53       argv[3] = 0;
54
55       const char* rootdir = NULL;
56       if(!root.empty() && root != "/")
57       {
58         rootdir = root.asString().c_str();
59       }
60       Environment environment;
61       start_program (argv, environment, stderr_disp, stderr_fd, default_locale, rootdir);
62     }
63
64
65     ExternalProgram::ExternalProgram (const Arguments &argv,
66                                       Stderr_Disposition stderr_disp,
67                                       bool use_pty, int stderr_fd,
68                                       bool default_locale,
69                                       const Pathname& root)
70         : use_pty (use_pty)
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         Environment environment;
82         const char* rootdir = NULL;
83         if(!root.empty() && root != "/")
84         {
85             rootdir = root.asString().c_str();
86         }
87         start_program (argvp, environment, stderr_disp, stderr_fd, default_locale, rootdir);
88     }
89
90
91     ExternalProgram::ExternalProgram (const Arguments &argv,
92                                       const Environment & environment,
93                                       Stderr_Disposition stderr_disp,
94                                       bool use_pty, int stderr_fd,
95                                       bool default_locale,
96                                       const Pathname& root)
97         : use_pty (use_pty)
98     {
99         const char * argvp[argv.size() + 1];
100         unsigned c = 0;
101         for_( i, argv.begin(), argv.end() )
102         {
103             argvp[c] = i->c_str();
104             ++c;
105         }
106         argvp[c] = 0;
107
108         const char* rootdir = NULL;
109         if(!root.empty() && root != "/")
110         {
111             rootdir = root.asString().c_str();
112         }
113         start_program (argvp, environment, stderr_disp, stderr_fd, default_locale, rootdir);
114
115     }
116
117
118
119
120     ExternalProgram::ExternalProgram( const char *const *argv,
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     {
128       const char* rootdir = NULL;
129       if(!root.empty() && root != "/")
130       {
131         rootdir = root.asString().c_str();
132       }
133       Environment environment;
134       start_program (argv, environment, stderr_disp, stderr_fd, default_locale, rootdir);
135     }
136
137
138     ExternalProgram::ExternalProgram (const char *const *argv, const Environment & environment,
139                                   Stderr_Disposition stderr_disp, bool use_pty,
140                                   int stderr_fd, bool default_locale,
141                                   const Pathname& root)
142       : use_pty (use_pty)
143     {
144       const char* rootdir = NULL;
145       if(!root.empty() && root != "/")
146       {
147         rootdir = root.asString().c_str();
148       }
149       start_program (argv, environment, stderr_disp, stderr_fd, default_locale, rootdir);
150     }
151
152
153     ExternalProgram::ExternalProgram (const char *binpath, const char *const *argv_1,
154                                   bool use_pty)
155       : use_pty (use_pty)
156     {
157       int i = 0;
158       while (argv_1[i++])
159         ;
160       const char *argv[i + 1];
161       argv[0] = binpath;
162       memcpy (&argv[1], argv_1, (i - 1) * sizeof (char *));
163       Environment environment;
164       start_program (argv, environment);
165     }
166
167
168     ExternalProgram::ExternalProgram (const char *binpath, const char *const *argv_1, const Environment & environment,
169                                   bool use_pty)
170       : use_pty (use_pty)
171     {
172       int i = 0;
173       while (argv_1[i++])
174         ;
175       const char *argv[i + 1];
176       argv[0] = binpath;
177       memcpy (&argv[1], argv_1, (i - 1) * sizeof (char *));
178       start_program (argv, environment);
179     }
180
181
182     ExternalProgram::~ExternalProgram()
183     {
184     }
185
186
187     void
188     ExternalProgram::start_program (const char *const *argv, const Environment & environment,
189                                 Stderr_Disposition stderr_disp,
190                                 int stderr_fd, bool default_locale, const char* root)
191     {
192       pid = -1;
193       _exitStatus = 0;
194       int to_external[2], from_external[2];  // fds for pair of pipes
195       int master_tty,   slave_tty;         // fds for pair of ttys
196
197       // do not remove the single quotes around every argument, copy&paste of
198       // command to shell will not work otherwise!
199       {
200         stringstream cmdstr;
201         for (int i = 0; argv[i]; i++)
202         {
203           if (i>0) cmdstr << ' ';
204           cmdstr << '\'';
205           cmdstr << argv[i];
206           cmdstr << '\'';
207         }
208         _command = cmdstr.str();
209       }
210       DBG << "Executing " << _command << endl;
211
212
213       if (use_pty)
214       {
215         // Create pair of ttys
216         DBG << "Using ttys for communication with " << argv[0] << endl;
217         if (openpty (&master_tty, &slave_tty, 0, 0, 0) != 0)
218         {
219           _execError = str::form( _("Can't open pty (%s)."), strerror(errno) );
220           _exitStatus = 126;
221           ERR << _execError << endl;
222           return;
223         }
224       }
225       else
226       {
227         // Create pair of pipes
228         if (pipe (to_external) != 0 || pipe (from_external) != 0)
229         {
230           _execError = str::form( _("Can't open pipe (%s)."), strerror(errno) );
231           _exitStatus = 126;
232           ERR << _execError << endl;
233           return;
234         }
235       }
236
237       // Create module process
238       if ((pid = fork()) == 0)
239       {
240         if (use_pty)
241         {
242             setsid();
243             if(slave_tty != 1)
244                 dup2 (slave_tty, 1);      // set new stdout
245             renumber_fd (slave_tty, 0);   // set new stdin
246             ::close(master_tty);          // Belongs to father process
247
248             // We currently have no controlling terminal (due to setsid).
249             // The first open call will also set the new ctty (due to historical
250             // unix guru knowledge ;-) )
251
252             char name[512];
253             ttyname_r(slave_tty, name, sizeof(name));
254             ::close(open(name, O_RDONLY));
255         }
256         else
257         {
258             renumber_fd (to_external[0], 0); // set new stdin
259             ::close(from_external[0]);    // Belongs to father process
260
261             renumber_fd (from_external[1], 1); // set new stdout
262             ::close(to_external  [1]);    // Belongs to father process
263         }
264
265         // Handle stderr
266         if (stderr_disp == Discard_Stderr)
267         {
268             int null_fd = open("/dev/null", O_WRONLY);
269             dup2(null_fd, 2);
270             ::close(null_fd);
271         }
272         else if (stderr_disp == Stderr_To_Stdout)
273         {
274             dup2(1, 2);
275         }
276         else if (stderr_disp == Stderr_To_FileDesc)
277         {
278             // Note: We don't have to close anything regarding stderr_fd.
279             // Our caller is responsible for that.
280             dup2 (stderr_fd, 2);
281         }
282
283         for ( Environment::const_iterator it = environment.begin(); it != environment.end(); ++it ) {
284           setenv( it->first.c_str(), it->second.c_str(), 1 );
285         }
286
287         if(default_locale)
288                 setenv("LC_ALL","C",1);
289
290         if(root)
291         {
292             if(chroot(root) == -1)
293             {
294                 _execError = str::form( _("Can't chroot to '%s' (%s)."), root, strerror(errno) );
295                 ERR << _execError << endl;
296                 std::cerr << _execError << endl;// After fork log on stderr too
297                 _exit (128);                    // No sense in returning! I am forked away!!
298             }
299             if(chdir("/") == -1)
300             {
301                 _execError = str::form( _("Can't chdir to '/' inside chroot (%s)."), strerror(errno) );
302                 ERR << _execError << endl;
303                 std::cerr << _execError << endl;// After fork log on stderr too
304                 _exit (128);                    // No sense in returning! I am forked away!!
305             }
306         }
307
308         // close all filedesctiptors above stderr
309         for ( int i = ::getdtablesize() - 1; i > 2; --i ) {
310           ::close( i );
311         }
312
313         execvp(argv[0], const_cast<char *const *>(argv));
314         // don't want to get here
315         _execError = str::form( _("Can't exec '%s' (%s)."), argv[0], strerror(errno) );
316         ERR << _execError << endl;
317         std::cerr << _execError << endl;// After fork log on stderr too
318         _exit (129);                    // No sense in returning! I am forked away!!
319       }
320
321       else if (pid == -1)        // Fork failed, close everything.
322       {
323         _execError = str::form( _("Can't fork (%s)."), strerror(errno) );
324         _exitStatus = 127;
325         ERR << _execError << endl;
326
327         if (use_pty) {
328             ::close(master_tty);
329             ::close(slave_tty);
330         }
331         else {
332             ::close(to_external[0]);
333             ::close(to_external[1]);
334             ::close(from_external[0]);
335             ::close(from_external[1]);
336         }
337       }
338
339       else {
340         if (use_pty)
341         {
342             ::close(slave_tty);        // belongs to child process
343             inputfile  = fdopen(master_tty, "r");
344             outputfile = fdopen(master_tty, "w");
345         }
346         else
347         {
348             ::close(to_external[0]);   // belongs to child process
349             ::close(from_external[1]); // belongs to child process
350             inputfile = fdopen(from_external[0], "r");
351             outputfile = fdopen(to_external[1], "w");
352         }
353
354         DBG << "pid " << pid << " launched" << endl;
355
356         if (!inputfile || !outputfile)
357         {
358             ERR << "Cannot create streams to external program " << argv[0] << endl;
359             close();
360         }
361       }
362     }
363
364
365     int
366     ExternalProgram::close()
367     {
368       if (pid > 0)
369       {
370         setBlocking( true );
371         while ( receiveLine().length() )
372           ; // discard any output instead of closing the pipe
373         //ExternalDataSource::close();
374
375         // Wait for child to exit
376         int ret;
377         int status = 0;
378         do
379         {
380             ret = waitpid(pid, &status, 0);
381         }
382         while (ret == -1 && errno == EINTR);
383
384         if (ret != -1)
385         {
386             status = checkStatus( status );
387         }
388           pid = -1;
389           return status;
390       }
391       else
392       {
393           return _exitStatus;
394       }
395     }
396
397
398     int ExternalProgram::checkStatus( int status )
399     {
400       if (WIFEXITED (status))
401       {
402         status = WEXITSTATUS (status);
403         if(status)
404         {
405             DBG << "Pid " << pid << " exited with status " << status << endl;
406             _execError = str::form( _("Command exited with status %d."), status );
407         }
408         else
409         {
410             // if 'launch' is logged, completion should be logged,
411             // even if successfull.
412             DBG << "Pid " << pid << " successfully completed" << endl;
413             //_execError = _("Command successfully completed.");
414         }
415       }
416       else if (WIFSIGNALED (status))
417       {
418         status = WTERMSIG (status);
419         WAR << "Pid " << pid << " was killed by signal " << status
420                 << " (" << strsignal(status);
421         if (WCOREDUMP (status))
422         {
423             WAR << ", core dumped";
424         }
425         WAR << ")" << endl;
426         _execError = str::form( _("Command was killed by signal %d (%s)."), status, strsignal(status) );
427         status+=128;
428       }
429       else {
430         ERR << "Pid " << pid << " exited with unknown error" << endl;
431         _execError = _("Command exited with unknown error.");
432       }
433
434       return status;
435     }
436
437     bool
438     ExternalProgram::kill()
439     {
440       if (pid > 0)
441       {
442         ::kill(pid, SIGKILL);
443         close();
444       }
445       return true;
446     }
447
448
449     bool
450     ExternalProgram::running()
451     {
452       if ( pid < 0 ) return false;
453
454       int status = 0;
455       int p = waitpid( pid, &status, WNOHANG );
456       switch ( p )
457         {
458         case -1:
459           ERR << "waitpid( " << pid << ") returned error '" << strerror(errno) << "'" << endl;
460           return false;
461           break;
462         case 0:
463           return true; // still running
464           break;
465         }
466
467       // Here: completed...
468       _exitStatus = checkStatus( status );
469       pid = -1;
470       return false;
471     }
472
473     // origfd will be accessible as newfd and closed (unless they were equal)
474     void ExternalProgram::renumber_fd (int origfd, int newfd)
475     {
476       // It may happen that origfd is already the one we want
477       // (Although in our circumstances, that would mean somebody has closed
478       // our stdin or stdout... weird but has appened to Cray, #49797)
479       if (origfd != newfd)
480       {
481         dup2 (origfd, newfd);
482         ::close (origfd);
483       }
484     }
485
486     std::ostream & ExternalProgram::operator>>( std::ostream & out_r )
487     {
488       setBlocking( true );
489       for ( std::string line = receiveLine(); line.length(); line = receiveLine() )
490         out_r << line;
491       return out_r;
492     }
493
494
495 } // namespace zypp