boinc/client/net_xfer_curl.C

439 lines
13 KiB
C

// 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 <cstdio>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <cerrno>
#include <cstdlib>
#include <cstring>
#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;
up_active = false;
down_active = false;
}
// Connect to a server,
// and if successful insert the NET_XFER object into the set
//
int NET_XFER_SET::insert(NET_XFER* nxp) {
//int retval = nxp->open_server();
//if (retval) return retval;
net_xfers.push_back(nxp);
return 0;
}
// Remove a NET_XFER object from the set
//
int NET_XFER_SET::remove(NET_XFER* nxp) {
vector<NET_XFER*>::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, "NET_XFER_SET::remove(): not found\n");
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;
//struct timeval tv;
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
// note that I use timeout value so that we don't "hog" 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
if ((nxf = lookup_curl(pcurlMsg->easy_handle)) ) {
// CURLINFO ci=CURLINFO_OS_ERRNO;
// 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;
if (nxf->want_download) {
bytes_down += nxf->bytes_xferred;
// get xfer speed (don't mess with bytes_xferred, that's in write function
curlErr = curl_easy_getinfo(nxf->curlEasy,
CURLINFO_SPEED_DOWNLOAD, &nxf->xfer_speed);
}
if (nxf->want_upload) {
bytes_up += nxf->bytes_xferred;
// get xfer speed (don't mess with bytes_xferred, that's in write function
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.
nxf->http_op_retval = HTTP_STATUS_INTERNAL_SERVER_ERROR;
if (nxf->CurlResult == CURLE_OK) {
if ((nxf->response/100)*100 == HTTP_STATUS_OK) {
nxf->http_op_retval = 0;
} else {
nxf->http_op_retval = nxf->response;
}
} else if (nxf->CurlResult == CURLE_OUT_OF_MEMORY) {
msg_printf(0, MSG_ERROR, "An out of memory condition has been detected");
nxf->http_op_retval = ERR_MALLOC;
} else if (nxf->CurlResult == CURLE_COULDNT_RESOLVE_HOST) {
msg_printf(0, MSG_ERROR, "Couldn't resolve hostname [%s]", nxf->hostname);
nxf->http_op_retval = ERR_GETHOSTBYNAME;
} else if (nxf->CurlResult == CURLE_COULDNT_RESOLVE_PROXY) {
msg_printf(0, MSG_ERROR, "Couldn't resolve proxy [%s]", nxf->hostname);
nxf->http_op_retval = ERR_GETHOSTBYNAME;
} else if (nxf->CurlResult == CURLE_COULDNT_CONNECT) {
msg_printf(0, MSG_ERROR, "Couldn't connect to hostname [%s]", nxf->hostname);
nxf->http_op_retval = ERR_IO;
} else if (nxf->CurlResult == CURLE_LOGIN_DENIED) {
msg_printf(0, MSG_ERROR, "Proxy authentication failed against proxy [%s]", nxf->hostname);
nxf->http_op_retval = ERR_AUTHENTICATOR;
} else if (nxf->CurlResult == CURLE_OPERATION_TIMEOUTED) {
msg_printf(0, MSG_ERROR, "Attempting to communicate with [%s] timed out", nxf->hostname);
nxf->http_op_retval = ERR_TIMEOUT;
} else if (nxf->CurlResult == CURLE_READ_ERROR) {
msg_printf(0, MSG_ERROR, "Attempting to read a file failed [%s]", nxf->strCurlResult);
nxf->http_op_retval = ERR_FREAD;
} else if (nxf->CurlResult == CURLE_WRITE_ERROR) {
msg_printf(0, MSG_ERROR, "Attempting to write to a file failed [%s]", nxf->strCurlResult);
nxf->http_op_retval = ERR_FWRITE;
} else if (nxf->CurlResult == CURLE_RECV_ERROR) {
msg_printf(0, MSG_ERROR, "Attempting to receive data from [%s] failed [%s]", nxf->hostname, nxf->strCurlResult);
nxf->http_op_retval = ERR_READ;
} else if (nxf->CurlResult == CURLE_SEND_ERROR) {
msg_printf(0, MSG_ERROR, "Attempting to send data to [%s] failed [%s]", nxf->hostname, nxf->strCurlResult);
nxf->http_op_retval = ERR_WRITE;
}
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
}
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 whose socket matches fd
NET_XFER* NET_XFER_SET::lookup_curl(CURL* pcurl) {
for (unsigned int i=0; i<net_xfers.size(); i++) {
if (net_xfers[i]->curlEasy == 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;
#if 0
// TODO: figure out what to do here
} else if (xfer_speed == 0) {
xfer_speed = 999999999;
#endif
}
}
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"
);
}
// return true if an upload is currently in progress
// or has been since the last call to this.
// Similar for download.
void NET_XFER_SET::check_active(bool& up, bool& down) {
unsigned int i;
NET_XFER* nxp;
up = up_active;
down = down_active;
for (i=0; i<net_xfers.size(); i++) {
nxp = net_xfers[i];
if (nxp->is_connected && nxp->do_file_io) {
nxp->want_download?down=true:up=true;
}
}
up_active = false;
down_active = false;
}
const char *BOINC_RCSID_e0a7088e04 = "$Id$";