/* udclient.c
 * VERSION: 0.5
 * AUTHOR: miura@da-cha.org
 * DATE: 2005.01.15
 *
 *  repeater to communicate with unix domain socket
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <stdio.h>
#include <sys/un.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#include "udclient.h"

#include <syslog.h>

#define BFSZ (4096) /* buffer size */

#define COMMAND "udclient"
#define VERSION "0.5"

/*
 * data transport routin
 */
static ssize_t ud_transport(int in, int out)
{
	char* buf = NULL;	/* data buffer */
	ssize_t len = BFSZ;	/* data length */
	int count = -1;	/* read count */

        char *debug = NULL;     /* debug buffer */
        int i=0;

	/* read input */
        debug = (char*)malloc(BFSZ*3+1);
	while (len == BFSZ) {
		count++;
		/* allocate data buffer */
		if ((buf = (char*)realloc(buf, (count + 1) * BFSZ)) == NULL) {
			perror("realloc");
			exit(1);
		}
		/* read input to data buffer */
		if ((len = read(in, buf + count * BFSZ, BFSZ)) < 0) {
			perror("read");
			exit(1);
		}
                /* for debug */
                for (i=0; i<len ; i++) {
                  syslog(LOG_LOCAL0|LOG_NOTICE, "udclient wrote byte:%04d:[%02X]", i, (unsigned int)((char)(*(buf + count * BFSZ + i))));
                }
                syslog(LOG_LOCAL0|LOG_NOTICE, "udclient wrote %d bytes [%s]", len, debug);
                /* end for debug */
	}
	len += count * BFSZ;
	/* write output */
	if (len > 0 && write(out, buf, len) < 0) {
		perror("write");
		exit(1);
	}
        //syslog(LOG_LOCAL0|LOG_NOTICE, "udclient wrote %d bytes", len);
	/* destroy data buffer */
	free(buf);
	return len;
}

/* unix domain client routine for SOCK_STREAM */
int ud_cli(FILE *in, FILE *out, int sockfd)
{
	int maxfd, in_eof;
	fd_set rset;

	in_eof = 0;
	FD_ZERO(&rset);
	for ( ; ; ) {
		if (in_eof == 0) {
			FD_SET(fileno(in), &rset);
		}
		FD_SET(sockfd, &rset);
		maxfd = max(fileno(in), sockfd) + 1;
		select(maxfd, &rset, NULL, NULL, NULL);

		if (FD_ISSET(sockfd, &rset)) { /*socket is readable */
                  syslog(LOG_LOCAL0|LOG_NOTICE, "socket is readable");
			if (ud_transport(sockfd, fileno(out)) == 0) {
				if (in_eof == 1) { /* normal termination */
					return 0;
				} else {
					perror("ud_cli: server terminated prematurely"); 
					exit(1);
				}
			}
		}
		if (FD_ISSET(fileno(in), &rset)) { /* stdin  is readable */
                  syslog(LOG_LOCAL0|LOG_NOTICE, "stdin is readable");
			if (ud_transport(fileno(in), sockfd) == 0) {
				in_eof = 1;
				shutdown(sockfd, SHUT_WR); /* send FIN */
				FD_CLR(fileno(in), &rset);
			}
		}
	}

	/* NOREACH */
	return 1;
}

static void print_help(void)
{
	fprintf(stderr, "usage: " COMMAND " [-v|-h] path\n"
		"\n"
		"-h\t show this message.\n"
		"-v\t print version.\n"
		"\n"
		"Communication with server through unix domain socket\n"
		"specified by path argument.\n"
		"Writtend by Hiroshi Miura <miura@da-cha.org>, Jan. 2005\n"
		);
}

static int proc_opt(int argc, char *argv[])
{
	int i = 1;
	char c;

	if (argc < 2)
		return 0;

	while (1) {
		if ((c = getopt(argc, argv, "hv")) == -1) 
			break;

		switch (c) {
		case 'h':
			return 0;
		case 'v':
			fprintf(stderr, COMMAND ": Version " VERSION "\n");
			return -1;
		default:
			fprintf(stderr, "Error: unknown option(s).\n\n");
			return 0;
		}
	}

	if ( argc == i + 1 )
		return i;
	
	if ( argc > i + 1 ) {
		fprintf(stderr, "Error: too many argument(s).\n\n");
	} else {
		fprintf(stderr, "Error: socket pathname is needed.\n\n");
	}
	return 0;
}

/* 引数はUnix Socket に用いるファイル名 */

int main(int argc, char *argv[])
{
	int sockfd;
	int pathname_i;
	struct sockaddr_un svaddr;
	FILE *in, *out;
	int proto = SOCK_STREAM;

	if ((pathname_i = proc_opt(argc, argv)) <= 0) {
		if (pathname_i == 0)
			print_help();
		exit(1);
	}

	in = stdin; out	= stdout; 

	/* create unix domain socket and connect server*/
	bzero(&svaddr, sizeof(svaddr));
	svaddr.sun_family = AF_LOCAL;
	strncpy(svaddr.sun_path, argv[pathname_i], sizeof(svaddr.sun_path) - 1);

	if ((sockfd = socket(PF_LOCAL, proto, 0)) < 0) {
		perror("socket");
		exit(1);
	}
	if (connect(sockfd, (struct sockaddr*)&svaddr, SUN_LEN(&svaddr)) < 0) {
		perror("connect");
		close(sockfd);
		exit(1);
	}

	/* transport data */
	if (!ud_cli(in, out, sockfd)){
		close(sockfd);	
		exit(1);
	}

	close(sockfd);	
	exit(0);
}
