/* ** 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 */ #include #include #include #include #include #ifdef STDC_HEADERS # include # include #endif #ifdef HAVE_UNISTD_H # include #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; }