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