/*
 *  Copyright (C) 2002 - 2003 Ardis Technologies <roman@ardistech.com>
 *  Copyright (C) 2007 - 2018 Vladislav Bolkhovitin
 *  Copyright (C) 2007 - 2018 Western Digital Corporation
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation, version 2
 *  of the License.
 *
 *  This program is distributed in the hope that 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.
 */

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <netdb.h>
#include <signal.h>

#include <poll.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/ioctl.h>

#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netinet/ip.h>
#include <arpa/inet.h>

#include "iscsid.h"
#include "iscsi_adm.h"

static char *server_addresses[ADDR_MAX];
uint16_t server_port = ISCSI_LISTEN_PORT;

struct pollfd poll_array[POLL_MAX];

static struct connection *incoming[INCOMING_MAX];
static int incoming_cnt;
int ctrl_fd, ipc_fd, nl_fd;
int conn_blocked;

struct iscsi_init_params iscsi_init_params;

static const char program_name[] = "iscsi-scstd";

static struct option const long_options[] = {
	{"config", required_argument, 0, 'c'},
	{"foreground", no_argument, 0, 'f'},
	{"debug", required_argument, 0, 'd'},
	{"uid", required_argument, 0, 'u'},
	{"gid", required_argument, 0, 'g'},
	{"address", required_argument, 0, 'a'},
	{"port", required_argument, 0, 'p'},
	{"version", no_argument, 0, 'v'},
	{"help", no_argument, 0, 'h'},
	{0, 0, 0, 0},
};

int init_report_pipe[2];

static void usage(int status)
{
	if (status != 0)
		fprintf(stderr, "Try `%s --help' for more information.\n", program_name);
	else {
		printf("Usage: %s [OPTION]\n", program_name);
		printf("\
iSCSI target daemon.\n\
  -c, --config=[path]     Execute in the config file.\n");
		printf("\
  -f, --foreground           make the program run in the foreground\n\
  -d, --debug debuglevel     print debugging information\n\
  -u, --uid=uid              run as uid, default is current user\n\
  -g, --gid=gid              run as gid, default is current user group\n\
  -a, --address=address ...  listen on specified space-separated list of local address instead of all\n\
  -p, --port=port            listen on specified port instead of 3260\n\
  -h, --help                 display this help and exit\n\
");
	}
	exit(1);
}

const char *get_error_str(int error)
{
	if (error == EAI_SYSTEM)
		return strerror(errno);
	else
		return gai_strerror(error);
}

static void create_listen_socket(struct pollfd *array)
{
	struct addrinfo hints, *res, *res0;
	char servname[64];
	int i, k, sock, opt, rc;

	memset(servname, 0, sizeof(servname));
	snprintf(servname, sizeof(servname), "%d", server_port);

	memset(&hints, 0, sizeof(hints));
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE;

	i = 0;
	for (k = 0; k < ADDR_MAX; k++) {
		char *server_address;

		server_address = server_addresses[k];
		if (k > 0 && server_address == NULL)
			break;

		if (i == LISTEN_MAX) {
			log_error("Cannot handle address %s! Too many were specified.", server_address);
			exit(1);
		}

		rc = getaddrinfo(server_address, servname, &hints, &res0);
		if (rc != 0) {
			log_error("Unable to get address info [%s] (%s)!",
				  server_address, get_error_str(rc));
			exit(1);
		}

		for (res = res0; res && i < LISTEN_MAX; res = res->ai_next) {
			sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
			if (sock < 0) {
				log_error("Unable to create server socket (%s) %d %d %d!",
					  strerror(errno), res->ai_family,
					  res->ai_socktype, res->ai_protocol);
				continue;
			}

			sock_set_keepalive(sock, 50);

			opt = 1;
			if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)))
				log_warning("Unable to set SO_REUSEADDR on server socket (%s)!",
					    strerror(errno));
			opt = 1;
			if (res->ai_family == AF_INET6 &&
				setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt))) {
				log_error("Unable to restrict IPv6 socket (%s)", strerror(errno));
				close(sock);
				continue;
			}

			if (bind(sock, res->ai_addr, res->ai_addrlen)) {
				log_error("Unable to bind server socket (%s)!", strerror(errno));
				close(sock);
				continue;
			}

			if (listen(sock, INCOMING_MAX)) {
				log_error("Unable to listen to server socket (%s)!", strerror(errno));
				close(sock);
				continue;
			}

			set_non_blocking(sock);

			array[i].fd = sock;
			array[i].events = POLLIN;

			i++;
		}

		if (res)
			log_error("Unable to listen on all available sockets.");

		freeaddrinfo(res0);
	}

	if (i == 0)
		exit(1);
}

