boinc/lib/util.C

507 lines
14 KiB
C
Executable File

// The contents of this file are subject to the BOINC Public License
// Version 1.0 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
// http://boinc.berkeley.edu/license_1.0.txt
//
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// License for the specific language governing rights and limitations
// under the License.
//
// The Original Code is the Berkeley Open Infrastructure for Network Computing.
//
// The Initial Developer of the Original Code is the SETI@home project.
// Portions created by the SETI@home project are Copyright (C) 2002
// University of California at Berkeley. All Rights Reserved.
//
// Contributor(s):
//
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <cctype>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifdef HAVE_SYS_FILE_H
#include <sys/file.h>
#endif
#include <algorithm>
#include <fstream>
using std::ifstream;
#ifndef _WIN32
using std::min;
#endif
#ifdef _WIN32
#include <time.h>
#include <afxwin.h>
#else
#include <sys/time.h>
#include <unistd.h>
#endif
#include "error_numbers.h"
#include "util.h"
// Converts a double precision time (where the value of 1 represents
// a day) into a string. smallest_timescale determines the smallest
// unit of time division used
// smallest_timescale: 0=seconds, 1=minutes, 2=hours, 3=days, 4=years
//
int double_to_ydhms (double x, int smallest_timescale, char *buf) {
double years, days, hours, minutes, seconds;
char year_buf[64], day_buf[16], hour_buf[16], min_buf[16], sec_buf[16];
if (x < 0 || buf == NULL) return -1;
years = x / 365.25;
days = fmod(x, 365.25);
hours = fmod(x*24, 24);
minutes = fmod(x*24*60, 60);
seconds = fmod(x*24*60*60, 60);
if (smallest_timescale==4) {
sprintf( year_buf, "%.3f yr ", years );
} else if (years > 1 && smallest_timescale < 4) {
sprintf( year_buf, "%d yr ", (int)years );
} else {
strcpy( year_buf, "" );
}
if (smallest_timescale==3) {
sprintf( day_buf, "%.2f day%s ", days, (days>1?"s":"") );
} else if (days > 1 && smallest_timescale < 3) {
sprintf( day_buf, "%d day%s ", (int)days, (days>1?"s":"") );
} else {
strcpy( day_buf, "" );
}
if (smallest_timescale==2) {
sprintf( hour_buf, "%.2f hr ", hours );
} else if (hours > 1 && smallest_timescale < 2) {
sprintf( hour_buf, "%d hr ", (int)hours );
} else {
strcpy( hour_buf, "" );
}
if (smallest_timescale==1) {
sprintf( min_buf, "%.2f min ", minutes );
} else if (minutes > 1 && smallest_timescale < 1) {
sprintf( min_buf, "%d min ", (int)minutes );
} else {
strcpy( min_buf, "" );
}
if (smallest_timescale==0) {
sprintf( sec_buf, "%.2f sec ", seconds );
} else if (seconds > 1 && smallest_timescale < 0) {
sprintf( sec_buf, "%d sec ", (int)seconds );
} else {
strcpy( sec_buf, "" );
}
// the "-0.05" below is to prevent it from printing 60.0 sec
// when the real value is e.g. 59.91
//
sprintf(buf, "%s%s%s%s%s", year_buf, day_buf, hour_buf, min_buf, sec_buf);
return 0;
}
// Convert nbytes into a string. If total_bytes is non-zero,
// convert the two into a fractional display (i.e. 4/16 KB)
//
void get_byte_string(double nbytes, double total_bytes, char* str, int len) {
char buf[256];
double xTera = (1024.0*1024.0*1024.0*1024.0);
double xGiga = (1024.0*1024.0*1024.0);
double xMega = (1024.0*1024.0);
double xKilo = (1024.0);
if (total_bytes != 0) {
if (total_bytes >= xTera) {
sprintf(buf, "%0.2f/%0.2f TB", nbytes/xTera, total_bytes/xTera);
} else if (total_bytes >= xGiga) {
sprintf(buf, "%0.2f/%0.2f GB", nbytes/xGiga, total_bytes/xGiga);
} else if (total_bytes >= xMega) {
sprintf(buf, "%0.2f/%0.2f MB", nbytes/xMega, total_bytes/xMega);
} else if (total_bytes >= xKilo) {
sprintf(buf, "%0.2f/%0.2f KB", nbytes/xKilo, total_bytes/xKilo);
} else {
sprintf(buf, "%0.0f/%0.0f bytes", nbytes, total_bytes);
}
} else {
if (nbytes >= xTera) {
sprintf(buf, "%0.2f TB", nbytes/xTera);
} else if (nbytes >= xGiga) {
sprintf(buf, "%0.2f GB", nbytes/xGiga);
} else if (nbytes >= xMega) {
sprintf(buf, "%0.2f MB", nbytes/xMega);
} else if (nbytes >= xKilo) {
sprintf(buf, "%0.2f KB", nbytes/xKilo);
} else {
sprintf(buf, "%0.0f bytes", nbytes);
}
}
safe_strncpy(str, buf, len);
}
// return time of day as a double
// Not necessarily in terms of UNIX time (especially on Windows)
//
double dtime() {
#ifdef _WIN32
LARGE_INTEGER time;
FILETIME sysTime;
GetSystemTimeAsFileTime(&sysTime);
time.LowPart = sysTime.dwLowDateTime;
time.HighPart = sysTime.dwHighDateTime; // Time is in 100 ns units
return (double)time.QuadPart/10000000; // Convert to 1 s units
#else
struct timeval tv;
gettimeofday(&tv, 0);
return tv.tv_sec + (tv.tv_usec/1.e6);
#endif
}
// sleep for a specified number of seconds
//
void boinc_sleep(double seconds) {
#ifdef _WIN32
::Sleep((int)(1000*seconds));
#else
sleep((int)seconds);
int x = (int)fmod(seconds*1000000,1000000);
if (x) usleep(x);
#endif
}
// take a string containing some space separated words.
// return an array of pointers to the null-terminated words.
// Modifies the string arg.
// Returns argc
// TODO: use strtok here
#define NOT_IN_TOKEN 0
#define IN_SINGLE_QUOTED_TOKEN 1
#define IN_DOUBLE_QUOTED_TOKEN 2
#define IN_UNQUOTED_TOKEN 3
int parse_command_line(char* p, char** argv) {
int state = NOT_IN_TOKEN;
int argc=0;
while (*p) {
switch(state) {
case NOT_IN_TOKEN:
if (isspace(*p)) {
} else if (*p == '\'') {
p++;
argv[argc++] = p;
state = IN_SINGLE_QUOTED_TOKEN;
break;
} else if (*p == '\"') {
p++;
argv[argc++] = p;
state = IN_DOUBLE_QUOTED_TOKEN;
break;
} else {
argv[argc++] = p;
state = IN_UNQUOTED_TOKEN;
}
break;
case IN_SINGLE_QUOTED_TOKEN:
if (*p == '\'') {
*p = 0;
state = NOT_IN_TOKEN;
}
break;
case IN_DOUBLE_QUOTED_TOKEN:
if (*p == '\"') {
*p = 0;
state = NOT_IN_TOKEN;
}
break;
case IN_UNQUOTED_TOKEN:
if (isspace(*p)) {
*p = 0;
state = NOT_IN_TOKEN;
}
break;
}
p++;
}
argv[argc] = 0;
return argc;
}
int lock_file(char* filename) {
int retval;
// some systems have both!
#ifdef HAVE_FLOCK
int lock = open(filename, O_WRONLY|O_CREAT, 0644);
retval = flock(lock, LOCK_EX|LOCK_NB);
#elif HAVE_LOCKF
int lock = open(filename, O_WRONLY|O_CREAT, 0644);
retval = lockf(lock, F_TLOCK, 1);
// must leave fd open
#endif
#ifdef _WIN32
HANDLE hfile = CreateFile(
filename, GENERIC_WRITE,
0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0
);
if (hfile == INVALID_HANDLE_VALUE) retval = 1;
else retval = 0;
#endif
return retval;
}
static char x2c(char *what) {
register char digit;
digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
digit *= 16;
digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));
return(digit);
}
void c2x(char *what) {
char buf[3];
char num = atoi(what);
char d1 = num / 16;
char d2 = num % 16;
int abase1, abase2;
if (d1 < 10) abase1 = 48;
else abase1 = 55;
if (d2 < 10) abase2 = 48;
else abase2 = 55;
buf[0] = d1+abase1;
buf[1] = d2+abase2;
buf[2] = 0;
strcpy(what, buf);
}
void strip_whitespace(char *str) {
int read_pos=0, write_pos=0;
while (str[read_pos]) {
if (!isspace(str[read_pos])) {
str[write_pos++] = str[read_pos];
}
read_pos++;
}
str[write_pos] = 0;
}
void unescape_url(char *url) {
register int x,y;
for(x=0,y=0;url[y];++x,++y) {
if((url[x] = url[y]) == '%') {
url[x] = x2c(&url[y+1]);
y+=2;
}
}
url[x] = '\0';
}
void escape_url(char *in, char*out) {
int x, y;
for (x=0, y=0; in[x]; ++x) {
if (isalnum(in[x])) {
out[y] = in[x];
++y;
} else {
out[y] = '%';
++y;
out[y] = 0;
char buf[256];
sprintf(buf, "%d", (char)in[x]);
c2x(buf);
strcat(out, buf);
y += 2;
}
}
out[y] = 0;
}
// Escape a URL for the project directory, cutting off the "http://",
// converting '\' '/' and ' ' to '_',
// and converting the non alphanumeric characters to %XY
// where XY is their hexadecimal equivalent
//
void escape_url_readable(char *in, char* out) {
int x, y;
char *temp;
temp = strstr(in,"://");
if (temp) {
in = temp + strlen("://");
}
for (x=0, y=0; in[x]; ++x) {
if (isalnum(in[x]) || in[x]=='.' || in[x]=='-' || in[x]=='_') {
out[y] = in[x];
++y;
} else {
out[y] = '_';
++y;
}
}
out[y] = 0;
}
inline void replace_string(
string& s, string const& src,
string const& dest, string::size_type start=0
) {
string::size_type p;
while ( (p=s.find(src, start)) != string::npos ) {
s.replace(p, src.length(), dest);
start = p + dest.length();
}
}
// Canonicalize a master url.
// - Convert the first part of a URL (before the "://") to lowercase
// - Remove double slashes
// - Add a trailing slash if necessary
//
void canonicalize_master_url(string& url) {
string::size_type p = url.find("://");
// lowercase http://
if (p != string::npos) {
std::transform(url.begin(), url.begin()+p, url.begin(), tolower);
p += 3;
} else {
p = 0;
}
// remove double slashes
replace_string(url, "//", "/", p);
// ensure trailing slash
if (url[url.length()-1] != '/') {
url += '/';
}
}
void canonicalize_master_url(char *xurl) {
string url = xurl;
canonicalize_master_url(url);
strcpy(xurl, url.c_str());
}
void safe_strncpy(char* dst, const char* src, int len) {
strncpy(dst, src, len);
dst[len-1]=0;
}
// return current time of day as ASCII string, no CR
//
char* timestamp() {
// time_t now = time(0);
// char* p = ctime(&now);
// *(strchr(p, '\n')) = 0;
// return p;
static char buf[100];
time_t now = time(0);
struct tm* tm = localtime(&now);
strftime(buf, sizeof(buf)-1, "%Y-%m-%d %H:%M:%S", tm);
return buf;
}
// set by command line
bool debug_fake_exponential_backoff = false;
double debug_total_exponential_backoff = 0;
static int count_debug_fake_exponential_backoff = 0;
static const int max_debug_fake_exponential_backoff = 1000; // safety limit
// return a random integer in the range [MIN,min(e^n,MAX))
int calculate_exponential_backoff(
const char* debug_descr, int n, double MIN, double MAX,
double factor /* = 1.0 */
) {
double rmax = min(MAX, factor*exp((double)n));
if (debug_fake_exponential_backoff) {
// For debugging/testing purposes, fake exponential back-off by
// returning 0 seconds; report arguments so we can tell what we would
// have done (this doesn't test the rand_range() functions but is
// very useful for testing backoff/retry policies).
//
double expected_backoff = (MIN > rmax) ? MIN : (rmax-MIN)/2.0;
debug_total_exponential_backoff += expected_backoff;
++count_debug_fake_exponential_backoff;
fprintf(
stderr,
"## calculate_exponential_backoff(): #%5d descr=\"%s\", n=%d, MIN=%.1f, MAX=%.1f, factor=%.1f; rand_range [%.1f,%.1f); total expected backoff=%.1f\n",
count_debug_fake_exponential_backoff,
debug_descr, n, MIN, MAX, factor,
MIN, rmax, debug_total_exponential_backoff
);
if (count_debug_fake_exponential_backoff >= max_debug_fake_exponential_backoff) {
fprintf(
stderr,
"## calculate_exponential_backoff(): reached max_debug_fake_exponential_backoff\n"
);
exit(1);
}
return 0;
}
return (int) rand_range(MIN, rmax);
}
string timediff_format(long tdiff) {
char buf[256];
int sex = tdiff % 60;
tdiff /= 60;
if (!tdiff) {
sprintf(buf, "%d seconds", sex);
return buf;
}
int min = tdiff % 60;
tdiff /= 60;
if (!tdiff) {
sprintf(buf, "%d minutes and %d seconds", min, sex);
return buf;
}
int hours = tdiff % 24;
tdiff /= 24;
if (!tdiff) {
sprintf(buf, "%d hours, %d minutes, and %d seconds", hours, min, sex);
return buf;
}
int days = tdiff % 7;
tdiff /= 7;
if (!tdiff) {
sprintf(buf, "%d days, %d hours, %d minutes, and %d seconds", days, hours, min, sex);
return buf;
}
sprintf(buf, "%d weeks, %d days, %d hours, %d minutes, and %d seconds", (int)tdiff, days, hours, min, sex);
return buf;
}
// read entire file into string
int read_file_string(const char* pathname, string& result) {
result.erase();
ifstream f(pathname);
if (!f) return -1;
char c;
while (f.get(c)) result += c;
return 0;
}