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