vboxwrapper.cpp 52.5 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
42
43
44
45
46
47
48
49
50
//
// Contributors:
// 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>
// Rom Walton
// David Anderson

#ifdef _WIN32
#include "boinc_win.h"
#include "win_util.h"
51
52
#include "atlcomcli.h"
#include "atlstr.h"
Rom Walton's avatar
Rom Walton committed
53
54
55
56
57
58
#else
#include <vector>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
59
#include <cmath>
Rom Walton's avatar
Rom Walton committed
60
61
62
63
#include <string>
#include <unistd.h>
#endif

64
#include "version.h"
Rom Walton's avatar
Rom Walton committed
65
66
67
#include "boinc_api.h"
#include "diagnostics.h"
#include "filesys.h"
68
#include "md5_file.h"
Rom Walton's avatar
Rom Walton committed
69
70
71
72
73
74
75
76
#include "parse.h"
#include "str_util.h"
#include "str_replace.h"
#include "util.h"
#include "error_numbers.h"
#include "procinfo.h"
#include "vboxwrapper.h"
#include "vbox.h"
77
78
79
80
81
82
#ifdef _WIN32
#include "vbox_win.h"
#else
#include "vbox_unix.h"
#endif

Rom Walton's avatar
Rom Walton committed
83
84
85
using std::vector;
using std::string;

86
87
88
double elapsed_time = 0;
    // job's total elapsed time (over all sessions)
double trickle_period = 0;
Rom Walton's avatar
Rom Walton committed
89

90
91
92
93
94
95
96
97
98
bool is_boinc_client_version_newer(APP_INIT_DATA& aid, int maj, int min, int rel) {
    if (maj < aid.major_version) return true;
    if (maj > aid.major_version) return false;
    if (min < aid.minor_version) return true;
    if (min > aid.minor_version) return false;
    if (rel < aid.release) return true;
    return false;
}

Rom Walton's avatar
Rom Walton committed
99
100
101
102
103
104
105
106
107
char* vboxwrapper_msg_prefix(char* sbuf, int len) {
    char buf[256];
    struct tm tm;
    struct tm *tmp = &tm;
    int n;

    time_t x = time(0);
#ifdef _WIN32
#ifdef __MINGW32__
108
    if ((tmp = localtime(&x)) == NULL)
Rom Walton's avatar
Rom Walton committed
109
#else
110
    if (localtime_s(&tm, &x) == EINVAL)
Rom Walton's avatar
Rom Walton committed
111
112
#endif
#else
113
    if (localtime_r(&x, &tm) == NULL)
Rom Walton's avatar
Rom Walton committed
114
#endif
115
    {
Rom Walton's avatar
Rom Walton committed
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
        strcpy(sbuf, "localtime() failed");
        return sbuf;
    }
    if (strftime(buf, sizeof(buf)-1, "%Y-%m-%d %H:%M:%S", tmp) == 0) {
        strcpy(sbuf, "strftime() failed");
        return sbuf;
    }
#ifdef _WIN32
    n = _snprintf(sbuf, len, "%s (%d):", buf, GetCurrentProcessId());
#else
    n = snprintf(sbuf, len, "%s (%d):", buf, getpid());
#endif
    if (n < 0) {
        strcpy(sbuf, "sprintf() failed");
        return sbuf;
    }
    sbuf[len-1] = 0;    // just in case
    return sbuf;
}

136
137
int parse_port_forward(VBOX_VM& vm, XML_PARSER& xp) {
    char buf2[256];
138
    int host_port=0, guest_port=0, nports=1;
139
    bool is_remote;
140
141
    while (!xp.get_tag()) {
        if (xp.match_tag("/port_forward")) {
142
143
144
145
146
147
148
            if (!host_port) {
                fprintf(stderr,
                    "%s parse_port_forward(): unspecified host port\n",
                    vboxwrapper_msg_prefix(buf2, sizeof(buf2))
                );
                return ERR_XML_PARSE;
            }
149
            if (!guest_port) {
150
151
152
                fprintf(stderr,
                    "%s parse_port_forward(): unspecified guest port\n",
                    vboxwrapper_msg_prefix(buf2, sizeof(buf2))
153
                );
154
                return ERR_XML_PARSE;
155
156
157
158
159
160
            }
            PORT_FORWARD pf;
            pf.host_port = host_port;
            pf.guest_port = guest_port;
            pf.is_remote = is_remote;
            for (int i=0; i<nports; i++) {
161
                vm.port_forwards.push_back(pf);
162
163
164
165
166
167
168
169
170
171
                pf.host_port++;
                pf.guest_port++;
            }
            return 0;
        }
        else if (xp.parse_bool("is_remote", is_remote)) continue;
        else if (xp.parse_int("host_port", host_port)) continue;
        else if (xp.parse_int("guest_port", guest_port)) continue;
        else if (xp.parse_int("nports", nports)) continue;
        else {
172
173
174
175
            fprintf(stderr,
                "%s parse_port_forward(): unparsed %s\n",
                vboxwrapper_msg_prefix(buf2, sizeof(buf2)),
                xp.parsed_tag
176
            );
177
178
179
180
        }
    }
    return ERR_XML_PARSE;
}
Rom Walton's avatar
Rom Walton committed
181

