New module 'pipe-filter-ii'.
authorBruno Haible <bruno@clisp.org>
Sun, 2 Aug 2009 21:46:44 +0000 (23:46 +0200)
committerBruno Haible <bruno@clisp.org>
Sun, 2 Aug 2009 23:57:19 +0000 (01:57 +0200)
ChangeLog
lib/pipe-filter-aux.h [new file with mode: 0644]
lib/pipe-filter-ii.c [new file with mode: 0644]
lib/pipe-filter.h [new file with mode: 0644]
modules/pipe-filter-ii [new file with mode: 0644]

index fd65cd3..07bd170 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2009-08-02  Bruno Haible  <bruno@clisp.org>
+            Paolo Bonzini  <bonzini@gnu.org>
+
+       New module 'pipe-filter-ii'.
+       * lib/pipe-filter.h: New file.
+       * lib/pipe-filter-ii.c: New file.
+       * lib/pipe-filter-aux.h: New file.
+       * modules/pipe-filter-ii: New file.
+
 2009-08-02  Simon Josefsson  <simon@josefsson.org>
 
        * lib/gc-libgcrypt.c: Change copyright to FSF.
diff --git a/lib/pipe-filter-aux.h b/lib/pipe-filter-aux.h
new file mode 100644 (file)
index 0000000..4964431
--- /dev/null
@@ -0,0 +1,116 @@
+/* Auxiliary code for filtering of data through a subprocess.
+   Copyright (C) 2001-2003, 2008-2009 Free Software Foundation, Inc.
+   Written by Bruno Haible <haible@clisp.cons.org>, 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 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 read
+   or to write, and let the parent behave 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 count)
+{
+  ssize_t retval;
+
+  do
+    retval = read (fd, buf, count);
+  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
diff --git a/lib/pipe-filter-ii.c b/lib/pipe-filter-ii.c
new file mode 100644 (file)
index 0000000..8281ad0
--- /dev/null
@@ -0,0 +1,446 @@
+/* Filtering of data through a subprocess.
+   Copyright (C) 2001-2003, 2008-2009 Free Software Foundation, Inc.
+   Written by Bruno Haible <bruno@clisp.org>, 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 <errno.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 <signal.h>
+# include <sys/select.h>
+#endif
+
+#include "error.h"
+#include "pipe.h"
+#include "wait-process.h"
+#include "gettext.h"
+
+#define _(str) gettext (str)
+
+#include "pipe-filter-aux.h"
+
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+
+struct locals
+{
+  /* Arguments passed to pipe_filter_ii_execute.  */
+  prepare_write_fn prepare_write;
+  done_write_fn done_write;
+  prepare_read_fn prepare_read;
+  done_read_fn done_read;
+
+  /* Management of the subprocess.  */
+  void *private_data;
+  int fd[2];
+
+  /* Status of the writer part.  */
+  volatile bool writer_terminated;
+  volatile int writer_errno;
+  /* Status of the reader part.  */
+  volatile bool reader_terminated;
+  volatile int reader_errno;
+};
+
+static unsigned int WINAPI
+writer_thread_func (void *thread_arg)
+{
+  struct locals *l = (struct locals *) thread_arg;
+
+  for (;;)
+    {
+      size_t bufsize;
+      const void *buf = l->prepare_write (&bufsize, l->private_data);
+      if (buf != NULL)
+       {
+         ssize_t nwritten =
+           write (l->fd[1], buf, bufsize > SSIZE_MAX ? SSIZE_MAX : bufsize);
+         if (nwritten < 0)
+           {
+             /* Don't assume that the gnulib modules 'write' and 'sigpipe' are
+                used.  */
+             if (GetLastError () == ERROR_NO_DATA)
+               errno = EPIPE;
+             l->writer_errno = errno;
+             break;
+           }
+         else if (nwritten > 0)
+           l->done_write ((void *) buf, nwritten, l->private_data);
+       }
+      else
+       break;
+    }
+
+  l->writer_terminated = true;
+  _endthreadex (0); /* calls ExitThread (0) */
+  abort ();
+}
+
+static unsigned int WINAPI
+reader_thread_func (void *thread_arg)
+{
+  struct locals *l = (struct locals *) thread_arg;
+
+  for (;;)
+    {
+      size_t bufsize;
+      void *buf = l->prepare_read (&bufsize, l->private_data);
+      if (!(buf != NULL && bufsize > 0))
+       /* prepare_read returned wrong values.  */
+       abort ();
+      {
+       ssize_t nread =
+         read (l->fd[0], buf, bufsize > SSIZE_MAX ? SSIZE_MAX : bufsize);
+       if (nread < 0)
+         {
+           l->reader_errno = errno;
+           break;
+         }
+       else if (nread > 0)
+         l->done_read (buf, nread, l->private_data);
+       else /* nread == 0 */
+         break;
+      }
+    }
+
+  l->reader_terminated = true;
+  _endthreadex (0); /* calls ExitThread (0) */
+  abort ();
+}
+
+#endif
+
+int
+pipe_filter_ii_execute (const char *progname,
+                       const char *prog_path, const char **prog_argv,
+                       bool null_stderr, bool exit_on_error,
+                       prepare_write_fn prepare_write,
+                       done_write_fn done_write,
+                       prepare_read_fn prepare_read,
+                       done_read_fn done_read,
+                       void *private_data)
+{
+  pid_t child;
+  int fd[2];
+#if !((defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__)
+  struct sigaction orig_sigpipe_action;
+#endif
+
+  /* Open a bidirectional pipe to a subprocess.  */
+  child = create_pipe_bidi (progname, prog_path, (char **) prog_argv,
+                           null_stderr, true, exit_on_error,
+                           fd);
+  if (child == -1)
+    return -1;
+
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+  /* Native Woe32 API.  */
+  /* Pipes have a non-blocking mode, see function SetNamedPipeHandleState and
+     the article "Named Pipe Type, Read, and Wait Modes", but Microsoft's
+     documentation discourages its use.  So don't use it.
+     Asynchronous I/O is also not suitable because it notifies the caller only
+     about completion of the I/O request, not about intermediate progress.
+     So do the writing and the reading in separate threads.  */
+  {
+    struct locals l;
+    HANDLE handles[2];
+    #define writer_thread_handle handles[0]
+    #define reader_thread_handle handles[1]
+    bool writer_cleaned_up;
+    bool reader_cleaned_up;
+
+    l.prepare_write = prepare_write;
+    l.done_write = done_write;
+    l.prepare_read = prepare_read;
+    l.done_read = done_read;
+    l.private_data = private_data;
+    l.fd[0] = fd[0];
+    l.fd[1] = fd[1];
+    l.writer_terminated = false;
+    l.writer_errno = 0;
+    l.reader_terminated = false;
+    l.reader_errno = 0;
+
+    writer_thread_handle =
+      (HANDLE) _beginthreadex (NULL, 100000, writer_thread_func, &l, 0, NULL);
+    reader_thread_handle =
+      (HANDLE) _beginthreadex (NULL, 100000, reader_thread_func, &l, 0, NULL);
+    if (writer_thread_handle == NULL || reader_thread_handle == NULL)
+      {
+       if (exit_on_error)
+         error (EXIT_FAILURE, 0, _("creation of threads failed"));
+       if (reader_thread_handle != NULL)
+         CloseHandle (reader_thread_handle);
+       if (writer_thread_handle != NULL)
+         CloseHandle (writer_thread_handle);
+       goto fail;
+      }
+    writer_cleaned_up = false;
+    reader_cleaned_up = false;
+    for (;;)
+      {
+       DWORD ret;
+
+       /* Here !(writer_cleaned_up && reader_cleaned_up).  */
+       if (writer_cleaned_up)
+         ret = WaitForSingleObject (reader_thread_handle, INFINITE);
+       else if (reader_cleaned_up)
+         ret = WaitForSingleObject (writer_thread_handle, INFINITE);
+       else
+         ret = WaitForMultipleObjects (2, handles, FALSE, INFINITE);
+       if (!(ret == WAIT_OBJECT_0 + 0 || ret == WAIT_OBJECT_0 + 1))
+         abort ();
+
+       if (l.writer_terminated)
+         {
+           /* The writer thread has just terminated.  */
+           l.writer_terminated = false;
+           CloseHandle (writer_thread_handle);
+           if (l.writer_errno)
+             {
+               if (exit_on_error)
+                 error (EXIT_FAILURE, l.writer_errno,
+                        _("write to %s subprocess failed"), progname);
+               if (!reader_cleaned_up)
+                 {
+                   TerminateThread (reader_thread_handle, 1);
+                   CloseHandle (reader_thread_handle);
+                 }
+               goto fail;
+             }
+           /* Tell the child there is nothing more the parent will send.  */
+           close (fd[1]);
+           writer_cleaned_up = true;
+         }
+       if (l.reader_terminated)
+         {
+           /* The reader thread has just terminated.  */
+           l.reader_terminated = false;
+           CloseHandle (reader_thread_handle);
+           if (l.reader_errno)
+             {
+               if (exit_on_error)
+                 error (EXIT_FAILURE, l.reader_errno,
+                        _("read from %s subprocess failed"), progname);
+               if (!writer_cleaned_up)
+                 {
+                   TerminateThread (writer_thread_handle, 1);
+                   CloseHandle (writer_thread_handle);
+                 }
+               goto fail;
+             }
+           reader_cleaned_up = true;
+         }
+       if (writer_cleaned_up && reader_cleaned_up)
+         break;
+      }
+  }
+#else
+  /* When we write to the child process and it has just terminated,
+     we don't want to die from a SIGPIPE signal.  So set the SIGPIPE
+     handler to SIG_IGN, and handle EPIPE error codes in write().  */
+  {
+    struct sigaction sigpipe_action;
+
+    sigpipe_action.sa_handler = SIG_IGN;
+    sigpipe_action.sa_flags = 0;
+    sigemptyset (&sigpipe_action.sa_mask);
+    if (sigaction (SIGPIPE, &sigpipe_action, &orig_sigpipe_action) < 0)
+      abort ();
+  }
+
+  {
+# if HAVE_SELECT
+    fd_set readfds;  /* All bits except fd[0] are always cleared.  */
+    fd_set writefds; /* All bits except fd[1] are always cleared.  */
+# endif
+    bool done_writing;
+
+    /* 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 (fd[1], F_GETFL, 0)) < 0
+         || fcntl (fd[1], F_SETFL, fcntl_flags | O_NONBLOCK) < 0
+         || (fcntl_flags = fcntl (fd[0], F_GETFL, 0)) < 0
+         || fcntl (fd[0], F_SETFL, fcntl_flags | O_NONBLOCK) < 0)
+       {
+         if (exit_on_error)
+           error (EXIT_FAILURE, errno,
+                  _("cannot set up nonblocking I/O to %s subprocess"),
+                  progname);
+         goto fail;
+       }
+    }
+
+# if HAVE_SELECT
+    FD_ZERO (&readfds);
+    FD_ZERO (&writefds);
+# endif
+    done_writing = false;
+    for (;;)
+      {
+# if HAVE_SELECT
+       int n;
+
+       FD_SET (fd[0], &readfds);
+       n = fd[0] + 1;
+       if (!done_writing)
+         {
+           FD_SET (fd[1], &writefds);
+           if (n <= fd[1])
+             n = fd[1] + 1;
+         }
+
+       n = select (n, &readfds, (!done_writing ? &writefds : NULL), NULL,
+                   NULL);
+       if (n < 0)
+         {
+           if (exit_on_error)
+             error (EXIT_FAILURE, errno,
+                    _("communication with %s subprocess failed"), progname);
+           goto fail;
+         }
+       if (!done_writing && FD_ISSET (fd[1], &writefds))
+         goto try_write;
+       if (FD_ISSET (fd[0], &readfds))
+         goto try_read;
+       /* How could select() return if none of the two descriptors is ready?  */
+       abort ();
+# endif
+
+       /* Attempt to write.  */
+# if HAVE_SELECT
+      try_write:
+# endif
+       if (!done_writing)
+         {
+           size_t bufsize;
+           const void *buf = prepare_write (&bufsize, private_data);
+           if (buf != NULL)
+             {
+               ssize_t nwritten =
+                 write (fd[1], buf,
+                        bufsize > SSIZE_MAX ? SSIZE_MAX : bufsize);
+               if (nwritten < 0)
+                 {
+                   if (!IS_EAGAIN (errno))
+                     {
+                       if (exit_on_error)
+                         error (EXIT_FAILURE, errno,
+                                _("write to %s subprocess failed"), progname);
+                       goto fail;
+                     }
+                 }
+               else if (nwritten > 0)
+                 done_write ((void *) buf, nwritten, private_data);
+             }
+           else
+             {
+               /* Tell the child there is nothing more the parent will send.  */
+               close (fd[1]);
+               done_writing = true;
+             }
+         }
+# if HAVE_SELECT
+       continue;
+# endif
+
+       /* Attempt to read.  */
+# if HAVE_SELECT
+      try_read:
+# endif
+       {
+         size_t bufsize;
+         void *buf = prepare_read (&bufsize, private_data);
+         if (!(buf != NULL && bufsize > 0))
+           /* prepare_read returned wrong values.  */
+           abort ();
+         {
+           ssize_t nread =
+             read (fd[0], buf, bufsize > SSIZE_MAX ? SSIZE_MAX : bufsize);
+           if (nread < 0)
+             {
+               if (!IS_EAGAIN (errno))
+                 {
+                   if (exit_on_error)
+                     error (EXIT_FAILURE, errno,
+                            _("read from %s subprocess failed"), progname);
+                   goto fail;
+                 }
+             }
+           else if (nread > 0)
+             done_read (buf, nread, private_data);
+           else /* nread == 0 */
+             {
+               if (done_writing)
+                 break;
+             }
+         }
+       }
+# if HAVE_SELECT
+       continue;
+# endif
+      }
+  }
+
+  /* Restore SIGPIPE signal handler.  */
+  if (sigaction (SIGPIPE, &orig_sigpipe_action, NULL) < 0)
+    abort ();
+#endif
+
+  close (fd[0]);
+
+  /* Remove zombie process from process list.  */
+  {
+    int exitstatus =
+      wait_subprocess (child, progname, false, null_stderr,
+                      true, exit_on_error, NULL);
+    if (exitstatus != 0 && exit_on_error)
+      error (EXIT_FAILURE, 0, _("%s subprocess terminated with exit code %d"),
+            progname, exitstatus);
+    return exitstatus;
+  }
+
+ fail:
+  {
+    int saved_errno = errno;
+    close (fd[1]);
+#if !((defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__)
+    if (sigaction (SIGPIPE, &orig_sigpipe_action, NULL) < 0)
+      abort ();
+#endif
+    close (fd[0]);
+    wait_subprocess (child, progname, true, true, true, false, NULL);
+    errno = saved_errno;
+    return -1;
+  }
+}
diff --git a/lib/pipe-filter.h b/lib/pipe-filter.h
new file mode 100644 (file)
index 0000000..0918235
--- /dev/null
@@ -0,0 +1,237 @@
+/* Filtering of data through a subprocess.
+   Copyright (C) 2009 Free Software Foundation, Inc.
+   Written by Bruno Haible <haible@clisp.cons.org>, 2009,
+   and Paolo Bonzini <bonzini@gnu.org>, 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 header file declares four set of functions that pipes data through
+   the subprocess, without risking these deadlocks.
+
+   The side that writes data to the subprocess can be seen as a "generator",
+   that is, as a subroutine that produces and writes a piece of data here and
+   there, see <http://en.wikipedia.org/wiki/Generator_(computer_science)>.
+   But often, it can be written in the form of an "iterator", that is, as a
+   function that, each time it is invoked, produces and writes one more piece
+   of data.
+
+   Similarly, the side that reads data from the subprocess can be seen as
+   a "generator", that is, as a subroutine that consumes a piece of data here
+   and there.  Often, it can be written in the form of an "iterator", that
+   is, as a function that, each time it is invoked, consumes one more piece
+   of data.
+
+   This header file declares four set of functions:
+
+                       |   writer   |   reader   |
+       ----------------+------------+------------+
+       pipe_filter_ii  |  iterator  |  iterator  |
+       pipe_filter_ig  |  iterator  |  generator |
+       pipe_filter_gi  |  generator |  iterator  |
+       pipe_filter_gg  |  generator |  generator |
+       ----------------+------------+------------+
+
+   The last one uses threads in order to implement two generators running at
+   the same time.  (For the relation between generators, coroutines, and
+   threads, see <http://en.wikipedia.org/wiki/Generator_(computer_science)>
+   and <http://en.wikipedia.org/wiki/Coroutine>.)  It is therefore only
+   portable to platforms with kernel-based POSIX threads.  */
+
+/* These two functions together describe the side that writes data to the
+   subprocess when it has the form of an iterator.
+   - prepare_write (&num_bytes, p) must either return a pointer to data that
+     is ready to be written and set num_bytes to the number of bytes ready to
+     be written, or return NULL when no more bytes are to be written.
+   - done_write (data_written, num_bytes_written) is called after
+     num_bytes_written bytes were written.  It is guaranteed that
+     num_bytes_written > 0.
+   Here p is always the private_data argument passed to the main function.  */
+typedef const void * (*prepare_write_fn) (size_t *num_bytes_p,
+                                         void *private_data);
+typedef void (*done_write_fn) (void *data_written, size_t num_bytes_written,
+                              void *private_data);
+
+/* These two functions together describe the side that reads data from the
+   subprocess when it has the form of an iterator.
+   - prepare_read (&num_bytes, p) must return a pointer to a buffer for data
+     that can be read and set num_bytes to the size of that buffer
+     (must be > 0).
+   - done_read (data_read, num_bytes_read, p) is called after num_bytes_read
+     bytes were read into the buffer.
+   Here p is always the private_data argument passed to the main function.  */
+typedef void * (*prepare_read_fn) (size_t *num_bytes_p,
+                                  void *private_data);
+typedef void (*done_read_fn) (void *data_read, size_t num_bytes_read,
+                             void *private_data);
+
+
+/* ============================ pipe_filter_ii ============================ */
+
+/* Create a subprocess and pipe some data through it.
+   Arguments:
+   - 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 terminate correctly, exit if exit_on_error is
+   true, otherwise return 127.
+   Callback arguments are as described above.
+
+   Data is alternatingly written to the subprocess, through the functions
+   prepare_write and done_write, and read from the subprocess, through the
+   functions prepare_read and done_read.
+
+   Note that the prepare_write/done_write functions and the
+   prepare_read/done_read functions may be called in different threads than
+   the current thread (depending on the platform).  But they will not be
+   called after the pipe_filter_ii_execute function has returned.
+
+   Return 0 upon success, or (only if exit_on_error is false):
+   - -1 with errno set upon failure,
+   - the positive exit code of the subprocess if that failed.  */
+extern int
+       pipe_filter_ii_execute (const char *progname,
+                              const char *prog_path, const char **prog_argv,
+                              bool null_stderr, bool exit_on_error,
+                              prepare_write_fn prepare_write,
+                              done_write_fn done_write,
+                              prepare_read_fn prepare_read,
+                              done_read_fn done_read,
+                              void *private_data);
+
+
+/* ============================ pipe_filter_ig ============================ */
+
+struct pipe_filter_ig;
+
+
+/* ============================ pipe_filter_gi ============================ */
+
+struct pipe_filter_gi;
+
+/* Create a subprocess and pipe some data through it.
+   Arguments:
+   - 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 pipe_filter_gi_write
+   and finally call pipe_filter_gi_write.  During such calls, the
+   prepare_read and done_read function may be called to process any data
+   that the subprocess has written.
+
+   Note that the prepare_read/done_read functions may be called in a
+   different thread than the current thread (depending on the platform).
+   But they will not be called after the pipe_filter_gi_close function has
+   returned.
+
+   Return the freshly created 'struct pipe_filter_gi'.  */
+extern struct pipe_filter_gi *
+       pipe_filter_gi_create (const char *progname,
+                             const char *prog_path, const char **prog_argv,
+                             bool null_stderr, bool exit_on_error,
+                             prepare_read_fn prepare_read,
+                             done_read_fn done_read,
+                             void *private_data);
+
+/* Write size bytes starting at buf into the pipe and in the meanwhile
+   possibly call the prepare_read and done_read functions specified to
+   pipe_filter_gi_create.
+
+   Note that the prepare_read/done_read functions may be called in a
+   different thread than the current thread (depending on the platform).
+   However, they will always be called before pipe_filter_gi_write has
+   returned, or otherwise not sooner than the next call to
+   pipe_filter_gi_write or pipe_filter_gi_close.
+
+   Return only after all the entire buffer has been written to the pipe or
+   the subprocess has exited.
+
+   Return 0 upon success, or (only if exit_on_error is false):
+   - -1 with errno set upon failure,
+   - the positive exit code of the subprocess if that failed.  */
+extern int
+       pipe_filter_gi_write (struct pipe_filter_gi *filter,
+                            const void *buf, size_t size);
+
+/* Finish reading the output via the prepare_read/done_read functions
+   specified to pipe_filter_gi_create.
+
+   Note that the prepare_read/done_read functions may be called in a
+   different thread than the current thread (depending on the platform).
+   However, they will always be called before pipe_filter_gi_close has
+   returned.
+
+   The write side of the pipe is closed as soon as pipe_filter_gi_close
+   starts, while the read side will be closed just before it finishes.
+
+   Return 0 upon success, or (only if exit_on_error is false):
+   - -1 with errno set upon failure,
+   - the positive exit code of the subprocess if that failed.  */
+extern int
+       pipe_filter_gi_close (struct pipe_filter_gi *filter);
+
+
+/* ============================ pipe_filter_gg ============================ */
+
+
+/* ======================================================================== */
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* _PIPE_FILTER_H */
diff --git a/modules/pipe-filter-ii b/modules/pipe-filter-ii
new file mode 100644 (file)
index 0000000..2c05aa1
--- /dev/null
@@ -0,0 +1,34 @@
+Description:
+Filtering of data through a subprocess.
+
+Files:
+lib/pipe-filter.h
+lib/pipe-filter-ii.c
+lib/pipe-filter-aux.h
+
+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-ii.c
+
+Include:
+"pipe-filter.h"
+
+License:
+GPL
+
+Maintainer:
+Bruno Haible