vboxwrapper.cpp 45.2 KB
Newer Older
Rom Walton's avatar
Rom Walton committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// This file is part of BOINC.
// http://boinc.berkeley.edu
// Copyright (C) 2010-2012 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 <http://www.gnu.org/licenses/>.

18
// BOINC VirtualBox wrapper; lets you run apps in VMs
Rom Walton's avatar
Rom Walton committed
19
// see: http://boinc.berkeley.edu/trac/wiki/VboxApps
20
21
22
//
// usage: vboxwrapper [options]
//
23
// --trickle X      send a trickle-up message reporting elapsed time every X sec
Rom Walton's avatar
Rom Walton committed
24
25
//                  (use this for credit granting if your app does its
//                  own job management, like CernVM).
26
// --nthreads N     create a VM with N threads.
27
// --vmimage N      Use "vm_image_N" as the VM image.
28
29
30
31
32
//                  This lets you create an app version with several images,
//                  and the app_plan function can decide which one to use
//                  for the particular host.
// --register_only  Register the VM but don't run it.
//                  Useful for debugging; see the wiki page
Rom Walton's avatar
Rom Walton committed
33
34
35
36
//
// Handles:
// - suspend/resume/quit/abort
// - reporting CPU time
37
38
39
// - loss of heartbeat from client
// - checkpoint (using snapshots)
// - a bunch of other stuff; see the wiki page
Rom Walton's avatar
Rom Walton committed
40
41
//
// Contributors:
42
43
// Rom Walton
// David Anderson
Rom Walton's avatar
Rom Walton committed
44
45
46
47
48
49
50
51
52
53
54
55
56
// Andrew J. Younge (ajy4490 AT umiacs DOT umd DOT edu)
// Jie Wu <jiewu AT cern DOT ch>
// Daniel Lombraña González <teleyinex AT gmail DOT com>

#ifdef _WIN32
#include "boinc_win.h"
#include "win_util.h"
#else
#include <vector>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
57
#include <cmath>
Rom Walton's avatar
Rom Walton committed
58
#include <string>
59
#include <sstream>
Rom Walton's avatar
Rom Walton committed
60
61
62
#include <unistd.h>
#endif

63
#include "version.h"
Rom Walton's avatar
Rom Walton committed
64
#include "boinc_api.h"
65
#include "graphics2.h"
Rom Walton's avatar
Rom Walton committed
66
67
#include "diagnostics.h"
#include "filesys.h"
68
#include "md5_file.h"
Rom Walton's avatar
Rom Walton committed
69
70
71
72
73
74
#include "parse.h"
#include "str_util.h"
#include "str_replace.h"
#include "util.h"
#include "error_numbers.h"
#include "procinfo.h"
75
#include "floppyio.h"
76
77
#include "vboxlogging.h"
#include "vboxcheckpoint.h"
Rom Walton's avatar
Rom Walton committed
78
#include "vboxwrapper.h"
79
#include "vbox_common.h"
80
#ifdef _WIN32
81
82
#include "vbox_mscom42.h"
#include "vbox_mscom43.h"
83
#include "vbox_mscom50.h"
84
#endif
85
86
#include "vbox_vboxmanage.h"

87

Rom Walton's avatar
Rom Walton committed
88
89
90
using std::vector;
using std::string;

91

92
bool read_fraction_done(double& frac_done, VBOX_VM& vm) {
93
    char path[MAXPATHLEN];
Rom Walton's avatar
Rom Walton committed
94
95
96
    char buf[256];
    double temp, frac = 0;

97
98
    sprintf(path, "shared/%s", vm.fraction_done_filename.c_str());
    FILE* f = fopen(path, "r");
99
    if (!f) return false;
Rom Walton's avatar
Rom Walton committed
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119

    // read the last line of the file
    //
    fseek(f, -32, SEEK_END);
    while (!feof(f)) {
        char* p = fgets(buf, 256, f);
        if (p == NULL) break;
        int n = sscanf(buf, "%lf", &temp);
        if (n == 1) frac = temp;
    }
    fclose(f);

    if (frac < 0) {
        frac = 0;
    }
    if (frac > 1) {
        frac = 1;
    }

    frac_done = frac;
120
	return true;
Rom Walton's avatar
Rom Walton committed
121
122
}

123
124
125
bool completion_file_exists(VBOX_VM& vm) {
    char path[MAXPATHLEN];
    sprintf(path, "shared/%s", vm.completion_trigger_file.c_str());
126
    if (vm.completion_trigger_file.size() && boinc_file_exists(path)) return true;
127
128
129
    return false;
}

130
void read_completion_file_info(unsigned long& exit_code, bool& is_notice, string& message, VBOX_VM& vm) {
131
132
133
134
135
136
137
138
139
140
    char path[MAXPATHLEN];
    char buf[1024];

    exit_code = 0;
    message = "";

    sprintf(path, "shared/%s", vm.completion_trigger_file.c_str());
    FILE* f = fopen(path, "r");
    if (f) {
        if (fgets(buf, 1024, f) != NULL) {
Rom Walton's avatar
Rom Walton committed
141
            exit_code = atoi(buf) != 0;
142
        }
143
        if (fgets(buf, 1024, f) != NULL) {
Rom Walton's avatar
Rom Walton committed
144
            is_notice = atoi(buf) != 0;
145
        }
146
147
148
149
150
151
152
        while (fgets(buf, 1024, f) != NULL) {
            message += buf;
        }
        fclose(f);
    }
}

