/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include <stdio.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <locale.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <errno.h>

#define	MAXLINELEN	4096

/*
 * Usage:
 *	sonconfig -f <file>
 *		Reads input from file. The file is structured as
 *			 <fam> <type> <protocol> <path|module>
 *			 <fam> <type> <protocol>
 *		with the first line registering and the second line
 *		deregistering.
 *
 *	soconfig <fam> <type> <protocol> <path|module>
 *		registers
 *
 *	soconfig <fam> <type> <protocol>
 *		deregisters
 *
 * Filter Operations (Consolidation Private):
 *
 *	soconfig -F <name> <modname> {auto [top | bottom | before:filter |
 *		after:filter] | prog} <fam>:<type>:<proto>,...
 *		configure filter
 *
 *	soconfig -F <name>
 *		unconfigures filter
 */

static int	parse_file(char *filename);

static int	split_line(char *line, char *argvec[], int maxargvec);

static int	parse_params(char *famstr, char *typestr, char *protostr,
				char *path, int line);

static int	parse_int(char *str);

static void	usage(void);

static int	parse_filter_params(int argc, char **argv);

int
main(argc, argv)
	int argc;
	char *argv[];
{
	int ret;

	argc--; argv++;

	(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)
#define	TEXT_DOMAIN "SYS_TEST"
#endif
	(void) textdomain(TEXT_DOMAIN);

	if (argc >= 2 && strcmp(argv[0], "-F") == 0) {
		argc--; argv++;
		ret = parse_filter_params(argc, argv);
		exit(ret);
	}
	if (argc == 2 && strcmp(argv[0], "-f") == 0) {
		ret = parse_file(argv[1]);
		exit(ret);
	}
	if (argc == 3) {
		ret = parse_params(argv[0], argv[1], argv[2], NULL, -1);
		exit(ret);
	}
	if (argc == 4) {
		ret = parse_params(argv[0], argv[1], argv[2], argv[3], -1);
		exit(ret);
	}
	usage();
	exit(1);
	/* NOTREACHED */
}

static void
usage(void)
{
	fprintf(stderr, gettext(
	    "Usage:	soconfig -f <file>\n"
	    "\tsoconfig <fam> <type> <protocol> <path|module>\n"
	    "\tsoconfig <fam> <type> <protocol>\n"));
}

/*
 * Open the specified file and parse each line. Skip comments (everything
 * after a '#'). Return 1 if at least one error was encountered; otherwise 0.
 */
static int
parse_file(char *filename)
{
	char line[MAXLINELEN];
	char pline[MAXLINELEN];
	int argcount;
	char *argvec[20];
	FILE *fp;
	int linecount = 0;
	int numerror = 0;

	fp = fopen(filename, "r");
	if (fp == NULL) {
		perror("soconfig: open");
		fprintf(stderr, "\n");
		usage();
		return (1);
	}

	while (fgets(line, sizeof (line) - 1, fp) != NULL) {
		linecount++;
		strcpy(pline, line);
		argcount = split_line(pline, argvec,
		    sizeof (argvec) / sizeof (argvec[0]));
#ifdef DEBUG
		{
			int i;

			printf("scanned %d args\n", argcount);
			for (i = 0; i < argcount; i++)
				printf("arg[%d]: %s\n", i, argvec[i]);
		}
#endif /* DEBUG */
		switch (argcount) {
		case 0:
			/* Empty line - or comment only line */
			break;
		case 3:
			numerror += parse_params(argvec[0], argvec[1],
			    argvec[2], NULL, linecount);
			break;
		case 4:
			numerror += parse_params(argvec[0], argvec[1],
			    argvec[2], argvec[3], linecount);
			break;
		default:
			numerror++;
			fprintf(stderr,
			    gettext("Malformed line: <%s>\n"), line);
			fprintf(stderr,
			    gettext("\ton line %d\n"), linecount);
			break;
		}
	}
	(void) fclose(fp);

	if (numerror > 0)
		return (1);
	else
		return (0);
}

/*
 * Parse a line splitting it off at whitspace characters.
 * Modifies the content of the string by inserting NULLs.
 */
