/*
 * DF-2836 PoC: sonewconn_faddr() inherits the listener's ENTIRE so_state
 * (uipc_socket2.c:383: `so->so_state = head->so_state | SS_NOFDREF |
 * SS_ASSERTINPROG;`).  A listener that has been shutdown(SHUT_WR) carries
 * SS_CANTSENDMORE; every child born afterwards (TCP handshake via the
 * syncache, or an AF_UNIX connect) is born half-shut: the accepted socket
 * returns EPIPE on its first send() even though the connection is fully
 * live and data flows in the receive direction.
 *
 * Unprivileged.  Expected on a correct stack (FreeBSD inherits only the
 * NBIO bit): accepted send() == 4.  On DragonFly: send() == -1 EPIPE.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>

static void
demo_tcp(void)
{
	struct sockaddr_in sa;
	socklen_t sl = sizeof(sa);
	char buf[16];
	int l, c, a, n;

	l = socket(AF_INET, SOCK_STREAM, 0);
	memset(&sa, 0, sizeof(sa));
	sa.sin_family = AF_INET;
	sa.sin_len = sizeof(sa);
	sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
	sa.sin_port = 0;
	if (bind(l, (struct sockaddr *)&sa, sizeof(sa)) < 0 ||
	    getsockname(l, (struct sockaddr *)&sa, &sl) < 0 ||
	    listen(l, 5) < 0) {
		printf("tcp: setup failed: %s\n", strerror(errno));
		return;
	}
	/* graceful "stop taking new requests" a la daemon restart */
	printf("tcp: shutdown(listener, SHUT_WR) = %d\n",
	       shutdown(l, SHUT_WR));

	c = socket(AF_INET, SOCK_STREAM, 0);
	if (connect(c, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
		printf("tcp: connect failed: %s\n", strerror(errno));
		return;
	}
	a = accept(l, NULL, 0);
	printf("tcp: connect ok, accept = %d\n", a);
	if (a < 0)
		return;

	n = send(c, "PING", 4, 0);
	printf("tcp: client  send    = %d (%s)\n", n, strerror(errno));
	usleep(200000);
	n = recv(a, buf, sizeof(buf), 0);
	printf("tcp: accepted recv   = %d (%s)  [recv direction is LIVE]\n",
	       n, strerror(errno));
	n = send(a, "PONG", 4, 0);
	printf("tcp: accepted send   = %d errno=%d [%s]  "
	       "<-- want 4; EPIPE(32) = BUG\n",
	       n, errno, strerror(errno));
	close(a); close(c); close(l);
}

static void
demo_unix(void)
{
	struct sockaddr_un sun;
	char buf[16];
	int l, c, a, n;

	l = socket(AF_UNIX, SOCK_STREAM, 0);
	memset(&sun, 0, sizeof(sun));
	sun.sun_family = AF_UNIX;
	sun.sun_len = sizeof(sun);
	strcpy(sun.sun_path, "/tmp/df2836.sock");
	unlink(sun.sun_path);
	if (bind(l, (struct sockaddr *)&sun, sizeof(sun)) < 0 ||
	    listen(l, 5) < 0) {
		printf("unix: setup failed: %s\n", strerror(errno));
		return;
	}
	printf("unix: shutdown(listener, SHUT_WR) = %d\n",
	       shutdown(l, SHUT_WR));

	c = socket(AF_UNIX, SOCK_STREAM, 0);
	if (connect(c, (struct sockaddr *)&sun, sizeof(sun)) < 0) {
		printf("unix: connect failed: %s\n", strerror(errno));
		return;
	}
	a = accept(l, NULL, 0);
	printf("unix: connect ok, accept = %d\n", a);
	if (a < 0)
		return;

	n = send(c, "PING", 4, 0);
	printf("unix: client  send  = %d (%s)\n", n, strerror(errno));
	n = recv(a, buf, sizeof(buf), 0);
	printf("unix: accepted recv = %d (%s)\n", n, strerror(errno));
	n = send(a, "PONG", 4, 0);
	printf("unix: accepted send = %d errno=%d [%s]  "
	       "<-- want 4; EPIPE(32) = BUG\n",
	       n, errno, strerror(errno));
	close(a); close(c); close(l);
	unlink(sun.sun_path);
}

int
main(void)
{
	signal(SIGPIPE, SIG_IGN);
	setvbuf(stdout, NULL, _IONBF, 0);
	printf("== DF-2836: listener state inheritance ==\n");
	demo_tcp();
	demo_unix();
	return (0);
}