182
int parse_job_file(VBOX_VM& vm) {
183
    INTERMEDIATE_UPLOAD iu;
Rom Walton's avatar
Rom Walton committed
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
    MIOFILE mf;
    string str;
    char buf[1024], buf2[256];

    boinc_resolve_filename(JOB_FILENAME, buf, sizeof(buf));
    FILE* f = boinc_fopen(buf, "r");
    if (!f) {
        fprintf(stderr,
            "%s can't open job file %s\n",
            vboxwrapper_msg_prefix(buf2, sizeof(buf2)), buf
        );
        return ERR_FOPEN;
    }
    mf.init_file(f);
    XML_PARSER xp(&mf);

    if (!xp.parse_start("vbox_job")) return ERR_XML_PARSE;
    while (!xp.get_tag()) {
        if (!xp.is_tag) {
            fprintf(stderr, "%s parse_job_file(): unexpected text %s\n",
                vboxwrapper_msg_prefix(buf, sizeof(buf)), xp.parsed_tag
            );
            continue;
        }
        if (xp.match_tag("/vbox_job")) {
            fclose(f);
            return 0;
        }
        else if (xp.parse_string("vm_disk_controller_type", vm.vm_disk_controller_type)) continue;
        else if (xp.parse_string("vm_disk_controller_model", vm.vm_disk_controller_model)) continue;
        else if (xp.parse_string("os_name", vm.os_name)) continue;
215
        else if (xp.parse_double("memory_size_mb", vm.memory_size_mb)) continue;
Rom Walton's avatar
Rom Walton committed
216
        else if (xp.parse_double("job_duration", vm.job_duration)) continue;
217
        else if (xp.parse_double("minimum_checkpoint_interval", vm.minimum_checkpoint_interval)) continue;
Rom Walton's avatar
Rom Walton committed
218
219
220
        else if (xp.parse_string("fraction_done_filename", vm.fraction_done_filename)) continue;
        else if (xp.parse_bool("enable_cern_dataformat", vm.enable_cern_dataformat)) continue;
        else if (xp.parse_bool("enable_network", vm.enable_network)) continue;
221
        else if (xp.parse_bool("network_bridged_mode", vm.network_bridged_mode)) continue;
Rom Walton's avatar
Rom Walton committed
222
223
        else if (xp.parse_bool("enable_shared_directory", vm.enable_shared_directory)) continue;
        else if (xp.parse_bool("enable_floppyio", vm.enable_floppyio)) continue;
224
        else if (xp.parse_bool("enable_cache_disk", vm.enable_cache_disk)) continue;
225
        else if (xp.parse_bool("enable_isocontextualization", vm.enable_isocontextualization)) continue;
Rom Walton's avatar
Rom Walton committed
226
        else if (xp.parse_bool("enable_remotedesktop", vm.enable_remotedesktop)) continue;
227
        else if (xp.parse_bool("enable_gbac", vm.enable_gbac)) continue;
228
229
        else if (xp.parse_int("pf_guest_port", vm.pf_guest_port)) continue;
        else if (xp.parse_int("pf_host_port", vm.pf_host_port)) continue;
Rom Walton's avatar
Rom Walton committed
230
        else if (xp.parse_string("copy_to_shared", str)) {
231
232
233
234
235
            vm.copy_to_shared.push_back(str);
            continue;
        }
        else if (xp.parse_string("trickle_trigger_file", str)) {
            vm.trickle_trigger_files.push_back(str);
Rom Walton's avatar
Rom Walton committed
236
237
            continue;
        }
238
239
240
241
242
243
        else if (xp.parse_string("intermediate_upload_file", str)) {
            iu.clear();
            iu.file = str;
            vm.intermediate_upload_files.push_back(iu);
            continue;
        }
244
        else if (xp.parse_string("completion_trigger_file", str)) {
245
            vm.completion_trigger_file = str;
246
247
            continue;
        }
248
249
250
251
        else if (xp.parse_string("temporary_exit_trigger_file", str)) {
            vm.temporary_exit_trigger_file = str;
            continue;
        }
252
        else if (xp.match_tag("port_forward")) {
253
            parse_port_forward(vm, xp);
254
        }
255
256
257
        fprintf(stderr, "%s parse_job_file(): unexpected tag %s\n",
            vboxwrapper_msg_prefix(buf, sizeof(buf)), xp.parsed_tag
        );
Rom Walton's avatar
Rom Walton committed
258
259
260
261
262
    }
    fclose(f);
    return ERR_XML_PARSE;
}

