/*-
 * Copyright (c)1996-2005 by Hartmut Brandt
 * 	All rights reserved.
 *
 * Author: Harti Brandt <harti@freebsd.org>
 * 
 * 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 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 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.
 *
 * $Begemot: libbegemot/frame.c,v 1.13 2005/06/01 07:50:40 brandt_h Exp $
 */
# include <stdio.h>
# include <stdlib.h>
# include <stdarg.h>
# include <unistd.h>
# include <sys/types.h>
# include <sys/socket.h>
# include <sys/uio.h>
# ifdef HAVE_STREAMS
#  include <stropts.h>
# endif
# include <netinet/in.h>

# include "begemot.h"

# define MAXIOVEC 100

# ifndef HAVE_OLDMSGHDR
struct passfd {
	struct cmsghdr cmsg;
	int fd;
};
# endif

struct hdr {
	u_int bytes;
	int fd;
};

int
frame_write(int fd, void *hdr, u_int hdr_len, void *arg, ...)
{
	struct iovec iov[MAXIOVEC];
	struct hdr *h;
	u_int iovlen;
	va_list ap;
	u_int len;
	int ret;

	iovlen = 0;
	h = (struct hdr *)hdr;

	iov[iovlen].iov_base = (caddr_t)hdr;
	iov[iovlen++].iov_len = hdr_len;
	h->bytes = 0;

	va_start(ap, arg);
	while(arg != NULL) {
		len = va_arg(ap, u_int);
		if(len != 0) {
			iov[iovlen].iov_base = (caddr_t)arg;
			iov[iovlen++].iov_len = len;
			h->bytes += len;
		}
		arg = va_arg(ap, void *);
	}
	va_end(ap);

	h->bytes = htonl(h->bytes);
	ret = writev(fd, iov, iovlen);
	h->bytes = ntohl(h->bytes);
	return ret;
}

int
frame_writev(int fd, void *hdr, u_int hdr_len, struct iovec *v, u_int vlen)
{
	struct iovec iov[MAXIOVEC];
	struct hdr *h;
	u_int i, iovlen;
	int ret;

	iovlen = 0;
	h = (struct hdr *)hdr;

	iov[iovlen].iov_base = (caddr_t)hdr;
	iov[iovlen++].iov_len = hdr_len;
	h->bytes = 0;

	for(i = 0; i < vlen; i++) {
		if(v[i].iov_len != 0) {
			h->bytes += v[i].iov_len;
			iov[iovlen++] = v[i];
		}
	}

	h->bytes = htonl(h->bytes);
	ret = writev(fd, iov, iovlen);
	h->bytes = ntohl(h->bytes);
	return ret;
}


# ifdef SEND_FD_BUG
/*
 * The Solaris 2.5 (and maybe earlier) socket emulation has a bug:
 * If you send one piece of data and a file descriptor. You can't receive
 * the data: the receiver must expect the data. So we send first the header
 * so that the receiver knows whether to wait for a file descriptor or not
 * and then the extensions together with the file descriptor.
 */
int
framefd_write(int fd, void *hdr, u_int hdr_len, void *arg, ...)
{
	struct msghdr msg;
	struct iovec iov[MAXIOVEC+1];
	va_list ap;
	u_int len;
	struct hdr *h;
	int sendfd, ret, ret1;
	u_int iovlen;

	h = (struct hdr *)hdr;
	sendfd = h->fd;

	iovlen = 0;
	iov[iovlen].iov_base = (caddr_t)hdr;
	iov[iovlen++].iov_len = sizeof(h->bytes);
	iov[iovlen].iov_base = (caddr_t)((char *)hdr + sizeof(h->bytes));
	iov[iovlen++].iov_len = hdr_len - sizeof(h->bytes);
	h->bytes = 0;
	h->fd = (sendfd >= 0);

	va_start(ap, arg);
	while(arg != NULL) {
		len = va_arg(ap, u_int);
		if(len != 0) {
			iov[iovlen].iov_base = (caddr_t)arg;
			iov[iovlen++].iov_len = len;
			h->bytes += len;
		}
		arg = va_arg(ap, void *);
	}
	va_end(ap);

	h->bytes = htonl(h->bytes);

	/* send the 4 byte length only */
	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	msg.msg_iov = iov;
	msg.msg_iovlen = 1;
	msg.msg_accrights = NULL;
	msg.msg_accrightslen = 0;

	ret = sendmsg(fd, &msg, 0);
	h->bytes = ntohl(h->bytes);
	if(ret != iov[0].iov_len)
		return ret;

	if(h->fd >= 0) {
		sendfd = h->fd;
		msg.msg_accrights = (caddr_t)&sendfd;
		msg.msg_accrightslen = sizeof(sendfd);
	} else {
		msg.msg_accrights = NULL;
		msg.msg_accrightslen = 0;
	}

	msg.msg_iov = iov + 1;
	msg.msg_iovlen = iovlen - 1;

	if((ret1 = sendmsg(fd, &msg, 0)) < 0)
		return ret1;

	return ret + ret1;
}

