/* 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 "ftpcmd.h"
#include "reply.h"

extern FTPCMD ftpcommandtable[];


int filter_toascii(char *data, int *len)
{
	int count, len2 = *len;
	char buffer2[BUFFERSIZE * 2];
	int last = 0;
	char *b2ptr = buffer2;
	
	for (count = 0; count < len2; count++)
	{
		if ((data[count] == 10) && (last != 13))
		{
			*b2ptr = 13;
			b2ptr++;
			(*len)++;
		}
		*b2ptr = data[count];
		last = *b2ptr;
		b2ptr++;
	}
	memcpy(data, buffer2, *len);
	return(*len);
}

int filter_fromascii(char *data, int *len)
{
	char *dptr = data;
	int count, len2 = *len;
	
	for (count = 0; count < len2; count++)
	{
		if (data[count] != 13)
		{
			*dptr = data[count];
			dptr++;
		}
		else
			(*len)--;
	}
	return(len2);
}
 
int download_write(SELECTER *sel, int fd, void *peerv)
{
	FTPSTATE *peer = (FTPSTATE *)peerv;
	DATAPORT *d = peer->dport;
	int startp, size, size2, finished = FALSE;
	char indata[BUFFERSIZE * 2];
	int maxsize;
	
	/* determine maximum transfer size */
	
	if (peer->maxtranspd_down)
		maxsize = MINIMUM(peer->maxtranspd_down, BUFFERSIZE);
	else if (peer->maxtranspd)
		maxsize = MINIMUM(peer->maxtranspd, BUFFERSIZE);
	else
		maxsize = BUFFERSIZE;

	/* if we have binary mode and no ratios to worry about,
	   use sendfile to improve performace. A huge amount of data
	   may be transmitted at a time using sendfile. But at the moment
	   sendfile in Linux REALLY SUCKS. Must keep size small otherwise
	   I hog processor/machine from other processes. Even other CPUS
	   are halted during a sendfile! (also any file operation) */

#ifdef HAVE_SENDFILE
#ifdef Linux	/* Don't know if this will work all the time */

	if ((d->binary) && (!peer->ratioinfo))
	{
		if (d->startpos != 0)
		{
			d->pos = d->startpos;
			d->startpos = 0;
		}
		size2 = size = sendfile(d->socketfd, d->filefd, &(d->pos), maxsize);
	} else
#endif
#ifdef FreeBSD
	if ((d->binary) && (!peer->ratioinfo))
	{
		int result;
		if (d->startpos != 0)
		{
			d->pos = d->startpos;
			d->startpos = 0;
		}
		result = sendfile(d->filefd, d->socketfd, d->pos, maxsize,
				  NULL, (off_t *)&size, 0);
		d->pos += size;
		if ((result == -1) && (errno != EAGAIN))
			size = -1;
		size2 = size;
	} else
#endif			
#endif
	if (STRLENGTH(d->buffer) == 0)
	{
		size = read(d->filefd, indata, maxsize);
		size2 = 0;
		
		if ((size > 0) && (!d->binary))
			filter_toascii(indata, &size);
	
		d->pos += size;	
		if (d->pos < d->startpos)
			return(FALSE);
		else
			startp = MAXIMUM(0, size - (d->pos - d->startpos));

		if (((size - startp) > 0) && (peer->ratioinfo))
			if (ratio_downloadbytes(peer->ratioinfo, (size - startp)))
			{
				finished = TRUE;
				size = 0;
				ftp_write(peer, TRUE, 0, REPLY_BYTELIMIT);
			}
	
		if ((size > 0) && (startp != size))
			size2 = write(d->socketfd, indata + startp, size - startp);
		
		if ((size2 > 0) && ((size2 + startp) < size))
			string_cat(&(d->buffer), indata + startp + size2, size - (startp + size2));
	}
	else
	{			
		size = size2 = write(d->socketfd, STRTOCHAR(d->buffer), STRLENGTH(d->buffer));
		if (size2 > 0)
			string_dropfront(&(d->buffer), size);
	}
	
	if ((size <= 0) || (size2 <= 0) || finished)
	{
		if (d->download_limiter)
			limiter_add(d->download_limiter, 0, TRUE);

		select_delfd(sel, d->filefd);
		if ((size == 0) && (!finished))
			ftp_write(peer, FALSE, 226, REPLY_TRANSDONE(peer->dport->transbytes));
		else
			ftp_write(peer, FALSE, 426, REPLY_TRANSABORT(peer->dport->transbytes));
		closedatasocket(peer);
		return(2);
	}

	if (d->download_limiter)
		limiter_add(d->download_limiter, size, FALSE);
	
	d->transbytes += size;
	peer->downloadedfilebytes += size;
	return(FALSE);
}