263
void write_checkpoint(double elapsed, double cpu, VBOX_VM& vm) {
Rom Walton's avatar
Rom Walton committed
264
265
    FILE* f = fopen(CHECKPOINT_FILENAME, "w");
    if (!f) return;
266
    fprintf(f, "%f %f %d %d\n", elapsed, cpu, vm.pf_host_port, vm.rd_host_port);
Rom Walton's avatar
Rom Walton committed
267
268
269
    fclose(f);
}

270
void read_checkpoint(double& elapsed, double& cpu, VBOX_VM& vm) {
Rom Walton's avatar
Rom Walton committed
271
    double c;
272
    double e;
Rom Walton's avatar
Rom Walton committed
273
274
    int pf_host;
    int rd_host;
275
    elapsed = 0.0;
Rom Walton's avatar
Rom Walton committed
276
277
278
279
280
    cpu = 0.0;
    vm.pf_host_port = 0;
    vm.rd_host_port = 0;
    FILE* f = fopen(CHECKPOINT_FILENAME, "r");
    if (!f) return;
281
    int n = fscanf(f, "%lf %lf %d %d", &e, &c, &pf_host, &rd_host);
Rom Walton's avatar
Rom Walton committed
282
    fclose(f);
283
284
    if (n != 4) return;
    elapsed = e;
Rom Walton's avatar
Rom Walton committed
285
286
287
288
289
290
    cpu = c;
    vm.pf_host_port = pf_host;
    vm.rd_host_port = rd_host;
}

void read_fraction_done(double& frac_done, VBOX_VM& vm) {
291
    char path[MAXPATHLEN];
Rom Walton's avatar
Rom Walton committed
292
293
294
    char buf[256];
    double temp, frac = 0;

295
296
    sprintf(path, "shared/%s", vm.fraction_done_filename.c_str());
    FILE* f = fopen(path, "r");
Rom Walton's avatar
Rom Walton committed
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
    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;
}

320
321
322
323
324
325
326
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;
}

327
void read_completion_file_info(unsigned long& exit_code, bool& is_notice, string& message, VBOX_VM& vm) {
328
329
330
331
332
333
334
335
336
337
    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
338
            exit_code = atoi(buf) != 0;
339
        }
340
        if (fgets(buf, 1024, f) != NULL) {
Rom Walton's avatar
Rom Walton committed
341
            is_notice = atoi(buf) != 0;
342
        }
343
344
345
346
347
348
349
        while (fgets(buf, 1024, f) != NULL) {
            message += buf;
        }
        fclose(f);
    }
}

350
351
352
353
354
355
356
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;
}

357
358
359
360
361
362
363
364
365
366
367
368
369
370
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
371
            is_notice = atoi(buf) != 0;
372
373
374
375
376
377
378
379
        }
        while (fgets(buf, 1024, f) != NULL) {
            message += buf;
        }
        fclose(f);
    }
}

