/*-
 *  Copyright (c) 2002, 2003  Peter Pentchev
 *  All rights reserved.
 * 
 *  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 THE 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 THE 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.
 */

#include <sys/types.h>
#include <sys/socket.h>

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "urelay.h"
#include "ur_err.h"

__RINGID("$Ringlet: c/net/urelay/urelay.c,v 1.3 2003/07/16 10:21:04 roam Exp $");

int		verbose, quiet;

char		ur_verstr[64] = "urelay";
char		*msgsrc, *msgdst;
char		*ignsrc, *igndst;

static ur_err_t	ur_init(int, char *[]);
static ur_err_t	ur_close(void);
static ur_err_t	ur_doit(int, char *[]);

static void	usage(void);
static ur_err_t	version(void);

/*
 * Function:
 *         ur_makeversion()                - generate version string
 * Inputs:
 *         none
 * Returns:
 *         ur_err_t
 * Modifies:
 *         ur_verstr
 */

static ur_err_t
ur_makeversion(void) {

	snprintf(ur_verstr, sizeof(ur_verstr), "urelay v%d.%d"
#if UR_VER_PRE
	    "-pre%d"
#endif /* UR_VER_PRE */
#if UR_VER_PATCH
	    "p%d"
#endif /* UR_VER_PATCH */
	    ,
	    UR_VER_MAJ, UR_VER_MIN
#if UR_VER_PRE
	    , UR_VER_PRE
#endif /* UR_VER_PRE */
#if UR_VER_PATCH
	    , UR_VER_PATCH
#endif /* UR_VER_PATCH */
		);
	
	return (UR_ERR_NONE);
}

static ur_err_t
ur_nonblock(int fd) {
	int flags;

	if (fcntl(fd, F_GETFL, &flags) == -1)
		return (UR_ERR_FCNTL_GET);
	if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
		return (UR_ERR_FCNTL_SET);
	return (UR_ERR_NONE);
}

/*
 * Function:
 * 	ur_init				- general-purpose init function
 * Inputs:
 * 	argc, argv			- main() args for option processing
 * Returns:
 * 	ur_err_t
 * 	CMDLINE
 * Modifies:
 * 	verbose, quiet
 */

static ur_err_t
ur_init(int argc, char *argv[]) {
	int ch, helpq, versq;
	ur_err_t err;
	
	helpq = versq = 0;
	ur_makeversion();
	
	/* Process cmdline options */
	
	opterr = 0;
	while (ch = getopt(argc, argv, UR_OPTSTR), ch != EOF)
		switch (ch) {
			case 'h':
				helpq = 1;
				break;
			case 'M':
				msgsrc = optarg;
				break;
			case 'm':
				msgdst = optarg;
				break;
			case 'S':
				ignsrc = optarg;
				break;
			case 's':
				igndst = optarg;
				break;
			case 'q':
				quiet++;
				break;
			case 'V':
				versq = 1;
				break;
			case 'v':
				verbose++;
				break;
			case '?':
			default:
				usage();
				/* NOTREACHED */
		}
	
	argc -= optind; argv += optind;
	
	if (versq)
		version();
	if (helpq)
		usage();
	if (versq)
		/* no need for "|| helpq": usage() never returns */
		exit(UR_ERR_NONE);
	
	if ((err = ur_nonblock(0)) != UR_ERR_NONE)
		return (err);
	if ((err = ur_nonblock(6)) != UR_ERR_NONE)
		return (err);
	return (UR_ERR_NONE);
}

/*
 * Function:
 * 	ur_close			- general-purpose shutdown function
 * Inputs:
 * 	none
 * Returns:
 * 	ur_err_t
 * 	nothing for the present (FIXME)
 * Modifies:
 * 	nothing for the present (FIXME)
 */

static ur_err_t
ur_close(void) {
	
	return (UR_ERR_NONE);
}

/*
 * Function:
 * 	usage			- startup help info
 * Inputs:
 * 	none
 * Returns:
 * 	nothing
 * Modifies:
 * 	nothing, writes to stdout and exits
 */

void
usage(void) {
	static const char *msg[] = {
	    "usage: urelay [-hqVv]",
	    "\t-h\tprint this help text and exit;",
	    "\t-q\tquiet operation; multiple -q's make it even more quiet;",
	    "\t-V\tprint version information and exit;",
	    "\t-v\tverbose operation; multiple -v's increase verbosity level.",
	    NULL
	};
	unsigned i;
	
	for (i = 0; msg[i] != NULL; i++)
		fprintf(stderr, "%s\n", msg[i]);
	exit(UR_ERR_CMDLINE);
}

/*
 * function:
 * 	ur_version		- output version info
 * Inputs:
 * 	none
 * Returns:
 * 	UR_ERR_NONE
 * Modifies:
 * 	nothing, writes to stdout
 */

ur_err_t
version(void) {
	
	printf("%s\n", ur_verstr);
	
	if (verbose) {
		printf("%s\n", "Built on " __DATE__ ", " __TIME__);
#ifdef __GNUC__
		if (verbose > 1)
			printf("%s\n", "Compiler: GNU C " __VERSION__
			    " on " UR_OS " " UR_OSREL ": " UR_OSHOST);
#endif /* __GNUC__ */
	}

	return (UR_ERR_NONE);
}

static ur_err_t
ur_write(int fd, char *buf, size_t len) {
	int n;

	while (len > 0) {
		if ((n = write(fd, buf, len)) == -1)
			return (UR_ERR_WRITE);
		len -= n;
		buf += n;
	}
	return (UR_ERR_NONE);
}

