Original patch as attached on the bugreport
[midnight-commander.git] / src / pipethrough.c
blobc2274a5cadfcf7498ff8bcdf1dfa5b9a529623ec
1 /*
2 This program is free software; you can redistribute it and/or modify
3 it under the terms of the GNU General Public License as published by
4 the Free Software Foundation; either version 2 of the License, or
5 (at your option) any later version.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY; without even the implied warranty of
9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 GNU General Public License for more details.
12 You should have received a copy of the GNU General Public License
13 along with this program; if not, write to the Free Software
14 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 Roland Illig <roland.illig@gmx.de>, 2004.
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
23 #include <errno.h>
24 #include <signal.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h> /* On Solaris, FD_SET invokes memset(3) */
29 #include <sys/types.h>
30 #include <sys/time.h>
31 #include <sys/wait.h>
32 #ifdef HAVE_SYS_SELECT_H
33 # include <sys/select.h>
34 #endif
35 #include <unistd.h>
37 #include "pipethrough.h"
39 #define PIPE_RD 0
40 #define PIPE_WR 1
42 /* Internal data types *************************************************/
44 struct writer_buffer {
45 /*@temp@*/ const void *data;
46 size_t size;
47 size_t pos;
48 int fd;
49 int lasterror;
52 struct reader_buffer {
53 /*@null@*/ /*@only@*/ void *data;
54 size_t size;
55 size_t maxsize;
56 int fd;
57 int lasterror;
60 struct select_request {
61 int n;
62 fd_set readfds;
63 fd_set writefds;
66 /* Helper functions ****************************************************/
68 static int closeref(int *fd)
69 /*@globals internalState, fileSystem, errno; @*/
70 /*@modifies internalState, fileSystem, errno, *fd; @*/
72 int result = close(*fd);
73 *fd = -1;
74 return result;
77 static void propagate(int *dest, int src)
78 /*@modifies *dest; @*/
80 if (*dest == 0) {
81 *dest = src;
85 /* reader_buffer -- implementation *************************************/
87 static void reader_buffer_init(/*@out@*/ struct reader_buffer *buf, int fd)
88 /*@modifies *buf; @*/
90 buf->data = NULL;
91 buf->size = 0;
92 buf->maxsize = 0;
93 buf->fd = fd;
94 buf->lasterror = 0;
97 /* SPlint cannot describe realloc(3) correctly. */
98 /*@-usereleased@*/ /*@-compdef@*/ /*@-branchstate@*/ /*@-compmempass@*/
99 static ssize_t reader_buffer_read(struct reader_buffer *buf)
100 /*@globals internalState, errno; @*/
101 /*@modifies internalState, errno, *buf; @*/
103 ssize_t res;
105 if (buf->size == buf->maxsize) {
106 size_t newsize = buf->maxsize + 4096;
107 /*@only@*/ void *newdata;
108 if (buf->data == NULL) {
109 newdata = malloc(newsize);
110 } else {
111 newdata = realloc(buf->data, newsize);
113 if (newdata == NULL) {
114 return (ssize_t) -1;
116 buf->data = newdata;
117 newdata = NULL;
118 buf->maxsize = newsize;
120 res = read(buf->fd, &(((char *) buf->data)[buf->size]),
121 buf->maxsize - buf->size);
122 if (res == (ssize_t) -1) {
123 return res;
125 buf->size += (ssize_t) res;
126 return res;
128 /*@=usereleased@*/ /*@=compdef@*/ /*@=branchstate@*/ /*@=compmempass@*/
130 static void reader_buffer_handle(struct reader_buffer *buf)
131 /*@globals internalState, fileSystem, errno; @*/
132 /*@modifies internalState, fileSystem, errno, *buf; @*/
134 ssize_t rd = reader_buffer_read(buf);
135 if (rd == (ssize_t) -1) {
136 propagate(&buf->lasterror, errno);
138 if (rd <= 0) {
139 if (closeref(&(buf->fd)) == -1) {
140 propagate(&buf->lasterror, errno);
145 /*@-mustfreeonly@*/
146 static void reader_buffer_finalize(/*@special@*/ struct reader_buffer *buf)
147 /*@modifies *buf; @*/
148 /*@releases buf->data; @*/
150 if (buf->data != NULL) {
151 free(buf->data);
152 buf->data = NULL;
154 buf->size = 0;
155 buf->maxsize = 0;
156 buf->fd = -1;
158 /*@=mustfreeonly@*/
160 /* writer_buffer -- implementation *************************************/
162 static void writer_buffer_init(/*@out@*/ struct writer_buffer *buf, int fd,
163 const void *data, size_t size)
164 /*@globals internalState, fileSystem, errno; @*/
165 /*@modifies internalState, fileSystem, errno, *buf; @*/
167 buf->fd = fd;
168 buf->data = data;
169 buf->size = size;
170 buf->pos = 0;
171 buf->lasterror = 0;
172 if (size == 0) {
173 if (closeref(&(buf->fd)) == -1) {
174 propagate(&buf->lasterror, errno);
179 static void writer_buffer_handle(struct writer_buffer *buf)
180 /*@globals internalState, fileSystem, errno; @*/
181 /*@modifies internalState, fileSystem, errno, *buf; @*/
183 typedef void (*my_sighandler_fn) (int);
184 my_sighandler_fn old_sigpipe = signal(SIGPIPE, SIG_IGN);
185 ssize_t wr = write(buf->fd, &(((const char *) buf->data)[buf->pos]),
186 buf->size - buf->pos);
187 if (wr == (ssize_t) -1) {
188 propagate(&buf->lasterror, errno);
189 } else {
190 buf->pos += wr;
192 if (wr == (ssize_t) -1 || buf->pos == buf->size) {
193 if (closeref(&(buf->fd)) == -1) {
194 propagate(&buf->lasterror, errno);
197 (void) signal(SIGPIPE, old_sigpipe);
200 static void writer_buffer_finalize(/*@special@*/ struct writer_buffer *buf)
201 /*@modifies *buf; @*/
203 buf->data = NULL;
204 buf->size = 0;
205 buf->pos = 0;
206 buf->fd = -1;
209 /* select_request -- implementation ************************************/
211 static void select_request_init(/*@out@*/ struct select_request *sr)
212 /*@modifies *sr; @*/
214 FD_ZERO(&(sr->readfds));
215 FD_ZERO(&(sr->writefds));
216 sr->n = 0;
219 static void select_request_add_reader(struct select_request *sr,
220 const struct reader_buffer *buf)
221 /*@modifies *sr; @*/
223 if (buf->fd != -1) {
224 FD_SET(buf->fd, &(sr->readfds));
225 if (buf->fd + 1 > sr->n) {
226 sr->n = buf->fd + 1;
231 static void select_request_add_writer(struct select_request *sr,
232 const struct writer_buffer *buf)
233 /*@modifies *sr; @*/
235 if (buf->fd != -1) {
236 FD_SET(buf->fd, &(sr->writefds));
237 if (buf->fd + 1 > sr->n) {
238 sr->n = buf->fd + 1;
243 static void select_request_handle_writer(const struct select_request *sr,
244 struct writer_buffer *buf)
245 /*@globals internalState, fileSystem, errno; @*/
246 /*@modifies internalState, fileSystem, errno, *buf; @*/
248 if (buf->fd != -1 && FD_ISSET(buf->fd, &(sr->writefds))) {
249 writer_buffer_handle(buf);
253 static void select_request_handle_reader(const struct select_request *sr,
254 struct reader_buffer *buf)
255 /*@globals internalState, fileSystem, errno; @*/
256 /*@modifies internalState, fileSystem, errno, *buf; @*/
258 if (buf->fd != -1 && FD_ISSET(buf->fd, &(sr->readfds))) {
259 reader_buffer_handle(buf);
263 /* pipethrough -- helper functions *************************************/
265 static void pipethrough_child(const char *command,
266 const int *stdin_pipe,
267 const int *stdout_pipe,
268 const int *stderr_pipe)
269 /*@globals internalState, fileSystem, errno, stderr; @*/
270 /*@modifies internalState, fileSystem, errno, *stderr; @*/
272 const char *shell = getenv("SHELL");
273 if (shell == NULL) {
274 shell = "/bin/sh";
277 if (close(STDIN_FILENO) == -1
278 || dup2(stdin_pipe[PIPE_RD], STDIN_FILENO) == -1
279 || close(stdin_pipe[PIPE_RD]) == -1
280 || close(stdin_pipe[PIPE_WR]) == -1
281 || close(STDOUT_FILENO) == -1
282 || dup2(stdout_pipe[PIPE_WR], STDOUT_FILENO) == -1
283 || close(stdout_pipe[PIPE_RD]) == -1
284 || close(stdout_pipe[PIPE_WR]) == -1
285 || close(STDERR_FILENO) == -1
286 || dup2(stderr_pipe[PIPE_WR], STDERR_FILENO) == -1
287 || close(stderr_pipe[PIPE_RD]) == -1
288 || close(stderr_pipe[PIPE_WR]) == -1
289 || execl(shell, shell, "-c", command, NULL) == -1) {
290 perror("pipethrough_child");
291 (void) fflush(stderr);
292 _exit(EXIT_FAILURE);
296 static int pipethrough_parent(int stdin_fd,
297 int stdout_fd,
298 int stderr_fd,
299 const struct pipe_inbuffer *stdin_buf,
300 /*@out@*/ struct pipe_outbuffer *stdout_buf,
301 /*@out@*/ struct pipe_outbuffer *stderr_buf)
302 /*@globals internalState, fileSystem, errno; @*/
303 /*@modifies internalState, fileSystem, errno,
304 *stdout_buf, *stderr_buf; @*/
306 struct writer_buffer stdin_wbuf;
307 struct reader_buffer stdout_rbuf;
308 struct reader_buffer stderr_rbuf;
309 struct select_request sr;
310 int select_res;
311 int firsterror;
313 firsterror = 0;
314 writer_buffer_init(&stdin_wbuf, stdin_fd, stdin_buf->data,
315 stdin_buf->size);
316 propagate(&firsterror, stdin_wbuf.lasterror);
317 reader_buffer_init(&stdout_rbuf, stdout_fd);
318 propagate(&firsterror, stdout_rbuf.lasterror);
319 reader_buffer_init(&stderr_rbuf, stderr_fd);
320 propagate(&firsterror, stdout_rbuf.lasterror);
322 while (stdin_wbuf.fd != -1 || stdout_rbuf.fd != -1
323 || stderr_rbuf.fd != -1) {
324 select_request_init(&sr);
326 retry_select:
327 select_request_add_writer(&sr, &stdin_wbuf);
328 select_request_add_reader(&sr, &stdout_rbuf);
329 select_request_add_reader(&sr, &stderr_rbuf);
330 select_res = select(sr.n, &(sr.readfds), &(sr.writefds),
331 NULL, NULL);
332 if (select_res == -1) {
333 if (errno == EINTR) {
334 goto retry_select;
335 } else {
336 propagate(&firsterror, errno);
337 goto cleanup_select;
340 select_request_handle_writer(&sr, &stdin_wbuf);
341 propagate(&firsterror, stdin_wbuf.lasterror);
342 select_request_handle_reader(&sr, &stdout_rbuf);
343 propagate(&firsterror, stdout_rbuf.lasterror);
344 select_request_handle_reader(&sr, &stderr_rbuf);
345 propagate(&firsterror, stdout_rbuf.lasterror);
347 stdout_buf->data = stdout_rbuf.data;
348 stdout_rbuf.data = NULL;
349 stdout_buf->size = stdout_rbuf.size;
350 stderr_buf->data = stderr_rbuf.data;
351 stderr_rbuf.data = NULL;
352 stderr_buf->size = stderr_rbuf.size;
353 return 0;
355 cleanup_select:
356 writer_buffer_finalize(&stdin_wbuf);
357 reader_buffer_finalize(&stdout_rbuf);
358 reader_buffer_finalize(&stderr_rbuf);
359 errno = firsterror;
360 return -1;
363 /* pipethrough -- implementation ***************************************/
365 extern int pipethrough(const char *command,
366 const struct pipe_inbuffer *stdin_buf,
367 /*@out@*/ struct pipe_outbuffer *stdout_buf,
368 /*@out@*/ struct pipe_outbuffer *stderr_buf,
369 /*@out@*/ int *status)
370 /*@globals internalState, fileSystem, errno, stderr; @*/
371 /*@modifies internalState, fileSystem, errno, *stderr,
372 *stdout_buf, *stderr_buf, *status; @*/
374 int retval = -1;
375 pid_t pid;
376 int stdin_pipe[2] = { -1, -1 };
377 int stdout_pipe[2] = { -1, -1 };
378 int stderr_pipe[2] = { -1, -1 };
379 int firsterror = 0;
381 if (pipe(stdin_pipe) == -1
382 || pipe(stdout_pipe) == -1
383 || pipe(stderr_pipe) == -1) {
384 propagate(&firsterror, errno);
385 goto cleanup;
388 if ((pid = fork()) == (pid_t) -1) {
389 propagate(&firsterror, errno);
390 goto cleanup;
393 if (pid == 0) {
394 pipethrough_child(command, stdin_pipe, stdout_pipe,
395 stderr_pipe);
396 /*@notreached@*/
399 if (closeref(&stdin_pipe[PIPE_RD]) == -1
400 || closeref(&stdout_pipe[PIPE_WR]) == -1
401 || closeref(&stderr_pipe[PIPE_WR]) == -1) {
402 propagate(&firsterror, errno);
403 goto cleanup;
406 if (pipethrough_parent(stdin_pipe[PIPE_WR], stdout_pipe[PIPE_RD],
407 stderr_pipe[PIPE_RD], stdin_buf,
408 stdout_buf, stderr_buf) == -1) {
409 propagate(&firsterror, errno);
410 } else {
411 retval = 0;
414 /*@-branchstate@*/
415 if (waitpid(pid, status, 0) == (pid_t) -1) {
416 propagate(&firsterror, errno);
417 pipe_outbuffer_finalize(stdout_buf);
418 pipe_outbuffer_finalize(stderr_buf);
419 retval = -1;
421 /*@=branchstate@*/
423 cleanup:
424 (void) close(stderr_pipe[PIPE_RD]);
425 (void) close(stderr_pipe[PIPE_WR]);
426 (void) close(stdout_pipe[PIPE_RD]);
427 (void) close(stdout_pipe[PIPE_WR]);
428 (void) close(stdin_pipe[PIPE_RD]);
429 (void) close(stdin_pipe[PIPE_WR]);
430 if (retval == -1) {
431 errno = firsterror;
433 return retval;
436 /* pipe_outbuffer -- implementation ************************************/
438 /*@-mustfreeonly@*/ /*@-nullstate@*/
439 extern void pipe_outbuffer_finalize(struct pipe_outbuffer *buf)
440 /*@modifies *buf; @*/
441 /*@releases buf->data; @*/
442 /*@ensures isnull buf->data; @*/
444 if (buf->data != NULL) {
445 free(buf->data);
446 buf->data = NULL;
448 buf->size = 0;
450 /*@=mustfreeonly@*/ /*@=nullstate@*/