/** * Gets the current timeout value to use for the main loop * * First check to see if any active groups have an expired timeout, and * handle that timeout. Once all expired timeouts have been handled, find * the active group with the earliest timeout and return the time until that * timeout. If there are no active groups, return NULL. */ struct timeval *getrecenttimeout(void) { static struct timeval tv = {0,0}; struct timeval current_timestamp, min_timestamp; int i, found_timeout, done, sent_naks; struct group_list_t *group; unsigned int section, nak_count; unsigned char *naks; gettimeofday(¤t_timestamp, NULL); done = 0; while (!done) { found_timeout = 0; done = 1; for (i = 0; i < MAXLIST; i++) { group = &group_list[i]; if (group->group_id != 0) { if (cmptimestamp(current_timestamp, group->timeout_time) >= 0) { switch (group->phase) { case PHASE_REGISTERED: send_register(group); break; case PHASE_RECEIVING: case PHASE_MIDGROUP: glog1(group, "Transfer timed out"); send_abort(group, "Transfer timed out"); break; case PHASE_COMPLETE: send_complete(group, 0); break; } done = 0; } else if ((!found_timeout) || (cmptimestamp(group->timeout_time, min_timestamp) < 0)) { glog5(group, "found min timeout time: %d:%06d", group->timeout_time.tv_sec, group->timeout_time.tv_usec); min_timestamp = group->timeout_time; found_timeout = 1; } // Check for a NAK timeout for sending a STATUS or COMPLETE if ((group->fileinfo.nak_time.tv_sec != 0) && cmptimestamp(current_timestamp, group->fileinfo.nak_time) >= 0) { group->fileinfo.nak_time.tv_sec = 0; group->fileinfo.nak_time.tv_usec = 0; // Send NAKs sent_naks = 0; retry_naks: for (section = group->fileinfo.nak_section_first; section < group->fileinfo.nak_section_last; section++) { naks = NULL; nak_count = get_naks(group, section, &naks); glog3(group, "read %d NAKs for section %d", nak_count, section); if (nak_count > 0) { send_status(group, section, naks, nak_count); sent_naks = 1; } free(naks); naks = NULL; } if (file_done(group, 1)) { glog2(group, "File transfer complete"); send_complete(group, 0); file_cleanup(group, 0); } else if (group->fileinfo.got_done && !sent_naks) { // We didn't send any NAKs since the last time // but the server is asking for some, // so check all prior sections group->fileinfo.nak_section_last = group->fileinfo.nak_section_first; group->fileinfo.nak_section_first = 0; group->fileinfo.got_done = 0; goto retry_naks; } } else if ((group->fileinfo.nak_time.tv_sec != 0) && ((!found_timeout) || (cmptimestamp(group->fileinfo.nak_time, min_timestamp) < 0))) { glog5(group, "found min nak time: %d:%06d", group->fileinfo.nak_time.tv_sec, group->fileinfo.nak_time.tv_usec); min_timestamp = group->fileinfo.nak_time; found_timeout = 1; } // Check congestion control feedback timer if (!group->isclr) { if ((group->cc_time.tv_sec != 0) && (cmptimestamp(current_timestamp, group->cc_time) >= 0)) { send_cc_ack(group); } else if ((group->cc_time.tv_sec != 0) && ((!found_timeout) || (cmptimestamp(group->cc_time, min_timestamp) < 0))) { glog5(group, "found min CC time: %d:%06d", group->cc_time.tv_sec, group->cc_time.tv_usec); min_timestamp = group->cc_time; found_timeout = 1; } } } } // Check timeout for proxy key request if (has_proxy && (proxy_pubkey.key == 0)) { if (cmptimestamp(current_timestamp, next_keyreq_time) >= 0) { send_key_req(); done = 0; } else if ((!found_timeout) || (cmptimestamp(next_keyreq_time, min_timestamp) < 0)) { min_timestamp = next_keyreq_time; found_timeout = 1; } } // Check timeout for sending heartbeat if (hbhost_count) { if (cmptimestamp(current_timestamp, next_hb_time) >= 0) { send_hb_request(listener, hb_hosts, hbhost_count, &next_hb_time, hb_interval, uid); done = 0; } else if ((!found_timeout) || (cmptimestamp(next_hb_time, min_timestamp) < 0)) { min_timestamp = next_hb_time; found_timeout = 1; } } } if (found_timeout) { tv = diff_timeval(min_timestamp, current_timestamp); return &tv; } else { return NULL; } }
/** * Performs the Transfer phase for a particular file * Returns 1 if at least one client finished, 0 if all are dropped or aborted */ int transfer_phase(struct finfo_t *finfo) { unsigned char *packet, *encpacket, *data; char path[MAXPATHNAME]; struct uftp_h *header; struct fileseg_h *fileseg; int max_time, alldone, numbytes, sent_blocks, current_naks; unsigned int pass, section, numnaks, block; struct timeval start_time, last_sent, current_sent; int64_t avgwait, waitcnt, overage, tdiff; f_offset_t offset, curr_offset; int file, i; if (finfo->file_id != 0) { // First check to see if all clients are already done for this file. // This can happen on a restart when the file finished on the // last attempt and responded to the FILEINFO with a COMPLETE for (i = 0, alldone = 1; (i < destcount) && alldone; i++) { alldone = alldone && ((destlist[i].status == DEST_DONE) || (client_error(i)) || (destlist[i].clientcnt != -1)); } if (alldone) { gettimeofday(&start_time, NULL); print_status(finfo, start_time); return 1; } } // If rate is -1, use 100Mbps for purpose of calculating max time max_time = (int)floor(((double)weight / 100) * ((double)finfo->size / (((rate == -1) ? 100000 : rate) / 8) / 1024)); if (max_time < min_time) { max_time = min_time; } if ((finfo->file_id != 0) && (finfo->ftype == FTYPE_REG)) { log(0, 0, "Maximum file transfer time: %d seconds", max_time); snprintf(path, sizeof(path), "%s%c%s", finfo->basedir, PATH_SEP, finfo->filename); if ((file = open(path, OPENREAD, 0)) == -1) { syserror(0, 0, "Error opening file"); return 1; } } else { // At end of group, all non-errored client are DEST_DONE from the // last file, so reset them to DEST_ACTIVE to get the final COMPLETE. for (i = 0; i < destcount; i++) { if (!client_error(i)) { destlist[i].status = DEST_ACTIVE; } } } packet = calloc(mtu, 1); encpacket = calloc(mtu, 1); if ((packet == NULL) || (encpacket == NULL)) { syserror(0, 0, "calloc failed!"); exit(1); } header = (struct uftp_h *)packet; fileseg = (struct fileseg_h *)(packet + sizeof(struct uftp_h)); data = (unsigned char *)fileseg + sizeof(struct fileseg_h); set_uftp_header(header, FILESEG, finfo, &receive_dest); pass = 1; alldone = 0; gettimeofday(&start_time, NULL); do { avgwait = 0; waitcnt = 0; numnaks = 0; overage = 0; section = 1; curr_offset = 0; sent_blocks = 0; gettimeofday(&last_sent, NULL); if (finfo->file_id != 0) { log(0, 0, "Sending file...pass %d", pass); lseek_func(file, 0, SEEK_SET); } else { log(0, 0, "Finishing group"); } fileseg->func = FILESEG; fileseg->file_id = htons(finfo->file_id); fileseg->pass = pass; fileseg->section = htons(section); for (block = 0; block < finfo->blocks; block++) { // If all clients received this file partially on a prior attempt // and it's the first pass, request NAKs for all sections // right away and don't send any data packets. if (((pass == 1) || finfo->naklist[block]) && !((pass == 1) && finfo->partial)) { if (diff_sec(last_sent, start_time) > max_time) { log0(0, 0, "Max file transfer time exceeded"); send_abort(finfo, "Max file transfer time exceeded", &receive_dest, NULL, (keytype != KEY_NONE), !quit_on_error); alldone = 1; for (i = 0; i < destcount; i++) { if (quit_on_error || ((destlist[i].status == DEST_ACTIVE) && destlist[i].clientcnt == -1 )) { destlist[i].status = DEST_ABORT; } } break; } // On the first pass, go straight through the file. // On later passes, seek to the next packet. if (pass != 1) { log4(0, 0, "Resending %d", block); if (!seek_block(file, block, &offset, curr_offset)) { continue; } } if ((numbytes = read(file, data, blocksize)) == -1) { syserror(0, 0, "read failed"); continue; } if (pass != 1) { curr_offset = offset + numbytes; } // Keep track of how long we really slept compared to how // long we expected to sleep. If we went over, subtract the // time over from the next sleep time. This way we maintain // the proper average sleep time. This can result in multiple // packets going out at once, potentially losing packets. if (packet_wait > overage) { usleep(packet_wait - (int32_t)overage); } gettimeofday(¤t_sent, NULL); tdiff = diff_usec(current_sent, last_sent); avgwait += tdiff; waitcnt++; if (packet_wait) overage += tdiff - packet_wait; last_sent = current_sent; fileseg->seq_num = htonl(block); send_data(finfo, packet, numbytes, encpacket); sent_blocks++; } if ((block % (blocksize * 8) == (blocksize * 8) - 1) || (block == finfo->blocks - 1)) { if ((pass == 1) || sent_blocks) { current_naks = get_naks(finfo, pass, section, &alldone); numnaks += current_naks; if ((rate != -1) && (cc_count > 0)) { if (!read_cc_config(cc_config)) { log1(0, 0, "Error rereading congestion control " "config, using prior values"); } adjust_rate(current_naks, sent_blocks); } overage = 0; if (alldone) break; } sent_blocks = 0; gettimeofday(&last_sent, NULL); fileseg->section = htons(++section); } } if ((finfo->size == 0) && !alldone) { // If it's the end of the group, or an empty file, a DONE was // never sent, so send it now numnaks += get_naks(finfo, pass, section, &alldone); } if (finfo->file_id != 0) { log(0, 0, "Average wait time = %.2f us", (waitcnt == 0) ? 0 : (float)avgwait / waitcnt); log(0, 0, "Received %d distinct NAKs for pass %d", numnaks, pass); } pass++; } while (!alldone); if ((finfo->file_id != 0) && (finfo->ftype == FTYPE_REG)) { close(file); } print_status(finfo, start_time); free(packet); free(encpacket); for (i = 0; i < destcount; i++) { if (quit_on_error) { // Check to see that all finished if ((destlist[i].status != DEST_DONE) && (destlist[i].clientcnt == -1)) { return 0; } } else { // Check to see if at least one finished if (destlist[i].status == DEST_DONE) { return 1; } } } if (quit_on_error) { return 1; } else { return 0; } }