[Pkg-gnupg-commit] [libgpg-error] 04/19: estream: Add gpgrt_set_nonblock and gpgrt_poll.

Daniel Kahn Gillmor dkg at fifthhorseman.net
Tue Dec 15 15:55:25 UTC 2015


This is an automated email from the git hooks/post-receive script.

dkg pushed a commit to branch master
in repository libgpg-error.

commit 071c2170479869f4c6694ae85d2b113e84482a01
Author: Werner Koch <wk at gnupg.org>
Date:   Fri Sep 25 10:45:22 2015 +0200

    estream: Add gpgrt_set_nonblock and gpgrt_poll.
    
    * configure.ac (AC_CHECK_HEADERS): Add sys/select.h and sys/time.h.
    * src/estream.c: Include both header if available.
    (COOKIE_IOCTL_NONBLOCK): New.
    (struct estream_cookie_fd): Add field nonblock.
    (func_fd_create): Set nonblock from MODEFLAGS.
    (es_func_fd_ioctl): New.
    (parse_mode): Add modeflag "nonblock".
    (es_fill): Map EWOULDBLOCK to EAGAIN.  Do not set error indicator for
    EAGAIN.
    (es_flush, es_seek, es_write_nbf): Map EWOULDBLOCK to EAGAIN.
    (do_fdopen): Call COOKIE_IOCTL_NONBLOCK.
    (_gpgrt_set_nonblock): New.
    (_gpgrt_get_nonblock): New.
    (_gpgrt_poll): New.
    
    * src/gpg-error.h.in (struct _gpgrt_poll_s): New.
    (gpgrt_poll_t, es_poll_t): New.
    (es_set_nonblock, es_get_nonblock, es_poll): New.
    
    * src/gpg-error.vers, src/gpg-error.def.in: Add gpgrt_set_nonblock,
    gpgrt_get_nonblock, and gpgrt_poll.
    * src/visibility.c (gpgrt_set_nonblock, gpgrt_get_nonblock): New.
    (gpgrt_poll): New.
    
    * tests/t-common.h (DIM): New.
    * tests/t-poll.c: New.
    * tests/Makefile.am (TESTS): Add t-poll.
    (t_poll_LDADD): New.
    --
    
    The poll interface uses select(2) internally because that is more
    portable than poll(2).
    
    Signed-off-by: Werner Koch <wk at gnupg.org>
---
 NEWS                 |  16 ++-
 configure.ac         |   2 +-
 doc/errorref.txt     |   2 +-
 src/estream.c        | 349 +++++++++++++++++++++++++++++++++++++++++++++-
 src/gpg-error.def.in |   4 +
 src/gpg-error.h.in   |  33 +++++
 src/gpg-error.vers   |   3 +
 src/gpgrt-int.h      |   4 +
 src/visibility.c     |  18 +++
 src/visibility.h     |   6 +
 tests/Makefile.am    |   3 +-
 tests/t-common.h     |   3 +
 tests/t-poll.c       | 383 +++++++++++++++++++++++++++++++++++++++++++++++++++
 13 files changed, 818 insertions(+), 8 deletions(-)

diff --git a/NEWS b/NEWS
index ad19b1b..9bfc5b3 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,20 @@
-Noteworthy changes in version 1.21 (unreleased) [C16/A16/R_]
+Noteworthy changes in version 1.21 (unreleased) [C17/A17/R0]
 -----------------------------------------------
 
+ * New functions gpgrt_poll and gpgrt_set_nonblock.  For now only
+   pipes and sockets on Unix are supported.
+
+ * Interface changes relative to the 1.20 release:
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ gpgrt_set_nonblock               NEW.
+ gpgrt_get_nonblock               NEW.
+ gpgrt_poll                       NEW.
+ gpgrt_poll_t                     NEW type.
+ es_poll_t                        NEW type.
+ es_set_nonblock                  NEW macro.
+ es_get_nonblock                  NEW macro.
+ es_poll                          NEW macro.
+
 
 Noteworthy changes in version 1.20 (2015-08-26) [C16/A16/R0]
 -----------------------------------------------
diff --git a/configure.ac b/configure.ac
index 8946ffa..39b57d8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -160,7 +160,7 @@ AM_GNU_GETTEXT([external])
 
 # Checks for header files.
 AC_HEADER_STDC
-AC_CHECK_HEADERS([stdlib.h locale.h stdint.h])
+AC_CHECK_HEADERS([stdlib.h locale.h stdint.h sys/select.h sys/time.h])
 AC_FUNC_STRERROR_R
 case "${host_os}" in
      solaris*)
diff --git a/doc/errorref.txt b/doc/errorref.txt
index f9dbf59..a369fbf 100644
--- a/doc/errorref.txt
+++ b/doc/errorref.txt
@@ -750,7 +750,7 @@ GPG_ERR_BOGUS_STRING            Bogus string
 
 GPG_ERR_FORBIDDEN		Forbidden
 
-    The use of a features is not allowed due to insuffcient rights.
+    The use of a features is not allowed due to insufficient rights.
     Use by gpg-agent as an error codes for restricted commands.
 
 GPG_ERR_KEY_DISABLED            Key disabled
