/*
 * $Id: port.c,v 2.0.1.5 1996/06/26 18:39:38 alexis Exp alexis $
 *
 * UPS Daemon
 * The Wild Wind Communications, 1995, 1996
 *
 * See file LICENSE for the distribution terms of this software.
 */

#include "upsd.h"

#include <errno.h>
#include <fcntl.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <termios.h>

/*
 * Opens upsp->port.device for exclusive use and places the file
 * descriptor back to the structure. The open is non-blocking.
 * Initial terminal settings are stored in upsp->port.otty to be
 * restored in closeport(). This function also allocates space
 * for the port read queue.
 *
 * The functon returns -1 upon error or 0 if succeeded.
 */
int
openport(void)
{
	if((upsp->port.fd = open(upsp->port.device, O_RDWR | O_NONBLOCK)) == -1) {
		syslog(LOG_ERR, "openport: cannot open port %s: %m",
		    upsp->port.device);
		return -1;
	}
	if(ioctl(upsp->port.fd, TIOCEXCL) == -1) {
		syslog(LOG_ERR, "openport: cannot lock port %s: %m",
		    upsp->port.device);
		return -1;
	}
	if(tcgetattr(upsp->port.fd, &(upsp->port.otty)) == -1) {
		syslog(LOG_ERR, "openport: cannot get %s settings: %m",
		    upsp->port.device);
		return -1;
	}
	if(tcsetattr(upsp->port.fd, TCSANOW, &(upsp->port.ntty)) == -1) {
		syslog(LOG_ERR, "openport: cannot set %s settings: %m",
		    upsp->port.device);
		return -1;
	}
	if((upsp->port.queue.base = upsp->port.queue.head = upsp->port.queue.tail
	    = xalloc(upsp->port.queue.size)) == NULL) {
		return -1;
	}
	if(ioctl(upsp->port.fd, TIOCSCTTY, 0) == -1) {
		syslog(LOG_ERR, "openport: cannot set ctty: %m");
		return -1;
	}
	if(fcntl(upsp->port.fd, F_SETOWN, getpid()) == -1) {
		syslog(LOG_ERR, "openport: cannot set owner for %s: %m",
		    upsp->port.device);
		return -1;
	}
	if(fcntl(upsp->port.fd, F_SETFL, O_NONBLOCK | O_ASYNC) == -1) {
		syslog(LOG_ERR, "openport: cannot enable async io: %m");
		return -1;
	}
	return 0;
}

/*
 * Unlocks the port, restores its original terminal settings and
 * closes the port.
 *
 * Returns -1 upon error, or 0 if succeeded.
 */
int
closeport(void)
{

	if(fcntl(upsp->port.fd, F_SETFL, O_NONBLOCK) == -1) {
		syslog(LOG_ERR, "closeport: cannot disable async io: %m");
		return -1;
	}
	if(tcsetattr(upsp->port.fd, TCSANOW, &(upsp->port.otty)) == -1) {
		syslog(LOG_ERR, "closeport: cannot set %s settings: %m",
		    upsp->port.device);
		return -1;
	}
	if(ioctl(upsp->port.fd, TIOCNXCL) == -1) {
		syslog(LOG_ERR, "closeport: cannot unlock %s: %m",
		    upsp->port.device);
		return -1;
	}
	if(close(upsp->port.fd) == -1) {
		syslog(LOG_ERR, "closeport: cannot close %s: %m",
		    upsp->port.device);
		return -1;
	}
	if(upsp->port.queue.base != NULL) {
		free(upsp->port.queue.base);
	}
	return 0;
}

/*
 * Writes size characters to the specified port, from the buffer
 * pointed with data.
 *
 * Returns -1 upon error, or number of actually written characters
 * otherwise.
 */
int
writeport(data, size)
	char *data;
	int size;
{
	register int s;
	register int i;
	struct timeval delay = upsp->port.writedelay;

	for(i = 0, s = 0; i < size; select(0, NULL, NULL, NULL, &delay)) {
		s = write(upsp->port.fd, data + i, ((size - i) < upsp->port.writeblksz) ?
		    size - i : upsp->port.writeblksz);
		if(s == -1) {
			syslog(LOG_ERR, "writeport: cannot write to %s: %m",
			    upsp->port.device);
			return -1;
		}
		i += s;
	}
	return i;
}

/*
 * Reads size characters from the queue.
 *
 * Returns -1 upon error or number of actually read characters
 * otherwise.
 */
