/*
 *  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 <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/vfs.h>
#include <unistd.h>

#define SYSRSRV		52428800
#define MEMPERVE	5542912
#define LOWPERVE	1348608
#define MAXVAL		LONG_MAX
#define DEF_CPUUNITS	1000
#define CHECK_LIMIT(val)	(val > MAXVAL) ? MAXVAL: val

#define PROCMEM		"/proc/meminfo"
#define PROCTHREADS	"/proc/sys/kernel/threads-max"
#define OUTPATH		"/etc/sysconfig/vz-scripts/"
#define PROCCPU		"/proc/cpuinfo"

#define MAX_SL		3

/* maximal per system values */
#define MAX_TOTAL_PIDS	8000
#define MAXIPTENT	2000 

/* minimal per VE values */
#define MINPROC		30
#define MINTCPBUF	65536
#define MINDGRAMBUF	32768
#define MINSOCKBUF	2560
#define MIN_PTY		4
#define MIN_IPTENT	4
#define MIN_PGLOCK	4
#define MIN_GUARPG	1024

/* maximal per VE values */
#define PRIVVM_PVE	0.6 / 4096
#define IPTENT_PVE	200
#define FLOCK_PVE	1000
#define PTY_PVE		512

/* default discspace values */
#define HOST_DS		10737418240ULL /* 10 GB */
#define DEF_DS		225280
#define HOST_DI		100000
#define DEF_DI		88000

/* helper values */
#define SHMPG_PRIVVM	0.10
#define NFLOCK_NFILE	0.10
#define NFILE_AVNPROC	32
#define PTY_PROC	0.1
#define PGLOCK_KMEM	204800 /* 2% KMEM */
#define K_KMEM_MAX	2.0

/* limit/barrier delta values */
#define KMEM_DELTA	1.1
#define NFLOCK_DELTA	1.1
#define PRIVVM_DELTA	1.1
#define DCACHE_DELTA	1.03
#define DS_DELTA	1.1
#define DI_DELTA	1.1

#define NUMUBC		23

enum {	NPROC = 0,
	AVNUMPROC,
	NTCPSOCK,
	NOTHSOCK,
	VMGUAR,
	KMEM,
	TCPSND,
	TCPRCV,
	SOCKOTH,
	DGRAM,
	OOMGUAR,
	PRIVVMPG,
	LOCKPG,
	SHMPG,
	NPHYPG,
	NFILE,
	NFLOCK,
	NPTY,
	NSIGINFO,
	DCACHE,
	IPTENT,
	DISKSPACE,
	DISKINODES
};

char *ubcnames[] = {
	"NUMPROC",
	"AVNUMPROC",
	"NUMTCPSOCK",
	"NUMOTHERSOCK",
	"VMGUARPAGES",
	"KMEMSIZE",
	"TCPSNDBUF",
	"TCPRCVBUF",
	"OTHERSOCKBUF",
	"DGRAMRCVBUF",
	"OOMGUARPAGES",
	"PRIVVMPAGES",
	"LOCKEDPAGES",
	"SHMPAGES",
	"PHYSPAGES",
	"NUMFILE",
	"NUMFLOCK",
	"NUMPTY",
	"NUMSIGINFO",
	"DCACHESIZE",
	"NUMIPTENT",
	"DISKSPACE",
	"DISKINODES",
	NULL};

struct par_limits {
	unsigned long long bar;
	unsigned long long lim;
};

struct par_limits params[NUMUBC];

/* Global variables */
unsigned long long mem_total, low_total, swap_total, ds_total, di_total; 
long pagesize, proc_calc;
int num_ve, osl;

float	k_kmem[MAX_SL]		= {1, 1.2, 1.8};
float	k_nproc[MAX_SL]		= {1, 1.5, 2};
float	k_avnpr[MAX_SL]		= {8, 5, 3};
int	k_kpp[MAX_SL]		= {81920, 65536, 53248};
int	k_sock1[MAX_SL]		= {262144, 131072, 65536};
int	k_sock2[MAX_SL]		= {4096, 3036, 2560};
int	numsiginfo[MAX_SL]	= {1024, 512, 256};
float	k_dcache[MAX_SL]	= {1.5 * 384, 1.2 * 384, 1 * 384};
float	k_privvm[MAX_SL]	= {6, 3, 1.5};
int	k_pglock[MAX_SL]	= {10, 3, 1};
int	k_msl[MAX_SL]		= {10485760, 2097152, 0};

