/*
 *  Copyright (C) 2000-2006 SWsoft. All rights reserved.
 *
 *  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.
 *
 *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdio.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <linux/vzcalluser.h>
#include <linux/vzctl_venet.h>

#include "types.h"
#include "net.h"
#include "vzerror.h"
#include "logger.h"
#include "list.h"
#include "dist.h"
#include "exec.h"
#include "env.h"
#include "script.h"
#include "util.h"
#include "vps_configure.h"


int find_ip(list_head_t *ip_h, char *ipaddr)
{
	ip_param *ip;

	if (list_empty(ip_h))
		return 0;
	list_for_each(ip, ip_h, list) {
		if (!strcmp(ip->val, ipaddr))
			return 1;
	}
	return 0;
}

static inline int _ip_ctl(vps_handler *h, envid_t veid, int op,
	unsigned int ip)
{
	struct sockaddr_in addr;
	struct vzctl_ve_ip_map ip_map;

	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = ip;
	addr.sin_port = 0;

	ip_map.veid = veid;
	ip_map.op = op;
	ip_map.addr = (struct sockaddr*) &addr;
	ip_map.addrlen = sizeof(addr);

	return ioctl(h->vzfd, VENETCTL_VE_IP_MAP, &ip_map);
}

int ip_ctl(vps_handler *h, envid_t veid, int op, char *ip)
{
	int ret;
	unsigned int ipaddr;

	if ((ret = get_ipaddr(ip, &ipaddr)))
		return 0;
	ret = _ip_ctl(h, veid, op, ipaddr);
	if (ret) {
		switch (errno) {
			case EADDRINUSE	:
				ret = VZ_IP_INUSE;
				break;
			case ESRCH	:
	                        ret = VZ_VE_NOT_RUNNING;
				break;
			case EADDRNOTAVAIL:
				if (op == VE_IP_DEL)
					return 0;
				ret = VZ_IP_NA;
				break;
			default:
				ret = VZ_CANT_ADDIP;
				break;
		}
		logger(0, errno, "Unable to %s IP %s",
			op == VE_IP_ADD ? "add" : "del", ip);
	}
	return ret;
}

int run_net_script(envid_t veid, int op, list_head_t *ip_h, int state,
	int skip_arpdetect)
{
	char *argv[3];
	char *envp[10];
	const char *script;
	int ret;
	char buf[STR_SIZE];
	int i = 0;
	char *skip_str = "SKIP_ARPDETECT=yes";

	if (list_empty(ip_h))
		return 0;
	snprintf(buf, sizeof(buf), "VEID=%d", veid);
	envp[i++] = strdup(buf);
	snprintf(buf, sizeof(buf), "VE_STATE=%s", state2str(state));
	envp[i++] = strdup(buf);
	envp[i++] = list2str("IP_ADDR", ip_h);
	envp[i++] = strdup(ENV_PATH);
	if (skip_arpdetect)
		envp[i++] = strdup(skip_str);
	envp[i] = NULL;
	switch (op) {
		case ADD:
			script = VPS_NET_ADD;
			break;
		case DEL:
			script = VPS_NET_DEL;
			break;
		default:
			return 0;
	}
	argv[0] = (char *)script;
	argv[1] = NULL;
	ret = run_script(script, argv, envp, 0);
	free_arg(envp);

	return ret;
}

static inline int invert_ip_op(int op)
{
	if (op == VE_IP_ADD)
		return VE_IP_DEL;
	else
		return VE_IP_ADD;
}

static int vps_ip_ctl(vps_handler *h, envid_t veid, int op,
		list_head_t *ip_h, int rollback)
{
	int ret = 0;
	ip_param *ip;
	int inv_op;

	list_for_each(ip, ip_h, list) {
		if ((ret = ip_ctl(h, veid, op, ip->val))) 
			break;
	}
	if (ret && rollback) {
		/* restore original ip state op of error */
		inv_op = invert_ip_op(op);
		list_for_each_prev(ip, ip_h, list) {
			ip_ctl(h, veid, inv_op, ip->val);
		}
	}

	return ret;
}

static inline int vps_add_ip(vps_handler *h, envid_t veid,
	net_param *net, int state)
{
	char *str;
	int ret;
	list_head_t *ip_h = &net->ip;

	if ((str = list2str(NULL, ip_h)) != NULL) {
		logger(0, 0, "Adding IP address(es): %s", str);
		free(str);
	}
	if ((ret = vps_ip_ctl(h, veid, VE_IP_ADD, ip_h, 1)))
		return ret;
	if ((ret = run_net_script(veid, ADD, ip_h, state, net->skip_arpdetect)))
		vps_ip_ctl(h, veid, VE_IP_DEL, ip_h, 0);
	
	return ret;
}

static inline int vps_del_ip(vps_handler *h, envid_t veid,
	net_param *net, int state)
{
	char *str;
	int ret;
	list_head_t *ip_h = &net->ip;

	if ((str = list2str(NULL, ip_h)) != NULL) {
		logger(0, 0, "Deleting IP address(es): %s", str);
		free(str);
	}
	if ((ret = vps_ip_ctl(h, veid, VE_IP_DEL, ip_h, 1)))
		return ret;
	run_net_script(veid, DEL, ip_h, state, net->skip_arpdetect);

	return ret;
}

static inline int vps_set_ip(vps_handler *h, envid_t veid,
	net_param *net, int state)
{
	int ret;
	net_param oldnet;

	bzero(&oldnet, sizeof(oldnet));
	list_head_init(&oldnet.ip);
	if (get_vps_ip(h, veid, &oldnet.ip) < 0)
		return VZ_GET_IP_ERROR;
	if (!(ret = vps_del_ip(h, veid, &oldnet, state))) {
		if ((ret = vps_add_ip(h, veid, net, state)))
			vps_add_ip(h, veid, &oldnet, state);
	}
	free_str_param(&oldnet.ip);

	return ret;
}