int
framefd_writev(int fd, void *hdr, u_int hdr_len, struct iovec *v, u_int vlen)
{
	struct msghdr msg;
	struct iovec iov[MAXIOVEC+1];
	struct hdr *h;
	int sendfd, ret, ret1;
	u_int iovlen, i;

	h = (struct hdr *)hdr;
	sendfd = h->fd;

	iovlen = 0;
	iov[iovlen].iov_base = (caddr_t)hdr;
	iov[iovlen++].iov_len = sizeof(h->bytes);
	iov[iovlen].iov_base = (caddr_t)((char *)hdr + sizeof(h->bytes));
	iov[iovlen++].iov_len = hdr_len - sizeof(h->bytes);
	h->bytes = 0;
	h->fd = (sendfd >= 0);

	for(i = 0; i < vlen; i++) {
		if(v[i].iov_len != 0) {
			h->bytes += v[i].iov_len;
			iov[iovlen++] = v[i];
		}
	}

	h->bytes = htonl(h->bytes);

	/* send the 4 byte length only */
	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	msg.msg_iov = iov;
	msg.msg_iovlen = 1;
	msg.msg_accrights = NULL;
	msg.msg_accrightslen = 0;

	ret = sendmsg(fd, &msg, 0);
	h->bytes = ntohl(h->bytes);
	if(ret != iov[0].iov_len)
		return ret;

	if(h->fd >= 0) {
		sendfd = h->fd;
		msg.msg_accrights = (caddr_t)&sendfd;
		msg.msg_accrightslen = sizeof(sendfd);
	} else {
		msg.msg_accrights = NULL;
		msg.msg_accrightslen = 0;
	}

	msg.msg_iov = iov + 1;
	msg.msg_iovlen = iovlen - 1;

	if((ret1 = sendmsg(fd, &msg, 0)) < 0)
		return ret1;

	return ret + ret1;
}

# else
int
framefd_write(int fd, void *hdr, u_int hdr_len, void *arg, ...)
{
	struct msghdr msg;
	struct iovec iov[MAXIOVEC];
	va_list ap;
	u_int len;
	int ret;
	struct hdr *h;
# ifndef HAVE_OLDMSGHDR
	struct passfd passfd;
# else
	int sendfd;
# endif

	h = (struct hdr *)hdr;

	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	msg.msg_iov = iov;
	msg.msg_iovlen = 0;

	if(h->fd >= 0) {
# ifdef HAVE_OLDMSGHDR
		sendfd = h->fd;
		msg.msg_accrights = (caddr_t)&sendfd;
		msg.msg_accrightslen = sizeof(sendfd);
# else
		passfd.fd = h->fd;
		passfd.cmsg.cmsg_len = sizeof(passfd);
		passfd.cmsg.cmsg_level = SOL_SOCKET;
		passfd.cmsg.cmsg_type = SCM_RIGHTS;
		msg.msg_control = (caddr_t)&passfd;
		msg.msg_controllen = sizeof(passfd);
# endif
	} else {
# ifdef HAVE_OLDMSGHDR
		msg.msg_accrights = NULL;
		msg.msg_accrightslen = 0;
# else
		msg.msg_control = NULL;
		msg.msg_controllen = 0;
# endif
	}

	iov[msg.msg_iovlen].iov_base = (caddr_t)hdr;
	iov[msg.msg_iovlen++].iov_len = hdr_len;
	h->bytes = 0;
# ifdef HAVE_OLDMSGHDR
	h->fd = (sendfd >= 0);
# else
	h->fd = (passfd.fd >= 0);
# endif

	va_start(ap, arg);
	while(arg != NULL) {
		len = va_arg(ap, u_int);
		if(len != 0) {
			iov[msg.msg_iovlen].iov_base = (caddr_t)arg;
			iov[msg.msg_iovlen++].iov_len = len;
			h->bytes += len;
		}
		arg = va_arg(ap, void *);
	}
	va_end(ap);

	h->bytes = htonl(h->bytes);
	ret = sendmsg(fd, &msg, 0);
	h->bytes = ntohl(h->bytes);
	return ret;
}

