Deprecate MediaAccess::downloads (accidentally deleted)
[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         //////////////////////////////////////////////////////////////////////
241         // Don't write to the logfile after fork!
242         //////////////////////////////////////////////////////////////////////
243         if (use_pty)
244         {
245             setsid();
246             if(slave_tty != 1)
247                 dup2 (slave_tty, 1);      // set new stdout
248             renumber_fd (slave_tty, 0);   // set new stdin
249             ::close(master_tty);          // Belongs to father process
250
251             // We currently have no controlling terminal (due to setsid).
252             // The first open call will also set the new ctty (due to historical
253             // unix guru knowledge ;-) )
254
255             char name[512];
256             ttyname_r(slave_tty, name, sizeof(name));
257             ::close(open(name, O_RDONLY));
258         }
259         else
260         {
261             renumber_fd (to_external[0], 0); // set new stdin
262             ::close(from_external[0]);    // Belongs to father process
263
264             renumber_fd (from_external[1], 1); // set new stdout
265             ::close(to_external  [1]);    // Belongs to father process
266         }
267
268         // Handle stderr
269         if (stderr_disp == Discard_Stderr)
270         {
271             int null_fd = open("/dev/null", O_WRONLY);
272             dup2(null_fd, 2);
273             ::close(null_fd);
274         }
275         else if (stderr_disp == Stderr_To_Stdout)
276         {
277             dup2(1, 2);
278         }
279         else if (stderr_disp == Stderr_To_FileDesc)
280         {
281             // Note: We don't have to close anything regarding stderr_fd.
282             // Our caller is responsible for that.
283             dup2 (stderr_fd, 2);
284         }
285
286         for ( Environment::const_iterator it = environment.begin(); it != environment.end(); ++it ) {
287           setenv( it->first.c_str(), it->second.c_str(), 1 );
288         }
289
290         if(default_locale)
291                 setenv("LC_ALL","C",1);
292
293         if(root)
294         {
295             if(chroot(root) == -1)
296             {
297                 _execError = str::form( _("Can't chroot to '%s' (%s)."), root, strerror(errno) );
298                 std::cerr << _execError << endl;// After fork log on stderr too
299                 _exit (128);                    // No sense in returning! I am forked away!!
300             }
301             if(chdir("/") == -1)
302             {
303                 _execError = str::form( _("Can't chdir to '/' inside chroot (%s)."), strerror(errno) );
304                 std::cerr << _execError << endl;// After fork log on stderr too
305                 _exit (128);                    // No sense in returning! I am forked away!!
306             }
307         }
308
309         // close all filedesctiptors above stderr
310         for ( int i = ::getdtablesize() - 1; i > 2; --i ) {
311           ::close( i );
312         }
313
314         execvp(argv[0], const_cast<char *const *>(argv));
315         // don't want to get here
316         _execError = str::form( _("Can't exec '%s' (%s)."), argv[0], strerror(errno) );
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
322       else if (pid == -1)        // Fork failed, close everything.
323       {
324         _execError = str::form( _("Can't fork (%s)."), strerror(errno) );
325         _exitStatus = 127;
326         ERR << _execError << endl;
327
328         if (use_pty) {
329             ::close(master_tty);
330             ::close(slave_tty);
331         }
332         else {
333             ::close(to_external[0]);
334             ::close(to_external[1]);
335             ::close(from_external[0]);
336             ::close(from_external[1]);
337         }
338       }
339
340       else {
341         if (use_pty)
342         {
343             ::close(slave_tty);        // belongs to child process
344             inputfile  = fdopen(master_tty, "r");
345             outputfile = fdopen(master_tty, "w");
346         }
347         else
348         {
349             ::close(to_external[0]);   // belongs to child process
350             ::close(from_external[1]); // belongs to child process
351             inputfile = fdopen(from_external[0], "r");
352             outputfile = fdopen(to_external[1], "w");
353         }
354
355         DBG << "pid " << pid << " launched" << endl;
356
357         if (!inputfile || !outputfile)
358         {
359             ERR << "Cannot create streams to external program " << argv[0] << endl;
360             close();
361         }
362       }
363     }
364
365
366     int
367     ExternalProgram::close()
368     {
369       if (pid > 0)
370       {
371         setBlocking( true );
372         while ( receiveLine().length() )
373           ; // discard any output instead of closing the pipe
374         //ExternalDataSource::close();
375
376         // Wait for child to exit
377         int ret;
378         int status = 0;
379         do
380         {
381             ret = waitpid(pid, &status, 0);
382         }
383         while (ret == -1 && errno == EINTR);
384
385         if (ret != -1)
386         {
387             status = checkStatus( status );
388         }
389           pid = -1;
390           return status;
391       }
392       else
393       {
394           return _exitStatus;
395       }
396     }
397
398
399     int ExternalProgram::checkStatus( int status )
400     {
401       if (WIFEXITED (status))
402       {
403         status = WEXITSTATUS (status);
404         if(status)
405         {
406             DBG << "Pid " << pid << " exited with status " << status << endl;
407             _execError = str::form( _("Command exited with status %d."), status );
408         }
409         else
410         {
411             // if 'launch' is logged, completion should be logged,
412             // even if successfull.
413             DBG << "Pid " << pid << " successfully completed" << endl;
414             //_execError = _("Command successfully completed.");
415         }
416       }
417       else if (WIFSIGNALED (status))
418       {
419         status = WTERMSIG (status);
420         WAR << "Pid " << pid << " was killed by signal " << status
421                 << " (" << strsignal(status);
422         if (WCOREDUMP (status))
423         {
424             WAR << ", core dumped";
425         }
426         WAR << ")" << endl;
427         _execError = str::form( _("Command was killed by signal %d (%s)."), status, strsignal(status) );
428         status+=128;
429       }
430       else {
431         ERR << "Pid " << pid << " exited with unknown error" << endl;
432         _execError = _("Command exited with unknown error.");
433       }
434
435       return status;
436     }
437
438     bool
439     ExternalProgram::kill()
440     {
441       if (pid > 0)
442       {
443         ::kill(pid, SIGKILL);
444         close();
445       }
446       return true;
447     }
448
449
450     bool
451     ExternalProgram::running()
452     {
453       if ( pid < 0 ) return false;
454
455       int status = 0;
456       int p = waitpid( pid, &status, WNOHANG );
457       switch ( p )
458         {
459         case -1:
460           ERR << "waitpid( " << pid << ") returned error '" << strerror(errno) << "'" << endl;
461           return false;
462           break;
463         case 0:
464           return true; // still running
465           break;
466         }
467
468       // Here: completed...
469       _exitStatus = checkStatus( status );
470       pid = -1;
471       return false;
472     }
473
474     // origfd will be accessible as newfd and closed (unless they were equal)
475     void ExternalProgram::renumber_fd (int origfd, int newfd)
476     {
477       // It may happen that origfd is already the one we want
478       // (Although in our circumstances, that would mean somebody has closed
479       // our stdin or stdout... weird but has appened to Cray, #49797)
480       if (origfd != newfd)
481       {
482         dup2 (origfd, newfd);
483         ::close (origfd);
484       }
485     }
486
487     std::ostream & ExternalProgram::operator>>( std::ostream & out_r )
488     {
489       setBlocking( true );
490       for ( std::string line = receiveLine(); line.length(); line = receiveLine() )
491         out_r << line;
492       return out_r;
493     }
494
495
496 } // namespace zypp