static ur_err_t
ur_xfer(int in, int out) {
	char buf[512];
	int n;
	ur_err_t err;

	RDBG(("xfer, in = %d, out = %d\n", in, out));

	while ((n = read(in, buf, sizeof(buf))) > 0) {
		RDBG(("xfer: read %d\n", n));
		if ((err = ur_write(out, buf, n)) != UR_ERR_NONE)
			return (err);
	}
	RDBG(("xfer: out of the loop, n = %d\n", n));
	if (n == -1) {
		if (errno == EAGAIN)
			return (UR_ERR_NONE);
		return (UR_ERR_READ);
	}
	if (n == 0)
		return (UR_ERR_NOREAD);
	return (UR_ERR_NONE);
}

static ur_err_t
ur_swallow(int fd, char *str, size_t len) {
	char *buf;
	int n;

	if ((buf = malloc(len)) == NULL)
		return (UR_ERR_NOMEM);
	while (len > 0) {
		if ((n = read(fd, buf, len)) == -1) {
			if (errno == EAGAIN)
				continue;
			free(buf);
			return (UR_ERR_READ);
		}
		if (n == 0)
			continue;

		if (memcmp(str, buf, n)) {
			free(buf);
			return (UR_ERR_DIFF);
		}
		str += n;
		len -= n;
	}
	free(buf);
	return (UR_ERR_NONE);
}

/*
 * Function:
 * 	ur_selloop			- do the actual connection proxying.
 * Inputs:
 * 	src				- the source file descriptor array
 * 	dst				- the destination file desc array
 * Returns:
 * 	ur_err_t
 * 	UR_ERR_SELECT			- select() error
 * 	Error codes from ur_swallow(), ur_write(), ur_xfer().
 * Modifies:
 *	nothing by itself; does a select() on the file descriptors and
 * 	invokes ur_swallow(), ur_write(), or ur_xfer() as needed.
 */

static ur_err_t
ur_selloop(int src[2], int dst[2]) {
	fd_set fdr;
	int maxfd, left, n;
	ur_err_t err;
	
	RDBG(("selloop, src=(%d, %d), dst = (%d, %d)\n",
	    src[0], src[1], dst[0], dst[1]));

	if ((msgsrc != NULL) &&
	    ((err = ur_write(src[1], msgsrc, strlen(msgsrc))) != UR_ERR_NONE))
		return (err);
	if ((msgdst != NULL) &&
	    ((err = ur_write(dst[1], msgdst, strlen(msgdst))) != UR_ERR_NONE))
		return (err);
	if ((ignsrc != NULL) &&
	    ((err = ur_swallow(src[0], ignsrc, strlen(ignsrc))) != UR_ERR_NONE))
		return (err);
	if ((igndst != NULL) &&
	    ((err = ur_swallow(dst[0], igndst, strlen(igndst))) != UR_ERR_NONE))
		return (err);

	left = 2;

	maxfd = src[0];
	if (src[1] > maxfd)
		maxfd = src[1];
	if (dst[0] > maxfd)
		maxfd = dst[0];
	if (dst[1] > maxfd)
		maxfd = dst[1];
	
	while (left > 0) {
		RDBG(("selloop: loop, left = %d\n", left));

		FD_ZERO(&fdr);
		FD_SET(src[0], &fdr);
		FD_SET(dst[0], &fdr);

		if ((n = select(maxfd, &fdr, NULL, NULL, NULL)) == -1)
			return (UR_ERR_SELECT);
		RDBG(("selloop: select() returned %d\n", n));
		if (n == 0)
			continue;

		if (FD_ISSET(src[0], &fdr))
			if ((err = ur_xfer(src[0], dst[1])) != UR_ERR_NONE) {
				if (err == UR_ERR_NOREAD) {
					shutdown(dst[1], SHUT_WR);
					left--;
				} else {
					return (err);
				}
			}
		if (FD_ISSET(dst[0], &fdr))
			if ((err = ur_xfer(dst[0], src[1])) != UR_ERR_NONE) {
				if (err == UR_ERR_NOREAD) {
					shutdown(src[1], SHUT_WR);
					left--;
				} else {
					return (err);
				}
			}
	}
	return (UR_ERR_NONE);
}

/*
 * Function:
 * 	ur_doit				- perform the actual work
 * Inputs:
 * 	argc, argv			- main() args
 * Returns:
 * 	The error code from ur_selloop().
 * Modifies:
 *	nothing by itself; invokes ur_selloop().
 */

static ur_err_t
ur_doit(int argc __unused, char *argv[] __unused) {
	int fdin[2] = {0, 1}, fdout[2] = {6, 7};
	ur_err_t err;
	
	if ((err = ur_selloop(fdin, fdout)) != UR_ERR_NONE)
		return (err);
	return (UR_ERR_NONE);
}

/*
 *   M A I N   F U N C T I O N
 */

int
main(int argc, char *argv[]) {
	ur_err_t r, cr;
	
	if (r = ur_init(argc, argv), r)
		return (ur_prerror("init", r));
	
	argc -= optind;
	argv += optind;
	if (r = ur_doit(argc, argv), r)
		ur_prerror("doit", r);
	
	if (cr = ur_close(), cr)
		ur_prerror("close", cr);
	
	return (r? r: cr);
}


syntax highlighted by Code2HTML, v. 0.9.1