int
framefd_writev(int fd, void *hdr, u_int hdr_len, struct iovec *v, u_int vlen)
{
	struct msghdr msg;
	struct iovec iov[MAXIOVEC];
	u_int i;
	int ret;
	struct hdr *h;
# ifndef HAVE_OLDMSGHDR
	struct passfd passfd;
# else
	int sendfd;
# endif

	h = (struct hdr *)hdr;

	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	msg.msg_iov = iov;
	msg.msg_iovlen = 0;

	if(h->fd >= 0) {
# ifdef HAVE_OLDMSGHDR
		sendfd = h->fd;
		msg.msg_accrights = (caddr_t)&sendfd;
		msg.msg_accrightslen = sizeof(sendfd);
# else
		passfd.fd = h->fd;
		passfd.cmsg.cmsg_len = sizeof(passfd);
		passfd.cmsg.cmsg_level = SOL_SOCKET;
		passfd.cmsg.cmsg_type = SCM_RIGHTS;
		msg.msg_control = (caddr_t)&passfd;
		msg.msg_controllen = sizeof(passfd);
# endif
	} else {
# ifdef HAVE_OLDMSGHDR
		msg.msg_accrights = NULL;
		msg.msg_accrightslen = 0;
# else
		msg.msg_control = NULL;
		msg.msg_controllen = 0;
# endif
	}

	iov[msg.msg_iovlen].iov_base = (caddr_t)hdr;
	iov[msg.msg_iovlen++].iov_len = hdr_len;
	h->bytes = 0;
# ifdef HAVE_OLDMSGHDR
	h->fd = (sendfd >= 0);
# else
	h->fd = (passfd.fd >= 0);
# endif

	for(i = 0; i < vlen; i++) {
		if(v[i].iov_len != 0) {
			h->bytes += v[i].iov_len;
			iov[msg.msg_iovlen++] = v[i];
		}
	}

	h->bytes = htonl(h->bytes);
	ret = sendmsg(fd, &msg, 0);
	h->bytes = ntohl(h->bytes);
	return ret;
}
# endif


int
frame_read(int fd, void *hdr, u_int hdr_len, void **parg, u_int *plen)
{
	struct iovec iov[1];
	struct hdr *h;
	u_int iovlen;
	int n, n1;

	iovlen = 0;
	h = (struct hdr *)hdr;

	iov[iovlen].iov_base = (caddr_t)hdr;
	iov[iovlen++].iov_len = hdr_len;

	if((n = readv(fd, iov, iovlen)) < 0 || (u_int)n < hdr_len)
		return n;

	h->bytes = ntohl(h->bytes);

	if(h->bytes == 0)
		return n;

	if(h->bytes > *plen) {
		*parg = xrealloc(*parg, h->bytes);
		*plen = h->bytes;
	}

	iovlen = 0;

	iov[iovlen].iov_base = (caddr_t)*parg;
	iov[iovlen++].iov_len = h->bytes;

	if((n1 = readv(fd, iov, iovlen)) <= 0)
		return n1;

	*plen = n1;

	return n + n1;
}

