/**************************************************************************** * apps/netutils/ftpc/ftpc_getfile.c * * Copyright (C) 2011 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include "ftpc_config.h" #include #include #include #include #include #include #include #include "ftpc_internal.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /**************************************************************************** * Private Types ****************************************************************************/ /**************************************************************************** * Private Data ****************************************************************************/ /**************************************************************************** * Public Data ****************************************************************************/ /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: ftpc_recvinit * * Description: * Initialize to receive a file * ****************************************************************************/ static int ftpc_recvinit(struct ftpc_session_s *session, FAR const char *path, uint8_t xfrmode, off_t offset) { int ret; /* Reset transfer related variables */ ftpc_xfrreset(session); ret = ftpc_xfrinit(session); if (ret != OK) { return ERROR; } /* Configure the transfer: Initial file offset and tranfer mode */ session->offset = 0; session->filesize = -1; ftpc_xfrmode(session, xfrmode); /* Handle the resume offset (caller is responsible for fseeking in the * file) */ if (offset > 0) { /* Send the REST command. This command sets the offset where the * transfer should start. This must come after PORT or PASV commands. */ ret = ftpc_cmd(session, "REST %ld", offset); if (ret < 0) { ndbg("REST command failed: %d\n", errno); return ERROR; } session->size = offset; session->rstrsize = offset; } /* Send the RETR (Retrieve a remote file) command. Normally the server * responds with a mark using code 150: * * - "150 File status okay; about to open data connection" * * It then stops accepting new connections, attempts to send the contents * of the file over the data connection, and closes the data connection. * Finally it either accepts the RETR request with: * * - "226 Closing data connection" if the entire file was successfully * written to the server's TCP buffers * * Or rejects the RETR request with: * * - "425 Can't open data connection" if no TCP connection was established * - "426 Connection closed; transfer aborted" if the TCP connection was * established but then broken by the client or by network failure * - "451 Requested action aborted: local error in processing" or * "551 Requested action aborted: page type unknown" if the server had * trouble reading the file from disk. */ ret = ftpc_cmd(session, "RETR %s", path); if (ret < 0) { ndbg("RETR command failed: %d\n", errno); return ERROR; } /* Accept a connection on the data socket */ ret = ftpc_sockaccept(&session->data, "r", FTPC_IS_PASSIVE(session)); if (ret != OK) { ndbg("data connection not accepted\n"); } return ret; } /**************************************************************************** * Name: ftpc_waitinput * * Description: * Wait to receive data. * ****************************************************************************/ static int ftpc_waitinput(FAR struct ftpc_session_s *session) { int ret; do { ret = ftpc_waitdata(session, session->data.instream, true); if (ret == -1) { if (errno == EINTR) { FTPC_SET_INTERRUPT(session); } return ERROR; } } while(ret == 0); return OK; } /**************************************************************************** * Name: ftpc_recvbinary * * Description: * Receive a binary file. * ****************************************************************************/ static int ftpc_recvbinary(FAR struct ftpc_session_s *session, FAR FILE *rinstream, FAR FILE *loutstream) { FAR char *buf; ssize_t nread; ssize_t nwritten; int err; int ret = OK; buf = (FAR char *)malloc(CONFIG_FTP_BUFSIZE); if (!buf) { err = ENOMEM; goto errout_with_err; } for (;;) { if (ftpc_waitinput(session) != 0) { nvdbg("ftpc_waitinput() failed\n"); err = EIO; goto errout_with_buf; } nread = fread(buf, sizeof(char), CONFIG_FTP_BUFSIZE, rinstream); if (nread <= 0) { /* nread == 0 means end of file */ if (nread < 0) { (void)ftpc_xfrabort(session, rinstream); ret = ERROR; } break; } nwritten = fwrite(buf, sizeof(char), nread, loutstream); if (nwritten != nread) { (void)ftpc_xfrabort(session, loutstream); ret = ERROR; break; } session->size += nread; } free(buf); return ret; errout_with_buf: free(buf); errout_with_err: set_errno(err); return ERROR; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: ftpc_getfile * * Description: * Get a file from the remote host. * ****************************************************************************/ int ftpc_getfile(SESSION handle, FAR const char *rname, FAR const char *lname, uint8_t how, uint8_t xfrmode) { FAR struct ftpc_session_s *session = (FAR struct ftpc_session_s *)handle; struct stat statbuf; FILE *loutstream; off_t offset; int ret; /* Get information about the local file */ ret = stat(lname, &statbuf); if (ret == 0) { /* It already exists. Is it a directory? */ if (S_ISDIR(statbuf.st_mode)) { ndbg("'%s' is a directory\n", lname); return ERROR; } } /* Is it write-able? */ #ifdef S_IWRITE if (!(statbuf.st_mode & S_IWRITE)) { ndbg("'%s' permission denied\n", lname); return ERROR; } #endif /* Are we resuming the transfers? Is so then the starting offset is the * size of the existing, partial file. */ if (how == FTPC_GET_RESUME) { offset = statbuf.st_size; } else { offset = 0; } /* Setup to receive the file */ ret = ftpc_recvinit(session, rname, xfrmode, offset); if (ret != OK) { ndbg("ftpc_recvinit failed\n"); return ERROR; } loutstream = fopen(lname, (offset > 0 || (how == FTPC_GET_APPEND)) ? "a" : "w"); if (!loutstream) { ndbg("fopen failed: %d\n", errno); session->offset = 0; return ERROR; } if (offset > 0) { ret = fseek(loutstream, offset, SEEK_SET); if (ret != OK) { ndbg("fseek failed: %d\n", errno); fclose(loutstream); session->offset = 0; return ERROR; } } /* Save the new local and remote file names */ free(session->rname); free(session->lname); session->rname = strdup(rname); session->lname = strdup(lname); /* And receive the new file data */ if (xfrmode == FTPC_XFRMODE_ASCII) { ret = ftpc_recvbinary(session, session->data.instream, loutstream); } else { ret = ftpc_recvtext(session, session->data.instream, loutstream); } ftpc_sockclose(&session->data); if (ret == 0) { fptc_getreply(session); } fclose(loutstream); return (ret == OK && !FTPC_INTERRUPTED(session)) ? OK : ERROR; } /**************************************************************************** * Name: ftpc_recvbinary * * Description: * Receive a text file. * ****************************************************************************/ int ftpc_recvtext(FAR struct ftpc_session_s *session, FAR FILE *rinstream, FAR FILE *loutstream) { char *buf = (char *)malloc(CONFIG_FTP_BUFSIZE); int c; int ret = OK; while((c = fgetc(rinstream)) != EOF) { if (ftpc_waitinput(session) != 0) { break; } if (c == '\r') { c = fgetc(rinstream); if (c == EOF) { (void)ftpc_xfrabort(session, rinstream); ret = ERROR; break; } if (c != '\n') { ungetc(c, rinstream); c = '\r'; } } if (fputc(c, loutstream) == EOF) { (void)ftpc_xfrabort(session, loutstream); ret = ERROR; break; } session->size++; } free(buf); return ret; }