380
381
382
383
384
385
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
386
387
388
// set CPU and network throttling if needed
//
void set_throttles(APP_INIT_DATA& aid, VBOX_VM& vm) {
389
    double x = 0, y = 0;
390

391
    // VirtualBox freaks out if the CPU Usage value is too low to actually
392
393
394
    // do any processing.  It probably wouldn't be so bad if the RDP interface
    // didn't also get hosed by it.
    //
395
396
397
398
    x = aid.global_prefs.cpu_usage_limit;
    // 0 means "no limit"
    //
    if (x == 0.0) x = 100;
399
    // For now set the minimum CPU Usage value to 1.
400
    //
401
402
    if (x < 1) x = 1;
    vm.set_cpu_usage((int)x);
Rom Walton's avatar
Rom Walton committed
403
404
405
406

    // vbox doesn't distinguish up and down bandwidth; use the min of the prefs
    //
    x = aid.global_prefs.max_bytes_sec_up;
407
    y = aid.global_prefs.max_bytes_sec_down;
Rom Walton's avatar
Rom Walton committed
408
    if (y) {
409
        if (!x || y < x) {
Rom Walton's avatar
Rom Walton committed
410
411
412
413
            x = y;
        }
    }
    if (x) {
414
        vm.set_network_usage((int)(x/1024));
Rom Walton's avatar
Rom Walton committed
415
    }
416

Rom Walton's avatar
Rom Walton committed
417
418
419
}

// If the Floppy device has been specified, initialize its state so that
420
421
422
423
// 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
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
//
// 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) {
                fprintf(stderr,
                    "%s can't write init_data.xml to floppy abstration device\n",
                    vboxwrapper_msg_prefix(buf, sizeof(buf))
                );
            }
        } 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);
    }
}

468
// if there's a port for web graphics, tell the client about it
Rom Walton's avatar
Rom Walton committed
469
//
470
void set_web_graphics_url(VBOX_VM& vm) {
471
    char buf[256], buf2[256];
472
473
    for (unsigned int i=0; i<vm.port_forwards.size(); i++) {
        PORT_FORWARD& pf = vm.port_forwards[i];
474
        if (pf.guest_port == vm.pf_guest_port) {
475
            sprintf(buf2, "http://localhost:%d", pf.host_port);
476
            fprintf(stderr, "%s Detected: Web Application Enabled (%s)\n",
477
478
                vboxwrapper_msg_prefix(buf, sizeof(buf)),
                buf2
479
            );
480
            boinc_web_graphics_url(buf2);
481
482
            break;
        }
Rom Walton's avatar
Rom Walton committed
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
    }
}

// set remote desktop information if needed
//
void set_remote_desktop_info(APP_INIT_DATA& /* aid */, VBOX_VM& vm) {
    char buf[256];

    if (vm.rd_host_port) {
        // Write info to disk
        //
        MIOFILE mf;
        FILE* f = boinc_fopen(REMOTEDESKTOP_FILENAME, "w");
        mf.init_file(f);

        mf.printf(
            "<remote_desktop>\n"
            "  <host_port>%d</host_port>\n"
            "</remote_desktop>\n",
            vm.rd_host_port
        );

        fclose(f);

        sprintf(buf, "localhost:%d", vm.rd_host_port);
        boinc_remote_desktop_addr(buf);
    }
}

512
513
// check for trickle trigger files, and send trickles if find them.
//
514
void check_trickle_triggers(VBOX_VM& vm) {
515
    char filename[256], path[MAXPATHLEN], buf[256];
516
517
    for (unsigned int i=0; i<vm.trickle_trigger_files.size(); i++) {
        strcpy(filename, vm.trickle_trigger_files[i].c_str());
518
519
520
521
522
523
524
525
526
        sprintf(path, "shared/%s", filename);
        if (!boinc_file_exists(path)) continue;
        string text;
        int retval = read_file_string(path, text);
        if (retval) {
            fprintf(stderr,
                "%s can't read trickle trigger file %s\n",
                vboxwrapper_msg_prefix(buf, sizeof(buf)), filename
            );
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
        } else {
            retval = boinc_send_trickle_up(
                filename, const_cast<char*>(text.c_str())
            );
            if (retval) {
                fprintf(stderr,
                    "%s boinc_send_trickle_up() failed: %s\n",
                    vboxwrapper_msg_prefix(buf, sizeof(buf)), boincerror(retval)
                );
            } else {
                fprintf(stderr,
                    "%s sent trickle-up of variety %s\n",
                    vboxwrapper_msg_prefix(buf, sizeof(buf)), filename
                );
            }
542
543
544
545
546
        }
        boinc_delete_file(path);
    }
}

