[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[PATCH] My version of pipe-filter
From: |
Paolo Bonzini |
Subject: |
[PATCH] My version of pipe-filter |
Date: |
Sat, 25 Jul 2009 12:49:40 +0200 |
Hi all, here is a version of pipe-filter that works under Win32
too (tested with Wine), has tests, and so on.
Testing under native Windows would be appreciated.
Bruno, what do you think?
Paolo
2009-07-25 Paolo Bonzini <address@hidden>
* lib/pipe-filter.c: New.
* lib/pipe-filter.h: New.
* modules/pipe-filter: New.
* modules/pipe-filter-tests: New.
* tests/test-pipe-filter.sh: New.
* tests/test-pipe-filter-helper.c: New.
* tests/test-pipe-filter-main.c: New.
Cc: Bruno Haible <address@hidden>
---
lib/pipe-filter.c | 522 +++++++++++++++++++++++++++++++++++++++
lib/pipe-filter.h | 122 +++++++++
modules/pipe-filter | 33 +++
modules/pipe-filter-tests | 15 ++
tests/test-pipe-filter-helper.c | 38 +++
tests/test-pipe-filter-main.c | 101 ++++++++
tests/test-pipe-filter.sh | 32 +++
8 files changed, 875 insertions(+), 0 deletions(-)
create mode 100644 lib/pipe-filter.c
create mode 100644 lib/pipe-filter.h
create mode 100644 modules/pipe-filter
create mode 100644 modules/pipe-filter-tests
create mode 100644 tests/test-pipe-filter-helper.c
create mode 100644 tests/test-pipe-filter-main.c
create mode 100755 tests/test-pipe-filter.sh
diff --git a/lib/pipe-filter.c b/lib/pipe-filter.c
new file mode 100644
index 0000000..7949761
--- /dev/null
+++ b/lib/pipe-filter.c
@@ -0,0 +1,522 @@
+/* Synchronous writing, asynchronous reading of pipes connected to a
+ subprocess.
+
+ Copyright (C) 2009 Free Software Foundation, Inc.
+ Written by Paolo Bonzini <address@hidden> and Bruno Haible
+ <address@hidden>, 2009.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include "pipe-filter.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+#include <windows.h>
+#else
+#include <sys/select.h>
+#endif
+
+#include "error.h"
+#include "gettext.h"
+#include "pipe.h"
+#include "wait-process.h"
+#include "xalloc.h"
+
+#define _(str) gettext (str)
+
+#ifndef SSIZE_MAX
+# define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2))
+#endif
+
+
+/* We use a child process, and communicate through a bidirectional pipe.
+ To avoid deadlocks, let the child process decide when it wants to write,
+ and let the parent read accordingly. The parent uses select() to
+ know whether it must write or read. On platforms without select(),
+ we use non-blocking I/O. (This means the parent is busy looping
+ while waiting for the child. Not good. But hardly any platform
+ lacks select() nowadays.) */
+
+/* On BeOS select() works only on sockets, not on normal file descriptors. */
+#ifdef __BEOS__
+# undef HAVE_SELECT
+#endif
+
+#ifdef EINTR
+
+/* EINTR handling for close(), read(), write(), select().
+ These functions can return -1/EINTR even though we don't have any
+ signal handlers set up, namely when we get interrupted via SIGSTOP. */
+
+static inline int
+nonintr_close (int fd)
+{
+ int retval;
+
+ do
+ retval = close (fd);
+ while (retval < 0 && errno == EINTR);
+
+ return retval;
+}
+#undef close /* avoid warning related to gnulib module unistd */
+#define close nonintr_close
+
+static inline ssize_t
+nonintr_read (int fd, void *buf, size_t bufsize)
+{
+ ssize_t retval;
+
+ do
+ retval = read (fd, buf, bufsize);
+ while (retval < 0 && errno == EINTR);
+
+ return retval;
+}
+#define read nonintr_read
+
+static inline ssize_t
+nonintr_write (int fd, const void *buf, size_t count)
+{
+ ssize_t retval;
+
+ do
+ retval = write (fd, buf, count);
+ while (retval < 0 && errno == EINTR);
+
+ return retval;
+}
+#undef write /* avoid warning on VMS */
+#define write nonintr_write
+
+# if HAVE_SELECT
+
+static inline int
+nonintr_select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
+ struct timeval *timeout)
+{
+ int retval;
+
+ do
+ retval = select (n, readfds, writefds, exceptfds, timeout);
+ while (retval < 0 && errno == EINTR);
+
+ return retval;
+}
+# undef select /* avoid warning on VMS */
+# define select nonintr_select
+
+# endif
+
+#endif
+
+/* Non-blocking I/O. */
+#ifndef O_NONBLOCK
+# define O_NONBLOCK O_NDELAY
+#endif
+#if HAVE_SELECT
+# define IS_EAGAIN(errcode) 0
+#else
+# ifdef EWOULDBLOCK
+# define IS_EAGAIN(errcode) ((errcode) == EAGAIN || (errcode) == EWOULDBLOCK)
+# else
+# define IS_EAGAIN(errcode) ((errcode) == EAGAIN)
+# endif
+#endif
+
+struct filter {
+ pid_t child;
+ const char *progname;
+ bool null_stderr;
+ bool exit_on_error;
+ char *read_buf;
+ size_t read_bufsize;
+ done_read_fn done_read;
+ void *private_data;
+ int fd[2];
+ int exit_code;
+
+ volatile int reader_errno;
+ volatile int writer_errno;
+
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+ HANDLE hCallbackMutex;
+ HANDLE hReader;
+#else
+ fd_set readfds, writefds;
+#endif
+};
+
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+
+static unsigned int WINAPI
+reader_thread_func (void *thread_arg)
+{
+ struct filter *f = (struct filter *) thread_arg;
+
+ for (;;)
+ {
+ ssize_t nread = read (f->fd[0], f->read_buf, f->read_bufsize);
+ WaitForSingleObject (f->hCallbackMutex, INFINITE);
+ if (f->writer_errno)
+ break;
+
+ if (nread == -1)
+ {
+ f->reader_errno = errno;
+ break;
+ }
+ if (nread > 0)
+ f->done_read (f->read_buf, nread, f->private_data);
+ else
+ break;
+ ReleaseMutex (f->hCallbackMutex);
+ }
+
+ f->reader_errno = -1;
+ ReleaseMutex (f->hCallbackMutex);
+ _endthreadex (0); /* calls ExitThread (0) */
+ abort ();
+}
+
+static int
+filter_init (struct filter *f)
+{
+ f->hCallbackMutex = CreateMutex (NULL, TRUE, NULL);
+
+ f->hReader =
+ (HANDLE) _beginthreadex (NULL, 100000, reader_thread_func, f, 0, NULL);
+
+ if (f->hReader == NULL)
+ {
+ if (f->exit_on_error)
+ error (EXIT_FAILURE, 0, _("creation of reading thread failed"));
+ return -1;
+ }
+ else
+ return 0;
+}
+
+static void
+filter_loop (struct filter *f, const char *buf, size_t count)
+{
+ /* While within filter_write, the reader thread can do callbacks. */
+ ReleaseMutex (f->hCallbackMutex);
+
+ if (f->writer_errno == 0)
+ {
+ ssize_t nwritten = write (f->fd[1], buf, count);
+ if (nwritten == -1)
+ f->writer_errno = errno;
+ }
+
+ /* Wait for the current callback to return. */
+ WaitForSingleObject (f->hCallbackMutex, INFINITE);
+
+ if (!f->writer_errno && f->reader_errno && f->reader_errno != -1)
+ errno = f->reader_errno;
+}
+
+static void
+filter_cleanup (struct filter *f, bool finish_reading)
+{
+ if (finish_reading)
+ {
+ ReleaseMutex (f->hCallbackMutex);
+ WaitForSingleObject (f->hReader, INFINITE);
+ }
+ else
+ TerminateThread (f->hReader, 1);
+
+ CloseHandle (f->hReader);
+ CloseHandle (f->hCallbackMutex);
+}
+
+#else
+static int
+filter_init (struct filter *f)
+{
+ /* Enable non-blocking I/O. This permits the read() and write() calls
+ to return -1/EAGAIN without blocking; this is important for polling
+ if HAVE_SELECT is not defined. It also permits the read() and write()
+ calls to return after partial reads/writes; this is important if
+ HAVE_SELECT is defined, because select() only says that some data
+ can be read or written, not how many. Without non-blocking I/O,
+ Linux 2.2.17 and BSD systems prefer to block instead of returning
+ with partial results. */
+ int fcntl_flags;
+
+ if ((fcntl_flags = fcntl (f->fd[1], F_GETFL, 0)) < 0
+ || fcntl (f->fd[1], F_SETFL, fcntl_flags | O_NONBLOCK) < 0
+ || (fcntl_flags = fcntl (f->fd[0], F_GETFL, 0)) < 0
+ || fcntl (f->fd[0], F_SETFL, fcntl_flags | O_NONBLOCK) < 0)
+ {
+ if (f->exit_on_error)
+ error (EXIT_FAILURE, errno,
+ _("cannot set up nonblocking I/O to %s subprocess"),
+ f->progname);
+ return -1;
+ }
+
+ FD_ZERO (&f->readfds);
+ FD_ZERO (&f->writefds);
+ return 0;
+}
+
+static void
+filter_loop (struct filter *f, const char *buf, size_t count)
+{
+ static struct timeval tv0;
+
+ assert (!f->reader_errno && !f->writer_errno);
+ for (;;)
+ {
+#if HAVE_SELECT
+ int n = f->fd[0] > f->fd[1] ? f->fd[0] + 1 : f->fd[1] + 1;
+ if (!f->reader_errno)
+ FD_SET (f->fd[0], &f->readfds);
+ if (!f->writer_errno)
+ FD_SET (f->fd[1], &f->writefds);
+ n = select (n, &f->readfds, (count ? &f->writefds : NULL), NULL,
+ (buf && !count ? &tv0 : NULL));
+ if (n == 0)
+ break;
+
+ if (n == -1)
+ {
+ f->writer_errno = errno;
+ if (f->exit_on_error)
+ error (EXIT_FAILURE, errno,
+ _("communication with %s subprocess failed"),
+ f->progname);
+ return;
+ }
+
+ if (count && FD_ISSET (f->fd[1], &f->writefds))
+ goto try_write;
+ if (FD_ISSET (f->fd[0], &f->readfds))
+ goto try_read;
+ break;
+#endif
+
+ /* Attempt to write. */
+#if HAVE_SELECT
+ try_write:
+#endif
+ if (count)
+ {
+ ssize_t nwritten = write (f->fd[1], buf,
+ count > SSIZE_MAX ? SSIZE_MAX : count);
+ if (nwritten == -1)
+ {
+ if (IS_EAGAIN (errno))
+ continue;
+ f->writer_errno = errno;
+ if (f->exit_on_error)
+ error (EXIT_FAILURE, errno,
+ _("write to %s subprocess failed"), f->progname);
+ return;
+ }
+ else
+ {
+ count -= nwritten;
+ buf += nwritten;
+ }
+ }
+#if HAVE_SELECT
+ continue;
+#endif
+
+ /* Attempt to read. */
+#if HAVE_SELECT
+ try_read:
+#endif
+ {
+ ssize_t nread = read (f->fd[0], f->read_buf, f->read_bufsize);
+ if (nread == -1)
+ {
+ if (IS_EAGAIN (errno))
+ continue;
+ f->reader_errno = errno;
+ if (f->exit_on_error)
+ error (EXIT_FAILURE, errno,
+ _("read from %s subprocess failed"), f->progname);
+ return;
+ }
+ else if (nread == 0)
+ {
+ f->reader_errno = -1;
+ return;
+ }
+ else
+ f->done_read (f->read_buf, nread, f->private_data);
+ }
+#if HAVE_SELECT
+ continue;
+#endif
+ }
+}
+
+static void
+filter_cleanup (struct filter *f, bool finish_reading)
+{
+ if (finish_reading)
+ filter_loop (f, NULL, 0);
+}
+#endif
+
+
+static void
+filter_terminate (struct filter *f)
+{
+ if (f->exit_code == -1)
+ {
+ close (f->fd[1]);
+ filter_cleanup (f, !f->reader_errno && !f->writer_errno);
+ close (f->fd[0]);
+ f->exit_code = wait_subprocess (f->child, f->progname, true,
f->null_stderr,
+ true, f->exit_on_error, NULL);
+ if (f->exit_on_error && f->exit_code)
+ error (EXIT_FAILURE, 0, _("subprocess %s failed (exit status %d)"),
+ f->progname, f->exit_code);
+
+ }
+}
+
+/* Create a subprocess and pipe some data through it.
+ progname is the program name used in error messages.
+ prog_path is the file name of the program to invoke.
+ prog_argv is a NULL terminated argument list, starting with
+ prog_path as first element.
+ If null_stderr is true, the subprocess' stderr will be redirected
+ to /dev/null, and the usual error message to stderr will be
+ omitted. This is suitable when the subprocess does not fulfill an
+ important task.
+ If exit_on_error is true, any error will cause the main process to
+ exit with an error status.
+ If the subprocess does not start correctly, exit if exit_on_error is
+ true, otherwise return NULL and set errno.
+
+ The caller will write to the subprocess through filter_write; during
+ calls to filter_write, the done_read function may be called to
+ process any data that the subprocess has written. done_read will
+ receive at most read_bufsize bytes stored into buf, as well as a
+ copy of private_data. */
+struct filter *
+filter_create (const char *progname,
+ const char *prog_path, const char **prog_argv,
+ bool null_stderr, bool exit_on_error,
+ char *read_buf, size_t read_bufsize,
+ done_read_fn done_read,
+ void *private_data)
+{
+ struct filter *f = xmalloc (sizeof (struct filter));
+ pid_t child;
+ int fd[2];
+
+ /* Open a bidirectional pipe to a subprocess. */
+ f->child = create_pipe_bidi (progname, prog_path, (char **) prog_argv,
+ null_stderr, true, exit_on_error,
+ fd);
+
+ f->progname = progname;
+ f->null_stderr = null_stderr;
+ f->exit_on_error = exit_on_error;
+ f->exit_code = -1;
+ f->read_buf = read_buf;
+ f->read_bufsize = read_bufsize;
+ f->done_read = done_read;
+ f->private_data = private_data;
+ f->reader_errno = 0;
+ f->writer_errno = 0;
+ f->fd[0] = fd[0];
+ f->fd[1] = fd[1];
+
+ if (filter_init (f) < 0)
+ filter_terminate (f);
+
+ return f;
+}
+
+
+/* Write size bytes starting at buf into the pipe and in the meanwhile
+ possibly call the done_read function specified in create_filter.
+ The done_read function may be called in a different thread than
+ the current thread, depending on the platform. However, it will
+ always be called before filter_write has returned (or else will be
+ delayed to the next call to filter_write or filter_close). Return
+ only after all the entire buffer has been written to the pipe.
+
+ If the subprocess exits early with zero status, subsequent writes
+ will becomes no-ops and zero is returned.
+
+ If there is a problem reading or writing, return -1 and set errno.
+
+ If the subprocess exits early with nonzero status, return the status.
+ (In either case, filter_write will instead exit if exit_on_error was
+ passed as true).
+
+ Otherwise return 0. */
+int
+filter_write (struct filter *f, const char *buf, size_t count)
+{
+ int rc, save_errno;
+ assert (buf);
+ if (f->exit_code != -1)
+ return f->exit_code;
+ if (!count)
+ return 0;
+
+ filter_loop (f, buf, count);
+ if (!f->reader_errno && !f->writer_errno)
+ return 0;
+
+ save_errno = errno;
+ filter_terminate (f);
+ errno = save_errno;
+ return (f->writer_errno ? -1 : f->exit_code);
+}
+
+/* Finish reading the output via the done_read function specified in
+ create_filter. The done_read function may be called in a different
+ thread than. However, it will always be called before filter_close
+ has returned. The write side of the pipe is closed as soon as
+ filter_close starts, while the read side will be closed just before
+ it finishes. If there is a problem reading or closing the pipe,
+ return -1 and set errno. If the subprocess exits early with nonzero
+ status, return the status. (In either case, filter_close will
+ instead exit if exit_on_error was passed as true).
+
+ Otherwise return 0. */
+int
+filter_close (struct filter *f)
+{
+ int rc, save_errno;
+ filter_terminate (f);
+ rc = f->exit_code;
+ save_errno = errno;
+ free (f);
+ errno = save_errno;
+ return rc;
+}
diff --git a/lib/pipe-filter.h b/lib/pipe-filter.h
new file mode 100644
index 0000000..0290c11
--- /dev/null
+++ b/lib/pipe-filter.h
@@ -0,0 +1,122 @@
+/* Synchronous writing, asynchronous reading of pipes connected to a
+ subprocess.
+
+ Copyright (C) 2009 Free Software Foundation, Inc.
+ Written by Bruno Haible <address@hidden>, 2009.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef _PIPE_FILTER_H
+#define _PIPE_FILTER_H
+
+#include <stdbool.h>
+#include <stddef.h>
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* Piping data through a subprocess in the naïve way - write data to the
+ subprocess and read from the subprocess when you expect it to have
+ produced results - is subject to two kinds of deadlocks:
+ 1) If you write more than PIPE_MAX bytes or, more generally, if you write
+ more bytes than the subprocess can handle at once, the subprocess
+ may write its data and wait on you to read it, but you are currently
+ busy writing.
+ 2) When you don't know ahead of time how many bytes the subprocess
+ will produce, the usual technique of calling read (fd, buf, BUFSIZ)
+ with a fixed BUFSIZ will, on Linux 2.2.17 and on BSD systems, cause
+ the read() call to block until *all* of the buffer has been filled.
+ But the subprocess cannot produce more data until you gave it more
+ input. But you are currently busy reading from it.
+ This module provides a function that pipes data to a subprocess and
+ gets its output back via a callback, without risking these deadlocks. */
+
+
+typedef void (*done_read_fn) (char *read_buf, size_t num_bytes_read,
+ void *private_data);
+
+struct filter;
+
+/* Create a subprocess and pipe some data through it.
+ progname is the program name used in error messages.
+ prog_path is the file name of the program to invoke.
+ prog_argv is a NULL terminated argument list, starting with
+ prog_path as first element.
+ If null_stderr is true, the subprocess' stderr will be redirected
+ to /dev/null, and the usual error message to stderr will be
+ omitted. This is suitable when the subprocess does not fulfill an
+ important task.
+ If exit_on_error is true, any error will cause the main process to
+ exit with an error status.
+ If the subprocess does not start correctly, exit if exit_on_error is
+ true, otherwise return NULL and set errno.
+
+ The caller will write to the subprocess through filter_write; during
+ calls to filter_write, the done_read function may be called to
+ process any data that the subprocess has written. done_read will
+ receive at most read_bufsize bytes stored into buf, as well as a
+ copy of private_data. */
+extern struct filter * filter_create (const char *progname,
+ const char *prog_path,
+ const char **prog_argv,
+ bool null_stderr, bool exit_on_error,
+ char *read_buf, size_t read_bufsize,
+ done_read_fn done_read,
+ void *private_data);
+
+
+/* Write size bytes starting at buf into the pipe and in the meanwhile
+ possibly call the done_read function specified in create_filter.
+ The done_read function may be called in a different thread than
+ the current thread, depending on the platform. However, it will
+ always be called before filter_write has returned (or else will be
+ delayed to the next call to filter_write or filter_close). Return
+ only after all the entire buffer has been written to the pipe or
+ the subprocess has exited.
+
+ If there is a problem reading or writing, return -1 and set errno.
+ If the subprocess exits early with nonzero status, return the status.
+ (In either case, filter_write will instead exit if exit_on_error was
+ passed as true).
+
+ If the subprocess exits early with zero status, subsequent writes
+ will becomes no-ops and zero is returned.
+
+ Otherwise return 0. */
+extern int filter_write (struct filter *f,
+ const char *buf, size_t size);
+
+/* Finish reading the output via the done_read function specified in
+ create_filter. The done_read function may be called in a different
+ thread than. However, it will always be called before filter_close
+ has returned. The write side of the pipe is closed as soon as
+ filter_close starts, while the read side will be closed just before
+ it finishes. If there is a problem reading or closing the pipe,
+ return -1 and set errno. If the subprocess exits early with nonzero
+ status, return the status. (In either case, filter_close will
+ instead exit if exit_on_error was passed as true).
+
+ Otherwise return 0. */
+extern int filter_close (struct filter *f);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* _PIPE_FILTER_H */
+
diff --git a/modules/pipe-filter b/modules/pipe-filter
new file mode 100644
index 0000000..9f04daf
--- /dev/null
+++ b/modules/pipe-filter
@@ -0,0 +1,33 @@
+Description:
+Filtering of data through a subprocess.
+
+Files:
+lib/pipe-filter.h
+lib/pipe-filter.c
+
+Depends-on:
+pipe
+wait-process
+error
+exit
+gettext-h
+stdbool
+stdint
+sys_select
+unistd
+
+configure.ac:
+AC_REQUIRE([AC_C_INLINE])
+AC_CHECK_FUNCS([select])
+
+Makefile.am:
+lib_SOURCES += pipe-filter.c
+
+Include:
+"pipe-filter.h"
+
+License:
+GPL
+
+Maintainer:
+Paolo Bonzini
diff --git a/modules/pipe-filter-tests b/modules/pipe-filter-tests
new file mode 100644
index 0000000..acdcc55
--- /dev/null
+++ b/modules/pipe-filter-tests
@@ -0,0 +1,15 @@
+Files:
+tests/test-pipe-filter
+tests/test-pipe-filter-main.c
+tests/test-pipe-filter-helper.c
+
+Depends-on:
+sleep
+progname
+
+configure.ac:
+AC_CHECK_HEADERS_ONCE([unistd.h sys/wait.h])
+
+Makefile.am:
+TESTS += test-pipe-filter.sh
+check_PROGRAMS += test-pipe-filter-main test-pipe-filter-helper
diff --git a/tests/test-pipe-filter-helper.c b/tests/test-pipe-filter-helper.c
new file mode 100644
index 0000000..ed82632
--- /dev/null
+++ b/tests/test-pipe-filter-helper.c
@@ -0,0 +1,38 @@
+/* Helper program invoked by test-pipe-filter-main.
+
+ Copyright (C) 2009 Free Software Foundation, Inc.
+ Written by Paolo Bonzini <address@hidden>, 2009.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+
+int main()
+{
+ int i, j;
+ for (;;)
+ {
+ if (scanf (" %d", &i) != 1)
+ break;
+ if (scanf (" %d", &j) != 1)
+ break;
+ if (j == -1)
+ exit (i);
+ while (i <= j)
+ printf ("%d\n", i++);
+ }
+ exit (0);
+}
diff --git a/tests/test-pipe-filter-main.c b/tests/test-pipe-filter-main.c
new file mode 100644
index 0000000..742724c
--- /dev/null
+++ b/tests/test-pipe-filter-main.c
@@ -0,0 +1,101 @@
+/* Test harness for pipe-filter.
+
+ Copyright (C) 2009 Free Software Foundation, Inc.
+ Written by Paolo Bonzini <address@hidden>, 2009.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#include "pipe-filter.h"
+#include "progname.h"
+
+void dummy_read_cb (char *buf, size_t nread, void *data)
+{
+}
+
+void read_cb (char *buf, size_t nread, void *data)
+{
+ write (STDOUT_FILENO, buf, nread);
+}
+
+int filter_writes (struct filter *f, const char *s)
+{
+ filter_write (f, s, strlen (s));
+}
+
+int main (int argc, char **argv)
+{
+ struct filter *f;
+ const char *path[] = { NULL, NULL };
+ char buf[512];
+ int rc;
+
+ set_program_name (argv[0]);
+
+#ifdef SIGPIPE
+ signal (SIGPIPE, SIG_IGN);
+#endif
+
+ /* Test writing to a nonexistent program traps sooner or later. */
+ path[0] = "/nonexistent/blah";
+ f = filter_create ("pipe-filter-test", path[0], path, true, false,
+ buf, sizeof(buf), dummy_read_cb, NULL);
+ sleep (1);
+ rc = filter_write (f, "", 1);
+ if (rc != 127 && rc != -1)
+ abort ();
+ if (filter_close (f) != 127)
+ abort ();
+ printf ("Test 1 passed.\n");
+ fflush (stdout);
+
+ /* Test returning the exit status. */
+ path[0] = "./test-pipe-filter-helper";
+ f = filter_create ("pipe-filter-test", path[0], path, true, false,
+ buf, sizeof(buf), dummy_read_cb, NULL);
+ filter_writes (f, "1 -1");
+ if (filter_close (f) != 1)
+ abort ();
+ printf ("Test 2 passed.\n");
+ fflush (stdout);
+
+ /* Same, but test returning the status in filter_write. */
+ f = filter_create ("pipe-filter-test", path[0], path, true, false,
+ buf, sizeof(buf), dummy_read_cb, NULL);
+ filter_writes (f, "1 -1\n");
+ sleep (1);
+ rc = filter_write (f, " ", 1);
+ if (rc != 1 && rc != -1)
+ abort ();
+ if (filter_close (f) != 1)
+ abort ();
+ printf ("Test 3 passed.\n");
+ fflush (stdout);
+
+ /* Now test asynchronous I/O. */
+ f = filter_create ("pipe-filter-test", path[0], path, true, true,
+ buf, sizeof(buf), read_cb, NULL);
+ filter_writes (f, "1 500\n");
+ filter_writes (f, "501\n");
+ filter_writes (f, "1000");
+ filter_close (f);
+ printf ("Test 4 passed.\n");
+ fflush (stdout);
+}
diff --git a/tests/test-pipe-filter.sh b/tests/test-pipe-filter.sh
new file mode 100755
index 0000000..3e983be
--- /dev/null
+++ b/tests/test-pipe-filter.sh
@@ -0,0 +1,32 @@
+#! /bin/sh
+
+# pipe-filter test driver.
+#
+# Copyright (C) 2009 Free Software Foundation, Inc.
+# Written by Paolo Bonzini <address@hidden>, 2009.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+./test-pipe-filter-main | {
+ set -e
+ read a && test "$a" = "Test 1 passed."
+ read a && test "$a" = "Test 2 passed."
+ read a && test "$a" = "Test 3 passed."
+ i=1
+ while [ $i -le 1000 ]; do
+ read a && test "$a" = "$i"
+ let i++ || i=`expr $i + 1`
+ done
+ read a && test "$a" = "Test 4 passed."
+}
--
1.6.2.5
- [PATCH] My version of pipe-filter,
Paolo Bonzini <=