Select Git revision
vbox_win.cpp 90.91 KiB
// 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/>.
#include "boinc_win.h"
#if defined(_MSC_VER) || defined(__MINGW32__)
#define stricmp _stricmp
#endif
#include "win_util.h"
#include "atlcomcli.h"
#include "atlsafe.h"
#include "atlcoll.h"
#include "atlstr.h"
#include "mscom/VirtualBox.h"
#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 "vboxwrapper.h"
#include "vbox.h"
#include "vbox_win.h"
using std::string;
const char *MachineStateToName(MachineState State)
{
switch (State)
{
case MachineState_PoweredOff:
return "poweroff";
case MachineState_Saved:
return "saved";
case MachineState_Aborted:
return "aborted";
case MachineState_Teleported:
return "teleported";
case MachineState_Running:
return "running";
case MachineState_Paused:
return "paused";
case MachineState_Stuck:
return "gurumeditation";
case MachineState_LiveSnapshotting:
return "livesnapshotting";
case MachineState_Teleporting:
return "teleporting";
case MachineState_Starting:
return "starting";
case MachineState_Stopping:
return "stopping";
case MachineState_Saving:
return "saving";
case MachineState_Restoring:
return "restoring";
case MachineState_TeleportingPausedVM:
return "teleportingpausedvm";
case MachineState_TeleportingIn:
return "teleportingin";
case MachineState_RestoringSnapshot:
return "restoringsnapshot";
case MachineState_DeletingSnapshot:
return "deletingsnapshot";
case MachineState_DeletingSnapshotOnline:
return "deletingsnapshotlive";
case MachineState_DeletingSnapshotPaused:
return "deletingsnapshotlivepaused";
case MachineState_SettingUp:
return "settingup";
default:
break;
}
return "unknown";
}
//
// Helper function to print MSCOM exception information set on the current
// thread after a failed MSCOM method call. This function will also print
// extended VirtualBox error info if it is available.
//
void virtualbox_dump_error() {
HRESULT rc;
BSTR strDescription = NULL;
char buf[256];
IErrorInfo* pErrorInfo;
rc = GetErrorInfo(0, &pErrorInfo);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error: getting error info! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
} else {
rc = pErrorInfo->GetDescription(&strDescription);
if (FAILED(rc) || !strDescription) {
fprintf(
stderr,
"%s Error: getting error description! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
} else {
fprintf(
stderr,
"%s Error description: %S\n",
boinc_msg_prefix(buf, sizeof(buf)),
strDescription
);
SysFreeString(strDescription);
}
pErrorInfo->Release();
}
}
VBOX_VM::VBOX_VM()
{
VBOX_BASE::VBOX_BASE();
m_pVirtualBox;
m_pSession;
m_pMachine;
vm_pid = 0;
vm_pid_handle = 0;
vboxsvc_pid = 0;
vboxsvc_pid_handle = 0;
}
VBOX_VM::~VBOX_VM()
{
VBOX_BASE::~VBOX_BASE();
if (vm_pid_handle) {
CloseHandle(vm_pid_handle);
vm_pid_handle = NULL;
}
if (vboxsvc_pid_handle) {
CloseHandle(vboxsvc_pid_handle);
vboxsvc_pid_handle = NULL;
}
}
int VBOX_VM::initialize() {
int rc = BOINC_SUCCESS;
string old_path;
string new_path;
string command;
string output;
APP_INIT_DATA aid;
bool force_sandbox = false;
char buf[256];
boinc_get_init_data_p(&aid);
get_install_directory(virtualbox_install_directory);
// Prep the environment so we can execute the vboxmanage application
//
// TODO: Fix for non-Windows environments if we ever find another platform
// where vboxmanage is not already in the search path
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()))) {
fprintf(
stderr,
"%s Failed to modify the search path.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
}
}
// 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.
virtualbox_home_directory = getenv("USERPROFILE");
virtualbox_home_directory += "/.VirtualBox";
}
// 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());
}
if (!SetEnvironmentVariable("VBOX_USER_HOME", const_cast<char*>(virtualbox_home_directory.c_str()))) {
fprintf(
stderr,
"%s Failed to modify the search path.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
}
}
// Initialize the COM subsystem.
CoInitialize(NULL);
// Instantiate the VirtualBox root object.
rc = m_pVirtualBox.CoCreateInstance(CLSID_VirtualBox);
if (!SUCCEEDED(rc))
{
fprintf(
stderr,
"%s Error creating VirtualBox instance! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
return rc;
}
// Create the session object.
rc = m_pSession.CoCreateInstance(CLSID_Session);
if (!SUCCEEDED(rc))
{
fprintf(
stderr,
"%s Error creating Session instance! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
return rc;
}
rc = get_version_information(virtualbox_version);
if (rc) return rc;
get_guest_additions(virtualbox_guest_additions);
return rc;
}
int VBOX_VM::create_vm() {
int retval = ERR_EXEC;
HRESULT rc;
char buf[256];
APP_INIT_DATA aid;
CComBSTR vm_machine_uuid;
CComPtr<IBIOSSettings> pBIOSSettings;
CComPtr<INetworkAdapter> pNetworkAdapter;
CComPtr<INATEngine> pNATEngine;
CComPtr<ISerialPort> pSerialPort;
CComPtr<IParallelPort> pParallelPort;
CComPtr<IAudioAdapter> pAudioAdapter;
CComPtr<IStorageController> pDiskController;
CComPtr<IStorageController> pFloppyController;
CComPtr<IBandwidthControl> pBandwidthControl;
CComPtr<IVRDEServer> pVRDEServer;
ULONG lOHCICtrls = 0;
bool disable_acceleration = false;
string virtual_machine_slot_directory;
string default_interface;
boinc_get_init_data_p(&aid);
get_slot_directory(virtual_machine_slot_directory);
// 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) {
fprintf(
stderr,
"%s Updating drive controller type and model for desired configuration.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
vm_disk_controller_type = "sata";
vm_disk_controller_model = "IntelAHCI";
}
}
// Start the VM creation process
//
fprintf(
stderr,
"%s Create VM. (%s, slot#%d) \n",
vboxwrapper_msg_prefix(buf, sizeof(buf)),
vm_name.c_str(),
aid.slot
);
rc = m_pVirtualBox->CreateMachine(
CComBSTR(string(virtual_machine_slot_directory + string("/") + vm_name + string(".vbox")).c_str()),
CComBSTR(vm_name.c_str()),
NULL,
CComBSTR(os_name.c_str()),
CComBSTR(""),
&m_pMachine
);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error creating virtual machine instance! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
// Register the VM. Note that this call also saves the VM config
// to disk. It is also possible to save the VM settings but not
// register the VM.
//
// Also note that due to current VirtualBox limitations, the machine
// must be registered *before* we can attach hard disks to it.
//
rc = m_pVirtualBox->RegisterMachine(m_pMachine);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error registering virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
rc = m_pMachine->LockMachine(m_pSession, LockType_Write);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error locking virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
rc = m_pSession->get_Machine(&m_pMachine);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error retrieving mutable virtual machine object! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
rc = m_pMachine->get_BIOSSettings(&pBIOSSettings);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error retrieving the BIOS settings for the virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
rc = m_pMachine->get_BandwidthControl(&pBandwidthControl);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error retrieving the Bandwidth Control settings for the virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
rc = m_pMachine->get_VRDEServer(&pVRDEServer);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error retrieving the Remote Desktop settings for the virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
rc = m_pMachine->GetNetworkAdapter(0, &pNetworkAdapter);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error retrieving the Network Adapter for the virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
rc = pNetworkAdapter->get_NATEngine(&pNATEngine);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error retrieving the NAT Engine for the virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
rc = m_pMachine->get_AudioAdapter(&pAudioAdapter);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error retrieving the Audio Adapter for the virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
// Set some properties
m_pMachine->put_Description(CComBSTR(vm_master_description.c_str()));
// Tweak the VM's Memory Size
//
fprintf(
stderr,
"%s Setting Memory Size for VM. (%dMB)\n",
vboxwrapper_msg_prefix(buf, sizeof(buf)),
(int)memory_size_mb
);
rc = m_pMachine->put_MemorySize((int)(memory_size_mb*1024));
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error memory size for the virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
// Tweak the VM's CPU Count
//
fprintf(
stderr,
"%s Setting CPU Count for VM. (%s)\n",
vboxwrapper_msg_prefix(buf, sizeof(buf)),
vm_cpu_count.c_str()
);
rc = m_pMachine->put_CPUCount((int)atoi(vm_cpu_count.c_str()));
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error CPU count for the virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
// Tweak the VM's Chipset Options
//
fprintf(
stderr,
"%s Setting Chipset Options for VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
rc = pBIOSSettings->put_ACPIEnabled(TRUE);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error setting ACPI enabled for the virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
rc = pBIOSSettings->put_IOAPICEnabled(TRUE);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error setting IOAPIC enabled for the virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
// Tweak the VM's Boot Options
//
fprintf(
stderr,
"%s Setting Boot Options for VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
rc = m_pMachine->SetBootOrder(1, DeviceType_HardDisk);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error setting hard disk boot order for the virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
rc = m_pMachine->SetBootOrder(2, DeviceType_DVD);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error setting DVD boot order for the virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
m_pMachine->SetBootOrder(3, DeviceType_Null);
m_pMachine->SetBootOrder(4, DeviceType_Null);
// Tweak the VM's Network Configuration
//
fprintf(
stderr,
"%s Disconnecting VM Network Access.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
rc = pNetworkAdapter->put_Enabled(FALSE);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error disabling network access for the virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
if (network_bridged_mode) {
fprintf(
stderr,
"%s Setting Network Configuration for Bridged Mode.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
rc = pNetworkAdapter->put_AttachmentType(NetworkAttachmentType_Bridged);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error setting network configuration for the virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
get_default_network_interface(default_interface);
fprintf(
stderr,
"%s Setting Bridged Interface. (%s)\n",
vboxwrapper_msg_prefix(buf, sizeof(buf)),
default_interface.c_str()
);
rc = pNetworkAdapter->put_BridgedInterface(CComBSTR(CA2W(default_interface.c_str())));
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error setting network configuration (brigded interface) for the virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
} else {
fprintf(
stderr,
"%s Setting Network Configuration for NAT.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
rc = pNetworkAdapter->put_AttachmentType(NetworkAttachmentType_NAT);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error setting network configuration for the virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
rc = pNATEngine->put_DNSProxy(TRUE);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error setting network configuration (DNS Proxy) for the virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
}
// Tweak the VM's USB Configuration
//
fprintf(
stderr,
"%s Disabling USB Support for VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
rc = m_pMachine->GetUSBControllerCountByType(USBControllerType_OHCI, &lOHCICtrls);
if (SUCCEEDED(rc) && lOHCICtrls) {
rc = m_pMachine->RemoveUSBController(CComBSTR("OHCI"));
}
// Tweak the VM's COM Port Support
//
fprintf(
stderr,
"%s Disabling COM Port Support for VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
rc = m_pMachine->GetSerialPort(0, &pSerialPort);
if (SUCCEEDED(rc)) {
pSerialPort->put_Enabled(FALSE);
}
rc = m_pMachine->GetSerialPort(1, &pSerialPort);
if (SUCCEEDED(rc)) {
pSerialPort->put_Enabled(FALSE);
}
// Tweak the VM's LPT Port Support
//
fprintf(
stderr,
"%s Disabling LPT Port Support for VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
rc = m_pMachine->GetParallelPort(0, &pParallelPort);
if (SUCCEEDED(rc)) {
pParallelPort->put_Enabled(FALSE);
}
rc = m_pMachine->GetParallelPort(1, &pParallelPort);
if (SUCCEEDED(rc)) {
pParallelPort->put_Enabled(FALSE);
}
// Tweak the VM's Audio Support
//
fprintf(
stderr,
"%s Disabling Audio Support for VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
pAudioAdapter->put_Enabled(FALSE);
// Tweak the VM's Clipboard Support
//
fprintf(
stderr,
"%s Disabling Clipboard Support for VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
m_pMachine->put_ClipboardMode(ClipboardMode_Disabled);
// Tweak the VM's Drag & Drop Support
//
fprintf(
stderr,
"%s Disabling Drag and Drop Support for VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
m_pMachine->put_DragAndDropMode(DragAndDropMode_Disabled);
// 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")) {
fprintf(
stderr,
"%s Hardware acceleration CPU extensions not detected. Disabling VirtualBox hardware acceleration support.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
disable_acceleration = true;
}
if (strstr(aid.host_info.p_features, "hypervisor")) {
fprintf(
stderr,
"%s Running under Hypervisor. Disabling VirtualBox hardware acceleration support.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
disable_acceleration = true;
}
if (is_boinc_client_version_newer(aid, 7, 2, 16)) {
if (aid.vm_extensions_disabled) {
fprintf(
stderr,
"%s Hardware acceleration failed with previous execution. Disabling VirtualBox hardware acceleration support.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
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.
vboxwrapper_msg_prefix(buf, sizeof(buf));
fprintf(
stderr,
"%s Legacy fallback configuration detected. Disabling VirtualBox hardware acceleration support.\n"
"%s NOTE: Upgrading to BOINC 7.2.16 or better may re-enable hardware acceleration.\n",
buf,
buf
);
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) {
fprintf(
stderr,
"%s Disabling hardware acceleration support for virtualization.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
rc = m_pMachine->SetHWVirtExProperty(HWVirtExPropertyType_Enabled, FALSE);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error disabling hardware acceleration support for the virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
}
} else if (os_name.find("_64") != std::string::npos) {
if (disable_acceleration) {
fprintf(
stderr,
"%s ERROR: Invalid configuration. VM type requires acceleration but the current configuration cannot support it.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
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
//
fprintf(
stderr,
"%s Adding storage controller(s) to VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
pDiskController;
pFloppyController;
if (0 == stricmp(vm_disk_controller_type.c_str(), "ide")) {
rc = m_pMachine->AddStorageController(CComBSTR("Hard Disk Controller"), StorageBus_IDE, &pDiskController);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error adding storage controller (IDE) to the virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
}
if (0 == stricmp(vm_disk_controller_type.c_str(), "sata")) {
rc = m_pMachine->AddStorageController(CComBSTR("Hard Disk Controller"), StorageBus_SATA, &pDiskController);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error adding storage controller (SATA) to the virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
pDiskController->put_UseHostIOCache(FALSE);
pDiskController->put_PortCount(3);
}
if (0 == stricmp(vm_disk_controller_type.c_str(), "sas")) {
rc = m_pMachine->AddStorageController(CComBSTR("Hard Disk Controller"), StorageBus_SAS, &pDiskController);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error adding storage controller (SAS) to the virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
pDiskController->put_UseHostIOCache(FALSE);
}
if (0 == stricmp(vm_disk_controller_type.c_str(), "scsi")) {
rc = m_pMachine->AddStorageController(CComBSTR("Hard Disk Controller"), StorageBus_SCSI, &pDiskController);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error adding storage controller (SCSI) to the virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
pDiskController->put_UseHostIOCache(FALSE);
}
if (0 == stricmp(vm_disk_controller_model.c_str(), "lsilogic")) {
pDiskController->put_ControllerType(StorageControllerType_LsiLogic);
} else if (0 == stricmp(vm_disk_controller_model.c_str(), "buslogic")) {
pDiskController->put_ControllerType(StorageControllerType_BusLogic);
} else if (0 == stricmp(vm_disk_controller_model.c_str(), "intelahci")) {
pDiskController->put_ControllerType(StorageControllerType_IntelAhci);
} else if (0 == stricmp(vm_disk_controller_model.c_str(), "piix3")) {
pDiskController->put_ControllerType(StorageControllerType_PIIX3);
} else if (0 == stricmp(vm_disk_controller_model.c_str(), "piix4")) {
pDiskController->put_ControllerType(StorageControllerType_PIIX4);
} else if (0 == stricmp(vm_disk_controller_model.c_str(), "ich6")) {
pDiskController->put_ControllerType(StorageControllerType_ICH6);
} else if (0 == stricmp(vm_disk_controller_model.c_str(), "i82078")) {
pDiskController->put_ControllerType(StorageControllerType_I82078);
} else if (0 == stricmp(vm_disk_controller_model.c_str(), "lsilogicsas")) {
pDiskController->put_ControllerType(StorageControllerType_LsiLogicSas);
}
// Add storage controller for a floppy device if desired
//
if (enable_floppyio) {
rc = m_pMachine->AddStorageController(CComBSTR("Floppy Controller"), StorageBus_Floppy, &pFloppyController);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error adding storage controller (Floppy) to the virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
}
if (enable_isocontextualization) {
// Add virtual ISO 9660 disk drive to VM
//
fprintf(
stderr,
"%s Adding virtual ISO 9660 disk drive to VM. (%s)\n",
vboxwrapper_msg_prefix(buf, sizeof(buf)),
iso_image_filename.c_str()
);
CComPtr<IMedium> pISOImage;
rc = m_pVirtualBox->OpenMedium(
CComBSTR(string(virtual_machine_slot_directory + "/" + iso_image_filename).c_str()),
DeviceType_DVD,
AccessMode_ReadOnly,
FALSE,
&pISOImage
);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error adding virtual ISO 9660 disk drive to VirtualBox! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
rc = m_pMachine->AttachDevice(
CComBSTR("Hard Disk Controller"),
0,
0,
DeviceType_DVD,
pISOImage
);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error adding virtual ISO 9660 disk drive to virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
// Add guest additions to the VM
//
fprintf(
stderr,
"%s Adding VirtualBox Guest Additions to VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
CComPtr<IMedium> pGuestAdditionsImage;
rc = m_pVirtualBox->OpenMedium(
CComBSTR(virtualbox_guest_additions.c_str()),
DeviceType_DVD,
AccessMode_ReadOnly,
FALSE,
&pGuestAdditionsImage
);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error adding VirtualBox Guest Additions to VirtualBox! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
rc = m_pMachine->AttachDevice(
CComBSTR("Hard Disk Controller"),
2,
0,
DeviceType_DVD,
pGuestAdditionsImage
);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error adding VirtualBox Guest Additions to virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
// Add a virtual cache disk drive to VM
//
if (enable_cache_disk){
fprintf(
stderr,
"%s Adding virtual cache disk drive to VM. (%s)\n",
vboxwrapper_msg_prefix(buf, sizeof(buf)),
cache_disk_filename.c_str()
);
CComPtr<IMedium> pCacheImage;
rc = m_pVirtualBox->OpenMedium(
CComBSTR(string(virtual_machine_slot_directory + "/" + cache_disk_filename).c_str()),
DeviceType_HardDisk,
AccessMode_ReadWrite,
TRUE,
&pCacheImage
);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error adding virtual cache disk drive to VirtualBox! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
rc = m_pMachine->AttachDevice(
CComBSTR("Hard Disk Controller"),
1,
0,
DeviceType_HardDisk,
pCacheImage
);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error adding virtual cache disk drive to virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
}
} else {
// Adding virtual hard drive to VM
//
fprintf(
stderr,
"%s Adding virtual disk drive to VM. (%s)\n",
vboxwrapper_msg_prefix(buf, sizeof(buf)),
image_filename.c_str()
);
CComPtr<IMedium> pDiskImage;
rc = m_pVirtualBox->OpenMedium(
CComBSTR(string(virtual_machine_slot_directory + "/" + cache_disk_filename).c_str()),
DeviceType_HardDisk,
AccessMode_ReadWrite,
TRUE,
&pDiskImage
);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error adding virtual disk drive to VirtualBox! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
rc = m_pMachine->AttachDevice(
CComBSTR("Hard Disk Controller"),
0,
0,
DeviceType_HardDisk,
pDiskImage
);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error adding virtual disk drive to virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
// Add guest additions to the VM
//
fprintf(
stderr,
"%s Adding VirtualBox Guest Additions to VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
CComPtr<IMedium> pGuestAdditionsImage;
rc = m_pVirtualBox->OpenMedium(
CComBSTR(virtualbox_guest_additions.c_str()),
DeviceType_DVD,
AccessMode_ReadOnly,
FALSE,
&pGuestAdditionsImage
);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error adding VirtualBox Guest Additions to VirtualBox! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
rc = m_pMachine->AttachDevice(
CComBSTR("Hard Disk Controller"),
1,
0,
DeviceType_DVD,
pGuestAdditionsImage
);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error adding VirtualBox Guest Additions to virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
}
// 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 FloppyIO(floppy_image_filename.c_str());
if (!pFloppy->ready()) {
vboxwrapper_msg_prefix(buf, sizeof(buf));
fprintf(
stderr,
"%s Creating virtual floppy image failed.\n"
"%s Error Code '%d' Error Message '%s'\n",
buf,
buf,
pFloppy->error,
pFloppy->errorStr.c_str()
);
return ERR_FWRITE;
}
fprintf(
stderr,
"%s Adding virtual floppy disk drive to VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
CComPtr<IMedium> pFloppyImage;
rc = m_pVirtualBox->OpenMedium(
CComBSTR(string(virtual_machine_slot_directory + "/" + floppy_image_filename).c_str()),
DeviceType_Floppy,
AccessMode_ReadWrite,
TRUE,
&pFloppyImage
);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error adding virtual floppy disk image to VirtualBox! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
rc = m_pMachine->AttachDevice(
CComBSTR("Hard Disk Controller"),
0,
0,
DeviceType_Floppy,
pFloppyImage
);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error adding virtual floppy disk image to virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
}
// Add network bandwidth throttle group
//
fprintf(
stderr,
"%s Adding network bandwidth throttle group to VM. (Defaulting to 1024GB)\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
rc = pBandwidthControl->CreateBandwidthGroup(
CComBSTR(string(vm_name + "_net").c_str()),
BandwidthGroupType_Network,
(LONG64)1024*1024*1024*1024
);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error adding network bandwidth group to virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
// Enable the network adapter if a network connection is required.
//
if (enable_network) {
set_network_access(true);
// set up port forwarding
//
if (pf_guest_port) {
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++) {
PORT_FORWARD& pf = port_forwards[i];
fprintf(
stderr,
"%s forwarding host port %d to guest port %d\n",
vboxwrapper_msg_prefix(buf, sizeof(buf)),
pf.host_port, pf.guest_port
);
// Add new firewall rule
//
rc = pNATEngine->AddRedirect(
CComBSTR(""),
NATProtocol_TCP,
pf.is_remote?CComBSTR(""):CComBSTR("127.0.0.1"),
pf.host_port,
CComBSTR(""),
pf.guest_port
);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error adding port forward to virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
}
}
// If the VM wants to enable remote desktop for the VM do it here
//
if (enable_remotedesktop) {
fprintf(
stderr,
"%s Enabling remote desktop for VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
if (!is_extpack_installed()) {
fprintf(
stderr,
"%s Required extension pack not installed, remote desktop not enabled.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
} else {
retval = boinc_get_port(false, rd_host_port);
if (retval) return retval;
sprintf(buf, "%d", rd_host_port);
pVRDEServer->put_Enabled(TRUE);
pVRDEServer->put_VRDEExtPack(CComBSTR(""));
pVRDEServer->put_AuthLibrary(CComBSTR(""));
pVRDEServer->put_AuthType(AuthType_Null);
pVRDEServer->SetVRDEProperty(CComBSTR("TCP/Ports"), CComBSTR(buf));
}
}
// Enable the shared folder if a shared folder is specified.
//
if (enable_shared_directory) {
fprintf(
stderr,
"%s Enabling shared directory for VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
rc = m_pMachine->CreateSharedFolder(
CComBSTR("shared"),
CComBSTR(string(virtual_machine_slot_directory + "/shared").c_str()),
TRUE,
TRUE
);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error could not create shared folder for virtual machine! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
}
CLEANUP:
return retval;
}
int VBOX_VM::register_vm() {
string command;
string output;
string virtual_machine_slot_directory;
APP_INIT_DATA aid;
char buf[256];
int retval;
boinc_get_init_data_p(&aid);
get_slot_directory(virtual_machine_slot_directory);
// Reset VM name in case it was changed while deregistering a stale VM
//
vm_name = vm_master_name;
fprintf(
stderr,
"%s Register VM. (%s, slot#%d) \n",
vboxwrapper_msg_prefix(buf, sizeof(buf)),
vm_name.c_str(),
aid.slot
);
// 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 retval;
}
int VBOX_VM::deregister_vm(bool delete_media) {
string command;
string output;
string virtual_machine_slot_directory;
char buf[256];
get_slot_directory(virtual_machine_slot_directory);
fprintf(
stderr,
"%s Deregistering VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
// Cleanup any left-over snapshots
//
cleanup_snapshots(true);
// Delete network bandwidth throttle group
//
if (is_virtualbox_version_newer(4, 2, 0)) {
fprintf(
stderr,
"%s Removing network bandwidth throttle group from VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
command = "bandwidthctl \"" + vm_name + "\" ";
command += "remove \"" + vm_name + "_net\" ";
vbm_popen(command, output, "network throttle group (remove)", false, false);
}
// Delete its storage controller(s)
//
fprintf(
stderr,
"%s Removing storage controller(s) from VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
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
//
fprintf(
stderr,
"%s Removing VM from VirtualBox.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
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) {
fprintf(
stderr,
"%s Removing virtual ISO 9660 disk from VirtualBox.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
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) {
fprintf(
stderr,
"%s Removing virtual cache disk from VirtualBox.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
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 {
fprintf(
stderr,
"%s Removing virtual disk drive from VirtualBox.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
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) {
fprintf(
stderr,
"%s Removing virtual floppy disk from VirtualBox.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
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);
// We need to determine what the name or uuid is of the previous VM which owns
// this virtual disk
//
command = "showhdinfo \"" + virtual_machine_slot_directory + "/" + image_filename + "\" ";
retval = vbm_popen(command, output, "get HDD info");
if (retval) return retval;
// Output 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
//
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);
}
}
}
return 0;
}
void VBOX_VM::poll(bool log_state) {
char buf[256];
APP_INIT_DATA aid;
HRESULT rc;
MachineState vmstate;
static MachineState vmstate_old = MachineState_PoweredOff;
boinc_get_init_data_p(&aid);
//
// Is our environment still sane?
//
if (aid.using_sandbox && vboxsvc_pid_handle && !process_exists(vboxsvc_pid_handle)) {
fprintf(
stderr,
"%s Status Report: vboxsvc.exe is no longer running.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
}
if (vm_pid_handle && !process_exists(vm_pid_handle)) {
fprintf(
stderr,
"%s Status Report: virtualbox.exe/vboxheadless.exe is no longer running.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
}
//
// What state is the VM in?
//
rc = m_pMachine->get_State(&vmstate);
if (SUCCEEDED(rc)) {
// 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.
//
switch(vmstate)
{
case MachineState_Running:
online = true;
saving = false;
restoring = false;
suspended = false;
crashed = false;
break;
case MachineState_Paused:
online = true;
saving = false;
restoring = false;
suspended = true;
crashed = false;
break;
case MachineState_Starting:
online = true;
saving = false;
restoring = false;
suspended = false;
crashed = false;
break;
case MachineState_Stopping:
online = true;
saving = false;
restoring = false;
suspended = false;
crashed = false;
break;
case MachineState_Saving:
online = true;
saving = true;
restoring = false;
suspended = false;
crashed = false;
break;
case MachineState_Restoring:
online = true;
saving = false;
restoring = true;
suspended = false;
crashed = false;
break;
case MachineState_LiveSnapshotting:
online = true;
saving = false;
restoring = false;
suspended = false;
crashed = false;
break;
case MachineState_DeletingSnapshotOnline:
online = true;
saving = false;
restoring = false;
suspended = false;
crashed = false;
break;
case MachineState_DeletingSnapshotPaused:
online = true;
saving = false;
restoring = false;
suspended = false;
crashed = false;
break;
case MachineState_Aborted:
online = false;
saving = false;
restoring = false;
suspended = false;
crashed = true;
break;
case MachineState_Stuck:
online = false;
saving = false;
restoring = false;
suspended = false;
crashed = true;
break;
default:
online = false;
saving = false;
restoring = false;
suspended = false;
crashed = false;
break;
}
if (log_state) {
fprintf(
stderr,
"%s VM is no longer is a running state. It is in '%s'.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf)),
MachineStateToName(vmstate)
);
}
if (log_state && (vmstate_old != vmstate)) {
fprintf(
stderr,
"%s VM state change detected. (old = '%s', new = '%s')\n",
vboxwrapper_msg_prefix(buf, sizeof(buf)),
MachineStateToName(vmstate_old),
MachineStateToName(vmstate)
);
vmstate_old = vmstate;
}
}
//
// 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();
}
int VBOX_VM::start() {
int retval = ERR_EXEC;
HRESULT rc;
CComBSTR vm_name(vm_master_name.c_str());
CComBSTR session_type;
CComPtr<IProgress> pProgress;
BOOL bCompleted;
char buf[256];
double timeout;
fprintf(
stderr,
"%s Starting VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
if (!headless) {
session_type = _T("vrdp");
} else {
session_type = _T("headless");
}
rc = m_pVirtualBox->FindMachine(vm_name, &m_pMachine);
if (SUCCEEDED(rc)) {
// Start a VM session
rc = m_pMachine->LaunchVMProcess(m_pSession, session_type, NULL, &pProgress);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error could not launch VM process! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
// Wait until VM is running.
rc = pProgress->WaitForCompletion(-1);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error could not wait for VM start completion! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
pProgress->get_Completed(&bCompleted);
if (bCompleted) {
// We should now own what goes on with the VM.
//
m_pMachine->LockMachine(m_pSession, LockType_Write);
rc = m_pMachine->get_SessionPID((ULONG*)&vm_pid);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error could not get VM PID! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
}
vm_pid_handle = OpenProcess(
PROCESS_QUERY_INFORMATION | PROCESS_SET_INFORMATION,
FALSE,
vm_pid
);
// Make sure we are in a running state before proceeding
//
timeout = dtime() + 300;
do {
poll(false);
if (online) break;
boinc_sleep(1.0);
} while (timeout >= dtime());
fprintf(
stderr,
"%s Successfully started VM. (PID = '%d')\n",
vboxwrapper_msg_prefix(buf, sizeof(buf)),
vm_pid
);
retval = BOINC_SUCCESS;
} else {
fprintf(
stderr,
"%s VM failed to start.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
}
}
CLEANUP:
return retval;
}
int VBOX_VM::stop() {
int retval = ERR_EXEC;
HRESULT rc;
char buf[256];
double timeout;
CComPtr<IConsole> pConsole;
CComPtr<IProgress> pProgress;
fprintf(
stderr,
"%s Stopping VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
// Get console object.
rc = m_pSession->get_Console(&pConsole);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error retrieving console object! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
// Save the state of the machine.
rc = pConsole->SaveState(&pProgress);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error could not save the state of the VM! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
// Wait until VM is powered down.
rc = pProgress->WaitForCompletion(-1);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error could not wait for VM save state completion! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
if (!retval) {
timeout = dtime() + 300;
do {
poll(false);
if (!online && !saving) break;
boinc_sleep(1.0);
} while (timeout >= dtime());
}
if (!online) {
fprintf(
stderr,
"%s Successfully stopped VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
retval = BOINC_SUCCESS;
} else {
fprintf(
stderr,
"%s VM did not stop when requested.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
// Attempt to terminate the VM
retval = kill_program(vm_pid);
if (retval) {
fprintf(
stderr,
"%s VM was NOT successfully terminated.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
} else {
fprintf(
stderr,
"%s VM was successfully terminated.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
}
}
CLEANUP:
return retval;
}
int VBOX_VM::poweroff() {
int retval = ERR_EXEC;
HRESULT rc;
char buf[256];
double timeout;
CComPtr<IConsole> pConsole;
CComPtr<IProgress> pProgress;
fprintf(
stderr,
"%s Powering off VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
if (online) {
// Get console object.
rc = m_pSession->get_Console(&pConsole);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error retrieving console object! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
// Power down the VM as quickly as possible.
rc = pConsole->PowerDown(&pProgress);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error could not save the state of the VM! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
// Wait until VM is powered down.
rc = pProgress->WaitForCompletion(-1);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error could not wait for VM save state completion! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
// 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) {
fprintf(
stderr,
"%s Successfully stopped VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
retval = BOINC_SUCCESS;
} else {
fprintf(
stderr,
"%s VM did not stop when requested.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
// Attempt to terminate the VM
retval = kill_program(vm_pid);
if (retval) {
fprintf(
stderr,
"%s VM was NOT successfully terminated.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
} else {
fprintf(
stderr,
"%s VM was successfully terminated.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
}
}
}
CLEANUP:
return retval;
}
int VBOX_VM::pause() {
int retval = ERR_EXEC;
HRESULT rc;
char buf[256];
CComPtr<IConsole> pConsole;
// 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();
// Get console object.
rc = m_pSession->get_Console(&pConsole);
if (FAILED(rc))
{
fprintf(
stderr,
"%s Error retrieving console object! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
// Pause the machine.
rc = pConsole->Pause();
if (FAILED(rc))
{
fprintf(
stderr,
"%s Error could not pause VM! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
else
{
retval = BOINC_SUCCESS;
}
CLEANUP:
return retval;
}
int VBOX_VM::resume() {
int retval = ERR_EXEC;
HRESULT rc;
char buf[256];
CComPtr<IConsole> pConsole;
// Set the process priority back to the lowest level before resuming
// execution
//
lower_vm_process_priority();
// Get console object.
rc = m_pSession->get_Console(&pConsole);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error retrieving console object! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
// Resume the machine.
rc = pConsole->Resume();
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error could not resume VM! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
else
{
retval = BOINC_SUCCESS;
}
CLEANUP:
return retval;
}
int VBOX_VM::create_snapshot(double elapsed_time) {
string command;
string output;
char buf[256];
int retval;
fprintf(
stderr,
"%s Creating new snapshot for VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
// 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
retval = cleanup_snapshots(false);
if (retval) return retval;
fprintf(
stderr,
"%s Checkpoint completed.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
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;
char buf[256];
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);
fprintf(
stderr,
"%s Deleting stale snapshot.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
// 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;
char buf[256];
int retval = BOINC_SUCCESS;
fprintf(
stderr,
"%s Restore from previously saved snapshot.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
command = "snapshot \"" + vm_name + "\" ";
command += "restorecurrent ";
retval = vbm_popen(command, output, "restore current snapshot", true, false, 0);
if (retval) return retval;
fprintf(
stderr,
"%s Restore completed.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
return retval;
}
void VBOX_VM::dump_hypervisor_status_reports() {
char buf[256];
SIZE_T ulMinimumWorkingSetSize;
SIZE_T ulMaximumWorkingSetSize;
if (
GetProcessWorkingSetSize(
vboxsvc_pid_handle,
&ulMinimumWorkingSetSize,
&ulMaximumWorkingSetSize)
) {
fprintf(
stderr,
"%s Status Report (VirtualBox VboxSvc.exe): Minimum WSS: '%dKB', Maximum WSS: '%dKB'\n",
vboxwrapper_msg_prefix(buf, sizeof(buf)),
ulMinimumWorkingSetSize/1024,
ulMaximumWorkingSetSize/1024
);
}
if (
GetProcessWorkingSetSize(
vm_pid_handle,
&ulMinimumWorkingSetSize,
&ulMaximumWorkingSetSize)
) {
fprintf(
stderr,
"%s Status Report (VirtualBox Vboxheadless.exe/VirtualBox.exe): Minimum WSS: '%dKB', Maximum WSS: '%dKB'\n",
vboxwrapper_msg_prefix(buf, sizeof(buf)),
ulMinimumWorkingSetSize/1024,
ulMaximumWorkingSetSize/1024
);
}
}
int VBOX_VM::is_registered() {
int retval = ERR_NOT_FOUND;
HRESULT rc;
CComBSTR vm_name(vm_master_name.c_str());
CComPtr<IMachine> pMachine;
rc = m_pVirtualBox->FindMachine(vm_name, &pMachine);
if (VBOX_E_OBJECT_NOT_FOUND != rc) {
retval = BOINC_SUCCESS;
}
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;
char buf[256];
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) {
fprintf(
stderr,
"%s WARNING: Communication with VM Hypervisor failed. (Possibly Out of Memory).\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
message = "Communication with VM Hypervisor failed. (Possibly Out of Memory).";
rc = false;
}
if (output.find("Processor count:") == string::npos) {
fprintf(
stderr,
"%s WARNING: Communication with VM Hypervisor failed.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
message = "Communication with VM Hypervisor failed.";
rc = false;
}
if (output.find("WARNING: The vboxdrv kernel module is not loaded.") != string::npos) {
vboxwrapper_msg_prefix(buf, sizeof(buf));
fprintf(
stderr,
"%s WARNING: The vboxdrv kernel module is not loaded.\n"
"%s WARNING: Please update/recompile VirtualBox kernel drivers.\n",
buf,
buf
);
message = "Please update/recompile VirtualBox kernel drivers.";
rc = false;
}
return rc;
}
bool VBOX_VM::is_hdd_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;
}
}
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 ) {
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 1;
}
return 0;
}
int VBOX_VM::get_version_information(string& version) {
int retval = ERR_EXEC;
HRESULT rc;
CComBSTR tmp;
rc = m_pVirtualBox->get_VersionNormalized(&tmp);
if (SUCCEEDED(rc)) {
version = CW2A(tmp);
retval = BOINC_SUCCESS;
}
return retval;
}
int VBOX_VM::get_guest_additions(string& guest_additions) {
int retval = ERR_EXEC;
HRESULT rc;
CComPtr<ISystemProperties> properties;
CComBSTR tmp;
rc = m_pVirtualBox->get_SystemProperties(&properties);
if (SUCCEEDED(rc)) {
rc = properties->get_DefaultAdditionsISO(&tmp);
if (SUCCEEDED(rc)) {
guest_additions = CW2A(tmp);
retval = BOINC_SUCCESS;
}
}
return retval;
}
int VBOX_VM::get_default_network_interface(string& iface) {
int retval = ERR_EXEC;
HRESULT rc;
CComPtr<IHost> pHost;
SAFEARRAY* pNICS;
CComBSTR tmp;
rc = m_pVirtualBox->get_Host(&pHost);
if (SUCCEEDED(rc)) {
rc = pHost->FindHostNetworkInterfacesOfType(HostNetworkInterfaceType_Bridged, &pNICS);
if (SUCCEEDED(rc)) {
//aNICS.Attach(pNICS);
//aNICS[0]->get_Name(tmp);
iface = CW2A(tmp);
retval = BOINC_SUCCESS;
}
}
return retval;
}
int VBOX_VM::get_vm_network_bytes_sent(double& sent) {
int retval = ERR_EXEC;
char buf[256];
HRESULT rc;
CComPtr<IConsole> pConsole;
CComPtr<IMachineDebugger> pDebugger;
CComBSTR strPattern("/Devices/*/TransmitBytes");
CComBSTR strOutput;
string output;
string counter_value;
size_t counter_start;
size_t counter_end;
// Get console object.
rc = m_pSession->get_Console(&pConsole);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error retrieving console object! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
// Get debugger object
rc = pConsole->get_Debugger(&pDebugger);
if (SUCCEEDED(rc)) {
rc = pDebugger->GetStats(strPattern, false, &strOutput);
if (SUCCEEDED(rc)) {
output = CW2A(strOutput);
// 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);
}
retval = BOINC_SUCCESS;
}
}
CLEANUP:
return retval;
}
int VBOX_VM::get_vm_network_bytes_received(double& received) {
int retval = ERR_EXEC;
char buf[256];
HRESULT rc;
CComPtr<IConsole> pConsole;
CComPtr<IMachineDebugger> pDebugger;
CComBSTR strPattern("/Devices/*/ReceiveBytes");
CComBSTR strOutput;
string output;
string counter_value;
size_t counter_start;
size_t counter_end;
// Get console object.
rc = m_pSession->get_Console(&pConsole);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error retrieving console object! rc = 0x%x\n",
boinc_msg_prefix(buf, sizeof(buf)),
rc
);
virtualbox_dump_error();
retval = rc;
goto CLEANUP;
}
// Get debugger object
rc = pConsole->get_Debugger(&pDebugger);
if (SUCCEEDED(rc)) {
rc = pDebugger->GetStats(strPattern, false, &strOutput);
if (SUCCEEDED(rc)) {
output = CW2A(strOutput);
// 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);
}
retval = BOINC_SUCCESS;
}
}
CLEANUP:
return retval;
}
int VBOX_VM::get_vm_process_id() {
return vm_pid;
}
int VBOX_VM::get_vm_exit_code(unsigned long& exit_code) {
if (vm_pid_handle) {
GetExitCodeProcess(vm_pid_handle, &exit_code);
}
return 0;
}
// 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) {
int retval;
char buf[256];
CComPtr<INetworkAdapter> pNetworkAdapter;
HRESULT rc = ERR_EXEC;
network_suspended = !enabled;
if (enabled) {
fprintf(
stderr,
"%s Enabling network access for VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
rc = m_pMachine->GetNetworkAdapter(0, &pNetworkAdapter);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error retrieving virtualized network adapter for VM! rc = 0x%x\n",
vboxwrapper_msg_prefix(buf, sizeof(buf)),
rc
);
}
rc = pNetworkAdapter->put_Enabled(TRUE);
if (SUCCEEDED(rc)) {
retval = BOINC_SUCCESS;
}
} else {
fprintf(
stderr,
"%s Disabling network access for VM.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
rc = m_pMachine->GetNetworkAdapter(0, &pNetworkAdapter);
if (FAILED(rc)) {
fprintf(
stderr,
"%s Error retrieving virtualized network adapter for VM! rc = 0x%x\n",
vboxwrapper_msg_prefix(buf, sizeof(buf)),
rc
);
}
rc = pNetworkAdapter->put_Enabled(FALSE);
if (SUCCEEDED(rc)) {
retval = BOINC_SUCCESS;
}
}
return retval;
}
int VBOX_VM::set_cpu_usage(int percentage) {
string command;
string output;
char buf[256];
int retval;
// the arg to controlvm is percentage
//
fprintf(
stderr,
"%s Setting CPU throttle for VM. (%d%%)\n",
vboxwrapper_msg_prefix(buf, sizeof(buf)),
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) {
fprintf(
stderr,
"%s Setting network throttle for VM. (1024GB)\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
} else {
fprintf(
stderr,
"%s Setting network throttle for VM. (%dKB)\n",
vboxwrapper_msg_prefix(buf, sizeof(buf)),
kilobytes
);
}
// 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;
}
return 0;
}
void VBOX_VM::lower_vm_process_priority() {
char buf[256];
if (vm_pid_handle) {
SetPriorityClass(vm_pid_handle, BELOW_NORMAL_PRIORITY_CLASS);
fprintf(
stderr,
"%s Lowering VM Process priority.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
}
}
void VBOX_VM::reset_vm_process_priority() {
char buf[256];
if (vm_pid_handle) {
SetPriorityClass(vm_pid_handle, NORMAL_PRIORITY_CLASS);
fprintf(
stderr,
"%s Restoring VM Process priority.\n",
vboxwrapper_msg_prefix(buf, sizeof(buf))
);
}
}
// Launch VboxSVC.exe before going any further. if we don't, it'll be launched by
// svchost.exe with its environment block which will not contain the reference
// to VBOX_USER_HOME which is required for running in the BOINC account-based
// sandbox on Windows.
int VBOX_VM::launch_vboxsvc() {
APP_INIT_DATA aid;
PROC_MAP pm;
PROCINFO p;
string command;
int retval = ERR_EXEC;
char buf[256];
STARTUPINFO si;
PROCESS_INFORMATION pi;
int pidVboxSvc = 0;
HANDLE hVboxSvc = NULL;
memset(&si, 0, sizeof(si));
memset(&pi, 0, sizeof(pi));
boinc_get_init_data_p(&aid);
if (aid.using_sandbox) {
if (!vboxsvc_pid_handle || !process_exists(vboxsvc_pid_handle)) {
if (vboxsvc_pid_handle) CloseHandle(vboxsvc_pid_handle);
procinfo_setup(pm);
for (PROC_MAP::iterator i = pm.begin(); i != pm.end(); ++i) {
p = i->second;
// We are only looking for vboxsvc
if (0 != stricmp(p.command, "vboxsvc.exe")) continue;
// Store process id for later use
pidVboxSvc = p.id;
// Is this the vboxsvc for the current user?
// Non-service install it would be the current username
// Service install it would be boinc_project
hVboxSvc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, p.id);
if (hVboxSvc) break;
}
if (pidVboxSvc && hVboxSvc) {
fprintf(
stderr,
"%s Status Report: Detected vboxsvc.exe. (PID = '%d')\n",
vboxwrapper_msg_prefix(buf, sizeof(buf)),
pidVboxSvc
);
vboxsvc_pid = pidVboxSvc;
vboxsvc_pid_handle = hVboxSvc;
retval = BOINC_SUCCESS;
} else {
si.cb = sizeof(STARTUPINFO);
si.dwFlags |= STARTF_FORCEOFFFEEDBACK | STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
command = "\"VBoxSVC.exe\" --logrotate 1";
CreateProcess(NULL, (LPTSTR)command.c_str(), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
if (pi.hThread) CloseHandle(pi.hThread);
if (pi.hProcess) {
fprintf(
stderr,
"%s Status Report: Launching vboxsvc.exe. (PID = '%d')\n",
vboxwrapper_msg_prefix(buf, sizeof(buf)),
pi.dwProcessId
);
vboxsvc_pid = pi.dwProcessId;
vboxsvc_pid_handle = pi.hProcess;
retval = BOINC_SUCCESS;
} else {
fprintf(
stderr,
"%s Status Report: Launching vboxsvc.exe failed!.\n"
" Error: %s",
vboxwrapper_msg_prefix(buf, sizeof(buf)),
windows_format_error_string(GetLastError(), buf, sizeof(buf))
);
}
}
}
}
return retval;
}
// Launch the VM.
int VBOX_VM::launch_vboxvm() {
char buf[256];
char cmdline[1024];
char* argv[5];
int argc;
std::string output;
int retval = ERR_EXEC;
char error_msg[256];
STARTUPINFO si;
PROCESS_INFORMATION pi;
SECURITY_ATTRIBUTES sa;
SECURITY_DESCRIPTOR sd;
HANDLE hReadPipe = NULL, hWritePipe = NULL;
void* pBuf = NULL;
DWORD dwCount = 0;
unsigned long ulExitCode = 0;
unsigned long ulExitTimeout = 0;
memset(&si, 0, sizeof(si));
memset(&pi, 0, sizeof(pi));
memset(&sa, 0, sizeof(sa));
memset(&sd, 0, sizeof(sd));
// Construct the command line parameters
//
if (headless) {
argv[0] = const_cast<char*>("VboxHeadless.exe");
} else {
argv[0] = const_cast<char*>("VirtualBox.exe");
}
argv[1] = const_cast<char*>("--startvm");
argv[2] = const_cast<char*>(vm_name.c_str());
if (headless) {
argv[3] = const_cast<char*>("--vrde config");
} else {
argv[3] = const_cast<char*>("--no-startvm-errormsgbox");
}
argv[4] = NULL;
argc = 4;
strcpy(cmdline, "");
for (int i=0; i<argc; i++) {
strcat(cmdline, argv[i]);
if (i<argc-1) {
strcat(cmdline, " ");
}
}
InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(&sd, true, NULL, false);
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = &sd;
if (!CreatePipe(&hReadPipe, &hWritePipe, &sa, NULL)) {
fprintf(
stderr,
"%s CreatePipe failed! (%d).\n",
vboxwrapper_msg_prefix(buf, sizeof(buf)),
GetLastError()
);
goto CLEANUP;
}
SetHandleInformation(hReadPipe, HANDLE_FLAG_INHERIT, 0);
si.cb = sizeof(STARTUPINFO);
si.dwFlags |= STARTF_FORCEOFFFEEDBACK | STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.wShowWindow = SW_HIDE;
si.hStdOutput = hWritePipe;
si.hStdError = hWritePipe;
si.hStdInput = NULL;
// Execute command
if (!CreateProcess(
NULL,
cmdline,
NULL,
NULL,
TRUE,
CREATE_NO_WINDOW,
NULL,
NULL,
&si,
&pi
)) {
vboxwrapper_msg_prefix(buf, sizeof(buf));
fprintf(
stderr,
"%s Status Report: Launching virtualbox.exe/vboxheadless.exe failed!.\n"
"%s Error: %s (%d)\n",
buf,
buf,
windows_format_error_string(GetLastError(), error_msg, sizeof(error_msg)),
GetLastError()
);
goto CLEANUP;
}
while(1) {
GetExitCodeProcess(pi.hProcess, &ulExitCode);
// Copy stdout/stderr to output buffer, handle in the loop so that we can
// copy the pipe as it is populated and prevent the child process from blocking
// in case the output is bigger than pipe buffer.
PeekNamedPipe(hReadPipe, NULL, NULL, NULL, &dwCount, NULL);
if (dwCount) {
pBuf = malloc(dwCount+1);
memset(pBuf, 0, dwCount+1);
if (ReadFile(hReadPipe, pBuf, dwCount, &dwCount, NULL)) {
output += (char*)pBuf;
}
free(pBuf);
}
if ((ulExitCode != STILL_ACTIVE) || (ulExitTimeout >= 1000)) break;
Sleep(250);
ulExitTimeout += 250;
}
if (ulExitCode != STILL_ACTIVE) {
vboxwrapper_msg_prefix(buf, sizeof(buf));
fprintf(
stderr,
"%s Status Report: Virtualbox.exe/Vboxheadless.exe exited prematurely!.\n"
"%s Exit Code: %d\n"
"%s Output:\n"
"%s\n",
buf,
buf,
ulExitCode,
buf,
output.c_str()
);
}
if (pi.hProcess && (ulExitCode == STILL_ACTIVE)) {
vm_pid = pi.dwProcessId;
vm_pid_handle = pi.hProcess;
retval = BOINC_SUCCESS;
}
CLEANUP:
if (pi.hThread) CloseHandle(pi.hThread);
if (hReadPipe) CloseHandle(hReadPipe);
if (hWritePipe) CloseHandle(hWritePipe);
return retval;
}