153
154
155
bool temporary_exit_file_exists(VBOX_VM& vm) {
    char path[MAXPATHLEN];
    sprintf(path, "shared/%s", vm.temporary_exit_trigger_file.c_str());
156
    if (vm.temporary_exit_trigger_file.size() && boinc_file_exists(path)) return true;
157
158
159
    return false;
}

160
161
162
163
164
165
166
167
168
169
170
171
172
173
void read_temporary_exit_file_info(int& temp_delay, bool& is_notice, string& message, VBOX_VM& vm) {
    char path[MAXPATHLEN];
    char buf[1024];

    temp_delay = 0;
    message = "";

    sprintf(path, "shared/%s", vm.temporary_exit_trigger_file.c_str());
    FILE* f = fopen(path, "r");
    if (f) {
        if (fgets(buf, 1024, f) != NULL) {
            temp_delay = atoi(buf);
        }
        if (fgets(buf, 1024, f) != NULL) {
Rom Walton's avatar
Rom Walton committed
174
            is_notice = atoi(buf) != 0;
175
176
177
178
179
180
181
182
        }
        while (fgets(buf, 1024, f) != NULL) {
            message += buf;
        }
        fclose(f);
    }
}

183
184
185
186
187
188
void delete_temporary_exit_trigger_file(VBOX_VM& vm) {
    char path[MAXPATHLEN];
    sprintf(path, "shared/%s", vm.temporary_exit_trigger_file.c_str());
    boinc_delete_file(path);
}

Rom Walton's avatar
Rom Walton committed
189
190
191
// set CPU and network throttling if needed
//
void set_throttles(APP_INIT_DATA& aid, VBOX_VM& vm) {
192
    double x = 0, y = 0;
193

194
    // VirtualBox freaks out if the CPU Usage value is too low to actually
195
196
197
    // do any processing.  It probably wouldn't be so bad if the RDP interface
    // didn't also get hosed by it.
    //
198
199
200
201
    x = aid.global_prefs.cpu_usage_limit;
    // 0 means "no limit"
    //
    if (x == 0.0) x = 100;
202
    // For now set the minimum CPU Usage value to 1.
203
    //
204
205
    if (x < 1) x = 1;
    vm.set_cpu_usage((int)x);
Rom Walton's avatar
Rom Walton committed
206
207
208
209

    // vbox doesn't distinguish up and down bandwidth; use the min of the prefs
    //
    x = aid.global_prefs.max_bytes_sec_up;
210
    y = aid.global_prefs.max_bytes_sec_down;
Rom Walton's avatar
Rom Walton committed
211
    if (y) {
212
        if (!x || y < x) {
Rom Walton's avatar
Rom Walton committed
213
214
215
216
            x = y;
        }
    }
    if (x) {
217
        vm.set_network_usage((int)(x/1024));
Rom Walton's avatar
Rom Walton committed
218
    }
219

Rom Walton's avatar
Rom Walton committed
220
221
222
}

// If the Floppy device has been specified, initialize its state so that
223
224
225
226
// it contains the contents of the init_data.xml file.
// In theory this would allow network enabled VMs to know about
// proxy server configurations either specified by the volunteer
// or automatically detected by the client.
Rom Walton's avatar
Rom Walton committed
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
//
// CERN decided they only needed a small subset of the data and changed the
// data format to 'name=value\n' pairs.  So if we are running under their
// environment set things up accordingly.
//
void set_floppy_image(APP_INIT_DATA& aid, VBOX_VM& vm) {
    int retval;
    char buf[256];
    std::string scratch;

    if (vm.enable_floppyio) {
        scratch = "";
        if (!vm.enable_cern_dataformat) {
            retval = read_file_string(INIT_DATA_FILE, scratch);
            if (retval) {
242
                vboxlog_msg("WARNING: Cannot write init_data.xml to floppy abstration device");
Rom Walton's avatar
Rom Walton committed
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
            }
        } else {
            // Per: https://github.com/stig/json-framework/issues/48
            //
            // Use %.17g to represent doubles
            //
            scratch  = "BOINC_USERNAME=" + string(aid.user_name) + "\n";
            scratch += "BOINC_AUTHENTICATOR=" + string(aid.authenticator) + "\n";

            sprintf(buf, "%d", aid.userid);
            scratch += "BOINC_USERID=" + string(buf) + "\n";

            sprintf(buf, "%d", aid.hostid);
            scratch += "BOINC_HOSTID=" + string(buf) + "\n";

            sprintf(buf, "%.17g", aid.user_total_credit);
            scratch += "BOINC_USER_TOTAL_CREDIT=" + string(buf) + "\n";

            sprintf(buf, "%.17g", aid.host_total_credit);
            scratch += "BOINC_HOST_TOTAL_CREDIT=" + string(buf) + "\n";
        }
        vm.write_floppy(scratch);
    }
}

268
// if there's a port for web graphics, tell the client about it
Rom Walton's avatar
Rom Walton committed
269
//
270
void report_web_graphics_url(VBOX_VM& vm) {
271
    char buf[256];
272
273
274
275
    if (vm.pf_host_port && !boinc_file_exists("graphics_app")) {
        sprintf(buf, "http://localhost:%d", vm.pf_host_port);
        vboxlog_msg("Detected: Web Application Enabled (%s)", buf);
        boinc_web_graphics_url(buf);
Rom Walton's avatar
Rom Walton committed
276
277
278
279
280
    }
}