static struct connection *alloc_and_init_conn(int fd)
{
	struct pollfd *pollfd;
	struct connection *conn = NULL;
	int i;

	for (i = 0; i < INCOMING_MAX; i++) {
		if (!incoming[i])
			break;
	}
	if (i >= INCOMING_MAX) {
		log_error("Unable to find incoming slot? %d\n", i);
		goto out;
	}

	conn = conn_alloc();
	if (!conn) {
		log_error("Fail to allocate %s", "conn\n");
		goto out;
	}

	conn->fd = fd;
	incoming[i] = conn;

	pollfd = &poll_array[POLL_INCOMING + i];
	pollfd->fd = fd;
	pollfd->events = POLLIN;
	pollfd->revents = 0;

	conn_read_pdu(conn);
	set_non_blocking(fd);

out:
	return conn;
}

static int transmit_iser(int fd, bool start)
{
	int opt = start;

	return ioctl(fd, RDMA_CORK, &opt, sizeof(opt));
}

static int cork_transmit_iser(int fd)
{
	return transmit_iser(fd, true);
}

static int uncork_transmit_iser(int fd)
{
	return transmit_iser(fd, false);
}

static void create_iser_listen_socket(struct pollfd *array)
{
	struct addrinfo hints, *res, *res0;
	char servname[64];
	char *server_address;
	int rc, i, k;
	int iser_fd;
	struct isert_addr_info info;

	iser_fd = create_and_open_dev("isert_scst", 1);

	if (iser_fd >= 0) {
		poll_array[POLL_ISER_LISTEN].fd = iser_fd;
		poll_array[POLL_ISER_LISTEN].events = POLLIN;

		/* RDMAExtensions */
		session_keys[key_rdma_extensions].max = 1;
		session_keys[key_rdma_extensions].local_def = 1;
	} else {
		poll_array[POLL_ISER_LISTEN].fd = -1;
		poll_array[POLL_ISER_LISTEN].events = 0;
		return;
	}

	memset(servname, 0, sizeof(servname));
	snprintf(servname, sizeof(servname), "%d", server_port);

	memset(&hints, 0, sizeof(hints));
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE;

	i = 0;
	for (k = 0; k < ADDR_MAX; k++) {
		server_address = server_addresses[k];

		if (k > 0 && server_address == NULL)
			break;

		if (i == ISERT_MAX_PORTALS) {
			log_error("iSER: Cannot handle address %s! Too many were specified.", server_address);
			exit(1);
		}

		rc = getaddrinfo(server_address, servname, &hints, &res0);
		if (rc != 0) {
			log_error("iSER: Unable to get address info[%s] (%s)!",
				server_address, get_error_str(rc));
			exit(1);
		}

		for (res = res0; res && i < ISERT_MAX_PORTALS; res = res->ai_next) {
			memcpy(&info.addr, res->ai_addr, res->ai_addrlen);
			info.addr_len = res->ai_addrlen;

			rc = ioctl(iser_fd, SET_LISTEN_ADDR, &info);
			if (rc != 0) {
				log_error("iSER: Unable to set listen address (%s)!",
					strerror(errno));
			}
			++i;
		}

		if (res)
			log_error("iSER: Unable to listen on all available sockets.");

		freeaddrinfo(res0);
	}
}

static int iser_getsockname(int fd, struct sockaddr *name, socklen_t *namelen)
{
	struct isert_addr_info addr;
	int ret;

	ret = ioctl(fd, GET_PORTAL_ADDR, &addr, sizeof(addr));
	if (ret)
		return ret;

	memcpy(name, &addr.addr, addr.addr_len);
	*namelen = addr.addr_len;

	return ret;
}

static int iser_is_discovery(int fd)
{
	int val = 1;

	return ioctl(fd, DISCOVERY_SESSION, &val, sizeof(val));
}

