/* Public domain. */

/* #define _POSIX_C_SOURCE 2 */

#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PATH_OFFSET offsetof(struct sockaddr_un, sun_path)

static int die(char *message)
{
	perror(message);
	exit(1);
}

static void usage(const char *name)
{
	printf("\
Usage: %s [options] <path>\n\
\n\
Listen on a Unix domain socket at <path> and for each accepted connection,\n\
print:\n\
\n\
   - the length of the value in sun_path, as determined from the address\n\
     length returned by accept()\n\
   - the contents of sun_path up to that length, or to the length passed to\n\
     accept(), whichever is smaller\n\
   - the entire contents of the sun_path buffer\n\
\n\
The sun_path buffer is initially filled with the '=' character, and any null\n\
characters in the returned value are replaced with the '@' character.\n\
\n\
Options:\n\
\n\
   -l N   Call accept() with a sun_path buffer of size N, instead of the\n\
          standard size.\n\
\n\
   -b N   Allocate the address buffer with a sun_path length of N, rather\n\
          than the size which will be passed to accept().\n\
\n", name);
	exit(1);
}

int main(int argc, char *argv[])
{
	int listenfd, connfd;
	struct sockaddr_un *peeraddrp;
	struct sockaddr_un bindaddr;
	socklen_t realsize, acceptsize;
	int c;
	long i, peerlen, reallen = -1, acceptlen = -1;

	while ((c = getopt(argc, argv, "hl:b:")) != -1) {
		switch (c) {
		case 'l':
			acceptlen = atol(optarg);
			break;
		case 'b':
			reallen = atol(optarg);
			break;
		case 'h':
		case '?':
			usage(argv[0]);
			break;
		}
	}
	if (argc - optind != 1)
		usage(argv[0]);
	if (acceptlen < 0)
		acceptlen = sizeof(peeraddrp->sun_path);
	if (reallen < 0)
		reallen = acceptlen;

	memset(&bindaddr, 0, sizeof(bindaddr));
	bindaddr.sun_family = AF_UNIX;
	strncpy(bindaddr.sun_path, argv[optind], sizeof(bindaddr.sun_path));

	unlink(argv[optind]);
	if ((listenfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
		die("socket");
	if (bind(listenfd, (struct sockaddr *)&bindaddr, sizeof(bindaddr)))
		die("bind");
	if (listen(listenfd, 1))
		die("listen");

	realsize = PATH_OFFSET + reallen;
	if (!(peeraddrp = malloc(realsize)))
		die("malloc");
	for (;;) {
		memset(peeraddrp, 0, PATH_OFFSET);
		memset(peeraddrp->sun_path, '=', reallen);
		peeraddrp->sun_family = AF_UNIX;
		acceptsize = PATH_OFFSET + acceptlen;
		printf("Calling accept() with addrlen=%ld "
		       "(sun_path size %ld)...\n",
		       (long)acceptsize, (long)(acceptsize - PATH_OFFSET));
		if ((connfd=accept(listenfd, (struct sockaddr *)peeraddrp,
				     &acceptsize)) < 0)
			die("accept");
		peerlen = acceptsize - PATH_OFFSET;

		printf("Received addrlen=%ld (sun_path size %ld)\n",
		       (long)acceptsize, peerlen);
		for (i = 0; i < reallen; i++)
			if (peeraddrp->sun_path[i] == 0)
				peeraddrp->sun_path[i] = '@';
		printf("%.*s\n", (int)(peerlen <= acceptlen ?
				       peerlen : acceptlen),
		       peeraddrp->sun_path);
		printf("%.*s\n", (int)reallen, peeraddrp->sun_path);
		close(connfd);
	}
}

