1/* Subprocesses with pipes.
2
3   Copyright (C) 2005-2012 Free Software Foundation, Inc.
4
5   This program is free software: you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation, either version 3 of the License, or
8   (at your option) any later version.
9
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14
15   You should have received a copy of the GNU General Public License
16   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
17
18/* Written by Juan Manuel Guerrero <juan.guerrero@gmx.de>. */
19
20
21#include <config.h>
22
23#include "subpipe.h"
24
25#include <errno.h>
26#include <fcntl.h>
27#include <sys/stat.h>
28#include <process.h>
29#include <signal.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <unistd.h>
34#include "xalloc.h"
35
36
37#ifndef STDIN_FILENO
38# define STDIN_FILENO 0
39#endif
40#ifndef STDOUT_FILENO
41# define STDOUT_FILENO 1
42#endif
43
44
45#include "error.h"
46
47#include "gettext.h"
48#define _(Msgid)  gettext (Msgid)
49
50
51/* Initialize this module. */
52
53
54static int old_stdin;
55static int old_stdout;
56static char **arguments;
57static char tmp_file_name[2][L_tmpnam];
58
59#define remove_tmp_file(fd, name)                                     \
60  do {                                                                \
61    close ((fd));                                                     \
62    if (unlink ((name)))                                              \
63      error (EXIT_FAILURE, 0, _("removing of '%s' failed"), (name));  \
64  } while (0)
65
66
67void
68init_subpipe(void)
69{
70  char *tmpdir;
71  int fd;
72
73  tmpdir = getenv("TMPDIR");
74  if (tmpdir == NULL)
75    tmpdir = getenv("TMP");
76  if (tmpdir == NULL)
77    tmpdir = getenv("TEMP");
78  if (access(tmpdir, D_OK))
79    tmpdir = ".";
80
81  strcpy(tmp_file_name[0], tmpdir);
82  strcat(tmp_file_name[0], "/bnXXXXXX");
83  fd = mkstemp(tmp_file_name[0]);
84  if (fd < 0)
85    error(EXIT_FAILURE, 0, _("creation of a temporary file failed"));
86  close (fd);
87
88  strcpy(tmp_file_name[1], tmpdir);
89  strcat(tmp_file_name[1], "/bnXXXXXX");
90  fd = mkstemp(tmp_file_name[1]);
91  if (fd < 0)
92    error(EXIT_FAILURE, 0, _("creation of a temporary file failed"));
93  close (fd);
94}
95
96
97/* Create a subprocess that is run as a filter.  ARGV is the
98   NULL-terminated argument vector for the subprocess.  Store read and
99   write file descriptors for communication with the subprocess into
100   FD[0] and FD[1]: input meant for the process can be written into
101   FD[0], and output from the process can be read from FD[1].  Return
102   the subprocess id.
103
104   Because DOS has neither fork nor pipe functionality to run the subprocess
105   as a filter, the filter is reproduced using temporary files. First bison's
106   stdout is redirected to a temporary file. After bison has produced all of
107   is output, this file is closed and connected to m4's stdin. All m4's output
108   is redirected from m4's stdout to a second temporary file and reopened as
109   bison's stdin. */
110
111pid_t
112create_subpipe(char const *const *argv, int fd[2])
113{
114  int argc;
115  int from_in_fd;  /* pipe from bison to m4. */
116  pid_t pid;
117
118
119  pid = getpid();
120
121  /*
122   *  Save original stdin and stdout
123   *  for later restauration.
124   */
125  old_stdin = dup(STDIN_FILENO);
126  if (old_stdin < 0)
127    error(EXIT_FAILURE, 0, _("saving stdin failed"));
128
129  old_stdout = dup(STDOUT_FILENO);
130  if (old_stdout < 0)
131    error(EXIT_FAILURE, 0, _("saving stdout failed"));
132
133  /*
134   *  Save argv for later use.
135   */
136  for (argc = 0; argv[argc]; argc++)
137    ;
138  argc++;
139  arguments = xmalloc(argc * sizeof(arguments[0]));
140  for (argc = 0; argv[argc]; argc++)
141  {
142    arguments[argc] = xmalloc((strlen(argv[argc]) + 1) * sizeof(arguments[0][0]));
143    strcpy(arguments[argc], argv[argc]);
144  }
145  arguments[argc] = NULL;
146
147  /*
148   *  All bison's output will be gathered in this temporary file
149   *  and will be redirected to m4's stdin.
150   */
151  from_in_fd = open(tmp_file_name[0], O_WRONLY | O_CREAT | O_TRUNC, S_IWUSR);
152  if (from_in_fd < 0)
153    error(EXIT_FAILURE, 0, _("opening of tmpfile failed"));
154  if (dup2(from_in_fd, STDOUT_FILENO) < 0)
155  {
156    remove_tmp_file(from_in_fd, tmp_file_name[0]);
157    error(EXIT_FAILURE, 0, _("redirecting bison's stdout to the temporary file failed"));
158  }
159  close(from_in_fd);
160
161
162  fd[0] = STDOUT_FILENO;
163  return pid;
164}
165
166
167/* A signal handler that just records that a signal has happened. */
168static int child_interrupted;
169
170static void
171signal_catcher(int signo)
172{
173  child_interrupted++;
174}
175
176
177void
178end_of_output_subpipe(pid_t pid, int fd[2])
179{
180  char *program;
181  int from_out_fd = open(tmp_file_name[0], O_RDONLY, S_IRUSR);                   /* pipe from bison to m4. */
182  int to_in_fd = open(tmp_file_name[1], O_WRONLY | O_CREAT | O_TRUNC, S_IWUSR);  /* pipe from m4 to bison. */
183  int status;
184  void (*previous_handler)(int);
185
186
187  program = strrchr(arguments[0], '/');
188  if (program)
189    program++;
190  else
191    program = arguments[0];
192
193  /*
194   *  Redirect bison's output to m4's stdin.
195   */
196  if (from_out_fd < 0)
197    error(EXIT_FAILURE, 0, _("opening of tmpfile failed"));
198  if (dup2(from_out_fd, STDIN_FILENO) < 0)
199  {
200    remove_tmp_file(from_out_fd, tmp_file_name[0]);
201    error(EXIT_FAILURE, 0, _("redirecting m4's stdin from the temporary file failed"));
202  }
203  close(from_out_fd);
204
205  /*
206   *  All m4's output will be gathered in this temporary file
207   *  and will be redirected to bison's stdin.
208   */
209  if (to_in_fd < 0)
210  {
211    remove_tmp_file(STDIN_FILENO, tmp_file_name[0]);
212    error(EXIT_FAILURE, 0, _("opening of a temporary file failed"));
213  }
214  if (dup2(to_in_fd, STDOUT_FILENO) < 0)
215  {
216    remove_tmp_file(STDIN_FILENO, tmp_file_name[0]);
217    remove_tmp_file(to_in_fd, tmp_file_name[1]);
218    error(EXIT_FAILURE, 0, _("redirecting m4's stdout to a temporary file failed"));
219  }
220  close(to_in_fd);
221
222  /*
223   *  Run m4.
224   */
225  child_interrupted = 0;
226  errno = 0;
227  previous_handler = signal(SIGINT, signal_catcher);
228  status = spawnvp(P_WAIT, program, arguments);
229  signal(SIGINT, previous_handler);
230  if (child_interrupted)
231  {
232    remove_tmp_file(STDIN_FILENO, tmp_file_name[0]);
233    remove_tmp_file(STDOUT_FILENO, tmp_file_name[1]);
234    error(EXIT_FAILURE, 0, _("subsidiary program '%s' interrupted"), program);
235  }
236  if (status)
237  {
238    remove_tmp_file(STDIN_FILENO, tmp_file_name[0]);
239    remove_tmp_file(STDOUT_FILENO, tmp_file_name[1]);
240    error(EXIT_FAILURE, 0, _(errno == ENOENT
241			     ? "subsidiary program '%s' not found"
242			     : status < 1
243			     ? "subsidiary program '%s' failed"
244			     : "subsidiary program '%s' failed (status=%i, errno=%i)"), program, status, errno);
245  }
246
247
248  /*
249   *  Redirect m4's output to bison's stdin.
250   */
251  if (dup2(old_stdout, STDOUT_FILENO) < 0)
252    error(EXIT_FAILURE, 0, "restore of bison's stdout failed");
253  close(old_stdout);
254  to_in_fd = open(tmp_file_name[1], O_RDONLY, S_IRUSR);  /* pipe from m4 to bison. */
255  if (to_in_fd < 0)
256  {
257    remove_tmp_file(STDIN_FILENO, tmp_file_name[0]);
258    error(EXIT_FAILURE, 0, _("opening of tmpfile failed"));
259  }
260  if (dup2(to_in_fd, STDIN_FILENO) < 0)
261  {
262    remove_tmp_file(STDIN_FILENO, tmp_file_name[0]);
263    remove_tmp_file(to_in_fd, tmp_file_name[1]);
264    error(EXIT_FAILURE, -1, "dup2");
265    error(EXIT_FAILURE, 0, _("redirecting bison's stdin from the temporary file failed"));
266  }
267  close(to_in_fd);
268
269
270  fd[1] = STDIN_FILENO;
271}
272
273
274/* Free resources, unlink temporary files and restore stdin and stdout. */
275
276void
277reap_subpipe(pid_t pid, char const *program)
278{
279  int argc;
280
281  for (argc = 0; arguments[argc]; argc++)
282    free(arguments[argc]);
283  free(arguments);
284
285  if (unlink(tmp_file_name[0]))
286    error(EXIT_FAILURE, 0, _("removing of '%s' failed"), tmp_file_name[0]);
287  if (unlink(tmp_file_name[1]))
288    error(EXIT_FAILURE, 0, _("removing of '%s' failed"), tmp_file_name[1]);
289
290  if (dup2(old_stdin, STDIN_FILENO) < 0)
291    error(EXIT_FAILURE, 0, "restore of bison's stdin failed");
292  close(old_stdin);
293}
294