static void iser_accept(int fd)
{
	char buff[256];
	int ret, conn_fd;
	struct connection *conn;
	char target_portal[ISCSI_PORTAL_LEN], target_portal_port[NI_MAXSERV];
	struct isert_addr_info addr;

	ret = read(fd, buff, sizeof(buff));
	if (ret == -1)
		goto out;

	conn_fd = open(buff, O_RDWR);
	if (conn_fd == -1) {
		log_error("open(iser_connection) %s failed: %s\n",
			buff, strerror(errno));
		goto out;
	}

	ret = ioctl(conn_fd, GET_PORTAL_ADDR, &addr, sizeof(addr));
	if (ret) {
		log_error("ioctl(GET_PORTAL_ADDR) failed: %s\n",
			  strerror(errno));
		goto out_close;
	}

	ret = getnameinfo((struct sockaddr *)&addr, sizeof(addr), target_portal,
			 sizeof(target_portal), target_portal_port,
			 sizeof(target_portal_port),
			 NI_NUMERICHOST | NI_NUMERICSERV);
	if (ret != 0) {
		log_error("Target portal getnameinfo() failed: %s!",
			get_error_str(ret));
		goto out_close;
	}

	log_info("iSER Connect to %s:%s", target_portal, target_portal_port);

	if (conn_blocked) {
		log_warning("Connection refused due to blocking\n");
		goto out_close;
	}

	conn = alloc_and_init_conn(conn_fd);
	if (!conn)
		goto out_close;

	conn->target_portal = strdup(target_portal);
	if (conn->target_portal == NULL) {
		log_error("Unable to duplicate target portal %s", target_portal);
		goto out_free;
	}

	conn->cork_transmit = cork_transmit_iser;
	conn->uncork_transmit = uncork_transmit_iser;
	conn->getsockname = iser_getsockname;
	conn->is_discovery = iser_is_discovery;
	conn->is_iser = true;
	incoming_cnt++;

out:
	return;

out_free:
	conn_free(conn);

out_close:
	close(conn_fd);
	goto out;
}

static int transmit_sock(int fd, bool start)
{
	int opt = start;

	return setsockopt(fd, SOL_TCP, TCP_CORK, &opt, sizeof(opt));
}

static int cork_transmit_sock(int fd)
{
	return transmit_sock(fd, true);
}

static int uncork_transmit_sock(int fd)
{
	return transmit_sock(fd, false);
}

static int tcp_is_discovery(int fd)
{
	return 0;
}

static void accept_connection(int listen)
{
	union {
		struct sockaddr sa;
		struct sockaddr_in sin;
		struct sockaddr_in6 sin6;
	} from, to;
	socklen_t namesize;
	struct connection *conn;
	int fd, rc;
	char initiator_addr[ISCSI_PORTAL_LEN], initiator_port[NI_MAXSERV];
	char target_portal[ISCSI_PORTAL_LEN], target_portal_port[NI_MAXSERV];

	namesize = sizeof(from);
	if ((fd = accept(listen, &from.sa, &namesize)) < 0) {
		switch (errno) {
		case EINTR:
		case EAGAIN:
		case ENETDOWN:
		case EPROTO:
		case ENOPROTOOPT:
		case EHOSTDOWN:
		case ENONET:
		case EHOSTUNREACH:
		case EOPNOTSUPP:
		case ENETUNREACH:
			break;
		default:
			log_error("accept(incoming_socket) failed: %s",
				strerror(errno));
			exit(1);
		}
		goto out;
	}

	namesize = sizeof(to);
	rc = getsockname(fd, &to.sa, &namesize);
	if (rc != 0) {
		log_error("getsockname() failed: %s", strerror(errno));
		goto out_close;
	}

	rc = getnameinfo(&to.sa, sizeof(to), target_portal, sizeof(target_portal),
		target_portal_port, sizeof(target_portal_port),
		NI_NUMERICHOST | NI_NUMERICSERV);
	if (rc != 0) {
		log_error("Target portal getnameinfo() failed: %s!",
			get_error_str(rc));
		goto out_close;
	}

	rc = getnameinfo(&from.sa, sizeof(from), initiator_addr,
		 sizeof(initiator_addr), initiator_port,
		 sizeof(initiator_port), NI_NUMERICHOST | NI_NUMERICSERV);
	if (rc != 0) {
		log_error("Initiator getnameinfo() failed: %s!",
			get_error_str(rc));
		goto out_close;
	}

	log_info("Connect from %s:%s to %s:%s", initiator_addr, initiator_port,
		target_portal, target_portal_port);

	if (conn_blocked) {
		log_warning("Connection refused due to blocking\n");
		goto out_close;
	}

	conn = alloc_and_init_conn(fd);
	if (!conn)
		goto out_close;

	conn->target_portal = strdup(target_portal);
	if (conn->target_portal == NULL) {
		log_error("Unable to duplicate target portal %s", target_portal);
		goto out_free;
	}

	conn->cork_transmit = cork_transmit_sock;
	conn->uncork_transmit = uncork_transmit_sock;
	conn->getsockname = getsockname;
	conn->is_discovery = tcp_is_discovery;
	conn_read_pdu(conn);

	incoming_cnt++;

out:
	return;

out_free:
	conn_free(conn);

out_close:
	close(fd);
	goto out;
}

