/*
 * Copyright (C) 2008 Anders Waldenborg <anders@0x63.nu>
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <termios.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <fcntl.h>
#include <ctype.h>

#include <syslog.h>

#include <assert.h>

struct nameintmap {
	const char *name;
	int val;
};

struct nameintmap baudrates[] = {
	{"300", B300},
	{"1200", B1200},
	{"2400", B2400},
	{"9600", B9600},
	{"19200", B19200},
	{"38400", B38400},
	{"57600", B57600},
	{"115200", B115200}
};

struct nameintmap facilities[] = {
	{"AUTH", LOG_AUTH},
	{"AUTHPRIV", LOG_AUTHPRIV},
	{"CRON", LOG_CRON},
	{"DAEMON", LOG_DAEMON},
	{"FTP", LOG_FTP},
	{"KERN", LOG_KERN},
	{"LOCAL0", LOG_LOCAL0},
	{"LOCAL1", LOG_LOCAL1},
	{"LOCAL2", LOG_LOCAL2},
	{"LOCAL3", LOG_LOCAL3},
	{"LOCAL4", LOG_LOCAL4},
	{"LOCAL5", LOG_LOCAL5},
	{"LOCAL6", LOG_LOCAL6},
	{"LOCAL7", LOG_LOCAL7},
	{"LPR", LOG_LPR},
	{"MAIL", LOG_MAIL},
	{"NEWS", LOG_NEWS},
	{"SYSLOG", LOG_SYSLOG},
	{"USER", LOG_USER},
	{"UUCP", LOG_UUCP}
};

struct nameintmap levels[] = {
	{"EMERG", LOG_EMERG},
	{"ALERT", LOG_ALERT},
	{"CRIT", LOG_CRIT},
	{"ERR", LOG_ERR},
	{"WARNING", LOG_WARNING},
	{"NOTICE", LOG_NOTICE},
	{"INFO", LOG_INFO},
	{"DEBUG", LOG_DEBUG}
};

#define N_ENTRIES(x) (sizeof (x) / sizeof (x[0]))

static int
get_val (struct nameintmap *map, int entries, const char *key, const char *name)
{
	int i;
	for (i = 0; i < entries; i++) {
		if (!strcasecmp (map[i].name, key)) {
			return map[i].val;
		}
	}
	fprintf (stderr, "Unknown %s: %s\n", name, key);
	fprintf (stderr, " valid %ss:\n", name);
	for (i = 0; i < entries; i++) {
		fprintf (stderr, " %s\n", map[i].name);
	}
	exit (1);
}

static int
do_log (int level, char *buf, int len, int bufsiz)
{
	int i;
	int lastrs = 0;

	for (i = 0; i < len; i++) {
		if (buf[i] == '\n' || buf[i] == '\r') {
			buf[i] = '\0';
			if (lastrs == i) {
				lastrs = i + 1;
				continue;
			}
			syslog (level, "%s\n", buf + lastrs);
			lastrs = i + 1;
		}
	}

	/* no linebreak in buffer, print what we got, so we can read more data */
	if (lastrs == 0 && len == bufsiz) {
		char t = buf[len - 1];
		buf[len - 1] = 0;
		syslog (level, "%s-\n", buf + lastrs);
		buf[0] = t;
		return 1;
	}

	if (lastrs >= len)
		return 0;

	memmove (buf, buf+lastrs, len-lastrs);
	return len-lastrs;
}

int
main (int argc, char **argv)
{
	char buf[4096];
	char *nam;
	int buffered = 0;
	struct termios tio;
	int serfd, ret, i;
	fd_set rfds;
	int rate, facility, level;

	if (argc != 5) {
		fprintf (stderr, "Usage: serialsyslog DEVICE BAUDRATE FACITITY LEVEL\n");
		exit (1);
	}

	rate = get_val(baudrates, N_ENTRIES (baudrates), argv[2], "baudrate");
	facility = get_val(facilities, N_ENTRIES (facilities), argv[3], "facility");
	level = get_val(levels, N_ENTRIES (levels), argv[4], "level");

	serfd = open (argv[1], O_RDONLY | O_NONBLOCK);
	if (serfd == -1) {
		perror ("Couldn't open serial port");
		exit (1);
	}

	nam = argv[1];
	for (i = 0; nam[i] != '\0'; i++) {
		if (!isalnum (nam[i])) {
			nam += i + 1;
			i = 0;
		}
	}

	snprintf (buf, 32, "serial_%s", nam);

	openlog (strdup (buf), 0, facility);



	memset (&tio, 0, sizeof (struct termios));

	tio.c_cflag = rate | CRTSCTS | CS8 | CLOCAL | CREAD;
	tio.c_iflag = IGNPAR | ICRNL;
	tio.c_oflag = 0;
	tio.c_lflag = ICANON;

	if (tcflush (serfd, TCIFLUSH) == -1) {
		perror ("Couldn't flush serial port");
	}
	if (tcsetattr (serfd, TCSANOW, &tio) == -1) {
		perror ("Couldn't set serial port parameters");
		exit (1);
	}

	syslog (level, "Starting logging '%s'", argv[1]);

	for (;;) {
		FD_ZERO (&rfds);
		FD_SET (serfd, &rfds);
		ret = select (serfd + 1, &rfds, NULL, NULL, NULL);

		if (ret > 0) {
			assert (buffered < sizeof (buf));
			ret = read (serfd, &buf[buffered], sizeof (buf) - buffered);
			if (ret > 0) {
				buffered = do_log (level, buf, buffered + ret, sizeof (buf));
			} else {
				perror ("Error reading");
				exit (1);
			}
		} else {
			perror ("Error selecting");
			exit (1);
		}

	}
	return 0;
}
