mirror of https://github.com/BOINC/boinc.git
606 lines
19 KiB
C++
606 lines
19 KiB
C++
|
|
#ifdef _WIN32
|
|
#include "boinc_win.h"
|
|
#include "win_util.h"
|
|
#else
|
|
#include <stdio.h>
|
|
#endif
|
|
|
|
#include <iostream>
|
|
#include <fstream>
|
|
|
|
|
|
#include "filesys.h"
|
|
#include "str_replace.h"
|
|
#include "util.h"
|
|
#include "parse.h"
|
|
#include "gbac.h"
|
|
#include "boinc_api.h"
|
|
|
|
#define GBAC_EXEC_LOG "gbac-exec.log"
|
|
#define GBAC_VAUNZIP_STATUS "gbac-va-unzipped.status"
|
|
|
|
static const char* dc_files[] =
|
|
{
|
|
"dc_stdout.txt",
|
|
"dc_stderr.txt",
|
|
"dc_clientlog.txt",
|
|
"dc_ckpt_out",
|
|
NULL
|
|
};
|
|
|
|
static const char* log_files[] =
|
|
{
|
|
"shared/guest-tools-exec.log",
|
|
"shared/gbac-app.stdout",
|
|
"shared/gbac-app.stderr",
|
|
"shared/gbac_job.xml",
|
|
"shared/gbac_exit_status",
|
|
"shared/gbac_command_line.xml",
|
|
"shared/stdout.txt",
|
|
"shared/stderr.txt",
|
|
"shared/gbac-execution.stdout",
|
|
"shared/gbac-execution.stderr",
|
|
"shared/shared-dir-contents.log",
|
|
"shared/dc_clientlog.txt",
|
|
"shared/dc_stdout.txt",
|
|
"shared/dc_stderr.txt",
|
|
NULL
|
|
};
|
|
|
|
|
|
GBAC gbac;
|
|
|
|
|
|
GBAC::GBAC()
|
|
{
|
|
this->environment.clear();
|
|
}
|
|
|
|
|
|
GBAC::~GBAC()
|
|
{
|
|
free(this->hostdir);
|
|
free(this->dirsep);
|
|
}
|
|
|
|
|
|
int GBAC::init(int argc_, char **argv_)
|
|
{
|
|
char resolved_buffer[2048];
|
|
char msg_buf[256];
|
|
|
|
this->argc = argc_;
|
|
this->argv = argv_;
|
|
|
|
this->hostdir = strdup("shared");
|
|
if (this->hostdir == NULL)
|
|
return EXIT_FAILURE;
|
|
|
|
#ifdef _WIN32
|
|
this->dirsep = strdup("\\");
|
|
#else
|
|
this->dirsep = strdup("/");
|
|
#endif
|
|
if (this->dirsep == NULL)
|
|
return EXIT_FAILURE;
|
|
|
|
// need to create various files expected by DC-API
|
|
// in case the application fails, DC-API still expects them
|
|
FILE* f;
|
|
for (int i=0; dc_files[i] != NULL; i++)
|
|
{
|
|
boinc_resolve_filename(dc_files[i], resolved_buffer,
|
|
sizeof(resolved_buffer));
|
|
|
|
|
|
f = fopen(resolved_buffer, "w");
|
|
if (f) {
|
|
fclose(f);
|
|
} else {
|
|
fprintf(stderr,
|
|
"%s failed to create DC-API file '%s'\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)),
|
|
dc_files[i]);
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int GBAC::prepareHostSharedDir()
|
|
{
|
|
DIRREF mydir;
|
|
char msg_buf[256];
|
|
char buffer[1024];
|
|
char dest_buffer[2048];
|
|
char resolved_buffer[2048];
|
|
|
|
if (!boinc_file_exists(this->hostdir))
|
|
{
|
|
fprintf(stderr,
|
|
"%s creating host shared directory.\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)));
|
|
if (boinc_mkdir(this->hostdir) != 0)
|
|
{
|
|
fprintf(stderr,
|
|
"%s could not create host shared directory"\
|
|
": %s\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)),
|
|
this->hostdir);
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
else if (is_file(this->hostdir))
|
|
{
|
|
fprintf(stderr,
|
|
"%s there is a file with the same name as "\
|
|
"the host share dir in the slot directory.\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)));
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
fprintf(stderr,
|
|
"%s === copying files to host directory ===\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)));
|
|
mydir = dir_open(".");
|
|
while (dir_scan(buffer, mydir, sizeof(buffer))==0)
|
|
{
|
|
if (strstr(buffer, ".vdi") == NULL &&
|
|
strcmp(buffer, "vbox_job.xml") &&
|
|
strcmp(buffer, "boinc_lockfile") &&
|
|
strcmp(buffer, "stderr.txt") &&
|
|
strcmp(buffer, "boinc_finish_called") &&
|
|
// strcmp(buffer, "gbac_job.xml") &&
|
|
strcmp(buffer, "init_data.xml") &&
|
|
strcmp(buffer, "boinc_task_state.xml") &&
|
|
strcmp(buffer, "vbox_checkpoint.txt") &&
|
|
strcmp(buffer, this->argv[0]) &&
|
|
!is_dir(buffer))
|
|
{
|
|
if (boinc_resolve_filename(buffer,
|
|
resolved_buffer,
|
|
sizeof(resolved_buffer)) != 0)
|
|
{
|
|
fprintf(stderr,
|
|
"%s cannot resolve filename: '%s'",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)),
|
|
buffer);
|
|
continue;
|
|
}
|
|
|
|
snprintf(dest_buffer, sizeof(dest_buffer),
|
|
"%s%s%s",
|
|
this->hostdir, this->dirsep, buffer);
|
|
boinc_copy(resolved_buffer, dest_buffer);
|
|
fprintf(stderr,
|
|
"%s '%s' -> '%s'\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)),
|
|
resolved_buffer, dest_buffer);
|
|
}
|
|
}
|
|
dir_close(mydir);
|
|
|
|
// create gbac_command_line.xml file
|
|
int i;
|
|
char arg_buffer[4096];
|
|
FILE *cl_file;
|
|
char cl_file_buffer[1024];
|
|
|
|
string arguments = "";
|
|
|
|
memset(arg_buffer, 0, sizeof(arg_buffer));
|
|
|
|
for (i=1; i<this->argc; i++)
|
|
{
|
|
arguments.append(" ");
|
|
arguments.append(this->argv[i]);
|
|
}
|
|
|
|
fprintf(stderr,
|
|
"%s command line is: '%s'\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)),
|
|
arguments.c_str());
|
|
snprintf(arg_buffer, sizeof(arg_buffer),
|
|
"<gbac_command_line>\n"\
|
|
" <command_line>%s</command_line>\n"\
|
|
"</gbac_command_line>\n",
|
|
arguments.c_str());
|
|
snprintf(cl_file_buffer, sizeof(cl_file_buffer), "%s%s%s",
|
|
this->hostdir, this->dirsep, "gbac_command_line.xml");
|
|
|
|
cl_file = fopen(cl_file_buffer, "w+");
|
|
if (cl_file == NULL)
|
|
{
|
|
fprintf(stderr,
|
|
"%s cannot create command line file: '%s'\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)),
|
|
cl_file_buffer);
|
|
return EXIT_FAILURE;
|
|
}
|
|
fputs(arg_buffer, cl_file);
|
|
fclose(cl_file);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int GBAC::copyOutputFiles()
|
|
{
|
|
DIRREF mydir;
|
|
char msg_buf[256];
|
|
char buffer[1024];
|
|
char src_buffer[2048];
|
|
char resolved_buffer[2048];
|
|
|
|
if (!boinc_file_exists(this->hostdir))
|
|
{
|
|
fprintf(stderr,
|
|
"%s host shared directory does not exist.\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)));
|
|
return EXIT_FAILURE;
|
|
}
|
|
fprintf(stderr,
|
|
"%s === copying files back to project/slot directory ===\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)));
|
|
|
|
mydir = dir_open(this->hostdir);
|
|
while (dir_scan(buffer, mydir, sizeof(buffer))==0)
|
|
{
|
|
if (strcmp(buffer, "boinc_finish_called") &&
|
|
strcmp(buffer, "boinc_lockfile") &&
|
|
strcmp(buffer, "stderr.txt") &&
|
|
strcmp(buffer, "stdout.txt") &&
|
|
strcmp(buffer, "gbac-execution.log") &&
|
|
strcmp(buffer, "gbac-execution.stderr") &&
|
|
strcmp(buffer, "gbac-execution.stdout") &&
|
|
strcmp(buffer, "gbac_command_line.xml") &&
|
|
strcmp(buffer, "gbac_exit_status") &&
|
|
strcmp(buffer, "gbac_job.xml"))
|
|
{
|
|
snprintf(src_buffer, sizeof(src_buffer),
|
|
"%s%s%s",
|
|
this->hostdir, this->dirsep, buffer);
|
|
if (is_dir(src_buffer))
|
|
continue;
|
|
if (boinc_resolve_filename(buffer,
|
|
resolved_buffer,
|
|
sizeof(resolved_buffer)) != 0)
|
|
{
|
|
printf("cannot resolve '%s'\n", buffer);
|
|
continue;
|
|
}
|
|
boinc_copy(src_buffer, resolved_buffer);
|
|
fprintf(stderr,
|
|
"%s '%s' -> '%s'\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)),
|
|
src_buffer, resolved_buffer);
|
|
}
|
|
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int GBAC::copyLogFiles()
|
|
{
|
|
ofstream output_file;
|
|
ifstream input_file;
|
|
string line;
|
|
char msg_buf[8192];
|
|
|
|
fprintf(stderr,
|
|
"%s Appending all output files to this logfile:\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)));
|
|
|
|
for (int i=0; log_files[i] != NULL; i++)
|
|
{
|
|
input_file.open(log_files[i], ios::in | ios::binary);
|
|
if (!input_file.is_open())
|
|
{
|
|
// for gbac_job.xml we give just a notice
|
|
if (!strcmp(log_files[i], "shared/gbac_job.xml"))
|
|
{
|
|
fprintf(stderr,
|
|
"%s NOTICE: Cannot open output file '%s' "
|
|
"for reading\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)),
|
|
log_files[i]);
|
|
} else {
|
|
fprintf(stderr,
|
|
"%s Cannot open output file '%s' for reading\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)),
|
|
log_files[i]);
|
|
}
|
|
continue;
|
|
}
|
|
fprintf(stderr,
|
|
"%s >>>>>>>>>> %s starts here >>>>>>>>>>\n\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)),
|
|
log_files[i]);
|
|
output_file.open("stderr.txt", ios::out | ios::app);
|
|
if (!output_file.is_open())
|
|
{
|
|
fprintf(stderr,
|
|
"%s Cannot open output file 'stderr.txt' for writing\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)));
|
|
continue;
|
|
}
|
|
output_file.flush();
|
|
output_file.seekp(0, ios_base::end);
|
|
|
|
while (input_file.good())
|
|
{
|
|
getline(input_file, line);
|
|
output_file << " > " << line << endl;
|
|
}
|
|
input_file.close();
|
|
output_file.flush();
|
|
output_file.close();
|
|
fprintf(stderr,
|
|
"\n%s <<<<<<<<<< %s ends here <<<<<<<<<<\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)),
|
|
log_files[i]);
|
|
}
|
|
|
|
fprintf(stderr,
|
|
"%s Done appending all output files to this logfile.\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)));
|
|
|
|
//
|
|
// check if stderr.txt and stdout.txt are requested as output files, and copy
|
|
// the content of the gbac-app.stdout and gbac-app.stderr local files
|
|
// (in the slot directory) to the output files (in the project directory).
|
|
//
|
|
// These files contain the stdout and stderr of the application run in the VM.
|
|
std::string file_stderr;
|
|
std::string file_stdout;
|
|
|
|
boinc_resolve_filename_s(STDERR_FILE, file_stderr);
|
|
boinc_resolve_filename_s(STDOUT_FILE, file_stdout);
|
|
|
|
int cmpresult = 0;
|
|
cmpresult = file_stdout.compare(STDOUT_FILE);
|
|
if (cmpresult != 0) {
|
|
fprintf(stderr, "%s '%s' is requested as output file, copying "
|
|
"contents of standard output of the application "
|
|
"(gbac-app.stdout) to this file.\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)), STDOUT_FILE);
|
|
std::ifstream ifs("gbac-app.stdout", std::ios::binary);
|
|
std::ofstream ofs(file_stdout.c_str(), std::ios::binary);
|
|
ofs << ifs.rdbuf();
|
|
ifs.close();
|
|
ofs.close();
|
|
} else {
|
|
fprintf(stderr, "%s '%s' is NOT requested as output file.\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)), STDOUT_FILE);
|
|
fprintf(stderr, "%s ('%s'.compare('%s') == %d)\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)), STDOUT_FILE,
|
|
file_stdout.c_str(), cmpresult);
|
|
}
|
|
cmpresult = file_stderr.compare(STDERR_FILE);
|
|
if (cmpresult != 0) {
|
|
fprintf(stderr, "%s '%s' is requested as output file, copying "
|
|
"contents of standard error of the application "
|
|
"(gbac-app.stderr) to this file.\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)), STDERR_FILE);
|
|
std::ifstream ifs("gbac-app.stderr", std::ios::binary);
|
|
std::ofstream ofs(file_stderr.c_str(), std::ios::binary);
|
|
ofs << ifs.rdbuf();
|
|
ifs.close();
|
|
ofs.close();
|
|
} else {
|
|
fprintf(stderr, "%s '%s' is NOT requested as output file.\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)), STDERR_FILE);
|
|
fprintf(stderr, "%s ('%s'.compare('%s') == %d)\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)), STDERR_FILE,
|
|
file_stderr.c_str(), cmpresult);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int GBAC::copyDebugLog()
|
|
{
|
|
// Copy the contents of the BOINC stderr to a special log file
|
|
// (GBAC_EXEC_LOG) if requested. This serves better debugging
|
|
// when submitting jobs from other DCI's, e.g., gLite.
|
|
//
|
|
// This method should be called right before boinc_finish().
|
|
|
|
std::string file_debuglog;
|
|
char msg_buf[8192];
|
|
|
|
boinc_resolve_filename_s(GBAC_EXEC_LOG, file_debuglog);
|
|
int cmpresult = 0;
|
|
cmpresult = file_debuglog.compare(GBAC_EXEC_LOG);
|
|
if (cmpresult != 0) {
|
|
fprintf(stderr, "%s '%s' is requested as output file, copying "
|
|
"contents of standard error of BOINC (contains all logs) "
|
|
"to this file.\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)), GBAC_EXEC_LOG);
|
|
fflush(stderr);
|
|
std::ifstream ifs(STDERR_FILE, std::ios::binary);
|
|
std::ofstream ofs(file_debuglog.c_str(), std::ios::binary);
|
|
ofs << ifs.rdbuf();
|
|
ifs.close();
|
|
ofs.close();
|
|
} else {
|
|
fprintf(stderr, "%s '%s' is NOT requested as output file.\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)), STDERR_FILE);
|
|
fprintf(stderr, "%s ('%s'.compare('%s') == %d)\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)), STDERR_FILE,
|
|
file_debuglog.c_str(), cmpresult);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int GBAC::parse(const char* file)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|
|
int GBAC::getExitStatus(int &status)
|
|
{
|
|
ifstream input_file;
|
|
string line;
|
|
char msg_buf[1024];
|
|
|
|
input_file.open("shared/gbac_exit_status", ios::in | ios::binary);
|
|
if (input_file.is_open())
|
|
{
|
|
getline(input_file, line);
|
|
fprintf(stderr,
|
|
"%s getExitStatus(): Content of shared/gbac_exit_status "
|
|
"is '%s'.\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)),
|
|
line.c_str());
|
|
input_file.close();
|
|
status = strtol(line.c_str(), 0, 0);
|
|
if (errno == ERANGE)
|
|
{
|
|
fprintf(stderr,
|
|
"%s getExitStatus(): Cannot convert value '%s'.\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)),
|
|
line.c_str());
|
|
return 1;
|
|
}
|
|
} else {
|
|
fprintf(stderr,
|
|
"%s getExitStatus(): Cannot open 'shared/gbac_exit_status' "
|
|
"for reading.\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)));
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int GBAC::doGunzip(const char* strGZ, const char* strInput, bool bKeep) {
|
|
unsigned char buf[1024];
|
|
char msg_buf[MSG_CHANNEL_SIZE];
|
|
long lRead = 0,
|
|
lWrite = 0;
|
|
int iGZErr = 0;
|
|
// take an input file (strInput) and turn it into a compressed file (strGZ)
|
|
// get rid of the input file after
|
|
//s.quit_request = 0;
|
|
//checkBOINCStatus();
|
|
FILE* fIn = boinc_fopen(strInput, "wb");
|
|
if (!fIn) {
|
|
fprintf(stderr,
|
|
"%s ERROR: doGunzip(): Error opening '%s'.\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)),
|
|
strInput);
|
|
return false; //error
|
|
}
|
|
gzFile fOut = gzopen(strGZ, "rb");
|
|
if (!fOut) {
|
|
fprintf(stderr,
|
|
"%s ERROR: doGunzip(): Error opening '%s'.\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)),
|
|
strGZ);
|
|
return false; //error
|
|
}
|
|
fseek(fIn, 0, SEEK_SET); // go to the top of the files
|
|
gzseek(fOut, 0, SEEK_SET);
|
|
while (!gzeof(fOut)) { // read 1KB at a time until end of file
|
|
memset(buf, 0x00, 1024);
|
|
lRead = 0;
|
|
lWrite = 0;
|
|
lRead = (long) gzread(fOut, buf, 1024);
|
|
if (lRead < 0) {
|
|
fprintf(stderr,
|
|
"%s ERROR: doGunzip(): gzread() encountered an error: %s.\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)),
|
|
gzerror(fOut, &iGZErr));
|
|
break;
|
|
}
|
|
lWrite = (long) fwrite(buf, 1, lRead, fIn);
|
|
if (lRead != lWrite) { //error -- read bytes != written bytes
|
|
fprintf(stderr,
|
|
"%s ERROR: doGunzip(): Write error gunzipping '%s': read %ld bytes, "
|
|
"but written %ld bytes.\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)),
|
|
strGZ, lRead, lWrite);
|
|
break;
|
|
}
|
|
//boinc_get_status(&s);
|
|
//if (s.quit_request || s.abort_request || s.no_heartbeat) break;
|
|
}
|
|
gzclose(fOut);
|
|
fclose(fIn);
|
|
//checkBOINCStatus();
|
|
if (lRead != lWrite) {
|
|
return false;
|
|
}
|
|
// if we made it here, it compressed OK, can erase strInput and leave
|
|
if (!bKeep) {
|
|
boinc_delete_file(strGZ);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
int GBAC::prepareVa(std::string &strVaFilename) {
|
|
char msg_buf[MSG_CHANNEL_SIZE];
|
|
std::string strVaUngzippedname;
|
|
std::ofstream outfile;
|
|
if (!access(GBAC_VAUNZIP_STATUS, R_OK)) { // returns zero on success
|
|
fprintf(stderr,
|
|
"%s INFO: prepareVa(): VA '%s' is already uncompressed\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)),
|
|
strVaFilename.c_str());
|
|
return 0;
|
|
}
|
|
if (!hasEnding(strVaFilename, ".gz")) {
|
|
fprintf(stderr,
|
|
"%s INFO: prepareVa(): VA '%s' is propably not compressed. Filename should end with '.gz'.\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)),
|
|
strVaFilename.c_str());
|
|
return 1; // do nothing, filename should end with .gz
|
|
}
|
|
// remove '.gz' from the end
|
|
strVaUngzippedname = strVaFilename.substr(0, strVaFilename.size() - 3);
|
|
if (access(strVaFilename.c_str(), R_OK) == -1) {
|
|
fprintf(stderr,
|
|
"%s ERROR: prepareVa(): Cannot access VA '%s'.\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)),
|
|
strVaFilename.c_str());
|
|
return 1; // error - file access
|
|
}
|
|
fprintf(stderr,
|
|
"%s prepareVa(): Uncompressing VA '%s'.\n",
|
|
boinc_msg_prefix(msg_buf, sizeof(msg_buf)),
|
|
strVaFilename.c_str());
|
|
if (!doGunzip(strVaFilename.c_str(), strVaUngzippedname.c_str(), true)) {
|
|
return 1;
|
|
} else {
|
|
outfile.open(GBAC_VAUNZIP_STATUS, ios::out | ios::trunc);
|
|
outfile << "va unzipped" << std::endl;
|
|
outfile.close();
|
|
strVaFilename = strVaUngzippedname;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
int GBAC::hasEnding(std::string const &fullString, std::string const &ending) {
|
|
if (fullString.length() >= ending.length()) {
|
|
return (0 == fullString.compare(fullString.length() - ending.length(), ending.length(), ending));
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
int GBAC::printVersion() {
|
|
char buf[256];
|
|
|
|
fprintf(stderr, "%s GBAC %s (build date: %s)\n",
|
|
boinc_msg_prefix(buf, sizeof(buf)), SVNREV, __DATE__);
|
|
return 0;
|
|
}
|