/* ratio.c Basic ratio implementation for muddleftpd
   Copyright (C) 1999 Beau Kuiper

   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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include "ftpd.h"
#include "reply.h"

extern int doesdint;

int lockarea(int fd, int pos, int len, int locktype, int do_wait)
{
	struct flock lock;
	register int err;
	int lockt;
	
	lockt = (do_wait == TRUE ? F_SETLKW : F_SETLK);
	lock.l_type = locktype;
	lock.l_whence = SEEK_SET;
	lock.l_start = pos;
	lock.l_len = len;
	NOSIGNALINTR(err = fcntl(fd, lockt, &lock));
	
	if (err == -1)
	{
		if (errno != EAGAIN)
			ERRORMSGFATAL(safe_snprintf("Locking failed: %s", strerror(errno)));
		else
			return(FALSE);
	}
	return(TRUE);
}

void lock_n_read(RATIOHANDLE *rh)
{
	if (rh->ratiofilefd != 0)
	{
		lockarea(rh->ratiofilefd, rh->filepos, sizeof(RATIOFILEDATA), F_WRLCK, TRUE);
		lseek(rh->ratiofilefd, rh->filepos, SEEK_SET);
		read(rh->ratiofilefd, rh->rdat, sizeof(RATIOFILEDATA));
	}
}

void write_n_unlock(RATIOHANDLE *rh)
{
	if (rh->ratiofilefd != 0)
	{
		lseek(rh->ratiofilefd, rh->filepos, SEEK_SET);
		write(rh->ratiofilefd, rh->rdat, sizeof(RATIOFILEDATA));
		lockarea(rh->ratiofilefd, rh->filepos, sizeof(RATIOFILEDATA), F_UNLCK, TRUE);
	}
}

int openratiofile(char *filename)
{
	int fd;
	if (filename == NULL)
		return(-1);
		
	fd = open(filename, O_CREAT | O_RDWR, 0600);
	if (fd == -1)
	{
		char *err = safe_snprintf("Error opening ratio file '%s': %s", filename, strerror(errno));
		log_addentry(MYLOG_INFO, NULL, err);
		freewrapper(err);
	}	
	return(fd);
}

int decoderatiostr(char *ratstr, int *pa, int *pb)
{
	if (ratstr == NULL)
		return(FALSE);
	strtrimspace(ratstr);
	return(sscanf(ratstr, "%d:%d", pa, pb) == 2);
}

/* this function really is too complex and too long, fix later */

RATIOHANDLE *ratio_loaduser(char *username, int section)
{
	RATIOHANDLE *newrh = mallocwrapper(sizeof(RATIOHANDLE));
	RATIOFILEDATA *filedata = mallocwrapper(sizeof(RATIOFILEDATA));
	char *data, *defint, *byteratio, *fileratio;
	int pos = 0;
	long long defaultdownload;
	int upbytes, downbytes, upfiles, downfiles, initfiles;
	int flags = 0;

	if (!doesdint)
		goto error;
	/* load the config */
	loadstrfromconfig(config->configfile, section, "ratiofile", &data,
			  NULL);
	loadstrfromconfig(config->configfile, section, "byteratios", 
		 	  &byteratio, NULL);
	loadstrfromconfig(config->configfile, section, "fileratios",
			  &fileratio, NULL);

	if (fileratio)
	{
		if (!decoderatiostr(fileratio, &upfiles, &downfiles))
			goto error;
		flags |= FILECREDITS;
		loadintfromconfig(config->configfile, section, "initalfiles", 
				  &initfiles, 0);
		initfiles *= downfiles;
	}

	if (byteratio)
	{
		if (!decoderatiostr(byteratio, &upbytes, &downbytes))
			goto error;
		flags |= BYTECREDITS;
		loadstrfromconfig(config->configfile, section, 
				  "initalbytes", &defint, "0");
		sscanf(defint, "%lld", &defaultdownload);
		defaultdownload *= downbytes;
	}
	newrh->rdat = filedata;
	newrh->ratiofilefd = 0;
	
	/* this is for non-persistant ratios */
	if (!data)
		goto newrecord;

	newrh->ratiofilefd = openratiofile(data);
	if (newrh->ratiofilefd == -1)
		goto error;

	lockarea(newrh->ratiofilefd, pos, sizeof(RATIOFILEDATA), F_WRLCK, TRUE); 
	
	while(read(newrh->ratiofilefd, filedata, sizeof(RATIOFILEDATA)) ==
	      sizeof(RATIOFILEDATA))
	{
		lockarea(newrh->ratiofilefd, pos, sizeof(RATIOFILEDATA), F_UNLCK, TRUE);
		
		if (strcmp(filedata->username, username) == 0)
		{
			newrh->filepos = pos;
			return(newrh);
		}
		pos += sizeof(RATIOFILEDATA);
		lockarea(newrh->ratiofilefd, pos, sizeof(RATIOFILEDATA), F_WRLCK, TRUE); 
	}
newrecord:
	/* we need to create a record, the area we want is already locked */
	
	/* if the area gets written to disk, make sure it is clear so we don't
	   accidently write a clear password to disk that may have been in
	   the memory! */
	memset(filedata, 0, sizeof(RATIOFILEDATA));

	strncpy(filedata->username, username, MAXNAMELEN);
	filedata->username[MAXNAMELEN] = 0;

	filedata->flags = flags;
	if (flags & FILECREDITS)
	{
		filedata->filecredits = initfiles;
		filedata->fileinmult = (short int)upfiles;
		filedata->fileoutmult = (short int)downfiles;
	}
	if (flags & BYTECREDITS)
	{
		filedata->downloadcredits = defaultdownload;
		filedata->byteinmult = (short int)upbytes;
		filedata->byteoutmult = (short int)downbytes;
	}
	
	newrh->filepos = pos;
	write_n_unlock(newrh);

	return(newrh);
error:
	freewrapper(newrh);
	freewrapper(filedata);
	return(NULL);
}

