// 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;
}
}