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