// This file is part of BOINC. // http://boinc.berkeley.edu // Copyright (C) 2023 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 . // Scheduler code for directing a client to one of several // download servers based on its time zone #include "config.h" #include #include #include #include #include "filesys.h" #include "parse.h" #include "sched_types.h" #include "sched_msgs.h" #include "sched_config.h" #include "boinc_stdio.h" typedef struct urltag { int zone; char name[124]; } URLTYPE; // these global variables are needed to pass information into the // compare function below. // static int tzone=0; static int hostid=0; // Evaluate differences between time-zone. Two time zones that differ // by almost 24 hours are actually very close on the surface of the // earth. This function finds the 'shortest way around' // static int compare(const void *x, const void *y) { const URLTYPE *a=(const URLTYPE *)x; const URLTYPE *b=(const URLTYPE *)y; char longname[512]; const int twelve_hours = 12*3600; int diffa = abs(tzone - (a->zone)); int diffb = abs(tzone - (b->zone)); if (diffa > twelve_hours) { diffa = 2*twelve_hours-diffa; } if (diffb > twelve_hours) { diffb = 2*twelve_hours-diffb; } if (diffa < diffb) { return -1; } if (diffa > diffb) { return +1; } // In order to ensure uniform distribution, we hash paths that are // equidistant from the host's timezone in a way that gives a // unique ordering for each host but which is effectively random // between hosts. // sprintf(longname, "%s%d", a->name, hostid); std::string sa = md5_string((const unsigned char *)longname, strlen((const char *)longname)); sprintf(longname, "%s%d", b->name, hostid); std::string sb = md5_string((const unsigned char *)longname, strlen((const char *)longname)); int xa = strtol(sa.substr(1, 7).c_str(), 0, 16); int xb = strtol(sb.substr(1, 7).c_str(), 0, 16); if (xaxb) { return 1; } return 0; } static URLTYPE *cached=NULL; #define BLOCKSIZE 32 URLTYPE* read_download_list() { int count=0; int i; if (cached) return cached; const char *download_servers = config.project_path("download_servers"); FILE *fp=boinc::fopen(download_servers, "r"); if (!fp) { log_messages.printf(MSG_CRITICAL, "File %s not found or unreadable!\n", download_servers ); return NULL; } // read in lines from file // while (1) { // allocate memory in blocks if ((count % BLOCKSIZE)==0) { cached=(URLTYPE *)realloc(cached, (count+BLOCKSIZE)*sizeof(URLTYPE)); if (!cached) { boinc::fclose(fp); return NULL; } } // read timezone offset and URL from file, and store in cache list // if (2==boinc::fscanf(fp, "%d %s", &(cached[count].zone), cached[count].name)) { count++; } else { // provide a null terminator so we don't need to keep // another global variable for count. // cached[count].name[0]='\0'; break; } } boinc::fclose(fp); if (!count) { log_messages.printf(MSG_CRITICAL, "File %s contained no valid entries!\n" "Format of this file is one or more lines containing:\n" "TIMEZONE_OFFSET_IN_SEC http://some.url.path\n", download_servers ); free(cached); return NULL; } // sort URLs by distance from host timezone. See compare() above // for details. qsort(cached, count, sizeof(URLTYPE), compare); log_messages.printf(MSG_DEBUG, "Sorted list of URLs follows [host timezone: UTC%+d]\n", tzone ); for (i=0; i%s%s", i?"\n ":"", serverlist[i].name, path); len += l; if (len >= lim) { *start = '\0'; return (start-buffer); } start += l; } return (start-buffer); } // returns zero on success, non-zero to indicate an error // int add_download_servers(char *old_xml, char *new_xml, int tz) { char *p, *q, *r; int total_free = BLOB_SIZE - strlen(old_xml); p = (r = old_xml); // search for next URL to do surgery on while ((q=strstr(p, ""))) { // p is at current position // q is at beginning of next "" tag char *s; char path[MAXPATHLEN]; int len = q-p; // copy everything from p to q to new_xml // strlcpy(new_xml, p, len); new_xml += len; // locate next instance of // if (!(r = strstr(q, ""))) { return 1; } r += strlen(""); // r points to the end of the whole "..." tag // parse out the URL into 'path' // if (!parse_str(q, "", path, 1024)) { return 1; } // check if path contains the string specified in config.xml // if (!(s = strstr(path,config.replace_download_url_by_timezone))) { // if it doesn't, just copy the whole tag as it is strlcpy(new_xml, q, r-q); new_xml += r-q; p=r; } else { // calculate free space available for URL replaces int lim = total_free - (len - (p - old_xml)); // find end of the specified replace string, // i.e. start of the 'path' s += strlen(config.replace_download_url_by_timezone); // insert new download list in place of the original single URL len = make_download_list(new_xml, s, lim, tz); if (len == 0) { // if the replacement would exceed the maximum XML length, // just keep the original URL len = r-q; strlcpy(new_xml, q, len); } else if (len < 0) { return 1; } new_xml += len; // advance pointer to start looking for next tag. // p=r; } } strcpy(new_xml, r); return 0; } // replace the download URL for apps with a list of // multiple download servers. // void process_av_timezone(APP_VERSION* avp, APP_VERSION& av2) { int retval; // set these global variables, needed by the compare() // function so that the download URL list can be sorted by timezone // tzone = g_reply->host.timezone; hostid = g_reply->host.id; retval = add_download_servers(avp->xml_doc, av2.xml_doc, g_reply->host.timezone); if (retval) { log_messages.printf(MSG_CRITICAL, "add_download_servers(to APP version) failed\n" ); // restore original WU! av2 = *avp; } } // replace the download URL for WU files with a list of // multiple download servers. // void process_wu_timezone( WORKUNIT& wu2, WORKUNIT& wu3 ) { int retval; tzone = g_reply->host.timezone; hostid = g_reply->host.id; retval = add_download_servers(wu2.xml_doc, wu3.xml_doc, g_reply->host.timezone); if (retval) { log_messages.printf(MSG_CRITICAL, "add_download_servers(to WU) failed\n" ); // restore original WU! wu3 = wu2; } }