vboxwrapper.cpp 41.4 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
59
60
61
#include <string>
#include <unistd.h>
#endif

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

84

Rom Walton's avatar
Rom Walton committed
85
86
87
using std::vector;
using std::string;

88

Rom Walton's avatar
Rom Walton committed
89
void read_fraction_done(double& frac_done, VBOX_VM& vm) {
90
    char path[MAXPATHLEN];
Rom Walton's avatar
Rom Walton committed
91
92
93
    char buf[256];
    double temp, frac = 0;

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

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

119
120
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());
    if (boinc_file_exists(path)) return true;
    return false;
}

126
void read_completion_file_info(unsigned long& exit_code, bool& is_notice, string& message, VBOX_VM& vm) {
127
128
129
130
131
132
133
134
135
136
    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
137
            exit_code = atoi(buf) != 0;
138
        }
139
        if (fgets(buf, 1024, f) != NULL) {
Rom Walton's avatar
Rom Walton committed
140
            is_notice = atoi(buf) != 0;
141
        }
142
143
144
145
146
147
148
        while (fgets(buf, 1024, f) != NULL) {
            message += buf;
        }
        fclose(f);
    }
}

149
150
151
152
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());
    if (boinc_file_exists(path)) return true;
    return false;
}

156
157
158
159
160
161
162
163
164
165
166
167
168
169
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
170
            is_notice = atoi(buf) != 0;
171
172
173
174
175
176
177
178
        }
        while (fgets(buf, 1024, f) != NULL) {
            message += buf;
        }
        fclose(f);
    }
}

179
180
181
182
183
184
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
185
186
187
// set CPU and network throttling if needed
//
void set_throttles(APP_INIT_DATA& aid, VBOX_VM& vm) {
188
    double x = 0, y = 0;
189

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

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

Rom Walton's avatar
Rom Walton committed
216
217
218
}

// If the Floppy device has been specified, initialize its state so that
219
220
221
222
// 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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
//
// 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) {
238
                vboxlog_msg("WARNING: Cannot write init_data.xml to floppy abstration device");
Rom Walton's avatar
Rom Walton committed
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
            }
        } 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);
    }
}

264
// if there's a port for web graphics, tell the client about it
Rom Walton's avatar
Rom Walton committed
265
//
266
void report_web_graphics_url(VBOX_VM& vm) {
267
    char buf[256];
268
269
270
271
    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
272
273
274
275
276
    }
}

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

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

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

340
341
// see if it's time to send trickle-up reporting elapsed time
//
342
void check_trickle_period(double& elapsed_time, double& trickle_period) {
343
344
345
346
347
348
349
    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;
350
    vboxlog_msg("Status Report: Trickle-Up Event.");
351
352
353
354
355
356
357
    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) {
358
        vboxlog_msg("Sending Trickle-Up Event failed (%d).", retval);
359
360
361
    }
}

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


397
398
399
400
401
402
    // Initialize diagnostics system
    //
    boinc_init_diagnostics(BOINC_DIAG_DEFAULTS);

    // Configure BOINC Runtime System environment
    //
403
404
405
406
407
408
409
410
    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
    //
411
    vboxlog_msg("vboxwrapper (%d.%d.%d): starting", BOINC_MAJOR_VERSION, BOINC_MINOR_VERSION, VBOXWRAPPER_RELEASE);
Rom Walton's avatar
Rom Walton committed
412

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

427
428
429
430
    // Prepare environment for detecting system conditions
    //
    boinc_parse_init_data_file();
    boinc_get_init_data(aid);
431
432

#ifdef _WIN32
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
    // 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.
    //
    string vbox_version;
    int vbox_major = 0, vbox_minor = 0;

    if (BOINC_SUCCESS != vbox42::VBOX_VM::get_version_information(vbox_version)) {
        vbox43::VBOX_VM::get_version_information(vbox_version);
    }
    if (!vbox_version.empty()) {
        sscanf(vbox_version.c_str(), "%d.%d", &vbox_major, &vbox_minor);
        if ((4 == vbox_major) && (2 == vbox_minor)) {
            pVM = (VBOX_VM*) new vbox42::VBOX_VM();
        }
        if ((4 == vbox_major) && (3 == vbox_minor)) {
            pVM = (VBOX_VM*) new vbox43::VBOX_VM();
        }
    }
    if (!pVM) {
457
458
459
460
461
462
        pVM = (VBOX_VM*) new vboxmanage::VBOX_VM();
    }
