// Berkeley Open Infrastructure for Network Computing // http://boinc.berkeley.edu // Copyright (C) 2005 University of California // // This is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; // either version 2.1 of the License, or (at your option) any later version. // // This software 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 Lesser General Public License for more details. // // To view the GNU Lesser General Public License visit // http://www.gnu.org/copyleft/lesser.html // or write to the Free Software Foundation, Inc., // 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "cpp.h" #ifdef _WIN32 #ifndef _CONSOLE #include "stdafx.h" #include "wingui_mainwindow.h" #else #include "boinc_win.h" #endif #include "win_util.h" #endif #ifndef _WIN32 #include "config.h" #include #ifdef HAVE_UNISTD_H #include #endif #include #include #ifdef HAVE_SYS_SOCKET_H #include #endif #include #include #include #include #endif #include "error_numbers.h" #include "net_xfer_curl.h" #include "util.h" #include "network.h" #include "filesys.h" #include "client_types.h" #include "client_state.h" #include "client_msgs.h" using std::vector; // if an active transfer doesn't get any activity // in this many seconds, error out // #define NET_XFER_TIMEOUT 600 CURLM* g_curlMulti = NULL; // global curl for this module, can handle http & https // the file descriptor sets need to be global so libcurl has access always fd_set read_fds, write_fds, error_fds; // call these once at the start of the program and once at the end (init & cleanup of course) int curl_init() { curl_global_init(CURL_GLOBAL_ALL); g_curlMulti = curl_multi_init(); return (int)(g_curlMulti == NULL); } int curl_cleanup() { if (g_curlMulti) { curl_multi_cleanup(g_curlMulti); } return 0; } void NET_XFER::reset() { req1 = NULL; strcpy(infile, ""); strcpy(outfile, ""); CurlResult = CURLE_OK; strcpy(strCurlResult, ""); bTempOutfile = true; is_connected = false; want_download = false; want_upload = false; do_file_io = true; // CMC Note: should I default to true, i.e. this does all i/o? io_done = false; fileIn = NULL; fileOut = NULL; io_ready = true; // don't allow higher levels to do i/o? error = 0; file_read_buf_offset = 0; file_read_buf_len = 0; bytes_xferred = 0; xfer_speed = 0; bSentHeader = false; close_socket(); } NET_XFER::NET_XFER() { pcurlList = NULL; // these have to be NULL, just in constructor curlEasy = NULL; pcurlFormStart = NULL; pcurlFormEnd = NULL; pByte = NULL; lSeek = 0; reset(); } NET_XFER::~NET_XFER() { close_socket(); close_file(); } void NET_XFER::close_socket() { // CMC: this just cleans up the curlEasy, and "spoofs" the old close_socket if (pcurlList) { curl_slist_free_all(pcurlList); pcurlList = NULL; } if (curlEasy && pcurlFormStart) { curl_formfree(pcurlFormStart); curl_formfree(pcurlFormEnd); pcurlFormStart = pcurlFormEnd = NULL; } if (curlEasy && g_curlMulti) { // release this handle curl_multi_remove_handle(g_curlMulti, curlEasy); curl_easy_cleanup(curlEasy); curlEasy = NULL; } } void NET_XFER::close_file() { if (fileIn) { fclose(fileIn); fileIn = NULL; } if (fileOut) { fclose(fileOut); fileOut = NULL; } if (pByte) { //free any read memory used delete [] pByte; pByte = NULL; } } void NET_XFER::init(char* host, int p, int b) { reset(); safe_strcpy(hostname, host); port = p; blocksize = (b > MAX_BLOCKSIZE ? MAX_BLOCKSIZE : b); start_time = gstate.now; reset_timeout(); } bool NET_XFER::check_timeout(bool time_passed) { if (seconds_until_timeout == 0) { io_done = true; error = ERR_TIMEOUT; return true; } if (time_passed) { seconds_until_timeout--; } return false; } void NET_XFER::reset_timeout() { seconds_until_timeout = NET_XFER_TIMEOUT; } char* NET_XFER::get_hostname() { return hostname; } NET_XFER_SET::NET_XFER_SET() { max_bytes_sec_up = 0; max_bytes_sec_down = 0; bytes_left_up = 0; bytes_left_down = 0; bytes_up = 0; bytes_down = 0; } // Connect to a server, // and if successful insert the NET_XFER object into the set // int NET_XFER_SET::insert(NET_XFER* nxp) { net_xfers.push_back(nxp); return 0; } // Remove a NET_XFER object from the set // int NET_XFER_SET::remove(NET_XFER* nxp) { vector::iterator iter; iter = net_xfers.begin(); while (iter != net_xfers.end()) { if (*iter == nxp) { net_xfers.erase(iter); return 0; } iter++; } msg_printf(NULL, MSG_ERROR, "Network transfer object not found"); return ERR_NOT_FOUND; } void NET_XFER_SET::get_fdset(FDSET_GROUP& fg) { CURLMcode curlMErr; curlMErr = curl_multi_fdset(g_curlMulti, &fg.read_fds, &fg.write_fds, &fg.exc_fds, &fg.max_fd); //printf("curl msfd %d %d\n", curlMErr, fg.max_fd); } void NET_XFER_SET::got_select(FDSET_GROUP&, double timeout) { int iNumMsg; NET_XFER* nxf = NULL; bool time_passed = false; CURLMsg *pcurlMsg = NULL; SCOPE_MSG_LOG scope_messages(log_messages, CLIENT_MSG_LOG::DEBUG_NET_XFER); // if a second has gone by, do rate-limit accounting // time_t t = time(0); if (t != last_time) { time_passed = true; last_time = (int)t; if (bytes_left_up < max_bytes_sec_up) { bytes_left_up += max_bytes_sec_up; } if (bytes_left_down < max_bytes_sec_down) { bytes_left_down += max_bytes_sec_down; } } int iRunning = 0; // curl flags for max # of fds & # running queries CURLMcode curlMErr; CURLcode curlErr; // get the data waiting for transfer in or out // use timeout value so that we don't hog CPU in this loop // bool got_data = false; while (1) { curlMErr = curl_multi_perform(g_curlMulti, &iRunning); if (curlMErr != CURLM_CALL_MULTI_PERFORM) break; if (dtime() - gstate.now > timeout) break; got_data = true; } // read messages from curl that may have come in from the above loop while ((pcurlMsg = curl_multi_info_read(g_curlMulti, &iNumMsg))) { // if we have a msg, then somebody finished // can check also with pcurlMsg->msg == CURLMSG_DONE // nxf = lookup_curl(pcurlMsg->easy_handle); if (!nxf) continue; // we have a message from one of our http_ops // get the response code for this request // curlErr = curl_easy_getinfo(nxf->curlEasy, CURLINFO_RESPONSE_CODE, &nxf->response ); // CURLINFO_LONG+25 is a workaround for a bug in the gcc version // included with Mac OS X 10.3.9 // curlErr = curl_easy_getinfo(nxf->curlEasy, (CURLINFO)(CURLINFO_LONG+25) /*CURLINFO_OS_ERRNO*/, &nxf->error); nxf->io_done = true; nxf->io_ready = false; // update byte counts and transfer speed // if (nxf->want_download) { bytes_down += nxf->bytes_xferred; curlErr = curl_easy_getinfo(nxf->curlEasy, CURLINFO_SPEED_DOWNLOAD, &nxf->xfer_speed ); } if (nxf->want_upload) { bytes_up += nxf->bytes_xferred; curlErr = curl_easy_getinfo(nxf->curlEasy, CURLINFO_SPEED_UPLOAD, &nxf->xfer_speed ); } // the op is done if curl_multi_msg_read gave us a msg for this http_op nxf->http_op_state = HTTP_STATE_DONE; // added a more useful error string (just pass the curl string up for now) nxf->CurlResult = pcurlMsg->data.result; safe_strcpy(nxf->strCurlResult, curl_easy_strerror(nxf->CurlResult)); // optional, example use, non-zero CurlResult has a useful error string: //if (nxf->CurlResult) fprintf(stdout, "Error: %s\n", nxf->strCurlResult); // 200 is a good HTTP response code // It may not mean the data received is "good" // (the calling program will have to check/parse that) // but it at least means that the server operation // went through fine // // NOTE: http_op_retval is multipurposed, // it can also contain any error code that BOINC would return // for IO errors and DNS errors. // We need to translate between the curl error codes and the equiv. // BOINC error codes here. // if (nxf->CurlResult == CURLE_OK) { if ((nxf->response/100)*100 == HTTP_STATUS_OK) { nxf->http_op_retval = 0; } else if ((nxf->response/100)*100 == HTTP_STATUS_CONTINUE) { continue; } else { nxf->http_op_retval = nxf->response; } } else { msg_printf(0, MSG_ERROR, "HTTP error: %s", nxf->strCurlResult); nxf->http_op_retval = ERR_HTTP_ERROR; } if (!nxf->http_op_retval && nxf->http_op_type == HTTP_OP_POST2) { // for a successfully completed request on a "post2" -- // read in the temp file into req1 memory // fclose(nxf->fileOut); double dSize = 0.0f; file_size(nxf->outfile, dSize); nxf->fileOut = boinc_fopen(nxf->outfile, "rb"); if (!nxf->fileOut) { // ack, can't open back up! nxf->response = 1; // flag as a bad response for a possible retry later } else { fseek(nxf->fileOut, 0, SEEK_SET); // CMC Note: req1 is a pointer to "header" which is 4096 memset(nxf->req1, 0x00, 4096); fread(nxf->req1, 1, (size_t) dSize, nxf->fileOut); } } // close files and "sockets" (i.e. libcurl handles) // nxf->close_file(); nxf->close_socket(); // finally remove the tmpfile if not explicitly set // if (nxf->bTempOutfile) { boinc_delete_file(nxf->outfile); } } } // Return the NET_XFER object with given Curl object // NET_XFER* NET_XFER_SET::lookup_curl(CURL* pcurl) { for (unsigned int i=0; icurlEasy == pcurl) { return net_xfers[i]; } } return 0; } // Update the transfer speed for this NET_XFER // called on every I/O // void NET_XFER::update_speed() { double delta_t = dtime() - start_time; if (delta_t > 0) { xfer_speed = bytes_xferred / delta_t; } } void NET_XFER::got_error() { // TODO: which socket?? error = ERR_IO; io_done = true; log_messages.printf( CLIENT_MSG_LOG::DEBUG_NET_XFER, "IO error on socket\n" ); } const char *BOINC_RCSID_e0a7088e04 = "$Id$";