/*
 * Misc helpers
 *
 * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman
 * Copyright (C) 2006 - 2010 Mike Christie
 * Copyright (C) 2006 - 2010 Red Hat, Inc. All rights reserved.
 * maintained by open-iscsi@googlegroups.com
 *
 * 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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.
 *
 * See the file COPYING included with this distribution for more details.
 */
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <sys/prctl.h>

#include "sysdeps.h"
#include "log.h"
#include "iscsi_settings.h"
#include "iface.h"
#include "session_info.h"
#include "iscsi_util.h"

#ifndef PR_SET_IO_FLUSHER
#define PR_SET_IO_FLUSHER 57
#endif

int setup_abstract_addr(struct sockaddr_un *addr, char *unix_sock_name)
{
	memset(addr, 0, sizeof(*addr));
	addr->sun_family = AF_LOCAL;
	strlcpy(addr->sun_path + 1, unix_sock_name, sizeof(addr->sun_path) - 1);
	return offsetof(struct sockaddr_un, sun_path) +
		strlen(addr->sun_path + 1) + 1;
}

void daemon_init(void)
{
	int fd;

	fd = open("/dev/null", O_RDWR);
	if (fd == -1) {
		exit(-1);
	}

	dup2(fd, 0);
	dup2(fd, 1);
	dup2(fd, 2);
	setsid();
	if (chdir("/") < 0)
		log_debug(1, "Could not chdir to /: %s", strerror(errno));
	close(fd);
}

/*
 * make a best effort at ajusting our nice
 * score and our OOM score, but it's not considered
 * fatal if either adjustment fails
 *
 * return 0 on success of OOM adjustment
 */
int oom_adjust(void)
{
	int fd;
	int res = 0;

	errno = 0;
	if (nice(-10) == -1 && errno != 0)
		log_warning("Could not increase process priority: %s",
			  strerror(errno));

	/*
	 * try the modern method of adjusting our OOM score,
	 * then try the old one, if that fails
	 */
	if ((fd = open("/proc/self/oom_score_adj", O_WRONLY)) >= 0) {
		if ((res = write(fd, "-1000", 5)) < 0)
			log_warning("Could not set /proc/self/oom_score_adj to -1000: %s",
				strerror(errno));
	} else if ((fd = open("/proc/self/oom_adj", O_WRONLY)) >= 0) {
		if ((res = write(fd, "-17", 3)) < 0)
			log_warning("Could not set /proc/self/oom_adj to -16: %s",
				strerror(errno));
	} else
		return -1;

	close(fd);
	if (res < 0)
		return res;
	else
		return 0;
}

char*
str_to_ipport(char *str, int *port, int *tpgt)
{
	char *stpgt, *sport = str, *ip = str;

	if (!strchr(ip, '.')) {
		if (*ip == '[') {
			/* IPv6 with [] */
			if (!(sport = strchr(ip, ']')))
				return NULL;
			*sport++ = '\0';
			ip++;
			str = sport;
		} else {
			/* hostname or ipv6 */
			sport = strchr(ip, ':');
			if (sport) {
				if (strchr(sport + 1, ':'))
					/* ipv6 */
					sport = NULL;
				else
					/* hostname:port */
					str = sport;
			}
		}
	}

	if (sport && (sport = strchr(str, ':'))) {
		*sport++ = '\0';
		*port = strtoul(sport, NULL, 10);
		str = sport;
	}

	if ((stpgt = strchr(str, ','))) {
		*stpgt++ = '\0';
		*tpgt = strtoul(stpgt, NULL, 10);
	} else
		*tpgt = PORTAL_GROUP_TAG_UNKNOWN;

	log_debug(2, "ip %s, port %d, tgpt %d", ip, *port, *tpgt);
	return ip;
}

#define ISCSI_MAX_FILES 16384

int increase_max_files(void)
{
	struct rlimit rl;
	int err;

	err = getrlimit(RLIMIT_NOFILE, &rl);
	if (err) {
		log_debug(1, "Could not get file limit (err %d)", errno);
		return errno;
	}
	log_debug(1, "Max file limits %lu %lu",
		        (long unsigned)rl.rlim_cur,
			(long unsigned)rl.rlim_max);

	if (rl.rlim_cur < ISCSI_MAX_FILES)
		rl.rlim_cur = ISCSI_MAX_FILES;
	if (rl.rlim_max < ISCSI_MAX_FILES)
		rl.rlim_max = ISCSI_MAX_FILES;

	err = setrlimit(RLIMIT_NOFILE, &rl);
	if (err) {
		log_debug(1, "Could not set file limit to %lu/%lu (err %d)",
			  (long unsigned)rl.rlim_cur,
			  (long unsigned)rl.rlim_max, errno);
		return errno;
	}

	return 0;
}

/*
 * from linux kernel
 */
char *strstrip(char *s)
{
	size_t size;
	char *end;

	size = strlen(s);
	if (!size)
		return s;

	end = s + size - 1;
	while (end >= s && isspace(*end))
		end--;
	*(end + 1) = '\0';

	while (*s && isspace(*s))
		s++;

	return s;
}

