1 /*---------------------------------------------------------------------\
3 | |__ / \ / / . \ . \ |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/ExternalProgram.cc
12 #define _GNU_SOURCE 1 // for ::getline
19 #include <pty.h> // openpty
20 #include <stdlib.h> // setenv
22 #include <cstring> // strsignal
26 #include "zypp/base/Logger.h"
27 #include "zypp/ExternalProgram.h"
33 ExternalProgram::ExternalProgram()
38 ExternalProgram::ExternalProgram( std::string commandline,
39 Stderr_Disposition stderr_disp,
43 const Pathname & root )
49 argv[2] = commandline.c_str();
52 const char* rootdir = NULL;
53 if(!root.empty() && root != "/")
55 rootdir = root.asString().c_str();
57 Environment environment;
58 start_program (argv, environment, stderr_disp, stderr_fd, default_locale, rootdir);
62 ExternalProgram::ExternalProgram( const char *const *argv,
63 Stderr_Disposition stderr_disp,
67 const Pathname & root )
70 const char* rootdir = NULL;
71 if(!root.empty() && root != "/")
73 rootdir = root.asString().c_str();
75 Environment environment;
76 start_program (argv, environment, stderr_disp, stderr_fd, default_locale, rootdir);
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,
86 const char* rootdir = NULL;
87 if(!root.empty() && root != "/")
89 rootdir = root.asString().c_str();
91 start_program (argv, environment, stderr_disp, stderr_fd, default_locale, rootdir);
95 ExternalProgram::ExternalProgram (const char *binpath, const char *const *argv_1,
102 const char *argv[i + 1];
104 memcpy (&argv[1], argv_1, (i - 1) * sizeof (char *));
105 Environment environment;
106 start_program (argv, environment);
110 ExternalProgram::ExternalProgram (const char *binpath, const char *const *argv_1, const Environment & environment,
117 const char *argv[i + 1];
119 memcpy (&argv[1], argv_1, (i - 1) * sizeof (char *));
120 start_program (argv, environment);
124 ExternalProgram::~ExternalProgram()
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)
136 int to_external[2], from_external[2]; // fds for pair of pipes
137 int master_tty, slave_tty; // fds for pair of ttys
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)
145 ERR << "openpty failed" << endl;
151 // Create pair of pipes
152 if (pipe (to_external) != 0 || pipe (from_external) != 0)
154 ERR << "pipe failed" << endl;
159 // do not remove the single quotes around every argument, copy&paste of
160 // command to shell will not work otherwise!
164 cmdstr << "Executing ";
165 for (int i = 0; argv[i]; i++)
167 if (i>0) cmdstr << ' ';
172 DBG << cmdstr.str() << endl;
174 // Create module process
175 if ((pid = fork()) == 0)
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
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 ;-) )
190 ttyname_r(slave_tty, name, sizeof(name));
191 ::close(open(name, O_RDONLY));
195 renumber_fd (to_external[0], 0); // set new stdin
196 ::close(from_external[0]); // Belongs to father process
198 renumber_fd (from_external[1], 1); // set new stdout
199 ::close(to_external [1]); // Belongs to father process
203 if (stderr_disp == Discard_Stderr)
205 int null_fd = open("/dev/null", O_WRONLY);
209 else if (stderr_disp == Stderr_To_Stdout)
213 else if (stderr_disp == Stderr_To_FileDesc)
215 // Note: We don't have to close anything regarding stderr_fd.
216 // Our caller is responsible for that.
220 for ( Environment::const_iterator it = environment.begin(); it != environment.end(); ++it ) {
221 setenv( it->first.c_str(), it->second.c_str(), 1 );
225 setenv("LC_ALL","C",1);
229 if(chroot(root) == -1)
231 ERR << "chroot to " << root << " failed: " << strerror(errno) << endl;
232 _exit (3); // No sense in returning! I am forked away!!
236 ERR << "chdir to / inside chroot failed: " << strerror(errno) << endl;
237 _exit (4); // No sense in returning! I am forked away!!
241 // close all filedesctiptors above stderr
242 for ( int i = ::getdtablesize() - 1; i > 2; --i ) {
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!!
252 else if (pid == -1) // Fork failed, close everything.
259 ::close(to_external[0]);
260 ::close(to_external[1]);
261 ::close(from_external[0]);
262 ::close(from_external[1]);
264 ERR << "Cannot fork " << strerror(errno) << endl;
271 ::close(slave_tty); // belongs to child process
272 inputfile = fdopen(master_tty, "r");
273 outputfile = fdopen(master_tty, "w");
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");
283 DBG << "pid " << pid << " launched" << endl;
285 if (!inputfile || !outputfile)
287 ERR << "Cannot create streams to external program " << argv[0] << endl;
295 ExternalProgram::close()
299 ExternalDataSource::close();
300 // Wait for child to exit
305 ret = waitpid(pid, &status, 0);
307 while (ret == -1 && errno == EINTR);
311 status = checkStatus( status );
323 int ExternalProgram::checkStatus( int status )
325 if (WIFEXITED (status))
327 status = WEXITSTATUS (status);
330 DBG << "pid " << pid << " exited with status " << status << endl;
334 // if 'launch' is logged, completion should be logged,
335 // even if successfull.
336 DBG << "pid " << pid << " successfully completed" << endl;
339 else if (WIFSIGNALED (status))
341 status = WTERMSIG (status);
342 WAR << "pid " << pid << " was killed by signal " << status
343 << " (" << strsignal(status);
344 if (WCOREDUMP (status))
346 WAR << ", core dumped";
352 ERR << "pid " << pid << " exited with unknown error" << endl;
359 ExternalProgram::kill()
363 ::kill(pid, SIGKILL);
371 ExternalProgram::running()
373 if ( pid < 0 ) return false;
376 int p = waitpid( pid, &status, WNOHANG );
380 ERR << "waitpid( " << pid << ") returned error '" << strerror(errno) << "'" << endl;
384 return true; // still running
388 // Here: completed...
389 _exitStatus = checkStatus( status );
394 // origfd will be accessible as newfd and closed (unless they were equal)
395 void ExternalProgram::renumber_fd (int origfd, int newfd)
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)
402 dup2 (origfd, newfd);