static void __set_fd(int idx, int fd)
{
	poll_array[idx].fd = fd;
	poll_array[idx].events = fd ? POLLIN : 0;
}

void isns_set_fd(int isns, int scn_listen, int scn)
{
	__set_fd(POLL_ISNS, isns);
	__set_fd(POLL_SCN_LISTEN, scn_listen);
	__set_fd(POLL_SCN, scn);
}

static void event_conn(struct connection *conn, struct pollfd *pollfd)
{
	int res;

again:
	switch (conn->iostate) {
	case IOSTATE_READ_BHS:
	case IOSTATE_READ_AHS_DATA:
	      read_again:
		errno = 0;	/* for the log_debug() */
		res = read(pollfd->fd, conn->buffer, conn->rwsize);
		if (res <= 0) {
			log_debug(1, "read(%u, %p, %u) returned %d, errno=%u",
			      pollfd->fd, conn->buffer, conn->rwsize, res, errno);
			if (res == 0 || (errno != EINTR && errno != EAGAIN)) {
				conn->state = STATE_DROP;
				goto out;
			} else if (errno == EINTR)
				goto read_again;
			break;
		}
		conn->rwsize -= res;
		conn->buffer += res;
		if (conn->rwsize)
			break;

		switch (conn->iostate) {
		case IOSTATE_READ_BHS:
			conn->iostate = IOSTATE_READ_AHS_DATA;
			conn->req.ahssize = conn->req.bhs.ahslength * 4;
			conn->req.datasize = ((conn->req.bhs.datalength[0] << 16) +
					      (conn->req.bhs.datalength[1] << 8) +
					      conn->req.bhs.datalength[2]);
			conn->rwsize = (conn->req.ahssize + conn->req.datasize + 3) & -4;
			if (conn->rwsize > INCOMING_BUFSIZE) {
				log_warning("Recv PDU with invalid size %d "
					"(max: %d)", conn->rwsize,
					INCOMING_BUFSIZE);
				conn->state = STATE_DROP;
				goto out;
			}
			if (conn->rwsize) {
				if (!conn->req_buffer) {
					conn->req_buffer = malloc(INCOMING_BUFSIZE);
					if (!conn->req_buffer) {
						log_error("Failed to alloc recv buffer");
						conn->state = STATE_DROP;
						goto out;
					}
				}
				conn->buffer = conn->req_buffer;
				conn->req.ahs = conn->buffer;
				conn->req.data = conn->buffer + conn->req.ahssize;
				goto read_again;
			}
			/* fall-through */

		case IOSTATE_READ_AHS_DATA:
			conn_write_pdu(conn);
			pollfd->events = POLLOUT;

			log_pdu(2, &conn->req);
			if (!cmnd_execute(conn))
				conn->state = STATE_EXIT;

			if (conn->state == STATE_EXIT) {
				/* We need to send response */
				goto again;
			}
			break;
		}
		break;

	case IOSTATE_WRITE_BHS:
	case IOSTATE_WRITE_AHS:
	case IOSTATE_WRITE_DATA:
	      write_again:
		conn->cork_transmit(pollfd->fd);
		res = write(pollfd->fd, conn->buffer, conn->rwsize);
		if (res < 0) {
			log_debug(1, "write(%u, %p, %u) returned %d, errno=%u",
			      pollfd->fd, conn->buffer, conn->rwsize, res, errno);
			if (errno != EINTR && errno != EAGAIN) {
				conn->state = STATE_DROP;
				goto out;
			} else if (errno == EINTR)
				goto write_again;
			break;
		}

		conn->rwsize -= res;
		conn->buffer += res;
		if (conn->rwsize)
			goto write_again;

		switch (conn->iostate) {
		case IOSTATE_WRITE_BHS:
			if (conn->rsp.ahssize) {
				conn->iostate = IOSTATE_WRITE_AHS;
				conn->buffer = conn->rsp.ahs;
				conn->rwsize = conn->rsp.ahssize;
				goto write_again;
			}
			/* fall-through */
		case IOSTATE_WRITE_AHS:
			if (conn->rsp.datasize) {
				int o;

				conn->iostate = IOSTATE_WRITE_DATA;
				conn->buffer = conn->rsp.data;
				conn->rwsize = conn->rsp.datasize;
				o = conn->rwsize & 3;
				if (o) {
					for (o = 4 - o; o; o--)
						*((u8 *) conn->buffer + conn->rwsize++) =
						    0;
				}
				goto write_again;
			}
			/* fall-through */
		case IOSTATE_WRITE_DATA:
			conn->uncork_transmit(pollfd->fd);
			cmnd_finish(conn);

			switch (conn->state) {
			case STATE_KERNEL:
				conn_pass_to_kern(conn, pollfd->fd);
				if (conn->passed_to_kern)
					conn->state = STATE_CLOSE;
				else
					conn->state = STATE_EXIT;
				break;
			case STATE_EXIT:
			case STATE_CLOSE:
			case STATE_DROP:
				break;
			default:
				conn_read_pdu(conn);
				pollfd->events = POLLIN;
				break;
			}
			break;
		}

		break;
	default:
		log_error("illegal iostate %d for port %d!\n", conn->iostate,
			pollfd->fd);
		exit(1);
	}
out:
	return;
}