static int
split_line(char *line, char *argvec[], int maxargvec)
{
	int i = 0;
	char *cp;

	/* Truncate at the beginning of a comment */
	cp = strchr(line, '#');
	if (cp != NULL)
		*cp = NULL;

	/* CONSTCOND */
	while (1) {
		/* Skip any whitespace */
		while (isspace(*line) && *line != NULL)
			line++;

		if (i >= maxargvec)
			return (i);

		argvec[i] = line;
		if (*line == NULL)
			return (i);
		i++;
		/* Skip until next whitespace */
		while (!isspace(*line) && *line != NULL)
			line++;
		if (*line != NULL) {
			/* Break off argument */
			*line++ = NULL;
		}
	}
	/* NOTREACHED */
}

/*
 * Parse the set of parameters and issues the sockconfig syscall.
 * If line is not -1 it is assumed to be the line number in the file.
 */
static int
parse_params(char *famstr, char *typestr, char *protostr, char *path, int line)
{
	int cmd, fam, type, protocol;

	fam = parse_int(famstr);
	if (fam == -1) {
		fprintf(stderr, gettext("Bad family number: %s\n"), famstr);
		if (line != -1)
			fprintf(stderr,
			    gettext("\ton line %d\n"), line);
		else {
			fprintf(stderr, "\n");
			usage();
		}
		return (1);
	}

	type = parse_int(typestr);
	if (type == -1) {
		fprintf(stderr,
		    gettext("Bad socket type number: %s\n"), typestr);
		if (line != -1)
			fprintf(stderr,
			    gettext("\ton line %d\n"), line);
		else {
			fprintf(stderr, "\n");
			usage();
		}
		return (1);
	}

	protocol = parse_int(protostr);
	if (protocol == -1) {
		fprintf(stderr,
		    gettext("Bad protocol number: %s\n"), protostr);
		if (line != -1)
			fprintf(stderr,
			    gettext("\ton line %d\n"), line);
		else {
			fprintf(stderr, "\n");
			usage();
		}
		return (1);
	}


	if (path != NULL) {
		struct stat stats;

		if (strncmp(path, "/dev", strlen("/dev")) == 0 &&
		    stat(path, &stats) == -1) {
			perror(path);
			if (line != -1)
				fprintf(stderr,
				    gettext("\ton line %d\n"), line);
			else {
				fprintf(stderr, "\n");
				usage();
			}
			return (1);
		}

		cmd = SOCKCONFIG_ADD_SOCK;
	} else {
		cmd = SOCKCONFIG_REMOVE_SOCK;
	}

#ifdef DEBUG
	printf("not calling sockconfig(%d, %d, %d, %d, %s)\n",
	    cmd, fam, type, protocol, path == NULL ? "(null)" : path);
#else
	if (_sockconfig(cmd, fam, type, protocol, path) == -1) {
		perror("sockconfig");
		return (1);
	}
#endif
	return (0);
}

static int
parse_int(char *str)
{
	char *end;
	int res;

	res = strtol(str, &end, 0);
	if (end == str)
		return (-1);
	return (res);
}

/*
 * Add and remove socket filters.
 */
