// This file is part of BOINC. // http://boinc.berkeley.edu // Copyright (C) 2008 University of California // // BOINC 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 3 of the License, or (at your option) any later version. // // BOINC 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. // // You should have received a copy of the GNU Lesser General Public License // along with BOINC. If not, see . // The "policy" part of file transfer is here. // The "mechanism" part is in pers_file_xfer.C and file_xfer.C // #include "cpp.h" #ifdef _WIN32 #include "boinc_win.h" #else #include "config.h" #include #include #include #endif #include "md5_file.h" #include "crypt.h" #include "str_replace.h" #include "str_util.h" #include "filesys.h" #include "cert_sig.h" #include "error_numbers.h" #include "async_file.h" #include "file_names.h" #include "client_types.h" #include "client_state.h" #include "client_msgs.h" #include "file_xfer.h" #include "project.h" #include "result.h" #include "sandbox.h" using std::vector; // Decide whether to consider starting a new file transfer // for the given persistent file transfer // bool CLIENT_STATE::start_new_file_xfer(PERS_FILE_XFER& pfx) { unsigned int i; int ntotal=0, nproj=0; if (network_suspended) return false; if (file_xfers_suspended) return false; // limit the number of file transfers per project // (uploads and downloads are limited separately) // for (i=0; ifile_xfers.size(); i++) { FILE_XFER* fxp = file_xfers->file_xfers[i]; // don't count user or project files // FILE_INFO* fip = fxp->fip; if (fip->is_user_file) continue; if (fip->is_project_file) continue; // count transfers in the same direction as this // if (pfx.is_upload == fxp->is_upload) { ntotal++; if (pfx.fip->project == fxp->fip->project) { nproj++; } } } if (nproj >= cc_config.max_file_xfers_per_project) return false; if (ntotal >= cc_config.max_file_xfers) return false; return true; } // Make a directory for each of the projects in the client state // int CLIENT_STATE::make_project_dirs() { unsigned int i; int retval; for (i=0; i ASYNC_FILE_THRESHOLD) { ASYNC_VERIFY* avp = new ASYNC_VERIFY; retval = avp->init(this); if (retval) { status = retval; return retval; } status = FILE_VERIFY_PENDING; return ERR_IN_PROGRESS; } retval = gunzip(cksum); if (retval) return retval; } else { safe_strcat(gzpath, "t"); if (!boinc_file_exists(gzpath)) { status = FILE_NOT_PRESENT; } return ERR_FILE_MISSING; } } // If the file isn't there at all, set status to FILE_NOT_PRESENT; // this will trigger a new download rather than erroring out // if (file_size(pathname, size)) { status = FILE_NOT_PRESENT; return ERR_FILE_MISSING; } if (gstate.global_prefs.dont_verify_images && is_image_file(name) && size>0 ) { return 0; } if (nbytes && (nbytes != size) && (!cc_config.dont_check_file_sizes)) { if (show_errors) { msg_printf(project, MSG_INTERNAL_ERROR, "File %s has wrong size: expected %.0f, got %.0f", name, nbytes, size ); } status = ERR_WRONG_SIZE; return ERR_WRONG_SIZE; } if (!verify_contents) return 0; if (signature_required) { if (!strlen(file_signature) && !cert_sigs) { msg_printf(project, MSG_INTERNAL_ERROR, "Application file %s missing signature", name ); msg_printf(project, MSG_INTERNAL_ERROR, "BOINC cannot accept this file" ); error_msg = "missing signature"; status = ERR_NO_SIGNATURE; return ERR_NO_SIGNATURE; } if (cc_config.use_certs || cc_config.use_certs_only) { if (verify_file_certs()) { verified = true; return 0; } } if (cc_config.use_certs_only) { msg_printf(project, MSG_INTERNAL_ERROR, "Unable to verify %s using certificates", name ); return ERR_NO_SIGNATURE; } if (allow_async && nbytes > ASYNC_FILE_THRESHOLD) { ASYNC_VERIFY* avp = new ASYNC_VERIFY(); retval = avp->init(this); if (retval) { status = retval; return retval; } status = FILE_VERIFY_PENDING; return ERR_IN_PROGRESS; } if (!strlen(cksum)) { double file_length; retval = md5_file(pathname, cksum, file_length); if (retval) { status = retval; msg_printf(project, MSG_INFO, "md5_file failed for %s: %s", pathname, boincerror(retval) ); return retval; } } retval = check_file_signature2( cksum, file_signature, project->code_sign_key, verified ); if (retval) { msg_printf(project, MSG_INTERNAL_ERROR, "Signature verification error for %s", name ); error_msg = "signature verification error"; status = ERR_RSA_FAILED; return ERR_RSA_FAILED; } if (!verified && show_errors) { msg_printf(project, MSG_INTERNAL_ERROR, "Signature verification failed for %s", name ); error_msg = "signature verification failed"; status = ERR_RSA_FAILED; return ERR_RSA_FAILED; } } else if (strlen(md5_cksum)) { if (!strlen(cksum)) { if (allow_async && nbytes > ASYNC_FILE_THRESHOLD) { ASYNC_VERIFY* avp = new ASYNC_VERIFY(); retval = avp->init(this); if (retval) { status = retval; return retval; } status = FILE_VERIFY_PENDING; return ERR_IN_PROGRESS; } retval = md5_file(pathname, cksum, local_nbytes); if (retval) { msg_printf(project, MSG_INTERNAL_ERROR, "MD5 computation error for %s: %s\n", name, boincerror(retval) ); error_msg = "MD5 computation error"; status = retval; return retval; } } if (strcmp(cksum, md5_cksum)) { if (show_errors) { msg_printf(project, MSG_INTERNAL_ERROR, "MD5 check failed for %s", name ); msg_printf(project, MSG_INTERNAL_ERROR, "expected %s, got %s\n", md5_cksum, cksum ); } error_msg = "MD5 check failed"; status = ERR_MD5_FAILED; return ERR_MD5_FAILED; } } return 0; } // scan FILE_INFOs and create PERS_FILE_XFERs as needed. // NOTE: this doesn't start the file transfers // scan PERS_FILE_XFERs and delete finished ones. // bool CLIENT_STATE::create_and_delete_pers_file_xfers() { unsigned int i; FILE_INFO* fip; PERS_FILE_XFER *pfx; bool action = false; int retval; static double last_time; if (!clock_change && now - last_time < PERS_FILE_XFER_START_PERIOD) return false; last_time = now; // Look for FILE_INFOs for which we should start a transfer, // and make PERS_FILE_XFERs for them // for (i=0; ipers_file_xfer; if (pfx) continue; if (fip->downloadable() && fip->status == FILE_NOT_PRESENT) { pfx = new PERS_FILE_XFER; pfx->init(fip, false); fip->pers_file_xfer = pfx; pers_file_xfers->insert(fip->pers_file_xfer); action = true; } else if (fip->uploadable() && fip->status == FILE_PRESENT && !fip->uploaded) { pfx = new PERS_FILE_XFER; pfx->init(fip, true); fip->pers_file_xfer = pfx; pers_file_xfers->insert(fip->pers_file_xfer); action = true; } } // Scan existing PERS_FILE_XFERs, looking for those that are done, // and deleting them // vector::iterator iter; iter = pers_file_xfers->pers_file_xfers.begin(); while (iter != pers_file_xfers->pers_file_xfers.end()) { pfx = *iter; // If the transfer finished, remove the PERS_FILE_XFER object // from the set and delete it // if (pfx->pers_xfer_done) { fip = pfx->fip; if (pfx->is_upload) { // file has been uploaded - delete if not sticky // if (!fip->sticky) { fip->delete_file(); } fip->uploaded = true; active_tasks.upload_notify_app(fip); } else if (fip->status >= 0) { // file transfer did not fail (non-negative status) // If this was a compressed download, rename .gzt to .gz // if (fip->download_gzipped) { char path[MAXPATHLEN], from_path[MAXPATHLEN+16], to_path[MAXPATHLEN+16]; get_pathname(fip, path, sizeof(path)); snprintf(from_path, sizeof(from_path), "%s.gzt", path); snprintf(to_path, sizeof(to_path), "%s.gz", path); boinc_rename(from_path, to_path); } // verify the file with RSA or MD5, and change permissions // retval = fip->verify_file(true, true, true); if (retval == ERR_IN_PROGRESS) { // do nothing } else if (retval) { msg_printf(fip->project, MSG_INTERNAL_ERROR, "Checksum or signature error for %s", fip->name ); fip->status = retval; } else { // Set the appropriate permissions depending on whether // it's an executable or normal file // retval = fip->set_permissions(); fip->status = FILE_PRESENT; } // if it's a user file, tell running apps to reread prefs // if (fip->is_user_file) { active_tasks.request_reread_prefs(fip->project); } // if it's a project file, make a link in project dir // if (fip->is_project_file) { PROJECT* p = fip->project; p->write_symlink_for_project_file(fip); p->update_project_files_downloaded_time(); } } iter = pers_file_xfers->pers_file_xfers.erase(iter); delete pfx; action = true; // `delete pfx' should have set pfx->fip->pfx to NULL assert (fip == NULL || fip->pers_file_xfer == NULL); } else { ++iter; } } return action; } #endif // for each FILE_INFO (i.e. each project file the client knows about) // check that the file exists and is of the right size. // Called at startup. // void CLIENT_STATE::check_file_existence() { unsigned int i; char path[MAXPATHLEN]; for (i=0; istatus < 0 && fip->downloadable()) { // file had an error; reset it so that we download again get_pathname(fip, path, sizeof(path)); msg_printf(fip->project, MSG_INFO, "Resetting file %s: %s", path, boincerror(fip->status) ); fip->reset(); continue; } if (cc_config.dont_check_file_sizes) continue; if (fip->status == FILE_PRESENT) { get_pathname(fip, path, sizeof(path)); double size; int retval = file_size(path, size); if (retval) { delete_project_owned_file(path, true); fip->status = FILE_NOT_PRESENT; msg_printf(fip->project, MSG_INFO, "File %s not found", path); } else if (fip->nbytes && (size != fip->nbytes)) { if (gstate.global_prefs.dont_verify_images && is_image_file(path)) continue; delete_project_owned_file(path, true); fip->status = FILE_NOT_PRESENT; msg_printf(fip->project, MSG_INFO, "File %s has wrong size: expected %.0f, got %.0f", path, fip->nbytes, size ); } // If an output file disappears before it's uploaded, // flag the job as an error. // if (fip->status == FILE_NOT_PRESENT && fip->uploadable() && !fip->uploaded) { RESULT* rp = file_info_to_result(fip); if (rp) { gstate.report_result_error( *rp, "output file missing or invalid" ); } } } } } // return the result that *fip is an output file of, if any // RESULT* CLIENT_STATE::file_info_to_result(FILE_INFO* fip) { unsigned int i, j; for (i=0; iproject != fip->project) continue; for (j=0; joutput_files.size(); j++) { FILE_REF& fr = rp->output_files[j]; if (fr.file_info == fip) { return rp; } } } return NULL; }