/* This file is part of gacopyz.
   Copyright (C) 2007-2025 Sergey Poznyakoff

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

#include <gacopyz_priv.h>

int
_gacopyz_read_tm(gacopyz_iod_t iod, int tm, char *buf, size_t size)
{
	int rc = MI_SUCCESS;
	
	while (size) {
		fd_set rset;
		fd_set xset;
		int res;
		struct timeval to;
		
		FD_ZERO(&rset);
		FD_ZERO(&xset);
		FD_SET(iod->sd, &rset);
		FD_SET(iod->sd, &xset);

		to = iod->timeout[tm];

		res = select(iod->sd + 1, &rset, NULL, &xset, &to);
		if (res == 0) {
			gacopyz_io_log(iod,
				       SMI_LOG_ERR,
				       _("%s: connection timed out"),
				       "_gacopyz_read");
			errno = ETIMEDOUT;
			rc = MI_FAILURE;
			break;
		} else if (res < 0) {
			if (errno == EINTR)
				continue;
			rc = MI_FAILURE;
			break;
		} else if (rc > 0) {
			if (FD_ISSET(iod->sd, &xset)) {
				gacopyz_io_log(iod,
					       SMI_LOG_ERR,
					       _("%s: exception on control fd"),
					       "_gacopyz_read");
				rc = MI_FAILURE;
				break;
			}
			/* Otherwise, FD_ISSET(iod->sd, &rset) is true */
		}

		res = read(iod->sd, buf, size);
		if (res == -1) {
			gacopyz_io_log(iod,
				       SMI_LOG_ERR,
				       _("%s: read failed: %s"),
				       "_gacopyz_read",
				       strerror(errno));
			rc = MI_FAILURE;
			break;
		} else if (res == 0) {
			gacopyz_io_log(iod,
				       SMI_LOG_ERR,
				       _("%s: end of file"),
				       "_gacopyz_read");
			rc = MI_FAILURE;
			break;
		}

		buf += res;
		size -= res;
	}
	return rc;
}

int
_gacopyz_read(gacopyz_iod_t iod, char *buf, size_t size)
{
	return _gacopyz_read_tm(iod, GACOPYZ_TO_READ, buf, size);
}

int
_gacopyz_write(gacopyz_iod_t iod, const char *buf, size_t size)
{
	int rc = MI_SUCCESS;
	
	while (size) {
		fd_set wset;
		fd_set xset;
		int res;
		struct timeval to;
		
		FD_ZERO(&wset);
		FD_ZERO(&xset);
		FD_SET(iod->sd, &wset);
		FD_SET(iod->sd, &xset);

		to = iod->timeout[GACOPYZ_TO_READ];

		res = select(iod->sd + 1, NULL, &wset, &xset, &to);
		if (res == 0) {
			gacopyz_io_log(iod,
				       SMI_LOG_ERR,
				       _("%s: connection timed out"),
				       "_gacopyz_write");
			errno = ETIMEDOUT;
			rc = MI_FAILURE;
			break;
		} else if (res < 0) {
			if (errno == EINTR)
				continue;
			rc = MI_FAILURE;
			break;
		} else if (rc > 0) {
			if (FD_ISSET(iod->sd, &xset)) {
				gacopyz_io_log(iod,
					       SMI_LOG_ERR,
					       _("%s: exception on control fd"),
					       "_gacopyz_write");
				rc = MI_FAILURE;
				break;
			}
			/* Otherwise, FD_ISSET(iod->sd, &wset) is true */
		}

		res = write(iod->sd, buf, size);
		if (res == -1) {
			gacopyz_io_log(iod,
				       SMI_LOG_ERR,
				       _("%s: write failed: %s"),
				       "_gacopyz_write",
				       strerror(errno));
			rc = MI_FAILURE;
			break;
		} else if (res == 0) {
			gacopyz_io_log(iod,
				       SMI_LOG_ERR,
				       _("%s: wrote 0 bytes"),
				       "_gacopyz_write");
			rc = MI_FAILURE;
			break;
		} 

		buf += res;
		size -= res;
	}
	return rc;
}

union header {
	struct {
		uint32_t size;
		unsigned char cmd;
	} hdr;
	char buf[5];
};

int
gacopyz_send_command(gacopyz_iod_t iod, int cmd, const void *data, size_t size)
{
	int rc;
	union header header;

	gacopyz_io_log(iod, SMI_LOG_DEBUG,
		       _("send header: size=%lu, cmd=%c"),
		       size, cmd);

	gacopyz_io_logdump(iod, _("send data"), data, size);
	header.hdr.size = htonl(size + 1);
	header.hdr.cmd = cmd;
	rc = _gacopyz_write(iod, header.buf, sizeof header.buf);
	if (rc != MI_SUCCESS)
		return rc;
	if (size)
		rc = _gacopyz_write(iod, data, size);
	return rc;
}

int
gacopyz_read_command(gacopyz_iod_t iod, int tm,
		     unsigned char *cmd, size_t *pcount,
		     char **pbuf, size_t *psize)
{
	union header header;
	size_t size;
	int rc;

	if ((rc = _gacopyz_read_tm(iod, tm, header.buf, sizeof header.buf))
	    != MI_SUCCESS)
		return rc;
	
	size = ntohl(header.hdr.size) - 1;
	if (size + 1 > *psize) {
		char *p = realloc(*pbuf, size + 1);
		if (!p) {
			gacopyz_io_log(iod,
				       SMI_LOG_ERR, "%s", strerror(errno));
			return MI_FAILURE;
		}
		*pbuf = p;
		*psize = size + 1;
	}

	gacopyz_io_log(iod, SMI_LOG_DEBUG,
		       _("read header: size=%lu, cmd=%c"),
		       size, header.hdr.cmd);
	
	if ((rc = _gacopyz_read(iod, *pbuf, size)) != MI_SUCCESS)
		return rc;
	(*pbuf)[size] = 0;

	gacopyz_io_logdump(iod, _("read data"), *pbuf, size);
	*pcount = size;
	*cmd = header.hdr.cmd;
	return MI_SUCCESS;
}