char *level_string[MAX_SL+1] = {
"Free resource distribution. Any parameters may be increased\0",
"Normal resource distribution. Secondary parameters may be increased\0",
"Partial resource shortage. Auxiliary parameters may be increased\0",
"Overall resource shortage. Please, do not change any parameters!\0"
};

void usage()
{
	fprintf(stderr, "Usage: vzsplit [-f config_name] "
			"| [-n numves] | [-s swap_size\n"
		"\t -f specified config name\n"
		"\t -n specified number of VPS\n"
		"\t -s specified swap in Kbytes\n");
}

void header(FILE *fp)
{
	fprintf(fp,"# Configuration file generated by vzsplit for %d VPS\n"
		"# on HN with total amount of physical mem %llu Mb\n"
		"# low memory %llu Mb, swap size %llu Mb, Max treads %lu\n"
		"# Resourse commit level %d:\n# %s\n",
		 num_ve, (mem_total >> 20), (low_total >> 20), (swap_total >> 20),
		 proc_calc, osl, level_string[osl]);
	return;
}

int get_cpupower(int *cpuunits)
{
	FILE *fd;
	char str[1024];
	int val, total = 0;
	
	*cpuunits = DEF_CPUUNITS;
	if ((fd = fopen(PROCCPU, "r")) == NULL) {
		fprintf(stderr, "Cannot open " PROCCPU "\n");
		return 1;
	}
	while (fgets(str, sizeof(str), fd))
		if (sscanf(str, "bogomips\t: %u", &val) == 1)
			total += val;
	if (total) {
		total *= 25;
		*cpuunits = total / (num_ve + 1);
		return 0;
	}
	return 1;
}

int lconv(char *name)
{
	int i, cpuunits;
	FILE *fp;
	
	if (name != NULL) {
		if ((fp = fopen(name, "w")) == NULL) {
			fprintf(stderr, "Unable to create %s: %s\n", name,
					strerror(errno));
			return 1;
		}
	} else {
		fp = stdout;
	}
	header(fp);

	fprintf(fp, "# Primary parameters\n");
	for (i = 0;  i < NUMUBC; i++) {
		if (i == KMEM)
			fprintf(fp, "\n# Secondary parameters\n");
		else if (i ==LOCKPG)
			fprintf(fp, "\n# Auxiliary parameters\n");
		fprintf(fp, "%s=\"%lu:%lu\"\n", ubcnames[i],
				(unsigned long) params[i].bar,
				(unsigned long)	params[i].lim);
	}
	get_cpupower(&cpuunits);
	fprintf(fp, "CPUUNITS=\"%d\"\n", cpuunits);
	if (name) {
		fclose(fp);
		fprintf(stderr, "Config %s was created\n", name);
	}
	return 0;
}