// set remote desktop information if needed
//
281
void report_remote_desktop_info(VBOX_VM& vm) {
Rom Walton's avatar
Rom Walton committed
282
283
284
    char buf[256];
    if (vm.rd_host_port) {
        sprintf(buf, "localhost:%d", vm.rd_host_port);
285
        vboxlog_msg("Detected: Remote Desktop Enabled (%s)", buf);
Rom Walton's avatar
Rom Walton committed
286
287
288
289
        boinc_remote_desktop_addr(buf);
    }
}

290
291
// check for trickle trigger files, and send trickles if find them.
//
292
void check_trickle_triggers(VBOX_VM& vm) {
293
294
295
    int retval;
    char filename[256], path[MAXPATHLEN];
    std::string text;
296
297
    for (unsigned int i=0; i<vm.trickle_trigger_files.size(); i++) {
        strcpy(filename, vm.trickle_trigger_files[i].c_str());
298
299
        sprintf(path, "shared/%s", filename);
        if (!boinc_file_exists(path)) continue;
300
301
        vboxlog_msg("Reporting a trickle. (%s)", filename);
        retval = read_file_string(path, text);
302
        if (retval) {
303
            vboxlog_msg("ERROR: can't read trickle trigger file %s", filename);
304
305
306
307
308
        } else {
            retval = boinc_send_trickle_up(
                filename, const_cast<char*>(text.c_str())
            );
            if (retval) {
309
                vboxlog_msg("boinc_send_trickle_up() failed: %s (%d)", boincerror(retval), retval);
310
            }
311
312
313
314
315
        }
        boinc_delete_file(path);
    }
}

316
317
// check for intermediate upload files, and send them if found.
//
318
void check_intermediate_uploads(VBOX_VM& vm) {
319
    int retval;
320
    char filename[256], path[MAXPATHLEN];
321
322
    for (unsigned int i=0; i<vm.intermediate_upload_files.size(); i++) {
        strcpy(filename, vm.intermediate_upload_files[i].file.c_str());
323
324
        sprintf(path, "shared/%s", filename);
        if (!boinc_file_exists(path)) continue;
325
        if (!vm.intermediate_upload_files[i].reported && !vm.intermediate_upload_files[i].ignore) {
326
            vboxlog_msg("Reporting an intermediate file. (%s)", vm.intermediate_upload_files[i].file.c_str());
327
            retval = boinc_upload_file(vm.intermediate_upload_files[i].file);
328
            if (retval) {
329
                vboxlog_msg("boinc_upload_file() failed: %s", boincerror(retval));
330
                vm.intermediate_upload_files[i].ignore = true;
331
            } else {
332
                vm.intermediate_upload_files[i].reported = true;
333
            }
334
335
        } else if (vm.intermediate_upload_files[i].reported && !vm.intermediate_upload_files[i].ignore) {
            retval = boinc_upload_status(vm.intermediate_upload_files[i].file);
336
            if (!retval) {
337
                vboxlog_msg("Intermediate file uploaded. (%s)", vm.intermediate_upload_files[i].file.c_str());
338
                vm.intermediate_upload_files[i].ignore = true;
339
340
341
342
343
            }
        }
    }
}

344
345
// see if it's time to send trickle-up reporting elapsed time
//
346
void check_trickle_period(double& elapsed_time, double& trickle_period) {
347
348
349
350
351
352
353
    char buf[256];
    static double last_trickle_report_time = 0;

    if ((elapsed_time - last_trickle_report_time) < trickle_period) {
        return;
    }
    last_trickle_report_time = elapsed_time;
354
    vboxlog_msg("Status Report: Trickle-Up Event.");
355
356
357
358
359
360
361
    sprintf(buf,
        "<cpu_time>%f</cpu_time>", last_trickle_report_time
    );
    int retval = boinc_send_trickle_up(
        const_cast<char*>("cpu_time"), buf
    );
    if (retval) {
362
        vboxlog_msg("Sending Trickle-Up Event failed (%d).", retval);
363
364
365
    }
}