#else
    pVM = (VBOX_VM*) new vboxmanage::VBOX_VM();
#endif

463
464
    // Parse command line parameters
    //
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
    for (int i=1; i<argc; i++) {
        if (!strcmp(argv[i], "--trickle")) {
            trickle_period = atof(argv[++i]);
        }
        if (!strcmp(argv[i], "--ncpus")) {
            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;
        }
    }
482
483
484

    // Choose a random interleave value for checkpoint intervals to stagger disk I/O.
    // 
485
486
487
488
489
490
    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)));
    }
491
    random_checkpoint_factor = (double)(((int)(drand() * 100000.0)) % 600);
492
    vboxlog_msg("Feature: Checkpoint interval offset (%d seconds)", (int)random_checkpoint_factor);
493
494
495
496

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

500
501
502
503
    // Check for architecture incompatibilities
    // 
#if defined(_WIN32) && defined(_M_IX86)
    if (strstr(aid.host_info.os_version, "x64")) {
504
        vboxlog_msg("64-bit version of BOINC is required, please upgrade. Rescheduling execution for a later date.");
505
506
507
508
        boinc_temporary_exit(86400, "Architecture incompatibility detected.");
    }
#endif

509
510
    // Initialize VM Hypervisor
    //
511
    retval = pVM->initialize();
512
    if (retval) {
513
        vboxlog_msg("Could not detect VM Hypervisor. Rescheduling execution for a later date.");
514
        boinc_temporary_exit(86400, "Detection of VM Hypervisor failed.");
515
516
    }

517
518
    // Record what version of VirtualBox was used.
    // 
519
    if (!pVM->virtualbox_version.empty()) {
520
        vboxlog_msg("Detected: %s", pVM->virtualbox_version.c_str());
521
522
    }

523
524
525
    // Record if anonymous platform was used.
    // 
    if (boinc_file_exists((std::string(aid.project_dir) + std::string("/app_info.xml")).c_str())) {
526
        vboxlog_msg("Detected: Anonymous Platform Enabled");
527
528
529
530
531
    }

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

Rom Walton's avatar
Rom Walton committed
535
    // Record which mode VirtualBox should be started in.
536
    //
537
    if (aid.vbox_window || boinc_is_standalone()) {
538
        vboxlog_msg("Detected: Headless Mode Disabled");
539
        pVM->headless = false;
540
541
542
543
544
    }

    // Check for invalid confgiurations.
    //
    if (aid.using_sandbox && aid.vbox_window) {
545
546
        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
547
        boinc_temporary_exit(86400, "Incompatible configuration detected.");
548
549
    }

550
    // Check against known incompatible versions of VirtualBox.  
551
552
    // VirtualBox 4.2.6 crashes during snapshot operations
    // and 4.2.18 fails to restore from snapshots properly.
553
    //
554
555
556
    if ((pVM->virtualbox_version.find("4.2.6") != std::string::npos) || 
        (pVM->virtualbox_version.find("4.2.18") != std::string::npos) || 
        (pVM->virtualbox_version.find("4.3.0") != std::string::npos) ) {
557
        vboxlog_msg("Incompatible version of VirtualBox detected. Please upgrade to a later version.");
558
559
560
561
        boinc_temporary_exit(86400,
            "Incompatible version of VirtualBox detected; please upgrade.",
            true
        );
562
563
    }

564
565
566
567
    // 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.
    //
568
    if (!pVM->is_system_ready(message)) {
569
        vboxlog_msg("Could not communicate with VM Hypervisor. Rescheduling execution for a later date.");
570
        boinc_temporary_exit(86400, message.c_str());
571
572
    }

573
574
    // Parse Job File
    //
575
    retval = pVM->parse();