int upload_read(SELECTER *sel, int fd, void *peerv)
{
	FTPSTATE *peer = (FTPSTATE *)peerv;
	DATAPORT *d = peer->dport;
	int size, size2;
	int finished = FALSE;
	char indata[BUFFERSIZE * 2];
	int maxsize;

	/* determine maximum read size */
	
	if (peer->maxtranspd_up)
		maxsize = MINIMUM(peer->maxtranspd_up, BUFFERSIZE);
	else if (peer->maxtranspd)
		maxsize = MINIMUM(peer->maxtranspd, BUFFERSIZE);
	else
		maxsize = BUFFERSIZE;
	
	size = read(d->socketfd, indata, maxsize);

	/* false alarm, no data, return */
	if ((size == -1) && (errno == EAGAIN))
		return FALSE;
		
	if (size > 0)
	{
		d->transbytes += size;
		peer->uploadedfilebytes += size;
		if (peer->maxtranspd || peer->maxtranspd_up)
			limiter_add(d->upload_limiter, size, FALSE);
		if (peer->ratioinfo)
			ratio_uploadbytes(peer->ratioinfo, size);
	}
	
	if ((size > 0) && (!d->binary))
		filter_fromascii(indata, &size);
	
	if (size > 0)
	{
		int re = 0;
		size2 = 0;
		while(((size - size2) > 0) && (re != -1))
		{
			re = write(d->filefd, indata + size2, size - size2);
			if (re == -1)
				finished = TRUE;
			else	
				size2 += re;
		}	
	}
	
	if ((size <= 0) || finished)
	{
		select_delfd(sel, d->filefd);
		if (peer->maxtranspd || peer->maxtranspd_up)
			limiter_add(d->upload_limiter, size, FALSE);

		if ((size == 0) && (!finished))
			ftp_write(peer, FALSE, 226, REPLY_TRANSDONE(peer->dport->transbytes));
		else
			ftp_write(peer, FALSE, 426, REPLY_TRANSABORT(peer->dport->transbytes));
		closedatasocket(peer);
		return(2);
	}


	return(FALSE);
}
	
int ftp_retr(FTPSTATE *peer, char *filename)
{
	int filefd;
	off_t size;
	char *fullname;
	
	if ((filefd = file_readopen(peer, filename, &fullname)) < 0)
		reporterror(peer, filename, errno);
	else
	{
		if (peer->ratioinfo)
			if (ratio_downloadfile(peer->ratioinfo))
			{
				ftp_write(peer, FALSE, 550, REPLY_FILELIMIT);
				return(FALSE);
			}
		
		peer->downloadedfiles++;
		
		/* if ascii transfer size is set to -1, else size is binary filesize */
		size = -1;
		if (peer->binary)
		{
			size = lseek(filefd, 0, SEEK_END);
			lseek(filefd, MINIMUM(size, peer->restartpos), SEEK_SET);
		}
		
		if (startdatasocket(peer, filefd, TRANS_DOWNLOAD, size) == 0)
			log_giveentry(MYLOG_FTRANS, peer, safe_snprintf("retrieve %s", fullname));
	}
	freewrapper(fullname);
	return(FALSE);
}

int ftp_stor_core(FTPSTATE *peer, char *filename, int unique)
{
	int filefd;
	int nounique = FALSE;
	char *fullname;

	/* There is 2 good reasons thus is here:
		1) My code won't work for resume ASCII upload.
		2) It is impossible to do. Telnet ascii to unix ascii is
		   irreversible. Therefore you don't know where the user
		   wants to start saving.
	*/
	if ((peer->restartpos != 0) && (!peer->binary))
		return(ftp_write(peer, FALSE, 500, REPLY_RESUMEASCIIUP));

	/* if the user asked for a unique filename, find one */
	if (unique)
		filefd = file_ustoreopen(peer, filename, &nounique, &fullname);
	else
		filefd = file_storeopen(peer, filename, &fullname);
	
	if (nounique)
		ftp_write(peer, FALSE, 500, REPLY_STOUNOUNIQUE);
	else if (filefd < 0)
		reporterror(peer, filename, errno);
	else
	{
		peer->uploadedfiles++;		
		if (startdatasocket(peer, filefd, TRANS_UPLOAD, -1) == 0)
		{
			log_giveentry(MYLOG_FTRANS, peer, safe_snprintf("store %s", fullname));
			/* now truncate the file to the restart pos length */
			ftruncate(filefd, peer->dport->pos);
			lseek(filefd, peer->dport->pos, SEEK_SET);
		}
	}
	
	freewrapper(fullname);
	return(FALSE);
}
	
int ftp_stor(FTPSTATE *peer, char *filename)
{
	return(ftp_stor_core(peer, filename, FALSE));	
}

int ftp_stou(FTPSTATE *peer, char *filename)
{
	return(ftp_stor_core(peer, filename, TRUE));
}

int ftp_appe(FTPSTATE *peer, char *filename)
{
	int filefd;
	char *fullname;

	peer->restartpos = 0;
	
	filefd = file_storeopen(peer, filename, &fullname);

	if (filefd < 0)
		reporterror(peer, filename, errno);
	else
	{
		peer->uploadedfiles++;
		
		if (startdatasocket(peer, filefd, TRANS_UPLOAD, -1) == 0)
		{
			log_giveentry(MYLOG_FTRANS, peer, safe_snprintf("append %s", fullname));
			
			/* this is for ratios, If the final file position is 0, then it is 
			   a fresh upload. */
			if (lseek(filefd, 0, SEEK_END) == 0) /* set to append */
				peer->dport->trans_type = TRANS_SUPLOAD;
		
		}
	}
	
	freewrapper(fullname);
	return(FALSE);
}