int
readport(data, size, actual)
	char *data;
	int size;
	int actual;
{
	register size_t s;

	if(size == 0) {
		return 0;
	}

	/*
	 * The scheme of waiting for all the data to arrive is tight
	 * tied with signal handling. The main idea is that the long
	 * selecting will be interrupted when listenport() receives
	 * SIGIO. Well, if the select() won't be interrupted then
	 * we will sleep for port.timeout value independently of any I/O.
	 */
	if(actual) {
		while(queuelength() < size) {
			if(select(0, NULL, NULL, NULL, &upsp->port.timeout) == 0) {
				break;
			} else {
				if(errno != EINTR) {
					syslog(LOG_ERR, "readport: cannot sleep: %m");
					return -1;
				}
			}
		}
	}

	if(upsp->port.queue.head <= upsp->port.queue.tail) {
		if(upsp->port.queue.tail - upsp->port.queue.head > size) {
			s = size;
		} else {
			s = upsp->port.queue.tail - upsp->port.queue.head;
		}
		bcopy(upsp->port.queue.head, data, s);
		if(actual) {
			upsp->port.queue.head += s;
		}
		return s;
	} else {
		s = upsp->port.queue.base + upsp->port.queue.size - upsp->port.queue.head;
		if(s > size) {
			s = size;
			bcopy(upsp->port.queue.head, data, s);
			if(actual) {
				upsp->port.queue.head += s;
			}
			return s;
		} else {
			/* the first bcopy */
			bcopy(upsp->port.queue.head, data, s);
			data += s;

			/* the second bcopy */
			if(upsp->port.queue.tail - upsp->port.queue.base > size - s) {
				bcopy(upsp->port.queue.base, data, size - s);
				if(actual) {
					upsp->port.queue.head =
					    upsp->port.queue.base +
					    size - s;
				}
				return size;
			} else {
				bcopy(upsp->port.queue.base, data,
				    upsp->port.queue.tail - upsp->port.queue.base);
				if(actual) {
					upsp->port.queue.head = upsp->port.queue.tail;
				}
				return upsp->port.queue.tail - upsp->port.queue.base + s;
			}
		}
		/* NOTREACHED */
	}
	/* NOTREACHED */
	return 0;
}

/*
 * Listens and actually reads the port. Normally invoked as signal
 * handler caused by SIGIO.
 */
int
listenport(void)
{
	size_t s;
	void *t;

	switch((s = read(upsp->port.fd, upsp->port.queue.tail,
	    upsp->port.queue.base + upsp->port.queue.size - upsp->port.queue.tail))) {
	case 0:
		return 0;
		/* NOTREACHED */
	case -1:
		syslog(LOG_ERR, "listenport: cannot read %s: %m",
		    upsp->port.device);
		return -1;
		/* NOTREACHED */
	}

	t = upsp->port.queue.tail;

	if(upsp->port.queue.tail + s < upsp->port.queue.base + upsp->port.queue.size) {
		upsp->port.queue.tail += s;
	} else {
		upsp->port.queue.tail += s - upsp->port.queue.size;
	}

	if((t < upsp->port.queue.head && upsp->port.queue.tail >= upsp->port.queue.head) ||
	    (t > upsp->port.queue.head && upsp->port.queue.tail == upsp->port.queue.head)) {
		if(upsp->port.queue.tail + 1 < upsp->port.queue.base + upsp->port.queue.size) {
			upsp->port.queue.head = upsp->port.queue.tail + 1;
		} else {
			upsp->port.queue.head = upsp->port.queue.tail + 1 - upsp->port.queue.size;
		}
		syslog(LOG_WARNING, "listenport: overrun detected");
	}

	return 0;
}

/*
 * Flushes the port cleansing the queue.
 */
void
flushport(void)
{
	upsp->port.queue.head = upsp->port.queue.tail = upsp->port.queue.base;
	return;
}

/*
 * Returns the effective length of the queue.
 */
size_t
queuelength(void)
{
	return (upsp->port.queue.head <= upsp->port.queue.tail) ?
		upsp->port.queue.tail - upsp->port.queue.head :
		(upsp->port.queue.base + upsp->port.queue.size
		- upsp->port.queue.head) + (upsp->port.queue.tail
		- upsp->port.queue.base);
}


syntax highlighted by Code2HTML, v. 0.9.1