source: trunk/debathena/third/schroot/sbuild/sbuild-run-parts.cc @ 24314

Revision 24314, 9.0 KB checked in by geofft, 14 years ago (diff)
In schroot: * Merge with Debian unstable; remaining changes: - Backport to Karmic, and adjust build-deps.
Line 
1/* Copyright © 2005-2009  Roger Leigh <rleigh@debian.org>
2 *
3 * schroot is free software: you can redistribute it and/or modify it
4 * under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * schroot is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program.  If not, see
15 * <http://www.gnu.org/licenses/>.
16 *
17 *********************************************************************/
18
19#include <config.h>
20
21#include "sbuild-run-parts.h"
22#include "sbuild-util.h"
23
24#include <cerrno>
25
26#include <poll.h>
27#include <sys/wait.h>
28
29#include <syslog.h>
30
31#include <boost/format.hpp>
32#include <boost/filesystem/operations.hpp>
33
34using boost::format;
35using namespace sbuild;
36
37namespace
38{
39
40  typedef std::pair<sbuild::run_parts::error_code,const char *> emap;
41
42  /**
43   * This is a list of the supported error codes.  It's used to
44   * construct the real error codes map.
45   */
46  emap init_errors[] =
47    {
48      emap(run_parts::CHILD_FORK, N_("Failed to fork child")),
49      emap(run_parts::CHILD_WAIT, N_("Wait for child failed")),
50      // TRANSLATORS: %1% = command name
51      emap(run_parts::EXEC,       N_("Failed to execute '%1%'")),
52      emap(run_parts::PIPE,       N_("Failed to create pipe")),
53      emap(run_parts::DUP,        N_("Failed to duplicate file descriptor")),
54      emap(run_parts::POLL,       N_("Failed to poll file descriptor")),
55      emap(run_parts::READ,       N_("Failed to read file descriptor"))
56    };
57
58}
59
60template<>
61error<run_parts::error_code>::map_type
62error<run_parts::error_code>::error_strings
63(init_errors,
64 init_errors + (sizeof(init_errors) / sizeof(init_errors[0])));
65
66run_parts::run_parts (std::string const& directory,
67                      bool               lsb_mode,
68                      bool               abort_on_error,
69                      mode_t             umask):
70  lsb_mode(true),
71  abort_on_error(abort_on_error),
72  umask(umask),
73  verbose(false),
74  reverse(false),
75  directory(directory),
76  programs()
77{
78  boost::filesystem::path dirpath(directory);
79  boost::filesystem::directory_iterator end_iter;
80  for (boost::filesystem::directory_iterator dirent(dirpath);
81       dirent != end_iter;
82       ++dirent)
83    {
84      std::string name(dirent->leaf());
85
86      // Skip common directories.
87      if (name == "." || name == "..")
88        continue;
89
90      // Skip backup files and dpkg configuration backup files.
91      if (is_valid_filename(name, this->lsb_mode))
92        this->programs.insert(name);
93    }
94}
95
96run_parts::~run_parts ()
97{
98}
99
100bool
101run_parts::get_verbose () const
102{
103  return this->verbose;
104}
105
106void
107run_parts::set_verbose (bool verbose)
108{
109  this->verbose = verbose;
110}
111
112bool
113run_parts::get_reverse () const
114{
115  return this->reverse;
116}
117
118void
119run_parts::set_reverse (bool reverse)
120{
121  this->reverse = reverse;
122}
123
124int
125run_parts::run (string_list const& command,
126                environment const& env)
127{
128  int exit_status = 0;
129
130  if (!this->reverse)
131    {
132      for (program_set::const_iterator pos = this->programs.begin();
133           pos != this->programs.end();
134           ++pos)
135        {
136          string_list real_command;
137          real_command.push_back(*pos);
138          for (string_list::const_iterator spos = command.begin();
139               spos != command.end();
140               ++spos)
141            real_command.push_back(*spos);
142
143          exit_status = run_child(*pos, real_command, env);
144
145          if (exit_status && this->abort_on_error)
146            return exit_status;
147        }
148    }
149  else
150    {
151      for (program_set::const_reverse_iterator pos = this->programs.rbegin();
152           pos != this->programs.rend();
153           ++pos)
154        {
155          string_list real_command;
156          real_command.push_back(*pos);
157          for (string_list::const_iterator spos = command.begin();
158               spos != command.end();
159               ++spos)
160            real_command.push_back(*spos);
161
162          exit_status = run_child(*pos, real_command, env);
163
164          if (exit_status && this->abort_on_error)
165            return exit_status;
166        }
167    }
168
169  return exit_status;
170}
171
172int
173run_parts::run_child (std::string const& file,
174                      string_list const& command,
175                      environment const& env)
176{
177  int stdout_pipe[2];
178  int stderr_pipe[2];
179  int exit_status = 0;
180  pid_t pid;
181
182  try
183    {
184      if (pipe(stdout_pipe) < 0)
185        throw error(PIPE, strerror(errno));
186      if (pipe(stderr_pipe) < 0)
187        throw error(PIPE, strerror(errno));
188
189      if ((pid = fork()) == -1)
190        {
191          throw error(CHILD_FORK, strerror(errno));
192        }
193      else if (pid == 0)
194        {
195          try
196            {
197              log_debug(DEBUG_INFO) << "run_parts: executing "
198                                    << string_list_to_string(command, ", ")
199                                    << std::endl;
200              if (this->verbose)
201                // TRANSLATORS: %1% = command
202                log_info() << format(_("Executing '%1%'"))
203                  % string_list_to_string(command, " ")
204                           << std::endl;
205              ::umask(this->umask);
206
207              // Don't leak syslog file descriptor to child processes.
208              closelog();
209
210              // Set up pipes for stdout and stderr
211              if (dup2(stdout_pipe[1], STDOUT_FILENO) < 0)
212                throw error(DUP, strerror(errno));
213              if (dup2(stderr_pipe[1], STDERR_FILENO) < 0)
214                throw error(DUP, strerror(errno));
215
216              close(stdout_pipe[0]);
217              close(stdout_pipe[1]);
218              close(stderr_pipe[0]);
219              close(stderr_pipe[1]);
220
221              exec(this->directory + '/' + file, command, env);
222              error e(file, EXEC, strerror(errno));
223              log_exception_error(e);
224            }
225          catch (std::exception const& e)
226            {
227              sbuild::log_exception_error(e);
228            }
229          catch (...)
230            {
231              sbuild::log_error()
232                << _("An unknown exception occurred") << std::endl;
233            }
234          _exit(EXIT_FAILURE);
235        }
236
237      // Log stdout and stderr.
238      close(stdout_pipe[1]);
239      close(stderr_pipe[1]);
240
241      struct pollfd pollfds[2];
242      pollfds[0].fd = stdout_pipe[0];
243      pollfds[0].events = POLLIN;
244      pollfds[0].revents = 0;
245      pollfds[1].fd = stderr_pipe[0];
246      pollfds[1].events = POLLIN;
247      pollfds[1].revents = 0;
248
249      char buffer[BUFSIZ];
250
251      std::string stdout_buf;
252      std::string stderr_buf;
253
254      while (1)
255        {
256          int status;
257          if ((status = poll(pollfds, 2, -1)) < 0)
258            throw error(POLL, strerror(errno));
259
260          int outdata = 0;
261          int errdata = 0;
262
263          if (pollfds[1].revents & POLLIN)
264            {
265              int errdata;
266              if ((errdata = read(pollfds[1].fd, buffer, BUFSIZ)) < 0
267                  && errno != EINTR)
268                throw error(READ, strerror(errno));
269
270              if (errdata)
271                stderr_buf += std::string(&buffer[0], errdata);
272            }
273
274          if (pollfds[0].revents & POLLIN)
275            {
276              if ((outdata = read(pollfds[0].fd, buffer, BUFSIZ)) < 0
277                  && errno != EINTR)
278                throw error(READ, strerror(errno));
279
280              if (outdata)
281                stdout_buf += std::string(&buffer[0], outdata);
282            }
283
284          if (!stderr_buf.empty())
285            {
286              string_list lines = split_string_strict(stderr_buf, "\n");
287              // If the buffer ends in a newline before splitting,
288              // it's OK to flush all lines.
289              bool flush = *stderr_buf.rbegin() == '\n';
290
291              for (string_list::const_iterator pos = lines.begin();
292                   pos != lines.end();
293                   ++pos)
294                {
295                  if (pos + 1 != lines.end() || flush)
296                    log_error() << file << ": " << *pos << '\n';
297                  else // Save possibly incompete line
298                    stderr_buf = *pos;
299                }
300
301              if (flush)
302                stderr_buf.clear();
303            }
304
305          if (!stdout_buf.empty())
306            {
307              string_list lines = split_string_strict(stdout_buf, "\n");
308              // If the buffer ends in a newline before splitting,
309              // it's OK to flush all lines.
310              bool flush = *stdout_buf.rbegin() == '\n';
311
312              for (string_list::const_iterator pos = lines.begin();
313                   pos != lines.end();
314                   ++pos)
315                {
316                  if (pos + 1 != lines.end() || flush)
317                    log_info() << file << ": " << *pos << '\n';
318                  else // Save possibly incompete line
319                    stdout_buf = *pos;
320                }
321
322              if (flush)
323                stdout_buf.clear();
324            }
325
326          if (outdata == 0 && errdata == 0) // pipes closed
327            {
328              // Flush any remaining lines
329              if (!stderr_buf.empty())
330                log_error() << file << ": " << stderr_buf << '\n';
331              if (!stdout_buf.empty())
332                log_info() << file << ": " << stdout_buf << '\n';
333              break;
334            }
335        }
336
337      close(stdout_pipe[0]);
338      close(stderr_pipe[0]);
339      wait_for_child(pid, exit_status);
340    }
341  catch (error const& e)
342    {
343      close(stdout_pipe[0]);
344      close(stdout_pipe[1]);
345      close(stderr_pipe[0]);
346      close(stderr_pipe[1]);
347      throw;
348    }
349
350  if (exit_status)
351    log_debug(DEBUG_INFO) << "run_parts: " << file
352                          << " failed with status " << exit_status
353                          << std::endl;
354  else
355    log_debug(DEBUG_INFO) << "run_parts: " << file
356                          << " succeeded"
357                          << std::endl;
358
359  return exit_status;
360}
361
362void
363run_parts::wait_for_child (pid_t pid,
364                           int&  child_status)
365{
366  child_status = EXIT_FAILURE; // Default exit status
367
368  int status;
369
370  while (1)
371    {
372      if (waitpid(pid, &status, 0) == -1)
373        {
374          if (errno == EINTR)
375            continue; // Wait again.
376          else
377            throw error(CHILD_WAIT, strerror(errno));
378        }
379      else
380        break;
381    }
382
383  if (WIFEXITED(status))
384    child_status = WEXITSTATUS(status);
385}
Note: See TracBrowser for help on using the repository browser.