vbox_vboxmanage.cpp 60.2 KB
Newer Older
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
19
20
21
#ifdef _WIN32
#include "boinc_win.h"
#include "win_util.h"
#else
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <string>
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <unistd.h>
38
#endif
39
40
41

using std::string;

42
43
44
#if defined(_MSC_VER)
#define getcwd      _getcwd
#define stricmp     _stricmp
45
#define snprintf    _snprintf
46
47
#endif

48
49
50
51
52
53
54
55
56
57
58
#include "diagnostics.h"
#include "filesys.h"
#include "parse.h"
#include "str_util.h"
#include "str_replace.h"
#include "util.h"
#include "error_numbers.h"
#include "procinfo.h"
#include "network.h"
#include "boinc_api.h"
#include "floppyio.h"
59
#include "vboxlogging.h"
60
#include "vboxwrapper.h"
61
62
63
64
#include "vbox_common.h"
#include "vbox_vboxmanage.h"


65
66
namespace vboxmanage {

67
68
69
70
71
72
VBOX_VM::VBOX_VM() {
}

VBOX_VM::~VBOX_VM() {
}

73
74
75
76
77
78
79
80
81
82
83
84

int VBOX_VM::initialize() {
    int rc = 0;
    string old_path;
    string new_path;
    string command;
    string output;
    APP_INIT_DATA aid;
    bool force_sandbox = false;

    boinc_get_init_data_p(&aid);
    get_install_directory(virtualbox_install_directory);
85
    get_scratch_directory(virtualbox_scratch_directory);
86

87
88
89
90
91
92
93
94
95
    // Prep the environment so we can execute the vboxmanage application
    //
#ifdef _WIN32
    if (!virtualbox_install_directory.empty())
    {
        old_path = getenv("PATH");
        new_path = virtualbox_install_directory + ";" + old_path;

        if (!SetEnvironmentVariable("PATH", const_cast<char*>(new_path.c_str()))) {
96
            vboxlog_msg("Failed to modify the search path.");
97
98
        }
    }
99
100
101
102
103
104
105
106
107
108
109
110
#else
    old_path = getenv("PATH");
    if(boinc_file_exists("/usr/local/bin/VboxManage")) {
        new_path = "/usr/local/bin/:" + old_path;
    }
    if(boinc_file_exists("/usr/bin/VboxManage")) {
        new_path = "/usr/bin/:" + old_path;
    }
    // putenv does not copy its input buffer, so we must use setenv
    if (setenv("PATH", const_cast<char*>(new_path.c_str()), 1)) {
        vboxlog_msg("Failed to modify the search path.");
    }
111
#endif
112
113
114
115
116
117
118
119

    // Determine the VirtualBox home directory.  Overwrite as needed.
    //
    if (getenv("VBOX_USER_HOME")) {
        virtualbox_home_directory = getenv("VBOX_USER_HOME");
    } else {
        // If the override environment variable isn't specified then
        // it is based of the current users HOME directory.
120
121
122
#ifdef _WIN32
        virtualbox_home_directory = getenv("USERPROFILE");
#else
123
        virtualbox_home_directory = getenv("HOME");
124
#endif
125
126
127
128
129
        virtualbox_home_directory += "/.VirtualBox";
    }

    // On *nix style systems, VirtualBox expects that there is a home directory specified
    // by environment variable.  When it doesn't exist it attempts to store logging information
130
131
    // in root's home directory.  Bad things happen if the process attempts to use root's home
    // directory.
132
133
134
    //
    // if the HOME environment variable is missing force VirtualBox to use a directory it
    // has a reasonable chance of writing log files too.
135
#ifndef _WIN32
136
137
138
    if (NULL == getenv("HOME")) {
        force_sandbox = true;
    }
139
#endif
140
141
142
143
144
145
146
147
148

    // Set the location in which the VirtualBox Configuration files can be
    // stored for this instance.
    if (aid.using_sandbox || force_sandbox) {
        virtualbox_home_directory = aid.project_dir;
        virtualbox_home_directory += "/../virtualbox";

        if (!boinc_file_exists(virtualbox_home_directory.c_str())) boinc_mkdir(virtualbox_home_directory.c_str());

149
150
#ifdef _WIN32
        if (!SetEnvironmentVariable("VBOX_USER_HOME", const_cast<char*>(virtualbox_home_directory.c_str()))) {
151
            vboxlog_msg("Failed to modify the search path.");
152
153
        }
#else
154
155
        // putenv does not copy its input buffer, so we must use setenv
        if (setenv("VBOX_USER_HOME", const_cast<char*>(virtualbox_home_directory.c_str()), 1)) {
156
            vboxlog_msg("Failed to modify the VBOX_USER_HOME path.");
157
        }
158
#endif
159
160
    }

161
162
163
164
165
166
167
168
#ifdef _WIN32
    // Launch vboxsvc manually so that the DCOM subsystem won't be able too.  Our version
    // will have permission and direction to write its state information to the BOINC
    // data directory.
    //
    launch_vboxsvc();
#endif

169
    rc = get_version_information(virtualbox_version_raw, virtualbox_version_display);
170
171
    if (rc) return rc;

172
    get_guest_additions(virtualbox_guest_additions);
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190

    return 0;
}

int VBOX_VM::create_vm() {
    string command;
    string output;
    string virtual_machine_slot_directory;
    string default_interface;
    APP_INIT_DATA aid;
    bool disable_acceleration = false;
    char buf[256];
    int retval;

    boinc_get_init_data_p(&aid);
    get_slot_directory(virtual_machine_slot_directory);


191
192
193
    vboxlog_msg("Create VM. (%s, slot#%d)", vm_master_name.c_str(), aid.slot);


194
195
196
197
198
199
200
201
    // Reset VM name in case it was changed while deregistering a stale VM
    //
    vm_name = vm_master_name;

    // Fixup chipset and drive controller information for known configurations
    //
    if (enable_isocontextualization) {
        if ("PIIX4" == vm_disk_controller_model) {
202
            vboxlog_msg("Updating drive controller type and model for desired configuration.");
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
            vm_disk_controller_type = "sata";
            vm_disk_controller_model = "IntelAHCI";
        }
    }

    // Create and register the VM
    //
    command  = "createvm ";
    command += "--name \"" + vm_name + "\" ";
    command += "--basefolder \"" + virtual_machine_slot_directory + "\" ";
    command += "--ostype \"" + os_name + "\" ";
    command += "--register";
    
    retval = vbm_popen(command, output, "create");
    if (retval) return retval;

    // Tweak the VM's Description
    //
    command  = "modifyvm \"" + vm_name + "\" ";
    command += "--description \"" + vm_master_description + "\" ";

    vbm_popen(command, output, "modifydescription", false, false);

226
    // Tweak the VM's Memory Size
227
    //
228
229
    vboxlog_msg("Setting Memory Size for VM. (%dMB)", (int)memory_size_mb);
    sprintf(buf, "%d", (int)memory_size_mb);
230
    command  = "modifyvm \"" + vm_name + "\" ";
231
    command += "--memory " + string(buf) + " ";
232

233
    retval = vbm_popen(command, output, "modifymem");
234
235
    if (retval) return retval;

236
    // Tweak the VM's CPU Count
237
    //
238
    vboxlog_msg("Setting CPU Count for VM. (%s)", vm_cpu_count.c_str());
239
    command  = "modifyvm \"" + vm_name + "\" ";
240
    command += "--cpus " + vm_cpu_count + " ";
241

242
    retval = vbm_popen(command, output, "modifycpu");
243
244
245
246
    if (retval) return retval;

    // Tweak the VM's Chipset Options
    //
247
    vboxlog_msg("Setting Chipset Options for VM.");
248
249
250
251
252
253
254
255
256
    command  = "modifyvm \"" + vm_name + "\" ";
    command += "--acpi on ";
    command += "--ioapic on ";

    retval = vbm_popen(command, output, "modifychipset");
    if (retval) return retval;

    // Tweak the VM's Boot Options
    //
257
    vboxlog_msg("Setting Boot Options for VM.");
258
259
260
261
262
263
264
265
266
267
268
269
    command  = "modifyvm \"" + vm_name + "\" ";
    command += "--boot1 disk ";
    command += "--boot2 dvd ";
    command += "--boot3 none ";
    command += "--boot4 none ";

    retval = vbm_popen(command, output, "modifyboot");
    if (retval) return retval;

    // Tweak the VM's Network Configuration
    //
    if (network_bridged_mode) {
270
        vboxlog_msg("Setting Network Configuration for Bridged Mode.");
271
272
273
274
275
276
277
278
        command  = "modifyvm \"" + vm_name + "\" ";
        command += "--nic1 bridged ";
        command += "--cableconnected1 off ";

        retval = vbm_popen(command, output, "set bridged mode");
        if (retval) return retval;

        get_default_network_interface(default_interface);
279
280

        vboxlog_msg("Setting Bridged Interface. (%s)", default_interface.c_str());
281
282
283
284
285
286
287
288
        command  = "modifyvm \"" + vm_name + "\" ";
        command += "--bridgeadapter1 \"";
        command += default_interface;
        command += "\" ";

        retval = vbm_popen(command, output, "set bridged interface");
        if (retval) return retval;
    } else {
289
        vboxlog_msg("Setting Network Configuration for NAT.");
290
291
292
293
294
        command  = "modifyvm \"" + vm_name + "\" ";
        command += "--nic1 nat ";
        command += "--natdnsproxy1 on ";
        command += "--cableconnected1 off ";

295
        retval = vbm_popen(command, output, "set nat mode");
296
297
298
        if (retval) return retval;
    }

299
300
301
302
303
304
305
306
307
308
309
310
311
312
    if (enable_network) {
        vboxlog_msg("Enabling VM Network Access.");
        command  = "modifyvm \"" + vm_name + "\" ";
        command += "--cableconnected1 on ";
        retval = vbm_popen(command, output, "enable network");
        if (retval) return retval;
    } else {
        vboxlog_msg("Disabling VM Network Access.");
        command  = "modifyvm \"" + vm_name + "\" ";
        command += "--cableconnected1 off ";
        retval = vbm_popen(command, output, "disable network");
        if (retval) return retval;
    }

313
314
    // Tweak the VM's USB Configuration
    //
315
    vboxlog_msg("Disabling USB Support for VM.");
316
317
318
319
320
321
322
    command  = "modifyvm \"" + vm_name + "\" ";
    command += "--usb off ";

    vbm_popen(command, output, "modifyusb", false, false);

    // Tweak the VM's COM Port Support
    //
323
    vboxlog_msg("Disabling COM Port Support for VM.");
324
325
326
327
328
329
330
331
    command  = "modifyvm \"" + vm_name + "\" ";
    command += "--uart1 off ";
    command += "--uart2 off ";

    vbm_popen(command, output, "modifycom", false, false);

    // Tweak the VM's LPT Port Support
    //
332
    vboxlog_msg("Disabling LPT Port Support for VM.");
333
334
335
336
337
338
339
340
    command  = "modifyvm \"" + vm_name + "\" ";
    command += "--lpt1 off ";
    command += "--lpt2 off ";

    vbm_popen(command, output, "modifylpt", false, false);

    // Tweak the VM's Audio Support
    //
341
    vboxlog_msg("Disabling Audio Support for VM.");
342
343
344
345
346
347
348
    command  = "modifyvm \"" + vm_name + "\" ";
    command += "--audio none ";

    vbm_popen(command, output, "modifyaudio", false, false);

    // Tweak the VM's Clipboard Support
    //
349
    vboxlog_msg("Disabling Clipboard Support for VM.");
350
351
352
353
354
355
356
    command  = "modifyvm \"" + vm_name + "\" ";
    command += "--clipboard disabled ";

    vbm_popen(command, output, "modifyclipboard", false, false);

    // Tweak the VM's Drag & Drop Support
    //
357
    vboxlog_msg("Disabling Drag and Drop Support for VM.");
358
359
360
361
362
363
364
365
366
367
    command  = "modifyvm \"" + vm_name + "\" ";
    command += "--draganddrop disabled ";

    vbm_popen(command, output, "modifydragdrop", false, false);

    // Check to see if the processor supports hardware acceleration for virtualization
    // If it doesn't, disable the use of it in VirtualBox. Multi-core jobs require hardware
    // acceleration and actually override this setting.
    //
    if (!strstr(aid.host_info.p_features, "vmx") && !strstr(aid.host_info.p_features, "svm")) {
368
        vboxlog_msg("Hardware acceleration CPU extensions not detected. Disabling VirtualBox hardware acceleration support.");
369
370
371
        disable_acceleration = true;
    }
    if (strstr(aid.host_info.p_features, "hypervisor")) {
372
        vboxlog_msg("Running under Hypervisor. Disabling VirtualBox hardware acceleration support.");
373
374
375
376
        disable_acceleration = true;
    }
    if (is_boinc_client_version_newer(aid, 7, 2, 16)) {
        if (aid.vm_extensions_disabled) {
377
            vboxlog_msg("Hardware acceleration failed with previous execution. Disabling VirtualBox hardware acceleration support.");
378
379
380
381
382
383
            disable_acceleration = true;
        }
    } else {
        if (vm_cpu_count == "1") {
            // Keep this around for older clients.  Removing this for older clients might
            // lead to a machine that will only return crashed VM reports.
384
385
            vboxlog_msg("Legacy fallback configuration detected. Disabling VirtualBox hardware acceleration support.");
            vboxlog_msg("NOTE: Upgrading to BOINC 7.2.16 or better may re-enable hardware acceleration.");
386
387
388
389
390
391
392
393
            disable_acceleration = true;
        }
    }

    // Only allow disabling of hardware acceleration on 32-bit VM types, 64-bit VM types require it.
    //
    if (os_name.find("_64") == std::string::npos) {
        if (disable_acceleration) {
394
            vboxlog_msg("Disabling hardware acceleration support for virtualization.");
395
396
397
398
399
400
401
402
            command  = "modifyvm \"" + vm_name + "\" ";
            command += "--hwvirtex off ";

            retval = vbm_popen(command, output, "VT-x/AMD-V support");
            if (retval) return retval;
        }
    } else if (os_name.find("_64") != std::string::npos) {
        if (disable_acceleration) {
403
            vboxlog_msg("ERROR: Invalid configuration.  VM type requires acceleration but the current configuration cannot support it.");
404
405
406
407
408
409
410
411
            return ERR_INVALID_PARAM;
        }
    }

    // Add storage controller to VM
    // See: http://www.virtualbox.org/manual/ch08.html#vboxmanage-storagectl
    // See: http://www.virtualbox.org/manual/ch05.html#iocaching
    //
412
    vboxlog_msg("Adding storage controller(s) to VM.");
413
414
415
416
417
418
419
420
421
422
423
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
    command  = "storagectl \"" + vm_name + "\" ";
    command += "--name \"Hard Disk Controller\" ";
    command += "--add \"" + vm_disk_controller_type + "\" ";
    command += "--controller \"" + vm_disk_controller_model + "\" ";
    if (
         (vm_disk_controller_type == "sata") || (vm_disk_controller_type == "SATA") ||
         (vm_disk_controller_type == "scsi") || (vm_disk_controller_type == "SCSI") ||
         (vm_disk_controller_type == "sas") || (vm_disk_controller_type == "SAS")
    ) {
        command += "--hostiocache off ";
    }
    if ((vm_disk_controller_type == "sata") || (vm_disk_controller_type == "SATA")) {
        if (is_virtualbox_version_newer(4, 3, 0)) {
            command += "--portcount 3";
        } else {
            command += "--sataportcount 3";
        }
    }

    retval = vbm_popen(command, output, "add storage controller (fixed disk)");
    if (retval) return retval;

    // Add storage controller for a floppy device if desired
    //
    if (enable_floppyio) {
        command  = "storagectl \"" + vm_name + "\" ";
        command += "--name \"Floppy Controller\" ";
        command += "--add floppy ";

        retval = vbm_popen(command, output, "add storage controller (floppy)");
        if (retval) return retval;
    }

    if (enable_isocontextualization) {

        // Add virtual ISO 9660 disk drive to VM
        //
450
        vboxlog_msg("Adding virtual ISO 9660 disk drive to VM. (%s)", iso_image_filename.c_str());
451
452
453
454
455
456
457
458
459
460
461
462
        command  = "storageattach \"" + vm_name + "\" ";
        command += "--storagectl \"Hard Disk Controller\" ";
        command += "--port 0 ";
        command += "--device 0 ";
        command += "--type dvddrive ";
        command += "--medium \"" + virtual_machine_slot_directory + "/" + iso_image_filename + "\" ";

        retval = vbm_popen(command, output, "storage attach (ISO 9660 image)");
        if (retval) return retval;

        // Add guest additions to the VM
        //
463
464
465
466
467
468
469
470
        if (virtualbox_guest_additions.size()) {
            vboxlog_msg("Adding VirtualBox Guest Additions to VM.");
            command  = "storageattach \"" + vm_name + "\" ";
            command += "--storagectl \"Hard Disk Controller\" ";
            command += "--port 2 ";
            command += "--device 0 ";
            command += "--type dvddrive ";
            command += "--medium \"" + virtualbox_guest_additions + "\" ";
471

472
473
474
            retval = vbm_popen(command, output, "storage attach (guest additions image)");
            if (retval) return retval;
        }
475
476
477
478

        // Add a virtual cache disk drive to VM
        //
        if (enable_cache_disk){
479
            vboxlog_msg("Adding virtual cache disk drive to VM. (%s)", cache_disk_filename.c_str());
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
            command  = "storageattach \"" + vm_name + "\" ";
            command += "--storagectl \"Hard Disk Controller\" ";
            command += "--port 1 ";
            command += "--device 0 ";
            command += "--type hdd ";
            command += "--setuuid \"\" ";
            command += "--medium \"" + virtual_machine_slot_directory + "/" + cache_disk_filename + "\" ";

            retval = vbm_popen(command, output, "storage attach (cached disk)");
            if (retval) return retval;
        }

    } else {

        // Adding virtual hard drive to VM
        //
496
        vboxlog_msg("Adding virtual disk drive to VM. (%s)", image_filename.c_str());
497
498
499
500
501
502
503
504
505
506
507
508
509
        command  = "storageattach \"" + vm_name + "\" ";
        command += "--storagectl \"Hard Disk Controller\" ";
        command += "--port 0 ";
        command += "--device 0 ";
        command += "--type hdd ";
        command += "--setuuid \"\" ";
        command += "--medium \"" + virtual_machine_slot_directory + "/" + image_filename + "\" ";

        retval = vbm_popen(command, output, "storage attach (fixed disk)");
        if (retval) return retval;

        // Add guest additions to the VM
        //
510
511
512
513
514
515
516
517
        if (virtualbox_guest_additions.size()) {
            vboxlog_msg("Adding VirtualBox Guest Additions to VM.");
            command  = "storageattach \"" + vm_name + "\" ";
            command += "--storagectl \"Hard Disk Controller\" ";
            command += "--port 1 ";
            command += "--device 0 ";
            command += "--type dvddrive ";
            command += "--medium \"" + virtualbox_guest_additions + "\" ";
518

519
520
521
            retval = vbm_popen(command, output, "storage attach (guest additions image)");
            if (retval) return retval;
        }
522
523
524
525
526
527
528
529
530
531
532

    }

    // Adding virtual floppy disk drive to VM
    //
    if (enable_floppyio) {

        // Put in place the FloppyIO abstraction
        //
        // NOTE: This creates the floppy.img file at runtime for use by the VM.
        //
533
        pFloppy = new FloppyIONS::FloppyIO(floppy_image_filename.c_str());
534
        if (!pFloppy->ready()) {
535
536
            vboxlog_msg("Creating virtual floppy image failed.");
            vboxlog_msg("Error Code '%d' Error Message '%s'", pFloppy->error, pFloppy->errorStr.c_str());
537
538
539
            return ERR_FWRITE;
        }

540
        vboxlog_msg("Adding virtual floppy disk drive to VM.");
541
542
543
544
545
546
547
548
549
550
551
        command  = "storageattach \"" + vm_name + "\" ";
        command += "--storagectl \"Floppy Controller\" ";
        command += "--port 0 ";
        command += "--device 0 ";
        command += "--medium \"" + virtual_machine_slot_directory + "/" + floppy_image_filename + "\" ";

        retval = vbm_popen(command, output, "storage attach (floppy disk)");
        if (retval) return retval;

    }

552
553
554
555
556
557
558
559
560
561
562
563
564
565
    // Add network bandwidth throttle group
    //
    if (is_virtualbox_version_newer(4, 2, 0)) {
        vboxlog_msg("Adding network bandwidth throttle group to VM. (Defaulting to 1024GB)");
        command  = "bandwidthctl \"" + vm_name + "\" ";
        command += "add \"" + vm_name + "_net\" ";
        command += "--type network ";
        command += "--limit 1024G";
        command += " ";

        retval = vbm_popen(command, output, "network throttle group (add)");
        if (retval) return retval;
    }

566
567
568
569
570
571
    // Enable the network adapter if a network connection is required.
    //
    if (enable_network) {
        // set up port forwarding
        //
        if (pf_guest_port) {
572
            VBOX_PORT_FORWARD pf;
573
574
575
576
577
578
579
580
581
582
            pf.guest_port = pf_guest_port;
            pf.host_port = pf_host_port;
            if (!pf_host_port) {
                retval = boinc_get_port(false, pf.host_port);
                if (retval) return retval;
                pf_host_port = pf.host_port;
            }
            port_forwards.push_back(pf);
        }
        for (unsigned int i=0; i<port_forwards.size(); i++) {
583
584
585
            VBOX_PORT_FORWARD& pf = port_forwards[i];

            vboxlog_msg("forwarding host port %d to guest port %d", pf.host_port, pf.guest_port);
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603

            // Add new firewall rule
            //
            sprintf(buf, ",tcp,%s,%d,,%d",
                pf.is_remote?"":"127.0.0.1",
                pf.host_port, pf.guest_port
            );
            command  = "modifyvm \"" + vm_name + "\" ";
            command += "--natpf1 \"" + string(buf) + "\" ";

            retval = vbm_popen(command, output, "add updated port forwarding rule");
            if (retval) return retval;
        }
    }

    // If the VM wants to enable remote desktop for the VM do it here
    //
    if (enable_remotedesktop) {
604
        vboxlog_msg("Enabling remote desktop for VM.");
605
        if (!is_extpack_installed()) {
606
            vboxlog_msg("Required extension pack not installed, remote desktop not enabled.");
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
        } else {
            retval = boinc_get_port(false, rd_host_port);
            if (retval) return retval;

            sprintf(buf, "%d", rd_host_port);
            command  = "modifyvm \"" + vm_name + "\" ";
            command += "--vrde on ";
            command += "--vrdeextpack default ";
            command += "--vrdeauthlibrary default ";
            command += "--vrdeauthtype null ";
            command += "--vrdeport " + string(buf) + " ";

            retval = vbm_popen(command, output, "remote desktop");
            if(retval) return retval;
        }
    }

624
    // Enable the shared folder if a shared folder is specified.
625
626
    //
    if (enable_shared_directory) {
627
        vboxlog_msg("Enabling shared directory for VM.");
628
629
630
631
632
633
        command  = "sharedfolder add \"" + vm_name + "\" ";
        command += "--name \"shared\" ";
        command += "--hostpath \"" + virtual_machine_slot_directory + "/shared\"";

        retval = vbm_popen(command, output, "enable shared dir");
        if (retval) return retval;
634
    }
635

636
637
638
    // Enable the scratch folder if a scratch folder is specified.
    //
    if (enable_scratch_directory) {
639
640
641
642
643
644
645
        vboxlog_msg("Enabling scratch shared directory for VM.");
        command  = "sharedfolder add \"" + vm_name + "\" ";
        command += "--name \"scratch\" ";
        command += "--hostpath \"" + virtualbox_scratch_directory + "\"";

        retval = vbm_popen(command, output, "enable scratch shared dir");
        if (retval) return retval;
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
    }

    return 0;
}

int VBOX_VM::register_vm() {
    string command;
    string output;
    string virtual_machine_slot_directory;
    APP_INIT_DATA aid;
    int retval;

    boinc_get_init_data_p(&aid);
    get_slot_directory(virtual_machine_slot_directory);


662
663
664
    vboxlog_msg("Register VM. (%s, slot#%d)", vm_master_name.c_str(), aid.slot);


665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
    // Reset VM name in case it was changed while deregistering a stale VM
    //
    vm_name = vm_master_name;

    // Register the VM
    //
    command  = "registervm ";
    command += "\"" + virtual_machine_slot_directory + "/" + vm_name + "/" + vm_name + ".vbox\" ";
    
    retval = vbm_popen(command, output, "register");
    if (retval) return retval;

    return 0;
}

int VBOX_VM::deregister_vm(bool delete_media) {
    string command;
    string output;
    string virtual_machine_slot_directory;
684
685
    APP_INIT_DATA aid;

686

687
    boinc_get_init_data_p(&aid);
688
689
    get_slot_directory(virtual_machine_slot_directory);

690
691

    vboxlog_msg("Deregistering VM. (%s, slot#%d)", vm_name.c_str(), aid.slot);
692
693
694
695
696
697
698
699
700


    // Cleanup any left-over snapshots
    //
    cleanup_snapshots(true);

    // Delete network bandwidth throttle group
    //
    if (is_virtualbox_version_newer(4, 2, 0)) {
701
        vboxlog_msg("Removing network bandwidth throttle group from VM.");
702
703
704
705
706
707
708
709
        command  = "bandwidthctl \"" + vm_name + "\" ";
        command += "remove \"" + vm_name + "_net\" ";

        vbm_popen(command, output, "network throttle group (remove)", false, false);
    }

    // Delete its storage controller(s)
    //
710
    vboxlog_msg("Removing storage controller(s) from VM.");
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
    command  = "storagectl \"" + vm_name + "\" ";
    command += "--name \"Hard Disk Controller\" ";
    command += "--remove ";

    vbm_popen(command, output, "deregister storage controller (fixed disk)", false, false);

    if (enable_floppyio) {
        command  = "storagectl \"" + vm_name + "\" ";
        command += "--name \"Floppy Controller\" ";
        command += "--remove ";

        vbm_popen(command, output, "deregister storage controller (floppy disk)", false, false);
    }

    // Next, delete VM
    //
727
    vboxlog_msg("Removing VM from VirtualBox.");
728
729
730
731
732
733
734
735
    command  = "unregistervm \"" + vm_name + "\" ";
    command += "--delete ";

    vbm_popen(command, output, "delete VM", false, false);

    // Lastly delete medium(s) from Virtual Box Media Registry
    //
    if (enable_isocontextualization) {
736
        vboxlog_msg("Removing virtual ISO 9660 disk from VirtualBox.");
737
738
739
740
741
742
743
        command  = "closemedium dvd \"" + virtual_machine_slot_directory + "/" + iso_image_filename + "\" ";
        if (delete_media) {
            command += "--delete ";
        }
        vbm_popen(command, output, "remove virtual ISO 9660 disk", false, false);

        if (enable_cache_disk) {
744
            vboxlog_msg("Removing virtual cache disk from VirtualBox.");
745
746
747
748
749
750
751
752
            command  = "closemedium disk \"" + virtual_machine_slot_directory + "/" + cache_disk_filename + "\" ";
            if (delete_media) {
                command += "--delete ";
            }

            vbm_popen(command, output, "remove virtual cache disk", false, false);
        }
    } else {
753
        vboxlog_msg("Removing virtual disk drive from VirtualBox.");
754
755
756
757
758
759
760
761
        command  = "closemedium disk \"" + virtual_machine_slot_directory + "/" + image_filename + "\" ";
        if (delete_media) {
            command += "--delete ";
        }
        vbm_popen(command, output, "remove virtual disk", false, false);
    }

    if (enable_floppyio) {
762
        vboxlog_msg("Removing virtual floppy disk from VirtualBox.");
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
        command  = "closemedium floppy \"" + virtual_machine_slot_directory + "/" + floppy_image_filename + "\" ";
        if (delete_media) {
            command += "--delete ";
        }

        vbm_popen(command, output, "remove virtual floppy disk", false, false);
    }

    return 0;
}

int VBOX_VM::deregister_stale_vm() {
    string command;
    string output;
    string virtual_machine_slot_directory;
    size_t uuid_start;
    size_t uuid_end;
    int retval;

    get_slot_directory(virtual_machine_slot_directory);

784
    // Output from showhdinfo should look a little like this:
785
786
787
788
789
790
791
792
793
794
    //   UUID:                 c119acaf-636c-41f6-86c9-38e639a31339
    //   Accessible:           yes
    //   Logical size:         10240 MBytes
    //   Current size on disk: 0 MBytes
    //   Type:                 normal (base)
    //   Storage format:       VDI
    //   Format variant:       dynamic default
    //   In use by VMs:        test2 (UUID: 000ab2be-1254-4c6a-9fdc-1536a478f601)
    //   Location:             C:\Users\romw\VirtualBox VMs\test2\test2.vdi
    //
795
    if (enable_isocontextualization && enable_isocontextualization) {
796
797
798
799
800
801
802
803
804
805
806
807
808
809
        command  = "showhdinfo \"" + virtual_machine_slot_directory + "/" + cache_disk_filename + "\" ";
        retval = vbm_popen(command, output, "get HDD info");
        if (retval) return retval;

        uuid_start = output.find("(UUID: ");
        if (uuid_start != string::npos) {
            // We can parse the virtual machine ID from the output
            uuid_start += 7;
            uuid_end = output.find(")", uuid_start);
            vm_name = output.substr(uuid_start, uuid_end - uuid_start);

            // Deregister stale VM by UUID
            return deregister_vm(false);
        }
810
    } else {
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
        command  = "showhdinfo \"" + virtual_machine_slot_directory + "/" + image_filename + "\" ";
        retval = vbm_popen(command, output, "get HDD info");
        if (retval) return retval;

        uuid_start = output.find("(UUID: ");
        if (uuid_start != string::npos) {
            // We can parse the virtual machine ID from the output
            uuid_start += 7;
            uuid_end = output.find(")", uuid_start);
            vm_name = output.substr(uuid_start, uuid_end - uuid_start);

            // Deregister stale VM by UUID
            return deregister_vm(false);
        } else {
            // Did the user delete the VM in VirtualBox and not the medium?  If so,
            // just remove the medium.
            command  = "closemedium disk \"" + virtual_machine_slot_directory + "/" + image_filename + "\" ";
            vbm_popen(command, output, "remove virtual disk", false, false);
            if (enable_floppyio) {
                command  = "closemedium floppy \"" + virtual_machine_slot_directory + "/" + floppy_image_filename + "\" ";
                vbm_popen(command, output, "remove virtual floppy disk", false, false);
            }
            if (enable_isocontextualization) {
                command  = "closemedium dvd \"" + virtual_machine_slot_directory + "/" + iso_image_filename + "\" ";
                vbm_popen(command, output, "remove virtual ISO 9660 disk", false);
                if (enable_cache_disk) {
                    command  = "closemedium disk \"" + virtual_machine_slot_directory + "/" + cache_disk_filename + "\" ";
                    vbm_popen(command, output, "remove virtual cache disk", false);
                }
840
841
842
843
844
845
            }
        }
    }
    return 0;
}

846
847
int VBOX_VM::poll(bool log_state) {
    int retval = ERR_EXEC;
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
    APP_INIT_DATA aid;
    string command;
    string output;
    string::iterator iter;
    string vmstate;
    static string vmstate_old = "poweroff";
    size_t vmstate_start;
    size_t vmstate_end;

    boinc_get_init_data_p(&aid);

    //
    // Is our environment still sane?
    //
#ifdef _WIN32
    if (aid.using_sandbox && vboxsvc_pid_handle && !process_exists(vboxsvc_pid_handle)) {
        vboxlog_msg("Status Report: vboxsvc.exe is no longer running.");
    }
866
    if (started_successfully && vm_pid_handle && !process_exists(vm_pid_handle)) {
867
868
869
        vboxlog_msg("Status Report: virtualbox.exe/vboxheadless.exe is no longer running.");
    }
#else
870
    if (started_successfully && vm_pid && !process_exists(vm_pid)) {
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
        vboxlog_msg("Status Report: virtualbox/vboxheadless is no longer running.");
    }
#endif

    //
    // What state is the VM in?
    //

    command  = "showvminfo \"" + vm_name + "\" ";
    command += "--machinereadable ";

    if (vbm_popen(command, output, "VM state", false, false, 45, false) == 0) {
        vmstate_start = output.find("VMState=\"");
        if (vmstate_start != string::npos) {
            vmstate_start += 9;
            vmstate_end = output.find("\"", vmstate_start);
            vmstate = output.substr(vmstate_start, vmstate_end - vmstate_start);

            // VirtualBox Documentation suggests that that a VM is running when its
            // machine state is between MachineState_FirstOnline and MachineState_LastOnline
            // which as of this writing is 5 and 17.
            //
            // VboxManage's source shows more than that though:
            // see: http://www.virtualbox.org/browser/trunk/src/VBox/Frontends/VBoxManage/VBoxManageInfo.cpp
            //
            // So for now, go with what VboxManage is reporting.
            //
            if (vmstate == "running") {
                online = true;
                saving = false;
                restoring = false;
                suspended = false;
                crashed = false;
            } else if (vmstate == "paused") {
                online = true;
                saving = false;
                restoring = false;
                suspended = true;
                crashed = false;
            } else if (vmstate == "starting") {
                online = true;
                saving = false;
                restoring = false;
                suspended = false;
                crashed = false;
            } else if (vmstate == "stopping") {
                online = true;
                saving = false;
                restoring = false;
                suspended = false;
                crashed = false;
            } else if (vmstate == "saving") {
                online = true;
                saving = true;
                restoring = false;
                suspended = false;
                crashed = false;
            } else if (vmstate == "restoring") {
                online = true;
                saving = false;
                restoring = true;
                suspended = false;
                crashed = false;
            } else if (vmstate == "livesnapshotting") {
                online = true;
                saving = false;
                restoring = false;
                suspended = false;
                crashed = false;
            } else if (vmstate == "deletingsnapshotlive") {
                online = true;
                saving = false;
                restoring = false;
                suspended = false;
                crashed = false;
            } else if (vmstate == "deletingsnapshotlivepaused") {
                online = true;
                saving = false;
                restoring = false;
                suspended = false;
                crashed = false;
            } else if (vmstate == "aborted") {
                online = false;
                saving = false;
                restoring = false;
                suspended = false;
                crashed = true;
            } else if (vmstate == "gurumeditation") {
                online = false;
                saving = false;
                restoring = false;
                suspended = false;
                crashed = true;
            } else {
                online = false;
                saving = false;
                restoring = false;
                suspended = false;
                crashed = false;
                if (log_state) {
                    vboxlog_msg("VM is no longer is a running state. It is in '%s'.", vmstate.c_str());
                }
            }
            if (log_state && (vmstate_old != vmstate)) {
                vboxlog_msg("VM state change detected. (old = '%s', new = '%s')", vmstate_old.c_str(), vmstate.c_str());
                vmstate_old = vmstate;
            }
978
979

            retval = BOINC_SUCCESS;
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
        }
    }

    //
    // Grab a snapshot of the latest log file.  Avoids multiple queries across several
    // functions.
    //
    if (online) {
        get_vm_log(vm_log);
    }

    //
    // Dump any new VM Guest Log entries
    //
    dump_vmguestlog_entries();
995
996

    return retval;
997
998
}

999
1000
int VBOX_VM::start() {
    int retval;