int calculate_values()
{
	long long rest, delta, low_pve, tot_pve;
	long long kmem, numproc, avnproc, sockbuf, tcpbuf, iptent, numfile;
	long long dcache, numflock;
	long long lockedpages, numpty, guarpg, privvm, shmpg, di_pve, ds_pve;
	int sl = 0;

	avnproc = tcpbuf = 0;
	tot_pve = (mem_total + swap_total - SYSRSRV) / num_ve;
	for (osl = 0; osl < MAX_SL; osl++) {
		numproc = k_nproc[osl] * proc_calc / num_ve;
		low_pve = low_total * 0.4 * k_kmem[osl] / num_ve;
		guarpg = (tot_pve - low_pve) / pagesize;
		if  (guarpg < MIN_GUARPG) {
			guarpg = MIN_GUARPG;
			low_pve = tot_pve - guarpg * pagesize;
		}
		kmem = low_pve / 2;
		if (low_pve < k_msl[osl])
			continue;
		if (numproc < MINPROC)
			numproc = MINPROC;
		avnproc = kmem / k_kpp[osl];
		if (avnproc < MINPROC / 2)
			continue;
		
		if (numproc <  avnproc)
			numproc = avnproc;
		if ((numproc < 2 * avnproc) && (osl < MAX_SL))
			numproc = 2 * avnproc;
		if (numproc > avnproc * k_avnpr[osl])
			numproc = avnproc * k_avnpr[osl];

		sockbuf = low_pve - kmem;
		tcpbuf = sockbuf / 3;
		if (tcpbuf < k_sock1[osl] + k_sock2[osl] * numproc)
			continue;

		sl = osl;
		break;
	}

	if (osl == MAX_SL) {
		sl = osl - 1;
                numproc = k_nproc[sl] * proc_calc / num_ve;
                low_pve = low_total * 0.4 * K_KMEM_MAX / num_ve;
		guarpg = (tot_pve - low_pve) / pagesize;
		if  (guarpg < MIN_GUARPG) {
			guarpg = MIN_GUARPG;
			low_pve = tot_pve - guarpg * pagesize;
		}
		do {
			numproc /= 2;
                	if (numproc < MINPROC)
                	        numproc = MINPROC;
			avnproc = numproc / 2;
			delta = MINSOCKBUF * numproc;
			sockbuf = 2 * (MINDGRAMBUF + MINTCPBUF + delta);
			kmem = low_pve - sockbuf;
			rest = kmem - avnproc * k_kpp[sl];
		} while  ((rest < 0) && (numproc > MINPROC));

		if (rest < 0) {
			fprintf(stderr, "Fatal resource shortage, "
					"try to decrease the number of VPS\n");
			return 1;
		}
		params[DGRAM].lim = params[DGRAM].bar = MINDGRAMBUF;
		params[SOCKOTH].bar = MINDGRAMBUF;
		params[SOCKOTH].lim = MINDGRAMBUF + delta;
		params[TCPSND].bar = MINTCPBUF;
		params[TCPSND].lim = MINTCPBUF + delta;
		params[TCPRCV].bar = MINTCPBUF;
		if (rest > delta) {
			params[TCPRCV].lim = MINTCPBUF + delta;
			kmem += rest - delta;
		} else 
			params[TCPRCV].lim = MINTCPBUF + rest;
	} else {
		delta = k_sock2[sl] * numproc;
		rest = tcpbuf - delta;
		params[TCPSND].lim = params[TCPRCV].lim = CHECK_LIMIT(tcpbuf);
		params[TCPSND].bar = params[TCPRCV].bar = CHECK_LIMIT(rest);
		params[DGRAM].lim = params[DGRAM].bar = 
			params[SOCKOTH].bar = CHECK_LIMIT(rest / 2);
		params[SOCKOTH].lim = CHECK_LIMIT(rest / 2 + delta);
	}

	params[KMEM].lim = CHECK_LIMIT(kmem * KMEM_DELTA);
	params[KMEM].bar = CHECK_LIMIT(kmem);
	params[NPROC].lim = params[NPROC].bar = CHECK_LIMIT(numproc);
	params[AVNUMPROC].lim = params[AVNUMPROC].bar = CHECK_LIMIT(avnproc);
	params[NTCPSOCK].lim = params[NTCPSOCK].bar = CHECK_LIMIT(numproc);
	params[NOTHSOCK].lim = params[NOTHSOCK].bar = CHECK_LIMIT(numproc);

	iptent = MAXIPTENT / num_ve;
	if (iptent > IPTENT_PVE)
		iptent = IPTENT_PVE;
	else if (iptent < MIN_IPTENT)
		iptent = MIN_IPTENT;

	params[IPTENT].lim = params[IPTENT].bar = iptent;

	numfile = avnproc * NFILE_AVNPROC;
	params[NFILE].lim = params[NFILE].bar = CHECK_LIMIT(numfile);

	dcache = numfile * k_dcache[sl];
	params[DCACHE].lim = CHECK_LIMIT(dcache); 
	params[DCACHE].bar = CHECK_LIMIT(dcache / DCACHE_DELTA);

	numflock = numfile * NFLOCK_NFILE;
	if (numflock > FLOCK_PVE)
		numflock = FLOCK_PVE;
	params[NFLOCK].lim = CHECK_LIMIT(numflock * NFLOCK_DELTA);
	params[NFLOCK].bar = numflock;

	lockedpages = kmem * k_pglock[sl] / PGLOCK_KMEM;
	if (lockedpages < MIN_PGLOCK)
		lockedpages = MIN_PGLOCK;
	params[LOCKPG].lim = params[LOCKPG].bar = CHECK_LIMIT(lockedpages);
	
	numpty = numproc * PTY_PROC;
	if (numpty < MIN_PTY)
		numpty = MIN_PTY;
	if (numpty > PTY_PVE)
		numpty = PTY_PVE;
	
	params[NPTY].lim = params[NPTY].bar = numpty;
	
	params[NSIGINFO].lim = params[NSIGINFO].bar = numsiginfo[sl];
	params[NPHYPG].bar = 0; params[NPHYPG].lim = MAXVAL;

	privvm = guarpg * k_privvm[sl];
	if (privvm > PRIVVM_PVE * mem_total) {
		privvm = PRIVVM_PVE * mem_total;
		guarpg = privvm;
	}
	params[PRIVVMPG].bar = CHECK_LIMIT(privvm);
	params[PRIVVMPG].lim = CHECK_LIMIT(privvm * PRIVVM_DELTA);
	params[VMGUAR].bar = params[OOMGUAR].bar = CHECK_LIMIT(guarpg);
	params[VMGUAR].lim = params[OOMGUAR].lim = MAXVAL;

	shmpg = privvm * SHMPG_PRIVVM;
	params[SHMPG].bar = params[SHMPG].lim = CHECK_LIMIT(shmpg);

	if (ds_total == 0) {
		ds_pve = DEF_DS;
		di_pve = DEF_DI;
	} else {
		ds_pve = ds_total / (2 * num_ve);
		di_pve = di_total / (2 * num_ve);
	}
	params[DISKSPACE].bar = ds_pve / DS_DELTA;
	params[DISKSPACE].lim = ds_pve;
	params[DISKINODES].bar = di_pve / DI_DELTA;
	params[DISKINODES].lim = di_pve;

	return 0;
}