547
548
// check for intermediate upload files, and send them if found.
//
549
void check_intermediate_uploads(VBOX_VM& vm) {
550
551
    int retval;
    char filename[256], path[MAXPATHLEN], buf[256];
552
553
    for (unsigned int i=0; i<vm.intermediate_upload_files.size(); i++) {
        strcpy(filename, vm.intermediate_upload_files[i].file.c_str());
554
555
        sprintf(path, "shared/%s", filename);
        if (!boinc_file_exists(path)) continue;
556
        if (!vm.intermediate_upload_files[i].reported && !vm.intermediate_upload_files[i].ignore) {
557
558
559
            fprintf(stderr,
                "%s Reporting an intermediate file. (%s)\n",
                vboxwrapper_msg_prefix(buf, sizeof(buf)),
560
                vm.intermediate_upload_files[i].file.c_str()
561
            );
562
            retval = boinc_upload_file(vm.intermediate_upload_files[i].file);
563
564
565
566
567
            if (retval) {
                fprintf(stderr,
                    "%s boinc_upload_file() failed: %s\n",
                    vboxwrapper_msg_prefix(buf, sizeof(buf)), boincerror(retval)
                );
568
                vm.intermediate_upload_files[i].ignore = true;
569
            } else {
570
                vm.intermediate_upload_files[i].reported = true;
571
            }
572
573
        } else if (vm.intermediate_upload_files[i].reported && !vm.intermediate_upload_files[i].ignore) {
            retval = boinc_upload_status(vm.intermediate_upload_files[i].file);
574
575
576
577
            if (!retval) {
                fprintf(stderr,
                    "%s Intermediate file uploaded. (%s)\n",
                    vboxwrapper_msg_prefix(buf, sizeof(buf)),
578
                    vm.intermediate_upload_files[i].file.c_str()
579
                );
580
                vm.intermediate_upload_files[i].ignore = true;
581
582
583
584
585
            }
        }
    }
}

586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
// see if it's time to send trickle-up reporting elapsed time
//
void check_trickle_period() {
    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;
    fprintf(
        stderr,
        "%s Status Report: Trickle-Up Event.\n",
        vboxwrapper_msg_prefix(buf, sizeof(buf))
    );
    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) {
        fprintf(
            stderr,
            "%s Sending Trickle-Up Event failed (%d).\n",
            vboxwrapper_msg_prefix(buf, sizeof(buf)),
            retval
        );
    }
}

Rom Walton's avatar
Rom Walton committed
617
618
int main(int argc, char** argv) {
    int retval;
619
    int loop_iteration = 0;
Rom Walton's avatar
Rom Walton committed
620
621
622
    BOINC_OPTIONS boinc_options;
    VBOX_VM vm;
    APP_INIT_DATA aid;
623
    double random_checkpoint_factor = 0;
Rom Walton's avatar
Rom Walton committed
624
    double fraction_done = 0;
625
    double current_cpu_time = 0;
626
627
    double starting_cpu_time = 0;
    double last_checkpoint_time = 0;
Rom Walton's avatar
Rom Walton committed
628
    double last_status_report_time = 0;
629
    double stopwatch_starttime = 0;
Rom Walton's avatar
Rom Walton committed
630
    double stopwatch_endtime = 0;
631
    double stopwatch_elapsedtime = 0;
Rom Walton's avatar
Rom Walton committed
632
633
634
635
    double sleep_time = 0;
    double bytes_sent = 0;
    double bytes_received = 0;
    double ncpus = 0;
636
    double memory_size_mb = 0;
637
    double timeout = 0.0;
Rom Walton's avatar
Rom Walton committed
638
    bool report_net_usage = false;
639
    double net_usage_timer = 600;
640
	int vm_image = 0;
Rom Walton's avatar
Rom Walton committed
641
    unsigned long vm_exit_code = 0;
642
    bool is_notice = false;
643
    int temp_delay = 86400;
644
    string message;
Rom Walton's avatar
Rom Walton committed
645
646
647
648
649
650
651
    char buf[256];


    for (int i=1; i<argc; i++) {
        if (!strcmp(argv[i], "--trickle")) {
            trickle_period = atof(argv[++i]);
        }
652
        if (!strcmp(argv[i], "--ncpus")) {
Rom Walton's avatar
Rom Walton committed
653
654
            ncpus = atof(argv[++i]);
        }
655
656
657
        if (!strcmp(argv[i], "--memory_size_mb")) {
            memory_size_mb = atof(argv[++i]);
        }
658
659
660
        if (!strcmp(argv[i], "--vmimage")) {
            vm_image = atoi(argv[++i]);
        }
Rom Walton's avatar
Rom Walton committed
661
662
663
664
665
        if (!strcmp(argv[i], "--register_only")) {
            vm.register_only = true;
        }
    }

666
667
668
669
670
671
672
673
674
675
676
677
    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);

    // Prepare environment for detecting system conditions
    //
    boinc_get_init_data_p(&aid);

    // Log banner
    //