/* This looks expensive!, but isn't really too bad because of kernel
   writeback cache. */

void ratio_uploadbytes(RATIOHANDLE *rh, int bytes)
{
	lock_n_read(rh);
	if (rh->rdat->flags & BYTECREDITS)
		rh->rdat->downloadcredits += bytes * rh->rdat->byteinmult;
	write_n_unlock(rh);
}

int ratio_downloadbytes(RATIOHANDLE *rh, int bytes)
{
	int ret = FALSE;

	lock_n_read(rh);
	if (rh->rdat->flags & BYTECREDITS)
	{
		ret = ((rh->rdat->downloadcredits - (bytes * rh->rdat->byteoutmult)) < 0LL);
		if (!ret)
			rh->rdat->downloadcredits -= bytes * rh->rdat->byteoutmult;
	}
	write_n_unlock(rh);
	return(ret);
}


void ratio_uploadfile(RATIOHANDLE *rh)
{
	lock_n_read(rh);
	if (rh->rdat->flags & FILECREDITS)
		rh->rdat->filecredits += rh->rdat->fileinmult;
	write_n_unlock(rh);
}

int ratio_downloadfile(RATIOHANDLE *rh)
{
	int ret = FALSE;

	lock_n_read(rh);
	if (rh->rdat->flags & FILECREDITS)
	{
		ret = (rh->rdat->fileoutmult > rh->rdat->filecredits);
		if (!ret)
			rh->rdat->filecredits -= rh->rdat->fileoutmult;
	}
	write_n_unlock(rh);
	return(ret);
}

void ratio_reread(RATIOHANDLE *rh)
{
	lock_n_read(rh);
	write_n_unlock(rh);
}

void ratio_stat(FTPSTATE *peer, RATIOHANDLE *rh, char *replystr)
{
	ratio_reread(rh);
	if (rh->rdat->flags & BYTECREDITS)
	{
		ftp_write(peer, TRUE, 0, REPLY_RATIOBYTECREDITS(replystr, rh->rdat->downloadcredits / rh->rdat->byteoutmult));
		ftp_write(peer, TRUE, 0, REPLY_RATIOBYTERATIO(replystr, rh->rdat->byteoutmult, rh->rdat->byteinmult));
	}
	if (rh->rdat->flags & FILECREDITS)
	{
		ftp_write(peer, TRUE, 0, REPLY_RATIOFILECREDITS(replystr, rh->rdat->filecredits / rh->rdat->fileoutmult));
		ftp_write(peer, TRUE, 0, REPLY_RATIOFILERATIO(replystr, rh->rdat->fileoutmult, rh->rdat->fileinmult));
	}
}

void ratio_settokens(RATIOHANDLE *rh, TOKENSET *ts)
{
	ratio_reread(rh);
	if (rh->rdat->flags & BYTECREDITS)
		tokenset_settoken(ts, 'a', safe_snprintf("%lld", rh->rdat->downloadcredits / rh->rdat->byteoutmult));
	if (rh->rdat->flags & FILECREDITS)
		tokenset_settoken(ts, 'A', safe_snprintf("%d", rh->rdat->filecredits / rh->rdat->fileoutmult));
}	

void ratio_finish(RATIOHANDLE *rh)
{
	freewrapper(rh->rdat);
	if (rh->ratiofilefd != 0)
		close(rh->ratiofilefd);
	freewrapper(rh);
}
