diff options
author | Werner Koch <wk@gnupg.org> | 2015-09-25 10:45:22 +0200 |
---|---|---|
committer | Werner Koch <wk@gnupg.org> | 2015-09-25 11:32:07 +0200 |
commit | 071c2170479869f4c6694ae85d2b113e84482a01 (patch) | |
tree | 00f0ef082a9c2f26b6ee0e26b37c0828d2886f26 | |
parent | 61d832c53b7db5367a5542347e3454c882d0bf28 (diff) | |
download | libgpg-error-071c2170479869f4c6694ae85d2b113e84482a01.tar.gz |
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@gnupg.org>
-rw-r--r-- | NEWS | 16 | ||||
-rw-r--r-- | configure.ac | 2 | ||||
-rw-r--r-- | doc/errorref.txt | 2 | ||||
-rw-r--r-- | src/estream.c | 349 | ||||
-rw-r--r-- | src/gpg-error.def.in | 4 | ||||
-rw-r--r-- | src/gpg-error.h.in | 33 | ||||
-rw-r--r-- | src/gpg-error.vers | 3 | ||||
-rw-r--r-- | src/gpgrt-int.h | 4 | ||||
-rw-r--r-- | src/visibility.c | 18 | ||||
-rw-r--r-- | src/visibility.h | 6 | ||||
-rw-r--r-- | tests/Makefile.am | 3 | ||||
-rw-r--r-- | tests/t-common.h | 3 | ||||
-rw-r--r-- | tests/t-poll.c | 383 |
13 files changed, 818 insertions, 8 deletions
@@ -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; +} |