static void event_loop(void)
{
	int res, i;

	create_listen_socket(poll_array + POLL_LISTEN);
	create_iser_listen_socket(poll_array);

	poll_array[POLL_IPC].fd = ipc_fd;
	poll_array[POLL_IPC].events = POLLIN;
	poll_array[POLL_NL].fd = nl_fd;
	poll_array[POLL_NL].events = POLLIN;

	for (i = 0; i < INCOMING_MAX; i++) {
		poll_array[POLL_INCOMING + i].fd = -1;
		poll_array[POLL_INCOMING + i].events = 0;
		incoming[i] = NULL;
	}

	close(init_report_pipe[0]);
	res = 0;

	if (log_daemon)
		res = write(init_report_pipe[1], &res, sizeof(res));

	close(init_report_pipe[1]);

	while (1) {
		if (!iscsi_enabled) {
			handle_iscsi_events(nl_fd, true);
			continue;
		}
		res = poll(poll_array, POLL_MAX, isns_timeout);
		if (res == 0) {
			isns_handle(1);
			continue;
		} else if (res < 0) {
			if (errno == EINTR)
				continue;
			else if (errno == EINVAL)
				log_error("%s: poll() failed with EINVAL. Should "
					"you increase RLIMIT_NOFILE (ulimit -n)? "
					"Or upgrade your kernel? Kernels below 2.6.19 "
					"have a bug, which doesn't allow poll() "
					"to work with >256 file descriptors. See "
					"http://sourceforge.net/mailarchive/forum.php?"
					"thread_name=9392A06CB0FDC847B3A530B3DC174E7B0"
					"55F1EF3%40mse10be1.mse10.exchange.ms&forum_"
					"name=scst-devel for more details. Alternatively, "
					"you can decrease iscsi_scstd.c::INCOMING_MAX "
					"constant to a lower value, e.g. 128, then "
					"recompile and reinstall the user space part "
					"of iSCSI-SCST.", __func__);
			else
				log_error("%s: poll() failed: %s", __func__,
					strerror(errno));
			exit(1);
		}

		for (i = 0; i < LISTEN_MAX; i++) {
			if (poll_array[POLL_LISTEN + i].revents
			    && incoming_cnt < INCOMING_MAX)
				accept_connection(poll_array[POLL_LISTEN + i].fd);
		}

		if (poll_array[POLL_NL].revents)
			handle_iscsi_events(nl_fd, false);

		if (poll_array[POLL_IPC].revents)
			iscsi_adm_request_handle(ipc_fd);

		if (poll_array[POLL_ISNS].revents)
			isns_handle(0);

		if (poll_array[POLL_SCN_LISTEN].revents)
			isns_scn_handle(1);

		if (poll_array[POLL_SCN].revents)
			isns_scn_handle(0);

		if (poll_array[POLL_ISER_LISTEN].revents)
			iser_accept(poll_array[POLL_ISER_LISTEN].fd);

		for (i = 0; i < INCOMING_MAX; i++) {
			struct connection *conn = incoming[i];
			struct pollfd *pollfd = &poll_array[POLL_INCOMING + i];

			if (!conn || !pollfd->revents)
				continue;

			pollfd->revents = 0;

			event_conn(conn, pollfd);

			if ((conn->state == STATE_CLOSE) ||
			    (conn->state == STATE_EXIT) ||
			    (conn->state == STATE_DROP)) {
				struct session *sess = conn->sess;

				log_debug(1, "closing conn %p state=0x%x fd=%u",
					  conn, conn->state, pollfd->fd);
				conn_free_pdu(conn);
				close(pollfd->fd);
				pollfd->fd = -1;
				incoming[i] = NULL;
				incoming_cnt--;
				if (conn->state != STATE_CLOSE) {
					if (conn->passed_to_kern) {
						kernel_conn_destroy(conn->tid,
							conn->sess->sid.id64,
							conn->cid);
					} else {
						/*
						 * Check if session could not be established,
						 * but sessions count was already incremented
						 */
						if (!sess && conn->sessions_count_incremented)
							conn->target->sessions_count--;
						log_debug(1, "conn %p freed (sess %p, empty %d)",
							conn, sess,
							sess ? list_empty(&sess->conn_list) : -1);
						conn_free(conn);
						if (sess && list_empty(&sess->conn_list))
							session_free(sess);
					}
				}
			}
		}
	}
}