Rom Walton's avatar
Rom Walton committed
576
    if (retval) {
577
        vboxlog_msg("ERROR: Cannot parse job file: %d", retval);
Rom Walton's avatar
Rom Walton committed
578
579
580
        boinc_finish(retval);
    }

581
    // Record what the minimum checkpoint interval is.
582
    //
583
    vboxlog_msg("Detected: Minimum checkpoint interval (%f seconds)", pVM->minimum_checkpoint_interval);
584

Rom Walton's avatar
Rom Walton committed
585
586
    // Validate whatever configuration options we can
    //
587
    if (pVM->enable_shared_directory) {
Rom Walton's avatar
Rom Walton committed
588
589
        if (boinc_file_exists("shared")) {
            if (!is_dir("shared")) {
590
                vboxlog_msg("ERROR: 'shared' exists but is not a directory.");
Rom Walton's avatar
Rom Walton committed
591
592
593
594
            }
        } else {
            retval = boinc_mkdir("shared");
            if (retval) {
595
                vboxlog_msg("ERROR: couldn't created shared directory: %s.", boincerror(retval));
Rom Walton's avatar
Rom Walton committed
596
597
598
599
600
601
            }
        }
    }

    // Copy files to the shared directory
    //
602
603
    if (pVM->enable_shared_directory && pVM->copy_to_shared.size()) {
        for (vector<string>::iterator iter = pVM->copy_to_shared.begin(); iter != pVM->copy_to_shared.end(); iter++) {
Rom Walton's avatar
Rom Walton committed
604
605
606
607
            string source = *iter;
            string destination = string("shared/") + *iter;
            if (!boinc_file_exists(destination.c_str())) {
                if (!boinc_copy(source.c_str(), destination.c_str())) {
608
                    vboxlog_msg("Successfully copied '%s' to the shared directory.", source.c_str());
Rom Walton's avatar
Rom Walton committed
609
                } else {
610
                    vboxlog_msg("Failed to copy '%s' to the shared directory.", source.c_str());
Rom Walton's avatar
Rom Walton committed
611
612
613
614
615
                }
            }
        }
    }

616
617
    // Configure Instance specific VM Parameters
    //
618
619
    pVM->vm_master_name = "boinc_";
    pVM->image_filename = IMAGE_FILENAME_COMPLETE;
Rom Walton's avatar
Rom Walton committed
620
    if (boinc_is_standalone()) {
621
622
623
        pVM->vm_master_name += "standalone";
        pVM->vm_master_description = "standalone";
        if (pVM->enable_floppyio) {
624
625
626
            sprintf(buf, "%s.%s",
                FLOPPY_IMAGE_FILENAME, FLOPPY_IMAGE_FILENAME_EXTENSION
            );
627
            pVM->floppy_image_filename = buf;
Rom Walton's avatar
Rom Walton committed
628
629
        }
    } else {
630
631
        pVM->vm_master_name += md5_string(std::string(aid.result_name)).substr(0, 16);
        pVM->vm_master_description = aid.result_name;
632
		if (vm_image) {
633
634
635
            sprintf(buf, "%s_%d.%s",
                IMAGE_FILENAME, vm_image, IMAGE_FILENAME_EXTENSION
            );
636
            pVM->image_filename = buf;
637
		}
638
        if (pVM->enable_floppyio) {
639
640
641
642
            sprintf(buf, "%s_%d.%s",
                FLOPPY_IMAGE_FILENAME, aid.slot,
                FLOPPY_IMAGE_FILENAME_EXTENSION
            );
643
            pVM->floppy_image_filename = buf;
Rom Walton's avatar
Rom Walton committed
644
        }
645
    }
646
647
    if (pVM->enable_cache_disk) {
        pVM->cache_disk_filename = CACHE_DISK_FILENAME;
648
    }