static int netdev_ctl(vps_handler *h, int veid, int op, char *name)
{
	struct vzctl_ve_netdev ve_netdev;

	ve_netdev.veid = veid;
	ve_netdev.op = op;
	ve_netdev.dev_name = name;
	if (ioctl(h->vzfd, VZCTL_VE_NETDEV, &ve_netdev) < 0) 
		return VZ_NETDEV_ERROR;
	return 0;
}

int vps_netdev_ctl(vps_handler *h, envid_t veid, int op, net_param *net)
{
	int ret = 0;
	list_head_t *dev_h = &net->dev;
	net_dev_param *dev;
	int cmd;

	if (list_empty(dev_h))
		return 0;
	if (!vps_is_run(h, veid)){
		logger(0, 0, "Unable to setup network devices:"
			" VPS is not running");
		return VZ_VE_NOT_RUNNING;
	}
	cmd = (op == ADD) ? VE_NETDEV_ADD : VE_NETDEV_DEL;
	list_for_each(dev, dev_h, list) {
		if ((ret = netdev_ctl(h, veid, cmd, dev->val))) {
			logger(0, errno, "Unable to %s netdev %s",
				(op == ADD) ? "add": "del", dev->val);
			break;
		}
	}
	return ret;
}

int vps_net_ctl(vps_handler *h, envid_t veid, int op, net_param *net,
	dist_actions *actions, char *root, int state, int skip)
{
	list_head_t *ip_h = &net->ip;
	int ret = 0;

	if (list_empty(ip_h) &&	!net->delall) {
		/* make initial network setup on VPS start*/
		if (state == STATE_STARTING && op == ADD)
			goto configure;
		return 0;
	}
	if (!vps_is_run(h, veid)) {
		logger(0, 0, "Unable to apply network parameters: VPS is not"
			" running");
		return VZ_VE_NOT_RUNNING;
	}
	if (op == ADD) {
		if (net->delall == YES)
			ret = vps_set_ip(h, veid, net, state);
		else
			ret = vps_add_ip(h, veid, net, state);
	} else if (op == DEL) {
		ret = vps_del_ip(h, veid, net, state);
	}
configure:
	if (!ret && !(skip & SKIP_CONFIGURE))
		vps_ip_configure(h, veid, actions, root, op, net, state);
	return ret;
}

#define	PROC_VEINFO	"/proc/vz/veinfo"
static inline int get_vps_ip_proc(envid_t veid, list_head_t *ip_h)
{
	FILE *fd;
	char str[16384];
	char *token;
	int id, cnt = 0;

	if ((fd = fopen(PROC_VEINFO, "r")) == NULL) {
		logger(0, errno, "Unable to open %s", PROC_VEINFO);
		return -1;
	}
	while (!feof(fd)) {
		if (fgets(str, sizeof(str), fd) == NULL)
			break;
		token = strtok(str, " ");
		if (token == NULL)
			continue;
		if (parse_int(token, &id))
			continue;	
		if (veid != id)
			continue;
		if ((token = strtok(NULL, " ")) != NULL)
			token = strtok(NULL, " ");
		if (token == NULL)
			break;
		while ((token = strtok(NULL, " \t\n")) != NULL) {
			if (add_str_param(ip_h, token)) {
				free_str_param(ip_h);
				cnt = -1;
				break;
			}
			cnt++;
		}
		break;
	}
	fclose(fd);
	return cnt;
}

#if HAVE_VZLIST_IOCTL
#ifndef NIPQUAD
#define NIPQUAD(addr) \
	((unsigned char *)&addr)[0], \
	((unsigned char *)&addr)[1], \
	((unsigned char *)&addr)[2], \
	((unsigned char *)&addr)[3]
#endif

static inline int get_vps_ip_ioctl(vps_handler *h, envid_t veid,
	list_head_t *ip_h)
{
	int ret = -1;
	struct vzlist_veipv4ctl veip;
	uint32_t *addr, *tmp;
	char buf[16];
	int i;

	veip.veid = veid;
	veip.num = 256;
	addr = malloc(veip.num * sizeof(*veip.ip));
	if (addr == NULL)
		return -1;
	for (;;) {
		veip.ip = addr;
		ret = ioctl(h->vzfd, VZCTL_GET_VEIPS, &veip);
		if (ret < 0) 
			goto out;
		else if (ret <= veip.num)
			break;
		veip.num = ret;
		tmp = realloc(addr, veip.num * sizeof(*veip.ip));
		if (tmp == NULL) {
			ret = -1;
			goto out;
		}
		addr = tmp;
	}
	if (ret > 0) {
		for (i = ret - 1; i >= 0; i--) {
			snprintf(buf, sizeof(buf), "%d.%d.%d.%d",
				NIPQUAD(addr[i]));
			if ((ret = add_str_param(ip_h, buf)))
				break;
		}
	}
out:
	free(addr);
	return ret;
}

int get_vps_ip(vps_handler *h, envid_t veid, list_head_t *ip_h)
{
	int ret;
	ret = get_vps_ip_ioctl(h, veid, ip_h);
	if (ret < 0) 
		ret = get_vps_ip_proc(veid, ip_h);
	if (ret < 0)
		free_str_param(ip_h);
	return ret;
}
#else

int get_vps_ip(vps_handler *h, envid_t veid, list_head_t *ip_h)
{
	int ret;

	if ((ret = get_vps_ip_proc(veid, ip_h)) < 0)
		free_str_param(ip_h);
	return ret;
}
#endif