static void init_max_params(void)
{
	if ((session_keys[key_max_recv_data_length].local_def != -1) ||
	    (session_keys[key_max_recv_data_length].max != -1) ||
	    (session_keys[key_max_xmit_data_length].local_def != -1) ||
	    (session_keys[key_max_xmit_data_length].max != -1) ||
	    (session_keys[key_max_burst_length].local_def != -1) ||
	    (session_keys[key_max_burst_length].max != -1) ||
	    (session_keys[key_first_burst_length].max != -1)) {
		log_error("Wrong session_keys initialization");
		exit(-1);
	}

	/* QueuedCommands */
	target_keys[key_queued_cmnds].local_def = min((int)target_keys[key_queued_cmnds].local_def,
						      iscsi_init_params.max_queued_cmds);
	target_keys[key_queued_cmnds].max = min((int)target_keys[key_queued_cmnds].max,
						iscsi_init_params.max_queued_cmds);
	target_keys[key_queued_cmnds].min = min((int)target_keys[key_queued_cmnds].min,
						iscsi_init_params.max_queued_cmds);

	/* MaxRecvDataSegmentLength */
	session_keys[key_max_recv_data_length].local_def = iscsi_init_params.max_data_seg_len;
	session_keys[key_max_recv_data_length].max = iscsi_init_params.max_data_seg_len;

	/* MaxXmitDataSegmentLength */
	session_keys[key_max_xmit_data_length].local_def = iscsi_init_params.max_data_seg_len;
	session_keys[key_max_xmit_data_length].max = iscsi_init_params.max_data_seg_len;

	/* MaxBurstLength */
	session_keys[key_max_burst_length].local_def = iscsi_init_params.max_data_seg_len;
	session_keys[key_max_burst_length].max = iscsi_init_params.max_data_seg_len;

	/* FirstBurstLength */
	session_keys[key_first_burst_length].local_def =
		min((int)session_keys[key_first_burst_length].local_def,
		    iscsi_init_params.max_data_seg_len);
	session_keys[key_first_burst_length].max = iscsi_init_params.max_data_seg_len;

	return;
}