Rom Walton's avatar
Rom Walton committed
678
679
    fprintf(
        stderr,
680
        "%s vboxwrapper (%d.%d.%d): starting\n",
681
        vboxwrapper_msg_prefix(buf, sizeof(buf)),
682
683
684
        BOINC_MAJOR_VERSION,
        BOINC_MINOR_VERSION,
        VBOXWRAPPER_RELEASE
Rom Walton's avatar
Rom Walton committed
685
686
    );

687
688
    // Log important information
    //
689
690
691

    // Choose a random interleave value for checkpoint intervals to stagger disk I/O.
    // 
692
693
694
695
696
697
    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)));
    }
698
699
700
    random_checkpoint_factor = (double)(((int)(drand() * 100000.0)) % 600);
    fprintf(
        stderr,
701
        "%s Feature: Checkpoint interval offset (%d seconds)\n",
702
        vboxwrapper_msg_prefix(buf, sizeof(buf)),
703
        (int)random_checkpoint_factor
704
705
706
707
708
709
710
711
712
713
714
715
716
717
    );

    // Display trickle value if specified
    //
    if (trickle_period > 0.0) {
        fprintf(
            stderr,
            "%s Feature: Enabling trickle-ups (Interval: %f)\n",
            vboxwrapper_msg_prefix(buf, sizeof(buf)), trickle_period
        );
    }

    // Initialize system services
    // 
718
719
720
721
722
723
724
725
726
727
728
729
730
731
#if defined(_WIN32) && defined(USE_WINSOCK)
    WSADATA wsdata;
    retval = WSAStartup( MAKEWORD( 1, 1 ), &wsdata);
    if (retval) {
        fprintf(
            stderr,
            "%s can't initialize winsock: %d\n",
            vboxwrapper_msg_prefix(buf, sizeof(buf)),
            retval
        );
        boinc_finish(retval);
    }
#endif

732
733
734
735
736
737
    // Check for architecture incompatibilities
    // 
#if defined(_WIN32) && defined(_M_IX86)
    if (strstr(aid.host_info.os_version, "x64")) {
        fprintf(
            stderr,
738
            "%s 64-bit version of BOINC is required, please upgrade. Rescheduling execution for a later date.\n",
739
740
741
742
743
744
            vboxwrapper_msg_prefix(buf, sizeof(buf))
        );
        boinc_temporary_exit(86400, "Architecture incompatibility detected.");
    }
#endif

745
746
    // Initialize VM Hypervisor
    //
747
748
749
750
    retval = vm.initialize();
    if (retval) {
        fprintf(
            stderr,
751
            "%s Could not detect VM Hypervisor. Rescheduling execution for a later date.\n",
752
            vboxwrapper_msg_prefix(buf, sizeof(buf))
753
        );
754
        boinc_temporary_exit(86400, "Detection of VM Hypervisor failed.");
755
756
    }

757
758
759
    // Record what version of VirtualBox was used.
    // 
    if (!vm.virtualbox_version.empty()) {
Rom Walton's avatar
Rom Walton committed
760
761
        fprintf(
            stderr,
762
            "%s Detected: VirtualBox %s\n",
Rom Walton's avatar
Rom Walton committed
763
            vboxwrapper_msg_prefix(buf, sizeof(buf)),
764
765
766
767
            vm.virtualbox_version.c_str()
        );
    }

768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
    // Record if anonymous platform was used.
    // 
    if (boinc_file_exists((std::string(aid.project_dir) + std::string("/app_info.xml")).c_str())) {
        fprintf(
            stderr,
            "%s Detected: Anonymous Platform Enabled\n",
            vboxwrapper_msg_prefix(buf, sizeof(buf))
        );
    }

    // Record if the sandboxed configuration is going to be used.
    //
    if (aid.using_sandbox) {
        fprintf(
            stderr,
            "%s Detected: Sandbox Configuration Enabled\n",
            vboxwrapper_msg_prefix(buf, sizeof(buf))
        );
    }

Rom Walton's avatar
Rom Walton committed
788
    // Record which mode VirtualBox should be started in.
789
    //