# ifdef SEND_FD_BUG
int
framefd_read(int fd, void *hdr, u_int hdr_len, void **parg, u_int *plen)
{
	struct msghdr msg;
	struct iovec iov[2];
	struct hdr *h;
	int n, n1;
	int recvfd;

	h = (struct hdr *)hdr;

	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	msg.msg_iov = iov;
	msg.msg_iovlen = 1;
	msg.msg_accrights = NULL;
	msg.msg_accrightslen = 0;

	iov[0].iov_base = (caddr_t)hdr;
	iov[0].iov_len = sizeof(h->bytes);

	if((n = recvmsg(fd, &msg, 0)) != sizeof(h->bytes))
		return n;

	h->bytes = ntohl(h->bytes);

	iov[0].iov_base = (caddr_t)((char *)hdr + sizeof(h->bytes));
	iov[0].iov_len = hdr_len - sizeof(h->bytes);

	if(h->bytes > *plen) {
		*parg = xrealloc(*parg, h->bytes);
		*plen = h->bytes;
	}

	if(h->bytes) {
		iov[1].iov_base = (caddr_t)*parg;
		iov[1].iov_len = h->bytes;
	}

	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	msg.msg_iov = iov;
	msg.msg_iovlen = (h->bytes ? 2 : 1);
	msg.msg_accrights = (caddr_t)&recvfd;
	msg.msg_accrightslen = sizeof(recvfd);
	recvfd = -1;

	if((n1 = recvmsg(fd, &msg, 0)) <= 0)
		return n1;

	if((u_int)n >= sizeof(struct hdr) - sizeof(h->bytes)) {
		if(h->fd) {
# ifdef HAVE_OLDMSGHDR
			if(recvfd == -1)
				h->fd = -2;
			else
				h->fd = recvfd;
# else
			if(passfd.fd == -1)
				h->fd = -2;
			else
				h->fd = passfd.fd;
# endif
		} else {
# ifdef HAVE_OLDMSGHDR
			if(recvfd >= 0)
				(void)close(recvfd);
# else
			if(passfd.fd >= 0)
				(void)close(passfd.fd);
# endif
			h->fd = -1;
		}
	}

	return n + n1;
}

# else

int
framefd_read(int fd, void *hdr, u_int hdr_len, void **parg, u_int *plen)
{
	struct msghdr msg;
	struct iovec iov[1];
	struct hdr *h;
	int n, n1;
# ifndef HAVE_OLDMSGHDR
	struct passfd passfd;
# else
	int recvfd;
# endif

	h = (struct hdr *)hdr;

	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	msg.msg_iov = iov;
	msg.msg_iovlen = 1;

# ifdef HAVE_OLDMSGHDR
	msg.msg_accrights = (caddr_t)&recvfd;
	msg.msg_accrightslen = sizeof(recvfd);
	recvfd = -1;
# else
	msg.msg_control = (caddr_t)&passfd;
	msg.msg_controllen = sizeof(passfd);

	passfd.cmsg.cmsg_len = sizeof(passfd);
	passfd.cmsg.cmsg_level = SOL_SOCKET;
	passfd.cmsg.cmsg_type = SCM_RIGHTS;
	passfd.fd = -1;
# endif

	iov[0].iov_base = (caddr_t)hdr;
	iov[0].iov_len = hdr_len;

	if((n = recvmsg(fd, &msg, 0)) < 0 || (u_int)n < sizeof(struct hdr))
		return n;

	h->bytes = ntohl(h->bytes);

	if((u_int)n >= sizeof(struct hdr)) {
		if(h->fd) {
# ifdef HAVE_OLDMSGHDR
			if(recvfd == -1)
				h->fd = -2;
			else
				h->fd = recvfd;
# else
			if(passfd.fd == -1)
				h->fd = -2;
			else
				h->fd = passfd.fd;
# endif
		} else {
# ifdef HAVE_OLDMSGHDR
			if(recvfd >= 0)
				(void)close(recvfd);
# else
			if(passfd.fd >= 0)
				(void)close(passfd.fd);
# endif
			h->fd = -1;
		}
	}

	if((u_int)n < hdr_len)
		return n;

	if(h->bytes == 0)
		return n;

	if(h->bytes > *plen) {
		*parg = xrealloc(*parg, h->bytes);
		*plen = h->bytes;
	}

	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	msg.msg_iov = iov;
	msg.msg_iovlen = 1;
# ifdef HAVE_OLDMSGHDR
	msg.msg_accrights = NULL;
	msg.msg_accrightslen = 0;
# else
	msg.msg_control = NULL;
	msg.msg_controllen = 0;
# endif

	iov[0].iov_base = (caddr_t)*parg;
	iov[0].iov_len = h->bytes;

	if((n1 = recvmsg(fd, &msg, 0)) <= 0)
		return n1;

	*plen = n1;

	return n + n1;
}
#endif


# ifdef TEST

# include <stdio.h>
# include <string.h>
# include <errno.h>
# include <fcntl.h>
# include <sys/stat.h>

int process(int argc, char *argv[]);