int main(int argc, char **argv)
{
	int ch, longindex;
	char *config = NULL;
	uid_t uid = 0;
	gid_t gid = 0;
	int err;


	if (pipe(init_report_pipe) == -1) {
		perror("pipe failed");
		exit(-1);
	}

	/*
	 * Otherwise we could die in some later write() during the event_loop()
	 * instead of getting EPIPE!
	 *
	 * The effects of signal(2) in a multithreaded process are unspecified,
	 * so use sigaction(2) instead.
	 */
	struct sigaction act = (struct sigaction) { .sa_handler = SIG_IGN };
	int rc = sigaction(SIGPIPE, &act, NULL);
	assert(rc == 0);

	while ((ch = getopt_long(argc, argv, "c:fd:s:u:g:a:p:vh", long_options, &longindex)) >= 0) {
		switch (ch) {
		case 'c':
			config = optarg;
			break;
		case 'f':
			log_daemon = 0;
			break;
		case 'd':
			log_level = strtol(optarg, NULL, 0);
			break;
		case 'u':
			uid = strtoul(optarg, NULL, 0);
			break;
		case 'g':
			gid = strtoul(optarg, NULL, 0);
			break;
		case 'a': {
			char *server_address, *token;
			int i = 0;

			server_address = strdup(optarg);
			if (server_address == NULL) {
				perror("strdup failed");
				exit(-1);
			}

			token = strtok(server_address, " ");

			while ((i < ADDR_MAX) && token) {
				log_debug(0, "Address to listen: %s\n", token);
				server_addresses[i] = token;

				i++;
				token = strtok(NULL, " ");
			}

			break;
		}
		case 'p':
			server_port = (uint16_t)strtoul(optarg, NULL, 0);
			break;
		case 'v':
			printf("%s version %s\n", program_name, ISCSI_VERSION_STRING);
			exit(0);
			break;
		case 'h':
			usage(0);
			break;
		default:
			usage(1);
			break;
		}
	}

	if ((ctrl_fd = kernel_open()) < 0)
		exit(-1);

	init_max_params();

	if ((nl_fd = nl_open()) < 0) {
		perror("netlink open failed");
		exit(-1);
	};

	err = kernel_attr_add(NULL, ISCSI_ISNS_SERVER_ATTR_NAME,
			S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR, 0);
	if (err != 0)
		exit(err);

	err = kernel_attr_add(NULL, ISCSI_ENABLED_ATTR_NAME,
			S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR, 0);
	if (err != 0)
		exit(err);
	err = kernel_attr_add(NULL, ISCSI_ISNS_ENTITY_ATTR_NAME,
			S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR, 0);
	if (err != 0)
		exit(err);

	if ((ipc_fd = iscsi_adm_request_listen()) < 0) {
		perror("Opening AF_LOCAL socket failed");
		exit(-1);
	}

	log_init();
	if (log_daemon) {
		char buf[64];
		pid_t pid;
		int fd;

		fd = open("/var/run/iscsi-scstd.pid", O_WRONLY|O_CREAT, 0644);
		if (fd < 0) {
			log_error("unable to create pid file");
			exit(1);
		}
		pid = fork();
		if (pid < 0) {
			log_error("starting daemon failed");
			exit(1);
		} else if (pid) {
			int res = -1;

			close(init_report_pipe[1]);
			if (read(init_report_pipe[0], &res, sizeof(res)) < sizeof(res))
				exit(-1);
			else
				exit(res);
		}

		if (chdir("/") < 0) {
			log_error("failed to set working dir to /: %m");
			exit(1);
		}

		if (lockf(fd, F_TLOCK, 0) < 0) {
			log_error("unable to lock pid file");
			exit(1);
		}
		if (ftruncate(fd, 0) < 0) {
			log_error("failed to ftruncate the PID file: %m");
			exit(1);
		}

		sprintf(buf, "%d\n", getpid());
		if (write(fd, buf, strlen(buf)) < strlen(buf)) {
			log_error("failed to write PID to PID file: %m");
			exit(1);
		}

		close(0);
		open("/dev/null", O_RDWR);
		dup2(0, 1);
		dup2(0, 2);
		setsid();
	}

	err = config_load(config);
	if (err != 0)
		exit(1);

	if (gid && setgid(gid) < 0)
		log_error("setgid failed: %s", strerror(errno));

	if (uid && setuid(uid) < 0)
		log_error("setuid failed: %s", strerror(errno));

	event_loop();

	return 0;
}