static int
parse_filter_params(int argc, char **argv)
{
	struct sockconfig_filter_props filprop;
	sof_socktuple_t *socktuples;
	size_t tupcnt, nalloc;
	char *hintarg, *socktup, *tupstr;
	int i;

	if (argc == 1) {
		if (_sockconfig(SOCKCONFIG_REMOVE_FILTER, argv[0], 0,
		    0, 0) < 0) {
			switch (errno) {
			case ENXIO:
				fprintf(stderr,
				    gettext("socket filter is not configured "
				    "'%s'\n"), argv[0]);
				break;
			default:
				perror("sockconfig");
				break;
			}
			return (1);
		}
		return (0);
	}

	if (argc < 4 || argc > 5)
		return (1);


	if (strlen(argv[1]) >= MODMAXNAMELEN) {
		fprintf(stderr,
		    gettext("invalid module name '%s': name too long\n"),
		    argv[1]);
		return (1);
	}
	filprop.sfp_modname = argv[1];

	/* Check the attach semantics */
	if (strcmp(argv[2], "auto") == 0) {
		filprop.sfp_autoattach = B_TRUE;
		if (argc == 5) {
			/* placement hint */
			if (strcmp(argv[3], "top") == 0) {
				filprop.sfp_hint = SOF_HINT_TOP;
			} else if (strcmp(argv[3], "bottom") == 0) {
				filprop.sfp_hint = SOF_HINT_BOTTOM;
			} else {
				if (strncmp(argv[3], "before", 6) == 0) {
					filprop.sfp_hint = SOF_HINT_BEFORE;
				} else if (strncmp(argv[3], "after", 5) == 0) {
					filprop.sfp_hint = SOF_HINT_AFTER;
				} else {
					fprintf(stderr,
					    gettext("invalid placement hint "
					    "'%s'\n"), argv[3]);
					return (1);
				}

				hintarg = strchr(argv[3], ':');
				if (hintarg == NULL ||
				    (strlen(++hintarg) == 0) ||
				    (strlen(hintarg) >= FILNAME_MAX)) {
					fprintf(stderr,
					    gettext("invalid placement hint "
					    "argument '%s': name too long\n"),
					    argv[3]);
					return (1);
				}

				filprop.sfp_hintarg = hintarg;
			}
		} else {
			filprop.sfp_hint = SOF_HINT_NONE;
		}
	} else if (strcmp(argv[2], "prog") == 0) {
		filprop.sfp_autoattach = B_FALSE;
		filprop.sfp_hint = SOF_HINT_NONE;
		/* cannot specify placement hint for programmatic filter */
		if (argc == 5) {
			fprintf(stderr,
			    gettext("placement hint specified for programmatic "
			    "filter\n"));
			return (1);
		}
	} else {
		fprintf(stderr, gettext("invalid attach semantic '%s'\n"),
		    argv[2]);
		return (1);
	}

	/* parse the socket tuples */
	nalloc = 4;
	socktuples = calloc(nalloc, sizeof (sof_socktuple_t));
	if (socktuples == NULL) {
		perror("calloc");
		return (1);
	}

	tupcnt = 0;
	tupstr = argv[(argc == 4) ? 3 : 4];
	while ((socktup = strsep(&tupstr, ",")) != NULL) {
		int val;
		char *valstr;

		if (tupcnt == nalloc) {
			sof_socktuple_t *new;

			nalloc *= 2;
			new = realloc(socktuples,
			    nalloc * sizeof (sof_socktuple_t));
			if (new == NULL) {
				perror("realloc");
				free(socktuples);
				return (1);
			}
			socktuples = new;
		}
		i = 0;
		while ((valstr = strsep(&socktup, ":")) != NULL && i < 3) {
			val = parse_int(valstr);
			if (val == -1) {
				fprintf(stderr, gettext("bad socket tuple\n"));
				free(socktuples);
				return (1);
			}
			switch (i) {
			case 0:	socktuples[tupcnt].sofst_family = val; break;
			case 1:	socktuples[tupcnt].sofst_type = val; break;
			case 2:	socktuples[tupcnt].sofst_protocol = val; break;
			}
			i++;
		}
		if (i != 3) {
			fprintf(stderr, gettext("bad socket tuple\n"));
			free(socktuples);
			return (1);
		}
		tupcnt++;
	}
	if (tupcnt == 0) {
		fprintf(stderr, gettext("no socket tuples specified\n"));
		free(socktuples);
		return (1);
	}
	filprop.sfp_socktuple_cnt = tupcnt;
	filprop.sfp_socktuple = socktuples;

	if (_sockconfig(SOCKCONFIG_ADD_FILTER, argv[0], &filprop, 0, 0) < 0) {
		switch (errno) {
		case EINVAL:
			fprintf(stderr,
			    gettext("invalid socket filter configuration\n"));
			break;
		case EEXIST:
			fprintf(stderr,
			    gettext("socket filter is already configured "
			    "'%s'\n"), argv[0]);
			break;
		case ENOSPC:
			fprintf(stderr, gettext("unable to satisfy placement "
			    "constraint\n"));
			break;
		default:
			perror("sockconfig");
			break;
		}
		free(socktuples);
		return (1);
	}
	free(socktuples);
	return (0);
}