/*
 * Copyright(c) 2010-2011 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
 */

#define __STDC_FORMAT_MACROS 1
#include <ctype.h>
#include <getopt.h>
#include <inttypes.h>
#include <malloc.h>
#include <signal.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>
#include <libgen.h>
#include <ulimit.h>
#include <unistd.h>
#include <paths.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/queue.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <linux/sockios.h>
#include <linux/if.h>
#include <linux/if_arp.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/ethtool.h>
#include <linux/if_vlan.h>
#include <linux/dcbnl.h>

#include <lldpad/dcb_types.h>
#include <lldpad/clif.h>
#include <lldpad/lldp_dcbx_cmds.h>

#include "scsi_netlink_fc.h"
#include "fcoe_utils_version.h"
#include "fcoemon_utils.h"
#include "fcoemon.h"
#include "fcoe_clif.h"
#include "fcoe_utils.h"
#include "sysfs_hba.h"
#include "strarr.h"

#include "fip.h"
#include "rtnetlink.h"

#ifndef SYSCONFDIR
#define SYSCONFDIR                  "/etc"
#endif

#define CONFIG_DIR                  SYSCONFDIR "/fcoe"
#define CONFIG_MIN_VAL_LEN          (1 + 2)
#define CONFIG_MAX_VAL_LEN          (20 + 2)
#define DCB_APP_0_DEFAULT_ENABLE    1
#define DCB_APP_0_DEFAULT_WILLING   1
#define FILE_NAME_LEN               (NAME_MAX + 1)
#define CFG_FILE_PREFIX             "cfg-"
#define DEF_CFG_FILE                CFG_FILE_PREFIX "ethx"
#define FCOE_VLAN_SUFFIX            "-fcoe"
#define FCOE_VLAN_FORMAT            "%s.%d" FCOE_VLAN_SUFFIX
#define FCOE_VID_SCAN_FORMAT        "%*[^.].%d" FCOE_VLAN_SUFFIX

#define VLAN_DIR                "/proc/net/vlan"

#define DCBD_CONNECT_TIMEOUT    (10 * 1000 * 1000)	/* 10 seconds */
#define DCBD_CONNECT_RETRY_TIMEOUT   (1 * 1000 * 1000)	/* 1 seconds */
#define DCBD_REQ_RETRY_TIMEOUT  (200 * 1000)            /* 0.2 seconds */
#define DCBD_MAX_REQ_RETRIES    10
#define FCM_PING_REQ_LEN	1 /* byte-length of dcbd PING request */
#define FCM_PING_RSP_LEN	8 /* byte-length of dcbd PING response */

#define FCM_VLAN_DISC_TIMEOUT	(1000 * 1000)	/* 1 seconds */

#define DEF_RX_BUF_SIZE		4096

#define NLA_DATA(nla)        ((void *)((char *)(nla) + NLA_HDRLEN))
#define NLA_NEXT(nla) (struct rtattr *)((char *)nla + NLMSG_ALIGN(nla->rta_len))

#define FCOE_ETH_TYPE	0x8906

#define CFG_IF_VAR_FCOEENABLE  "FCOE_ENABLE"
#define CFG_IF_VAR_DCBREQUIRED "DCB_REQUIRED"
#define CFG_IF_VAR_AUTOVLAN    "AUTO_VLAN"
#define CFG_IF_VAR_MODE        "MODE"
#define CFG_IF_VAR_FIP_RESP    "FIP_RESP"

enum fcoe_mode {
	FCOE_MODE_FABRIC = 0,
	FCOE_MODE_VN2VN = 1,
};

static bool force_legacy;
static sigset_t block_sigset;

void fcm_vlan_disc_timeout(void *arg);

/*
 * fcoe service configuration data
 * Note: These information are read in from the fcoe service
 *       files in CONFIG_DIR
 */
struct fcoe_port {
	struct fcoe_port *next;

	/* information from fcoe configuration files in CONFIG_DIR */
	char ifname[IFNAMSIZ];       /* netif on which fcoe i/f is created */
	char real_ifname[IFNAMSIZ];  /* underlying net ifname - e.g. if ifname
					is a VLAN */
	int fcoe_enable;
	int dcb_required;
	enum fcoe_mode mode;
	bool fip_resp;
	int auto_vlan;
	int auto_created;
	int ready;

	/* following track data required to manage FCoE interface state */
	enum fcp_action action;      /* current state */
	enum fcp_action last_action; /* last action */
	int last_msg_type;     /* last rtnetlink msg type received on if name */
	struct sock_info *sock_reply;

	int ifindex;
	unsigned char mac[ETHER_ADDR_LEN];
	struct sa_timer vlan_disc_timer;
	int vlan_disc_count;
	int fip_socket;
	int fip_responder_socket;
	char fchost[FCHOSTBUFLEN];
	char ctlr[FCHOSTBUFLEN];
	uint32_t last_fc_event_num;
};

enum fcoeport_ifname {
	FCP_CFG_IFNAME = 0,
	FCP_REAL_IFNAME
};

/*
 * Interact with DCB daemon.
 */
static void fcm_dcbd_timeout(void *);
static void fcm_dcbd_retry_timeout(void *);
static void fcm_dcbd_disconnect(void);
static int fcm_dcbd_request(char *);
static void fcm_dcbd_rx(void *);
static void fcm_dcbd_event(char *, size_t);
static void fcm_dcbd_cmd_resp(char *, cmd_status);
static void fcm_netif_advance(struct fcm_netif *);
static void fcm_fcoe_action(struct fcoe_port *);
static void fcp_set_next_action(struct fcoe_port *, enum fcp_action);
static enum fcoe_status fcm_fcoe_if_action(char *, char *);

/*
 * Used for backwards compatibility amongst libfcoe
 * "control" interfaces.
 */
struct libfcoe_interface_template {
	enum fcoe_status (*create)(struct fcoe_port *);
	enum fcoe_status (*destroy)(struct fcoe_port *);
	enum fcoe_status (*enable)(struct fcoe_port *);
	enum fcoe_status (*disable)(struct fcoe_port *);
};

static const struct libfcoe_interface_template *libfcoe_control;

static enum fcoe_status fcm_module_create(struct fcoe_port *p)
{
	enum fcoe_status rc;

	switch (p->mode) {
	case FCOE_MODE_VN2VN:
		rc = fcm_fcoe_if_action(FCOE_CREATE_VN2VN, p->ifname);
		break;

	case FCOE_MODE_FABRIC:
	default:
		rc = fcm_fcoe_if_action(FCOE_CREATE, p->ifname);
		break;
	}
	if (rc)
		return rc;

	/*
	 * This call validates that the interface name
	 * has an active fcoe session by checking for
	 * the fc_host in sysfs.
	 */
	if (fcoe_find_fchost(p->ifname, p->fchost, FCHOSTBUFLEN)) {
		FCM_LOG_DBG("Failed to find fc_host for %s\n", p->ifname);
		return ENOSYSFS;
	}

	return SUCCESS;
}

static enum fcoe_status fcm_module_destroy(struct fcoe_port *p)
{
	return fcm_fcoe_if_action(FCOE_DESTROY, p->ifname);
}

static enum fcoe_status fcm_module_enable(struct fcoe_port *p)
{
	return fcm_fcoe_if_action(FCOE_ENABLE, p->ifname);
}

static enum fcoe_status fcm_module_disable(struct fcoe_port *p)
{
	return fcm_fcoe_if_action(FCOE_DISABLE, p->ifname);
}

static struct libfcoe_interface_template libfcoe_module_tmpl = {
	.create = fcm_module_create,
	.destroy = fcm_module_destroy,
	.enable = fcm_module_enable,
	.disable = fcm_module_disable,
};

static enum fcoe_status fcm_bus_enable(struct fcoe_port *p)
{
	return fcm_write_str_to_ctlr_attr(p->ctlr, FCOE_CTLR_ATTR_ENABLED, "1");
}

static int fcm_bus_configure(struct fcoe_port *p)
{
	int rc;

	if (p->mode != FCOE_MODE_VN2VN)
		return 0;

	rc = fcm_write_str_to_ctlr_attr(p->ctlr, FCOE_CTLR_ATTR_MODE, "vn2vn");
	return rc;
}

static enum fcoe_status fcm_bus_create(struct fcoe_port *p)
{
	enum fcoe_status rc;

	rc = fcm_write_str_to_sysfs_file(FCOE_BUS_CREATE, p->ifname);
	if (rc)
		return rc;

	/*
	 * This call validates that the interface name
	 * has an active fcoe session by checking for
	 * the fc_host in sysfs.
	 */
	if (fcoe_find_fchost(p->ifname, p->fchost, FCHOSTBUFLEN)) {
		FCM_LOG_DBG("Failed to find fc_host for %s\n", p->ifname);
		return ENOSYSFS;
	}

	/*
	 * The fcoe_ctlr_device lookup only happens when the fcoe_sysfs
	 * kernel interfaces are used. It is a defect if p->ctlr is used
	 * outside of these abstracted routines.
	 */
	if (fcoe_find_ctlr(p->fchost, p->ctlr, FCHOSTBUFLEN)) {
		FCM_LOG_DBG("Failed to get ctlr for %s\n", p->ifname);
		return ENOSYSFS;
	}

	rc = fcm_bus_configure(p);
	if (!rc)
		rc = fcm_bus_enable(p);

	return rc;
}

static enum fcoe_status fcm_bus_destroy(struct fcoe_port *p)
{
	return fcm_write_str_to_sysfs_file(FCOE_BUS_DESTROY, p->ifname);
}

static enum fcoe_status fcm_bus_disable(struct fcoe_port *p)
{
	return fcm_write_str_to_ctlr_attr(p->ctlr, FCOE_CTLR_ATTR_ENABLED, "0");
}

static struct libfcoe_interface_template libfcoe_bus_tmpl = {
	.create = fcm_bus_create,
	.destroy = fcm_bus_destroy,
	.enable = fcm_bus_enable,
	.disable = fcm_bus_disable,
};

struct fcm_clif {
	int cl_fd;
	int cl_busy;		/* non-zero if command pending */
	int cl_ping_pending;
	struct sockaddr_un cl_local;
};

static struct fcm_clif fcm_clif_st;
static struct fcm_clif *fcm_clif = &fcm_clif_st;
static struct sa_timer fcm_dcbd_timer;

/* Debugging routine */
static void print_errors(int errors);

struct fcm_netif_head fcm_netif_head = TAILQ_HEAD_INITIALIZER(fcm_netif_head);

static int fcm_fc_socket;

static int fcm_link_socket;
static int fcm_link_seq;
static void fcm_link_recv(void *);
static void fcm_link_getlink(void);
static void clear_dcbd_info(struct fcm_netif *ff);
static int fcoe_vid_from_ifname(const char *ifname);

/*
 * Table for getopt_long(3).
 */
static struct option fcm_options[] = {
	{"debug", 1, NULL, 'd'},
	{"legacy", 0, NULL, 'l'},
	{"syslog", 1, NULL, 's'},
	{"exec", 1, NULL, 'e'},
	{"foreground", 0, NULL, 'f'},
	{"version", 0, NULL, 'v'},
	{NULL, 0, NULL, 0}
};

char progname[20];

/*
 * Issue with buffer size:  It isn't clear how to read more than one
 * buffer's worth of GETLINK replies.  The kernel seems to just drop the
 * interface messages if they don't fit in the buffer, so we just make it
 * large enough to fit and expand it if we ever do a read that almost fills it.
 */
static char *fcm_link_buf;
static size_t fcm_link_buf_size = 8192;	/* initial size */

/*
 * A value must be surrounded by quates, e.g. "x".
 * The minimum length of a value is 1 excluding the quotes.
 * The maximum length of a value is 20 excluding the quotes.
 */
static int fcm_remove_quotes(char *buf, int len)
{
	char *s = buf;
	char *e = buf + len - 1;
	char tmp[CONFIG_MAX_VAL_LEN + 1];

	if (len < CONFIG_MIN_VAL_LEN)
		return -1;
	if ((*s >= '0' && *s <= '9') ||
	    (*s >= 'a' && *s <= 'z') ||
	    (*s >= 'A' && *s <= 'Z'))
		return -1;
	if ((*e >= '0' && *e <= '9') ||
	    (*e >= 'a' && *e <= 'z') ||
	    (*e >= 'A' && *e <= 'Z'))
		return -1;
	s = buf + 1;
	*e = '\0';
	strncpy(tmp, s, len - 1);
	strncpy(buf, tmp, len - 1);

	return 0;
}

/*
 * Read a configuration variable for a port from a config file.
 * There's no problem if the file doesn't exist.
 * The buffer is set to an empty string if the variable is not found.
 *
 * Returns:  1    found
 *           0    not found
 *           -1   error in format
 */
static size_t fcm_read_config_variable(char *file, char *val_buf, size_t len,
				       FILE *fp, const char *var_name)
{
	char *s;
	char *var;
	char *val;
	char buf[FILE_NAME_LEN];
	int n;

	val_buf[0] = '\0';
	buf[sizeof(buf) - 1] = '\0';
	rewind(fp);
	while ((s = fgets(buf, sizeof(buf) - 1, fp)) != NULL) {
		while (isspace(*s))
			s++;
		if (*s == '\0' || *s == '#')
			continue;
		var = s;
		if (!isalpha(*var))
			continue;
		val = strchr(s, '=');
		if (val == NULL)
			continue;
		*val++ = '\0';
		s = val;
		if (strcmp(var_name, var) != 0)
			continue;
		while (*s != '\0' && !isspace(*s))
			s++;
		*s = '\0';
		n = snprintf(val_buf, len, "%s", val);
		if (fcm_remove_quotes(val_buf, n) < 0) {
			FCM_LOG("Invalid format in config file"
				" %s: %s=%s\n",
				file, var_name, val);
			/* error */
			return -1;
		}
		/* found */
		FCM_LOG_DBG("%s: %s = %s\n", file, var_name, val);
		return 1;
	}
	/* not found */
	return 0;
}

static struct fcoe_port *alloc_fcoe_port(char *ifname)
{
	struct fcoe_port *p = NULL;

	p = (struct fcoe_port *) calloc(1, sizeof(struct fcoe_port));
	if (p) {
		snprintf(p->ifname, sizeof(p->ifname), "%s", ifname);
		p->action = FCP_WAIT;
		/* last_action is initialized to FCP_DESTROY_IF to indicate
		 * that the interface is not created yet.
		 */
		p->last_action = FCP_DESTROY_IF;
		p->fip_socket = -1;
		p->fip_responder_socket = -1;
		p->fchost[0] = '\0';
		p->last_fc_event_num = 0;
		sa_timer_init(&p->vlan_disc_timer, fcm_vlan_disc_timeout, p);
		p->ready = 1;
	}

	return p;
}

static bool real_ifname_from_name(char *real_ifname, const char *ifname)
{
	const char *sep;

	sep = index(ifname, '.');
	if (!sep)
		return false;
	memset(real_ifname, 0, IFNAMSIZ);
	memcpy(real_ifname, ifname, sep - ifname);
	return true;
}

