/*
**  Copyright 2000-2004 University of Illinois Board of Trustees
**  Copyright 2000-2004 Mark D. Roth
**  All rights reserved.
**
**  ftpfile.c - FTP file access code
**
**  Mark D. Roth <roth@feep.net>
*/

#include <internal.h>

#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>

#ifdef STDC_HEADERS
# include <string.h>
# include <stdlib.h>
#endif

#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif


struct ftpfile
{
	FTP *ff_ftp;			/* FTP structure */
	int ff_mode;			/* mode (see below) */
	char ff_cmd[FTPBUFSIZE];	/* FTP command */
	off_t ff_offset;		/* current file offset */
};


/* open a file via FTP */
int
_vftp_open_aux(FTPFILE **ftpfile, FTP *ftp, int mode, char *fmt, va_list args)
{
	/* allocate FTPFILE handle */
	*ftpfile = (FTPFILE *)calloc(1, sizeof(FTPFILE));
	if (*ftpfile == NULL)
		return -1;
	(*ftpfile)->ff_ftp = ftp;
	(*ftpfile)->ff_mode = mode;

	vsnprintf((*ftpfile)->ff_cmd, sizeof((*ftpfile)->ff_cmd), fmt, args);

#ifdef DEBUG
	printf("    _vftp_open_aux(): (*ftpfile)->ff_cmd=\"%s\"\n",
	       (*ftpfile)->ff_cmd);
#endif

	if (_ftp_data_connect(ftp, "%s", (*ftpfile)->ff_cmd) == -1)
	{
		free(*ftpfile);
		*ftpfile = NULL;
		return -1;
	}

	return 0;
}


/* open a file via FTP */
int
_ftp_open_aux(FTPFILE **ftpfile, FTP *ftp, int mode, char *fmt, ...)
{
	va_list args;
	int retval;

	va_start(args, fmt);
	retval = _vftp_open_aux(ftpfile, ftp, mode, fmt, args);
	va_end(args);

	return retval;
}


int
ftp_open(FTPFILE **ftpfile, FTP *ftp, char *file, int mode)
{
	char *cp;
	char cmd[MAXPATHLEN + 5];

	if (mode != O_RDONLY
	    && mode != O_WRONLY)
	{
		errno = ENOSYS;
		return -1;
	}

	if (mode == O_RDONLY)
		cp = "RETR";
	else
		cp = "STOR";

	return _ftp_open_aux(ftpfile, ftp, mode, "%s %s", cp, file);
}


/* read a file via FTP */
ssize_t
ftp_read(FTPFILE *ftpfile, char *buf, size_t bufsize)
{
	ssize_t sz;

	if (ftpfile->ff_mode != O_RDONLY)
	{
		errno = EBADF;
		return -1;
	}

	sz = _ftp_data_read(ftpfile->ff_ftp, buf, bufsize);

	if (sz > 0)
		ftpfile->ff_offset += sz;

	return sz;
}


/* write a file via FTP */
ssize_t
ftp_write(FTPFILE *ftpfile, char *buf, size_t bufsize)
{
	ssize_t sz;

	if (ftpfile->ff_mode != O_WRONLY)
	{
		errno = EBADF;
		return -1;
	}

	sz = _ftp_data_write(ftpfile->ff_ftp, buf, bufsize);

	if (sz > 0)
		ftpfile->ff_offset += sz;

	return sz;
}


static int
_ftp_skip(FTPFILE *ftpfile, off_t skip)
{
	char buf[FTPBUFSIZE];
	off_t remainder;
	ssize_t sz;

	for (remainder = skip; remainder > 0; remainder -= sz)
	{
		sz = ftp_read(ftpfile, buf,
			       ((size_t)remainder > sizeof(buf)
			        ? sizeof(buf)
				: (size_t)remainder));

		/* check for premature EOF */
		if (sz == 0)
		{
			errno = EINVAL;
			return -1;
		}

		if (sz == -1)
			return -1;
	}

	return 0;
}