diff --git a/src/estream.c b/src/estream.c
index 7d12e36..58f069c 100644
--- a/src/estream.c
+++ b/src/estream.c
@@ -1,6 +1,6 @@
 /* estream.c - Extended Stream I/O Library
  * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010, 2011,
- *               2014 g10 Code GmbH
+ *               2014, 2015 g10 Code GmbH
  *
  * This file is part of Libestream.
  *
@@ -67,6 +67,12 @@
 # endif
 #endif
 
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
 #include <sys/types.h>
 #include <sys/file.h>
 #include <sys/stat.h>
@@ -108,6 +114,11 @@
 # define S_IWOTH S_IWUSR
 # define S_IXGRP S_IXUSR
 # define S_IXOTH S_IXUSR
+# define O_NONBLOCK  0  /* FIXME: Not yet supported.  */
+#endif
+
+#ifndef EAGAIN
+# define EAGAIN  EWOULDBLOCK
 #endif
 
 
@@ -157,6 +168,7 @@ typedef int (*cookie_ioctl_function_t) (void *cookie, int cmd,
 					void *ptr, size_t *len);
 /* IOCTL commands for the private cookie function.  */
 #define COOKIE_IOCTL_SNATCH_BUFFER 1
+#define COOKIE_IOCTL_NONBLOCK      2
 
 
 /* The internal stream object.  */
@@ -869,6 +881,7 @@ typedef struct estream_cookie_fd
 {
   int fd;        /* The file descriptor we are using for actual output.  */
   int no_close;  /* If set we won't close the file descriptor.  */
+  int nonblock;  /* Non-blocking mode is enabled.  */
 } *estream_cookie_fd_t;
 
 /* Create function for objects indentified by a libc file descriptor.  */
@@ -887,11 +900,10 @@ func_fd_create (void **cookie, int fd, unsigned int modeflags, int no_close)
       /* Make sure it is in binary mode if requested.  */
       if ( (modeflags & O_BINARY) )
         setmode (fd, O_BINARY);
-#else
-      (void)modeflags;
 #endif
       fd_cookie->fd = fd;
       fd_cookie->no_close = no_close;
+      fd_cookie->nonblock = !!(modeflags & O_NONBLOCK);
       *cookie = fd_cookie;
       err = 0;
     }
@@ -990,6 +1002,47 @@ es_func_fd_seek (void *cookie, gpgrt_off_t *offset, int whence)
   return err;
 }
 
+/* An IOCTL function for fd objects.  */
+static int
+es_func_fd_ioctl (void *cookie, int cmd, void *ptr, size_t *len)
+{
+  estream_cookie_fd_t fd_cookie = cookie;
+  int ret;
+
+  if (cmd == COOKIE_IOCTL_NONBLOCK && !len)
+    {
+      fd_cookie->nonblock = !!ptr;
+      if (IS_INVALID_FD (fd_cookie->fd))
+        {
+          _set_errno (EINVAL);
+          ret = -1;
+        }
+      else
+        {
+#ifdef _WIN32
+          _set_errno (EOPNOTSUPP); /* FIXME: Implement for Windows.  */
+          ret = -1;
+#else
+          _set_errno (0);
+          ret = fcntl (fd_cookie->fd, F_GETFL, 0);
+          if (ret == -1 && errno)
+            ;
+          else if (fd_cookie->nonblock)
+            ret = fcntl (fd_cookie->fd, F_SETFL, (ret | O_NONBLOCK));
+          else
+            ret = fcntl (fd_cookie->fd, F_SETFL, (ret & ~O_NONBLOCK));
+#endif
+        }
+    }
+  else
+    {
+      _set_errno (EINVAL);
+      ret = -1;
+    }
+
+  return ret;
+}
+
 /* Destroy function for fd objects.  */
 static int
 es_func_fd_destroy (void *cookie)
@@ -1496,6 +1549,10 @@ func_file_create (void **cookie, int *filedes,
       disables any internal locking.  This keyword is also found on
       IBM systems.
 
+   nonblock
+
+      The object is opened in non-blocking mode.  This is the same as
+      calling gpgrt_set_nonblock on the file.
 
    Note: R_CMODE is optional because is only required by functions
    which are able to creat a file.  */
@@ -1591,6 +1648,16 @@ parse_mode (const char *modestr,
             }
           *samethread = 1;
         }
+      else if (!strncmp (modestr, "nonblock", 8))
+        {
+          modestr += 8;
+          if (*modestr && !strchr (" \t,", *modestr))
+            {
+              _set_errno (EINVAL);
+              return -1;
+            }
+          oflags |= O_NONBLOCK;
+        }
     }
   if (!got_cmode)
     cmode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
@@ -1631,6 +1698,10 @@ es_fill (estream_t stream)
 	{
 	  bytes_read = 0;
 	  err = -1;
+#if EWOULDBLOCK != EAGAIN
+          if (errno == EWOULDBLOCK)
+            _set_errno (EAGAIN);
+#endif
 	}
       else
 	{
@@ -1640,7 +1711,10 @@ es_fill (estream_t stream)
     }
 
   if (err)
