Merge branch '1858_segfault_in_search'
[midnight-commander.git] / src / pipethrough.c
blobe31f81658f74a389adf5ea9ea6c49ac6979c5cfe
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.
20 /** \file pipethrough.c
21 * \brief Source: a module for piping
23 * Pipe a buffer through a child command and receive two containing
24 * the stdout and stderr output of the child process.
27 #ifdef HAVE_CONFIG_H
28 # include <config.h>
29 #endif
31 #include <errno.h>
32 #include <signal.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h> /* On Solaris, FD_SET invokes memset(3) */
37 #include <sys/types.h>
38 #include <sys/time.h>
39 #include <sys/wait.h>
40 #ifdef HAVE_SYS_SELECT_H
41 # include <sys/select.h>
42 #endif
43 #include <unistd.h>
45 #include "pipethrough.h"
47 #define PIPE_RD 0
48 #define PIPE_WR 1
50 /* Internal data types *************************************************/
52 struct writer_buffer {
53 /*@temp@*/ const void *data;
54 size_t size;
55 size_t pos;
56 int fd;
57 int lasterror;
60 struct reader_buffer {
61 /*@null@*/ /*@only@*/ void *data;
62 size_t size;
63 size_t maxsize;
64 int fd;
65 int lasterror;
68 struct select_request {
69 int n;
70 fd_set readfds;
71 fd_set writefds;
74 /* Helper functions ****************************************************/
76 static int closeref(int *fd)
77 /*@globals internalState, fileSystem, errno; @*/
78 /*@modifies internalState, fileSystem, errno, *fd; @*/
80 int result = close(*fd);
81 *fd = -1;
82 return result;
85 static void propagate(int *dest, int src)
86 /*@modifies *dest; @*/
88 if (*dest == 0) {
89 *dest = src;
93 /* reader_buffer -- implementation *************************************/
95 static void reader_buffer_init(/*@out@*/ struct reader_buffer *buf, int fd)
96 /*@modifies *buf; @*/
98 buf->data = NULL;
99 buf->size = 0;
100 buf->maxsize = 0;
101 buf->fd = fd;
102 buf->lasterror = 0;
105 /* SPlint cannot describe realloc(3) correctly. */
106 /*@-usereleased@*/ /*@-compdef@*/ /*@-branchstate@*/ /*@-compmempass@*/
107 static ssize_t reader_buffer_read(struct reader_buffer *buf)
108 /*@globals internalState, errno; @*/
109 /*@modifies internalState, errno, *buf; @*/
111 ssize_t res;
113 if (buf->size == buf->maxsize) {
114 size_t newsize = buf->maxsize + 4096;
115 /*@only@*/ void *newdata;
116 if (buf->data == NULL) {
117 newdata = malloc(newsize);
118 } else {
119 newdata = realloc(buf->data, newsize);
121 if (newdata == NULL) {
122 return (ssize_t) -1;
124 buf->data = newdata;
125 newdata = NULL;
126 buf->maxsize = newsize;
128 res = read(buf->fd, &(((char *) buf->data)[buf->size]),
129 buf->maxsize - buf->size);
130 if (res == (ssize_t) -1) {
131 return res;
133 buf->size += (ssize_t) res;
134 return res;
136 /*@=usereleased@*/ /*@=compdef@*/ /*@=branchstate@*/ /*@=compmempass@*/
138 static void reader_buffer_handle(struct reader_buffer *buf)
139 /*@globals internalState, fileSystem, errno; @*/
140 /*@modifies internalState, fileSystem, errno, *buf; @*/
142 ssize_t rd = reader_buffer_read(buf);
143 if (rd == (ssize_t) -1) {
144 propagate(&buf->lasterror, errno);
146 if (rd <= 0) {
147 if (closeref(&(buf->fd)) == -1) {
148 propagate(&buf->lasterror, errno);
153 /*@-mustfreeonly@*/
154 static void reader_buffer_finalize(/*@special@*/ struct reader_buffer *buf)
155 /*@modifies *buf; @*/
156 /*@releases buf->data; @*/
158 if (buf->data != NULL) {
159 free(buf->data);
160 buf->data = NULL;
162 buf->size = 0;
163 buf->maxsize = 0;
164 buf->fd = -1;
166 /*@=mustfreeonly@*/
168 /* writer_buffer -- implementation *************************************/
170 static void writer_buffer_init(/*@out@*/ struct writer_buffer *buf, int fd,
171 const void *data, size_t size)
172 /*@globals internalState, fileSystem, errno; @*/
173 /*@modifies internalState, fileSystem, errno, *buf; @*/
175 buf->fd = fd;
176 buf->data = data;
177 buf->size = size;
178 buf->pos = 0;
179 buf->lasterror = 0;
180 if (size == 0) {
181 if (closeref(&(buf->fd)) == -1) {
182 propagate(&buf->lasterror, errno);
187 static void writer_buffer_handle(struct writer_buffer *buf)
188 /*@globals internalState, fileSystem, errno; @*/
189 /*@modifies internalState, fileSystem, errno, *buf; @*/
191 typedef void (*my_sighandler_fn) (int);
192 my_sighandler_fn old_sigpipe = signal(SIGPIPE, SIG_IGN);
193 ssize_t wr = write(buf->fd, &(((const char *) buf->data)[buf->pos]),
194 buf->size - buf->pos);
195 if (wr == (ssize_t) -1) {
196 propagate(&buf->lasterror, errno);
197 } else {
198 buf->pos += wr;
200 if (wr == (ssize_t) -1 || buf->pos == buf->size) {
201 if (closeref(&(buf->fd)) == -1) {
202 propagate(&buf->lasterror, errno);
205 (void) signal(SIGPIPE, old_sigpipe);
208 static void writer_buffer_finalize(/*@special@*/ struct writer_buffer *buf)
209 /*@modifies *buf; @*/
211 buf->data = NULL;
212 buf->size = 0;
213 buf->pos = 0;
214 buf->fd = -1;
217 /* select_request -- implementation ************************************/
219 static void select_request_init(/*@out@*/ struct select_request *sr)
220 /*@modifies *sr; @*/
222 FD_ZERO(&(sr->readfds));
223 FD_ZERO(&(sr->writefds));
224 sr->n = 0;
227 static void select_request_add_reader(struct select_request *sr,
228 const struct reader_buffer *buf)
229 /*@modifies *sr; @*/
231 if (buf->fd != -1) {
232 FD_SET(buf->fd, &(sr->readfds));
233 if (buf->fd + 1 > sr->n) {
234 sr->n = buf->fd + 1;
239 static void select_request_add_writer(struct select_request *sr,
240 const struct writer_buffer *buf)
241 /*@modifies *sr; @*/
243 if (buf->fd != -1) {
244 FD_SET(buf->fd, &(sr->writefds));
245 if (buf->fd + 1 > sr->n) {
246 sr->n = buf->fd + 1;
251 static void select_request_handle_writer(const struct select_request *sr,
252 struct writer_buffer *buf)
253 /*@globals internalState, fileSystem, errno; @*/
254 /*@modifies internalState, fileSystem, errno, *buf; @*/
256 if (buf->fd != -1 && FD_ISSET(buf->fd, &(sr->writefds))) {
257 writer_buffer_handle(buf);
261 static void select_request_handle_reader(const struct select_request *sr,
262 struct reader_buffer *buf)
263 /*@globals internalState, fileSystem, errno; @*/
264 /*@modifies internalState, fileSystem, errno, *buf; @*/
266 if (buf->fd != -1 && FD_ISSET(buf->fd, &(sr->readfds))) {
267 reader_buffer_handle(buf);
271 /* pipethrough -- helper functions *************************************/
273 static void pipethrough_child(const char *command,
274 const int *stdin_pipe,
275 const int *stdout_pipe,
276 const int *stderr_pipe)
277 /*@globals internalState, fileSystem, errno, stderr; @*/
278 /*@modifies internalState, fileSystem, errno, *stderr; @*/
280 const char *shell = getenv("SHELL");
281 if (shell == NULL) {
282 shell = "/bin/sh";
285 if (close(STDIN_FILENO) == -1
286 || dup2(stdin_pipe[PIPE_RD], STDIN_FILENO) == -1
287 || close(stdin_pipe[PIPE_RD]) == -1
288 || close(stdin_pipe[PIPE_WR]) == -1
289 || close(STDOUT_FILENO) == -1
290 || dup2(stdout_pipe[PIPE_WR], STDOUT_FILENO) == -1
291 || close(stdout_pipe[PIPE_RD]) == -1
292 || close(stdout_pipe[PIPE_WR]) == -1
293 || close(STDERR_FILENO) == -1
294 || dup2(stderr_pipe[PIPE_WR], STDERR_FILENO) == -1
295 || close(stderr_pipe[PIPE_RD]) == -1
296 || close(stderr_pipe[PIPE_WR]) == -1
297 || execl(shell, shell, "-c", command, NULL) == -1) {
298 perror("pipethrough_child");
299 (void) fflush(stderr);
300 _exit(EXIT_FAILURE);
304 static int pipethrough_parent(int stdin_fd,
305 int stdout_fd,
306 int stderr_fd,
307 const struct pipe_inbuffer *stdin_buf,
308 /*@out@*/ struct pipe_outbuffer *stdout_buf,
309 /*@out@*/ struct pipe_outbuffer *stderr_buf)
310 /*@globals internalState, fileSystem, errno; @*/
311 /*@modifies internalState, fileSystem, errno,
312 *stdout_buf, *stderr_buf; @*/
314 struct writer_buffer stdin_wbuf;
315 struct reader_buffer stdout_rbuf;
316 struct reader_buffer stderr_rbuf;
317 struct select_request sr;
318 int select_res;
319 int firsterror;
321 firsterror = 0;
322 writer_buffer_init(&stdin_wbuf, stdin_fd, stdin_buf->data,
323 stdin_buf->size);
324 propagate(&firsterror, stdin_wbuf.lasterror);
325 reader_buffer_init(&stdout_rbuf, stdout_fd);
326 propagate(&firsterror, stdout_rbuf.lasterror);
327 reader_buffer_init(&stderr_rbuf, stderr_fd);
328 propagate(&firsterror, stdout_rbuf.lasterror);
330 while (stdin_wbuf.fd != -1 || stdout_rbuf.fd != -1
331 || stderr_rbuf.fd != -1) {
332 select_request_init(&sr);
334 retry_select:
335 select_request_add_writer(&sr, &stdin_wbuf);
336 select_request_add_reader(&sr, &stdout_rbuf);
337 select_request_add_reader(&sr, &stderr_rbuf);
338 select_res = select(sr.n, &(sr.readfds), &(sr.writefds),
339 NULL, NULL);
340 if (select_res == -1) {
341 if (errno == EINTR) {
342 goto retry_select;
343 } else {
344 propagate(&firsterror, errno);
345 goto cleanup_select;
348 select_request_handle_writer(&sr, &stdin_wbuf);
349 propagate(&firsterror, stdin_wbuf.lasterror);
350 select_request_handle_reader(&sr, &stdout_rbuf);
351 propagate(&firsterror, stdout_rbuf.lasterror);
352 select_request_handle_reader(&sr, &stderr_rbuf);
353 propagate(&firsterror, stdout_rbuf.lasterror);
355 stdout_buf->data = stdout_rbuf.data;
356 stdout_rbuf.data = NULL;
357 stdout_buf->size = stdout_rbuf.size;
358 stderr_buf->data = stderr_rbuf.data;
359 stderr_rbuf.data = NULL;
360 stderr_buf->size = stderr_rbuf.size;
361 return 0;
363 cleanup_select:
364 writer_buffer_finalize(&stdin_wbuf);
365 reader_buffer_finalize(&stdout_rbuf);
366 reader_buffer_finalize(&stderr_rbuf);
367 errno = firsterror;
368 return -1;
371 /* pipethrough -- implementation ***************************************/
373 extern int pipethrough(const char *command,
374 const struct pipe_inbuffer *stdin_buf,
375 /*@out@*/ struct pipe_outbuffer *stdout_buf,
376 /*@out@*/ struct pipe_outbuffer *stderr_buf,
377 /*@out@*/ int *status)
378 /*@globals internalState, fileSystem, errno, stderr; @*/
379 /*@modifies internalState, fileSystem, errno, *stderr,
380 *stdout_buf, *stderr_buf, *status; @*/
382 int retval = -1;
383 pid_t pid;
384 int stdin_pipe[2] = { -1, -1 };
385 int stdout_pipe[2] = { -1, -1 };
386 int stderr_pipe[2] = { -1, -1 };
387 int firsterror = 0;
389 if (pipe(stdin_pipe) == -1
390 || pipe(stdout_pipe) == -1
391 || pipe(stderr_pipe) == -1) {
392 propagate(&firsterror, errno);
393 goto cleanup;
396 if ((pid = fork()) == (pid_t) -1) {
397 propagate(&firsterror, errno);
398 goto cleanup;
401 if (pid == 0) {
402 pipethrough_child(command, stdin_pipe, stdout_pipe,
403 stderr_pipe);
404 /*@notreached@*/
407 if (closeref(&stdin_pipe[PIPE_RD]) == -1
408 || closeref(&stdout_pipe[PIPE_WR]) == -1
409 || closeref(&stderr_pipe[PIPE_WR]) == -1) {
410 propagate(&firsterror, errno);
411 goto cleanup;
414 if (pipethrough_parent(stdin_pipe[PIPE_WR], stdout_pipe[PIPE_RD],
415 stderr_pipe[PIPE_RD], stdin_buf,
416 stdout_buf, stderr_buf) == -1) {
417 propagate(&firsterror, errno);
418 } else {
419 retval = 0;
422 /*@-branchstate@*/
423 if (waitpid(pid, status, 0) == (pid_t) -1) {
424 propagate(&firsterror, errno);
425 pipe_outbuffer_finalize(stdout_buf);
426 pipe_outbuffer_finalize(stderr_buf);
427 retval = -1;
429 /*@=branchstate@*/
431 cleanup:
432 (void) close(stderr_pipe[PIPE_RD]);
433 (void) close(stderr_pipe[PIPE_WR]);
434 (void) close(stdout_pipe[PIPE_RD]);
435 (void) close(stdout_pipe[PIPE_WR]);
436 (void) close(stdin_pipe[PIPE_RD]);
437 (void) close(stdin_pipe[PIPE_WR]);
438 if (retval == -1) {
439 errno = firsterror;
441 return retval;
444 /* pipe_outbuffer -- implementation ************************************/
446 /*@-mustfreeonly@*/ /*@-nullstate@*/
447 extern void pipe_outbuffer_finalize(struct pipe_outbuffer *buf)
448 /*@modifies *buf; @*/
449 /*@releases buf->data; @*/
450 /*@ensures isnull buf->data; @*/
452 if (buf->data != NULL) {
453 free(buf->data);
454 buf->data = NULL;
456 buf->size = 0;
458 /*@=mustfreeonly@*/ /*@=nullstate@*/