Implement SO_NOSIGPIPE
authorJohn Marino <draco@marino.st>
Thu, 1 Nov 2012 22:54:16 +0000 (23:54 +0100)
committerJohn Marino <draco@marino.st>
Fri, 2 Nov 2012 00:15:22 +0000 (01:15 +0100)
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

sys/kern/sys_generic.c
sys/kern/sys_socket.c
sys/kern/uipc_socket.c
sys/kern/uipc_syscalls.c
sys/sys/socket.h
tools/regression/sockets/sigpipe/Makefile [new file with mode: 0644]
tools/regression/sockets/sigpipe/sigpipe.c [new file with mode: 0644]

index 2322c12..1088fb3 100644 (file)
@@ -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
index 2575a7d..9b9b42e 100644 (file)
@@ -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 <sys/param.h>
@@ -40,6 +39,7 @@
 #include <sys/file.h>
 #include <sys/lock.h>
 #include <sys/protosw.h>
+#include <sys/signalvar.h>
 #include <sys/socket.h>
 #include <sys/socketvar.h>
 #include <sys/socketops.h>
@@ -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);
 }
 
index 165c738..847db40 100644 (file)
@@ -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);
index 2b65b48..1a2ca1a 100644 (file)
@@ -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
index 54b462b..444415e 100644 (file)
@@ -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 (file)
index 0000000..6a7e0ed
--- /dev/null
@@ -0,0 +1,5 @@
+PROG=          sigpipe
+NOMAN=
+WARNS?=        2
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/sigpipe/sigpipe.c b/tools/regression/sockets/sigpipe/sigpipe.c
new file mode 100644 (file)
index 0000000..0422576
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+
+#include <err.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * 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);
+}