Rom Walton's avatar
Rom Walton committed
366
int main(int argc, char** argv) {
367
    int retval = 0;
368
    int loop_iteration = 0;
Rom Walton's avatar
Rom Walton committed
369
370
    BOINC_OPTIONS boinc_options;
    APP_INIT_DATA aid;
371
    VBOX_VM* pVM = NULL;
372
    VBOX_CHECKPOINT checkpoint;
373
    double desired_checkpoint_interval = 0;
374
    double random_checkpoint_factor = 0;
375
    double elapsed_time = 0;
Rom Walton's avatar
Rom Walton committed
376
    double fraction_done = 0;
377
    double trickle_period = 0;
378
    double current_cpu_time = 0;
379
    double starting_cpu_time = 0;
380
381
    double last_checkpoint_cpu_time = 0;
    double last_checkpoint_elapsed_time = 0;
Rom Walton's avatar
Rom Walton committed
382
    double last_status_report_time = 0;
383
    double stopwatch_starttime = 0;
Rom Walton's avatar
Rom Walton committed
384
    double stopwatch_endtime = 0;
385
    double stopwatch_elapsedtime = 0;
Rom Walton's avatar
Rom Walton committed
386
387
388
389
    double sleep_time = 0;
    double bytes_sent = 0;
    double bytes_received = 0;
    double ncpus = 0;
390
    double memory_size_mb = 0;
391
    double timeout = 0.0;
Rom Walton's avatar
Rom Walton committed
392
    bool report_net_usage = false;
393
    double net_usage_timer = 600;
394
	int vm_image = 0;
Rom Walton's avatar
Rom Walton committed
395
    unsigned long vm_exit_code = 0;
396
    bool is_notice = false;
397
    int temp_delay = 86400;
398
    string message;
399
    string scratch_dir;
Rom Walton's avatar
Rom Walton committed
400
401
    char buf[256];

402
403
404
405
406
407
    // Initialize diagnostics system
    //
    boinc_init_diagnostics(BOINC_DIAG_DEFAULTS);

    // Configure BOINC Runtime System environment
    //
408
409
410
411
412
413
414
415
    memset(&boinc_options, 0, sizeof(boinc_options));
    boinc_options.main_program = true;
    boinc_options.check_heartbeat = true;
    boinc_options.handle_process_control = true;
    boinc_init_options(&boinc_options);

    // Log banner
    //
416
    vboxlog_msg("vboxwrapper (%d.%d.%d): starting", BOINC_MAJOR_VERSION, BOINC_MINOR_VERSION, VBOXWRAPPER_RELEASE);
Rom Walton's avatar
Rom Walton committed
417

418
419
420
421
422
423
424
425
    // Initialize system services
    // 
#ifdef _WIN32
    CoInitialize(NULL);
#ifdef USE_WINSOCK
    WSADATA wsdata;
    retval = WSAStartup( MAKEWORD( 1, 1 ), &wsdata);
    if (retval) {
426
        vboxlog_msg("ERROR: Cannot initialize winsock: %d", retval);
427
428
429
430
431
        boinc_finish(retval);
    }
#endif
#endif

432
433
434
435
    // Prepare environment for detecting system conditions
    //
    boinc_parse_init_data_file();
    boinc_get_init_data(aid);
436
437

#ifdef _WIN32
438
439
440
441
442
443
444
445
    // Determine what version of VirtualBox we are using via the registry. Use a
    // namespace specific version of the function because VirtualBox has been known
    // to change the registry location from time to time.
    //
    // NOTE: We cannot use COM to automatically detect which interfaces are installed
    //       on the machine because it will attempt to launch the 'vboxsvc' process
    //       without out environment variable changes and muck everything up.
    //
446
447
    string vbox_version_raw;
    string vbox_version_display;
448
449
    int vbox_major = 0, vbox_minor = 0;

450
451
452
    if (BOINC_SUCCESS != vbox42::VBOX_VM::get_version_information(vbox_version_raw, vbox_version_display)) {
        if (BOINC_SUCCESS != vbox43::VBOX_VM::get_version_information(vbox_version_raw, vbox_version_display)) {
            vbox50::VBOX_VM::get_version_information(vbox_version_raw, vbox_version_display);
453
        }
454
    }
455
456
    if (!vbox_version_raw.empty()) {
        sscanf(vbox_version_raw.c_str(), "%d.%d", &vbox_major, &vbox_minor);
457
458
        if ((4 == vbox_major) && (2 == vbox_minor)) {
            pVM = (VBOX_VM*) new vbox42::VBOX_VM();
459
460
461
462
463
            retval = pVM->initialize();
            if (retval) {
                delete pVM;
                pVM = NULL;
            }
464
465
466
        }
        if ((4 == vbox_major) && (3 == vbox_minor)) {
            pVM = (VBOX_VM*) new vbox43::VBOX_VM();
467
468
469
470
471
            retval = pVM->initialize();
            if (retval) {
                delete pVM;
                pVM = NULL;
            }
472
        }
Rom Walton's avatar
Rom Walton committed
473
        if ((5 == vbox_major) && (0 <= vbox_minor)) {
474
            pVM = (VBOX_VM*) new vbox50::VBOX_VM();
475
476
477
478
479
            retval = pVM->initialize();
            if (retval) {
                delete pVM;
                pVM = NULL;
            }
480
        }
481
482
    }
    if (!pVM) {
483
        pVM = (VBOX_VM*) new vboxmanage::VBOX_VM();
484
485
486
        retval = pVM->initialize();
        if (retval) {
            vboxlog_msg("Could not detect VM Hypervisor. Rescheduling execution for a later date.");
487
            pVM->dump_hypervisor_logs(true);
488
489
            boinc_temporary_exit(86400, "Detection of VM Hypervisor failed.");
        }
490
491
492
    }
#else
    pVM = (VBOX_VM*) new vboxmanage::VBOX_VM();
493
494
495
496
497
498

    // Initialize VM Hypervisor
    //
    retval = pVM->initialize();
    if (retval) {
        vboxlog_msg("Could not detect VM Hypervisor. Rescheduling execution for a later date.");
499
        pVM->dump_hypervisor_logs(true);
500
501
        boinc_temporary_exit(86400, "Detection of VM Hypervisor failed.");
    }
502
503
#endif

504
505
    // Parse command line parameters
    //
506
507
508
509
    for (int i=1; i<argc; i++) {
        if (!strcmp(argv[i], "--trickle")) {
            trickle_period = atof(argv[++i]);
        }
marius's avatar
marius committed
510
        if (!strcmp(argv[i], "--nthreads")) {
511
512
513
514
515
516
517
518
519
520
521
522
            ncpus = atof(argv[++i]);
        }
        if (!strcmp(argv[i], "--memory_size_mb")) {
            memory_size_mb = atof(argv[++i]);
        }
        if (!strcmp(argv[i], "--vmimage")) {
            vm_image = atoi(argv[++i]);
        }
        if (!strcmp(argv[i], "--register_only")) {
            pVM->register_only = true;
        }
    }
523
524
525

    // Choose a random interleave value for checkpoint intervals to stagger disk I/O.
    // 
526
527
528
529
530
531
    struct stat vm_image_stat;
    if (-1 == stat(IMAGE_FILENAME_COMPLETE, &vm_image_stat)) {
        srand((int)time(NULL));
    } else {
        srand((int)(vm_image_stat.st_mtime * time(NULL)));
    }
532
    random_checkpoint_factor = (double)(((int)(drand() * 100000.0)) % 600);
533

534
    vboxlog_msg("Feature: Checkpoint interval offset (%d seconds)", (int)random_checkpoint_factor);
535
536
537
538

    // Display trickle value if specified
    //
    if (trickle_period > 0.0) {
539
        vboxlog_msg("Feature: Enabling trickle-ups (Interval: %f)", trickle_period);
540
541
    }

542
543
544
545
    // Check for architecture incompatibilities
    // 
#if defined(_WIN32) && defined(_M_IX86)
    if (strstr(aid.host_info.os_version, "x64")) {
546
        vboxlog_msg("64-bit version of BOINC is required, please upgrade. Rescheduling execution for a later date.");
547
548
549
550
        boinc_temporary_exit(86400, "Architecture incompatibility detected.");
    }
#endif

551
552
    // Record what version of VirtualBox was used.
    // 
553
554
    if (!pVM->virtualbox_version_display.empty()) {
        vboxlog_msg("Detected: %s", pVM->virtualbox_version_display.c_str());
555
556
    }

557
558
559
    // Record if anonymous platform was used.
    // 
    if (boinc_file_exists((std::string(aid.project_dir) + std::string("/app_info.xml")).c_str())) {
560
        vboxlog_msg("Detected: Anonymous Platform Enabled");
561
562
563
564
565
    }

    // Record if the sandboxed configuration is going to be used.
    //
    if (aid.using_sandbox) {
566
        vboxlog_msg("Detected: Sandbox Configuration Enabled");
567
568
    }

Rom Walton's avatar
Rom Walton committed
569
    // Record which mode VirtualBox should be started in.
570
    //
571
    if (aid.vbox_window || boinc_is_standalone()) {
572
        vboxlog_msg("Detected: Headless Mode Disabled");
573
        pVM->headless = false;
574
575
576
577
578
    }

    // Check for invalid confgiurations.
    //
    if (aid.using_sandbox && aid.vbox_window) {
579
580
        vboxlog_msg("Invalid configuration detected.");
        vboxlog_msg("NOTE: BOINC cannot be installed as a service and run VirtualBox in headfull mode at the same time.");
Rom Walton's avatar
Rom Walton committed
581
        boinc_temporary_exit(86400, "Incompatible configuration detected.");
582
583
    }

584
    // Check against known incompatible versions of VirtualBox.  
585
586
    // VirtualBox 4.2.6 crashes during snapshot operations
    // and 4.2.18 fails to restore from snapshots properly.
587
    //
588
589
590
    if ((pVM->virtualbox_version_raw.find("4.2.6") != std::string::npos) || 
        (pVM->virtualbox_version_raw.find("4.2.18") != std::string::npos) || 
        (pVM->virtualbox_version_raw.find("4.3.0") != std::string::npos) ) {
591
        vboxlog_msg("Incompatible version of VirtualBox detected. Please upgrade to a later version.");
592
593
594
595
        boinc_temporary_exit(86400,
            "Incompatible version of VirtualBox detected; please upgrade.",
            true
        );
596
597
    }

598
599
600
601
    // Check to see if the system is in a state in which we expect to be able to run
    // VirtualBox successfully.  Sometimes the system is in a wierd state after a
    // reboot and the system needs a little bit of time.
    //
602
    if (!pVM->is_system_ready(message)) {
603
        vboxlog_msg("Could not communicate with VM Hypervisor. Rescheduling execution for a later date.");
604
        boinc_temporary_exit(86400, message.c_str());
605
606
    }

607
608
    // Parse Job File
    //
609
    retval = pVM->parse();
Rom Walton's avatar
Rom Walton committed
610
    if (retval) {
611
        vboxlog_msg("ERROR: Cannot parse job file: %d", retval);
Rom Walton's avatar
Rom Walton committed
612
613
614
        boinc_finish(retval);
    }

615
    // Record what the minimum checkpoint interval is.
616
    //
617
    vboxlog_msg("Detected: Minimum checkpoint interval (%f seconds)", pVM->minimum_checkpoint_interval);
618

Rom Walton's avatar
Rom Walton committed
619
620
    // Validate whatever configuration options we can
    //
621
    if (pVM->enable_shared_directory) {
622
        pVM->get_scratch_directory(scratch_dir);
Rom Walton's avatar
Rom Walton committed
623
624
        if (boinc_file_exists("shared")) {
            if (!is_dir("shared")) {
625
                vboxlog_msg("ERROR: 'shared' exists but is not a directory.");
Rom Walton's avatar
Rom Walton committed
626
627
628
629
            }
        } else {
            retval = boinc_mkdir("shared");
            if (retval) {
630
631
632
633
634
635
636
637
638
639
640
                vboxlog_msg("ERROR: couldn't create shared directory: %s.", boincerror(retval));
            }
        }
        if (boinc_file_exists(scratch_dir.c_str())) {
            if (!is_dir(scratch_dir.c_str())) {
                vboxlog_msg("ERROR: 'scratch' exists but is not a directory.");
            }
        } else {
            retval = boinc_mkdir(scratch_dir.c_str());
            if (retval) {
                vboxlog_msg("ERROR: couldn't create scratch directory: %s.", boincerror(retval));
Rom Walton's avatar
Rom Walton committed
641
642
643
644
645
646
            }
        }
    }

    // Copy files to the shared directory
    //
647
    if (pVM->enable_shared_directory && pVM->copy_to_shared.size()) {
648
        for (vector<string>::iterator iter = pVM->copy_to_shared.begin(); iter != pVM->copy_to_shared.end(); ++iter) {
Rom Walton's avatar
Rom Walton committed
649
650
651
652
            string source = *iter;
            string destination = string("shared/") + *iter;
            if (!boinc_file_exists(destination.c_str())) {
                if (!boinc_copy(source.c_str(), destination.c_str())) {
653
                    vboxlog_msg("Successfully copied '%s' to the shared directory.", source.c_str());
Rom Walton's avatar
Rom Walton committed
654
                } else {
655
                    vboxlog_msg("Failed to copy '%s' to the shared directory.", source.c_str());
Rom Walton's avatar
Rom Walton committed
656
657
658
659
660
                }
            }
        }
    }

661
662
    // Configure Instance specific VM Parameters
    //
663
664
    pVM->vm_master_name = "boinc_";
    pVM->image_filename = IMAGE_FILENAME_COMPLETE;
Rom Walton's avatar
Rom Walton committed
665
    if (boinc_is_standalone()) {
666
667
668
        pVM->vm_master_name += "standalone";
        pVM->vm_master_description = "standalone";
        if (pVM->enable_floppyio) {
669
670
671
            sprintf(buf, "%s.%s",
                FLOPPY_IMAGE_FILENAME, FLOPPY_IMAGE_FILENAME_EXTENSION
            );
672
            pVM->floppy_image_filename = buf;
Rom Walton's avatar
Rom Walton committed
673
674
        }
    } else {
675
676
        pVM->vm_master_name += md5_string(std::string(aid.result_name)).substr(0, 16);
        pVM->vm_master_description = aid.result_name;
677
		if (vm_image) {
678
679
680
            sprintf(buf, "%s_%d.%s",
                IMAGE_FILENAME, vm_image, IMAGE_FILENAME_EXTENSION
            );
681
            pVM->image_filename = buf;
682
		}
683
        if (pVM->enable_floppyio) {
684
685
686
687
            sprintf(buf, "%s_%d.%s",
                FLOPPY_IMAGE_FILENAME, aid.slot,
                FLOPPY_IMAGE_FILENAME_EXTENSION
            );
688
            pVM->floppy_image_filename = buf;
Rom Walton's avatar
Rom Walton committed
689
        }
690
    }
691
692
    if (pVM->enable_cache_disk) {
        pVM->cache_disk_filename = CACHE_DISK_FILENAME;
693
    }
694
695
    if (pVM->enable_isocontextualization) {
        pVM->iso_image_filename = ISO_IMAGE_FILENAME;
Rom Walton's avatar
Rom Walton committed
696
697
698
699
700
701
702
    }
    if (aid.ncpus > 1.0 || ncpus > 1.0) {
        if (ncpus) {
            sprintf(buf, "%d", (int)ceil(ncpus));
        } else {
            sprintf(buf, "%d", (int)ceil(aid.ncpus));
        }
703
        pVM->vm_cpu_count = buf;
Rom Walton's avatar
Rom Walton committed
704
    } else {
705
        pVM->vm_cpu_count = "1";
Rom Walton's avatar
Rom Walton committed
706
    }
707
    if (pVM->memory_size_mb > 1.0 || memory_size_mb > 1.0) {
708
709
710
        if (memory_size_mb) {
            sprintf(buf, "%d", (int)ceil(memory_size_mb));
        } else {
711
            sprintf(buf, "%d", (int)ceil(pVM->memory_size_mb));
712
713
        }
    }
Rom Walton's avatar
Rom Walton committed
714
    if (aid.vbox_window && !aid.using_sandbox) {
715
        pVM->headless = false;
Rom Walton's avatar
Rom Walton committed
716
717
718
    }

    // Restore from checkpoint
719
    //
720
721
722
    checkpoint.parse();
    elapsed_time = checkpoint.elapsed_time;
    current_cpu_time = checkpoint.cpu_time;
723
724
    pVM->pf_host_port = checkpoint.webapi_port;
    pVM->rd_host_port = checkpoint.remote_desktop_port;
725
    last_checkpoint_elapsed_time = elapsed_time;
726
    starting_cpu_time = current_cpu_time;
727
    last_checkpoint_cpu_time = current_cpu_time;
Rom Walton's avatar
Rom Walton committed
728
729

    // Should we even try to start things up?
730
    //
731
    if (pVM->job_duration && (elapsed_time > pVM->job_duration)) {
Rom Walton's avatar
Rom Walton committed
732
733
734
        return EXIT_TIME_LIMIT_EXCEEDED;
    }

735
    retval = pVM->run(current_cpu_time > 0);
Rom Walton's avatar
Rom Walton committed
736
    if (retval) {
737
        // All 'failure to start' errors are unrecoverable by default
738
        bool   unrecoverable_error = true;
739
        bool   skip_cleanup = false;
740
        bool   do_dump_hypervisor_logs = false;
741
        string error_reason;
742
        const char*  temp_reason = "";
Rom Walton's avatar
Rom Walton committed
743

744
745
746
747
748
        if (VBOXWRAPPER_ERR_RECOVERABLE == retval) {
            error_reason =
                "    BOINC will be notified that it needs to clean up the environment.\n"
                "    This is a temporary problem and so this job will be rescheduled for another time.\n";
            unrecoverable_error = false;
749
            temp_reason = "VM environment needed to be cleaned up.";
750
        } else if (ERR_NOT_EXITED == retval) {
751
752
753
754
755
            error_reason =
                "   NOTE: VM was already running.\n"
                "    BOINC will be notified that it needs to clean up the environment.\n"
                "    This might be a temporary problem and so this job will be rescheduled for another time.\n";
            unrecoverable_error = false;
756
            temp_reason = "VM environment needed to be cleaned up.";
757
758
        } else if (ERR_INVALID_PARAM == retval) {
            unrecoverable_error = false;
759
            temp_reason = "Please upgrade BOINC to the latest version.";
760
            temp_delay = 86400;
761
        } else if (retval == (int)RPC_S_SERVER_UNAVAILABLE) {
762
763
764
765
766
            error_reason =
                "    VboxSvc crashed while attempting to restore the current snapshot.  This is a critical\n"
                "    operation and this job cannot be recovered.\n";
            skip_cleanup = true;
            retval = ERR_EXEC;
767
768
769
770
771
772
773
        } else if (retval == (int)VBOX_E_INVALID_OBJECT_STATE) {
            error_reason =
                "   NOTE: VM session lock error encountered.\n"
                "    BOINC will be notified that it needs to clean up the environment.\n"
                "    This might be a temporary problem and so this job will be rescheduled for another time.\n";
            unrecoverable_error = false;
            temp_reason = "VM environment needed to be cleaned up.";
774
        } else if (pVM->is_logged_failure_vm_extensions_disabled()) {
775
776
            error_reason =
                "   NOTE: BOINC has detected that your computer's processor supports hardware acceleration for\n"
Rom Walton's avatar
Rom Walton committed
777
                "    virtual machines but the hypervisor failed to successfully launch with this feature enabled.\n"
Rom Walton's avatar
Rom Walton committed
778
                "    This means that the hardware acceleration feature has been disabled in the computer's BIOS.\n"
Rom Walton's avatar
Rom Walton committed
779
                "    Please enable this feature in your computer's BIOS.\n"
Rom Walton's avatar
Rom Walton committed
780
781
                "    Intel calls it 'VT-x'\n"
                "    AMD calls it 'AMD-V'\n"
Rom Walton's avatar
Rom Walton committed
782
                "    More information can be found here: http://en.wikipedia.org/wiki/X86_virtualization\n"
783
                "    Error Code: ERR_CPU_VM_EXTENSIONS_DISABLED\n";
784
            retval = ERR_EXEC;
785
        } else if (pVM->is_logged_failure_vm_extensions_not_supported()) {
786
787
            error_reason =
                "   NOTE: VirtualBox has reported an improperly configured virtual machine. It was configured to require\n"
Rom Walton's avatar
Rom Walton committed
788
                "    hardware acceleration for virtual machines, but your processor does not support the required feature.\n"
789
                "    Please report this issue to the project so that it can be addresssed.\n";
790
        } else if (pVM->is_logged_failure_vm_extensions_in_use()) {
791
792
793
794
            error_reason =
                "   NOTE: VirtualBox hypervisor reports that another hypervisor has locked the hardware acceleration\n"
                "    for virtual machines feature in exclusive mode.\n";
            unrecoverable_error = false;
795
            temp_reason = "Forign VM Hypervisor locked hardware acceleration features.";
796
            temp_delay = 86400;
797
        } else if (pVM->is_logged_failure_host_out_of_memory()) {
798
799
800
            error_reason =
                "   NOTE: VirtualBox has failed to allocate enough memory to start the configured virtual machine.\n"
                "    This might be a temporary problem and so this job will be rescheduled for another time.\n";
Rom Walton's avatar
Rom Walton committed
801
            unrecoverable_error = false;
802
            temp_reason = "VM Hypervisor was unable to allocate enough memory to start VM.";
Rom Walton's avatar
Rom Walton committed
803
        } else {
804
            do_dump_hypervisor_logs = true;
805
        }
Rom Walton's avatar
Rom Walton committed
806

807
808
        if (unrecoverable_error) {
            // Attempt to cleanup the VM and exit.
809
            if (!skip_cleanup) {
810
                pVM->cleanup();
811
            }
812
813

            checkpoint.update(elapsed_time, current_cpu_time);
814
815

            if (error_reason.size()) {
816
                vboxlog_msg("\n%s", error_reason.c_str());
817
818
            }

819
            if (do_dump_hypervisor_logs) {
820
                pVM->dump_hypervisor_logs(true);
821
822
            }

823
824
825
            boinc_finish(retval);
        } else {
            // if the VM is already running notify BOINC about the process ID so it can
Rom Walton's avatar
Rom Walton committed
826
827
            // clean up the environment.  We should be safe to run after that.
            //
828
            if (pVM->vm_pid) {
Rom Walton's avatar
Rom Walton committed
829
                retval = boinc_report_app_status_aux(
830
                    current_cpu_time,
831
                    last_checkpoint_cpu_time,
Rom Walton's avatar
Rom Walton committed
832
                    fraction_done,
833
                    pVM->vm_pid,
Rom Walton's avatar
Rom Walton committed
834
835
836
837
                    bytes_sent,
                    bytes_received
                );
            }
838
839
 
            // Give the BOINC API time to report the pid to BOINC.
840
            //
841
842
            boinc_sleep(5.0);

843
            if (error_reason.size()) {
844
                vboxlog_msg("\n%s", error_reason.c_str());
845
846
            }

847
            // Exit and let BOINC clean up the rest.
848
            //
Rom Walton's avatar
Rom Walton committed
849
850
851
852
            boinc_temporary_exit(temp_delay, temp_reason);
        }
    }

853
854
    // Report the VM pid to BOINC so BOINC can deal with it when needed.
    //
855
    vboxlog_msg("Reporting VM Process ID to BOINC.");
856
    retval = boinc_report_app_status_aux(
857
        current_cpu_time,
858
        last_checkpoint_cpu_time,
859
        fraction_done,
860
        pVM->vm_pid,
861
862
863
864
        bytes_sent,
        bytes_received
    );

865
866
867
868
    // Wait for up to 5 minutes for the VM to switch states.
    // A system under load can take a while.
    // Since the poll function can wait for up to 60 seconds
    // to execute a command we need to make this time based instead
869
    // of iteration based.
870
    //
871
872
    timeout = dtime() + 300;
    do {
873
874
        pVM->poll(false);
        if (pVM->online && !pVM->restoring) break;
875
876
        boinc_sleep(1.0);
    } while (timeout >= dtime());
877
878

    // Lower the VM process priority after it has successfully brought itself online.
879
    //
880
    pVM->lower_vm_process_priority();
881
882

    // Log our current state 
883
    pVM->poll(true);
884
885

    // Did we timeout?
886
    if (!pVM->online && (timeout <= dtime())) {
887
888
        vboxlog_msg("NOTE: VM failed to enter an online state within the timeout period.");
        vboxlog_msg("  This might be a temporary problem and so this job will be rescheduled for another time.");
889
890
        pVM->reset_vm_process_priority();
        pVM->poweroff();
891
        pVM->dump_hypervisor_logs(true);
892
893
894
        boinc_temporary_exit(86400,
            "VM Hypervisor failed to enter an online state in a timely fashion."
        );
895
896
    }

897
    set_floppy_image(aid, *pVM);
898
899
    report_web_graphics_url(*pVM);
    report_remote_desktop_info(*pVM);
900
901
902
    checkpoint.webapi_port = pVM->pf_host_port;
    checkpoint.remote_desktop_port = pVM->rd_host_port;
    checkpoint.update(elapsed_time, current_cpu_time);
Rom Walton's avatar
Rom Walton committed
903

904
905
906
    // Force throttling on our first pass through the loop
    boinc_status.reread_init_data_file = true;

Rom Walton's avatar
Rom Walton committed
907
908
    while (1) {
        // Begin stopwatch timer
909
        stopwatch_starttime = dtime();
910
        loop_iteration += 1;
Rom Walton's avatar
Rom Walton committed
911
912

        // Discover the VM's current state
913
914
915
916
917
918
919
        retval = pVM->poll();
        if (retval) {
            vboxlog_msg("ERROR: Vboxwrapper lost communication with VirtualBox, rescheduling task for a later time.");
            pVM->reset_vm_process_priority();
            pVM->poweroff();
            boinc_temporary_exit(86400, "VM job unmanageable, restarting later.");
        }
Rom Walton's avatar
Rom Walton committed
920

921
922
923
924
        // Write updates for the graphics application's use
        if (pVM->enable_graphics_support) {
            boinc_write_graphics_status(current_cpu_time, elapsed_time, fraction_done);
        }
925

Rom Walton's avatar
Rom Walton committed
926
        if (boinc_status.no_heartbeat || boinc_status.quit_request) {
927
            pVM->reset_vm_process_priority();
928
929
930
931
932
933
934
935
936
937
            if (pVM->enable_vm_savestate_usage) {
                retval = pVM->create_snapshot(elapsed_time);
                if (!retval) {
                    checkpoint.update(elapsed_time, current_cpu_time);
                    boinc_checkpoint_completed();
                }
                pVM->stop();
            } else {
                pVM->poweroff();
            }
938
            boinc_temporary_exit(86400);