/*
 * Server for the sendfile test program
 * Syntax: testsf_s <own IP addr>
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/file.h>
#include <errno.h>
#include <sys/signal.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/sendfile.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include "test.h"
#include "netdefs.h"

int TST_TOTAL = 1;

#if INET6
char *TCID = "sendfile6_server";
#else
char *TCID = "sendfile_server";
#endif

int main(int argc, char *argv[])
{
	sai_t sa, *ap;
	sa_t from;
	struct addrinfo *hp;
	struct addrinfo hints;
	int as, fd, gai, rc, s;
	char *lp;
	char *number;
	int pid, nbytes, flen, count;
	char rbuf[PATH_MAX];
	int chunks = 0;
	off_t *offset;
	char nbuf[PATH_MAX];
	int port;

	if (argc != 3) {
		tst_brkm(TBROK, NULL, "usage: listen-address listen-port");
	}

	/* open socket */
	if ((s = socket(AFI, SOCK_STREAM, 0)) < 0) {
		tst_brkm(TBROK, NULL, "socket error = %d\n", errno);
	}

	signal(SIGCHLD, SIG_IGN);	/* ignore signals from children */

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = PFI;
	if ((gai = getaddrinfo(argv[1], NULL, &hints, &hp)) != 0) {
		tst_brkm(TBROK, NULL, "getaddrinfo failed");
	}
	if (!hp || !hp->ai_addr || hp->ai_addr->sa_family != AFI) {
		tst_brkm(TBROK, NULL, "getaddrinfo failed");
	}

	/* server IP and port */
	memcpy(&sa, hp->ai_addr, hp->ai_addrlen);
	port = atoi(argv[2]);
#if INET6
	sa.sin6_port = htons(port);
#else
	sa.sin_port = htons(port);
#endif

	/* bind IP and port to socket */
	if (bind(s, (sa_t *) & sa, sizeof(sa)) < 0) {
		tst_resm(TBROK, "bind error = %d\n", errno);
		close(s);
		tst_exit();
	}

	/* start to listen socket */
	if (listen(s, LISTEN_BACKLOG) < 0) {
		tst_resm(TBROK, "listen error = %d\n", errno);
		close(s);
		tst_exit();
	}

	socklen_t fromlen = sizeof(from);

	/* process connections */
	while (1) {

		/* accept a connection from a client */
		if ((as = accept(s, &from, &fromlen)) < 0) {
			tst_resm(TBROK, "accept error = %d\n", errno);
			if (errno == EINTR)
				continue;
			close(s);
			tst_exit();
		}

		ap = (sai_t *) & from;

		/* create a process to manage the connection */
		if ((pid = fork()) < 0) {
			tst_resm(TBROK, "fork error = %d\n", errno);
			close(as);
			tst_exit();
		}
		if (pid > 0) {	/* parent, go back to accept */
			close(as);
			continue;
		}

		/* child process to manage a connection */

		close(s);	/* close service socket */

		/* get client request information */
		if ((nbytes = read(as, rbuf, PATH_MAX)) <= 0) {
			tst_resm(TBROK, "socket read error = %d\n", errno);
			close(as);
			tst_exit();
		}
		rbuf[nbytes] = '\0';	/* null terminate the info */
		lp = &rbuf[0];

		/* start with file length, '=' will start the filename */
		count = flen = 0;
		number = &nbuf[0];
		while (*lp != '=') {	/* convert ascii to integer */
			nbuf[count] = *lp;
			count++;
			lp++;
		}
		nbuf[count] = '\0';
		flen = strtol(number, NULL, 10);

		/* the file name */
		lp++;

		tst_resm(TINFO, "The file to send is %s\n", lp);
		/* open requested file to send */
		if ((fd = open(lp, O_RDONLY)) < 0) {
			tst_resm(TBROK, "file open error = %d\n", errno);
			close(as);
			tst_exit();
		}
		offset = NULL;
		errno = 0;
		do {		/* send file parts until EOF */
			if ((rc = sendfile(as, fd, offset, flen)) != flen) {
				if ((errno != EWOULDBLOCK) && (errno != EAGAIN)) {
					tst_resm(TBROK,
						 "sendfile error = %d, rc = %d\n",
						 errno, rc);
					close(as);
					close(fd);
					tst_exit();
				}
			}
			chunks++;
		} while (rc != 0);
		tst_resm(TINFO, "File %s sent in %d parts\n", lp, chunks);

		close(as);	/* close connection */
		close(fd);	/* close requested file */

		exit(0);

	}

	close(s);		/* close parent socket (never reached because of the while (1)) */

	tst_exit();

}
