/*
 * Copyright(c) 2010 Intel Corporation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Maintained at www.Open-FCoE.org
 */

#include "fcoe_utils.h"

int fcoe_sysfs_read(char *buf, int size, const char *path)
{
	FILE *fp;
	unsigned int i;
	int rc = -EINVAL;

	fp = fopen(path, "r");
	if (fp) {
		if (fgets(buf, size, fp)) {
			/*
			 * Strip trailing newline by replacing
			 * any '\r' or '\n' instances with '\0'.
			 * It's not as elegant as it could be, but
			 * we know that the symbolic name won't
			 * have either of those characters until
			 * the end of the line.
			 */
			for (i = 0; i < strlen(buf); i++) {
				if (buf[i] == '\n' ||
				    buf[i] == '\r') {
					buf[i] = '\0';
					break;
				}
			}
			rc = 0;
		}

		fclose(fp);
	}

	return rc;
}

static int fcoe_check_fchost(const char *ifname, const char *dname)
{
	char buf[MAX_STR_LEN];
	char path[MAX_PATH_LEN];
	int rc = -EINVAL;

	sprintf(path, "%s/%s/symbolic_name", SYSFS_FCHOST, dname);

	if (!fcoe_sysfs_read(buf, MAX_STR_LEN, path))
		rc = check_symbolic_name_for_interface(buf, ifname);

	return rc;
}

enum fcoe_status fcoe_find_fchost(const char *ifname, char *fchost, int len)
{
	int n, status;
	struct dirent **namelist;
	int rc = ENOFCOECONN;
	int rrc;

	status = n = scandir(SYSFS_FCHOST, &namelist, 0, alphasort);

	for (n-- ; n >= 0 ; n--) {
		if (rc) {
			/* check symbolic name */
			if (!fcoe_check_fchost(ifname, namelist[n]->d_name)) {
				rrc = snprintf(fchost, len, "%s", namelist[n]->d_name);
				if (rrc < 0 || rrc >= len) {
					fchost[0] = '\0';
					/*
					 * The fc_host is too large
					 * for the buffer.
					 */
					rc = EINTERR;
				} else {
					/* rc = 0 indicates found */
					rc = SUCCESS;
				}
			}
		}
		free(namelist[n]);
	}
	if (status >= 0)
		free(namelist);

	return rc;
}

enum fcoe_status fcoe_validate_interface(char *ifname)
{
	enum fcoe_status rc = SUCCESS;
	char path[MAX_PATH_LEN];


	if (!strlen(ifname))
		rc = ENOETHDEV;

	/*
	 * TODO: Is there a better way to check if the
	 * interface name is correct?
	 */
	sprintf(path, "%s/%s", SYSFS_NET, ifname);
	if (!rc && fcoe_checkdir(path))
		rc = ENOETHDEV;

	return rc;
}

/*
 * Validate an existing instance for an FC interface
 */
enum fcoe_status fcoe_validate_fcoe_conn(char *ifname)
{
	char fchost[FCHOSTBUFLEN];
	enum fcoe_status rc = SUCCESS;

	rc = fcoe_validate_interface(ifname);

	if (!rc)
		rc = fcoe_find_fchost(ifname, fchost, FCHOSTBUFLEN);

	return rc;
}

/*
 * Open and close to check if directory exists
 */
int fcoe_checkdir(char *dir)
{
	DIR *d = NULL;

	if (!dir)
		return -EINVAL;
	/* check if we have sysfs */
	d = opendir(dir);
	if (!d)
		return -EINVAL;
	closedir(d);
	return 0;
}

/*
 * Parse the interface name from the symbolic name string.
 * Assumption: Symbolic name is of the type "<DRIVER> <VERSION> over <IFACE>"
 *             Specifically there is a space before the <IFACE>
 */
char *get_ifname_from_symbolic_name(const char *symbolic_name)
{
	char *last_space = strrchr(symbolic_name, ' ');

	if (!last_space || strlen(last_space) == 1)
		return NULL;

	return (char *)(last_space + 1);
}

int check_symbolic_name_for_interface(const char *symbolic_name,
				      const char *ifname)
{
	int rc = -EINVAL;
	char *symb;

	if (!ifname)
		return rc;

	symb = get_ifname_from_symbolic_name(symbolic_name);

	/*
	 * It's important to use the length of the ifname
	 * from the symbolic_name here. If the ifname length
	 * were used then if the user passed in a substring
	 * of the the interface name it would match because
	 * we'd only be looking for the first few characters,
	 * not the whole string.
	 */
	if (symb && !strncmp(ifname, symb, strlen(symb)))
		rc = 0;

	return rc;
}

enum fcoe_status fcm_write_str_to_sysfs_file(const char *path, const char *str)
{
	FILE *fp = NULL;
	enum fcoe_status ret = EFAIL;

	fp = fopen(path, "w");
	if (!fp)
		goto err_out;

	if (EOF == fputs(str, fp))
		goto out;

	ret = SUCCESS;
out:
	fclose(fp);
err_out:
	return ret;
}

static int fchost_filter(const struct dirent *dent)
{
	return !strncmp(dent->d_name, "host", 4);
}

static int fcoe_check_ctlr(const char *fchost, const char *dname)
{
	int n, status;
	struct dirent **namelist;
	char path[MAX_PATH_LEN];
	int rc = -EINVAL;

	sprintf(path, "%s/%s", SYSFS_FCOE_BUS_DEVICES, dname);
	status = n = scandir(path, &namelist, fchost_filter, alphasort);
	for (n-- ; n >= 0 ; n--) {
		if (rc) {
			if (!strncmp(namelist[n]->d_name, fchost, 20))
				rc = SUCCESS;
			else
				rc = EINTERR;
		}
		free(namelist[n]);
	}
	if (status >= 0)
		free(namelist);

	return rc;
}

static int ctlr_filter(const struct dirent *dent)
{
	return !strncmp(dent->d_name, "ctlr_", 5);
}

enum fcoe_status fcoe_find_ctlr(const char *fchost, char *ctlr, int len)
{
	int n, dname_len, status;
	struct dirent **namelist;
	int rc = ENOFCOECONN;

	status = n = scandir(SYSFS_FCOE_BUS_DEVICES, &namelist,
			     ctlr_filter, alphasort);
	for (n-- ; n >= 0 ; n--) {
		if (rc) {
			/* check ctlr against known host */
			if (!fcoe_check_ctlr(fchost,
					     namelist[n]->d_name)) {

				dname_len = strnlen(namelist[n]->d_name, len);

				if (len > dname_len) {
					strncpy(ctlr, namelist[n]->d_name,
						dname_len + 1);
					/* rc = 0 indicates found */
					rc = SUCCESS;
				} else {
					/*
					 * The fc_host is too large
					 * for the buffer.
					 */
					rc = EINTERR;
				}
			}
		}
		free(namelist[n]);
	}
	if (status >= 0)
		free(namelist);

	return rc;
}

enum fcoe_status fcm_write_str_to_ctlr_attr(const char *ctlr,
					    const char *attr,
					    const char *str)
{
	char path[MAX_PATH_LEN];

	sprintf(path, "%s/%s/%s", SYSFS_FCOE_BUS_DEVICES, ctlr, attr);
	return fcm_write_str_to_sysfs_file(path, str);
}
