vboxwrapper.cpp 41.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
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 set_web_graphics_url(VBOX_VM& vm) {
267
    char buf[256];
268
    for (unsigned int i=0; i<vm.port_forwards.size(); i++) {
269
        VBOX_PORT_FORWARD& pf = vm.port_forwards[i];
270
        if (pf.guest_port == vm.pf_guest_port) {
271
272
273
            sprintf(buf, "http://localhost:%d", pf.host_port);
            vboxlog_msg("Detected: Web Application Enabled (%s)", buf);
            boinc_web_graphics_url(buf);
274
275
            break;
        }
Rom Walton's avatar
Rom Walton committed
276
277
278
279
280
    }
}

// set remote desktop information if needed
//
281
void set_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 random_checkpoint_factor = 0;
374
    double elapsed_time = 0;
Rom Walton's avatar
Rom Walton committed
375
    double fraction_done = 0;
376
    double trickle_period = 0;
377
    double current_cpu_time = 0;
378
    double starting_cpu_time = 0;
379
380
    double last_checkpoint_cpu_time = 0;
    double last_checkpoint_elapsed_time = 0;
Rom Walton's avatar
Rom Walton committed
381
    double last_status_report_time = 0;
382
    double stopwatch_starttime = 0;
Rom Walton's avatar
Rom Walton committed
383
    double stopwatch_endtime = 0;
384
    double stopwatch_elapsedtime = 0;
Rom Walton's avatar
Rom Walton committed
385
386
387
388
    double sleep_time = 0;
    double bytes_sent = 0;
    double bytes_received = 0;
    double ncpus = 0;
389
    double memory_size_mb = 0;
390
    double timeout = 0.0;
Rom Walton's avatar
Rom Walton committed
391
    bool report_net_usage = false;
392
    double net_usage_timer = 600;
393
	int vm_image = 0;
Rom Walton's avatar
Rom Walton committed
394
    unsigned long vm_exit_code = 0;
395
    bool is_notice = false;
396
    int temp_delay = 86400;
397
    string message;
Rom Walton's avatar
Rom Walton committed
398
399
400
    char buf[256];


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

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

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

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

#ifdef _WIN32
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
    // 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) {
461
462
463
464
465
466
        pVM = (VBOX_VM*) new vboxmanage::VBOX_VM();
    }
#else
    pVM = (VBOX_VM*) new vboxmanage::VBOX_VM();
#endif

467
468
    // Parse command line parameters
    //
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
    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;
        }
    }
486
487
488

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

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

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

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

521
522
    // Record what version of VirtualBox was used.
    // 
523
    if (!pVM->virtualbox_version.empty()) {
524
        vboxlog_msg("Detected: %s", pVM->virtualbox_version.c_str());
525
526
    }

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

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

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

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

554
    // Check against known incompatible versions of VirtualBox.  
555
556
    // VirtualBox 4.2.6 crashes during snapshot operations
    // and 4.2.18 fails to restore from snapshots properly.
557
    //
558
559
560
    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) ) {
561
        vboxlog_msg("Incompatible version of VirtualBox detected. Please upgrade to a later version.");
562
563
564
565
        boinc_temporary_exit(86400,
            "Incompatible version of VirtualBox detected; please upgrade.",
            true
        );
566
567
    }

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

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

585
    // Record what the minimum checkpoint interval is.
586
    //
587
    vboxlog_msg("Detected: Minimum checkpoint interval (%f seconds)", pVM->minimum_checkpoint_interval);
588

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

    // Copy files to the shared directory
    //
606
607
    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
608
609
610
611
            string source = *iter;
            string destination = string("shared/") + *iter;
            if (!boinc_file_exists(destination.c_str())) {
                if (!boinc_copy(source.c_str(), destination.c_str())) {
612
                    vboxlog_msg("Successfully copied '%s' to the shared directory.", source.c_str());
Rom Walton's avatar
Rom Walton committed
613
                } else {
614
                    vboxlog_msg("Failed to copy '%s' to the shared directory.", source.c_str());
Rom Walton's avatar
Rom Walton committed
615
616
617
618
619
                }
            }
        }
    }

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

    // Restore from checkpoint
678
    //
679
680
681
    checkpoint.parse();
    elapsed_time = checkpoint.elapsed_time;
    current_cpu_time = checkpoint.cpu_time;
682
    last_checkpoint_elapsed_time = elapsed_time;
683
    starting_cpu_time = current_cpu_time;
684
    last_checkpoint_cpu_time = current_cpu_time;
Rom Walton's avatar
Rom Walton committed
685
686

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

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

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

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

            checkpoint.update(elapsed_time, current_cpu_time);
771
772

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

776
            if (do_dump_hypervisor_logs) {
777
                pVM->dump_hypervisor_logs(true);
778
779
            }

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

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

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

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

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

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

    // Log our current state 
840
    pVM->poll(true);
841
842

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

853
854
    set_floppy_image(aid, *pVM);
    set_web_graphics_url(*pVM);
855
856
857
858
    set_remote_desktop_info(*pVM);
    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
859

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

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

        // Discover the VM's current state
869
        pVM->poll();
Rom Walton's avatar
Rom Walton committed
870
871

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