int
main(int argc, char *argv[])
{
	int i, p[2];
	pid_t pid;

	if(strcmp(argv[0], "frame") == 0) {
		/* sender */

		for(i = 1; i < argc; i++)
			if(strcmp(argv[i], "-") == 0)
				break;

		if(i == argc)
			panic("missing arg delimiter");

		if(socketpair(PF_UNIX, SOCK_STREAM, 0, p) == -1)
			panic("socketpair: %s", strerror(errno));

# ifdef HAVE_STREAMS
		ioctl(p[0], I_SRDOPT, RNORM);
		ioctl(p[1], I_SRDOPT, RNORM);
# endif

		if((pid = fork()) < 0)
			panic("fork: %s", strerror(errno));

		if(pid == 0) {
			/* child */
			(void)close(p[1]);
			if(dup2(p[0], 0) == -1)
				panic("dup2: %s", strerror(errno));
			(void)close(p[0]);

			argv[i] = "-frame";
			argc -= i;
			argv += i;

			execv("frame", argv);

			_exit(127);
		}
		(void)close(p[0]);
		if(dup2(p[1], 1) == -1)
			panic("dup2: %s", strerror(errno));

		argv[i] = NULL;
		argc = i;

		return process(argc-1, argv+1);
	} else {
		/* receiver */

		return process(argc-1, argv+1);
	}
}

int
process(int argc, char *argv[])
{
	struct {
		struct hdr h;
		u_int spare[3];
	} hdr;
	u_int data[1000];
	int n, i;
	u_int extlen;
	void *ext;
	struct stat statb;

	for(i = 0; i < argc; i++) {
		hdr.h.fd = 0;
		ext = data;
		extlen = sizeof(data);

		if(strcmp(argv[i], "s") == 0) {
			n = frame_write(1, &hdr, sizeof(hdr),
				data, sizeof(data), 0);

			if(n <= 0)
				panic("write error: %s", strerror(errno));
			if((u_int)n != hdr.h.bytes + sizeof(hdr))
				panic("bad write: %d, %d", n, hdr.h.bytes +
					sizeof(hdr));

		} else if(strcmp(argv[i], "sf") == 0) {
			hdr.h.fd = open("/dev/null", O_RDONLY);
			n = framefd_write(1, &hdr, sizeof(hdr),
				data, sizeof(data), 0);

			if(n <= 0)
				panic("write error: %s", strerror(errno));
			if((u_int)n != hdr.h.bytes + sizeof(hdr))
				panic("bad write: %d, %d", n, hdr.h.bytes +
					sizeof(hdr));

		} else if(strcmp(argv[i], "s0") == 0) {
			hdr.h.fd = -1;
			n = framefd_write(1, &hdr, sizeof(hdr),
				data, sizeof(data), 0);

			if(n <= 0)
				panic("write error: %s", strerror(errno));
			if((u_int)n != hdr.h.bytes + sizeof(hdr))
				panic("bad write: %d, %d", n, hdr.h.bytes +
					sizeof(hdr));

		} else if(strcmp(argv[i], "r") == 0) {
			n = frame_read(0, &hdr, sizeof(hdr),
				&ext, &extlen);

			if(n <= 0)
				panic("read error: %s", strerror(errno));
			if((u_int)n < sizeof(hdr))
				panic("bad read: %d, %d", n, sizeof(hdr));
			if((u_int)n != hdr.h.bytes + sizeof(hdr))
				panic("bad read: %d, %d", n, sizeof(hdr) +
					hdr.h.bytes);

			printf("read: %u bytes\n", hdr.h.bytes);

		} else if(strcmp(argv[i], "rf") == 0) {
			n = framefd_read(0, &hdr, sizeof(hdr),
				&ext, &extlen);

			if(n <= 0)
				panic("read error: %s", strerror(errno));
			if((u_int)n < sizeof(hdr))
				panic("bad read: %d, %d", n, sizeof(hdr));
			if((u_int)n != hdr.h.bytes + sizeof(hdr))
				panic("bad read: %d, %d", n, sizeof(hdr) +
					hdr.h.bytes);

			printf("read: %u bytes\n", hdr.h.bytes);
			printf("fd:   %d\n", hdr.h.fd);

			if(hdr.h.fd >= 0) {
				if(fstat(hdr.h.fd, &statb))
					panic("fstat: %s", strerror(errno));
				prstat(stdout, &statb);
			}

		} else
			panic("bad key '%s'", argv[i]);
	}

	sleep(2);

	return 0;
}
# endif


syntax highlighted by Code2HTML, v. 0.9.1