From: John Marino Date: Thu, 1 Nov 2012 22:54:16 +0000 (+0100) Subject: Implement SO_NOSIGPIPE X-Git-Tag: v3.4.0rc~923 X-Git-Url: https://gitweb.dragonflybsd.org/dragonfly.git/commitdiff_plain/89233cfd97300c5309bc96594ef1b873b24d60c2 Implement SO_NOSIGPIPE The SO_NOSIGPIPE socket option allows a user process to mark a socket so that the socket does not generate SIGPIPE, only EPIPE, when a write is attempted after socket shutdown. Regression test added: tools/regression/sockets/sigpipe --- diff --git a/sys/kern/sys_generic.c b/sys/kern/sys_generic.c index 2322c12013..1088fb3a22 100644 --- a/sys/kern/sys_generic.c +++ b/sys/kern/sys_generic.c @@ -518,7 +518,7 @@ dofilewrite(int fd, struct file *fp, struct uio *auio, int flags, size_t *res) error == EINTR || error == EWOULDBLOCK)) error = 0; /* Socket layer is responsible for issuing SIGPIPE. */ - if (error == EPIPE) + if (error == EPIPE && fp->f_type != DTYPE_SOCKET) lwpsignal(lp->lwp_proc, lp, SIGPIPE); } #ifdef KTRACE diff --git a/sys/kern/sys_socket.c b/sys/kern/sys_socket.c index 2575a7dab5..9b9b42e292 100644 --- a/sys/kern/sys_socket.c +++ b/sys/kern/sys_socket.c @@ -32,7 +32,6 @@ * * @(#)sys_socket.c 8.1 (Berkeley) 6/10/93 * $FreeBSD: src/sys/kern/sys_socket.c,v 1.28.2.2 2001/02/26 04:23:16 jlemon Exp $ - * $DragonFly: src/sys/kern/sys_socket.c,v 1.14 2007/04/22 01:13:10 dillon Exp $ */ #include @@ -40,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -115,6 +115,9 @@ soo_write(struct file *fp, struct uio *uio, struct ucred *cred, int fflags) msgflags = 0; error = so_pru_sosend(so, NULL, uio, NULL, NULL, msgflags, uio->uio_td); + if (error == EPIPE && !(fflags & MSG_NOSIGNAL) && + !(so->so_options & SO_NOSIGPIPE)) + lwpsignal(uio->uio_td->td_proc, uio->uio_td->td_lwp, SIGPIPE); return (error); } diff --git a/sys/kern/uipc_socket.c b/sys/kern/uipc_socket.c index 165c738cc1..847db40368 100644 --- a/sys/kern/uipc_socket.c +++ b/sys/kern/uipc_socket.c @@ -1970,6 +1970,7 @@ sosetopt(struct socket *so, struct sockopt *sopt) case SO_REUSEPORT: case SO_OOBINLINE: case SO_TIMESTAMP: + case SO_NOSIGPIPE: error = sooptcopyin(sopt, &optval, sizeof optval, sizeof optval); if (error) @@ -2167,6 +2168,7 @@ sogetopt(struct socket *so, struct sockopt *sopt) case SO_BROADCAST: case SO_OOBINLINE: case SO_TIMESTAMP: + case SO_NOSIGPIPE: optval = so->so_options & sopt->sopt_name; integer: error = sooptcopyout(sopt, &optval, sizeof optval); diff --git a/sys/kern/uipc_syscalls.c b/sys/kern/uipc_syscalls.c index 2b65b48c89..1a2ca1a33f 100644 --- a/sys/kern/uipc_syscalls.c +++ b/sys/kern/uipc_syscalls.c @@ -711,7 +711,8 @@ kern_sendmsg(int s, struct sockaddr *sa, struct uio *auio, if (auio->uio_resid != len && (error == ERESTART || error == EINTR || error == EWOULDBLOCK)) error = 0; - if (error == EPIPE && !(flags & MSG_NOSIGNAL)) + if (error == EPIPE && !(flags & MSG_NOSIGNAL) && + !(so->so_options & SO_NOSIGPIPE)) lwpsignal(p, lp, SIGPIPE); } #ifdef KTRACE diff --git a/sys/sys/socket.h b/sys/sys/socket.h index 54b462b348..444415ebf0 100644 --- a/sys/sys/socket.h +++ b/sys/sys/socket.h @@ -86,7 +86,7 @@ typedef __socklen_t socklen_t; #define SO_OOBINLINE 0x0100 /* leave received OOB data in line */ #define SO_REUSEPORT 0x0200 /* allow local address & port reuse */ #define SO_TIMESTAMP 0x0400 /* timestamp received dgram traffic */ -#define SO_UNUSED0800 0x0800 +#define SO_NOSIGPIPE 0x0800 /* no SIGPIPE from EPIPE */ #define SO_ACCEPTFILTER 0x1000 /* there is an accept filter */ /* diff --git a/tools/regression/sockets/sigpipe/Makefile b/tools/regression/sockets/sigpipe/Makefile new file mode 100644 index 0000000000..6a7e0ed0f5 --- /dev/null +++ b/tools/regression/sockets/sigpipe/Makefile @@ -0,0 +1,5 @@ +PROG= sigpipe +NOMAN= +WARNS?= 2 + +.include diff --git a/tools/regression/sockets/sigpipe/sigpipe.c b/tools/regression/sockets/sigpipe/sigpipe.c new file mode 100644 index 0000000000..0422576ba2 --- /dev/null +++ b/tools/regression/sockets/sigpipe/sigpipe.c @@ -0,0 +1,322 @@ +/*- + * Copyright (c) 2005 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +/* + * This regression test is intended to verify whether or not SIGPIPE is + * properly generated in several simple test cases, as well as testing + * whether SO_NOSIGPIPE disables SIGPIPE, if available on the system. + * SIGPIPE is generated if a write or send is attempted on a socket that has + * been shutdown for write. This test runs several test cases with UNIX + * domain sockets and TCP sockets to confirm that either EPIPE or SIGPIPE is + * properly returned. + * + * For the purposes of testing TCP, an unused port number must be specified. + */ +static void +usage(void) +{ + + errx(-1, "usage: sigpipe tcpport"); +} + +/* + * Signal catcher. Set a global flag that can be tested by the caller. + */ +static int signaled; +static int +got_signal(void) +{ + + return (signaled); +} + +static void +signal_handler(int signum) +{ + + signaled = 1; +} + +static void +signal_setup(const char *testname) +{ + + signaled = 0; + if (signal(SIGPIPE, signal_handler) == SIG_ERR) + err(-1, "%s: signal(SIGPIPE)", testname); +} + +static void +test_send(const char *testname, int sock) +{ + ssize_t len; + char ch; + + ch = 0; + len = send(sock, &ch, sizeof(ch), 0); + if (len < 0) { + if (errno == EPIPE) + return; + err(-1, "%s: send", testname); + } + errx(-1, "%s: send: returned %d", testname, len); +} + +static void +test_write(const char *testname, int sock) +{ + ssize_t len; + char ch; + + ch = 0; + len = write(sock, &ch, sizeof(ch)); + if (len < 0) { + if (errno == EPIPE) + return; + err(-1, "%s: write", testname); + } + errx(-1, "%s: write: returned %d", testname, len); +} + +static void +test_send_wantsignal(const char *testname, int sock1, int sock2) +{ + + if (shutdown(sock2, SHUT_WR) < 0) + err(-1, "%s: shutdown", testname); + signal_setup(testname); + test_send(testname, sock2); + if (!got_signal()) + errx(-1, "%s: send: didn't receive SIGPIPE", testname); + close(sock1); + close(sock2); +} + +#ifdef SO_NOSIGPIPE +static void +test_send_dontsignal(const char *testname, int sock1, int sock2) +{ + int i; + + i = 1; + if (setsockopt(sock2, SOL_SOCKET, SO_NOSIGPIPE, &i, sizeof(i)) < 0) + err(-1, "%s: setsockopt(SOL_SOCKET, SO_NOSIGPIPE)", testname); + if (shutdown(sock2, SHUT_WR) < 0) + err(-1, "%s: shutdown", testname); + signal_setup(testname); + test_send(testname, sock2); + if (got_signal()) + errx(-1, "%s: send: got SIGPIPE", testname); + close(sock1); + close(sock2); +} +#endif + +static void +test_write_wantsignal(const char *testname, int sock1, int sock2) +{ + + if (shutdown(sock2, SHUT_WR) < 0) + err(-1, "%s: shutdown", testname); + signal_setup(testname); + test_write(testname, sock2); + if (!got_signal()) + errx(-1, "%s: write: didn't receive SIGPIPE", testname); + close(sock1); + close(sock2); +} + +#ifdef SO_NOSIGPIPE +static void +test_write_dontsignal(const char *testname, int sock1, int sock2) +{ + int i; + + i = 1; + if (setsockopt(sock2, SOL_SOCKET, SO_NOSIGPIPE, &i, sizeof(i)) < 0) + err(-1, "%s: setsockopt(SOL_SOCKET, SO_NOSIGPIPE)", testname); + if (shutdown(sock2, SHUT_WR) < 0) + err(-1, "%s: shutdown", testname); + signal_setup(testname); + test_write(testname, sock2); + if (got_signal()) + errx(-1, "%s: write: got SIGPIPE", testname); + close(sock1); + close(sock2); +} +#endif + +static int listen_sock; +static void +tcp_setup(u_short port) +{ + struct sockaddr_in sin; + + listen_sock = socket(PF_INET, SOCK_STREAM, 0); + if (listen_sock < 0) + err(-1, "tcp_setup: listen"); + + bzero(&sin, sizeof(sin)); + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sin.sin_port = htons(port); + + if (bind(listen_sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) + err(-1, "tcp_setup: bind"); + + if (listen(listen_sock, -1) < 0) + err(-1, "tcp_setup: listen"); +} + +static void +tcp_teardown(void) +{ + + close(listen_sock); +} + +static void +tcp_pair(u_short port, int sock[2]) +{ + int accept_sock, connect_sock; + struct sockaddr_in sin; + socklen_t len; + + connect_sock = socket(PF_INET, SOCK_STREAM, 0); + if (connect_sock < 0) + err(-1, "tcp_pair: socket"); + + bzero(&sin, sizeof(sin)); + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sin.sin_port = htons(port); + + if (connect(connect_sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) + err(-1, "tcp_pair: connect"); + + sleep(1); /* Time for TCP to settle. */ + + len = sizeof(sin); + accept_sock = accept(listen_sock, (struct sockaddr *)&sin, &len); + if (accept_sock < 0) + err(-1, "tcp_pair: accept"); + + sleep(1); /* Time for TCP to settle. */ + + sock[0] = accept_sock; + sock[1] = connect_sock; +} + +int +main(int argc, char *argv[]) +{ + char *dummy; + int sock[2]; + long port; + + if (argc != 2) + usage(); + + port = strtol(argv[1], &dummy, 10); + if (port < 0 || port > 65535 || *dummy != '\0') + usage(); + +#ifndef SO_NOSIGPIPE + warnx("sigpipe: SO_NOSIGPIPE not defined, skipping some tests"); +#endif + + /* + * UNIX domain socketpair(). + */ + if (socketpair(PF_LOCAL, SOCK_STREAM, 0, sock) < 0) + err(-1, "socketpair(PF_LOCAL, SOCK_STREAM)"); + test_send_wantsignal("test_send_wantsignal(PF_LOCAL)", sock[0], + sock[1]); + +#ifdef SO_NOSIGPIPE + if (socketpair(PF_LOCAL, SOCK_STREAM, 0, sock) < 0) + err(-1, "socketpair(PF_LOCAL, SOCK_STREAM)"); + test_send_dontsignal("test_send_dontsignal(PF_LOCAL)", sock[0], + sock[1]); +#endif + + if (socketpair(PF_LOCAL, SOCK_STREAM, 0, sock) < 0) + err(-1, "socketpair(PF_LOCAL, SOCK_STREAM)"); + test_write_wantsignal("test_write_wantsignal(PF_LOCAL)", sock[0], + sock[1]); + +#ifdef SO_NOSIGPIPE + if (socketpair(PF_LOCAL, SOCK_STREAM, 0, sock) < 0) + err(-1, "socketpair(PF_LOCAL, SOCK_STREAM)"); + test_write_dontsignal("test_write_dontsignal(PF_LOCAL)", sock[0], + sock[1]); +#endif + + /* + * TCP. + */ + tcp_setup(port); + tcp_pair(port, sock); + test_send_wantsignal("test_send_wantsignal(PF_INET)", sock[0], + sock[1]); + +#ifdef SO_NOSIGPIPE + tcp_pair(port, sock); + test_send_dontsignal("test_send_dontsignal(PF_INET)", sock[0], + sock[1]); +#endif + + tcp_pair(port, sock); + test_write_wantsignal("test_write_wantsignal(PF_INET)", sock[0], + sock[1]); + +#ifdef SO_NOSIGPIPE + tcp_pair(port, sock); + test_write_dontsignal("test_write_dontsignal(PF_INET)", sock[0], + sock[1]); +#endif + tcp_teardown(); + + fprintf(stderr, "PASS\n"); + return (0); +}