-    stream->intern->indicators.err = 1;
+    {
+      if (errno != EAGAIN)
+        stream->intern->indicators.err = 1;
+    }
   else if (!bytes_read)
     stream->intern->indicators.eof = 1;
 
@@ -1690,6 +1764,10 @@ es_flush (estream_t stream)
 	    {
 	      bytes_written = 0;
 	      err = -1;
+#if EWOULDBLOCK != EAGAIN
+              if (errno == EWOULDBLOCK)
+                _set_errno (EAGAIN);
+#endif
 	    }
 	  else
 	    bytes_written = ret;
@@ -1941,6 +2019,10 @@ es_read_nbf (estream_t _GPGRT__RESTRICT stream,
       if (ret == -1)
 	{
 	  err = -1;
+#if EWOULDBLOCK != EAGAIN
+          if (errno == EWOULDBLOCK)
+            _set_errno (EAGAIN);
+#endif
 	  break;
 	}
       else if (ret)
@@ -2209,6 +2291,10 @@ es_seek (estream_t _GPGRT__RESTRICT stream, gpgrt_off_t offset, int whence,
   if (ret == -1)
     {
       err = -1;
+#if EWOULDBLOCK != EAGAIN
+      if (errno == EWOULDBLOCK)
+        _set_errno (EAGAIN);
+#endif
       goto out;
     }
 
@@ -2259,6 +2345,10 @@ es_write_nbf (estream_t _GPGRT__RESTRICT stream,
       if (ret == -1)
 	{
 	  err = -1;
+#if EWOULDBLOCK != EAGAIN
+          if (errno == EWOULDBLOCK)
+            _set_errno (EAGAIN);
+#endif
 	  break;
 	}
       else
@@ -2941,6 +3031,13 @@ do_fdopen (int filedes, const char *mode, int no_close, int with_locked_list)
   err = es_create (&stream, cookie, &syshd, estream_functions_fd,
                    modeflags, samethread, with_locked_list);
 
+  if (!err && stream)
+    {
+      stream->intern->func_ioctl = es_func_fd_ioctl;
+      if ((modeflags & O_NONBLOCK))
+        err = es_func_fd_ioctl (cookie, COOKIE_IOCTL_NONBLOCK, "", NULL);
+    }
+
  out:
   if (err && create_called)
     (*estream_functions_fd.func_close) (cookie);
@@ -4293,6 +4390,249 @@ _gpgrt_set_binary (estream_t stream)
 }
 
 
+/* Set non-blocking mode for STREAM.  Use true for ONOFF to enable and
+   false to disable non-blocking mode.  Returns 0 on success or -1 on
+   error and sets ERRNO.  Note that not all backends support
+   non-blocking mode.
+
+   In non-blocking mode a system call will not block but return an
+   error and set errno to EAGAIN.  The estream API always uses EAGAIN
+   and not EWOULDBLOCK.  If a buffered function like es_fgetc() or
+   es_fgets() returns an error and both, feof() and ferror() return
+   false the caller may assume that the error condition was EAGAIN.
+
+   Switching back from non-blocking to blocking may raise problems
+   with buffering, thus care should be taken.  Although read+write
+   sockets are supported in theory, switching from write to read may
+   result into problems because estream may first flush the write
+   buffers and there is no way to handle that non-blocking (EAGAIN)
+   case.  Explicit flushing should thus be done before before
+   switching to read.  */
+int
+_gpgrt_set_nonblock (estream_t stream, int onoff)
+{
+  cookie_ioctl_function_t func_ioctl;
+  int ret;
+
+  lock_stream (stream);
+  func_ioctl = stream->intern->func_ioctl;
+  if (!func_ioctl)
+    {
+      _set_errno (EOPNOTSUPP);
+      ret = -1;
+    }
+  else
+    {
+      unsigned int save_flags = stream->intern->modeflags;
+
+      if (onoff)
+        stream->intern->modeflags |= O_NONBLOCK;
+      else
+        stream->intern->modeflags &= ~O_NONBLOCK;
+
+      ret = func_ioctl (stream->intern->cookie, COOKIE_IOCTL_NONBLOCK,
+                        onoff?"":NULL, NULL);
+      if (ret)
+        stream->intern->modeflags = save_flags;
+    }
+  unlock_stream (stream);
+  return ret;
+}
+
+
+/* Return true if STREAM is in non-blocking mode.  */
+int
+_gpgrt_get_nonblock (estream_t stream)
+{
+  int ret;
+
+  lock_stream (stream);
+  ret = !!(stream->intern->modeflags & O_NONBLOCK);
+  unlock_stream (stream);
+  return ret;
+}
+
+
+/* A version of poll(2) working on estream handles.  Note that not all
+   estream types work with this function.  In contrast to the standard
+   poll function the gpgrt_poll_t object uses a set of names bit flags
+   instead of the EVENTS and REVENTS members.  An item with the IGNORE
+   flag set is entirely ignored.  The TIMEOUT values is given in
+   milliseconds, a value of -1 waits indefinitely, and a value of 0
+   returns immediately.
+
+   A positive return value gives the number of fds with new
+   information.  A return value of 0 indicates a timeout and -1
+   indicates an error in which case ERRNO is set.  */
+int
+_gpgrt_poll (gpgrt_poll_t *fds, unsigned int nfds, int timeout)
+{
+  gpgrt_poll_t *item;
+  int count = 0;
+  fd_set readfds, writefds, exceptfds;
+  int any_readfd, any_writefd, any_exceptfd;
+  int idx;
+  int max_fd;
+  int fd, ret, any;
+
+  if (!fds)
+    {
+      _set_errno (EINVAL);
+      return -1;
+    }
+
+  /* Clear all response fields (even for ignored items).  */
+  for (item = fds, idx = 0; idx < nfds; item++, idx++)
+    {
+      item->got_read = 0;
+      item->got_write = 0;
+      item->got_oob = 0;
+      item->got_rdhup = 0;
+      item->got_err = 0;
+      item->got_hup = 0;
+      item->got_nval = 0;
+    }
+
+  /* Check for pending reads.  */
+  for (item = fds, idx = 0; idx < nfds; item++, idx++)
+    {
+      if (item->ignore)
+        continue;
+      if (!item->want_read)
+        continue;
+      if (_gpgrt__pending (item->stream))
+        {
+          item->got_read = 1;
+          count++;
+        }
+    }
+
+  /* Check for space in the write buffers.  */
+  for (item = fds, idx = 0; idx < nfds; item++, idx++)
+    {
+      if (item->ignore)
+        continue;
+      if (!item->want_write)
+        continue;
+      /* FIXME */
+    }
+
+  if (count)
+    return count;  /* Early return without waiting.  */
+
+  /* Now do the real select.  */
+  any_readfd = any_writefd = any_exceptfd = 0;
+  max_fd = 0;
+  for (item = fds, idx = 0; idx < nfds; item++, idx++)
+    {
+      if (item->ignore)
+        continue;
+      fd = _gpgrt_fileno (item->stream);
+      if (fd == -1)
+        continue;  /* Stream does not support polling.  */
+
+      if (item->want_read)
+        {
+          if (!any_readfd)
+            {
+              FD_ZERO (&readfds);
+              any_readfd = 1;
+            }
+          FD_SET (fd, &readfds);
+          if (fd > max_fd)
+            max_fd = fd;
+        }
+      if (item->want_write)
+        {
+          if (!any_writefd)
+            {
+              FD_ZERO (&writefds);
+              any_writefd = 1;
+            }
+          FD_SET (fd, &writefds);
+          if (fd > max_fd)
+            max_fd = fd;
+        }
+      if (item->want_oob)
+        {
+          if (!any_exceptfd)
+            {
+              FD_ZERO (&exceptfds);
+              any_exceptfd = 1;
+            }
+          FD_SET (fd, &exceptfds);
+          if (fd > max_fd)
+            max_fd = fd;
+        }
+    }
+
+#ifdef _WIN32
+  (void)timeout;
+  ret = -1;
+  _set_errno (EOPNOTSUPP);
+#else
+  if (pre_syscall_func)
+    pre_syscall_func ();
+  do
+    {
+      struct timeval timeout_val;
+
+      timeout_val.tv_sec = timeout / 1000;
+      timeout_val.tv_usec = (timeout % 1000) * 1000;
+      ret = select (max_fd+1,
+                    any_readfd?   &readfds   : NULL,
+                    any_writefd?  &writefds  : NULL,
+                    any_exceptfd? &exceptfds : NULL,
+                    timeout == -1 ? NULL : &timeout_val);
+    }
+  while (ret == -1 && errno == EINTR);
+  if (post_syscall_func)
+    post_syscall_func ();
+#endif
+
+  if (ret == -1)
+    return -1;
+  if (!ret)
+    return 0; /* Timeout.  Note that in this case we can't return
+                 got_err for an invalid stream.  */
+
+  for (item = fds, idx = 0; idx < nfds; item++, idx++)
+    {
+      if (item->ignore)
+        continue;
+      fd = _gpgrt_fileno (item->stream);
+      if (fd == -1)
+        {
+          item->got_err = 1;  /* Stream does not support polling.  */
+          count++;
+          continue;
+        }
+
+      any = 0;
+      if (item->want_read && FD_ISSET (fd, &readfds))
+        {
+          item->got_read = 1;
+          any = 1;
+        }
+      if (item->want_write && FD_ISSET (fd, &writefds))
+        {
+          item->got_write = 1;
+          any = 1;
+        }
+      if (item->want_oob && FD_ISSET (fd, &exceptfds))
+        {
+          item->got_oob = 1;
+          any = 1;
+        }
+
+      if (any)
+        count++;
+    }
+
+  return count;
+}
+
+
 void
 _gpgrt_opaque_set (estream_t stream, void *opaque)
 {
@@ -4373,6 +4713,7 @@ _gpgrt_fname_get (estream_t stream)
 }
 
 
+

 /* Print a BUFFER to STREAM while replacing all control characters and
    the characters in DELIMITERS by standard C escape sequences.
    Returns 0 on success or -1 on error.  If BYTES_WRITTEN is not NULL
diff --git a/src/gpg-error.def.in b/src/gpg-error.def.in
index cba973d..16de809 100644
--- a/src/gpg-error.def.in
+++ b/src/gpg-error.def.in
@@ -140,4 +140,8 @@ EXPORTS
  _gpgrt_pending               @104
  _gpgrt_pending_unlocked      @105
 
+ gpgrt_set_nonblock           @106
+ gpgrt_get_nonblock           @107
+ gpgrt_poll                   @108
+
 ;; end of file with public symbols for Windows.
diff --git a/src/gpg-error.h.in b/src/gpg-error.h.in
index ad5d470..61b57fc 100644
--- a/src/gpg-error.h.in
+++ b/src/gpg-error.h.in
@@ -525,6 +525,32 @@ typedef struct _gpgrt_syshd es_syshd_t;
 #define ES_SYSHD_HANDLE GPGRT_SYSHD_HANDLE
 #endif
 
+/* The object used with gpgrt_poll.  */
+struct _gpgrt_poll_s
+{
+  gpgrt_stream_t stream;
+  unsigned int want_read:1;
+  unsigned int want_write:1;
+  unsigned int want_oob:1;
+  unsigned int want_rdhup:1;
+  unsigned int _reserv1:4;
+  unsigned int got_read:1;
+  unsigned int got_write:1;
+  unsigned int got_oob:1;
+  unsigned int got_rdhup:1;
+  unsigned int _reserv2:4;
+  unsigned int got_err:1;
+  unsigned int got_hup:1;
+  unsigned int got_nval:1;
+  unsigned int _reserv3:4;
+  unsigned int ignore:1;
+  unsigned int user:8;       /* For application use.  */
+};
+typedef struct _gpgrt_poll_s gpgrt_poll_t;
+#ifdef GPGRT_ENABLE_ES_MACROS
+typedef struct _gpgrt_poll_s es_poll_t;
+#endif
+
 gpgrt_stream_t gpgrt_fopen (const char *_GPGRT__RESTRICT path,
                             const char *_GPGRT__RESTRICT mode);
 gpgrt_stream_t gpgrt_mopen (void *_GPGRT__RESTRICT data,
@@ -682,6 +708,10 @@ void gpgrt_setbuf (gpgrt_stream_t _GPGRT__RESTRICT stream,
                    char *_GPGRT__RESTRICT buf);
 
 void gpgrt_set_binary (gpgrt_stream_t stream);
+int  gpgrt_set_nonblock (gpgrt_stream_t stream, int onoff);
+int  gpgrt_get_nonblock (gpgrt_stream_t stream);
+
+int gpgrt_poll (gpgrt_poll_t *fdlist, unsigned int nfds, int timeout);
 
 gpgrt_stream_t gpgrt_tmpfile (void);
 
@@ -777,6 +807,9 @@ int gpgrt_vsnprintf (char *buf,size_t bufsize,
 # define es_setvbuf           gpgrt_setvbuf
 # define es_setbuf            gpgrt_setbuf
 # define es_set_binary        gpgrt_set_binary
+# define es_set_nonblock      gpgrt_set_nonblock
+# define es_get_nonblock      gpgrt_get_nonblock
+# define es_poll              gpgrt_poll
 # define es_tmpfile           gpgrt_tmpfile
 # define es_opaque_set        gpgrt_opaque_set
 # define es_opaque_get        gpgrt_opaque_get
diff --git a/src/gpg-error.vers b/src/gpg-error.vers
index 758e549..067bb29 100644
--- a/src/gpg-error.vers
+++ b/src/gpg-error.vers
@@ -104,6 +104,9 @@ GPG_ERROR_1.0 {
     gpgrt_setvbuf;
     gpgrt_setbuf;
     gpgrt_set_binary;
+    gpgrt_set_nonblock;
+    gpgrt_get_nonblock;
+    gpgrt_poll;
     gpgrt_tmpfile;
     gpgrt_opaque_set;
     gpgrt_opaque_get;
diff --git a/src/gpgrt-int.h b/src/gpgrt-int.h
index 34e5d72..0f7b29b 100644
--- a/src/gpgrt-int.h
+++ b/src/gpgrt-int.h
@@ -189,6 +189,10 @@ int _gpgrt_setvbuf (gpgrt_stream_t _GPGRT__RESTRICT stream,
                     char *_GPGRT__RESTRICT buf, int mode, size_t size);
 
 void _gpgrt_set_binary (gpgrt_stream_t stream);
+int  _gpgrt_set_nonblock (gpgrt_stream_t stream, int onoff);
+int  _gpgrt_get_nonblock (gpgrt_stream_t stream);
+
+int _gpgrt_poll (gpgrt_poll_t *fds, unsigned int nfds, int timeout);
 
 gpgrt_stream_t _gpgrt_tmpfile (void);
 
diff --git a/src/visibility.c b/src/visibility.c
index 9213ce9..4750685 100644
--- a/src/visibility.c
+++ b/src/visibility.c
@@ -592,6 +592,24 @@ gpgrt_set_binary (estream_t stream)
   _gpgrt_set_binary (stream);
 }
 
+int
+gpgrt_set_nonblock (estream_t stream, int onoff)
+{
+  return _gpgrt_set_nonblock (stream, onoff);
+}
+
+int
+gpgrt_get_nonblock (estream_t stream)
+{
+  return _gpgrt_get_nonblock (stream);
+}
+
+int
+gpgrt_poll (gpgrt_poll_t *fds, unsigned int nfds, int timeout)
+{
+  return _gpgrt_poll (fds, nfds, timeout);
+}
+
 estream_t
 gpgrt_tmpfile (void)
 {
diff --git a/src/visibility.h b/src/visibility.h
index 6f7de84..ec3a124 100644
--- a/src/visibility.h
+++ b/src/visibility.h
@@ -127,6 +127,9 @@ MARK_VISIBLE (gpgrt_vfprintf_unlocked)
 MARK_VISIBLE (gpgrt_setvbuf)
 MARK_VISIBLE (gpgrt_setbuf)
 MARK_VISIBLE (gpgrt_set_binary)
+MARK_VISIBLE (gpgrt_set_nonblock)
+MARK_VISIBLE (gpgrt_get_nonblock)
+MARK_VISIBLE (gpgrt_poll)
 MARK_VISIBLE (gpgrt_tmpfile)
 MARK_VISIBLE (gpgrt_opaque_set)
 MARK_VISIBLE (gpgrt_opaque_get)
@@ -232,6 +235,9 @@ MARK_VISIBLE (gpgrt_set_alloc_func)
 #define gpgrt_setvbuf               _gpgrt_USE_UNDERSCORED_FUNCTION
 #define gpgrt_setbuf                _gpgrt_USE_UNDERSCORED_FUNCTION
 #define gpgrt_set_binary            _gpgrt_USE_UNDERSCORED_FUNCTION
+#define gpgrt_set_nonblock          _gpgrt_USE_UNDERSCORED_FUNCTION
+#define gpgrt_get_nonblock          _gpgrt_USE_UNDERSCORED_FUNCTION
+#define gpgrt_poll                  _gpgrt_USE_UNDERSCORED_FUNCTION
 #define gpgrt_tmpfile               _gpgrt_USE_UNDERSCORED_FUNCTION
 #define gpgrt_opaque_set            _gpgrt_USE_UNDERSCORED_FUNCTION
 #define gpgrt_opaque_get            _gpgrt_USE_UNDERSCORED_FUNCTION
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 5cbd9f4..92b97f2 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -27,7 +27,7 @@ endif
 
 gpg_error_lib = ../src/libgpg-error.la
 
-TESTS = t-version t-strerror t-syserror t-lock t-printf
+TESTS = t-version t-strerror t-syserror t-lock t-printf t-poll
 
 AM_CPPFLAGS = -I$(top_builddir)/src $(extra_includes)
 
@@ -38,3 +38,4 @@ noinst_PROGRAMS = $(TESTS)
 noinst_HEADERS = t-common.h
 
 t_lock_LDADD = $(gpg_error_lib) $(LIBMULTITHREAD)
+t_poll_LDADD = $(gpg_error_lib) $(LIBMULTITHREAD)
diff --git a/tests/t-common.h b/tests/t-common.h
index 85bcd51..c6dcd12 100644
--- a/tests/t-common.h
+++ b/tests/t-common.h
@@ -24,6 +24,9 @@
 #ifndef PGM
 # error Macro PGM not defined.
 #endif
+#ifndef DIM
+# define DIM(array) (sizeof (array) / sizeof (*array))
+#endif
 
 
 static int verbose;
diff --git a/tests/t-poll.c b/tests/t-poll.c
new file mode 100644
index 0000000..5955d50
--- /dev/null
+++ b/tests/t-poll.c
@@ -0,0 +1,383 @@
+/* t-poll.c - Check the poll function
+ * Copyright (C) 2015 g10 Code GmbH
+ *
+ * This file is part of libgpg-error.
+ *
+ * libgpg-error is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * libgpg-error 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* FIXME: We need much better tests that this very basic one.  */
+
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+#ifdef _WIN32
+# include <windows.h>
+# include <time.h>
+#else
+# include <pthread.h>
+#endif
+
+#define PGM "t-lock"
+
+#include "t-common.h"
+
+#ifdef _WIN32
+# define THREAD_RET_TYPE  DWORD WINAPI
+# define THREAD_RET_VALUE 0
+#else
+# define THREAD_RET_TYPE  void *
+# define THREAD_RET_VALUE NULL
+#endif
+
+
+/* Object to convey data to a thread.  */
+struct thread_arg
+{
+  const char *name;
+  estream_t stream;
+  volatile int stop_me;
+#ifdef USE_POSIX_THREADS
+  pthread_t thread;
+#elif _WIN32
+  HANDLE thread;
+#endif
+};
+
+
+static struct thread_arg peer_stdin;  /* Thread to feed the stdin.  */
+static struct thread_arg peer_stdout; /* Thread to feed the stdout. */
+static struct thread_arg peer_stderr; /* Thread to feed the stderr. */
+
+static estream_t test_stdin;
+static estream_t test_stdout;
+static estream_t test_stderr;
+
+#if defined(_WIN32) || defined(USE_POSIX_THREADS)
+
+/* This thread feeds data to the given stream.  */
+static THREAD_RET_TYPE
+producer_thread (void *argaddr)
+{
+  struct thread_arg *arg = argaddr;
+  int i = 0;
+
+  (void)arg;
+
+  while (!arg->stop_me && i++ < 3)
+    {
+      show ("thread '%s' about to write\n", arg->name);
+      es_fprintf (arg->stream, "This is '%s' count=%d\n", arg->name, i);
+      es_fflush (arg->stream);
+    }
+  es_fclose (arg->stream);
+  return THREAD_RET_VALUE;
+}
+
+/* This thread eats data from the given stream.  */
+static THREAD_RET_TYPE
+consumer_thread (void *argaddr)
+{
+  struct thread_arg *arg = argaddr;
+  char buf[15];
+
+  (void)arg;
+
+  while (!arg->stop_me)
+    {
+      show ("thread '%s' ready to read\n", arg->name);
+      if (!es_fgets (buf, sizeof buf, arg->stream))
+        {
+          show ("Thread '%s' received EOF or error\n", arg->name);
+          break;
+        }
+      show ("Thread '%s' got: '%s'\n", arg->name, buf);
+    }
+  es_fclose (arg->stream);
+  return THREAD_RET_VALUE;
+}
+
+#endif /*_WIN32 || USE_POSIX_THREADS */
+
+
+static void
+launch_thread (THREAD_RET_TYPE (*fnc)(void *), struct thread_arg *th)
+{
+#ifdef _WIN32
+
+  th->thread = CreateThread (NULL, 0, fnc, th, 0, NULL);
+  if (!th->thread)
+    die ("creating thread '%s' failed: rc=%d", th->name, (int)GetLastError ());
+  show ("thread '%s' launched (fd=%d)\n", th->name, es_fileno (th->stream));
+
+#elif USE_POSIX_THREADS
+
+  th->stop_me = 0;
+  if (pthread_create (&th->thread, NULL, fnc, th))
+    die ("creating thread '%s' failed: %s\n", th->name, strerror (errno));
+  show ("thread '%s' launched (fd=%d)\n", th->name, es_fileno (th->stream));
+
+# else /* no thread support */
+
+  verbose++;
+  show ("no thread support - skipping test\n", PGM);
+  verbose--;
+
+#endif /* no thread support */
+}
+
+
+static void
+join_thread (struct thread_arg *th)
+{
+#ifdef _WIN32
+  int rc;
+
+  rc = WaitForSingleObject (th->thread, INFINITE);
+  if (rc == WAIT_OBJECT_0)
+    show ("thread '%s' has terminated\n", th->name);
+  else
+    fail ("waiting for thread '%s' failed: %d", th->name, (int)GetLastError ());
+  CloseHandle (th->thread);
+
+#elif USE_POSIX_THREADS
+
+  pthread_join (th->thread, NULL);
+  show ("thread '%s' has terminated\n", th->name);
+
+#endif
+}
+
+
+static void
+create_pipe (estream_t *r_in, estream_t *r_out)
+{
+  gpg_error_t err;
+  int filedes[2];
+
+#ifdef _WIN32
+  if (_pipe (filedes, 512, 0) == -1)
+#else
+  if (pipe (filedes) == -1)
+#endif
+    {
+      err = gpg_error_from_syserror ();
+      die ("error creating a pipe: %s\n", gpg_strerror (err));
+    }
+
+  show ("created pipe [%d, %d]\n", filedes[0], filedes[1]);
+
+  *r_in = es_fdopen (filedes[0], "r");
+  if (!*r_in)
+    {
+      err = gpg_error_from_syserror ();
+      die ("error creating a stream for a pipe: %s\n", gpg_strerror (err));
+    }
+
+  *r_out = es_fdopen (filedes[1], "w");
+  if (!*r_out)
+    {
+      err = gpg_error_from_syserror ();
+      die ("error creating a stream for a pipe: %s\n", gpg_strerror (err));
+    }
+}
+
+
+static void
+test_poll (void)
+{
+  int ret;
+  gpgrt_poll_t fds[3];
+  char buffer[16];
+  size_t used, nwritten;
+  int c;
+
+  memset (fds, 0, sizeof fds);
+  fds[0].stream = test_stdin;
+  fds[0].want_read = 1;
+  fds[1].stream = test_stdout;
+  fds[1].want_write = 1;
+  /* FIXME: We don't use the next stream at all.  */
+  fds[2].stream = test_stderr;
+  fds[2].want_write = 1;
+  fds[2].ignore = 1;
+
+
+  used = 0;
+  while (used || !fds[0].ignore)
+    {
+      ret = gpgrt_poll (fds, DIM(fds), -1);
+      if (ret == -1)
+        {
+          fail ("gpgrt_poll failed: %s\n", strerror (errno));
+          continue;
+        }
+      if (!ret)
+        {
+          fail ("gpgrt_poll unexpectedly timed out\n");
+          continue;
+        }
+      show ("gpgrt_poll detected %d events\n", ret);
+      if (fds[0].got_read)
+        {
+          /* Read from the producer.  */
+          for (;;)
+            {
+              c = es_fgetc (fds[0].stream);
+              if (c == EOF)
+                {
+                  if (es_feof (fds[0].stream))
+                    {
+                      show ("reading '%s': EOF\n", peer_stdin.name);
+                      fds[0].ignore = 1; /* Not anymore needed.  */
+                      peer_stdin.stop_me = 1; /* Tell the thread to stop.  */
+                    }
+                  else if (es_ferror (fds[0].stream))
+                    {
+                      fail ("error reading '%s': %s\n",
+                            peer_stdin.name, strerror (errno));
+                      fds[0].ignore = 1;    /* Disable.  */
+                      peer_stdin.stop_me = 1; /* Tell the thread to stop.  */
+                    }
+                  else
+                    show ("reading '%s': EAGAIN\n", peer_stdin.name);
+                  break;
+                }
+              else
+                {
+                  if (used <= sizeof buffer -1)
+                    buffer[used++] = c;
+                  if (used == sizeof buffer)
+                    {
+                      show ("throttling reading from '%s'\n", peer_stdin.name);
+                      fds[0].ignore = 1;
+                      break;
+                    }
+                }
+            }
+          show ("read from '%s': %zu bytes\n", peer_stdin.name, used);
+          if (used)
+            fds[1].ignore = 0; /* Data to send.  */
+        }
+      if (fds[1].got_write)
+        {
+          if (used)
+            {
+              ret = es_write (fds[1].stream, buffer, used, &nwritten);
+              show ("result for writing to '%s': ret=%d, n=%zu, nwritten=%zu\n",
+                    peer_stdout.name, ret, used, nwritten);
+              if (!ret)
+                {
+                  assert (nwritten <= used);
+                  memmove (buffer, buffer + nwritten, nwritten);
+                  used -= nwritten;
+                }
+              ret = es_fflush (fds[1].stream);
+              if (ret)
+                fail ("Flushing for '%s' failed: %s\n",
+                      peer_stdout.name, strerror (errno));
+            }
+          if (!used)
+            fds[1].ignore = 1; /* No need to send data.  */
+        }
+
+      if (used < sizeof buffer / 2 && !peer_stdin.stop_me && fds[0].ignore)
+        {
+          show ("accelerate reading from '%s'\n", peer_stdin.name);
+          fds[0].ignore = 0;
+        }
+    }
+}
+
+
+int
+main (int argc, char **argv)
+{
+  int last_argc = -1;
+
+  if (argc)
+    {
+      argc--; argv++;
+    }
+  while (argc && last_argc != argc )
+    {
+      last_argc = argc;
+      if (!strcmp (*argv, "--help"))
+        {
+          puts (
+"usage: ./t-poll [options]\n"
+"\n"
+"Options:\n"
+"  --verbose      Show what is going on\n"
+"  --debug        Flyswatter\n"
+);
+          exit (0);
+        }
+      if (!strcmp (*argv, "--verbose"))
+        {
+          verbose = 1;
+          argc--; argv++;
+        }
+      else if (!strcmp (*argv, "--debug"))
+        {
+          verbose = debug = 1;
+          argc--; argv++;
+        }
+    }
+
+  if (!gpg_error_check_version (GPG_ERROR_VERSION))
+    {
+      die ("gpg_error_check_version returned an error");
+      errorcount++;
+    }
+
+  peer_stdin.name  = "stdin producer";
+  create_pipe (&test_stdin, &peer_stdin.stream);
+  peer_stdout.name = "stdout consumer";
+  create_pipe (&peer_stdout.stream, &test_stdout);
+  peer_stderr.name = "stderr consumer";
+  create_pipe (&peer_stderr.stream, &test_stderr);
+
+  if (es_set_nonblock (test_stdin, 1))
+    fail ("error setting test_stdin to nonblock: %s\n", strerror (errno));
+  if (es_set_nonblock (test_stdout, 1))
+    fail ("error setting test_stdout to nonblock: %s\n", strerror (errno));
+  if (es_set_nonblock (test_stderr, 1))
+    fail ("error setting test_stderr to nonblock: %s\n", strerror (errno));
+
+  launch_thread (producer_thread, &peer_stdin );
+  launch_thread (consumer_thread, &peer_stdout);
+  launch_thread (consumer_thread, &peer_stderr);
+  test_poll ();
+  show ("Waiting for threads to terminate...\n");
+  es_fclose (test_stdin);
+  es_fclose (test_stdout);
+  es_fclose (test_stderr);
+  peer_stdin.stop_me = 1;
+  peer_stdout.stop_me = 1;
+  peer_stderr.stop_me = 1;
+  join_thread (&peer_stdin);
+  join_thread (&peer_stdout);
+  join_thread (&peer_stderr);
+
+  return errorcount ? 1 : 0;
+}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-gnupg/libgpg-error.git



More information about the Pkg-gnupg-commit mailing list