649
650
    if (pVM->enable_isocontextualization) {
        pVM->iso_image_filename = ISO_IMAGE_FILENAME;
Rom Walton's avatar
Rom Walton committed
651
652
653
654
655
656
657
    }
    if (aid.ncpus > 1.0 || ncpus > 1.0) {
        if (ncpus) {
            sprintf(buf, "%d", (int)ceil(ncpus));
        } else {
            sprintf(buf, "%d", (int)ceil(aid.ncpus));
        }
658
        pVM->vm_cpu_count = buf;
Rom Walton's avatar
Rom Walton committed
659
    } else {
660
        pVM->vm_cpu_count = "1";
Rom Walton's avatar
Rom Walton committed
661
    }
662
    if (pVM->memory_size_mb > 1.0 || memory_size_mb > 1.0) {
663
664
665
        if (memory_size_mb) {
            sprintf(buf, "%d", (int)ceil(memory_size_mb));
        } else {
666
            sprintf(buf, "%d", (int)ceil(pVM->memory_size_mb));
667
668
        }
    }
Rom Walton's avatar
Rom Walton committed
669
    if (aid.vbox_window && !aid.using_sandbox) {
670
        pVM->headless = false;
Rom Walton's avatar
Rom Walton committed
671
672
673
    }

    // Restore from checkpoint
674
    //
675
676
677
    checkpoint.parse();
    elapsed_time = checkpoint.elapsed_time;
    current_cpu_time = checkpoint.cpu_time;
678
679
    pVM->pf_host_port = checkpoint.webapi_port;
    pVM->rd_host_port = checkpoint.remote_desktop_port;
680
    last_checkpoint_elapsed_time = elapsed_time;
681
    starting_cpu_time = current_cpu_time;
682
    last_checkpoint_cpu_time = current_cpu_time;
Rom Walton's avatar
Rom Walton committed
683
684

    // Should we even try to start things up?
685
    //
686
    if (pVM->job_duration && (elapsed_time > pVM->job_duration)) {
Rom Walton's avatar
Rom Walton committed
687
688
689
        return EXIT_TIME_LIMIT_EXCEEDED;
    }

690
    retval = pVM->run((current_cpu_time > 0));
Rom Walton's avatar
Rom Walton committed
691
    if (retval) {
692
        // All 'failure to start' errors are unrecoverable by default
693
        bool   unrecoverable_error = true;
694
        bool   skip_cleanup = false;
695
        bool   do_dump_hypervisor_logs = false;
696
        string error_reason;
697
        const char*  temp_reason = "";
Rom Walton's avatar
Rom Walton committed
698

699
700
701
702
703
        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;
704
            temp_reason = "VM environment needed to be cleaned up.";
705
        } else if (ERR_NOT_EXITED == retval) {
706
707
708
709
710
            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;
711
            temp_reason = "VM environment needed to be cleaned up.";
712
713
        } else if (ERR_INVALID_PARAM == retval) {
            unrecoverable_error = false;
714
            temp_reason = "Please upgrade BOINC to the latest version.";
715
            temp_delay = 86400;
716
        } else if (retval == (int)RPC_S_SERVER_UNAVAILABLE) {
717
718
719
720
721
            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;
722
723
724
725
726
727
728
        } 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.";
729
        } else if (pVM->is_logged_failure_vm_extensions_disabled()) {
730
731
            error_reason =
                "   NOTE: BOINC has detected that your computer's processor supports hardware acceleration for\n"
Rom Walton's avatar
Rom Walton committed
732
                "    virtual machines but the hypervisor failed to successfully launch with this feature enabled.\n"
Rom Walton's avatar
Rom Walton committed
733
                "    This means that the hardware acceleration feature has been disabled in the computer's BIOS.\n"
Rom Walton's avatar
Rom Walton committed
734
                "    Please enable this feature in your computer's BIOS.\n"
Rom Walton's avatar
Rom Walton committed
735
736
                "    Intel calls it 'VT-x'\n"
                "    AMD calls it 'AMD-V'\n"
Rom Walton's avatar
Rom Walton committed
737
                "    More information can be found here: http://en.wikipedia.org/wiki/X86_virtualization\n"
738
                "    Error Code: ERR_CPU_VM_EXTENSIONS_DISABLED\n";
739
            retval = ERR_EXEC;
740
        } else if (pVM->is_logged_failure_vm_extensions_not_supported()) {
741
742
            error_reason =
                "   NOTE: VirtualBox has reported an improperly configured virtual machine. It was configured to require\n"
Rom Walton's avatar
Rom Walton committed
743
                "    hardware acceleration for virtual machines, but your processor does not support the required feature.\n"
744
                "    Please report this issue to the project so that it can be addresssed.\n";
745
        } else if (pVM->is_logged_failure_vm_extensions_in_use()) {
746
747
748
749
            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;
750
            temp_reason = "Forign VM Hypervisor locked hardware acceleration features.";
751
            temp_delay = 86400;
752
        } else if (pVM->is_logged_failure_host_out_of_memory()) {
753
754
755
            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
756
            unrecoverable_error = false;
757
            temp_reason = "VM Hypervisor was unable to allocate enough memory to start VM.";
Rom Walton's avatar
Rom Walton committed
758
        } else {
759
            do_dump_hypervisor_logs = true;
760
        }
