#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <getopt.h>
#include <termios.h>

#define PNAME			program_invocation_short_name

#ifdef DEBUG
#define CDEBUG(code) do { code } while (0)
#define PDEBUG(fmt, args...)	fprintf(stderr, "%s:%s[%d]: " fmt "\n", \
					PNAME, __FILE__, __LINE__ , ## args)
#define PINFO(fmt, args...)	fprintf(stderr, "%s:%s[%d]: " fmt "\n", \
					PNAME, __FILE__, __LINE__ , ## args)
#define PERR(fmt, args...)	fprintf(stderr, "%s:%s[%d]: " fmt "\n", \
					PNAME, __FILE__, __LINE__ , ## args)
#else
#define CDEBUG(code)		/* do nothing! */
#define PDEBUG(fmt, args...)	/* do nothing! */
#define PINFO(fmt, args...)	fprintf(stderr, "%s: " fmt "\n", \
					PNAME , ## args)
#define PERR(fmt, args...)	fprintf(stderr, "%s: " fmt "\n", \
					PNAME , ## args)
#endif 

#define GET_LDISC	0
#define SET_LDISC	1
#define SET_RAW		2	

int option_do_not_exit = 0;

void usage(void)
{
	fprintf(stderr, "usage: %s [<options>] <device>\n", program_invocation_short_name);
	fprintf(stderr, "   where options are:\n");
	fprintf(stderr, "      [-h | --help]             : show this help\n");
	fprintf(stderr, "      [-g | --get-ldisc]        : get current line disc. number\n");
	fprintf(stderr, "      [-r | --set-raw]          : set the serial line in raw mode\n");
	fprintf(stderr, "      [-s | --set-ldisc <num>]  : set line disc. to <num>\n");
	fprintf(stderr, "      [-X | --do-not-exit]      : wait for a signal (eg. CTRL+C) to exit\n");

	exit(EXIT_FAILURE);
}

int main(int argc, char *argv[])
{
	int c;
	static struct option long_options[] = {
		{"help",		no_argument,		0, 'h'},
		{"get-ldisc",	no_argument,		0, 'g'},
		{"set-ldisc",	required_argument,	0, 's'},
		{"set-raw",	no_argument,		0, 'r'},
		{"do-not-exit",	no_argument,		0, 'X'},
		{0, 0, 0, 0}
	};

	int action, ldisc, fd;
	char *dev;
	struct termios term;

	int ret;

	/* Check the command line */
	if (argc == 1) {   /* no arguments */
		usage();
		exit(EXIT_FAILURE);
	}
	while (1) {
		/* `getopt_long' stores the option index here. */
		int option_index = 0;

		c = getopt_long(argc, argv, "hgrs:X",
				long_options, &option_index);

		/* Detect the end of the options. */
		if (c == -1)
			break;

		switch (c) {
		case 'h' :   /* --help */
			usage();

		case 'g' :   /* --get-ldisc */
			action = GET_LDISC;

			break;

		case 's' :   /* --set-ldisc */
			action = SET_LDISC;

			if (sscanf(optarg, "%d", &ldisc) < 1) {
		        	PERR("must specify a line discipline number");
				exit(EXIT_FAILURE);
			}

		break;

		case 'r' :   /* --set-raw */
			action = SET_RAW;

			break;

		case 'X' :   /* --do-not-exit */
			option_do_not_exit = ~0;

			break;

		case ':' :
			/* "getopt_long" already prints an error message */
			exit(EXIT_FAILURE);

		case '?' :
			/* "getopt_long" already prints an error message */
			exit(EXIT_FAILURE);

		default :
			exit(EXIT_FAILURE);
		}
	}
	/* First non option is given from "argv[optind]" */

	if (argv[optind] == NULL) {
		PERR("must specify a serial device");
		exit(EXIT_FAILURE);
	}
	dev = argv[optind];

	ret = open(dev, O_RDWR);
	if (ret < 0) {
		PERR("unable to open device \"%s\" (%d)", dev, ret);
		exit(EXIT_FAILURE);
	}
	fd = ret;

	switch (action) {
	case GET_LDISC :
		ret = ioctl(fd, TIOCGETD, &ldisc);
		if (ret < 0) {
			PERR("unable to get current line discipline for device \"%s\" (%m)", dev);
			exit(EXIT_FAILURE);
		}
	 	printf("%d\n", ldisc);

	 	break;

	case SET_LDISC :
		ret = ioctl(fd, TIOCSETD, &ldisc);
		if (ret < 0) {
			PERR("unable to set line discipline \"%d\" for device \"%s\" (%m)", ldisc, dev);
			exit(EXIT_FAILURE);
		}

	 	break;

	case SET_RAW :
		ret = tcgetattr(fd, &term);
		if (ret < 0) {
			PERR("unable to get termios info for device \"%s\" (%m)", dev);
			exit(EXIT_FAILURE);
		}
		cfmakeraw(&term);

	 	break;

	default :
		PERR("invalid command!");
		exit(EXIT_FAILURE);
	}

	if (option_do_not_exit)
		pause();

	return 0;
}