790
    if (aid.vbox_window || boinc_is_standalone()) {
791
792
793
794
795
796
797
798
799
800
801
        fprintf(
            stderr,
            "%s Detected: Headless Mode Disabled\n",
            vboxwrapper_msg_prefix(buf, sizeof(buf))
        );
        vm.headless = false;
    }

    // Check for invalid confgiurations.
    //
    if (aid.using_sandbox && aid.vbox_window) {
Rom Walton's avatar
Rom Walton committed
802
        vboxwrapper_msg_prefix(buf, sizeof(buf));
803
804
805
        fprintf(
            stderr,
            "%s Invalid configuration detected.\n"
Rom Walton's avatar
Rom Walton committed
806
807
808
            "%s NOTE: BOINC cannot be installed as a service and run VirtualBox in headfull mode at the same time.\n",
            buf,
            buf
809
        );
Rom Walton's avatar
Rom Walton committed
810
        boinc_temporary_exit(86400, "Incompatible configuration detected.");
811
812
    }

813
    // Check against known incompatible versions of VirtualBox.  
814
815
    // VirtualBox 4.2.6 crashes during snapshot operations
    // and 4.2.18 fails to restore from snapshots properly.
816
    //
817
    if ((vm.virtualbox_version.find("4.2.6") != std::string::npos) || 
818
819
        (vm.virtualbox_version.find("4.2.18") != std::string::npos) || 
        (vm.virtualbox_version.find("4.3.0") != std::string::npos) ) {
820
821
822
823
824
        fprintf(
            stderr,
            "%s Incompatible version of VirtualBox detected. Please upgrade to a later version.\n",
            vboxwrapper_msg_prefix(buf, sizeof(buf))
        );
825
826
827
828
        boinc_temporary_exit(86400,
            "Incompatible version of VirtualBox detected; please upgrade.",
            true
        );
829
830
    }

831
832
833
834
    // 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.
    //
835
    if (!vm.is_system_ready(message)) {
836
837
        fprintf(
            stderr,
838
            "%s Could not communicate with VM Hypervisor. Rescheduling execution for a later date.\n",
839
840
            vboxwrapper_msg_prefix(buf, sizeof(buf))
        );
841
        boinc_temporary_exit(86400, message.c_str());
842
843
    }

844
845
    // Parse Job File
    //
846
    retval = parse_job_file(vm);
Rom Walton's avatar
Rom Walton committed
847
848
849
850
851
852
853
854
855
856
    if (retval) {
        fprintf(
            stderr,
            "%s can't parse job file: %d\n",
            vboxwrapper_msg_prefix(buf, sizeof(buf)),
            retval
        );
        boinc_finish(retval);
    }

857
858
    // Record which mode VirtualBox should be started in.
    //
859
860
861
862
863
864
    fprintf(
        stderr,
        "%s Detected: Minimum checkpoint interval (%f seconds)\n",
        vboxwrapper_msg_prefix(buf, sizeof(buf)),
        vm.minimum_checkpoint_interval
    );
865

Rom Walton's avatar
Rom Walton committed
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
    // Validate whatever configuration options we can
    //
    if (vm.enable_shared_directory) {
        if (boinc_file_exists("shared")) {
            if (!is_dir("shared")) {
                fprintf(
                    stderr,
                    "%s 'shared' exists but is not a directory.\n",
                    vboxwrapper_msg_prefix(buf, sizeof(buf))
                );
            }
        } else {
            retval = boinc_mkdir("shared");
            if (retval) {
                fprintf(stderr,
                    "%s couldn't created shared directory: %s.\n",
                    vboxwrapper_msg_prefix(buf, sizeof(buf)),
                    boincerror(retval)
                );
            }
        }
    }

    // Copy files to the shared directory
    //
891
892
    if (vm.enable_shared_directory && vm.copy_to_shared.size()) {
        for (vector<string>::iterator iter = vm.copy_to_shared.begin(); iter != vm.copy_to_shared.end(); iter++) {
Rom Walton's avatar
Rom Walton committed
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
            string source = *iter;
            string destination = string("shared/") + *iter;
            if (!boinc_file_exists(destination.c_str())) {
                if (!boinc_copy(source.c_str(), destination.c_str())) {
                    fprintf(stderr,
                        "%s successfully copied '%s' to the shared directory.\n",
                        vboxwrapper_msg_prefix(buf, sizeof(buf)),
                        source.c_str()
                    );
                } else {
                    fprintf(stderr,
                        "%s failed to copy '%s' to the shared directory.\n",
                        vboxwrapper_msg_prefix(buf, sizeof(buf)),
                        source.c_str()
                    );
                }
            }
        }
    }