Rom Walton's avatar
Rom Walton committed
761

762
763
        if (unrecoverable_error) {
            // Attempt to cleanup the VM and exit.
764
            if (!skip_cleanup) {
765
                pVM->cleanup();
766
            }
767
768

            checkpoint.update(elapsed_time, current_cpu_time);
769
770

            if (error_reason.size()) {
771
                vboxlog_msg("\n%s", error_reason.c_str());
772
773
            }

774
            if (do_dump_hypervisor_logs) {
775
                pVM->dump_hypervisor_logs(true);
776
777
            }

778
779
780
            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
781
782
            // clean up the environment.  We should be safe to run after that.
            //
783
            if (pVM->vm_pid) {
Rom Walton's avatar
Rom Walton committed
784
                retval = boinc_report_app_status_aux(
785
                    current_cpu_time,
786
                    last_checkpoint_cpu_time,
Rom Walton's avatar
Rom Walton committed
787
                    fraction_done,
788
                    pVM->vm_pid,
Rom Walton's avatar
Rom Walton committed
789
790
791
792
                    bytes_sent,
                    bytes_received
                );
            }
793
794
 
            // Give the BOINC API time to report the pid to BOINC.
795
            //
796
797
            boinc_sleep(5.0);

798
            if (error_reason.size()) {
799
                vboxlog_msg("\n%s", error_reason.c_str());
800
801
            }

802
            // Exit and let BOINC clean up the rest.
803
            //
Rom Walton's avatar
Rom Walton committed
804
805
806
807
            boinc_temporary_exit(temp_delay, temp_reason);
        }
    }

808
809
    // Report the VM pid to BOINC so BOINC can deal with it when needed.
    //
810
    vboxlog_msg("Reporting VM Process ID to BOINC.");
811
    retval = boinc_report_app_status_aux(
812
        current_cpu_time,
813
        last_checkpoint_cpu_time,
814
        fraction_done,
815
        pVM->vm_pid,
816
817
818
819
        bytes_sent,
        bytes_received
    );

820
821
822
823
    // 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
824
    // of iteration based.
825
    //
826
827
    timeout = dtime() + 300;
    do {
828
829
        pVM->poll(false);
        if (pVM->online && !pVM->restoring) break;
830
831
        boinc_sleep(1.0);
    } while (timeout >= dtime());
832
833

    // Lower the VM process priority after it has successfully brought itself online.
834
    //
835
    pVM->lower_vm_process_priority();
836
837

    // Log our current state 
838
    pVM->poll(true);
839
840

    // Did we timeout?
841
    if (!pVM->online && (timeout <= dtime())) {
842
843
        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.");
844
845
        pVM->reset_vm_process_priority();
        pVM->poweroff();
846
847
848
        boinc_temporary_exit(86400,
            "VM Hypervisor failed to enter an online state in a timely fashion."
        );
849
850
    }

851
    set_floppy_image(aid, *pVM);
852
853
    report_web_graphics_url(*pVM);
    report_remote_desktop_info(*pVM);
854
855
856
    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
857

858
859
860
    // Force throttling on our first pass through the loop
    boinc_status.reread_init_data_file = true;

