/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*           Copyright (c) 1990-2006 AT&T Knowledge Ventures            *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                      by AT&T Knowledge Ventures                      *
*                                                                      *
*                A copy of the License is available at                 *
*            http://www.opensource.org/licenses/cpl1.0.txt             *
*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                                                                      *
***********************************************************************/
#pragma prototyped

/*
 * syscall message user side call and return
 *
 * NOTE: errno and the st_mode bits are not yet canonical
 */

#include "msglib.h"

#include <cs.h>

static char	msg_buf[MSG_SIZE_BUF];

/*
 * ap version of msgcall()
 * if xp!=0 then it replaces the first MSG_ARG_file or MSG_ARG_string args
 * message size is returned
 */

ssize_t
msgvcall(int fd, unsigned long channel, unsigned long call, Msg_return_t* ret, void** xp, va_list ap)
{
	register long		at;
	register long		n;
	register char*		p;
	unsigned long		rtime;
	int			i;
	int			nv;
	ssize_t			r;
	Msg_file_t*		ip;
	char*			b;
	char*			e;
	char**			vp;
	void**			oxp;
	int*			zp;
	long*			np;
	struct dirent*		dp;
	struct dirent*		de;
	struct statvfs*		fp;
	struct stat*		sp;
	Cspoll_t		ack;
	va_list			oap;

	static struct
	{
	unsigned long		addr;
	unsigned long		port;
	short			fd;
	}			msg_ack;
	static unsigned long	msg_stamp;
	static int		msg_write;

	oxp = xp;
	va_copy(oap, ap);
	if (MSG_CALL(call) == MSG_write && !msg_write && (MSG_PURE(call) == MSG_PURE(MSG_write) || MSG_PURE(call) == (MSG_write|MSG_EXT(MSG_ARG_number, 4))))
	{
		size_t		c;
		ssize_t		z;
		ssize_t		t;
		off_t		o;

		/*
		 * long MSG_write messages may be split up
		 */

		msg_write = 1;
		ip = xp ? (Msg_file_t*)(*xp) : va_arg(ap, Msg_file_t*);
		b = va_arg(ap, char*);
		c = va_arg(ap, size_t);
		o = MSG_ARG(call, 4) == MSG_ARG_number ? va_arg(ap, off_t) : (off_t)0;
		r = 0;
		while (c > 0)
		{
			z = c > MSG_SIZE_IO ? MSG_SIZE_IO : c;
			if ((t = msgcall(fd, channel, call, ret, ip, b, z, o)) == -1)
			{
				r = -1;
				break;
			}
			c -= z;
			b += z;
			o += z;
			r += t;
		}
		msg_write = 0;
		return r;
	}
	b = msg_buf + MSG_SIZE_SIZE;
	e = msg_buf + sizeof(msg_buf);
	if (ret) call |= MSG_VALUE;
	msgputu(&b, e, MSG_VERSION);
	msgputu(&b, e, channel);
	msgputu(&b, e, call);
	msgputu(&b, e, msg_info.timestamp ? (msg_stamp = CSTIME()) : ++msg_stamp);
	if (call & MSG_ACK)
	{
		if (msg_ack.fd < 0)
			return -1;
		if (!msg_ack.fd)
		{
			if ((msg_ack.fd = csbind(&cs, "udp", 0, CS_PORT_NORMAL, 0L)) < 0)
				return -1;
			fcntl(msg_ack.fd, F_SETFD, FD_CLOEXEC);
			msgreserve(&msg_ack.fd);
			msg_ack.addr = cs.addr;
			msg_ack.port = cs.port;
		}
		msgputu(&b, e, msg_ack.addr);
		msgputu(&b, e, msg_ack.port);
	}
	nv = 0;
	at = call >> MSG_ARG_CALL;
	if (call & MSG_VALUE) switch (at & ((1 << MSG_ARG_TYPE) - 1))
	{
	case 0:
		break;
	case MSG_ARG_file:
		if (ret)
		{
			ip = &ret->file;
			for (n = 0; n < sizeof(*ip) / sizeof(ip->fid[0]); n++)
				msgputu(&b, e, ip->fid[n]);
		}
		else for (n = 0; n < sizeof(*ip) / sizeof(ip->fid[0]); n++)
			msgputu(&b, e, 0);
		break;
	default:
		msgputu(&b, e, ret ? ret->number : 0);
		break;
	}
	for (;;)
	{
		switch ((at >>= MSG_ARG_TYPE) & ((1 << MSG_ARG_TYPE) - 1))
		{
		case MSG_ARG_array:
			n = va_arg(ap, int);
			msgputu(&b, e, n);
			np = va_arg(ap, long*);
			while (n-- > 0)
				msgputu(&b, e, *np++);
			continue;
		case MSG_ARG_file:
			ip = (xp && *xp) ? (Msg_file_t*)(*xp++) : va_arg(ap, Msg_file_t*);
			for (n = 0; n < sizeof(*ip) / sizeof(ip->fid[0]); n++)
				msgputu(&b, e, ip->fid[n]);
			continue;
		case MSG_ARG_input:
			p = va_arg(ap, char*);
			n = va_arg(ap, long);
			msgputz(&b, e, p, n);
			at >>= MSG_ARG_TYPE;
			continue;
		case MSG_ARG_number:
			n = va_arg(ap, long);
			msgputu(&b, e, n);
			continue;
		case MSG_ARG_output:
			if (call & MSG_VALUE) switch (MSG_CALL(call))
			{
			case MSG_CALL(MSG_getdents):
				dp = va_arg(ap, struct dirent*);
				n = va_arg(ap, size_t);
				if (ret) n = ret->number;
				at >>= MSG_ARG_TYPE;
				de = (struct dirent*)((char*)dp + n);
				while (dp < de)
				{
					i = D_NAMLEN(dp);
					msgputz(&b, e, dp->d_name, i + 1);
					msgputu(&b, e, D_FILENO(dp));
#if _mem_d_reclen_dirent
					i = dp->d_reclen;
#else
					i = D_RECSIZ(dp, i);
#endif
					dp = (struct dirent*)((char*)dp + i);
				}
				msgputu(&b, e, 0);
				msgputu(&b, e, n);
				break;
			case MSG_CALL(MSG_pipe):
				ip = (xp && *xp) ? (Msg_file_t*)(*xp++) : va_arg(ap, Msg_file_t*);
				for (i = 0; i < 2; i++)
				{
					for (n = 0; n < sizeof(*ip) / sizeof(ip->fid[0]); n++)
						msgputu(&b, e, ip->fid[n]);
					ip++;
				}
				break;
			case MSG_CALL(MSG_stat):
				sp = va_arg(ap, struct stat*);
				msgputu(&b, e, sp->st_dev);
				msgputu(&b, e, sp->st_ino);
				msgputu(&b, e, sp->st_mode);
				msgputu(&b, e, sp->st_nlink);
				msgputu(&b, e, sp->st_uid);
				msgputu(&b, e, sp->st_gid);
				msgputu(&b, e, sp->st_size);
				msgputu(&b, e, sp->st_atime);
				msgputu(&b, e, sp->st_mtime);
				msgputu(&b, e, sp->st_ctime);
#if _mem_st_blksize_stat
				msgputu(&b, e, sp->st_blksize);
#else
				msgputu(&b, e, 1024);
#endif
#if _mem_st_blocks_stat
				msgputu(&b, e, sp->st_blocks);
#else
				msgputu(&b, e, sp->st_size ? ((sp->st_size - 1) / 1024 + 1) : 0);
#endif
				break;
			case MSG_CALL(MSG_statfs):
				fp = va_arg(ap, struct statvfs*);
				msgputu(&b, e, fp->f_bsize);
				msgputu(&b, e, fp->f_frsize);
				msgputu(&b, e, fp->f_blocks);
				msgputu(&b, e, fp->f_bfree);
				msgputu(&b, e, fp->f_bavail);
				msgputu(&b, e, fp->f_files);
				msgputu(&b, e, fp->f_ffree);
				msgputu(&b, e, fp->f_favail);
#if _mem_f_fsid_statvfs
				msgputu(&b, e, fp->f_fsid);
#else
				msgputu(&b, e, 0);
#endif
#if _mem_f_basetype_statvfs
				msgputz(&b, e, fp->f_basetype, strlen(fp->f_basetype) + 1);
#else
				msgputz(&b, e, "ufs", 4);
#endif
				msgputu(&b, e, fp->f_flag);
				msgputu(&b, e, fp->f_namemax);
#if _mem_f_fstr_statvfs
				msgputz(&b, e, fp->f_fstr, strlen(fp->f_fstr) + 1);
#endif
				break;
			default:
				p = va_arg(ap, char*);
				if (((at >>= MSG_ARG_TYPE) & ((1 << MSG_ARG_TYPE) - 1)) == MSG_ARG_output)
					n = (zp = va_arg(ap, int*)) ? *zp : 0;
				else n = va_arg(ap, size_t);
				msgputz(&b, e, p, n);
				break;
			}
			else va_arg(ap, char*);
			continue;
		case MSG_ARG_string:
			p = (xp && *xp) ? (char*)(*xp++) : va_arg(ap, char*);
			if (p) msgputz(&b, e, p, strlen(p) + 1);
			else msgputu(&b, e, 0);
			continue;
		case MSG_ARG_vector:
			if (vp = va_arg(ap, char**))
			{
				int		m;
				static char*	coexport;

				if (nv++ && !coexport && !(coexport = getenv("COEXPORT")))
					coexport = "";
				n = 0;
				while ((p = *vp++) && (n += (m = strlen(p)) + 4) < MSG_SIZE_IO / 2)
				{
					if (nv > 1)
					{
						char*	tx;
						char*	tp;

						tx = coexport;
					again:
						tp = p;
						while (*tp == *tx && *tx)
						{
							tp++;
							tx++;
						}
						if (*tp != '=' || *tx != ':' && *tx)
						{
							while (*tx)
								if (*tx++ == ':')
									goto again;
							continue;
						}
					}
					msgputz(&b, e, p, m + 1);
				}
			}
			msgputz(&b, e, NiL, 0);
			continue;
		}
		break;
	}
	n = b - msg_buf;
	msgsetsize(msg_buf, n);
	if (cswrite(&cs, fd, msg_buf, n) != n)
	{
		errno = EMSGIO;
		return -1;
	}
	r = n;
	if (call & MSG_FLUSH)
	{
		msgsetsize(msg_buf, 0);
		if (cswrite(&cs, fd, msg_buf, MSG_SIZE_SIZE) != MSG_SIZE_SIZE)
		{
			errno = EMSGIO;
			return -1;
		}
	}
	if (call & MSG_ACK)
	{
		ack.fd = msg_ack.fd;
		ack.events = CS_POLL_READ;
		for (;;)
		{
			switch (cspoll(&cs, &ack, 1, msg_info.timeout))
			{
			case -1:
				if (errno == EINTR) continue;
				/*FALLTHROUGH*/
			case 0:
				r = -1;
				errno = ENXIO;
				break;
			default:

				if ((rtime = msgvreturn(ack.fd, call, oxp, oap)) == -1)
				{
					r = -1;
					errno = ENXIO;
					break;
				}
				if (rtime == msg_stamp) break;
				if (rtime == ~msg_stamp)
				{
					r = -1;
					errno = EIO;
				}
				break;
			}
			break;
		}
	}
	else if (call & MSG_RETURN)
		r = msgvreturn(fd, call, oxp, oap);
	return r;
}

