From: Eric Blake Date: Thu, 31 Mar 2011 04:07:20 +0000 (-0600) Subject: nonblocking: new module X-Git-Tag: v0.1~3010 X-Git-Url: http://erislabs.net/gitweb/?p=gnulib.git;a=commitdiff_plain;h=111a4bf8dd3d9646133305c19964d8080e0b7085 nonblocking: new module * modules/nonblocking: New module. * modules/nonblocking-tests: Likewise. * lib/nonblocking.h: New file. * lib/nonblocking.c: Likewise. * tests/test-nonblocking.c: New test. * lib/ioctl.c (ioctl) [mingw]: Update comment. Signed-off-by: Eric Blake --- diff --git a/ChangeLog b/ChangeLog index 0794a3066..057a46dc0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +2011-03-31 Bruno Haible + and Eric Blake + + nonblocking: new module + * modules/nonblocking: New module. + * modules/nonblocking-tests: Likewise. + * lib/nonblocking.h: New file. + * lib/nonblocking.c: Likewise. + * tests/test-nonblocking.c: New test. + * lib/ioctl.c (ioctl) [mingw]: Update comment. + 2011-03-30 Bruno Haible stdio: Avoid GCC >= 4.4 warnings when using %lld and similar on mingw. diff --git a/lib/ioctl.c b/lib/ioctl.c index 7c09d95b4..4bbed7653 100644 --- a/lib/ioctl.c +++ b/lib/ioctl.c @@ -63,6 +63,10 @@ ioctl (int fd, int req, ...) buf = va_arg (args, void *); va_end (args); + /* We don't support FIONBIO on pipes here. If you want to make pipe + fds non-blocking, use the gnulib 'nonblocking' module, until + gnulib implements fcntl F_GETFL / F_SETFL with O_NONBLOCK. */ + sock = FD_TO_SOCKET (fd); r = ioctlsocket (sock, req, buf); if (r < 0) diff --git a/lib/nonblocking.c b/lib/nonblocking.c new file mode 100644 index 000000000..cb103be4b --- /dev/null +++ b/lib/nonblocking.c @@ -0,0 +1,138 @@ +/* Non-blocking I/O for pipe or socket descriptors. + Copyright (C) 2011 Free Software Foundation, Inc. + + 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 . */ + +#include + +/* Specification. */ +#include "nonblocking.h" + +#include + +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ +/* Native Woe32 API. */ + +# include +# include + +/* Get declarations of the Win32 API functions. */ +# define WIN32_LEAN_AND_MEAN +# include + +int +get_nonblocking_flag (int desc) +{ + HANDLE h = (HANDLE) _get_osfhandle (desc); + if (GetFileType (h) == FILE_TYPE_PIPE) + { + /* h is a pipe or socket. */ + DWORD state; + if (GetNamedPipeHandleState (h, &state, NULL, NULL, NULL, NULL, 0)) + /* h is a pipe. */ + return (state & PIPE_NOWAIT) != 0; + else + /* h is a socket. */ + errno = ENOSYS; + return -1; + } + else + /* Win32 does not support non-blocking on regular files. */ + return 0; +} + +int +set_nonblocking_flag (int desc, bool value) +{ + HANDLE h = (HANDLE) _get_osfhandle (desc); + if (GetFileType (h) == FILE_TYPE_PIPE) + { + /* h is a pipe or socket. */ + DWORD state; + if (GetNamedPipeHandleState (h, &state, NULL, NULL, NULL, NULL, 0)) + { + /* h is a pipe. */ + if ((state & PIPE_NOWAIT) != 0) + { + if (value) + return 0; + state &= ~PIPE_NOWAIT; + } + else + { + if (!value) + return 0; + state |= PIPE_NOWAIT; + } + if (SetNamedPipeHandleState (h, &state, NULL, NULL)) + return 0; + errno = EINVAL; + return -1; + } + else + { + /* h is a socket. */ + int v = value; + return ioctl (desc, FIONBIO, &v); + } + } + else + { + /* Win32 does not support non-blocking on regular files. */ + errno = ENOTSUP; + return -1; + } +} + +#else +/* Unix API. */ + +# include + +# if !O_NONBLOCK +# error Please port nonblocking to your platform +# endif + +/* We don't need the gnulib replacement of fcntl() here. */ +# undef fcntl + +int +get_nonblocking_flag (int desc) +{ + int fcntl_flags; + + fcntl_flags = fcntl (desc, F_GETFL, 0); + if (fcntl_flags < 0) + return -1; + return (fcntl_flags & O_NONBLOCK) != 0; +} + +int +set_nonblocking_flag (int desc, bool value) +{ + int fcntl_flags; + + fcntl_flags = fcntl (desc, F_GETFL, 0); + if (fcntl_flags < 0) + return -1; + if (((fcntl_flags & O_NONBLOCK) != 0) == value) + return 0; + if (value) + fcntl_flags |= O_NONBLOCK; + else + fcntl_flags &= ~O_NONBLOCK; + return fcntl (desc, F_SETFL, fcntl_flags); +} + +#endif diff --git a/lib/nonblocking.h b/lib/nonblocking.h new file mode 100644 index 000000000..f5835dedd --- /dev/null +++ b/lib/nonblocking.h @@ -0,0 +1,61 @@ +/* Non-blocking I/O for pipe or socket descriptors. + Copyright (C) 2011 Free Software Foundation, Inc. + + 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 . */ + +#ifndef _NONBLOCKING_H +#define _NONBLOCKING_H + +#include + +/* Non-blocking I/O is an I/O mode by which read(), write() calls avoid + blocking the current thread. When non-blocking is enabled: + - A read() call returns -1 with errno set to EAGAIN when no data or EOF + information is immediately available. + - A write() call returns -1 with errno set to EAGAIN when it cannot + transport the requested amount of data (but at most one pipe buffer) + without blocking. + Non-blocking I/O is most useful for character devices. Whether it + also works on regular files and block devices is platform dependent. + + There are three modern alternatives to non-blocking I/O: + - use select() or poll() followed by read() or write() if the descriptor + is ready, + - call read() or write() in separate threads, + - use interfaces. */ + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Return 1 if I/O to the descriptor DESC is currently non-blocking, 0 + it is blocking, or -1 with errno set if fd is invalid or blocking + status cannot be determined (such as with sockets on mingw). */ +extern int get_nonblocking_flag (int desc); + +/* Specify the non-blocking flag for the descriptor DESC. + Return 0 upon success, or -1 with errno set upon failure. + The default depends on the presence of the O_NONBLOCK flag for files + or pipes opened with open() or on the presence of the SOCK_NONBLOCK + flag for sockets. */ +extern int set_nonblocking_flag (int desc, bool value); + + +#ifdef __cplusplus +} +#endif + +#endif /* _NONBLOCKING_H */ diff --git a/modules/nonblocking b/modules/nonblocking new file mode 100644 index 000000000..9528405fa --- /dev/null +++ b/modules/nonblocking @@ -0,0 +1,26 @@ +Description: +Read, set or clear the non-blocking file descriptor flag. + +Files: +lib/nonblocking.c +lib/nonblocking.h + +Depends-on: +fcntl-h +ioctl +stdbool +sys_socket + +configure.ac: + +Makefile.am: +lib_SOURCES += nonblocking.c + +Include: +"nonblocking.h" + +License: +LGPLv2+ + +Maintainer: +Bruno Haible, Eric Blake diff --git a/modules/nonblocking-tests b/modules/nonblocking-tests new file mode 100644 index 000000000..94fccb208 --- /dev/null +++ b/modules/nonblocking-tests @@ -0,0 +1,16 @@ +Files: +tests/test-nonblocking.c +tests/macros.h + +Depends-on: +close +open +pipe-posix +socket + +configure.ac: + +Makefile.am: +TESTS += test-nonblocking +check_PROGRAMS += test-nonblocking +test_nonblocking_LDADD = $(LDADD) @LIBSOCKET@ diff --git a/tests/test-nonblocking.c b/tests/test-nonblocking.c new file mode 100644 index 000000000..0762cd0ae --- /dev/null +++ b/tests/test-nonblocking.c @@ -0,0 +1,125 @@ +/* Test manipulation of non-blocking flag. + Copyright (C) 2011 Free Software Foundation, Inc. + + 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 . */ + +/* Written by Eric Blake , 2011. */ + +#include + +#include "nonblocking.h" + +#include +#include +#include +#include + +#include "macros.h" + +int +main (void) +{ + const char *file = "test-nonblock.tmp"; + int fd_file; + int fd_pipe[2]; + int fd_sock; + bool sock_works = true; + +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ + /* For now, we can't get nonblocking status of windows sockets. */ + sock_works = false; +#endif + + fd_file = creat (file, 0600); + + /* Assume std descriptors were provided by invoker. */ + ASSERT (STDERR_FILENO < fd_file); + + /* Test regular files; setting nonblocking on file is unspecified. */ + ASSERT (get_nonblocking_flag (fd_file) == 0); + ASSERT (set_nonblocking_flag (fd_file, false) == 0); + ASSERT (get_nonblocking_flag (fd_file) == 0); + ASSERT (close (fd_file) == 0); + ASSERT (unlink (file) == 0); + + /* Test directories; setting nonblocking is unspecified. */ + fd_file = open (".", O_RDONLY); + ASSERT (STDERR_FILENO < fd_file); + ASSERT (get_nonblocking_flag (fd_file) == 0); + ASSERT (set_nonblocking_flag (fd_file, false) == 0); + ASSERT (get_nonblocking_flag (fd_file) == 0); + ASSERT (close (fd_file) == 0); + + /* Test pipes. */ + ASSERT (pipe (fd_pipe) == 0); + ASSERT (get_nonblocking_flag (fd_pipe[0]) == 0); + ASSERT (get_nonblocking_flag (fd_pipe[1]) == 0); + ASSERT (set_nonblocking_flag (fd_pipe[0], true) == 0); + ASSERT (get_nonblocking_flag (fd_pipe[0]) == 1); + ASSERT (get_nonblocking_flag (fd_pipe[1]) == 0); + ASSERT (set_nonblocking_flag (fd_pipe[1], true) == 0); + ASSERT (set_nonblocking_flag (fd_pipe[0], false) == 0); + ASSERT (get_nonblocking_flag (fd_pipe[0]) == 0); + ASSERT (get_nonblocking_flag (fd_pipe[1]) == 1); + ASSERT (close (fd_pipe[0]) == 0); + ASSERT (close (fd_pipe[1]) == 0); + +#if GNULIB_TEST_PIPE2 + /* mingw still lacks O_NONBLOCK replacement. */ + ASSERT (pipe2 (fd_pipe, O_NONBLOCK) == 0); + ASSERT (get_nonblocking_flag (fd_pipe[0]) == !!O_NONBLOCK); + ASSERT (get_nonblocking_flag (fd_pipe[1]) == !!O_NONBLOCK); + ASSERT (close (fd_pipe[0]) == 0); + ASSERT (close (fd_pipe[1]) == 0); +#endif /* GNULIB_TEST_PIPE2 */ + + /* Test sockets. */ + fd_sock = socket (AF_INET, SOCK_STREAM, 0); + ASSERT (get_nonblocking_flag (fd_sock) == (sock_works ? 0 : -1)); + ASSERT (set_nonblocking_flag (fd_sock, true) == 0); + ASSERT (get_nonblocking_flag (fd_sock) == (sock_works ? 1 : -1)); + ASSERT (set_nonblocking_flag (fd_sock, false) == 0); + ASSERT (get_nonblocking_flag (fd_sock) == (sock_works ? 0 : -1)); + ASSERT (close (fd_sock) == 0); + +#if SOCK_NONBLOCK + fd_sock = socket (AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); + ASSERT (get_nonblocking_flag (fd_sock) == (sock_works ? 1 : -1)); + ASSERT (close (fd_sock) == 0); +#endif /* SOCK_NONBLOCK */ + + /* Test error handling. */ + { + errno = 0; + ASSERT (get_nonblocking_flag (-1) == -1); + ASSERT (errno == EBADF); + } + { + errno = 0; + ASSERT (set_nonblocking_flag (-1, false) == -1); + ASSERT (errno == EBADF); + } + { + errno = 0; + ASSERT (set_nonblocking_flag (-1, true) == -1); + ASSERT (errno == EBADF); + } + { + errno = 0; + ASSERT (set_nonblocking_flag (10000000, false) == -1); + ASSERT (errno == EBADF); + } + + return 0; +}