/**
 * cfg_get_string_param - return param value
 * @pathname: pathname and filename of config file
 * @key: param name
 *
 * Assumes the delim is a "=". "#" comments a line, but if
 * the "#" is after the key= then it is a valid value.
*/
char *cfg_get_string_param(char *pathname, const char *key)
{
	FILE *f = NULL;
	char *line, buffer[1024];
	char *value = NULL, *param, *comment;

	if (!pathname) {
		log_error("No pathname to load %s from", key);
		return NULL;
	}

	if ((f = fopen(pathname, "r"))) {
		while ((line = fgets(buffer, sizeof (buffer), f))) {
			param = strstr(line, key);
			if (!param)
				continue;

			/* make sure it is not commented out */
			comment = strchr(line, '#');
			if (comment) {
				if (comment < param)
					continue;
			}

			param = strchr(param, '=');
			if (!param) {
				log_error("Invalid config line for %s. "
					  "Missing '='.", key);
				continue;
			}

			param++;
			if (!strlen(param)) {
				log_error("Invalid config line for %s. "
					  "Missing value", key);
				continue;
			}

			param = strstrip(param);
			if (!strlen(param)) {
				log_error("Invalid config line for %s. "
					  "Missing value", key);
				continue;
			}

			value = strdup(param);
			break;
		}
		fclose(f);
		if (value)
			log_debug(5, "%s=%s", key, value);
	} else
		log_error("can't open %s configuration file %s", key, pathname);

	return value;
}

/**
 * iscsi_addr_match - check if the addrs are to the same ip
 * @address1: pattern
 * @address2: address to check
 *
 * If address1 is blank then it matches any string passed in.
 */
static int iscsi_addr_match(char *address1, char *address2)
{
	struct addrinfo hints1, hints2, *res1, *res2;
	int rc;

	if (!strlen(address1))
		return 1;

	if (!strcmp(address1, address2))
		return 1;

	memset(&hints1, 0, sizeof(struct addrinfo));
	hints1.ai_family = AF_UNSPEC;
	hints1.ai_socktype = SOCK_STREAM;

	memset(&hints2, 0, sizeof(struct addrinfo));
	hints2.ai_family = AF_UNSPEC;
	hints2.ai_socktype = SOCK_STREAM;

	/*
	 * didn't match so we have to resolve to see if one is a dnsname
	 * that matches an ip address.
	 */
	rc = getaddrinfo(address1, NULL, &hints1, &res1);
	if (rc) {
		log_debug(1, "Match error. Could not resolve %s: %s", address1,
			  gai_strerror(rc));
		return 0;

	}

	rc = getaddrinfo(address2, NULL, &hints2, &res2);
	if (rc) {
		log_debug(1, "Match error. Could not resolve %s: %s", address2,
			  gai_strerror(rc));
		rc = 0;
		goto free_res1;
	}

	if ((res1->ai_addrlen != res2->ai_addrlen) ||
	    memcmp(res1->ai_addr, res2->ai_addr, res2->ai_addrlen))
		rc = 0;
	else
		rc = 1;

	freeaddrinfo(res2);
free_res1:
	freeaddrinfo(res1);
	return rc;
}

int __iscsi_match_session(node_rec_t *rec, char *targetname,
			  char *address, int port, struct iface_rec *iface,
			  unsigned sid)
{
	if (!rec) {
		log_debug(6, "no rec info to match");
		return 1;
	}

	log_debug(6, "match session [%s,%s,%d][%s %s,%s,%s]:%u",
		  rec->name, rec->conn[0].address, rec->conn[0].port,
		  rec->iface.name, rec->iface.transport_name,
		  rec->iface.hwaddress, rec->iface.ipaddress,
		  rec->session.sid);

	if (iface)
		log_debug(6, "to [%s,%s,%d][%s %s,%s,%s]:%u",
			  targetname, address, port, iface->name,
			  iface->transport_name, iface->hwaddress,
			  iface->ipaddress, sid);

	if (rec->session.sid && sid && rec->session.sid != sid)
		return 0;

	if (strlen(rec->name) && strcmp(rec->name, targetname))
		return 0;

	if (!iscsi_addr_match(rec->conn[0].address, address))
		return 0;

	if (rec->conn[0].port != -1 && port != rec->conn[0].port)
		return 0;

	if (!iface_match(&rec->iface, iface))
		return 0;

	return 1;
}

int iscsi_match_session(void *data, struct session_info *info)
{
	return __iscsi_match_session(data, info->targetname,
				     info->persistent_address,
				     info->persistent_port, &info->iface,
				     info->sid);
}

int iscsi_match_session_count(void *data, struct session_info *info)
{
	/*
	 * iscsi_sysfs_for_each_session expects:
	 *   0==match -1==nomatch >0==error
	 * but iscsi_match_session returns:
	 *   1==match 0==nomatch
	 */
	if (iscsi_match_session(data, info))
		return 0;
	return -1;
}

int iscsi_match_target(void *data, struct session_info *info)
{
	return __iscsi_match_session(data, info->targetname,
				     info->persistent_address,
				     info->persistent_port, NULL,
				     MATCH_ANY_SID);
}

/*
 * set thread's PR_SET_IO_FLUSHER flag
 *
 * val: 1 to set to set thread's PR_SET_IO_FLUSHER flag
 *      0 to clear thread's PR_SET_IO_FLUSHER flag
 *
 * return: return 0 on success, else error number is returned
 */
int set_thread_io_flusher(int val)
{
	if (prctl(PR_SET_IO_FLUSHER, val, 0, 0, 0) == 0)
		return 0;

	/*
	 * prctl would return EINVAL if the kernel do not support PR_SET_IO_FLUSHER
	 * so donot print error log if errorno is EINVAL to avoid unnecessary errorlog
	 */
	if (errno != EINVAL)
		log_error("prctl could not %s thread's PR_SET_IO_FLUSHER flag due to error %s\n", val ? "set" : "clear", strerror(errno));
	return errno;
}
