/*
** 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