// This file is part of BOINC.
// http://boinc.berkeley.edu
// Copyright (C) 2008 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 .
// vmwrapper.C
// VMWare wrapper program - lets you use BOINC to drive a VMWare Server
// guest OS
#include
#include
#include
#ifdef _WIN32
#include "boinc_win.h"
#include "win_util.h"
#else
#include
#include
#include
#include
#include "procinfo.h"
#endif
#include "boinc_api.h"
#include "diagnostics.h"
#include "filesys.h"
#include "parse.h"
#include "str_util.h"
#include "util.h"
#include "error_numbers.h"
#include "vmware-vix/vix.h"
#define JOB_FILENAME "job.xml"
#define CHECKPOINT_FILENAME "checkpoint.txt"
#define POLL_PERIOD 1.0
using std::vector;
using std::string;
struct TASK
{
string application;
string stdin_filename;
string stdout_filename;
string stderr_filename;
string vm;
string innerjob;
string datadir;
string partial_credit;
string snapshots;
string user;
string password;
vector < string > data;
vector < string > output;
string checkpoint_filename;
// name of task's checkpoint file, if any
double checkpoint_cpu_time;
// CPU time at last checkpoint
string command_line;
double weight;
// contribution of this task to overall fraction done
double final_cpu_time;
double starting_cpu;
// how much CPU time was used by tasks before this in the job file
bool suspended;
double wall_cpu_time;
// for estimating CPU time on Win98/ME and Mac
#ifdef _WIN32
HANDLE pid_handle;
DWORD pid;
HANDLE thread_handle;
struct _stat last_stat; // mod time of checkpoint file
#else
int pid;
struct stat last_stat;
#endif
bool stat_first;
int parse (XML_PARSER &);
bool poll (int &status);
int run (int argc, char **argv);
void kill ();
void stop ();
void resume ();
double cpu_time ();
inline bool has_checkpointed ()
{
bool changed = false;
if (checkpoint_filename.size () == 0)
return false;
struct stat new_stat;
int retval = stat (checkpoint_filename.c_str (), &new_stat);
if (retval)
return false;
if (!stat_first && new_stat.st_mtime != last_stat.st_mtime)
{
changed = true;
}
stat_first = false;
last_stat.st_mtime = new_stat.st_mtime;
return changed;
}
};
vector < TASK > tasks;
APP_INIT_DATA aid;
bool graphics = false;
int TASK::parse (XML_PARSER & xp)
{
string this_data, this_output;
char tag[1024], buf[8192], buf2[8192];
bool is_tag;
weight = 1;
final_cpu_time = 0;
stat_first = true;
while (!xp.get (tag, sizeof (tag), is_tag))
{
if (!is_tag)
{
fprintf (stderr, "SCHED_CONFIG::parse(): unexpected text %s\n",
tag);
continue;
}
if (!strcmp (tag, "/task"))
{
return 0;
}
else if (xp.parse_string (tag, "application", application))
continue;
else if (xp.parse_string (tag, "innerjob", innerjob))
continue;
else if (xp.parse_string (tag, "vm", vm))
continue;
else if (xp.parse_string (tag, "datadir", datadir))
continue;
else if (xp.parse_string (tag, "partial_credit", partial_credit))
continue;
else if (xp.parse_string (tag, "snapshots", snapshots))
continue;
else if (xp.parse_string (tag, "data", this_data)) {
data.push_back(this_data);
continue;
}
else if (xp.parse_string (tag, "output", this_output)) {
output.push_back(this_output);
continue;
}
else if (xp.parse_string (tag, "user", user))
continue;
else if (xp.parse_string (tag, "password", password))
continue;
else if (xp.parse_string (tag, "stdin_filename", stdin_filename))
continue;
else if (xp.parse_string (tag, "stdout_filename", stdout_filename))
continue;
else if (xp.parse_string (tag, "stderr_filename", stderr_filename))
continue;
else if (xp.parse_str (tag, "command_line", buf, sizeof (buf)))
{
while (1)
{
char *p = strstr (buf, "$PROJECT_DIR");
if (!p)
break;
strcpy (buf2, p + strlen ("$PROJECT_DIR"));
strcpy (p, aid.project_dir);
strcat (p, buf2);
}
command_line = buf;
continue;
}
else if (xp.
parse_string (tag, "checkpoint_filename",
checkpoint_filename))
continue;
else if (xp.parse_double (tag, "weight", weight))
continue;
}
return ERR_XML_PARSE;
}
int parse_job_file ()
{
MIOFILE mf;
char tag[1024], buf[256];
bool is_tag;
boinc_resolve_filename (JOB_FILENAME, buf, 1024);
FILE *f = boinc_fopen (buf, "r");
if (!f)
{
fprintf (stderr, "can't open job file %s\n", buf);
return ERR_FOPEN;
}
mf.init_file (f);
XML_PARSER xp (&mf);
if (!xp.parse_start ("job_desc"))
return ERR_XML_PARSE;
while (!xp.get (tag, sizeof (tag), is_tag))
{
if (!is_tag)
{
fprintf (stderr, "SCHED_CONFIG::parse(): unexpected text %s\n",
tag);
continue;
}
if (!strcmp (tag, "/job_desc"))
{
fclose (f);
return 0;
}
if (!strcmp (tag, "task"))
{
TASK task;
int retval = task.parse (xp);
if (!retval)
{
tasks.push_back (task);
}
}
}
fclose (f);
return ERR_XML_PARSE;
}
#ifdef _WIN32
// CreateProcess() takes HANDLEs for the stdin/stdout.
// We need to use CreateFile() to get them. Ugh.
//
HANDLE
win_fopen (const char *path, const char *mode)
{
SECURITY_ATTRIBUTES sa;
memset (&sa, 0, sizeof (sa));
sa.nLength = sizeof (sa);
sa.bInheritHandle = TRUE;
if (!strcmp (mode, "r"))
{
return CreateFile (path,
GENERIC_READ,
FILE_SHARE_READ, &sa, OPEN_EXISTING, 0, 0);
}
else if (!strcmp (mode, "w"))
{
return CreateFile (path,
GENERIC_WRITE,
FILE_SHARE_WRITE, &sa, OPEN_ALWAYS, 0, 0);
}
else if (!strcmp (mode, "a"))
{
HANDLE
hAppend = CreateFile (path,
GENERIC_WRITE,
FILE_SHARE_WRITE, &sa, OPEN_ALWAYS, 0, 0);
SetFilePointer (hAppend, 0, NULL, FILE_END);
return hAppend;
}
else
{
return 0;
}
}
#endif
void slash_to_backslash (char *p)
{
while (1)
{
char *q = strchr (p, '/');
if (!q)
break;
*q = '\\';
}
}
int TASK::run (int argct, char **argvt)
{
string stdout_path, stdin_path, stderr_path;
char app_path[1024], buf[256];
strcpy (buf, application.c_str ());
char *p = strstr (buf, "$PROJECT_DIR");
if (p)
{
p += strlen ("$PROJECT_DIR");
sprintf (app_path, "%s%s", aid.project_dir, p);
}
else
{
boinc_resolve_filename (buf, app_path, sizeof (app_path));
}
// Append wrapper's command-line arguments to those in the job file.
//
for (int i = 1; i < argct; i++)
{
command_line += argvt[i];
if ((i + 1) < argct)
{
command_line += string (" ");
}
}
fprintf (stderr, "wrapper: running %s (%s)\n",
app_path, command_line.c_str ());
#ifdef _WIN32
PROCESS_INFORMATION process_info;
STARTUPINFO startup_info;
string command;
slash_to_backslash (app_path);
memset (&process_info, 0, sizeof (process_info));
memset (&startup_info, 0, sizeof (startup_info));
command = string ("\"") + app_path + string ("\" ") + command_line;
// pass std handles to app
//
startup_info.dwFlags = STARTF_USESTDHANDLES;
if (stdout_filename != "")
{
boinc_resolve_filename_s (stdout_filename.c_str (), stdout_path);
startup_info.hStdOutput = win_fopen (stdout_path.c_str (), "a");
}
if (stdin_filename != "")
{
boinc_resolve_filename_s (stdin_filename.c_str (), stdin_path);
startup_info.hStdInput = win_fopen (stdin_path.c_str (), "r");
}
if (stderr_filename != "")
{
boinc_resolve_filename_s (stderr_filename.c_str (), stderr_path);
startup_info.hStdError = win_fopen (stderr_path.c_str (), "a");
}
else
{
startup_info.hStdError = win_fopen (STDERR_FILE, "a");
}
// bInheritHandles
if (!CreateProcess (app_path, (LPSTR) command.c_str (), NULL, NULL, TRUE,
CREATE_NO_WINDOW | IDLE_PRIORITY_CLASS,
NULL, NULL, &startup_info, &process_info))
{
return ERR_EXEC;
}
pid_handle = process_info.hProcess;
pid = process_info.dwProcessId;
thread_handle = process_info.hThread;
SetThreadPriority (thread_handle, THREAD_PRIORITY_IDLE);
#else
int retval, argc;
char progname[256];
char *argv[256];
char arglist[4096];
FILE *stdout_file;
FILE *stdin_file;
FILE *stderr_file;
pid = fork ();
if (pid == -1)
{
boinc_finish (ERR_FORK);
}
if (pid == 0)
{
// we're in the child process here
//
// open stdout, stdin if file names are given
// NOTE: if the application is restartable,
// we should deal with atomicity somehow
//
if (stdout_filename != "")
{
boinc_resolve_filename_s (stdout_filename.c_str (), stdout_path);
stdout_file = freopen (stdout_path.c_str (), "a", stdout);
if (!stdout_file)
return ERR_FOPEN;
}
if (stdin_filename != "")
{
boinc_resolve_filename_s (stdin_filename.c_str (), stdin_path);
stdin_file = freopen (stdin_path.c_str (), "r", stdin);
if (!stdin_file)
return ERR_FOPEN;
}
if (stderr_filename != "")
{
boinc_resolve_filename_s (stderr_filename.c_str (), stderr_path);
stderr_file = freopen (stderr_path.c_str (), "a", stderr);
if (!stderr_file)
return ERR_FOPEN;
}
// construct argv
// TODO: use malloc instead of stack var
//
argv[0] = app_path;
strlcpy (arglist, command_line.c_str (), sizeof (arglist));
argc = parse_command_line (arglist, argv + 1);
setpriority (PRIO_PROCESS, 0, PROCESS_IDLE_PRIORITY);
retval = execv (app_path, argv);
exit (ERR_EXEC);
}
#endif
wall_cpu_time = 0;
suspended = false;
return 0;
}
bool TASK::poll (int &status)
{
if (!suspended)
wall_cpu_time += POLL_PERIOD;
#ifdef _WIN32
unsigned long
exit_code;
if (GetExitCodeProcess (pid_handle, &exit_code))
{
if (exit_code != STILL_ACTIVE)
{
status = exit_code;
final_cpu_time = cpu_time ();
return true;
}
}
#else
int
wpid,
stat;
struct rusage
ru;
wpid = wait4 (pid, &status, WNOHANG, &ru);
if (wpid)
{
final_cpu_time =
(float) ru.ru_utime.tv_sec + ((float) ru.ru_utime.tv_usec) / 1e+6;
return true;
}
#endif
return false;
}
void TASK::kill ()
{
#ifdef _WIN32
TerminateProcess (pid_handle, -1);
#else
::kill (pid, SIGKILL);
#endif
}
void TASK::stop ()
{
suspended = true;
}
void TASK::resume ()
{
suspended = false;
}
double TASK::cpu_time ()
{
#ifdef _WIN32
FILETIME creation_time, exit_time, kernel_time, user_time;
ULARGE_INTEGER tKernel, tUser;
LONGLONG totTime;
int retval = GetProcessTimes (pid_handle, &creation_time, &exit_time,
&kernel_time,
&user_time);
if (retval == 0)
{
return wall_cpu_time;
}
tKernel.LowPart = kernel_time.dwLowDateTime;
tKernel.HighPart = kernel_time.dwHighDateTime;
tUser.LowPart = user_time.dwLowDateTime;
tUser.HighPart = user_time.dwHighDateTime;
totTime = tKernel.QuadPart + tUser.QuadPart;
return totTime / 1.e7;
#elif defined(__APPLE__)
// There's no easy way to get another process's CPU time in Mac OS X
//
return wall_cpu_time;
#else
return linux_cpu_time (pid);
#endif
}
// Support for multiple tasks.
// We keep a checkpoint file that says how many tasks we've completed
// and how much CPU time has been used so far
//
void write_checkpoint (int ntasks, double cpu)
{
FILE *f = fopen (CHECKPOINT_FILENAME, "w");
if (!f)
return;
fprintf (f, "%d %f\n", ntasks, cpu);
fclose (f);
}
void read_checkpoint (int &ntasks, double &cpu)
{
int nt;
double c;
ntasks = 0;
cpu = 0;
FILE *f = fopen (CHECKPOINT_FILENAME, "r");
if (!f)
return;
int n = fscanf (f, "%d %lf", &nt, &c);
fclose (f);
if (n != 2)
return;
ntasks = nt;
cpu = c;
}
void check_vm_result(VixError err, VixHandle hostHandle, string errorString)
{
if (VIX_OK != err)
{
fprintf (stderr, "\n\n Error: %s\n",errorString.c_str());
fprintf (stderr, "Error message: \"%s\"\n", Vix_GetErrorText (err, NULL));
VixHost_Disconnect (hostHandle);
boinc_finish(100);
}
}
int main (int argc, char **argv)
{
BOINC_OPTIONS options;
int retval, ntasks;
unsigned int i;
double cpu, total_weight = 0, w = 0;
for (i = 1; i < (unsigned int) argc; i++)
{
if (!strcmp (argv[i], "--graphics"))
{
graphics = true;
}
}
memset (&options, 0, sizeof (options));
options.main_program = true;
options.check_heartbeat = true;
options.handle_process_control = true;
if (graphics)
{
options.backwards_compatible_graphics = true;
}
// boinc_init_options(&options);
fprintf (stderr, "vmwrapper: starting\n");
boinc_get_init_data (aid);
retval = parse_job_file ();
if (retval)
{
fprintf (stderr, "can't parse job file: %d\n", retval);
boinc_finish (retval);
}
read_checkpoint (ntasks, cpu);
if (ntasks > (int) tasks.size ())
{
fprintf (stderr, "Checkpoint file: ntasks %d too large\n", ntasks);
boinc_finish (1);
}
for (i = 0; i < tasks.size (); i++)
{
total_weight += tasks[i].weight;
}
VixHandle hostHandle = VIX_INVALID_HANDLE;
VixHandle jobHandle = VIX_INVALID_HANDLE;
VixError err;
string vmxFilename;
jobHandle = VixHost_Connect (VIX_API_VERSION, VIX_SERVICEPROVIDER_VMWARE_SERVER, NULL, 0, NULL, NULL, 0, VIX_INVALID_HANDLE, NULL, NULL);
err = VixJob_Wait (jobHandle, VIX_PROPERTY_JOB_RESULT_HANDLE, &hostHandle, VIX_PROPERTY_NONE);
if (VIX_OK != err)
{
fprintf (stderr, "Unable to connect to server!\n");
fprintf (stderr, "Error message: \"%s\"\n", Vix_GetErrorText (err, NULL));
VixHost_Disconnect (hostHandle);
boinc_finish(1);
}
fprintf (stderr, "Connected to server.\n");
Vix_ReleaseHandle (jobHandle);
jobHandle = VIX_INVALID_HANDLE;
for (i = 0; i < tasks.size (); i++)
{
fprintf(stderr,"Processing task %d.\n", i);
TASK & task = tasks[i];
// w += task.weight;
// this line is to do with cpu time storage
// if ((int) i < ntasks)
// continue;
double frac_done = w / total_weight;
fprintf(stderr, "Task %d.\n========\n\n", i+1);
fprintf(stderr, "VM file root is %s\n", task.vm.c_str ());
string vm_file_in_1, vm_file_in_2;
char vm_file_1[1024], vm_file_2[1024];
string pwd = get_current_dir_name ();
// VMWare Server 1 requires full path
vm_file_in_1 = pwd + "/" + task.vm + ".vmx";
vm_file_in_2 = pwd + "/" + task.vm + ".vmdk";
// fprintf (stderr, "Input filenames are %s, %s\n",
// vm_file_in_1.c_str (), vm_file_in_2.c_str ());
boinc_resolve_filename (vm_file_in_1.c_str (), vm_file_1, 1024);
boinc_resolve_filename (vm_file_in_2.c_str (), vm_file_2, 1024);
// fprintf (stderr, "Resolved to %s, %s\n", vm_file_1, vm_file_2);
if (access (vm_file_1, R_OK))
{
fprintf (stderr, "Unable to access VM file %s!\n",vm_file_1);
boinc_finish (1);
}
if (access (vm_file_2, R_OK))
{
fprintf (stderr, "Unable to access VM file %s!\n",vm_file_2);
boinc_finish (1);
}
// Register VM
jobHandle = VixHost_RegisterVM (hostHandle, vm_file_1, NULL, NULL);
err = VixJob_Wait (jobHandle, VIX_PROPERTY_NONE);
check_vm_result(err, hostHandle, "Unable to register VM.");
fprintf (stderr, "Registered virtual machine with the server.\n");
VixHandle vmHandle = VIX_INVALID_HANDLE;
// Open VM
jobHandle = VixVM_Open (hostHandle, vm_file_1, NULL, NULL);
err = VixJob_Wait (jobHandle, VIX_PROPERTY_JOB_RESULT_HANDLE, &vmHandle, VIX_PROPERTY_NONE);
check_vm_result(err, hostHandle, "Unable to open VM.");
fprintf (stderr, "Got handle.\n");
// Power on VM
jobHandle = VixVM_PowerOn (vmHandle, 0, VIX_INVALID_HANDLE, NULL, NULL);
err = VixJob_Wait (jobHandle, VIX_PROPERTY_NONE);
check_vm_result(err, hostHandle, "Unable to power on VM.");
fprintf (stderr, "VM is now on (if it wasn't already).\n");
// Wait for guest OS to start
jobHandle = VixVM_WaitForToolsInGuest (vmHandle, 0, NULL, NULL);
err = VixJob_Wait (jobHandle, VIX_PROPERTY_NONE);
check_vm_result(err, hostHandle, "Did not get confirmation of VMWare Tools.");
// Log in
jobHandle = VixVM_LoginInGuest (vmHandle, task.user.c_str(), task.password.c_str(), 0, NULL, NULL);
err = VixJob_Wait (jobHandle, VIX_PROPERTY_NONE);
check_vm_result(err, hostHandle, "Unable to log in.");
fprintf (stderr, "Logged in. Preparing job...\n");
string guestfile = task.datadir + "/" + task.innerjob;
string st_hostfile = pwd + "/" + task.innerjob;
char hostfile[1024];
boinc_resolve_filename (st_hostfile.c_str (), hostfile, 1024);
jobHandle = VixVM_CopyFileFromHostToGuest (vmHandle, hostfile, guestfile.c_str(), 0, VIX_INVALID_HANDLE, NULL, NULL);
err = VixJob_Wait (jobHandle, VIX_PROPERTY_NONE);
check_vm_result(err, hostHandle, "Unable to copy executable.");
Vix_ReleaseHandle (jobHandle);
// Changing permissions, do we really need to do this?
jobHandle = VixVM_RunProgramInGuest (vmHandle, "/bin/chmod", ("+x " + guestfile).c_str (), 0, VIX_INVALID_HANDLE, NULL, NULL);
err = VixJob_Wait (jobHandle, VIX_PROPERTY_NONE);
check_vm_result(err, hostHandle, "Unable to change permissions.");
Vix_ReleaseHandle (jobHandle);
fprintf (stderr, "Copying in input...\n");
for(int files=0;files