/****************************************************************************** libprozilla - a download accelerator library Copyright (C) 2001 Kalum Somaratna This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ******************************************************************************/ /* $Id$ */ #include "common.h" #include "prozilla.h" #include "connect.h" #include "misc.h" #include "url.h" #include "netrc.h" #include "debug.h" #include "ftp.h" #include "ftp-retr.h" /* Will download a portion of/or the full file from the connection->url. */ uerr_t proz_ftp_get_file(connection_t * connection) { uerr_t err; char *user, *passwd; netrc_entry *netrc_ent; boolean passive_mode; char buffer[HTTP_BUFFER_SIZE]; /* we want it to terminate immediately */ pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); assert(connection->localfile != NULL); assert(connection->file_mode != NULL); /*clear the socks */ connection->ctrl_sock = 0; connection->data_sock = 0; connection->listen_sock = 0; /* if there is nothing to download then return */ if (connection->status == COMPLETED) { gettimeofday(&connection->time_begin, NULL); return FTPOK; } init_response(connection); pthread_mutex_lock(connection->status_change_mutex); connection->status = CONNECTING; /* connection_change_status(connection, CONNECTING); */ pthread_cond_broadcast(&connection->connecting_cond); pthread_mutex_unlock(connection->status_change_mutex); /* if we have to use a HTTP proxy call the routine which is defined in http-retr.c and just return. */ if (ftp_use_proxy(connection) && connection->ftp_proxy->type == HTTPPROXY) { err = ftp_get_file_from_http_proxy(connection); return err; } if (ftp_use_proxy(connection)) { /* Connect to the proxy server here. */ err = ftp_connect_to_server(connection, connection->ftp_proxy->proxy_url.host, connection->ftp_proxy->proxy_url.port); } else { err = ftp_connect_to_server(connection, connection->u.host, connection->u.port); } if (err == FTPCONREFUSED) { connection_change_status(connection, CONREJECT); close_sock(&connection->ctrl_sock); return err; } if (err != FTPOK) { connection_change_status(connection, REMOTEFATAL); return err; } done_with_response(connection); user = connection->u.user; passwd = connection->u.passwd; /* Use .netrc if asked to do so. */ if (connection->use_netrc == TRUE) { netrc_ent = search_netrc(libprozrtinfo.netrc_list, connection->u.host); if (netrc_ent != NULL) { user = netrc_ent->account; passwd = netrc_ent->password; } } user = user ? user : libprozrtinfo.ftp_default_user; passwd = passwd ? passwd : libprozrtinfo.ftp_default_passwd; proz_debug(_("Logging in as user %s with password %s."), user, passwd); connection_change_status(connection, LOGGININ); init_response(connection); err = ftp_login(connection, user, passwd); if (err != FTPOK) { if (err == FTPLOGREFUSED) { connection_change_status(connection, LOGINFAIL); } else { connection_change_status(connection, REMOTEFATAL); } close_sock(&connection->ctrl_sock); return err; } done_with_response(connection); init_response(connection); err = ftp_binary(connection); if (err != FTPOK) { connection_change_status(connection, REMOTEFATAL); close_sock(&connection->ctrl_sock); return err; } done_with_response(connection); /* Do we need to CWD? */ if (*connection->u.dir) { init_response(connection); err = ftp_cwd(connection, connection->u.dir); if (err != FTPOK) { connection_change_status(connection, REMOTEFATAL); proz_debug(_("CWD failed to change to directory '%s'."), connection->u.dir); close_sock(&connection->ctrl_sock); return err; } else { proz_debug(_("CWD ok.")); done_with_response(connection); } } else proz_debug(_("CWD not needed.")); err = ftp_setup_data_sock_1(connection, &passive_mode); if (err != FTPOK) { connection_change_status(connection, REMOTEFATAL); close_sock(&connection->ctrl_sock); return err; } /* do we need to REST */ if (connection->remote_startpos > 0 && connection->resume_support == TRUE) { err = ftp_rest(connection, connection->remote_startpos); } if (err != FTPOK) { if (err == FTPRESTFAIL) proz_debug (_ ("I have a bug in my code!!, check remote_starpos and resume_support values")); connection_change_status(connection, REMOTEFATAL); close_sock(&connection->ctrl_sock); return err; } err = ftp_retr(connection, connection->u.file); if (err != FTPOK) { proz_debug(_("RETR failed")); close_sock(&connection->ctrl_sock); connection_change_status(connection, REMOTEFATAL); return err; } err = ftp_setup_data_sock_2(connection, &passive_mode); if (err != FTPOK) { return err; } /* which routine to call */ if (connection->main_file_size == -1) err = connection_retr_fsize_not_known(connection, buffer, sizeof(buffer)); else err = connection_retr_fsize_known(connection, buffer, sizeof(buffer)); close_sock(&connection->data_sock); close_sock(&connection->ctrl_sock); if (err == FILEGETOK) return FTPOK; else return err; } /* A genuine loop ;) It willed be called by the main thread, and this will handle all possible errors itself, retrying until the number of maximum tries for the connection is realised or a error occurs which needs to be passed upwards for handling, such as LOGINFAIL */ uerr_t ftp_loop(connection_t * connection) { boolean retrying_from_loop = FALSE; assert(connection->max_attempts >= 0); assert(connection->attempts >= 0); pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); do { if (connection->attempts > 0) { if (retrying_from_loop == TRUE) { connection_show_message(connection, _("Retrying..Attempt %d in %d seconds"), connection->attempts, connection->retry_delay.tv_sec); delay_ms(connection->retry_delay.tv_sec * 1000); } if (connection->resume_support == TRUE) { if (connection_load_resume_info(connection) == -1) { connection_show_message(connection, _ ("Error while attemting to process download file ")); } } else { /*If we cant resume then reset the connections bytesreceived to 0 */ connection->remote_bytes_received = 0; } } /*Push the handler which will cleanup any sockets that are left open */ pthread_cleanup_push(cleanup_socks, (void *)connection); connection->err = proz_ftp_get_file(connection); /*pop the handler */ pthread_cleanup_pop(0); connection->attempts++; /*Should the error be handled at this level ? */ if (!ftp_loop_handle_error(connection->err)) { return connection->err; /*If not return and the main thread will handle it */ } switch (connection->err) { case FTPOK: connection_show_message(connection, _("Successfully got download")); return connection->err; break; default: connection_show_message(connection, _("Error occured in connection...")); break; } retrying_from_loop = TRUE; } while ((connection->attempts < connection->max_attempts) || connection->max_attempts == 0); connection_show_message(connection, _ ("I have tried %d attempt(s) and have failed, aborting"), connection->attempts); return connection->err; } /*Return true if it is a error which can be handled within the ftp_loop, or false if it should be passed upwards so that the main download thread can restart it when necessary after processing other threads status too */ boolean ftp_loop_handle_error(uerr_t err) { proz_debug("Error encountered in ftp_loop is %d", err); if (err == FTPNSFOD || err == FTPLOGREFUSED || err == FTPCONREFUSED || err == FWRITEERR || err == FOPENERR || err == FTPCWDFAIL || err == FTPRESTFAIL) return FALSE; else return TRUE; }