Rom Walton's avatar
Rom Walton committed
861
862
    while (1) {
        // Begin stopwatch timer
863
        stopwatch_starttime = dtime();
864
        loop_iteration += 1;
Rom Walton's avatar
Rom Walton committed
865
866

        // Discover the VM's current state
867
        pVM->poll();
Rom Walton's avatar
Rom Walton committed
868

869
870
871
        // Write updates for the graphics application's use 
        boinc_write_graphics_status(current_cpu_time, elapsed_time, fraction_done);

Rom Walton's avatar
Rom Walton committed
872
        if (boinc_status.no_heartbeat || boinc_status.quit_request) {
873
874
            pVM->reset_vm_process_priority();
            pVM->poweroff();
875
            boinc_temporary_exit(86400);
Rom Walton's avatar
Rom Walton committed
876
877
        }
        if (boinc_status.abort_request) {
878
879
880
            pVM->reset_vm_process_priority();
            pVM->cleanup();
            pVM->dump_hypervisor_logs(true);
Rom Walton's avatar
Rom Walton committed
881
882
            boinc_finish(EXIT_ABORTED_BY_CLIENT);
        }
883
        if (completion_file_exists(*pVM)) {
884
            vboxlog_msg("VM Completion File Detected.");
885
            read_completion_file_info(vm_exit_code, is_notice, message, *pVM);
886
            if (message.size()) {
887
                vboxlog_msg("VM Completion Message: %s.", message.c_str());
888
            }
889
890
            pVM->reset_vm_process_priority();
            pVM->cleanup();
891
            if (is_notice) {
892
893
894
895
                boinc_finish_message(vm_exit_code, message.c_str(), is_notice);
            } else {
                boinc_finish(vm_exit_code);
            }
896
        }
897
        if (temporary_exit_file_exists(*pVM)) {
898
            vboxlog_msg("VM Temporary Exit File Detected.");
899
            read_temporary_exit_file_info(temp_delay, is_notice, message, *pVM);
900
            if (message.size()) {
901
                vboxlog_msg("VM Temporary Exit Message: %s.", message.c_str());
902
            }
903
904
905
            delete_temporary_exit_trigger_file(*pVM);
            pVM->reset_vm_process_priority();
            pVM->stop();
906
907
908
909
910
911
            if (is_notice) {
                boinc_temporary_exit(temp_delay, message.c_str(), is_notice);
            } else {
                boinc_temporary_exit(temp_delay);
            }
        }
912
        if (!pVM->online) {
Rom Walton's avatar
Rom Walton committed
913
            // Is this a type of event we can recover from?
914
            if (pVM->is_logged_failure_host_out_of_memory()) {
915
916
                vboxlog_msg("NOTE: VirtualBox has failed to allocate enough memory to continue.");
                vboxlog_msg("  This might be a temporary problem and so this job will be rescheduled for another time.");
917
918
                pVM->reset_vm_process_priority();
                pVM->poweroff();
919
                boinc_temporary_exit(86400, "VM Hypervisor was unable to allocate enough memory.");
Rom Walton's avatar
Rom Walton committed
920
            } else {
921
922
                pVM->cleanup();
                if (pVM->crashed || (elapsed_time < pVM->job_duration)) {
923
                    vboxlog_msg("VM Premature Shutdown Detected.");
924
925
                    pVM->dump_hypervisor_logs(true);
                    pVM->get_vm_exit_code(vm_exit_code);
Rom Walton's avatar
Rom Walton committed
926
927
928
929
930
931
                    if (vm_exit_code) {
                        boinc_finish(vm_exit_code);
                    } else {
                        boinc_finish(EXIT_ABORTED_BY_CLIENT);
                    }
                } else {
932
                    vboxlog_msg("Virtual machine exited.");
933
                    pVM->dump_hypervisor_logs(false);
Rom Walton's avatar
Rom Walton committed
934
935
936
                    boinc_finish(0);
                }
            }
937
938
939
        } else {
            // Check to see if the guest VM has any log messages that indicate that we need need
            // to take action.
940
            if (pVM->is_logged_failure_guest_job_out_of_memory()) {
941
                vboxlog_msg("ERROR: VM reports there is not enough memory to finish the task.");