/*
 * construct a call and send it to fd
 * message size is returned
 */

ssize_t
msgcall(int fd, unsigned long channel, unsigned long call, Msg_return_t* ret, ...)
{
	ssize_t	n;
	va_list	ap;

	va_start(ap, ret);
	n = msgvcall(fd, channel, call, ret, NiL, ap);
	va_end(ap);
	return n;
}

/*
 * ap version of msgreturn
 * if xp!=0 then it replaces the first MSG_ARG_file or MSG_ARG_string args
 */

long
msgvreturn(int fd, unsigned long call, void** xp, va_list ap)
{
	register long		n;
	register unsigned long	at;
	int			i;
	long			r;
	char*			b;
	char*			e;
	char*			p;
	Msg_file_t*		ip;
	struct stat*		sp;
	struct dirent*		dp;
	struct statvfs*		vp;

	if ((n = msgread(fd, msg_buf, sizeof(msg_buf))) < 0)
	{
		errno = EMSGIO;
		return -1;
	}
	b = msg_buf + MSG_SIZE_SIZE;
	e = msg_buf + n;
	if (msggetu(&b, e) != call)
	{
		errno = EMSGIO;
		return -1;
	}
	if ((r = msggetu(&b, e)) == -1)
	{
		errno = msggetu(&b, e);
		return -1;
	}
	if (call & MSG_ACK)
		return r;
	at = call >> (MSG_ARG_CALL - MSG_ARG_TYPE);
	for (;;)
	{
		switch ((at >>= MSG_ARG_TYPE) & ((1 << MSG_ARG_TYPE) - 1))
		{
		case MSG_ARG_array:
			va_arg(ap, int);
			va_arg(ap, long*);
			continue;
		case MSG_ARG_file:
			if (xp && *xp) xp++;
			else va_arg(ap, Msg_file_t*);
			continue;
		case MSG_ARG_input:
			va_arg(ap, char*);
			continue;
		case MSG_ARG_number:
			va_arg(ap, long);
			continue;
		case MSG_ARG_output:
			switch (MSG_CALL(call))
			{
			case MSG_CALL(MSG_getdents):
				dp = va_arg(ap, struct dirent*);
				while (n = msggetz(&b, e, dp->d_name, sizeof(dp->d_name)))
				{
					n--;
#if _mem_d_namlen_dirent
					dp->d_namlen = n;
#endif
#if _mem_d_fileno_dirent
					dp->d_fileno =
#endif
					msggetu(&b, e);
#if _mem_d_off_dirent
					dp->d_off = 0;
#endif
					n = D_RECSIZ(dp, n);
#if _mem_d_reclen_dirent
					dp->d_reclen = n;
#endif
					dp = (struct dirent*)((char*)dp + n);
				}
				break;
			case MSG_CALL(MSG_pipe):
				ip = va_arg(ap, Msg_file_t*);
				for (i = 0; i < 2; i++)
				{
					for (n = 0; n < sizeof(*ip) / sizeof(ip->fid[0]); n++)
						ip->fid[n] = msggetu(&b, e);
					ip++;
				}
				break;
			case MSG_CALL(MSG_stat):
				sp = va_arg(ap, struct stat*);
				sp->st_dev = msggetu(&b, e);
				sp->st_ino = msggetu(&b, e);
				sp->st_mode = msggetu(&b, e);
				sp->st_nlink = msggetu(&b, e);
				sp->st_uid = msggetu(&b, e);
				sp->st_gid = msggetu(&b, e);
				sp->st_size = msggetu(&b, e);
				sp->st_atime = msggetu(&b, e);
				sp->st_mtime = msggetu(&b, e);
				sp->st_ctime = msggetu(&b, e);
#if _mem_st_blksize_stat
				sp->st_blksize = msggetu(&b, e);
#else
				msggetu(&b, e);
#endif
#if _mem_st_blocks_stat
				sp->st_blocks = msggetu(&b, e);
#else
				msggetu(&b, e);
#endif
				break;
			case MSG_CALL(MSG_statfs):
				vp = va_arg(ap, struct statvfs*);
				vp->f_bsize = msggetu(&b, e);
				vp->f_frsize = msggetu(&b, e);
				vp->f_blocks = msggetu(&b, e);
				vp->f_bfree = msggetu(&b, e);
				vp->f_bavail = msggetu(&b, e);
				vp->f_files = msggetu(&b, e);
				vp->f_ffree = msggetu(&b, e);
				vp->f_favail = msggetu(&b, e);
#if _mem_f_fsid_statvfs
				vp->f_fsid =
#endif
					msggetu(&b, e);
#if _mem_f_basetype_statvfs
				msggetz(&b, e, vp->f_basetype, sizeof(vp->f_basetype));
#else
				msggetz(&b, e, NiL, 0);
#endif
				vp->f_flag = msggetu(&b, e);
				vp->f_namemax = msggetu(&b, e);
#if _mem_f_fstr_statvfs
				msggetz(&b, e, vp->f_fstr, sizeof(vp->f_fstr));
#endif
				break;
			default:
				p = va_arg(ap, char*);
				if (e - b >= r)
				{
					memcpy(p, b, r);
					b += r;
				}
				break;
			}
			continue;
		case MSG_ARG_string:
			if (xp && *xp) xp++;
			else va_arg(ap, char*);
			continue;
		case MSG_ARG_vector:
			va_arg(ap, char**);
			continue;
		}
		break;
	}
	return r;
}

/*
 * read a return from a previous call on fd
 */

long
msgreturn(int fd, unsigned long call, ...)
{
	long	n;
	va_list	ap;

	va_start(ap, call);
	n = msgvreturn(fd, call, NiL, ap);
	va_end(ap);
	return n;
}
