boinc/samples/vboxwrapper/vbox_vboxmanage.cpp

2078 lines
70 KiB
C++

// 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/>.
#ifdef _WIN32
#include "boinc_win.h"
#include "win_util.h"
#else
#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>
#endif
using std::string;
#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"
#include "vboxlogging.h"
#include "vboxwrapper.h"
#include "vbox_common.h"
#include "vbox_vboxmanage.h"
namespace vboxmanage {
VBOX_VM::VBOX_VM() {
}
VBOX_VM::~VBOX_VM() {
}
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);
get_scratch_directory(virtualbox_scratch_directory);
// 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()))) {
vboxlog_msg("Failed to modify the search path.");
}
}
#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.");
}
#endif
// 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.
#ifdef _WIN32
virtualbox_home_directory = getenv("USERPROFILE");
#else
virtualbox_home_directory = getenv("HOME");
#endif
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
// in root's home directory. Bad things happen if the process attempts to use root's home
// directory.
//
// if the HOME environment variable is missing force VirtualBox to use a directory it
// has a reasonable chance of writing log files too.
#ifndef _WIN32
if (NULL == getenv("HOME")) {
force_sandbox = true;
}
#endif
// 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());
#ifdef _WIN32
if (!SetEnvironmentVariable("VBOX_USER_HOME", const_cast<char*>(virtualbox_home_directory.c_str()))) {
vboxlog_msg("Failed to modify the search path.");
}
#else
// 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)) {
vboxlog_msg("Failed to modify the VBOX_USER_HOME path.");
}
#endif
}
#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
rc = get_version_information(virtualbox_version_raw, virtualbox_version_display);
if (rc) return rc;
get_guest_additions(virtualbox_guest_additions);
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);
vboxlog_msg("Create VM. (%s, slot#%d)", vm_master_name.c_str(), aid.slot);
// 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) {
vboxlog_msg("Updating drive controller type and model for desired configuration.");
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);
// Tweak the VM's Memory Size
//
vboxlog_msg("Setting Memory Size for VM. (%dMB)", (int)memory_size_mb);
sprintf(buf, "%d", (int)memory_size_mb);
command = "modifyvm \"" + vm_name + "\" ";
command += "--memory " + string(buf) + " ";
retval = vbm_popen(command, output, "modifymem");
if (retval) return retval;
// Tweak the VM's CPU Count
//
vboxlog_msg("Setting CPU Count for VM. (%s)", vm_cpu_count.c_str());
command = "modifyvm \"" + vm_name + "\" ";
command += "--cpus " + vm_cpu_count + " ";
retval = vbm_popen(command, output, "modifycpu");
if (retval) return retval;
// Tweak the VM's Chipset Options
//
vboxlog_msg("Setting Chipset Options for VM.");
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
//
vboxlog_msg("Setting Boot Options for VM.");
command = "modifyvm \"" + vm_name + "\" ";
if (boot_iso) {
command += "--boot1 dvd ";
command += "--boot2 disk ";
} else {
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) {
vboxlog_msg("Setting Network Configuration for Bridged Mode.");
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);
vboxlog_msg("Setting Bridged Interface. (%s)", default_interface.c_str());
command = "modifyvm \"" + vm_name + "\" ";
command += "--bridgeadapter1 \"";
command += default_interface;
command += "\" ";
retval = vbm_popen(command, output, "set bridged interface");
if (retval) return retval;
} else {
vboxlog_msg("Setting Network Configuration for NAT.");
command = "modifyvm \"" + vm_name + "\" ";
command += "--nic1 nat ";
command += "--natdnsproxy1 on ";
command += "--cableconnected1 off ";
retval = vbm_popen(command, output, "set nat mode");
if (retval) return retval;
}
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;
}
// Tweak the VM's USB Configuration
//
vboxlog_msg("Disabling USB Support for VM.");
command = "modifyvm \"" + vm_name + "\" ";
command += "--usb off ";
vbm_popen(command, output, "modifyusb", false, false);
// Tweak the VM's COM Port Support
//
vboxlog_msg("Disabling COM Port Support for VM.");
command = "modifyvm \"" + vm_name + "\" ";
command += "--uart1 off ";
command += "--uart2 off ";
vbm_popen(command, output, "modifycom", false, false);
#ifndef __APPLE__
// Tweak the VM's LPT Port Support
//
vboxlog_msg("Disabling LPT Port Support for VM.");
command = "modifyvm \"" + vm_name + "\" ";
command += "--lpt1 off ";
command += "--lpt2 off ";
vbm_popen(command, output, "modifylpt", false, false);
#endif
// Tweak the VM's Audio Support
//
vboxlog_msg("Disabling Audio Support for VM.");
command = "modifyvm \"" + vm_name + "\" ";
command += "--audio none ";
vbm_popen(command, output, "modifyaudio", false, false);
// Tweak the VM's Clipboard Support
//
vboxlog_msg("Disabling Clipboard Support for VM.");
command = "modifyvm \"" + vm_name + "\" ";
command += "--clipboard disabled ";
vbm_popen(command, output, "modifyclipboard", false, false);
// Tweak the VM's Drag & Drop Support
//
vboxlog_msg("Disabling Drag and Drop Support for VM.");
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")) {
vboxlog_msg("Hardware acceleration CPU extensions not detected. Disabling VirtualBox hardware acceleration support.");
disable_acceleration = true;
}
if (strstr(aid.host_info.p_features, "hypervisor")) {
vboxlog_msg("Running under Hypervisor. Disabling VirtualBox hardware acceleration support.");
disable_acceleration = true;
}
if (is_boinc_client_version_newer(aid, 7, 2, 16)) {
if (aid.vm_extensions_disabled) {
vboxlog_msg("Hardware acceleration failed with previous execution. Disabling VirtualBox hardware acceleration support.");
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.
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.");
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) {
vboxlog_msg("Disabling hardware acceleration support for virtualization.");
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) {
vboxlog_msg("ERROR: Invalid configuration. VM type requires acceleration but the current configuration cannot support it.");
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
//
vboxlog_msg("Adding storage controller(s) to VM.");
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
//
vboxlog_msg("Adding virtual ISO 9660 disk drive to VM. (%s)", iso_image_filename.c_str());
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
//
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 + "\" ";
retval = vbm_popen(command, output, "storage attach (guest additions image)");
if (retval) return retval;
}
// Add a virtual cache disk drive to VM
//
if (enable_cache_disk){
vboxlog_msg("Adding virtual cache disk drive to VM. (%s)", cache_disk_filename.c_str());
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
//
vboxlog_msg("Adding virtual disk drive to VM. (%s)", image_filename.c_str());
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
//
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 + "\" ";
retval = vbm_popen(command, output, "storage attach (guest additions image)");
if (retval) return retval;
}
}
// 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.
//
pFloppy = new FloppyIONS::FloppyIO(floppy_image_filename.c_str());
if (!pFloppy->ready()) {
vboxlog_msg("Creating virtual floppy image failed.");
vboxlog_msg("Error Code '%d' Error Message '%s'", pFloppy->error, pFloppy->errorStr.c_str());
return ERR_FWRITE;
}
vboxlog_msg("Adding virtual floppy disk drive to VM.");
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;
}
// 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;
}
// Enable the network adapter if a network connection is required.
//
if (enable_network) {
// set up port forwarding
//
if (pf_guest_port) {
VBOX_PORT_FORWARD pf;
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++) {
VBOX_PORT_FORWARD& pf = port_forwards[i];
vboxlog_msg("forwarding host port %d to guest port %d", pf.host_port, pf.guest_port);
// 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) {
vboxlog_msg("Enabling remote desktop for VM.");
if (!is_extpack_installed()) {
vboxlog_msg("Required extension pack not installed, remote desktop not enabled.");
} 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;
}
}
// Enable the shared folder if a shared folder is specified.
//
if (enable_shared_directory) {
vboxlog_msg("Enabling shared directory for VM.");
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;
}
// Enable the scratch folder if a scratch folder is specified.
//
if (enable_scratch_directory) {
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;
}
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);
vboxlog_msg("Register VM. (%s, slot#%d)", vm_master_name.c_str(), aid.slot);
// 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;
APP_INIT_DATA aid;
boinc_get_init_data_p(&aid);
get_slot_directory(virtual_machine_slot_directory);
vboxlog_msg("Deregistering VM. (%s, slot#%d)", vm_name.c_str(), aid.slot);
// Cleanup any left-over snapshots
//
cleanup_snapshots(true);
// Delete network bandwidth throttle group
//
if (is_virtualbox_version_newer(4, 2, 0)) {
vboxlog_msg("Removing network bandwidth throttle group from VM.");
command = "bandwidthctl \"" + vm_name + "\" ";
command += "remove \"" + vm_name + "_net\" ";
vbm_popen(command, output, "network throttle group (remove)", 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
//
vboxlog_msg("Removing VM from VirtualBox.");
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) {
vboxlog_msg("Removing virtual ISO 9660 disk from VirtualBox.");
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) {
vboxlog_msg("Removing virtual cache disk from VirtualBox.");
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);
}
}
if (enable_floppyio) {
vboxlog_msg("Removing virtual floppy disk from VirtualBox.");
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);
// Output from showhdinfo should look a little like this:
// 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
//
if (enable_isocontextualization) {
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);
} else {
command = "closemedium dvd \"" + virtual_machine_slot_directory + "/" + iso_image_filename + "\" ";
// coverity[CHECKED_RETURN]
vbm_popen(command, output, "remove virtual ISO 9660 disk", false);
if (enable_cache_disk) {
command = "closemedium disk \"" + virtual_machine_slot_directory + "/" + cache_disk_filename + "\" ";
// coverity[CHECKED_RETURN]
vbm_popen(command, output, "remove virtual cache disk", false);
}
}
} else {
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);
}
}
}
return 0;
}
int VBOX_VM::poll(bool log_state) {
int retval = ERR_EXEC;
APP_INIT_DATA aid;
string command;
string output;
string::iterator iter;
string vmstate;
static string vmstate_old = "poweredoff";
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.");
}
if (started_successfully && vm_pid_handle && !process_exists(vm_pid_handle)) {
vboxlog_msg("Status Report: virtualbox.exe/vboxheadless.exe is no longer running.");
}
#else
if (started_successfully && vm_pid && !process_exists(vm_pid)) {
vboxlog_msg("Status Report: virtualbox/vboxheadless is no longer running.");
}
#endif
//
// What state is the VM in?
//
vmstate = read_vm_log();
if (vmstate != "Error in parsing the log file") {
// 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 == "deletingsnapshotonline" || vmstate == "deletingsnapshotlive") {
online = true;
saving = false;
restoring = false;
suspended = false;
crashed = false;
}
else if (vmstate == "deletingsnapshotpaused" || 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;
}
retval = BOINC_SUCCESS;
}
else
{
vboxlog_msg("Error in parsing the log file");
}
return retval;
}
int VBOX_VM::poll2(bool log_state) {
int retval = ERR_EXEC;
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.");
}
if (started_successfully && vm_pid_handle && !process_exists(vm_pid_handle)) {
vboxlog_msg("Status Report: virtualbox.exe/vboxheadless.exe is no longer running.");
}
#else
if (started_successfully && vm_pid && !process_exists(vm_pid)) {
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;
}
retval = BOINC_SUCCESS;
}
}
//
// Grab a snapshot of the latest log file. Avoids multiple queries across several
// functions.
//
get_vm_log(vm_log);
//
// Dump any new VM Guest Log entries
//
dump_vmguestlog_entries();
return retval;
}
int VBOX_VM::start() {
int retval;
APP_INIT_DATA aid;
string command;
string output;
int timeout = 0;
boinc_get_init_data_p(&aid);
log_pointer = 0;
vboxlog_msg("Starting VM using VBoxManage interface. (%s, slot#%d)", vm_name.c_str(), aid.slot);
command = "startvm \"" + vm_name + "\"";
if (headless) {
command += " --type headless";
}
retval = vbm_popen(command, output, "start VM", true, false, 0);
// Get the VM pid as soon as possible
while (!retval) {
boinc_sleep(1.0);
timeout += 1;
get_vm_process_id();
if (vm_pid) break;
if (timeout > 45) {
retval = ERR_TIMEOUT;
break;
}
}
if (BOINC_SUCCESS == retval) {
vboxlog_msg("Successfully started VM. (PID = '%d')", vm_pid);
started_successfully = true;
} else {
vboxlog_msg("VM failed to start.");
}
return retval;
}
int VBOX_VM::stop() {
string command;
string output;
double timeout;
int retval = 0;
vboxlog_msg("Stopping VM.");
if (online) {
command = "controlvm \"" + vm_name + "\" savestate";
retval = vbm_popen(command, output, "stop VM", true, false);
// 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 45 seconds to execute a command we need to make this time based instead
// of iteration based.
if (!retval) {
timeout = dtime() + 300;
do {
poll(false);
if (!online && !saving) break;
boinc_sleep(1.0);
} while (timeout >= dtime());
}
if (!online) {
vboxlog_msg("Successfully stopped VM.");
retval = BOINC_SUCCESS;
} else {
vboxlog_msg("VM did not stop when requested.");
// Attempt to terminate the VM
retval = kill_program(vm_pid);
if (retval) {
vboxlog_msg("VM was NOT successfully terminated.");
} else {
vboxlog_msg("VM was successfully terminated.");
}
}
}
return retval;
}
int VBOX_VM::poweroff() {
string command;
string output;
double timeout;
int retval = 0;
vboxlog_msg("Powering off VM.");
if (online) {
command = "controlvm \"" + vm_name + "\" poweroff";
retval = vbm_popen(command, output, "poweroff VM", true, false);
// 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 45 seconds to execute a command we need to make this time based instead
// of iteration based.
if (!retval) {
timeout = dtime() + 300;
do {
poll(false);
if (!online && !saving) break;
boinc_sleep(1.0);
} while (timeout >= dtime());
}
if (!online) {
vboxlog_msg("Successfully stopped VM.");
retval = BOINC_SUCCESS;
} else {
vboxlog_msg("VM did not power off when requested.");
// Attempt to terminate the VM
retval = kill_program(vm_pid);
if (retval) {
vboxlog_msg("VM was NOT successfully terminated.");
} else {
vboxlog_msg("VM was successfully terminated.");
}
}
}
return retval;
}
int VBOX_VM::pause() {
string command;
string output;
int retval;
// Restore the process priority back to the default process priority
// to speed up the last minute maintenance tasks before the VirtualBox
// VM goes to sleep
//
reset_vm_process_priority();
command = "controlvm \"" + vm_name + "\" pause";
retval = vbm_popen(command, output, "pause VM");
if (retval) return retval;
suspended = true;
return 0;
}
int VBOX_VM::resume() {
string command;
string output;
int retval;
// Set the process priority back to the lowest level before resuming
// execution
//
lower_vm_process_priority();
command = "controlvm \"" + vm_name + "\" resume";
retval = vbm_popen(command, output, "resume VM");
if (retval) return retval;
suspended = false;
return 0;
}
int VBOX_VM::capture_screenshot() {
if (enable_screenshots_on_error) {
if (is_virtualbox_version_newer(5, 0, 0)) {
string command;
string output;
string virtual_machine_slot_directory;
int retval = BOINC_SUCCESS;
get_slot_directory(virtual_machine_slot_directory);
vboxlog_msg("Capturing screenshot.");
command = "controlvm \"" + vm_name + "\" ";
command += "keyboardputscancode 39";
vbm_popen(command, output, "put scancode", true, true, 0);
boinc_sleep(1);
command = "controlvm \"" + vm_name + "\" ";
command += "screenshotpng \"";
command += virtual_machine_slot_directory;
command += "/";
command += SCREENSHOT_FILENAME;
command += "\"";
retval = vbm_popen(command, output, "capture screenshot", true, true, 0);
if (retval) return retval;
vboxlog_msg("Screenshot completed.");
}
}
return 0;
}
int VBOX_VM::create_snapshot(double elapsed_time) {
string command;
string output;
char buf[256];
int retval = BOINC_SUCCESS;
if (disable_automatic_checkpoints) return BOINC_SUCCESS;
vboxlog_msg("Creating new snapshot for VM.");
// Pause VM - Try and avoid the live snapshot and trigger an online
// snapshot instead.
pause();
// Create new snapshot
sprintf(buf, "%d", (int)elapsed_time);
command = "snapshot \"" + vm_name + "\" ";
command += "take boinc_";
command += buf;
retval = vbm_popen(command, output, "create new snapshot", true, true, 0);
if (retval) return retval;
// Resume VM
resume();
// Set the suspended flag back to false before deleting the stale
// snapshot
poll(false);
// Delete stale snapshot(s), if one exists
cleanup_snapshots(false);
vboxlog_msg("Checkpoint completed.");
return 0;
}
int VBOX_VM::cleanup_snapshots(bool delete_active) {
string command;
string output;
string snapshotlist;
string line;
string uuid;
size_t eol_pos;
size_t eol_prev_pos;
size_t uuid_start;
size_t uuid_end;
int retval;
// Enumerate snapshot(s)
command = "snapshot \"" + vm_name + "\" ";
command += "list ";
// Only log the error if we are not attempting to deregister the VM.
// delete_active is only set to true when we are deregistering the VM.
retval = vbm_popen(command, snapshotlist, "enumerate snapshot(s)", !delete_active, false, 0);
if (retval) return retval;
// Output should look a little like this:
// Name: Snapshot 2 (UUID: 1751e9a6-49e7-4dcc-ab23-08428b665ddf)
// Name: Snapshot 3 (UUID: 92fa8b35-873a-4197-9d54-7b6b746b2c58)
// Name: Snapshot 4 (UUID: c049023a-5132-45d5-987d-a9cfadb09664) *
//
// Traverse the list from newest to oldest. Otherwise we end up with an error:
// VBoxManage.exe: error: Snapshot operation failed
// VBoxManage.exe: error: Hard disk 'C:\ProgramData\BOINC\slots\23\vm_image.vdi' has
// more than one child hard disk (2)
//
// Prepend a space and line feed to the output since we are going to traverse it backwards
snapshotlist = " \n" + snapshotlist;
eol_prev_pos = snapshotlist.rfind("\n");
eol_pos = snapshotlist.rfind("\n", eol_prev_pos - 1);
while (eol_pos != string::npos) {
line = snapshotlist.substr(eol_pos, eol_prev_pos - eol_pos);
// Find the previous line to use in the next iteration
eol_prev_pos = eol_pos;
eol_pos = snapshotlist.rfind("\n", eol_prev_pos - 1);
// This VM does not yet have any snapshots
if (line.find("does not have any snapshots") != string::npos) break;
// The * signifies that it is the active snapshot and one we do not want to delete
if (!delete_active && (line.rfind("*") != string::npos)) continue;
uuid_start = line.find("(UUID: ");
if (uuid_start != string::npos) {
// We can parse the virtual machine ID from the line
uuid_start += 7;
uuid_end = line.find(")", uuid_start);
uuid = line.substr(uuid_start, uuid_end - uuid_start);
vboxlog_msg("Deleting stale snapshot.");
// Delete stale snapshot, if one exists
command = "snapshot \"" + vm_name + "\" ";
command += "delete \"";
command += uuid;
command += "\" ";
// Only log the error if we are not attempting to deregister the VM.
// delete_active is only set to true when we are deregistering the VM.
retval = vbm_popen(command, output, "delete stale snapshot", !delete_active, false, 0);
if (retval) return retval;
}
}
return 0;
}
int VBOX_VM::restore_snapshot() {
string command;
string output;
int retval = BOINC_SUCCESS;
if (disable_automatic_checkpoints) return BOINC_SUCCESS;
vboxlog_msg("Restore from previously saved snapshot.");
command = "snapshot \"" + vm_name + "\" ";
command += "restorecurrent ";
retval = vbm_popen(command, output, "restore current snapshot", true, false, 0);
if (retval) return retval;
vboxlog_msg("Restore completed.");
return retval;
}
void VBOX_VM::dump_hypervisor_status_reports() {
}
int VBOX_VM::is_registered() {
string command;
string output;
string needle;
int retval;
command = "showvminfo \"" + vm_master_name + "\" ";
command += "--machinereadable ";
// Look for this string in the output
//
needle = "name=\"" + vm_master_name + "\"";
retval = vbm_popen(command, output, "registration detection", false, false);
// Handle explicit cases first
if (ERR_TIMEOUT == retval) {
return ERR_TIMEOUT;
}
if (output.find("VBOX_E_OBJECT_NOT_FOUND") != string::npos) {
return ERR_NOT_FOUND;
}
if (!retval && output.find(needle.c_str()) != string::npos) {
return BOINC_SUCCESS;
}
// Something unexpected has happened. Dump diagnostic output.
vboxlog_msg(
"Error in registration for VM: %d\nArguments:\n%s\nOutput:\n%s",
retval,
command.c_str(),
output.c_str()
);
return retval;
}
// Attempt to detect any condition that would prevent VirtualBox from running a VM properly, like:
// 1. The DCOM service not being started on Windows
// 2. Vboxmanage not being able to communicate with vboxsvc for some reason
// 3. VirtualBox driver not loaded for the current Linux kernel.
//
// Luckly both of the above conditions can be detected by attempting to detect the host information
// via vboxmanage and it is cross platform.
//
bool VBOX_VM::is_system_ready(std::string& message) {
string command;
string output;
int retval;
bool rc = false;
command = "list hostinfo ";
retval = vbm_popen(command, output, "host info");
if (BOINC_SUCCESS == retval) {
rc = true;
}
if (output.size() == 0) {
vboxlog_msg("WARNING: Communication with VM Hypervisor failed. (Possibly Out of Memory).");
message = "Communication with VM Hypervisor failed. (Possibly Out of Memory).";
rc = false;
}
if (output.find("Processor count:") == string::npos) {
vboxlog_msg("WARNING: Communication with VM Hypervisor failed.");
message = "Communication with VM Hypervisor failed.";
rc = false;
}
if (
(output.find("WARNING: The vboxdrv kernel module is not loaded.") != string::npos) ||
(output.find("WARNING: The VirtualBox kernel modules are not loaded.") != string::npos)
){
vboxlog_msg("WARNING: The VirtualBox kernel modules are not loaded.");
vboxlog_msg("WARNING: Please update/recompile VirtualBox kernel drivers.");
message = "Please update/recompile VirtualBox kernel drivers.";
rc = false;
}
if (
(output.find("Warning: program compiled against ") != string::npos) &&
(output.find(" using older ") != string::npos)
){
vboxlog_msg("WARNING: VirtualBox incompatible dependencies detected.");
message = "Please update/reinstall VirtualBox";
rc = false;
}
return rc;
}
bool VBOX_VM::is_disk_image_registered() {
string command;
string output;
string virtual_machine_root_dir;
get_slot_directory(virtual_machine_root_dir);
command = "showhdinfo \"" + virtual_machine_root_dir + "/" + image_filename + "\" ";
if (vbm_popen(command, output, "hdd registration", false, false) == 0) {
if ((output.find("VBOX_E_FILE_ERROR") == string::npos) &&
(output.find("VBOX_E_OBJECT_NOT_FOUND") == string::npos) &&
(output.find("does not match the value") == string::npos)
) {
// Error message not found in text
return true;
}
}
if (enable_isocontextualization && enable_cache_disk) {
command = "showhdinfo \"" + virtual_machine_root_dir + "/" + cache_disk_filename + "\" ";
if (vbm_popen(command, output, "hdd registration", false, false) == 0) {
if ((output.find("VBOX_E_FILE_ERROR") == string::npos) &&
(output.find("VBOX_E_OBJECT_NOT_FOUND") == string::npos) &&
(output.find("does not match the value") == string::npos)
) {
// Error message not found in text
return true;
}
}
}
return false;
}
bool VBOX_VM::is_extpack_installed() {
string command;
string output;
command = "list extpacks";
if (vbm_popen(command, output, "extpack detection", false, false) == 0) {
if ((output.find("Oracle VM VirtualBox Extension Pack") != string::npos) && (output.find("VBoxVRDP") != string::npos)) {
return true;
}
}
return false;
}
int VBOX_VM::get_install_directory(string& install_directory) {
#ifdef _WIN32
LONG lReturnValue;
HKEY hkSetupHive;
LPTSTR lpszRegistryValue = NULL;
DWORD dwSize = 0;
// change the current directory to the boinc data directory if it exists
lReturnValue = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
_T("SOFTWARE\\Oracle\\VirtualBox"),
0,
KEY_READ,
&hkSetupHive
);
if (lReturnValue == ERROR_SUCCESS) {
// How large does our buffer need to be?
lReturnValue = RegQueryValueEx(
hkSetupHive,
_T("InstallDir"),
NULL,
NULL,
NULL,
&dwSize
);
if (lReturnValue != ERROR_FILE_NOT_FOUND) {
// Allocate the buffer space.
lpszRegistryValue = (LPTSTR) malloc(dwSize);
(*lpszRegistryValue) = NULL;
// Now get the data
lReturnValue = RegQueryValueEx(
hkSetupHive,
_T("InstallDir"),
NULL,
NULL,
(LPBYTE)lpszRegistryValue,
&dwSize
);
install_directory = lpszRegistryValue;
}
}
if (hkSetupHive) RegCloseKey(hkSetupHive);
if (lpszRegistryValue) free(lpszRegistryValue);
if (install_directory.empty()) {
return ERR_FREAD;
}
return BOINC_SUCCESS;
#else
install_directory = "";
return 0;
#endif
}
int VBOX_VM::get_version_information(std::string& version_raw, std::string& version_display) {
string command;
string output;
int vbox_major = 0, vbox_minor = 0, vbox_release = 0;
int retval;
char buf[256];
// Record the VirtualBox version information for later use.
command = "--version ";
retval = vbm_popen(command, output, "version check");
if (!retval) {
// Remove \r or \n from the output spew
string::iterator iter = output.begin();
while (iter != output.end()) {
if (*iter == '\r' || *iter == '\n') {
iter = output.erase(iter);
} else {
++iter;
}
}
if (3 == sscanf(output.c_str(), "%d.%d.%d", &vbox_major, &vbox_minor, &vbox_release)) {
snprintf(
buf, sizeof(buf),
"%d.%d.%d",
vbox_major, vbox_minor, vbox_release
);
version_raw = buf;
snprintf(
buf, sizeof(buf),
"VirtualBox VboxManage Interface (Version: %d.%d.%d)",
vbox_major, vbox_minor, vbox_release
);
version_display = buf;
} else {
version_raw = "Unknown";
version_display = "VirtualBox VboxManage Interface (Version: Unknown)";
}
}
return retval;
}
int VBOX_VM::get_guest_additions(string& guest_additions) {
string command;
string output;
size_t ga_start;
size_t ga_end;
int retval;
// Get the location of where the guest additions are
command = "list systemproperties";
retval = vbm_popen(command, output, "guest additions");
// Output should look like this:
// API version: 4_3
// Minimum guest RAM size: 4 Megabytes
// Maximum guest RAM size: 2097152 Megabytes
// Minimum video RAM size: 1 Megabytes
// Maximum video RAM size: 256 Megabytes
// ...
// Default Guest Additions ISO: C:\Program Files\Oracle\VirtualBox/VBoxGuestAdditions.iso
//
ga_start = output.find("Default Guest Additions ISO:");
if (ga_start == string::npos) {
return ERR_NOT_FOUND;
}
ga_start += strlen("Default Guest Additions ISO:");
ga_end = output.find("\n", ga_start);
guest_additions = output.substr(ga_start, ga_end - ga_start);
strip_whitespace(guest_additions);
if (guest_additions.size() <= 0) {
return ERR_NOT_FOUND;
}
if (!boinc_file_exists(guest_additions.c_str())) {
guest_additions.clear();
return ERR_NOT_FOUND;
}
return retval;
}
int VBOX_VM::get_default_network_interface(string& iface) {
string command;
string output;
size_t if_start;
size_t if_end;
int retval;
// Get the location of where the guest additions are
command = "list bridgedifs";
retval = vbm_popen(command, output, "default interface");
// Output should look like this:
// Name: Intel(R) Ethernet Connection I217-V
// GUID: 4b8796d6-a4ed-4752-8e8e-bf23984fd93c
// DHCP: Enabled
// IPAddress: 192.168.1.19
// NetworkMask: 255.255.255.0
// IPV6Address: fe80:0000:0000:0000:31c2:0053:4f50:4e64
// IPV6NetworkMaskPrefixLength: 64
// HardwareAddress: bc:5f:f4:ba:cc:16
// MediumType: Ethernet
// Status: Up
// VBoxNetworkName: HostInterfaceNetworking-Intel(R) Ethernet Connection I217-V
if_start = output.find("Name:");
if (if_start == string::npos) {
return ERR_NOT_FOUND;
}
if_start += strlen("Name:");
if_end = output.find("\n", if_start);
iface = output.substr(if_start, if_end - if_start);
strip_whitespace(iface);
if (iface.size() <= 0) {
return ERR_NOT_FOUND;
}
return retval;
}
int VBOX_VM::get_vm_network_bytes_sent(double& sent) {
string command;
string output;
string counter_value;
size_t counter_start;
size_t counter_end;
int retval;
command = "debugvm \"" + vm_name + "\" ";
command += "statistics --pattern \"/Devices/*/TransmitBytes\" ";
retval = vbm_popen(command, output, "get bytes sent");
if (retval) return retval;
// Output should look like this:
// <?xml version="1.0" encoding="UTF-8" standalone="no"?>
// <Statistics>
// <Counter c="397229" unit="bytes" name="/Devices/PCNet0/TransmitBytes"/>
// <Counter c="256" unit="bytes" name="/Devices/PCNet1/TransmitBytes"/>
// </Statistics>
// add up the counter(s)
//
sent = 0;
counter_start = output.find("c=\"");
while (counter_start != string::npos) {
counter_start += 3;
counter_end = output.find("\"", counter_start);
counter_value = output.substr(counter_start, counter_end - counter_start);
sent += atof(counter_value.c_str());
counter_start = output.find("c=\"", counter_start);
}
return 0;
}
int VBOX_VM::get_vm_network_bytes_received(double& received) {
string command;
string output;
string counter_value;
size_t counter_start;
size_t counter_end;
int retval;
command = "debugvm \"" + vm_name + "\" ";
command += "statistics --pattern \"/Devices/*/ReceiveBytes\" ";
retval = vbm_popen(command, output, "get bytes received");
if (retval) return retval;
// Output should look like this:
// <?xml version="1.0" encoding="UTF-8" standalone="no"?>
// <Statistics>
// <Counter c="9423150" unit="bytes" name="/Devices/PCNet0/ReceiveBytes"/>
// <Counter c="256" unit="bytes" name="/Devices/PCNet1/ReceiveBytes"/>
// </Statistics>
// add up the counter(s)
//
received = 0;
counter_start = output.find("c=\"");
while (counter_start != string::npos) {
counter_start += 3;
counter_end = output.find("\"", counter_start);
counter_value = output.substr(counter_start, counter_end - counter_start);
received += atof(counter_value.c_str());
counter_start = output.find("c=\"", counter_start);
}
return 0;
}
int VBOX_VM::get_vm_process_id() {
string virtualbox_vm_log;
string::iterator iter;
int retval = BOINC_SUCCESS;
string line;
string comp = "Process ID: ";
string pid;
std::size_t found;
virtualbox_vm_log = vm_master_name + "/Logs/VBox.log";
if (boinc_file_exists(virtualbox_vm_log.c_str())) {
std::ifstream src(virtualbox_vm_log.c_str(), std::ios::binary);
while (std::getline(src, line))
{
found = line.find(comp);
if (found != string::npos){
found += comp.size();
pid = line.substr(found, string::npos);
strip_whitespace(pid);
if (pid.size() <= 0) {
return ERR_NOT_FOUND;
}
vm_pid = atol(pid.c_str());
break;
}
}
if (pid.size() <= 0) {
return ERR_NOT_FOUND;
}
}
else retval = ERR_NOT_FOUND;
return retval;
}
int VBOX_VM::get_vm_exit_code(unsigned long& exit_code) {
#ifdef _WIN32
if (vm_pid_handle) {
GetExitCodeProcess(vm_pid_handle, &exit_code);
}
#else
int ec = 0;
waitpid(vm_pid, &ec, WNOHANG);
exit_code = ec;
#endif
return 0;
}
double VBOX_VM::get_vm_cpu_time() {
double x = process_tree_cpu_time(vm_pid);
if (x > current_cpu_time) {
current_cpu_time = x;
}
return current_cpu_time;
}
// Enable the network adapter if a network connection is required.
// NOTE: Network access should never be allowed if the code running in a
// shared directory or the VM image itself is NOT signed. Doing so
// opens up the network behind the company firewall to attack.
//
// Imagine a doomsday scenario where a project has been compromised and
// an unsigned executable/VM image has been tampered with. Volunteer
// downloads compromised code and executes it on a company machine.
// Now the compromised VM starts attacking other machines on the company
// network. The company firewall cannot help because the attacking
// machine is already behind the company firewall.
//
int VBOX_VM::set_network_access(bool enabled) {
string command;
string output;
int retval;
network_suspended = !enabled;
if (enabled) {
vboxlog_msg("Enabling network access for VM.");
command = "modifyvm \"" + vm_name + "\" ";
command += "--cableconnected1 on ";
retval = vbm_popen(command, output, "enable network");
if (retval) return retval;
} else {
vboxlog_msg("Disabling network access for VM.");
command = "modifyvm \"" + vm_name + "\" ";
command += "--cableconnected1 off ";
retval = vbm_popen(command, output, "disable network");
if (retval) return retval;
}
return 0;
}
int VBOX_VM::set_cpu_usage(int percentage) {
string command;
string output;
char buf[256];
int retval;
// the arg to controlvm is percentage
//
vboxlog_msg("Setting CPU throttle for VM. (%d%%)", percentage);
sprintf(buf, "%d", percentage);
command = "controlvm \"" + vm_name + "\" ";
command += "cpuexecutioncap ";
command += buf;
command += " ";
retval = vbm_popen(command, output, "CPU throttle");
if (retval) return retval;
return 0;
}
int VBOX_VM::set_network_usage(int kilobytes) {
string command;
string output;
char buf[256];
int retval;
// the argument to modifyvm is in KB
//
if (kilobytes == 0) {
vboxlog_msg("Setting network throttle for VM. (1024GB)");
} else {
vboxlog_msg("Setting network throttle for VM. (%dKB)", kilobytes);
}
if (is_virtualbox_version_newer(4, 2, 0)) {
// Update bandwidth group limits
//
if (kilobytes == 0) {
command = "bandwidthctl \"" + vm_name + "\" ";
command += "set \"" + vm_name + "_net\" ";
command += "--limit 1024G ";
retval = vbm_popen(command, output, "network throttle (set default value)");
if (retval) return retval;
} else {
sprintf(buf, "%d", kilobytes);
command = "bandwidthctl \"" + vm_name + "\" ";
command += "set \"" + vm_name + "_net\" ";
command += "--limit ";
command += buf;
command += "K ";
retval = vbm_popen(command, output, "network throttle (set)");
if (retval) return retval;
}
} else {
sprintf(buf, "%d", kilobytes);
command = "modifyvm \"" + vm_name + "\" ";
command += "--nicspeed1 ";
command += buf;
command += " ";
retval = vbm_popen(command, output, "network throttle");
if (retval) return retval;
}
return 0;
}
void VBOX_VM::lower_vm_process_priority() {
#ifdef _WIN32
if (vm_pid_handle) {
SetPriorityClass(vm_pid_handle, BELOW_NORMAL_PRIORITY_CLASS);
}
#else
if (vm_pid) {
setpriority(PRIO_PROCESS, vm_pid, PROCESS_MEDIUM_PRIORITY);
}
#endif
}
void VBOX_VM::reset_vm_process_priority() {
#ifdef _WIN32
if (vm_pid_handle) {
SetPriorityClass(vm_pid_handle, NORMAL_PRIORITY_CLASS);
}
#else
if (vm_pid) {
setpriority(PRIO_PROCESS, vm_pid, PROCESS_NORMAL_PRIORITY);
}
#endif
}
}