/* seek to a given offset in an FTP file */
off_t
ftp_lseek(FTPFILE *ftpfile, off_t offset, int whence)
{
	off_t new_offset;
	char buf[FTPBUFSIZE];
	int code;

	/* only implemented for downloads... */
	if (ftpfile->ff_mode != O_RDONLY)
	{
		errno = ENOSYS;
		return (off_t)-1;
	}

	/* calculate new offset */
	switch (whence)
	{
	case SEEK_SET:
		new_offset = offset;
		break;

	case SEEK_CUR:
		new_offset = ftpfile->ff_offset + offset;
		break;

	case SEEK_END:
		errno = ENOSYS;
		return (off_t)-1;

	default:
		errno = EINVAL;
		return (off_t)-1;
	}

	/*
	** if we're already at the right offset, just return what that is
	*/
	if (new_offset == ftpfile->ff_offset)
		return ftpfile->ff_offset;

	/*
	** if we're seeking forward from our current offset,
	** and if REST STREAM is not supported, then just read
	** from the data connection until we get to the right place
	*/
	if (new_offset > ftpfile->ff_offset
	    && ! BIT_ISSET(ftpfile->ff_ftp->ftp_features, FTP_FEAT_REST_STREAM))
		goto skip_ahead;

	/*
	** if REST STREAM is supported,
	** or if we're seeking backwards from our current offset,
	** then we need to close the ftp-data connection
	** and create a new one
	*/

	if (_ftp_data_close(ftpfile->ff_ftp) == -1)
		return -1;

	/*
	** if REST STREAM is supported,
	** use it to go right to the offset we're looking for
	*/
	if (BIT_ISSET(ftpfile->ff_ftp->ftp_features, FTP_FEAT_REST_STREAM))
	{
		if (_ftp_data_init(ftpfile->ff_ftp) == -1)
			return (off_t)-1;

		if (_ftp_send_command(ftpfile->ff_ftp, "REST %lu",
				      (unsigned long)new_offset) == -1
		    || _ftp_get_response(ftpfile->ff_ftp, &code, buf,
					 sizeof(buf)) == -1)
			return (off_t)-1;
		if (code != 350)
		{
			if (code == 421)
				errno = ECONNRESET;
			else
				errno = EINVAL;
			return -1;
		}

		if (_ftp_data_start(ftpfile->ff_ftp, "%s",
				    ftpfile->ff_cmd) == -1)
			return (off_t)-1;

		ftpfile->ff_offset = new_offset;
		return ftpfile->ff_offset;
	}

	/*
	** if REST STREAM is not supported, just start from the
	** beginning and read past the bytes we don't care about
	*/
	if (_ftp_data_connect(ftpfile->ff_ftp, "%s", ftpfile->ff_cmd) == -1)
		return (off_t)-1;
	ftpfile->ff_offset = 0;

  skip_ahead:
	/*
	** we're seeking ahead, so just read through the area
	** we want to skip
	*/
	if (_ftp_skip(ftpfile, (new_offset - ftpfile->ff_offset)) == -1)
		return (off_t)-1;

	return ftpfile->ff_offset;
}


/* close an FTP file */
int
ftp_close(FTPFILE *ftpfile)
{
	int retval = 0;
	char buf[FTPBUFSIZE];

#if 0
	if (ftpfile->ff_mode == O_WRONLY)
	{
		const char ftp_eof[2] = { 0xff, 1 };

		/* send EOF marker */
		write(ftpfile->ff_fd, ftp_eof, 2);
	}
#endif

	if (_ftp_data_close(ftpfile->ff_ftp) == -1)
		retval = -1;

	/* FIXME: if we uploaded a file, force a directory refresh */

	free(ftpfile);
	return retval;
}




syntax highlighted by Code2HTML, v. 0.9.1