int main(int argc, char **argv)
{
	int len, opt, swp;
	char *tail;
	char *name = NULL;
	struct stat st;
	FILE *fd;
	char str[1024];
	unsigned long long val;
	struct statfs statfs_buf;
	int retval, ve_allowed;

	num_ve = -1;
	swp = 0;
	mem_total = 0;
	low_total = 0;
	swap_total = 0;

	while ((opt = getopt(argc, argv, "f:n:s:h")) > 0) {
		switch(opt) {
		case 'f':
			len = strlen(optarg) + strlen(OUTPATH) +
			       strlen("ve-.conf-sample");
			name = (char*)malloc(len + 1);
			sprintf(name, OUTPATH "ve-%s.conf-sample", optarg);
			if (!stat(name, &st)) {
				fprintf(stderr,"File %s already exist\n",
					name);
				exit(1);
			}
			break;
		case 'n':
			num_ve = strtol(optarg, &tail, 10);
			if (*tail != '\0') {
				fprintf(stderr, "Invalid argument for -n: %s\n",
					optarg);
				exit(1);
			}
			break;
		case 's':
			if (optarg[0] == '-') {
				fprintf(stderr, "Negative value for -s\n");
				exit(-1);
			}
			swp = 1;
			swap_total = strtoll(optarg, &tail, 10);
			swap_total <<= 10;
			if (*tail != '\0') {
				fprintf(stderr, "Invalid argument for -s: %s\n",
						optarg);
				exit(1);
			}
			break;
		case 'h':
		default	:
			usage();
			exit(0);
		}
	}
	if (optind < argc) {
		usage();
		exit(1);
	}
	if (num_ve == -1) {
		printf("Enter the number of VPS: ");
		if (scanf("%d", &num_ve) != 1) {
			fprintf(stderr, "Invalid value.\n");
			exit(1);
		}
	}
	if (num_ve < 1) {
		fprintf(stderr, "Incorrect value for number of VPS.\n");
		exit(1);
	}
	
	if ((fd = fopen(PROCMEM, "r")) == NULL) {
		fprintf(stderr, "Cannot open " PROCMEM"\n");
		exit(1);
	}

	while (fgets(str, sizeof(str), fd))
		if (sscanf(str, "MemTotal:\t %llu", &val) == 1)
			mem_total = val << 10;
		else if (sscanf(str, "LowTotal:\t %llu", &val) == 1)
			low_total = val << 10;
		else if (!swp && sscanf(str, "SwapTotal:\t %llu", &val) == 1)
			swap_total = val << 10;

	fclose(fd);
	if (mem_total < SYSRSRV) {
		fprintf(stderr, "At least 128 Mb of RAM should be "
				"installed on Hardware Node\n");
		exit(1);
	}

	if (swap_total > 2 * mem_total)
		fprintf(stderr, "The optimal swap space size is %llu Mb, "
				"twice bigger than the RAM size\n\n", 
				(2 * mem_total) >> 20);
	ve_allowed = num_ve;
	retval = 0;
	
	if (((mem_total + swap_total - SYSRSRV) / num_ve) < MEMPERVE) {
		fprintf(stderr, "On node with %llu Mb of memory (RAM + swap) "
				"cannot be allocated %d VPS\n", 
				(mem_total + swap_total) >> 20, num_ve);
		ve_allowed = (mem_total + swap_total - SYSRSRV) / MEMPERVE;
                retval = 1;
	}

	if (((low_total - SYSRSRV)/ ve_allowed) < LOWPERVE) {
		int ve_low;
		
		fprintf(stderr, "On node with %llu Mb of Low Memory "
				"cannot be allocated %d VPS\n", 
				low_total >> 20, num_ve);
		ve_low = (low_total - SYSRSRV) / LOWPERVE; 
		if (ve_low < ve_allowed)
			ve_allowed = ve_low;
		retval = 1;
	}

	if (retval != 0) {
		fprintf(stderr, "The maximum allowed value is %d\n", ve_allowed);
		exit(retval);
	}

	if ((fd = fopen(PROCTHREADS, "r")) == NULL) {
		fprintf(stderr, "Cannot open " PROCTHREADS "\n");
		exit(1);
	}
	if (fgets(str, sizeof(str), fd))
		if (sscanf(str, "%llu", &val) == 1)
			proc_calc = val;
	fclose(fd);

	if (statfs ("/vz", &statfs_buf) < 0) {
		fprintf(stderr, "WARNING: statfs /vz return error\n");
		fprintf(stderr, "Therefore default disk values will be used\n\n");
		ds_total = 0; di_total = 0;
	} else {
		long ve_ds, ve_di; 
		long rec;

		ds_total = statfs_buf.f_blocks; 
		di_total = statfs_buf.f_files; 

		rec = 0;
		if (ds_total / 2 < HOST_DS / statfs_buf.f_bsize) {
			rec = 1;
			ds_total /= 2;
		} else
			ds_total -= HOST_DS / statfs_buf.f_bsize;
		if (di_total / 2 < HOST_DI) {
			rec = 1;
			di_total /= 2;
		} else
			di_total -= HOST_DI;
		if (rec)
			fprintf(stderr, "WARNING: Recommended minimal size"
					"of /vz partition is 20Gb!\n");

		ve_ds = ds_total / (DEF_DS);
		ve_di = di_total / (DEF_DI);
		
		if (ve_ds < num_ve) {
			retval = 1;
			ve_allowed = ve_ds;
		}
		if (ve_di < num_ve) {
			retval = 1;
			if (ve_di < ve_ds)
				ve_allowed = ve_di;
		}
		if (retval == 1) {
			fprintf(stderr, "WARNING: /vz partition do not have space "
				        "required for %d VPS\n"
				       	"The maximum allowed value is %d\n",
				       	num_ve, ve_allowed);
			fprintf(stderr, "Default disc space values will be used\n\n");
			ds_total = 0; di_total = 0;
		/*	exit(retval);*/
		}
	}
	if ((pagesize = sysconf(_SC_PAGE_SIZE)) == -1)
		pagesize = 4096;
	
	if (low_total > 2 << 30) {
		if (proc_calc > 2 * MAX_TOTAL_PIDS)
			proc_calc = 2 * MAX_TOTAL_PIDS;
	} else if (proc_calc > MAX_TOTAL_PIDS)
			proc_calc = MAX_TOTAL_PIDS; 

	if (calculate_values())
		exit(1);

	retval = lconv(name);
	exit(retval);
}

