/**************************************************************************** * 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 "netutils/ftpc.h" #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; 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) { nwarn("WARNING: REST command failed: %d\n", errno); return ERROR; } session->size = 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) { nwarn("WARNING: RETR command failed: %d\n", errno); return ERROR; } /* In active mode, we need to accept a connection on the data socket * (in passive mode, we have already connected the data channel to * the FTP server). */ if (!FTPC_IS_PASSIVE(session)) { ret = ftpc_sockaccept(&session->dacceptor, &session->data); if (ret != OK) { nerr("ERROR: Data connection not accepted\n"); } } return ret; } /**************************************************************************** * Name: ftpc_recvbinary * * Description: * Receive a binary file. * ****************************************************************************/ static int ftpc_recvbinary(FAR struct ftpc_session_s *session, FAR FILE *rinstream, FAR FILE *loutstream) { ssize_t nread; ssize_t nwritten; /* Loop until the entire file is received */ for (;;) { /* Read the data from the socket */ nread = fread(session->buffer, sizeof(char), CONFIG_FTP_BUFSIZE, rinstream); if (nread <= 0) { /* nread < 0 is an error */ if (nread < 0) { /* errno should already be set by fread */ (void)ftpc_xfrabort(session, rinstream); return ERROR; } /* nread == 0 means end of file. Return success */ return OK; } /* Write the data to the file */ nwritten = fwrite(session->buffer, sizeof(char), nread, loutstream); if (nwritten != nread) { (void)ftpc_xfrabort(session, loutstream); /* If nwritten < 0 errno should already be set by fwrite. * What would a short write mean? */ return ERROR; } /* Increment the size of the file written */ session->size += nwritten; } } /**************************************************************************** * 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; FAR char *abslpath; off_t offset; int ret; /* Don't call this with a NULL remote file name */ DEBUGASSERT(rname); /* If the local name is not specified, then it is assumed to the same as * the remote file name. */ if (!lname) { lname = rname; } /* Get the full path to the local file */ abslpath = ftpc_abslpath(session, lname); if (!abslpath) { nwarn("WARNING: ftpc_abslpath(%s) failed: %d\n", errno); goto errout; } /* Get information about the local file */ ret = stat(abslpath, &statbuf); if (ret == 0) { /* It already exists. Is it a directory? */ if (S_ISDIR(statbuf.st_mode)) { nwarn("WARNING: '%s' is a directory\n", abslpath); goto errout_with_abspath; } } /* Is it write-able? */ #ifdef S_IWRITE if (!(statbuf.st_mode & S_IWRITE)) { nwarn("WARNING: '%s' permission denied\n", abslpath); goto errout_with_abspath; } #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) { nerr("ERROR: ftpc_recvinit failed\n"); goto errout_with_abspath; } loutstream = fopen(abslpath, (offset > 0 || (how == FTPC_GET_APPEND)) ? "a" : "w"); if (!loutstream) { nerr("ERROR: fopen failed: %d\n", errno); goto errout_with_abspath; } /* If the offset is non-zero, then seek to that offset in the file */ if (offset > 0) { ret = fseek(loutstream, offset, SEEK_SET); if (ret != OK) { nerr("ERROR: fseek failed: %d\n", errno); goto errout_with_outstream; } } /* And receive the new file data */ if (xfrmode == FTPC_XFRMODE_ASCII) { ret = ftpc_recvtext(session, session->data.instream, loutstream); } else { ret = ftpc_recvbinary(session, session->data.instream, loutstream); } ftpc_sockclose(&session->data); if (ret == 0) { fptc_getreply(session); } /* Check for success */ if (ret == OK && !FTPC_INTERRUPTED(session)) { fclose(loutstream); free(abslpath); return OK; } /* Various error exits */ errout_with_outstream: fclose(loutstream); errout_with_abspath: free(abslpath); session->offset = 0; errout: return ERROR; } /**************************************************************************** * Name: ftpc_recvtext * * Description: * Receive a text file. * ****************************************************************************/ int ftpc_recvtext(FAR struct ftpc_session_s *session, FAR FILE *rinstream, FAR FILE *loutstream) { int ch; /* Read the next character from the incoming data stream */ while ((ch = fgetc(rinstream)) != EOF) { /* Is it a carriage return? Compress \r\n to \n */ if (ch == '\r') { /* Get the next character */ ch = fgetc(rinstream); if (ch == EOF) { /* Ooops... */ (void)ftpc_xfrabort(session, rinstream); return ERROR; } /* If its not a newline, then keep the carriage return */ if (ch != '\n') { ungetc(ch, rinstream); ch = '\r'; } } /* Then write the character to the output file */ if (fputc(ch, loutstream) == EOF) { (void)ftpc_xfrabort(session, loutstream); return ERROR; } /* Increase the actual size of the file by one */ session->size++; } return OK; }