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 
54 static int old_stdin;
55 static int old_stdout;
56 static char **arguments;
57 static 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 
67 void
init_subpipe(void)68 init_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 
111 pid_t
create_subpipe(char const * const * argv,int fd[2])112 create_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. */
168 static int child_interrupted;
169 
170 static void
signal_catcher(int signo)171 signal_catcher(int signo)
172 {
173   child_interrupted++;
174 }
175 
176 
177 void
end_of_output_subpipe(pid_t pid,int fd[2])178 end_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 
276 void
reap_subpipe(pid_t pid,char const * program)277 reap_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