static int fcm_read_config_files(void)
{
	char file[80];
	FILE *fp;
	char val[CONFIG_MAX_VAL_LEN + 1];
	DIR *dir;
	struct dirent *dp;
	struct fcoe_port *curr = NULL;
	struct fcoe_port *next = NULL;
	int rc;

	sigprocmask(SIG_BLOCK, &block_sigset, NULL);

	dir = opendir(CONFIG_DIR);
	if (dir == NULL) {
		FCM_LOG_ERR(errno, "Failed reading directory %s\n", CONFIG_DIR);
		return -1;
	}
	for (;;) {
		dp = readdir(dir);
		if (dp == NULL)
			break;
		if (dp->d_name[0] == '.' &&
		    (dp->d_name[1] == '\0' ||
		     (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
			continue;
		rc = strncmp(dp->d_name, CFG_FILE_PREFIX,
			     strlen(CFG_FILE_PREFIX));
		if (rc)
			continue;

		if (!strncmp(dp->d_name, DEF_CFG_FILE,
			     strlen(DEF_CFG_FILE)))
			continue;

		next = alloc_fcoe_port(dp->d_name + 4);

		if (!next) {
			FCM_LOG_ERR(errno, "failed to allocate fcoe_port %s",
				    dp->d_name);
			continue;
		}
		strncpy(file, CONFIG_DIR "/", sizeof(file));
		strncat(file, dp->d_name, sizeof(file) - strlen(file));
		file[sizeof(file) - 1] = '\0';
		fp = fopen(file, "r");
		if (!fp) {
			FCM_LOG_ERR(errno, "Failed to read %s\n", file);
			free(next);
			continue;
		}

		real_ifname_from_name(next->real_ifname, next->ifname);

		/* FCOE_ENABLE */
		rc = fcm_read_config_variable(file, val, sizeof(val),
					      fp, CFG_IF_VAR_FCOEENABLE);
		if (rc < 0) {
			FCM_LOG("Invalid format for %s variable in %s",
				CFG_IF_VAR_FCOEENABLE, file);
			fclose(fp);
			free(next);
			continue;
		}
		/* if not found, default to "no" */
		if (!strncasecmp(val, "yes", 3) && rc == 1)
			next->fcoe_enable = 1;

		/* DCB_REQUIRED */
		rc = fcm_read_config_variable(file, val, sizeof(val),
					      fp, CFG_IF_VAR_DCBREQUIRED);
		if (rc < 0) {
			FCM_LOG("Invalid format for %s variable in %s",
				CFG_IF_VAR_DCBREQUIRED, file);
			fclose(fp);
			free(next);
			continue;
		}
		/* if not found, default to "no" */
		if (!strncasecmp(val, "yes", 3) && rc == 1)
			next->dcb_required = 1;

		if (next->dcb_required == 1 && fcoe_config.dcb_init == 0)
			fcoe_config.dcb_init = 1;

		/* AUTO_VLAN */
		rc = fcm_read_config_variable(file, val, sizeof(val),
					      fp, CFG_IF_VAR_AUTOVLAN);
		if (rc < 0) {
			FCM_LOG("Invalid format for %s variable in %s",
				CFG_IF_VAR_AUTOVLAN, file);
			fclose(fp);
			free(next);
			continue;
		}
		/* if not found, default to "no" */
		if (!strncasecmp(val, "yes", 3) && rc == 1)
			next->auto_vlan = 1;

		/* FIP_RESP */
		rc = fcm_read_config_variable(file, val, sizeof(val),
					      fp, CFG_IF_VAR_FIP_RESP);
		if (rc < 0) {
			FCM_LOG("Invalid format for %s variable in %s",
				CFG_IF_VAR_FIP_RESP, file);
			fclose(fp);
			free(next);
			continue;
		}
		/* if not found, default to "none" */
		next->fip_resp = false;
		if (!strcasecmp(val, "yes") && rc == 1) {
			FCM_LOG("Starting FIP responder on %s", next->ifname);
			next->fip_resp = true;
		}

		/* MODE */
		rc = fcm_read_config_variable(file, val, sizeof(val),
					      fp, CFG_IF_VAR_MODE);
		if (rc < 0) {
			FCM_LOG("Invalid format for %s variable in %s",
				CFG_IF_VAR_MODE, file);
			fclose(fp);
			free(next);
			continue;
		}
		/* if not found, default to "fabric" */
		next->mode = FCOE_MODE_FABRIC;
		if (!strncasecmp(val, "vn2vn", 5) && rc == 1)
			next->mode = FCOE_MODE_VN2VN;

		fclose(fp);

		if (!fcoe_config.port) {
			fcoe_config.port = next;
			curr = next;
		} else {
			curr->next = next;
			curr = next;
		}
	}
	closedir(dir);

	sigprocmask(SIG_UNBLOCK, &block_sigset, NULL);

	return 0;
}

/*
 * Given an fcoe_port pointer and an ifname, find the next fcoe_port
 * in the list with a real ifname of 'ifname'.
 *
 * Returns:  fcoe_port pointer to fcoe port entry
 *           NULL - if not found
 */
static struct fcoe_port *fcm_find_next_fcoe_port(struct fcoe_port *p,
						 char *ifname)
{
	struct fcoe_port *np;
	struct fcoe_port *found_port = NULL;

	sigprocmask(SIG_BLOCK, &block_sigset, NULL);

	np = fcoe_config.port;
	while (np) {
		if (np == p)
			break;
		np = np->next;
	}

	if (np)
		np = np->next;

	while (np) {
		if (!strncmp(ifname, np->real_ifname, IFNAMSIZ)) {
			found_port = np;
			break;
		}
		np = np->next;
	}

	sigprocmask(SIG_UNBLOCK, &block_sigset, NULL);

	return found_port;
}

static struct fcoe_port *fcm_find_fcoe_port(char *ifname,
					    enum fcoeport_ifname t)
{
	struct fcoe_port *p;
	struct fcoe_port *found_port = NULL;
	char *fp_ifname;

	sigprocmask(SIG_BLOCK, &block_sigset, NULL);

	p = fcoe_config.port;
	while (p) {
		switch (t) {
		case FCP_CFG_IFNAME:
			fp_ifname = p->ifname;
			break;
		case FCP_REAL_IFNAME:
			fp_ifname = p->real_ifname;
			break;
		default:
			FCM_LOG("unhandled interface type [%d] for %s",
				t, ifname);
			goto found;
		}

		if (!strncmp(ifname, fp_ifname, IFNAMSIZ)) {
			found_port = p;
			goto found;
		}

		p = p->next;
	}

found:
	sigprocmask(SIG_UNBLOCK, &block_sigset, NULL);
	return found_port;
}

static struct fcoe_port *fcm_find_port_by_host(uint16_t host_no)
{
	struct fcoe_port *p;
	struct fcoe_port *found_port = NULL;
	char host[FCHOSTBUFLEN];

	sigprocmask(SIG_BLOCK, &block_sigset, NULL);

	snprintf(host, FCHOSTBUFLEN, "host%d", host_no);
	p = fcoe_config.port;
	while (p) {
		if (!strncmp(p->fchost, host, FCHOSTBUFLEN)) {
			found_port = p;
			break;
		}
		p = p->next;
	}

	sigprocmask(SIG_UNBLOCK, &block_sigset, NULL);

	return found_port;
}

static void fcm_fc_event_handler(struct fc_nl_event *fc_event)
{
	struct fcoe_port *p = fcm_find_port_by_host(fc_event->host_no);

	if (!p)
		return;

	switch (fc_event->event_code) {
	case HBA_EVENT_LIP_RESET_OCCURRED:
		if (!p->last_fc_event_num &&
		    fc_event->event_num == p->last_fc_event_num)
			return;

		if (!p->auto_created && !p->auto_vlan)
			return;

		p->last_fc_event_num = fc_event->event_num;

		/* find real interface port and re-activate again */
		p = fcm_find_fcoe_port(p->real_ifname, FCP_CFG_IFNAME);
		if (p && p->last_action != FCP_DISABLE_IF)
			fcp_set_next_action(p, FCP_ACTIVATE_IF);
		break;
	default:
		FCM_LOG("unsupported fc event:%d for host:%d\n",
			fc_event->event_code, fc_event->host_no);
	}
}

static int log_nlmsg_error(struct nlmsghdr *hp, size_t rlen, const char *str)
{
	struct nlmsgerr *ep;

	if (NLMSG_OK(hp, rlen)) {
		ep = (struct nlmsgerr *)NLMSG_DATA(hp);
		FCM_LOG_DBG("%s, err=%d, type=%d\n",
			    str, ep->error, ep->msg.nlmsg_type);
		return ep->error;
	} else {
		FCM_LOG("%s", str);
		return 0;
	}
}

static void fcm_fc_event_log(struct fc_nl_event *fe)
{
	/* from kernel "include/scsi/scsi_transport_fc.h" */
	enum fc_host_event_code  {
		FCH_EVT_LIP                     = 0x1,
		FCH_EVT_LINKUP                  = 0x2,
		FCH_EVT_LINKDOWN                = 0x3,
		FCH_EVT_LIPRESET                = 0x4,
		FCH_EVT_RSCN                    = 0x5,
		FCH_EVT_ADAPTER_CHANGE          = 0x103,
		FCH_EVT_PORT_UNKNOWN            = 0x200,
		FCH_EVT_PORT_OFFLINE            = 0x201,
		FCH_EVT_PORT_ONLINE             = 0x202,
		FCH_EVT_PORT_FABRIC             = 0x204,
		FCH_EVT_LINK_UNKNOWN            = 0x500,
		FCH_EVT_VENDOR_UNIQUE           = 0xffff,
	};
	/* from kernel "drivers/scsi/scsi_transport_fc.c" */
	const struct {
		enum fc_host_event_code         value;
		char                            *name;
	} fc_host_event_code_names[] = {
		{ FCH_EVT_LIP,                  "lip" },
		{ FCH_EVT_LINKUP,               "link_up" },
		{ FCH_EVT_LINKDOWN,             "link_down" },
		{ FCH_EVT_LIPRESET,             "lip_reset" },
		{ FCH_EVT_RSCN,                 "rscn" },
		{ FCH_EVT_ADAPTER_CHANGE,       "adapter_chg" },
		{ FCH_EVT_PORT_UNKNOWN,         "port_unknown" },
		{ FCH_EVT_PORT_ONLINE,          "port_online" },
		{ FCH_EVT_PORT_OFFLINE,         "port_offline" },
		{ FCH_EVT_PORT_FABRIC,          "port_fabric" },
		{ FCH_EVT_LINK_UNKNOWN,         "link_unknown" },
		{ FCH_EVT_VENDOR_UNIQUE,        "vendor_unique" },
	};
	unsigned int i;

	for (i = 0; i < ARRAY_SIZE(fc_host_event_code_names); i++) {
		if (fe->event_code == fc_host_event_code_names[i].value) {
			/* only do u32 data even len is not, e.g. vendor */
			FCM_LOG_DBG("FC_HOST_EVENT %d at %" PRIu64 " secs on "
				    "host%d code %d=%s datalen %d data=%d\n",
				    fe->event_num, fe->seconds,
				    fe->host_no, fe->event_code,
				    fc_host_event_code_names[i].name,
				    fe->event_datalen, fe->event_data);
			break;
		}
	}

}

static void fcm_fc_event_recv(UNUSED void *arg)
{
	struct nlmsghdr *hp;
	struct fc_nl_event *fc_event;
	size_t plen;
	size_t rlen;
	char *buf;
	int rc;

	buf = malloc(DEF_RX_BUF_SIZE);

	if (!buf) {
		FCM_LOG_ERR(errno, "failed to allocate FC event buffer\n");
		return;
	}

	rc = read(fcm_fc_socket, buf, DEF_RX_BUF_SIZE);
	if (!rc)
		goto free_buf;

	if (rc < 0) {
		FCM_LOG_ERR(errno, "fc read error");
		goto free_buf;
	}

	hp = (struct nlmsghdr *)buf;
	rlen = rc;
	for (hp = (struct nlmsghdr *)buf; NLMSG_OK(hp, rlen);
	     hp = NLMSG_NEXT(hp, rlen)) {

		if (hp->nlmsg_type == NLMSG_DONE)
			break;

		if (hp->nlmsg_type == NLMSG_ERROR) {
			log_nlmsg_error(hp, rlen, "fc nlmsg error");
			break;
		}

		plen = NLMSG_PAYLOAD(hp, 0);
		fc_event = (struct fc_nl_event *)NLMSG_DATA(hp);
		if (plen < sizeof(*fc_event)) {
			FCM_LOG("too short (%zu) to be an FC event", rlen);
			break;
		}
		fcm_fc_event_log(fc_event);
		fcm_fc_event_handler(fc_event);
	}
free_buf:
	free(buf);
}

static int fcm_fc_events_init(void)
{
	int fd, rc;
	struct sockaddr_nl fc_local;

	fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_SCSITRANSPORT);
	if (fd < 0) {
		FCM_LOG_ERR(errno, "fc socket error");
		return fd;
	}
	memset(&fc_local, 0, sizeof(fc_local));
	fc_local.nl_family = AF_NETLINK;
	fc_local.nl_groups = ~0;
	fc_local.nl_pid = getpid();
	rc = bind(fd, (struct sockaddr *)&fc_local, sizeof(fc_local));
	if (rc == -1) {
		FCM_LOG_ERR(errno, "fc socket bind error");
		close(fd);
		return rc;
	}
	fcm_fc_socket = fd;

	/* Add a given file descriptor readfds set with its rx handler */
	sa_select_add_fd(fd, fcm_fc_event_recv, NULL, NULL, NULL);
	return 0;
}

static int fcm_link_init(void)
{
	int fd;
	int rc;
	struct sockaddr_nl l_local;

	fcm_link_buf = malloc(fcm_link_buf_size);
	ASSERT(fcm_link_buf);

	fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
	if (fd < 0) {
		FCM_LOG_ERR(errno, "socket error");
		return fd;
	}
	memset(&l_local, 0, sizeof(l_local));
	l_local.nl_family = AF_NETLINK;
	l_local.nl_groups = RTMGRP_LINK | (1 << (RTNLGRP_DCB - 1));
	l_local.nl_pid = 0;
	rc = bind(fd, (struct sockaddr *)&l_local, sizeof(l_local));
	if (rc == -1) {
		FCM_LOG_ERR(errno, "bind error");
		close(fd);
		return rc;
	}
	fcm_link_socket = fd;

	/* Add a given file descriptor from a readfds set */
	sa_select_add_fd(fd, fcm_link_recv, NULL, NULL, NULL);

	fcm_link_getlink();

	return 0;
}

static struct fcoe_port *
fcm_port_create(char *ifname, enum clif_flags flags, int cmd);

static struct fcoe_port *fcm_new_vlan(int ifindex, int vid, bool vn2vn)
{
	char real_name[IFNAMSIZ];
	char vlan_name[IFNAMSIZ];
	struct fcoe_port *p;
	static const int flags[] = {
		[false] = CLIF_FLAGS_FABRIC,
		[true] = CLIF_FLAGS_VN2VN,
	};
	int rc;

	if (vn2vn)
		FCM_LOG_DBG("Auto VLAN found vn2vn on VID %d\n", vid);
	else
		FCM_LOG_DBG("Auto VLAN Found FCF on VID %d\n", vid);

	if (rtnl_find_vlan(ifindex, vid, vlan_name)) {
		rtnl_get_linkname(ifindex, real_name);
		rc = snprintf(vlan_name, sizeof(vlan_name), FCOE_VLAN_FORMAT,
			      real_name, vid);
		if (rc < 0 || (size_t) rc >= sizeof(vlan_name)) {
			FCM_LOG("Warning: Generating FCoE VLAN device name for"
				"interface %s VLAN %d: format resulted in a"
				"name larger than IFNAMSIZ\n", real_name, vid);
			vlan_name[sizeof(vlan_name) - 1] = 0;
			FCM_LOG("\tTruncating VLAN name to %s\n", vlan_name);
		}
		vlan_create(ifindex, vid, vlan_name);
	}
	rtnl_set_iff_up(0, vlan_name);
	p = fcm_find_fcoe_port(vlan_name, FCP_CFG_IFNAME);
	if (p && !p->fcoe_enable)
		return p;
	p = fcm_port_create(vlan_name, flags[vn2vn], FCP_ACTIVATE_IF);
	p->auto_created = 1;
	return p;
}

static int
fcm_vlan_disc_handler(struct fiphdr *fh, struct sockaddr_ll *sa, void *arg)
{
	int vid;
	unsigned char mac[ETHER_ADDR_LEN];
	int len = ntohs(fh->fip_desc_len);
	struct fip_tlv_hdr *tlv = (struct fip_tlv_hdr *)(fh + 1);
	struct fcoe_port *p = arg;
	struct fcoe_port *vp;
	int desc_mask = 0;
	bool vn2vn = false;

	enum {
		VALID_MAC	= 1,
		VALID_VLAN	= 2,
	};

	if (ntohs(fh->fip_proto) != FIP_PROTO_VLAN)
		return -1;

	if (fh->fip_subcode == FIP_VLAN_NOTE_VN2VN &&
	    (!p->auto_vlan || p->mode != FCOE_MODE_VN2VN)) {
		FCM_LOG_DBG("%s: vn2vn vlan notif: auto_vlan=%d, mode=%d\n",
			    __func__, p->auto_vlan, p->mode);
		return -1;
	}

	if (fh->fip_subcode != FIP_VLAN_NOTE &&
	    fh->fip_subcode != FIP_VLAN_NOTE_VN2VN) {
		FCM_LOG_DBG("%s: fip_subcode=%d\n", __func__, fh->fip_subcode);
		return -1;
	}

	if (fh->fip_subcode == FIP_VLAN_NOTE_VN2VN)
		vn2vn = true;

	while (len > 0) {
		switch (tlv->tlv_type) {
		case FIP_TLV_MAC_ADDR:
			memcpy(mac, ((struct fip_tlv_mac_addr *)tlv)->mac_addr,
			       ETHER_ADDR_LEN);
			desc_mask |= VALID_MAC;
			break;
			/*
			 * this expects to see the MAC_ADDR TLV first,
			 * and is broken if not
			 */
		case FIP_TLV_VLAN:
			if (tlv->tlv_len != 1) {
				FCM_LOG_ERR(EINVAL, "bad length on VLAN TLV");
				break;
			}
			vid = ntohs(((struct fip_tlv_vlan *)tlv)->vlan);
			FCM_LOG_DBG("%s: vid=%d\n", __func__, vid);
			if (vid) {
				vp = fcm_new_vlan(sa->sll_ifindex, vid, vn2vn);
				vp->dcb_required = p->dcb_required;
			} else {
				/* We received a 0 vlan id. Activate the
				 * physical port itself
				 */
				fcp_set_next_action(p, FCP_ACTIVATE_IF);
				p->auto_vlan = 0;
			}
			desc_mask |= VALID_VLAN;
			break;
		default:
			/* unexpected or unrecognized descriptor */
			FCM_LOG_DBG("ignoring TLV type %d", tlv->tlv_type);
			break;
		}
		len -= tlv->tlv_len;
		tlv = ((void *) tlv) + (tlv->tlv_len << 2);
	};

	if (desc_mask == (VALID_MAC | VALID_VLAN)) {
		/* cancel the retry timer, valid response received */
		sa_timer_cancel(&p->vlan_disc_timer);
		return 0;
	} else {
		return -1;
	}
}

static void fcm_fip_recv(void *arg)
{
	struct fcoe_port *p = arg;
	fip_recv(p->fip_socket, fcm_vlan_disc_handler, p);
}

static int fcm_vlan_disc_socket(struct fcoe_port *p)
{
	int fd;
	int origdev = 1;

	fd = fip_socket(p->ifindex, p->mac, FIP_NONE);
	if (fd < 0) {
		FCM_LOG_ERR(errno, "socket error");
		return fd;
	}
	setsockopt(fd, SOL_PACKET, PACKET_ORIGDEV, &origdev, sizeof(origdev));
	sa_select_add_fd(fd, fcm_fip_recv, NULL, NULL, p);
	return fd;
}


/* fcm_vlan_dev_real_dev - query vlan real_dev
 * @vlan_ifname - vlan device ifname to find real interface name for
 * @real_ifname - pointer to copy real ifname to
 *
 * Make an ioctl call to find the real device for vlan_ifname.
 * Copy to real_ifname if found.
 */
static void fcm_vlan_dev_real_dev(char *vlan_ifname, char *real_ifname)
{
	int fd;
	struct vlan_ioctl_args ifv;
	int rc;

	real_ifname[0] = '\0';

	fd = socket(PF_INET, SOCK_DGRAM, 0);

	if (fd < 0) {
		FCM_LOG_ERR(errno, "open vlan query socket error");
		return;
	}

	memset(&ifv, 0, sizeof(ifv));
	ifv.cmd = GET_VLAN_REALDEV_NAME_CMD;
	if (strlen(vlan_ifname) > (sizeof(ifv.device1) - 1)) {
		FCM_LOG_ERR(ENOSPC, "no room for vlan ifname");
		goto close_fd;
	}

	rc = snprintf(ifv.device1, IFNAMSIZ, "%s", vlan_ifname);
	if (rc < 0 || rc >= IFNAMSIZ)
		goto close_fd;

	if (ioctl(fd, SIOCGIFVLAN, &ifv) == 0) {
		rc = snprintf(real_ifname, IFNAMSIZ, "%s", ifv.u.device2);
		if (rc < 0 || rc >= IFNAMSIZ) {
			real_ifname[0] = '\0';
			goto close_fd;
		}
	}
close_fd:
	close(fd);
}

/* fcm_is_linkinfo_vlan - parse nlmsg linkinfo rtattr for vlan kind
 * @ap: pointer to the linkinfo rtattr
 *
 * This function parses the linkinfo rtattr and returns
 * 1 if it is kind vlan otherwise returns 0.
 */
static int fcm_is_linkinfo_vlan(struct rtattr *ap)
{
	struct rtattr *info;
	int len;

	info = (struct rtattr *) (RTA_DATA(ap));

	for (len = ap->rta_len; RTA_OK(info, len); info = RTA_NEXT(info, len)) {
		if (info->rta_type != IFLA_INFO_KIND)
			continue;

		if (strncmp("vlan", RTA_DATA(info), sizeof("vlan")))
			return 0;
		else
			return 1;
	}

	return 0;
}


/* fcm_set_next_action - determine the next action for the FCoE interface
 * @p - pointer to the fcoe_port structure for the FCoE interface
 * @action - requested next action to take on the FCoE interface
 *
 * Based on the last_action taken on the FCoE interface and the requested
 * next action, the next action field in the FCoE interface's fcoe_port
 * structure is set.
 * Notes: last_action is initialized to FCP_DESTROY_IF when the fcoe_port is
 *        created and it is never set to FCP_WAIT.
 *        The requested action FCP_ACTIVATE_IF is resolved to either
 *        FCP_CREATE_IF or FCP_ENABLE_IF as appropriate.
 */
static void fcp_set_next_action(struct fcoe_port *p, enum fcp_action action)
{
	switch (p->last_action) {
	case FCP_CREATE_IF:
		switch (action) {
		case FCP_DESTROY_IF:
		case FCP_ENABLE_IF:
		case FCP_DISABLE_IF:
		case FCP_RESET_IF:
		case FCP_SCAN_IF:
			p->action = action;
			break;
		case FCP_ACTIVATE_IF:
			if (p->auto_vlan)
				p->action = FCP_VLAN_DISC;
			else
				p->action = FCP_ENABLE_IF;
			break;
		default:
			p->action = FCP_WAIT;
			break;
		}
		break;
	case FCP_DESTROY_IF:
		switch (action) {
		case FCP_CREATE_IF:
		case FCP_ACTIVATE_IF:
		case FCP_ENABLE_IF:
			if (p->auto_vlan)
				p->action = FCP_VLAN_DISC;
			else if (p->fcoe_enable)
				p->action = FCP_CREATE_IF;
			else
				p->action = FCP_WAIT;
			break;
		default:
			p->action = FCP_WAIT;
			break;
		}
		break;
	case FCP_ENABLE_IF:
		switch (action) {
		case FCP_DESTROY_IF:
		case FCP_DISABLE_IF:
		case FCP_RESET_IF:
		case FCP_SCAN_IF:
			p->action = action;
			break;
		default:
			p->action = FCP_WAIT;
			break;
		}
		break;
	case FCP_DISABLE_IF:
		switch (action) {
		case FCP_DESTROY_IF:
		case FCP_RESET_IF:
			p->action = action;
			break;
		case FCP_ENABLE_IF:
		case FCP_ACTIVATE_IF:
			if (p->auto_vlan)
				p->action = FCP_VLAN_DISC;
			else
				p->action = FCP_ENABLE_IF;
			break;
		default:
			p->action = FCP_WAIT;
			break;
		}
		break;
	case FCP_RESET_IF:
	case FCP_SCAN_IF:
		switch (action) {
		case FCP_DESTROY_IF:
		case FCP_DISABLE_IF:
		case FCP_RESET_IF:
		case FCP_SCAN_IF:
			p->action = action;
			break;
		case FCP_ENABLE_IF:
		case FCP_ACTIVATE_IF:
			if (p->auto_vlan)
				p->action = FCP_VLAN_DISC;
			else
				p->action = FCP_ENABLE_IF;
			break;
		default:
			p->action = FCP_WAIT;
			break;
		}
		break;
	case FCP_VLAN_DISC:
		switch (action) {
		case FCP_ACTIVATE_IF:
			if (p->auto_vlan)
				p->action = FCP_VLAN_DISC;
			else if (p->fcoe_enable)
				p->action = FCP_CREATE_IF;
			else
				p->action = FCP_WAIT;
			break;
		case FCP_DESTROY_IF:
		case FCP_DISABLE_IF:
		case FCP_RESET_IF:
		case FCP_SCAN_IF:
			if (p->fip_socket >= 0) {
				sa_timer_cancel(&p->vlan_disc_timer);
				sa_select_rem_fd(p->fip_socket);
				close(p->fip_socket);
				p->fip_socket = -1;
			}
			p->action = action;
			break;
		default:
			p->action = FCP_WAIT;
			break;
		}
		break;
	default:
		/* last_action is never set to FCP_WAIT */
		break;
	}
}

static void fcp_action_set(char *ifname, enum fcp_action action)
{
	struct fcoe_port *p;

	p = fcm_find_fcoe_port(ifname, FCP_REAL_IFNAME);
	while (p) {
		if (p->fcoe_enable) {
			switch (action) {
			case FCP_ACTIVATE_IF:
				/*
				 * let the VLAN discovery code
				 * enabled auto-VLANs
				 */
				if (!p->auto_created)
					fcp_set_next_action(p, FCP_ACTIVATE_IF);
				else
					fcp_set_next_action(p, FCP_WAIT);
				break;
			default:
				fcp_set_next_action(p, action);
			}
		}
		p = fcm_find_next_fcoe_port(p, ifname);
	}
}

/*
 * Send DCB_CMD_IEEE_GET request for an interface.
 */
static void ieee_get_req(struct fcm_netif *ff)
{
	int iflen;
	int rc;
	int seq;
	struct {
		struct nlmsghdr nl;
		struct dcbmsg dcbmsg;
		struct rtattr rta;
		char ifname[IFNAMSIZ];
	} msg;

	seq = ++fcm_link_seq;
	if (!seq)
		seq = ++fcm_link_seq;

	iflen = strlen(ff->ifname);
	if (iflen >= IFNAMSIZ)
		iflen = IFNAMSIZ - 1;

	memset(&msg, 0, sizeof(msg));
	msg.nl.nlmsg_len = NLMSG_ALIGN(sizeof(msg) - sizeof(msg.ifname) +
				iflen + 1);
	msg.nl.nlmsg_type = RTM_GETDCB;
	msg.nl.nlmsg_flags = NLM_F_REQUEST;
	msg.nl.nlmsg_seq = seq;
	msg.nl.nlmsg_pid = getpid();
	msg.dcbmsg.cmd = DCB_CMD_IEEE_GET;
	msg.dcbmsg.dcb_family = AF_UNSPEC;
	msg.dcbmsg.dcb_pad = 0;
	msg.rta.rta_len = NLMSG_ALIGN(NLA_HDRLEN + iflen + 1);
	msg.rta.rta_type = DCB_ATTR_IFNAME;
	strncpy(msg.ifname, ff->ifname, iflen);
	ff->ieee_resp_pending = seq;
	rc = write(fcm_link_socket, &msg, msg.nl.nlmsg_len);
	if (rc < 0) {
		FCM_LOG_ERR(errno, "%s: %s: write failed\n", __func__,
			    ff->ifname);
		ff->ieee_resp_pending = 0;
	}
}

/*
 * clear_ieee_info - Clear IEEE info to unknown values
 */
static void clear_ieee_info(struct fcm_netif *ff)
{
	ff->ieee_pfc_info = 0;
	ff->ieee_app_info = 0;
	ff->dcbx_cap = 0;
}

STR_ARR(ieee_states, "Unknown", "Out of range",
	[IEEE_INIT] = "IEEE_INIT",
	[IEEE_GET_STATE] = "IEEE_GET_STATE",
	[IEEE_DONE] = "IEEE_DONE",
	[IEEE_ACTIVE] = "IEEE_ACTIVE",
);

static void
ieee_state_set(struct fcm_netif *ff, enum ieee_state new_state)
{
	if (ff->ff_operstate != IF_OPER_UP) {
		ff->ieee_state = IEEE_INIT;
		return;
	}

	if (fcoe_config.debug) {
		FCM_LOG_DEV_DBG(ff, "IEEE state change: %s -> %s",
				getstr(&ieee_states, ff->ieee_state),
				getstr(&ieee_states, new_state));
	}

	if (new_state == IEEE_GET_STATE)
		clear_ieee_info(ff);

	ff->ieee_state = new_state;
	ff->ieee_resp_pending = 0;
}

static struct sa_nameval fcm_dcbd_states[] = FCM_DCBD_STATES;

static void fcm_dcbd_state_set(struct fcm_netif *ff,
			       enum fcm_dcbd_state new_state)
{
	if (ff->ff_operstate != IF_OPER_UP) {
		ff->ff_dcbd_state = FCD_INIT;
		return;
	}

	if (fcoe_config.debug) {
		char old[32];
		char new[32];

		FCM_LOG_DEV_DBG(ff, "DCBD state change: %s -> %s",
				sa_enum_decode(old, sizeof(old),
					       fcm_dcbd_states,
					       ff->ff_dcbd_state),
				sa_enum_decode(new, sizeof(new),
					       fcm_dcbd_states, new_state));
	}

	if (new_state == FCD_GET_DCB_STATE)
		clear_dcbd_info(ff);

	if (new_state == FCD_INIT) {
		ff->dcbd_retry_cnt = 0;
		sa_timer_cancel(&ff->dcbd_retry_timer);
	}

	if (new_state == FCD_ERROR) {
		ff->dcbd_retry_cnt++;
		FCM_LOG_DEV_DBG(ff, "%s: SETTING lldpad RETRY TIMER  = %d\n",
				ff->ifname,
				ff->dcbd_retry_cnt * DCBD_REQ_RETRY_TIMEOUT);
		sa_timer_set(&ff->dcbd_retry_timer,
			     ff->dcbd_retry_cnt * DCBD_REQ_RETRY_TIMEOUT);
	}

	ff->ff_dcbd_state = new_state;
	ff->response_pending = 0;
}

static int fip_recv_vlan_req(struct sockaddr_ll *ssa, struct fcoe_port *sp)
{
	struct fip_tlv_vlan *vlan_tlvs = NULL;
	int vlan_count = 0;
	struct fcoe_port *p;
	int rc;

	/* Handle all FCoE ports which are on VLANs over this ifname. */
	p = fcm_find_fcoe_port(sp->real_ifname, FCP_REAL_IFNAME);
	for (; p; p = fcm_find_next_fcoe_port(p, sp->real_ifname)) {
		int vid;
		struct fip_tlv_vlan *vtp;

		if (!p->ready)
			continue;
		if (p->mode != FCOE_MODE_VN2VN)
			continue;
		vid = fcoe_vid_from_ifname(p->ifname);
		if (vid < 0)
			continue;
		vtp = realloc(vlan_tlvs, sizeof(*vlan_tlvs));
		if (!vtp)
			break;
		memset(&vtp[vlan_count], 0, sizeof(*vtp));
		vtp[vlan_count].hdr.tlv_type = FIP_TLV_VLAN;
		vtp[vlan_count].hdr.tlv_len = 1;
		vtp[vlan_count].vlan = htons(vid);
		++vlan_count;
		vlan_tlvs = vtp;
	}

	FCM_LOG_DBG("%s: %d vlans found\n", __func__, vlan_count);
	rc = fip_send_vlan_notification(sp->fip_responder_socket,
					ssa->sll_ifindex, sp->mac,
					ssa->sll_addr, vlan_tlvs, vlan_count);
	if (rc < 0) {
		FCM_LOG_ERR(-rc, "%s: fip_send_vlan_notification error\n",
			    __func__);
	}
	if (vlan_tlvs)
		free(vlan_tlvs);
	return rc;
}

static int fip_vlan_disc_handler(struct fiphdr *fh, struct sockaddr_ll *sa,
				 void *arg)
{
	struct fcoe_port *p = arg;
	int rc = -1;

	if (ntohs(fh->fip_proto) != FIP_PROTO_VLAN) {
		FCM_LOG_DBG("ignoring FIP packet, protocol %d\n",
			    ntohs(fh->fip_proto));
		return -1;
	}

	switch (fh->fip_subcode) {
	case FIP_VLAN_REQ:
		FCM_LOG_DBG("received VLAN req, subcode=%d\n", fh->fip_subcode);
		rc = fip_recv_vlan_req(sa, p);
		break;
	default:
		FCM_LOG_DBG("ignored FIP VLAN packet with subcode %d\n",
			    fh->fip_subcode);
		break;
	}
	return rc;
}

static void fip_responder(void *arg)
{
	struct fcoe_port *p = arg;

	fip_recv(p->fip_responder_socket, fip_vlan_disc_handler, p);
}

static void init_fip_vn2vn_responder(struct fcoe_port *p)
{
	int s;

	if (p->fip_responder_socket >= 0)
		return;

	s = fip_socket(p->ifindex, p->mac, FIP_ALL_VN2VN);
	if (s < 0) {
		FCM_LOG_ERR(errno, "%s: Failed to get fip socket\n", p->ifname);
		return;
	}
	p->fip_responder_socket = s;
	FCM_LOG_DBG("%s: Adding FIP responder socket\n", p->ifname);
	sa_select_add_fd(s, fip_responder, NULL, NULL, p);
}

static void update_fcoe_port_state(struct fcoe_port *p, unsigned int type,
				   u_int8_t operstate, enum fcoeport_ifname t)
{
	struct fcm_netif *ff = NULL;

	if (type != RTM_DELLINK) {
		ff = fcm_netif_lookup_create(p->real_ifname);
		if (!ff)
			return;

		/* Only set the ff_operstate field of the network interface
		 * element if this routine is being called for the real
		 * network interface, or, if the interface is a VLAN, if the
		 * network interface element has not been intialized and the
		 * VLAN operstate is up (if VLAN is up, then real interface is
		 * up).
		 */
		if ((t == FCP_REAL_IFNAME) ||
		    ((t == FCP_CFG_IFNAME) &&
		     (ff->ff_operstate == IF_OPER_UNKNOWN) &&
		     (operstate == IF_OPER_UP)))
			ff->ff_operstate = operstate;

		if (t == FCP_REAL_IFNAME && p->fip_resp)
			init_fip_vn2vn_responder(p);

		if (!p->fcoe_enable) {
			fcp_set_next_action(p, FCP_DESTROY_IF);
			return;
		}

		if (operstate == IF_OPER_UP) {
			if (p->dcb_required) {
				/* If DCB is required, do not start the dcbd
				 * query sequence if this routine is being
				 * called for a real interface and the FCoE
				 * interface is configured on a VLAN.
				 */
				if (!((t == FCP_REAL_IFNAME) &&
				      strncmp(p->ifname, p->real_ifname,
					      IFNAMSIZ))) {
					fcm_dcbd_state_set(ff,
							   FCD_GET_DCB_STATE);
					ieee_state_set(ff, IEEE_GET_STATE);
				}
			} else {
				/* hold off on auto-created VLAN ports until
				 * VLAN discovery can validate that the setup
				 * has not changed */
				if (!p->auto_created || !p->auto_vlan)
					fcp_set_next_action(p, FCP_ACTIVATE_IF);
			}
		} else {
			fcp_set_next_action(p, FCP_DISABLE_IF);
		}
	} else {
		fcp_set_next_action(p, FCP_DESTROY_IF);
	}
}

static int fcoe_vid_from_ifname(const char *ifname)
{
	int vid = -1;
	int rc;

	if (strlen(ifname) <= strlen(FCOE_VLAN_SUFFIX) ||
	    strcmp(&ifname[strlen(ifname) - strlen(FCOE_VLAN_SUFFIX)],
	           FCOE_VLAN_SUFFIX))
		return vid;
	rc = sscanf(ifname, FCOE_VID_SCAN_FORMAT, &vid);
	if (rc == 1)
		return vid;
	return -1;
}

static void fcm_process_link_msg(struct ifinfomsg *ip, int len, unsigned type)
{
	struct fcoe_port *p;
	struct rtattr *ap;
	char ifname[IFNAMSIZ];
	char real_dev[IFNAMSIZ];
	u_int8_t operstate;
	unsigned char mac[ETHER_ADDR_LEN];
	int is_vlan;
	int ifindex;

	is_vlan = 0;
	operstate = IF_OPER_UNKNOWN;

	ifindex = ip->ifi_index;

	if (ip->ifi_type != ARPHRD_ETHER)
		return;

	len -= sizeof(*ip);
	for (ap = (struct rtattr *)(ip + 1); RTA_OK(ap, len);
	     ap = RTA_NEXT(ap, len)) {
		switch (ap->rta_type) {
		case IFLA_ADDRESS:
			if (RTA_PAYLOAD(ap) == 6)
				memcpy(mac, RTA_DATA(ap), ETHER_ADDR_LEN);
			break;

		case IFLA_IFNAME:
			sa_strncpy_safe(ifname, sizeof(ifname),
					RTA_DATA(ap),
					RTA_PAYLOAD(ap));
			break;

		case IFLA_OPERSTATE:
			operstate = *(uint8_t *) RTA_DATA(ap);
			break;

		case IFLA_LINKINFO:
			if (fcm_is_linkinfo_vlan(ap))
				is_vlan = 1;
			break;

		default:
			break;
		}
	}

	p = fcm_find_fcoe_port(ifname, FCP_CFG_IFNAME);
	if (is_vlan) {
		/* if not in fcoe port list, then ignore this ifname */
		if (!p)
			return;

		p->ifindex = ifindex;
		memcpy(p->mac, mac, ETHER_ADDR_LEN);

		/* don't do VLAN discovery on a VLAN */
		p->auto_vlan = 0;

		/* try to find the real device name */
		real_dev[0] = '\0';
		fcm_vlan_dev_real_dev(ifname, real_dev);
		if (strlen(real_dev)) {
			strncpy(p->real_ifname, real_dev, IFNAMSIZ);
			p->real_ifname[IFNAMSIZ - 1] = '\0';
		}
		if (p->ready)
			update_fcoe_port_state(p, type, operstate,
					       FCP_CFG_IFNAME);
		p->last_msg_type = type;
	} else {
		/* the ifname is not a VLAN.  handle the case where it has
		 * an FCoE interface configured on it.
		 */
		if (p) {
			p->ifindex = ifindex;
			memcpy(p->mac, mac, ETHER_ADDR_LEN);
			strncpy(p->real_ifname, ifname, IFNAMSIZ);
			p->real_ifname[IFNAMSIZ - 1] = '\0';
			update_fcoe_port_state(p, type, operstate,
					       FCP_REAL_IFNAME);
		}

		/* handle all FCoE ports which are on VLANs over this
		 * ifname.
		 */
		p = fcm_find_fcoe_port(ifname, FCP_REAL_IFNAME);
		for (; p; p = fcm_find_next_fcoe_port(p, ifname)) {
			int vid;

			if (!p->ready)
				continue;
			vid = fcoe_vid_from_ifname(p->ifname);
			if (vid >= 0 && p->mode == FCOE_MODE_VN2VN) {
				struct fcoe_port *vp;

				vp = fcm_new_vlan(ifindex, vid, true);
				vp->dcb_required = p->dcb_required;
			}
			update_fcoe_port_state(p, type, operstate,
					       FCP_REAL_IFNAME);
		}
	}
}

static struct rtattr *find_nested_attr(struct rtattr *rta, __u16 type)
{
	struct rtattr *rta_child;

	rta_child = NLA_DATA(rta);
	rta = NLA_NEXT(rta);

	for (; rta > rta_child; rta_child = NLA_NEXT(rta_child))
		if (rta_child->rta_type == type)
			return rta_child;

	return NULL;
}

static struct rtattr *find_attr(struct nlmsghdr *nlh, __u16 type)
{
	struct rtattr *rta;
	int len;

	rta = (struct rtattr *)(((char *)NLMSG_DATA(nlh)) +
		NLMSG_ALIGN(sizeof(struct dcbmsg)));
	len = NLMSG_PAYLOAD(nlh, 0) - sizeof(struct dcbmsg);

	while (RTA_OK(rta, len)) {
		if (rta->rta_type == type)
			return rta;

		rta = RTA_NEXT(rta, len);
	}

	return NULL;
}

static int ieee_get_dcbx(struct nlmsghdr *nlh)
{
	struct rtattr *rta;

	rta = find_attr(nlh, DCB_ATTR_DCBX);
	if (!rta)
		return -EIO;

	return *(__u8 *)NLA_DATA(rta);
}

static int get_pri_mask_from_ieee(struct rtattr *rta, __u8 dcbx_cap)
{
	struct rtattr *rta_parent;
	struct rtattr *rta_child;
	int rval;
	__u8 ieee = dcbx_cap & DCB_CAP_DCBX_VER_IEEE;

	rta_parent = find_nested_attr(rta, DCB_ATTR_IEEE_APP_TABLE);
	if (!rta_parent)
		return -EIO;

	rta_child = NLA_DATA(rta_parent);
	rta_parent = NLA_NEXT(rta_parent);

	rval = 0;
	for (; rta_parent > rta_child; rta_child = NLA_NEXT(rta_child)) {
		struct dcb_app *app;

		if (rta_child->rta_type != DCB_ATTR_IEEE_APP)
			continue;

		app = (struct dcb_app *)NLA_DATA(rta_child);
		if (app->protocol != FCOE_ETH_TYPE)
			continue;

		if (ieee) {
			if (app->selector == IEEE_8021QAZ_APP_SEL_ETHERTYPE)
				rval |= 1 << app->priority;
		} else {
			if (app->selector == DCB_APP_IDTYPE_ETHTYPE)
				return app->priority;
		}
	}

	return rval;
}

static void fcm_process_ieee_msg(struct nlmsghdr *nlh)
{
	struct dcbmsg *d;
	struct rtattr *rta_parent;
	struct rtattr *rta_child;
	struct fcm_netif *ff;
	int dcbx_cap;
	int pri_mask;
	char ifname[IFNAMSIZ];

	d = (struct dcbmsg *)NLMSG_DATA(nlh);
	if (d->cmd != DCB_CMD_IEEE_GET && d->cmd != DCB_CMD_IEEE_SET) {
		FCM_LOG_DBG("Unexpected command type %d\n", d->cmd);
		return;
	}

	rta_parent = (struct rtattr *)(((char *)d) + NLMSG_ALIGN(sizeof(*d)));
	if (rta_parent->rta_type != DCB_ATTR_IFNAME)
		return;

	strncpy(ifname, NLA_DATA(rta_parent), IFNAMSIZ);
	ifname[IFNAMSIZ - 1] = '\0';

	ff = fcm_netif_lookup_create(ifname);
	if (!ff) {
		FCM_LOG("Processing IEEE message: %s not found or created\n",
			ifname);
		return;
	}

	dcbx_cap = ieee_get_dcbx(nlh);
	if (dcbx_cap < 0) {
		FCM_LOG("Processing IEEE message: No DCBx capabilities on %s\n",
			ifname);
		return;
	}
	ff->dcbx_cap = dcbx_cap;
	if (!ff->ff_dcb_state)
		ff->ff_dcb_state = !!(dcbx_cap & DCB_CAP_DCBX_VER_IEEE);
	if (d->cmd == DCB_CMD_IEEE_SET && !(dcbx_cap & DCB_CAP_DCBX_VER_IEEE)) {
		FCM_LOG("Processing IEEE messgae: %s not in IEEE mode\n",
			ifname);
	}

	rta_parent = find_attr(nlh, DCB_ATTR_IEEE);
	if (!rta_parent) {
		FCM_LOG("Processing IEEE message: No DCB attribute found on %s\n",
			ifname);
		return;
	}

	rta_child = find_nested_attr(rta_parent, DCB_ATTR_IEEE_PFC);
	if (!rta_child) {
		FCM_LOG("Processing IEEE messgae: No PFC attribute found on %s\n",
			ifname);
		return;
	}

	struct ieee_pfc *ieee_pfc = (struct ieee_pfc *)NLA_DATA(rta_child);

	ff->ieee_pfc_info = ieee_pfc->pfc_en;

	pri_mask = get_pri_mask_from_ieee(rta_parent, dcbx_cap);
	if (pri_mask < 0) {
		FCM_LOG("Processing IEEE message: No Priority mask found on %s\n",
			ifname);
		return;
	}
	FCM_LOG_DBG("Processing IEEE message: FCoE Priority mask on %s is 0x%02X\n",
		    ifname, pri_mask);
	ff->ieee_app_info = pri_mask;

	if (ff->ieee_state == IEEE_GET_STATE && d->cmd == DCB_CMD_IEEE_GET &&
	    ff->ieee_resp_pending == nlh->nlmsg_seq)
		ieee_state_set(ff, IEEE_DONE);
}

static void fcm_link_recv(UNUSED void *arg)
{
	int rc;
	char *buf;
	struct nlmsghdr *hp;
	struct ifinfomsg *ip;
	unsigned type;
	size_t plen;
	size_t rlen;

	/* check to make sure our receive buffer is large enough,
	 * or scale it up as needed */
	rc = recv(fcm_link_socket, NULL, 0, MSG_PEEK | MSG_TRUNC);
	if (rc > (int)fcm_link_buf_size) {
		FCM_LOG_DBG("resizing link buf to %d bytes\n", rc);
		void *resize = realloc(fcm_link_buf, rc);
		if (resize) {
			fcm_link_buf = resize;
			fcm_link_buf_size = rc;
		} else {
			FCM_LOG_ERR(errno, "Failed to allocate link buffer");
		}
	}

	buf = fcm_link_buf;
	rc = recv(fcm_link_socket, buf, fcm_link_buf_size, 0);
	if (rc <= 0) {
		if (rc < 0)
			FCM_LOG_ERR(errno, "Error reading from "
				    "netlink socket with fd %d",
				    fcm_link_socket);
		return;
	}

	hp = (struct nlmsghdr *)buf;
	rlen = rc;
	for (hp = (struct nlmsghdr *)buf; NLMSG_OK(hp, rlen);
	     hp = NLMSG_NEXT(hp, rlen)) {

		type = hp->nlmsg_type;
		if (hp->nlmsg_type == NLMSG_DONE)
			break;

		if (hp->nlmsg_type == NLMSG_ERROR) {
			rc = log_nlmsg_error(hp, rlen, "nlmsg error");
			break;
		}

		plen = NLMSG_PAYLOAD(hp, 0);
		ip = (struct ifinfomsg *)NLMSG_DATA(hp);
		if (plen < sizeof(*ip)) {
			FCM_LOG("too short (%d) to be a LINK message", rc);
			break;
		}

		switch (type) {
		case RTM_NEWLINK:
		case RTM_DELLINK:
		case RTM_GETLINK:
			FCM_LOG_DBG("Link event: %d flags %05X index %d ",
				    type, ip->ifi_flags, ip->ifi_index);

			fcm_process_link_msg(ip, plen, type);
			break;

		case RTM_GETDCB:
		case RTM_SETDCB:
			fcm_process_ieee_msg(hp);
			break;

		default:
			FCM_LOG_DBG("%s: Unexpected type %d\n", __func__, type);
			break;
		}
	}
	if (rc == -EBUSY) {
		FCM_LOG_DBG("%s: netlink returned -EBUSY, retry\n",
			      __func__);
		fcm_link_getlink();
	}
}

/*
 * Send rt_netlink request for all network interfaces.
 */
static void fcm_link_getlink(void)
{
	struct {
		struct nlmsghdr nl;
		struct ifinfomsg ifi;	/* link level specific information,
					   not dependent on network protocol */
	} msg;

	int rc;

	memset(&msg, 0, sizeof(msg));
	msg.nl.nlmsg_len = sizeof(msg);
	msg.nl.nlmsg_type = RTM_GETLINK;
	msg.nl.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT | NLM_F_ATOMIC;
	msg.nl.nlmsg_seq = ++fcm_link_seq;
	msg.nl.nlmsg_pid = getpid();
	msg.ifi.ifi_family = AF_UNSPEC;
	msg.ifi.ifi_type = ARPHRD_ETHER;
	rc = send(fcm_link_socket, &msg, sizeof(msg), 0);
	if (rc < 0)
		FCM_LOG_ERR(errno, "send error");
}

static void fcm_fcoe_init(void)
{
	if (fcm_read_config_files())
		exit(1);

	if (force_legacy || access(FCOE_BUS_CREATE, F_OK)) {
		FCM_LOG_DBG("Using libfcoe module parameter interfaces\n");
		libfcoe_control = &libfcoe_module_tmpl;
	} else {
		FCM_LOG_DBG("Using /sys/bus/fcoe interfaces\n");
		libfcoe_control = &libfcoe_bus_tmpl;
	}
}

/*
 * Allocate an FCoE interface state structure.
 */
static struct fcm_netif *fcm_netif_alloc(char *ifname)
{
	struct fcm_netif *ff;

	sigprocmask(SIG_BLOCK, &block_sigset, NULL);

	ff = calloc(1, sizeof(*ff));
	if (ff) {
		snprintf(ff->ifname, sizeof(ff->ifname), "%s", ifname);
		ff->ff_operstate = IF_OPER_UNKNOWN;
		ff->ff_enabled = 1;
		TAILQ_INSERT_TAIL(&fcm_netif_head, ff, ff_list);
	} else {
		FCM_LOG_ERR(errno, "failed to allocate fcm_netif");
	}

	sigprocmask(SIG_UNBLOCK, &block_sigset, NULL);

	return ff;
}

/*
 * Find or create an FCoE network interface by ifname.
 * @ifname - interface name to create
 *
 * This creates a netif interface structure with interface name,
 * or if one already exists returns the existing one.
 */
static struct fcm_netif *fcm_netif_lookup_create(char *ifname)
{
	struct fcm_netif *ff;

	TAILQ_FOREACH(ff, &fcm_netif_head, ff_list) {
		if (!strncmp(ifname, ff->ifname, IFNAMSIZ))
			return ff;
	}

	ff = fcm_netif_alloc(ifname);
	if (ff != NULL) {
		sa_timer_init(&ff->dcbd_retry_timer, fcm_dcbd_retry_timeout,
			      (void *)ff);
		FCM_LOG_DEV_DBG(ff, "Monitoring port %s\n", ifname);
	}

	return ff;
}

/*
 * Find an FCoE interface by name.
 */
static struct fcm_netif *fcm_netif_lookup(char *ifname)
{
	struct fcm_netif *curr, *ff = NULL;

	TAILQ_FOREACH(curr, &fcm_netif_head, ff_list) {
		if (strcmp(curr->ifname, ifname) == 0) {
			ff = curr;
			break;
		}
	}

	return ff;
}

static void fcm_dcbd_init()
{
	fcm_clif->cl_fd = -1;	/* not connected */
	fcm_clif->cl_ping_pending = 0;
	sa_timer_init(&fcm_dcbd_timer, fcm_dcbd_timeout, NULL);
	fcm_dcbd_timeout(NULL);
}

static int fcm_dcbd_connect(void)
{
	int rc;
	int fd;
	struct sockaddr_un dest;
	struct sockaddr_un *lp;
	socklen_t addrlen;

	ASSERT(fcm_clif->cl_fd < 0);
	fd = socket(PF_UNIX, SOCK_DGRAM, 0);
	if (fd < 0) {
		FCM_LOG_ERR(errno, "clif socket open failed");	/* XXX */
		return 0;
	}

	lp = &fcm_clif->cl_local;
	memset(lp, 0, sizeof(*lp));
	lp->sun_family = AF_LOCAL;
	lp->sun_path[0] = '\0';
	rc = bind(fd, (struct sockaddr *)lp, sizeof(sa_family_t));
	if (rc < 0) {
		FCM_LOG_ERR(errno, "clif bind failed");
		close(fd);
		return 0;
	}

	memset(&dest, 0, sizeof(dest));
	dest.sun_family = AF_LOCAL;
	dest.sun_path[0] = '\0';
	snprintf(&dest.sun_path[1], sizeof(dest.sun_path) - 1,
		 "%s", LLDP_CLIF_SOCK);
	addrlen = sizeof(sa_family_t) + strlen(dest.sun_path + 1) + 1;
	rc = connect(fd, (struct sockaddr *)&dest, addrlen);
	if (rc < 0) {
		FCM_LOG_ERR(errno, "Failed to connect to lldpad");
		close(fd);
		return 0;
	}
	fcm_clif->cl_fd = fd;
	sa_select_add_fd(fd, fcm_dcbd_rx, NULL, NULL, fcm_clif);
	FCM_LOG_DBG("connected to lldpad");
	return 1;
}

static void fcm_dcbd_timeout(UNUSED void *arg)
{
	if (fcm_clif->cl_ping_pending > 0) {
		fcm_dcbd_request("D");	/* DETACH_CMD */
		fcm_dcbd_disconnect();
	}
	if (fcm_clif->cl_fd < 0) {
		if (fcm_dcbd_connect())
			fcm_dcbd_request("A");	/* ATTACH_CMD: for events */
		else
			sa_timer_set(&fcm_dcbd_timer, DCBD_CONNECT_TIMEOUT);
	} else {
		fcm_clif->cl_ping_pending++;
		fcm_dcbd_request("P");	/* ping to verify connection */
	}
}

static void fcm_dcbd_retry_timeout(void *arg)
{
	struct fcm_netif *ff = (struct fcm_netif *)arg;

	ASSERT(ff);
	FCM_LOG_DBG("%s: lldpad retry TIMEOUT occurred [%d]",
		    ff->ifname, ff->dcbd_retry_cnt);

	fcm_dcbd_state_set(ff, FCD_GET_DCB_STATE);
	fcm_netif_advance(ff);
}

static void fcm_dcbd_disconnect(void)
{
	if (fcm_clif) {
		if (fcm_clif->cl_fd >= 0) {
			sa_select_rem_fd(fcm_clif->cl_fd);
			close(fcm_clif->cl_fd);
		}
		fcm_clif->cl_fd = -1;	/* mark as disconnected */
		fcm_clif->cl_busy = 0;
		fcm_clif->cl_ping_pending = 0;
		FCM_LOG_DBG("Disconnected from lldpad");
	}
}

static void fcm_dcbd_shutdown(void)
{
	FCM_LOG_DBG("Shutdown lldpad connection\n");
	fcm_dcbd_request("D");	/* DETACH_CMD */
	fcm_dcbd_disconnect();
	closelog();
}

static void fcm_cleanup(void)
{
	struct fcoe_port *curr, *next;
	struct fcm_netif *ff, *head;

	sigprocmask(SIG_BLOCK, &block_sigset, NULL);

	for (curr = fcoe_config.port; curr; curr = next) {
		FCM_LOG_DBG("OP: DESTROY %s\n", curr->ifname);
		fcm_fcoe_if_action(FCOE_DESTROY,  curr->ifname);
		next = curr->next;
		free(curr);
	}

	for (head = TAILQ_FIRST(&fcm_netif_head); head; head = ff) {
		ff = TAILQ_NEXT(head, ff_list);
		TAILQ_REMOVE(&fcm_netif_head, head, ff_list);
		free(head);
	}

	sigprocmask(SIG_UNBLOCK, &block_sigset, NULL);

	free(fcm_link_buf);
}

static u_int32_t fcm_get_hex(char *cp, u_int32_t len, char **endptr)
{
	u_int32_t hex = 0;

	while (len > 0) {
		len--;
		if (*cp >= '0' && *cp <= '9')
			hex = (hex << 4) | (*cp - '0');
		else if (*cp >= 'A' && *cp <= 'F')
			hex = (hex << 4) | (*cp - 'A' + 10);
		else if (*cp >= 'a' && *cp <= 'f')
			hex = (hex << 4) | (*cp - 'a' + 10);
		else
			break;
		cp++;
	}
	*endptr = (len == 0) ? NULL : cp;
	return hex;
}

static void fcm_dcbd_rx(void *arg)
{
	struct fcm_clif *clif = arg;
	cmd_status st;
	char buf[128];
	size_t len;
	int rc;
	char *ep;

	len = sizeof(buf);
	rc = read(clif->cl_fd, buf, sizeof(buf) - 1);
	if (rc < 0)
		FCM_LOG_ERR(errno, "read");
	else if (rc > 0 && rc < (int)sizeof(buf)) {
		buf[rc] = '\0';
		len = strlen(buf);
		ASSERT(len <= rc);

		FCM_LOG_DBG("recv '%s', len=%d bytes succeeded", buf, (int)len);
		switch (buf[CLIF_RSP_MSG_OFF]) {
		case CMD_RESPONSE:
			st = fcm_get_hex(buf + CLIF_STAT_OFF, CLIF_STAT_LEN,
					 &ep);
			if (ep != NULL)
				FCM_LOG("unexpected response code from lldpad: "
					"len %zd buf %s rc %d", len, buf, rc);
			else if (st != cmd_success &&
				 st != cmd_not_applicable &&
				 st != cmd_device_not_found) {
				FCM_LOG("error response from lldpad: "
					"error %d len %zd %s",
					st, len, buf);
			}
			fcm_clif->cl_busy = 0;

			switch (buf[3]) {
			case DCB_CMD:
				fcm_dcbd_cmd_resp(buf, st);
				break;
			case ATTACH_CMD:
				break;
			case DETACH_CMD:
				break;
			case PING_CMD:
				if (clif->cl_ping_pending > 0)
					--clif->cl_ping_pending;
				break;
			case LEVEL_CMD:
				break;
			default:
				FCM_LOG("Unexpected cmd in response "
					"from lldpad: len %zd %s",
					len, buf);
				break;
			}
			break;

		case EVENT_MSG:
			fcm_dcbd_event(buf, len);
			break;
		default:
			FCM_LOG("Unexpected message from lldpad: len %zd buf %s",
				len, buf);
			break;
		}
	}
}

/*
 * returns:  1 if request was successfully written
 *           0 if the write failed
 */
static int fcm_dcbd_request(char *req)
{
	size_t len;
	int rc;

	if (fcm_clif->cl_fd < 0)
		return 0;
	len = strlen(req);
	ASSERT(fcm_clif->cl_busy == 0);
	sa_timer_set(&fcm_dcbd_timer, DCBD_CONNECT_TIMEOUT);
	fcm_clif->cl_busy = 1;
	rc = write(fcm_clif->cl_fd, req, len);
	if (rc < 0) {
		FCM_LOG_ERR(errno, "Failed write req %s len %zd", req, len);
		fcm_clif->cl_busy = 0;
		fcm_dcbd_disconnect();
		sa_timer_set(&fcm_dcbd_timer, DCBD_CONNECT_RETRY_TIMEOUT);
		return 0;
	}

	if (rc > FCM_PING_REQ_LEN)
		FCM_LOG_DBG("sent '%s', rc=%d bytes succeeded", req, rc);
	return 1;
}

/*
 * Find port for message.
 * The port name length starts at len_off for len_len bytes.
 * The entire message length is len.
 * The pointer to the message pointer is passed in, and updated to point
 * past the interface name.
 */
static struct fcm_netif *fcm_dcbd_get_port(char **msgp, size_t len_off,
					   size_t len_len, size_t len)
{
	struct fcm_netif *ff;
	u_int32_t if_len;
	char *ep;
	char *msg;
	char ifname[IFNAMSIZ];

	msg = *msgp;
	if (len_off + len_len >= len)
		return NULL;

	if_len = fcm_get_hex(msg + len_off, len_len, &ep);
	if (ep != NULL) {
		FCM_LOG("Parse error on port len: msg %s", msg);
		return NULL;
	}

	if (len_off + len_len + if_len > len) {
		FCM_LOG("Invalid port len %d msg %s", if_len, msg);
		return NULL;
	}
	msg += len_off + len_len;
	sa_strncpy_safe(ifname, sizeof(ifname), msg, if_len);
	*msgp = msg + if_len;
	ff = fcm_netif_lookup(ifname);
	if (ff == NULL) {
		FCM_LOG("ifname '%s' not found", ifname);
	}
	return ff;
}

/*
 * (XXX) Notes:
 * This routine is here to help fcm_dcbd_cmd_resp() to pick up
 * information of the response packet from the DCBD.
 * Returns:  0 on success
 *          -1 on failure
 */
static int dcb_rsp_parser(struct fcm_netif *ff, char *rsp)
{
	int version;
	int dcb_cmd;
	int feature;
	int subtype;
	int plen;
	int doff;
	int i;
	int n;
	struct feature_info *f_info = NULL;
	char buf[20];

	feature = hex2int(rsp+DCB_FEATURE_OFF);

	dcb_cmd = hex2int(rsp+DCB_CMD_OFF);

	version = rsp[DCB_VER_OFF] & 0x0f;
	if (version != CLIF_MSG_VERSION) {
		FCM_LOG_DEV(ff, "WARNING: Unexpected rsp version %d\n",
			    version);
		return -1;
	}

	subtype = hex2int(rsp+DCB_SUBTYPE_OFF);
	plen = hex2int(rsp+DCB_PORTLEN_OFF);
	doff = DCB_PORT_OFF + plen;

	switch (feature) {
	case FEATURE_DCB:
		ff->ff_dcb_state = (*(rsp+doff+CFG_ENABLE) == '1');
		return 0;
	case FEATURE_PFC:
		f_info = &ff->ff_pfc_info;
		break;
	case FEATURE_APP:
		f_info = &ff->ff_app_info;
		f_info->subtype = subtype;
		break;
	default:
		return -1;
	}

	switch (dcb_cmd) {
	case CMD_GET_CONFIG:
		f_info->enable = (*(rsp+doff+CFG_ENABLE) == '1');
		f_info->advertise = (*(rsp+doff+CFG_ADVERTISE) == '1');
		f_info->willing = (*(rsp+doff+CFG_WILLING) == '1');
		doff += CFG_LEN;
		break;

	case CMD_GET_OPER:
		f_info->op_vers = hex2int(rsp+doff+OPER_OPER_VER);
		f_info->op_error = hex2int(rsp+doff+OPER_ERROR);
		f_info->op_mode = (*(rsp+doff+OPER_OPER_MODE) == '1');
		f_info->syncd = (*(rsp+doff+OPER_SYNCD) == '1');
		doff += OPER_LEN;
		if (feature == FEATURE_PFC) {
			f_info->u.pfcup = 0;
			for (i = 0; i < MAX_USER_PRIORITIES; i++) {
				if (*(rsp+doff+PFC_UP(i)) == '1')
					f_info->u.pfcup |= 1<<i;
			}
		}
		if (feature == FEATURE_APP && subtype == APP_FCOE_STYPE) {
			n = hex2int(rsp+doff+APP_LEN);
			snprintf(buf, sizeof(buf), "%*.*s\n",
				 n, n, rsp+doff+APP_DATA);
			f_info->u.appcfg = hex2int(buf);
		}
		break;
	}

	return 0;
}

/*
 * validate_ieee_info - Validation IEEE DCB status for FCoE
 *
 * Returns:  FCP_ACTIVATE_IF - if the dcb netif qualifies for an fcoe interface
 *           FCP_DESTROY_IF - if the dcb netif should not support fcoe interface
 *           FCP_ERROR - if dcb configuration has errors
 *           FCP_WAIT - if dcb criteria is inconclusive
 */
static enum fcp_action validate_ieee_info(struct fcm_netif *ff)
{
	if (ff->ieee_pfc_info & ff->ieee_app_info) {
		FCM_LOG_DBG("%s: %s: IEEE active and valid\n",
			__func__, ff->ifname);
		return FCP_ACTIVATE_IF;
	}
	FCM_LOG_DBG("%s: %s: IEEE active and invalid, pfc=0x%x, app=0x%x\n",
		    __func__, ff->ifname, ff->ieee_pfc_info, ff->ieee_app_info);
	return FCP_WAIT;
}

/*
 * validate_dcbd_info - Validating DCBD configuration and status
 *
 * Returns:  FCP_ACTIVATE_IF - if the dcb netif qualifies for an fcoe interface
 *           FCP_DESTROY_IF - if the dcb netif should not support fcoe interface
 *           FCP_ERROR - if dcb configuration has errors
 *           FCP_WAIT - if dcb criteria is inconclusive
 */
static enum fcp_action validate_dcbd_info(struct fcm_netif *ff)
{
	int errors = 0;
	int dcbon;

	dcbon = ff->ff_dcb_state;
	if (dcbon && (ff->dcbx_cap & DCB_CAP_DCBX_VER_IEEE))
		return validate_ieee_info(ff);

	/* check if dcb state qualifies to create the fcoe interface */
	if (dcbon &&
	    ff->ff_app_info.enable &&
	    ff->ff_pfc_info.enable &&
	    ff->ff_app_info.op_mode &&
	    ff->ff_pfc_info.op_mode &&
	    ff->ff_pfc_info.u.pfcup & ff->ff_app_info.u.appcfg) {

		if (dcbon && !ff->ff_app_info.willing) {
			FCM_LOG_DEV(ff,
				    "WARNING: FCoE willing mode is false\n");
			errors++;
		}
		if (dcbon && !ff->ff_app_info.advertise) {
			FCM_LOG_DEV(ff,
				    "WARNING: FCoE advertise mode is false\n");
			errors++;
		}
		if (dcbon && !ff->ff_pfc_info.willing) {
			FCM_LOG_DEV(ff,
				    "WARNING: PFC willing mode is false\n");
			errors++;
		}
		if (dcbon && !ff->ff_pfc_info.advertise) {
			FCM_LOG_DEV(ff,
				    "WARNING: PFC advertise mode is false\n");
			errors++;
		}

		if (errors)
			FCM_LOG_DEV_DBG(ff,
					"WARNING: DCB may not be configured correctly\n");
		else
			FCM_LOG_DEV_DBG(ff, "DCB is configured correctly\n");


		if (ff->ff_enabled)
			return FCP_ACTIVATE_IF;
		else {
			ff->ff_enabled = 1;
			return FCP_ENABLE_IF;
		}
	}

	/* check if dcb state qualifies to destroy the fcoe interface */
	if (!dcbon ||
	    !ff->ff_app_info.enable ||
	    (ff->ff_app_info.op_mode && ff->ff_pfc_info.op_mode &&
	     !(ff->ff_pfc_info.u.pfcup & ff->ff_app_info.u.appcfg))) {

		if (dcbon && !ff->ff_dcb_state)
			FCM_LOG_DEV(ff, "WARNING: DCB is disabled\n");

		if (dcbon && !ff->ff_app_info.enable)
			FCM_LOG_DEV(ff, "WARNING: FCoE enable is off\n");

		if (dcbon &&
		    !(ff->ff_pfc_info.u.pfcup & ff->ff_app_info.u.appcfg))
			FCM_LOG_DEV(ff,
				    "WARNING: FCoE priority (0x%02x) doesn't "
				    "intersect with PFC priority (0x%02x)\n",
				    ff->ff_app_info.u.appcfg,
				    ff->ff_pfc_info.u.pfcup);

		ff->ff_enabled = 0;
		return FCP_DISABLE_IF;
	}

	/* The dcbd state does not match the create or destroy criteria.
	 * Log possible problems.
	 */
	if (dcbon && !ff->ff_app_info.willing) {
		FCM_LOG_DEV(ff, "WARNING: FCoE willing mode is false\n");
		errors++;
	}
	if (dcbon && !ff->ff_app_info.advertise) {
		FCM_LOG_DEV(ff, "WARNING: FCoE advertise mode is false\n");
		errors++;
	}
	if (dcbon && !ff->ff_app_info.op_mode) {
		FCM_LOG_DEV(ff, "WARNING: FCoE operational mode is false\n");
		print_errors(ff->ff_app_info.op_error);
		errors++;
	}
	if (dcbon && !ff->ff_pfc_info.enable) {
		FCM_LOG_DEV(ff, "WARNING: PFC enable is off\n");
		errors++;
	}
	if (dcbon && !ff->ff_pfc_info.advertise) {
		FCM_LOG_DEV(ff, "WARNING: PFC advertise mode is false\n");
		errors++;
	}
	if (dcbon && !ff->ff_app_info.op_mode) {
		FCM_LOG_DEV(ff, "WARNING: APP:0 operational mode is false\n");
		print_errors(ff->ff_app_info.op_error);
		errors++;
	}
	if (dcbon && !ff->ff_pfc_info.op_mode) {
		FCM_LOG_DEV(ff, "WARNING: PFC operational mode is false\n");
		print_errors(ff->ff_pfc_info.op_error);
		errors++;
	}
	if (dcbon && !(ff->ff_pfc_info.u.pfcup & ff->ff_app_info.u.appcfg)) {
		FCM_LOG_DEV(ff, "WARNING: APP:0 priority (0x%02x) doesn't "
			    "intersect with PFC priority (0x%02x)\n",
			    ff->ff_app_info.u.appcfg,
			    ff->ff_pfc_info.u.pfcup);
		errors++;
	}
	if (errors) {
		FCM_LOG_DEV(ff, "WARNING: DCB may be configured incorrectly\n");
		return FCP_ERROR;
	}

	return FCP_WAIT;
}

/*
 * clear_dcbd_info - clear dcbd info to unknown values
 *
 */
static void clear_dcbd_info(struct fcm_netif *ff)
{
	memset(&ff->ff_pfc_info, 0, sizeof(struct feature_info));
	memset(&ff->ff_app_info, 0, sizeof(struct feature_info));
}


/**
 * fcm_dcbd_set_config() - Response handler for set config command
 * @ff: fcoe port structure
 * @st: status
 */
static void fcm_dcbd_set_config(struct fcm_netif *ff)
{
	if (ff->ff_dcbd_state == FCD_SEND_CONF)
		fcm_dcbd_state_set(ff, FCD_GET_PFC_CONFIG);
}

/**
 * fcm_dcbd_get_config() - Response handler for get config command
 * @ff:   fcoe port structure
 * @resp: response buffer
 * @st:   status
 */
static void fcm_dcbd_get_config(struct fcm_netif *ff, char *resp)
{
	switch (ff->ff_dcbd_state) {
	case FCD_GET_DCB_STATE:
		if (!dcb_rsp_parser(ff, resp)) {
			if (ff->ff_dcb_state &&
			    !(ff->dcbx_cap & DCB_CAP_DCBX_VER_IEEE))
				fcm_dcbd_state_set(ff, FCD_GET_PFC_CONFIG);
			else
				fcm_dcbd_state_set(ff, FCD_DONE);
		} else
			fcm_dcbd_state_set(ff, FCD_ERROR);
		break;
	case FCD_GET_PFC_CONFIG:
		if (!dcb_rsp_parser(ff, resp))
			fcm_dcbd_state_set(ff, FCD_GET_APP_CONFIG);
		else
			fcm_dcbd_state_set(ff, FCD_ERROR);
		break;
	case FCD_GET_APP_CONFIG:
		if (!dcb_rsp_parser(ff, resp))
			fcm_dcbd_state_set(ff, FCD_GET_PFC_OPER);
		else
			fcm_dcbd_state_set(ff, FCD_ERROR);
		break;
	default:
		break;
	}
}


/**
 * fcm_dcbd_get_oper() - Response handler for get operational state command
 * @ff:   fcoe port structure
 * @resp: response buffer
 * @cp:   response buffer pointer, points past the interface name
 * @st:   status
 *
 * Sample msg: R00C103050004eth8010100100208
 *                  opppssll    vvmmeemsllpp
 */
static void fcm_dcbd_get_oper(struct fcm_netif *ff, char *resp, char *cp)
{
	u_int32_t val;
	char *ep = NULL;

	val = fcm_get_hex(cp + OPER_ERROR, 2, &ep);

	if (ep) {
		FCM_LOG_DEV(ff, "Invalid get oper response "
			    "parse error byte %td, resp %s", ep - cp, cp);
		fcm_dcbd_state_set(ff, FCD_ERROR);
	} else {
		if (val && fcoe_config.debug)
			print_errors(val);

		switch (ff->ff_dcbd_state) {
		case FCD_GET_PFC_OPER:
			if (dcb_rsp_parser(ff, resp) || !ff->ff_pfc_info.syncd)
				fcm_dcbd_state_set(ff, FCD_ERROR);
			else
				fcm_dcbd_state_set(ff, FCD_GET_APP_OPER);

			FCM_LOG_DEV_DBG(ff, "PFC feature is %ssynced",
					ff->ff_pfc_info.syncd ? "" : "not ");
			FCM_LOG_DEV_DBG(ff, "PFC operating mode is %s",
					ff->ff_pfc_info.op_mode ? "on" :
					"off ");
			break;

		case FCD_GET_APP_OPER:
			if (dcb_rsp_parser(ff, resp) || !ff->ff_app_info.syncd)
				fcm_dcbd_state_set(ff, FCD_ERROR);
			else
				fcm_dcbd_state_set(ff, FCD_DONE);

			FCM_LOG_DEV_DBG(ff, "FCoE feature is %ssynced",
					ff->ff_app_info.syncd ? "" :
					"not ");
			FCM_LOG_DEV_DBG(ff, "FCoE operating mode is %s",
					ff->ff_app_info.op_mode ? "on" :
					"off ");
			break;

		default:
			break;
		}
	}
}

/*
 * Handle command response.
 * Response buffer points past command code character in response.
 */
static void fcm_dcbd_cmd_resp(char *resp, cmd_status st)
{
	struct fcm_netif *ff;
	u_int32_t ver;
	u_int32_t cmd;
	u_int32_t feature;
	u_int32_t state;
	char *ep;
	char *cp;
	size_t len;

	resp += CLIF_RSP_OFF;
	len = strlen(resp);
	ver = fcm_get_hex(resp + DCB_VER_OFF, DCB_VER_LEN, &ep);
	if (ep != NULL) {
		FCM_LOG("parse error: resp %s", resp);
		return;
	} else	if (ver != CLIF_RSP_VERSION) {
		FCM_LOG("unexpected version %d resp %s", ver, resp);
		return;
	}
	cmd = fcm_get_hex(resp + DCB_CMD_OFF, DCB_CMD_LEN, &ep);
	if (ep != NULL) {
		FCM_LOG("parse error on resp cmd: resp %s", resp);
		return;
	}
	feature = fcm_get_hex(resp + DCB_FEATURE_OFF, DCB_FEATURE_LEN, &ep);
	if (ep != NULL) {
		FCM_LOG("parse error on resp feature: resp %s", resp);
		return;
	}
	fcm_get_hex(resp + DCB_SUBTYPE_OFF, DCB_SUBTYPE_LEN, &ep);
	if (ep != NULL) {
		FCM_LOG("parse error on resp subtype: resp %s", resp);
		return;
	}
	cp = resp;
	ff = fcm_dcbd_get_port(&cp, DCB_PORTLEN_OFF, DCB_PORTLEN_LEN, len);
	if (ff == NULL) {
		FCM_LOG("port not found. resp %s", resp);
		return;
	}

	/*
	 * check that dcbd response matches the current dcbd state.
	 */
	state = ff->ff_dcbd_state;
	if (((cmd == CMD_GET_CONFIG) &&
	     ((state == FCD_GET_DCB_STATE && feature == FEATURE_DCB) ||
	      (state == FCD_GET_PFC_CONFIG && feature == FEATURE_PFC) ||
	      (state == FCD_GET_APP_CONFIG && feature == FEATURE_APP)))
	    ||
	    ((cmd == CMD_GET_OPER) &&
	     ((state == FCD_GET_PFC_OPER && feature == FEATURE_PFC) ||
	      (state == FCD_GET_APP_OPER && feature == FEATURE_APP)))) {

		/* the response matches the current pending query */
		ff->response_pending = 0;
		if (st != cmd_success) {
			if (st == cmd_not_applicable)
				fcm_dcbd_state_set(ff, FCD_DONE);
			else
				fcm_dcbd_state_set(ff, FCD_ERROR);
			return;
		}
	}

	switch (cmd) {
	case CMD_SET_CONFIG:
		fcm_dcbd_set_config(ff);
		break;

	case CMD_GET_CONFIG:
		fcm_dcbd_get_config(ff, resp);
		break;

	case CMD_GET_OPER:
		fcm_dcbd_get_oper(ff, resp, cp);
		break;

	default:
		FCM_LOG_DEV_DBG(ff, "Unknown cmd 0x%x in response: resp %s",
				cmd, resp);
		break;
	}
}

/*
 * Handle incoming DCB event message.
 * Example message: E5104eth8050001
 */
static void fcm_dcbd_event(char *msg, size_t len)
{
	struct fcm_netif *ff;
	struct fcoe_port *p;
	u_int32_t feature;
	u_int32_t subtype;
	char *cp;
	char *ep;

	if (msg[EV_LEVEL_OFF] != MSG_DCB + '0' || len <= EV_PORT_ID_OFF)
		return;
	if (msg[EV_VERSION_OFF] != CLIF_EV_VERSION + '0') {
		FCM_LOG("Unexpected version in event msg %s", msg);
		return;
	}
	cp = msg;
	ff = fcm_dcbd_get_port(&cp, EV_PORT_LEN_OFF, EV_PORT_LEN_LEN, len);
	if (ff == NULL)
		return;

	feature = fcm_get_hex(cp + EV_FEATURE_OFF, 2, &ep);
	if (ep != NULL) {
		FCM_LOG_DEV_DBG(ff, "Invalid feature code in event msg %s",
				msg);
		return;
	}

	/*
	 * Check if the FCoE ports which use the interface on which the
	 * dcbd event arrived are configured to require dcb.
	 */

	p = fcm_find_fcoe_port(ff->ifname, FCP_REAL_IFNAME);
	while (p) {
		if (p->dcb_required && p->last_msg_type != RTM_DELLINK &&
		    p->fcoe_enable)
			break;
		p = fcm_find_next_fcoe_port(p, ff->ifname);
	}

	/*
	 * dcb is not required or link was removed, ignore dcbd event
	 */
	if (!p)
		return;

	if (ff->ff_operstate != IF_OPER_UP)
		return;

	switch (feature) {
	case FEATURE_PG:     /* 'E5204eth2020001' */
		FCM_LOG_DEV_DBG(ff, "<Got PG Event>\n");
		break;
	case FEATURE_PFC:    /* 'E5204eth2030011' */
		FCM_LOG_DEV_DBG(ff, "<Got PFC Event>\n");
		fcm_dcbd_state_set(ff, FCD_GET_DCB_STATE);
		break;
	case FEATURE_APP:    /* 'E5204eth2050011' */
		FCM_LOG_DEV_DBG(ff, "<Got APP Event>\n");
		subtype = fcm_get_hex(cp + EV_SUBTYPE_OFF, 2, &ep);
		if (subtype != APP_FCOE_STYPE) {
			FCM_LOG_DEV_DBG(ff, "Unknown application subtype "
					"in msg %s", msg);
			break;
		}
		fcm_dcbd_state_set(ff, FCD_GET_DCB_STATE);
		break;
	default:
		FCM_LOG_DEV_DBG(ff, "Unknown feature 0x%x in msg %s",
				feature, msg);
		break;
	}

	if (fcoe_config.debug) {
		if (cp[EV_OP_MODE_CHG_OFF] == '1')
			FCM_LOG_DEV_DBG(ff,
					"Operational mode changed");
		if (cp[EV_OP_CFG_CHG_OFF] == '1')
			FCM_LOG_DEV_DBG(ff,
					"Operational config changed");
	}
}

/*
 * The status is interpreted by the client as an 'enum fcoe_status'.
 */
static void fcm_cli_reply(struct sock_info *r, enum fcoe_status status)
{
	char rbuf[MAX_MSGBUF];
	snprintf(rbuf, MSG_RBUF, "%d", status);
	sendto(r->sock, rbuf, MSG_RBUF, 0, (struct sockaddr *)&(r->from),
	       r->fromlen);
}

static enum fcoe_status fcm_fcoe_if_action(char *path, char *ifname)
{
	FILE *fp = NULL;
	enum fcoe_status ret = EFAIL;

	fp = fopen(path, "w");
	if (!fp) {
		FCM_LOG_ERR(errno, "%s: Failed to open path %s\n",
			    progname, path);
		goto err_out;
	}

	if (EOF == fputs(ifname, fp)) {
		FCM_LOG_ERR(errno, "%s: Failed to write %s to path %s.\n",
			    progname, ifname, path);
		goto out;
	}

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

static void fcm_send_fip_request(const struct fcoe_port *p)
{
	int dest;

	if (p->mode == FCOE_MODE_VN2VN)
		dest = FIP_ALL_VN2VN;
	else
		dest = FIP_ALL_FCF;
	fip_send_vlan_request(p->fip_socket, p->ifindex, p->mac, dest);
}

void fcm_vlan_disc_timeout(void *arg)
{
	struct fcoe_port *p = arg;
	int s;

	FCM_LOG_DBG("%s: VLAN discovery TIMEOUT [%d]",
		    p->ifname, p->vlan_disc_count);
	p->vlan_disc_count++;
	if (p->fip_socket < 0) {
		s = fcm_vlan_disc_socket(p);
		if (s < 0) {
			FCM_LOG_ERR(errno, "Could not acquire fip socket.\n");
			goto set_timeout;
		}
		p->fip_socket = s;
		p->vlan_disc_count = 1;
	}
	fcm_send_fip_request(p);
set_timeout:
	sa_timer_set(&p->vlan_disc_timer, FCM_VLAN_DISC_TIMEOUT);
}

static int fcm_start_vlan_disc(struct fcoe_port *p)
{
	int s;
	if (p->fip_socket < 0) {
		s = fcm_vlan_disc_socket(p);
		if (s < 0) {
			/*
			 * If we can't open the socket set the timeout
			 * anyways so we will retry sending the fipvlan
			 * request.
			 */
			FCM_LOG_ERR(errno, "Failed to open socket, setting VLAN DISC timer.\n");
			sa_timer_set(&p->vlan_disc_timer, FCM_VLAN_DISC_TIMEOUT);
			return s;
		}
		p->fip_socket = s;
	}
	p->vlan_disc_count = 1;
	fcm_send_fip_request(p);
	sa_timer_set(&p->vlan_disc_timer, FCM_VLAN_DISC_TIMEOUT);
	return 0;
}

/*
 *
 * Input:  action = 1      Destroy the FCoE interface
 *         action = 2      Create the FCoE interface
 *         action = 3      Reset the interface
 */
static void fcm_fcoe_action(struct fcoe_port *p)
{
	struct fcoe_port *vp;
	char path[MAX_PATH_LEN];
	enum fcoe_status rc = SUCCESS;

	switch (p->action) {
	case FCP_CREATE_IF:
		FCM_LOG_DBG("OP: CREATE %s\n", p->ifname);
		rc = libfcoe_control->create(p);
		if (rc) {
			FCM_LOG_DBG("Failed to create FCoE interface "
				    "for %s, rc is %d\n", p->ifname, rc);
			break;
		}

		FCM_LOG_DBG("OP: created fchost:%s for %s\n",
			    p->fchost, p->ifname);

		break;
	case FCP_DESTROY_IF:
		FCM_LOG_DBG("OP: DESTROY %s\n", p->ifname);
		if (p->auto_vlan) {
			/* destroy all the VLANs */
			vp = fcm_find_fcoe_port(p->ifname, FCP_REAL_IFNAME);
			while (vp) {
				if (vp->auto_created) {
					vp->ready = 0;
					fcp_set_next_action(vp, FCP_DESTROY_IF);
				}
				vp = fcm_find_next_fcoe_port(vp, p->ifname);
			}
			rc = SUCCESS;
			break;
		}
		rc = libfcoe_control->destroy(p);
		p->fchost[0] = '\0';
		break;
	case FCP_ENABLE_IF:
		FCM_LOG_DBG("OP: ENABLE %s\n", p->ifname);
		rc = libfcoe_control->enable(p);
		break;
	case FCP_DISABLE_IF:
		FCM_LOG_DBG("OP: DISABLE %s\n", p->ifname);
		if (p->auto_vlan) {
			/* disable all the VLANs */
			vp = fcm_find_fcoe_port(p->ifname, FCP_REAL_IFNAME);
			while (vp) {
				if (vp->auto_created) {
					vp->ready = 0;
					fcp_set_next_action(vp, FCP_DISABLE_IF);
				}
				vp = fcm_find_next_fcoe_port(vp, p->ifname);
			}
			break;
		}
		rc = libfcoe_control->disable(p);
		break;
	case FCP_RESET_IF:
		FCM_LOG_DBG("OP: RESET %s\n", p->ifname);

		if (strlen(p->fchost) <= 0)  {
			fcm_cli_reply(p->sock_reply, ENOFCHOST);
			return;
		}

		sprintf(path, "%s/%s/issue_lip", SYSFS_FCHOST, p->fchost);
		FCM_LOG_DBG("OP: RESET %s\n", path);
		rc = fcm_fcoe_if_action(path, "1");
		break;
	case FCP_SCAN_IF:
		FCM_LOG_DBG("OP: SCAN %s\n", p->ifname);
		if (strlen(p->fchost) <= 0)  {
			fcm_cli_reply(p->sock_reply, ENOFCHOST);
			return;
		}

		sprintf(path, "%s/%s/device/scsi_host/%s/scan",
			SYSFS_FCHOST, p->fchost, p->fchost);
		FCM_LOG_DBG("OP: SCAN %s\n", path);
		rc = fcm_fcoe_if_action(path, "- - -");
		break;
	case FCP_VLAN_DISC:
		FCM_LOG_DBG("OP: VLAN DISC %s\n", p->ifname);
		rc = fcm_start_vlan_disc(p);
		break;
	default:
		return;
		break;
	}

	if (p->sock_reply) {
		fcm_cli_reply(p->sock_reply, rc);
		free(p->sock_reply);
		p->sock_reply = NULL;
	}

	p->last_action = p->action;
}

/*
 * Called for all ports.  For FCoE ports and candidates,
 * get IEEE DCBX information and set the next action.
 */
static void fcm_netif_ieee_advance(struct fcm_netif *ff)
{
	enum fcp_action action;

	ASSERT(ff);
	ASSERT(fcm_clif);

	if (fcm_clif->cl_busy)
		return;

	if (ff->ieee_resp_pending)
		return;

	switch (ff->ieee_state) {
	case IEEE_INIT:
		break;
	case IEEE_GET_STATE:
		ieee_get_req(ff);
		break;
	case IEEE_DONE:
		action = validate_ieee_info(ff);
		if (action == FCP_ACTIVATE_IF) {
			fcp_action_set(ff->ifname, action);
			ieee_state_set(ff, IEEE_ACTIVE);
		}
		break;
	case IEEE_ACTIVE:
		/* TBD enable and disable if needed in IEEE mode */
		break;
	default:
		break;
	}
}

/*
 * Called for all ports.  For FCoE ports and candidates,
 * get information and send to dcbd.
 */
static void fcm_netif_advance(struct fcm_netif *ff)
{
	char buf[80], params[30];

	ASSERT(ff);
	ASSERT(fcm_clif);

	if (fcm_clif->cl_busy)
		return;

	if (ff->response_pending)
		return;

	if (sa_timer_active(&ff->dcbd_retry_timer))
		return;

	switch (ff->ff_dcbd_state) {
	case FCD_INIT:
	case FCD_ERROR:
		break;
	case FCD_GET_DCB_STATE:
		snprintf(buf, sizeof(buf), "%c%x%2.2x%2.2x%2.2x%2.2x%s",
			 DCB_CMD, CLIF_RSP_VERSION,
			 CMD_GET_CONFIG, FEATURE_DCB, 0,
			 (u_int) strlen(ff->ifname), ff->ifname);
		ff->response_pending = fcm_dcbd_request(buf);
		break;
	case FCD_SEND_CONF:
		snprintf(params, sizeof(params), "%x1%x02",
			 ff->ff_app_info.enable,
			 ff->ff_app_info.willing);
		snprintf(buf, sizeof(buf), "%c%x%2.2x%2.2x%2.2x%2.2x%s%s",
			 DCB_CMD, CLIF_RSP_VERSION,
			 CMD_SET_CONFIG, FEATURE_APP, APP_FCOE_STYPE,
			 (u_int) strlen(ff->ifname), ff->ifname, params);
		ff->response_pending = fcm_dcbd_request(buf);
		break;
	case FCD_GET_PFC_CONFIG:
		snprintf(buf, sizeof(buf), "%c%x%2.2x%2.2x%2.2x%2.2x%s%s",
			 DCB_CMD, CLIF_RSP_VERSION,
			 CMD_GET_CONFIG, FEATURE_PFC, 0,
			 (u_int) strlen(ff->ifname), ff->ifname, "");
		ff->response_pending = fcm_dcbd_request(buf);
		break;
	case FCD_GET_APP_CONFIG:
		snprintf(buf, sizeof(buf), "%c%x%2.2x%2.2x%2.2x%2.2x%s%s",
			 DCB_CMD, CLIF_RSP_VERSION,
			 CMD_GET_CONFIG, FEATURE_APP, APP_FCOE_STYPE,
			 (u_int) strlen(ff->ifname), ff->ifname, "");
		ff->response_pending = fcm_dcbd_request(buf);
		break;
	case FCD_GET_PFC_OPER:
		snprintf(buf, sizeof(buf), "%c%x%2.2x%2.2x%2.2x%2.2x%s%s",
			 DCB_CMD, CLIF_RSP_VERSION,
			 CMD_GET_OPER, FEATURE_PFC, 0,
			 (u_int) strlen(ff->ifname), ff->ifname, "");
		ff->response_pending = fcm_dcbd_request(buf);
		break;
	case FCD_GET_APP_OPER:
		snprintf(buf, sizeof(buf), "%c%x%2.2x%2.2x%2.2x%2.2x%s%s",
			 DCB_CMD, CLIF_RSP_VERSION,
			 CMD_GET_OPER, FEATURE_APP, APP_FCOE_STYPE,
			 (u_int) strlen(ff->ifname), ff->ifname, "");
		ff->response_pending = fcm_dcbd_request(buf);
		break;
	case FCD_GET_PEER:
		snprintf(buf, sizeof(buf), "%c%x%2.2x%2.2x%2.2x%2.2x%s%s",
			 DCB_CMD, CLIF_RSP_VERSION,
			 CMD_GET_PEER, FEATURE_APP, APP_FCOE_STYPE,
			 (u_int) strlen(ff->ifname), ff->ifname, "");
		ff->response_pending = fcm_dcbd_request(buf);
		break;
	case FCD_DONE:
		switch (validate_dcbd_info(ff)) {
		case FCP_DESTROY_IF:
			fcp_action_set(ff->ifname, FCP_DESTROY_IF);
			fcm_dcbd_state_set(ff, FCD_INIT);
			break;
		case FCP_ENABLE_IF:
			fcp_action_set(ff->ifname, FCP_ENABLE_IF);
			fcm_dcbd_state_set(ff, FCD_INIT);
			break;
		case FCP_DISABLE_IF:
			fcp_action_set(ff->ifname, FCP_DISABLE_IF);
			fcm_dcbd_state_set(ff, FCD_INIT);
			break;
		case FCP_ACTIVATE_IF:
			fcp_action_set(ff->ifname, FCP_ACTIVATE_IF);
			fcm_dcbd_state_set(ff, FCD_INIT);
			break;
		case FCP_ERROR:
			fcp_action_set(ff->ifname, FCP_DISABLE_IF);
			if (ff->dcbd_retry_cnt < DCBD_MAX_REQ_RETRIES)
				fcm_dcbd_state_set(ff, FCD_ERROR);
			else
				fcm_dcbd_state_set(ff, FCD_INIT);
			break;
		case FCP_WAIT:
		default:
			break;
		}

		break;
	default:
		break;
	}
}

/*
 * Run through these steps at the end of each select loop.
 * 1.  Process list of network interfaces
 *     - issue next dcbd query action
 *     - if query sequence is complete - update FCoE port objects
 *       as necessary with a CREATE or DESTROY next action.
 * 2.  Process FCoE port list - handle next actions, update states, clean up
 */
static void fcm_handle_changes(void)
{
	struct fcm_netif *ff;
	struct fcoe_port *p;

	/*
	 * Perform pending actions (dcbd queries) on network interfaces.
	 */
	TAILQ_FOREACH(ff, &fcm_netif_head, ff_list) {
		fcm_netif_advance(ff);
		fcm_netif_ieee_advance(ff);
	}

	/*
	 * Perform actions on FCoE ports
	 */
	p = fcoe_config.port;
	while (p) {
		ff = fcm_netif_lookup(p->real_ifname);
		if (!ff) {
			if (p->sock_reply) {
				fcm_cli_reply(p->sock_reply, ENOETHDEV);
				free(p->sock_reply);
				p->sock_reply = NULL;
				p->action = FCP_WAIT;
			}
			goto next_port;
		}

		fcm_fcoe_action(p);

		fcp_set_next_action(p, FCP_WAIT);
next_port:
		p = p->next;
	}
}

static void fcm_usage(void)
{
	printf("Usage: %s\n"
	       "\t [-f|--foreground]\n"
	       "\t [-d|--debug=yes|no]\n"
	       "\t [-l|--legacy]\n"
	       "\t [-s|--syslog=yes|no]\n"
	       "\t [-v|--version]\n"
	       "\t [-h|--help]\n\n", progname);
	exit(1);
}

static void fcm_dump(void)
{
	struct fcoe_port *curr, *next;
	struct fcm_netif *ff, *head;

	FCM_LOG("*** !!! fcoemon dump begin !!! ***\n");

	FCM_LOG("*** Listing of fcoe_port instances ***\n");
	for (curr = fcoe_config.port; curr; curr = next) {
		FCM_LOG("** ifname: %s\n", curr->ifname);
		FCM_LOG("ifindex: %d\n", curr->ifindex);
		FCM_LOG("real_ifname: %s\n", curr->real_ifname);
		FCM_LOG("fcoe_enable: %d\n", curr->fcoe_enable);
		FCM_LOG("dcb_required: %d\n", curr->dcb_required);
		FCM_LOG("auto_vlan: %d\n", curr->auto_vlan);
		FCM_LOG("auto_created: %d\n", curr->auto_created);
		FCM_LOG("ready: %d\n", curr->ready);
		FCM_LOG("action: %d\n", curr->action);
		FCM_LOG("last_action: %d\n", curr->last_action);
		FCM_LOG("last_msg_type: %d\n", curr->last_msg_type);
		FCM_LOG("mac: %s\n", curr->mac); //TODO
		FCM_LOG("vlan_disc_count: %d\n", curr->vlan_disc_count);
		FCM_LOG("fip_socket: %d\n", curr->fip_socket); //TODO
		FCM_LOG("fchost: %s\n", curr->fchost);
		FCM_LOG("ctlr: %s\n", curr->ctlr);
		FCM_LOG("last_fc_event_num: %d\n", curr->last_fc_event_num);

		next = curr->next;
	}

	FCM_LOG("*** Listing of fcm_netif instances ***\n");
	for (head = TAILQ_FIRST(&fcm_netif_head); head; head = ff) {
		FCM_LOG("** ifname: %s", head->ifname);
		FCM_LOG("ff_enabled: %d\n", head->ff_enabled);
		FCM_LOG("ff_dcb_state: %d\n", head->ff_dcb_state);
		FCM_LOG("dcbx_cap: %d\n", head->dcbx_cap);
		FCM_LOG("ieee_state: %d\n", head->ieee_state);
		FCM_LOG("ieee_resp_pending: %d\n", head->ieee_resp_pending);
		FCM_LOG("ieee_pfc_info: %d\n", head->ieee_pfc_info);
		FCM_LOG("ieee_app_info: %d\n", head->ieee_app_info);
		FCM_LOG("ff_pfc_info: %d\n", head->ff_pfc_info.op_mode);
		FCM_LOG("ff_app_info: %d\n", head->ff_app_info.op_mode);
		FCM_LOG("ff_operstate: %d\n", head->ff_operstate);
		FCM_LOG("ff_dcb_state: %d\n", head->ff_dcb_state);
		FCM_LOG("response_pending: %d\n", head->response_pending);
		FCM_LOG("dcbd_retry_cnt: %d\n", head->dcbd_retry_cnt);

		ff = TAILQ_NEXT(head, ff_list);
	}

	FCM_LOG("*** !!! fcoemon dump end !!! ***\n");
}

static void fcm_sig(int sig)
{
	switch (sig) {
	case SIGUSR1:
		fcm_dump();
		break;
	default:
		sa_select_exit(sig);
		break;
	}
}

/*
 * TODO: This routine does too much. It executes a 'cmd'
 * and allocates a fcoe_port if one doesn't exist. The
 * function name implies that it only does the latter.
 */
static struct fcoe_port *
fcm_port_create(char *ifname, enum clif_flags flags, int cmd)
{
	struct fcoe_port *p;
	struct fcoe_port *curr;
	struct fcm_netif *ff;

	p = fcm_find_fcoe_port(ifname, FCP_CFG_IFNAME);
	if (p) {
		p->ready = 1;
		if (!p->fcoe_enable) {
			p->fcoe_enable = 1;
			fcp_set_next_action(p, cmd);
			if (p->dcb_required) {
				ff = fcm_netif_lookup(p->real_ifname);
				if (!ff)
					return p;
				fcm_dcbd_state_set(ff, FCD_GET_DCB_STATE);
				if (ff->ff_dcbd_state == FCD_GET_DCB_STATE)
					fcp_set_next_action(p, FCP_WAIT);
			}
		} else {
			p->fcoe_enable = 1;
			fcp_set_next_action(p, cmd);
		}
		return p;
	}

	p = alloc_fcoe_port(ifname);
	if (!p) {
		FCM_LOG_ERR(errno, "fail to allocate fcoe_port %s", ifname);
		return NULL;
	}

	fcm_vlan_dev_real_dev(ifname, p->real_ifname);
	if (!strlen(p->real_ifname))
		snprintf(p->real_ifname, sizeof(p->real_ifname), "%s", ifname);
	p->fcoe_enable = 1;
	p->dcb_required = 0;
	p->mode = flags & CLIF_FLAGS_MODE_MASK;
	fcp_set_next_action(p, cmd);
	p->next = NULL;

	sigprocmask(SIG_BLOCK, &block_sigset, NULL);

	if (!fcoe_config.port)
		fcoe_config.port = p;
	else {
		curr = fcoe_config.port;
		while (curr->next)
			curr = curr->next;
		curr->next = p;
	}

	sigprocmask(SIG_UNBLOCK, &block_sigset, NULL);

	/* check and add the real_ifname to the network interface list */
	ff = fcm_netif_lookup_create(p->real_ifname);
	if (!ff) {
		FCM_LOG_ERR(errno, "fail to allocate fcm_netif %s", ifname);
		return NULL;
	}
	return p;
}

static enum fcoe_status fcm_cli_create(char *ifname, enum clif_flags flags,
				       struct sock_info **r)
{
	struct fcoe_port *p, *vp;
	enum fcoe_status rc = EFAIL;
	char fchost[FCHOSTBUFLEN];

	/* existing fchost already? e.g., from fipvlan -cs */
	if (!fcoe_find_fchost(ifname, fchost, FCHOSTBUFLEN)) {
		FCM_LOG_DBG("Existing fchost %s found for %s\n",
			    fchost, ifname);
		rc = EFCOECONN;
		goto out;
	}

	p = fcm_find_fcoe_port(ifname, FCP_CFG_IFNAME);
	if (p && p->fcoe_enable) {
		/* no action needed */
		rc = EFCOECONN;
		goto out;
	}
	/* re-enable previous VLANs */
	if (p && p->auto_vlan) {
		vp = fcm_find_fcoe_port(p->ifname, FCP_REAL_IFNAME);
		while (vp) {
			if (vp->auto_created)
				vp->fcoe_enable = 1;
			vp = fcm_find_next_fcoe_port(vp, p->ifname);
		}
	}

	/*
	 * This looks odd, and could use some improvement. We may
	 * or may not have found a valid port. fcm_port_create
	 * will execute the 'cmd' even if it doesn't allocate a
	 * new port. fcm_port_create should probably be split
	 * into two routines, one that allocs a new port and one
	 * that executes the command.
	 */
	p = fcm_port_create(ifname, flags, FCP_CREATE_IF);
	if (!p)
		goto out;

	rc = SUCCESS;
	p->sock_reply = *r;

out:
	return rc;
}

static enum fcoe_status fcm_cli_destroy(char *ifname,
					struct sock_info **r)
{
	struct fcoe_port *p;

	p = fcm_find_fcoe_port(ifname, FCP_CFG_IFNAME);
	if (p) {
		if (p->fcoe_enable) {
			p->fcoe_enable = 0;

			if (p->last_action == FCP_DESTROY_IF)
				return ENOACTION;

			fcp_set_next_action(p, FCP_DESTROY_IF);
			p->sock_reply = *r;
			return SUCCESS;
		} else {
			/* no action needed */
			return ENOACTION;
		}
	}

	FCM_LOG_ERR(errno, "%s is not in port list.\n", ifname);
	return EFAIL;
}

static enum fcoe_status fcm_cli_action(char *ifname, int cmd,
				       struct sock_info **r)
{
	struct fcoe_port *p;

	p = fcm_find_fcoe_port(ifname, FCP_CFG_IFNAME);
	if (p) {
		fcp_set_next_action(p, cmd);
		p->sock_reply = *r;
		return SUCCESS;
	}

	FCM_LOG_ERR(errno, "%s is not in port list.\n", ifname);
	return EFAIL;
}

static struct sock_info *fcm_alloc_reply(struct sockaddr_un *f,
					 socklen_t flen, int s)
{
	static struct sock_info *r;

	if (flen > sizeof(*f))
		return NULL;

	r = (struct sock_info *)malloc(sizeof(struct sock_info));
	if (!r) {
		FCM_LOG_ERR(errno, "Failed in alloc reply sock info.\n");
		return NULL;
	}
	r->sock = s;
	memcpy(&r->from, f, flen);
	r->fromlen = flen;
	return r;
}

/*
 * receive function registered in sa_select_loop
 */
static void fcm_srv_receive(void *arg)
{
	struct fcm_srv_info *srv_info = arg;
	struct clif_data *data;
	struct sockaddr_un from;
	socklen_t fromlen = sizeof(struct sockaddr_un);
	struct sock_info *reply = NULL;
	char buf[MAX_MSGBUF], rbuf[MAX_MSGBUF];
	char ifname[IFNAMSIZ + 1];
	enum fcoe_status rc = EFAIL;
	int res, cmd, snum;
	size_t size;

	snum = srv_info->srv_sock;
	res = recvfrom(snum, buf, sizeof(buf) - 1,
		       MSG_DONTWAIT, (struct sockaddr *)&from, &fromlen);
	if (res < 0) {
		FCM_LOG_ERR(errno, "Failed to receive data from socket %d",
			    snum);
		return;
	}

	data = (struct clif_data *)buf;
	size = res;
	if (size < sizeof(*data)) {
		if (size < sizeof(*data) - sizeof(data->flags)) {
			FCM_LOG_ERR(EMSGSIZE,
				    "Message too short from socket %d", snum);
			rc = EBADCLIFMSG;
			goto err;
		}
		data->flags = 0;
	} else if (size > sizeof(*data)) {
		FCM_LOG_ERR(EMSGSIZE, "Message too long from socket %d", snum);
		rc = EBADCLIFMSG;
		goto err;
	}

	cmd = data->cmd;
	strncpy(ifname, data->ifname, IFNAMSIZ);
	ifname[sizeof(data->ifname)] = 0;

	if (cmd != CLIF_PID_CMD) {
		rc = fcoe_validate_interface(ifname);
		if (rc)
			goto err;
	}
	reply = fcm_alloc_reply(&from, fromlen, snum);
	if (!reply)
		goto err_out;

	switch (cmd) {
	case CLIF_CREATE_CMD:
		FCM_LOG_DBG("Received command to create %s\n", ifname);
		rc = fcm_cli_create(ifname, data->flags, &reply);
		if (rc)
			goto err_out;
		break;
	case CLIF_DESTROY_CMD:
		FCM_LOG_DBG("Received command to destroy %s\n", ifname);
		rc = fcm_cli_destroy(ifname, &reply);
		if (rc)
			goto err_out;
		break;
	case CLIF_RESET_CMD:
		FCM_LOG_DBG("Received command to reset %s\n", ifname);
		rc = fcm_cli_action(ifname, FCP_RESET_IF, &reply);
		if (rc)
			goto err_out;
		break;
	case CLIF_SCAN_CMD:
		FCM_LOG_DBG("Received command to scan %s\n", ifname);
		rc = fcm_cli_action(ifname, FCP_SCAN_IF, &reply);
		if (rc)
			goto err_out;
		break;
	case CLIF_PID_CMD:
		FCM_LOG_DBG("FCMON PID\n");
		snprintf(rbuf, MAX_MSGBUF, "%lu", (long unsigned int)getpid());
		sendto(snum, rbuf, strlen(rbuf), 0, (struct sockaddr *)&from,
		       fromlen);
		break;
	default:
		FCM_LOG_DBG("Received invalid command %d for %s\n",
			    cmd, ifname);
		goto err_out;
	}

	return;

err_out:
	free(reply);
err:
	snprintf(rbuf, MSG_RBUF, "%d", rc);
	sendto(snum, rbuf, MSG_RBUF, 0, (struct sockaddr *)&from, fromlen);
}

static int fcm_systemd_socket(void)
{
	char *env, *ptr;
	unsigned int p, l;

	env = getenv("LISTEN_PID");
	if (!env)
		return -1;

	p = strtoul(env, &ptr, 10);
	if (ptr && ptr == env) {
		FCM_LOG_DBG("Invalid value '%s' for LISTEN_PID\n", env);
		return -1;
	}
	if ((pid_t)p != getpid()) {
		FCM_LOG_DBG("Invalid PID '%d' from LISTEN_PID\n", p);
		return -1;
	}
	env = getenv("LISTEN_FDS");
	if (!env) {
		FCM_LOG_DBG("LISTEN_FDS is not set\n");
		return -1;
	}
	l = strtoul(env, &ptr, 10);
	if (ptr && ptr == env) {
		FCM_LOG_DBG("Invalid value '%s' for LISTEN_FDS\n", env);
		return -1;
	}
	if (l != 1) {
		FCM_LOG_DBG("LISTEN_FDS specified %d fds\n", l);
		return -1;
	}
	/* systemd returns fds with an offset of '3' */
	return 3;
}

static int fcm_srv_create(struct fcm_srv_info *srv_info)
{
	socklen_t addrlen;
	struct sockaddr_un addr;
	int rc = 0;

	srv_info->srv_sock = fcm_systemd_socket();
	if (srv_info->srv_sock > 0) {
		FCM_LOG_DBG("Using systemd socket\n");
		goto out_done;
	}

	srv_info->srv_sock = socket(AF_LOCAL, SOCK_DGRAM, 0);
	if (srv_info->srv_sock < 0) {
		FCM_LOG_ERR(errno, "Failed to create socket\n");
		rc = errno;
		goto err;
	}

	memset(&addr, 0, sizeof(addr));
	addr.sun_family = AF_LOCAL;
	addr.sun_path[0] = '\0';
	snprintf(&addr.sun_path[1], sizeof(addr.sun_path) -1,
		 "%s", CLIF_IFNAME);
	addrlen = sizeof(sa_family_t) + strlen(addr.sun_path + 1) + 1;
	if (bind(srv_info->srv_sock, (struct sockaddr *)&addr, addrlen) < 0) {
		FCM_LOG_ERR(errno, "Failed to bind socket\n");
		rc = errno;
		goto err_close;
	}
out_done:
	sa_select_add_fd(srv_info->srv_sock, fcm_srv_receive,
			 NULL, NULL, srv_info);

	FCM_LOG_DBG("Successfully created socket, socket file and binding\n");

	return rc;

err_close:
	close(srv_info->srv_sock);

err:
	return rc;
}

static void fcm_srv_destroy(struct fcm_srv_info *srv_info)
{
	FCM_LOG_DBG("Shutdown fcmon server");
	close(srv_info->srv_sock);
}

int main(int argc, char **argv)
{
	struct fcm_srv_info srv_info;
	struct sigaction sig;
	int fcm_fg = 0;
	int rc;
	int c;

	memset(&fcoe_config, 0, sizeof(fcoe_config));

	strncpy(progname, basename(argv[0]), sizeof(progname));
	progname[sizeof(progname) - 1] = '\0';

	sa_log_prefix = progname;
	sa_log_flags = 0;
	openlog(sa_log_prefix, LOG_CONS, LOG_DAEMON);

	while ((c = getopt_long(argc, argv, "fd:hls:v",
				fcm_options, NULL)) != -1) {
		switch (c) {
		case 'f':
			fcm_fg = 1;
			break;
		case 'd':
			if (!strncmp(optarg, "yes", 3) ||
			    !strncmp(optarg, "YES", 3)) {
				fcoe_config.debug = 1;
				enable_debug_log(1);
			}
			break;
		case 'l':
			force_legacy = true;
			break;
		case 's':
			if (!strncmp(optarg, "yes", 3) ||
			    !strncmp(optarg, "YES", 3)) {
				fcoe_config.use_syslog = 1;
				enable_syslog(1);
			}
			break;
		case 'v':
			printf("%s\n", FCOE_UTILS_VERSION);
			return 0;
		case 'h':
		default:
			fcm_usage();
		break;
		}
	}
	if (argc != optind)
		fcm_usage();

	umask(0);

	/*
	 * Set up for signals.
	 */
	memset(&sig, 0, sizeof(sig));
	sig.sa_handler = fcm_sig;

	/*
	 * SIGUSR1 will walk the fcoe_port and fcm_netif lists,
	 * therefore we need to disable this interrupt when we
	 * do any list manipulations. The 'block_sigset' is the
	 * set of signals that will be blocked before list
	 * manipulations on either of the aforementioned lists.
	 */
	sigemptyset(&block_sigset);
	sigaddset(&block_sigset, SIGUSR1);

	rc = sigaction(SIGINT, &sig, NULL);
	if (rc < 0) {
		FCM_LOG_ERR(errno, "Failed to register handler for SIGINT");
		exit(1);
	}
	rc = sigaction(SIGTERM, &sig, NULL);
	if (rc < 0) {
		FCM_LOG_ERR(errno, "Failed to register handler for SIGTERM");
		exit(1);
	}
	rc = sigaction(SIGHUP, &sig, NULL);
	if (rc < 0) {
		FCM_LOG_ERR(errno, "Failed to register handler for SIGHUP");
		exit(1);
	}
	rc = sigaction(SIGUSR1, &sig, NULL);
	if (rc < 0) {
		FCM_LOG_ERR(errno, "Failed to register handler for SIGUSR1");
		exit(1);
	}

	/* check fcoe module */
	if (fcoe_checkdir(SYSFS_FCOE)) {
		FCM_LOG_ERR(errno, "make sure FCoE driver module is loaded!");
		exit(1);
	}

	fcm_fcoe_init();
	rc = fcm_fc_events_init();
	if (rc != 0)
		exit(1);

	rc = fcm_link_init();	/* NETLINK_ROUTE protocol */
	if (rc != 0)
		goto err_cleanup;

	if (fcoe_config.dcb_init)
		fcm_dcbd_init();

	rc = fcm_srv_create(&srv_info);
	if (rc == EADDRINUSE) {
		FCM_LOG("Daemon already running OR Failed to bind socket so exiting!\n");
		exit(1);
	}
	if (rc != 0)
		goto err_cleanup;

	if (!fcm_fg && daemon(0, !fcoe_config.use_syslog)) {
		FCM_LOG("Starting daemon failed");
		goto err_cleanup;
	}

	sa_select_set_callback(fcm_handle_changes);

	rc = sa_select_loop();
	if (rc < 0) {
		FCM_LOG_ERR(rc, "select error\n");
		goto err_cleanup;
	}
	fcm_dcbd_shutdown();
	fcm_srv_destroy(&srv_info);
	if (rc == SIGHUP)
		fcm_cleanup();
	return 0;

err_cleanup:
	fcm_cleanup();
	exit(1);
}

/*******************************************************
 *         The following are debug routines            *
 *******************************************************/
static void add_msg_to_buf(char *buf, size_t maxlen, char *msg, char *prefix)
{
	size_t len = strlen(buf);

	if (len + strlen(msg) + strlen(prefix) < maxlen)
		sprintf(buf+len, "%s%s", prefix, msg);
}

static void print_errors(int errors)
{
	char msg[256];
	int cnt = 0;

	memset(msg, 0, sizeof(msg));
	sprintf(msg, "0x%02x - ", errors);

	if (errors & 0x01)
		add_msg_to_buf(msg, sizeof(msg), "mismatch with peer",
			       (cnt++) ? ", " : "");

	if (errors & 0x02)
		add_msg_to_buf(msg, sizeof(msg), "local configuration error",
			       (cnt++) ? ", " : "");

	if (errors & 0x04)
		add_msg_to_buf(msg, sizeof(msg), "multiple TLV's received",
			       (cnt++) ? ", " : "");

	if (errors & 0x08)
		add_msg_to_buf(msg, sizeof(msg), "peer error",
			       (cnt++) ? ", " : "");

	if (errors & 0x10)
		add_msg_to_buf(msg, sizeof(msg), "multiple LLDP neighbors",
			       (cnt++) ? ", " : "");

	if (errors & 0x20)
		add_msg_to_buf(msg, sizeof(msg), "peer feature not present",
			       (cnt++) ? ", " : "");

	if (!errors)
		add_msg_to_buf(msg, sizeof(msg), "none", "");

	FCM_LOG("%s\n", msg);
}