913
914
    // Configure Instance specific VM Parameters
    //
Rom Walton's avatar
Rom Walton committed
915
916
917
918
    vm.vm_master_name = "boinc_";
    vm.image_filename = IMAGE_FILENAME_COMPLETE;
    if (boinc_is_standalone()) {
        vm.vm_master_name += "standalone";
919
        vm.vm_master_description = "standalone";
Rom Walton's avatar
Rom Walton committed
920
        if (vm.enable_floppyio) {
921
922
923
            sprintf(buf, "%s.%s",
                FLOPPY_IMAGE_FILENAME, FLOPPY_IMAGE_FILENAME_EXTENSION
            );
Rom Walton's avatar
Rom Walton committed
924
925
926
            vm.floppy_image_filename = buf;
        }
    } else {
927
        vm.vm_master_name += md5_string(std::string(aid.result_name)).substr(0, 16);
928
        vm.vm_master_description = aid.result_name;
929
		if (vm_image) {
930
931
932
            sprintf(buf, "%s_%d.%s",
                IMAGE_FILENAME, vm_image, IMAGE_FILENAME_EXTENSION
            );
933
934
            vm.image_filename = buf;
		}
Rom Walton's avatar
Rom Walton committed
935
        if (vm.enable_floppyio) {
936
937
938
939
            sprintf(buf, "%s_%d.%s",
                FLOPPY_IMAGE_FILENAME, aid.slot,
                FLOPPY_IMAGE_FILENAME_EXTENSION
            );
Rom Walton's avatar
Rom Walton committed
940
941
            vm.floppy_image_filename = buf;
        }
942
943
    }
    if (vm.enable_cache_disk) {
944
        vm.cache_disk_filename = CACHE_DISK_FILENAME;
945
946
    }
    if (vm.enable_isocontextualization) {
947
        vm.iso_image_filename = ISO_IMAGE_FILENAME;
Rom Walton's avatar
Rom Walton committed
948
949
950
951
952
953
954
955
956
957
958
    }
    if (aid.ncpus > 1.0 || ncpus > 1.0) {
        if (ncpus) {
            sprintf(buf, "%d", (int)ceil(ncpus));
        } else {
            sprintf(buf, "%d", (int)ceil(aid.ncpus));
        }
        vm.vm_cpu_count = buf;
    } else {
        vm.vm_cpu_count = "1";
    }
959
960
961
962
963
964
965
    if (vm.memory_size_mb > 1.0 || memory_size_mb > 1.0) {
        if (memory_size_mb) {
            sprintf(buf, "%d", (int)ceil(memory_size_mb));
        } else {
            sprintf(buf, "%d", (int)ceil(vm.memory_size_mb));
        }
    }
Rom Walton's avatar
Rom Walton committed
966
967
968
969
970
    if (aid.vbox_window && !aid.using_sandbox) {
        vm.headless = false;
    }

    // Restore from checkpoint
971
    //
972
    read_checkpoint(elapsed_time, current_cpu_time, vm);
973
    starting_cpu_time = current_cpu_time;
974
    last_checkpoint_time = current_cpu_time;
Rom Walton's avatar
Rom Walton committed
975
976

    // Should we even try to start things up?
977
    //
Rom Walton's avatar
Rom Walton committed
978
979
980
981
    if (vm.job_duration && (elapsed_time > vm.job_duration)) {
        return EXIT_TIME_LIMIT_EXCEEDED;
    }

982
    retval = vm.run((current_cpu_time > 0));
Rom Walton's avatar
Rom Walton committed
983
    if (retval) {
984
        // All 'failure to start' errors are unrecoverable by default
985
        bool   unrecoverable_error = true;
986
        bool   skip_cleanup = false;
987
        bool   do_dump_hypervisor_logs = false;
988
        string error_reason;
989
        const char*  temp_reason = "";
Rom Walton's avatar
Rom Walton committed
990

991
992
993
994
995
        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;
996
            temp_reason = "VM environment needed to be cleaned up.";
997
        } else if (ERR_NOT_EXITED == retval) {
998
999
1000
            error_reason =
                "   NOTE: VM was already running.\n"
                "    BOINC will be notified that it needs to clean up the environment.\n"
For faster browsing, not all history is shown. View entire blame