// 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 _MSC_VER
#pragma warning(disable: 4706)
#endif

#ifdef _VIRTUALBOX_IMPORT_FUNCTIONS_

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.
//
#define CHECK_ERROR(rc) \
    retval = virtualbox_check_error(rc, __FUNCTION__, __FILE__, __LINE__)

int virtualbox_check_error(HRESULT rc, char* szFunction, char* szFile, int iLine) {
    HRESULT hr;
    CComPtr<IErrorInfo> pErrorInfo;
    CComBSTR strSource;
    CComBSTR strDescription;

    if (FAILED(rc)) {
        vboxlog_msg("Error 0x%x in %s (%s:%d)", rc, szFunction, szFile, iLine);
        hr = ::GetErrorInfo(0, &pErrorInfo);
        if (SUCCEEDED(hr) && pErrorInfo) {
            hr = pErrorInfo->GetSource(&strSource);
            if (SUCCEEDED(hr) && strSource) {
                vboxlog_msg("Error Source     : %S", strSource);
            }
            hr = pErrorInfo->GetDescription(&strDescription);
            if (SUCCEEDED(hr) && strDescription) {
                vboxlog_msg("Error Description: %S", strDescription);
            }
        } else {
            vboxlog_msg("Error: Getting Error Info! hr = 0x%x", hr);
        }
    }
    return rc;
}

void __stdcall virtualbox_com_raise_error(HRESULT rc, IErrorInfo* perrinfo) {
    HRESULT hr;
    CComPtr<IErrorInfo> pErrorInfo;
    CComBSTR strSource;
    CComBSTR strDescription;

    pErrorInfo.Attach(perrinfo);

    vboxlog_msg("COM Error 0x%x", rc);
    hr = pErrorInfo->GetSource(&strSource);
    if (SUCCEEDED(hr) && strSource) {
        vboxlog_msg("Error Source     : %S", strSource);
    }
    hr = pErrorInfo->GetDescription(&strDescription);
    if (SUCCEEDED(hr) && strDescription) {
        vboxlog_msg("Error Description: %S", strDescription);
    }

    DebugBreak();
}


// We want to recurisively walk the snapshot tree so that we can get the most recent children first.
// We also want to skip whatever the most current snapshot is.
//
void TraverseSnapshots(std::string& current_snapshot_id, std::vector<std::string>& snapshots, ISnapshot* pSnapshot) {
    HRESULT rc;
    SAFEARRAY* pSnapshots = NULL;
    CComSafeArray<LPDISPATCH> aSnapshots;
    CComBSTR tmp;
    ULONG lCount;
    std::string snapshot_id;

    // Check to see if we have any children
    //
    rc = pSnapshot->GetChildrenCount(&lCount);
    if (SUCCEEDED(rc) && lCount) {
        rc = pSnapshot->get_Children(&pSnapshots);
        if (SUCCEEDED(rc)) {
            aSnapshots.Attach(pSnapshots);
            if (aSnapshots.GetCount() > 0) {
                for (int i = 0; i < (int)aSnapshots.GetCount(); i++) {
                    TraverseSnapshots(current_snapshot_id, snapshots, (ISnapshot*)(LPDISPATCH)aSnapshots[i]);
                }
            }
        }
    }

    // Check to see if we are the most recent snapshot.
    // if not, add the snapshot id to the list of snapshots to be deleted.
    //
    pSnapshot->get_Id(&tmp);
    if (SUCCEEDED(rc)) {
        snapshot_id = CW2A(tmp);
        if (current_snapshot_id == snapshot_id) {
            return;
        } else {
            snapshots.push_back(snapshot_id);
        }
    }
}


// We want to recurisively walk the medium tree so that we can get the most recent children first.
//
void TraverseMediums(std::vector<CComPtr<IMedium>>& mediums, IMedium* pMedium) {
    HRESULT rc;
    SAFEARRAY* pMediums = NULL;
    CComSafeArray<LPDISPATCH> aMediums;

    // Check to see if we have any children
    //
    rc = pMedium->get_Children(&pMediums);
    if (SUCCEEDED(rc)) {
        aMediums.Attach(pMediums);
        if (aMediums.GetCount() > 0) {
            for (int i = 0; i < (int)aMediums.GetCount(); i++) {
                TraverseMediums(mediums, (IMedium*)(LPDISPATCH)aMediums[i]);
            }
        }
    }

    mediums.push_back(CComPtr<IMedium>(pMedium));
}


VBOX_VM::VBOX_VM() {
    // Initialize COM Exception Trap
    //
    _set_com_error_handler(virtualbox_com_raise_error);

    m_pPrivate = new VBOX_PRIV();
}

VBOX_VM::~VBOX_VM() {
    if (m_pPrivate) {
        delete m_pPrivate;
        m_pPrivate = NULL;
    }
}

int VBOX_VM::initialize() {
    int rc = BOINC_SUCCESS;
    string old_path;
    string new_path;
    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
    //
    // 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()))) {
            vboxlog_msg("Failed to modify the search path");
        }
    }

    // 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()))) {
            vboxlog_msg("Failed to modify the search path");
        }
    }

    // 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();

    // Instantiate the VirtualBox root object.
    rc = m_pPrivate->m_pVirtualBox.CreateInstance(CLSID_VirtualBox);
    if (FAILED(rc)) {
        vboxlog_msg("Error creating VirtualBox instance! rc = 0x%x", rc);
        return rc;
    }

    // Create the session object.
    rc = m_pPrivate->m_pSession.CreateInstance(CLSID_Session);
    if (FAILED(rc)) {
        vboxlog_msg("Error creating Session instance! rc = 0x%x", rc);
        return rc;
    }

    rc = get_version_information(virtualbox_version_raw, virtualbox_version_display);
    if (rc) return rc;

    // Get the guest addition information
    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;
    CComPtr<IMachine> pMachineRO;
    CComPtr<IMachine> pMachine;
    CComPtr<ISession> pSession;
    CComPtr<IBIOSSettings> pBIOSSettings;
    CComPtr<INetworkAdapter> pNetworkAdapter;
    CComPtr<INATEngine> pNATEngine;
    CComPtr<IUSBController> pUSBContoller;
    CComPtr<ISerialPort> pSerialPort1;
    CComPtr<ISerialPort> pSerialPort2;
    CComPtr<IParallelPort> pParallelPort1;
    CComPtr<IParallelPort> pParallelPort2;
    CComPtr<IAudioAdapter> pAudioAdapter;
    CComPtr<IStorageController> pDiskController;
    CComPtr<IStorageController> pFloppyController;
    CComPtr<IBandwidthControl> pBandwidthControl;
    CComPtr<IVRDEServer> pVRDEServer;
    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);


    rc = pSession.CoCreateInstance(CLSID_Session);
    if (CHECK_ERROR(rc)) goto CLEANUP;


    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";
        }
    }

    // Start the VM creation process
    //
    rc = m_pPrivate->m_pVirtualBox->CreateMachine(
        CComBSTR(string(virtual_machine_slot_directory + "\\" + vm_name + "\\" + vm_name + ".vbox").c_str()),
        CComBSTR(vm_name.c_str()),
        NULL,
        CComBSTR(os_name.c_str()),
        CComBSTR(""),
        &pMachineRO
    );
    if (CHECK_ERROR(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_pPrivate->m_pVirtualBox->RegisterMachine(pMachineRO);
    if (CHECK_ERROR(rc)) goto CLEANUP;
    
    rc = pMachineRO->LockMachine(pSession, LockType_Write);
    if (CHECK_ERROR(rc)) goto CLEANUP;

    rc = pSession->get_Machine(&pMachine);
    if (CHECK_ERROR(rc)) goto CLEANUP;

    rc = pMachine->get_BIOSSettings(&pBIOSSettings);
    if (CHECK_ERROR(rc)) goto CLEANUP;

    rc = pMachine->get_BandwidthControl(&pBandwidthControl);
    if (CHECK_ERROR(rc)) goto CLEANUP;

    rc = pMachine->get_VRDEServer(&pVRDEServer);
    if (CHECK_ERROR(rc)) goto CLEANUP;

    rc = pMachine->GetNetworkAdapter(0, &pNetworkAdapter);
    if (CHECK_ERROR(rc)) goto CLEANUP;

    rc = pNetworkAdapter->get_NATEngine(&pNATEngine);
    if (CHECK_ERROR(rc)) goto CLEANUP;

    rc = pMachine->get_AudioAdapter(&pAudioAdapter);
    if (CHECK_ERROR(rc)) goto CLEANUP;

    // Set some properties
    //
    pMachine->put_Description(CComBSTR(vm_master_description.c_str()));

    // Tweak the VM's Memory Size
    //
    vboxlog_msg("Setting Memory Size for VM. (%dMB)", (int)memory_size_mb);
    rc = pMachine->put_MemorySize((int)(memory_size_mb));
    if (CHECK_ERROR(rc)) goto CLEANUP;

    // Tweak the VM's CPU Count
    //
    vboxlog_msg("Setting CPU Count for VM. (%s)", vm_cpu_count.c_str());
    rc = pMachine->put_CPUCount((int)atoi(vm_cpu_count.c_str()));
    if (CHECK_ERROR(rc)) goto CLEANUP;

    // Tweak the VM's Chipset Options
    //
    vboxlog_msg("Setting Chipset Options for VM.");
    rc = pBIOSSettings->put_ACPIEnabled(TRUE);
    if (CHECK_ERROR(rc)) goto CLEANUP;

    rc = pBIOSSettings->put_IOAPICEnabled(TRUE);
    if (CHECK_ERROR(rc)) goto CLEANUP;

    // Tweak the VM's Boot Options
    //
    vboxlog_msg("Setting Boot Options for VM.");
    rc = pMachine->SetBootOrder(boot_iso ? 2 : 1, DeviceType_HardDisk);
    if (CHECK_ERROR(rc)) goto CLEANUP;
    
    rc = pMachine->SetBootOrder(boot_iso ? 1 : 2, DeviceType_DVD);
    if (CHECK_ERROR(rc)) goto CLEANUP;

    pMachine->SetBootOrder(3, DeviceType_Null);
    pMachine->SetBootOrder(4, DeviceType_Null);

    // Tweak the VM's Network Configuration
    //
    if (enable_network) {
        vboxlog_msg("Enabling VM Network Access.");
        rc = pNetworkAdapter->put_Enabled(TRUE);
        if (CHECK_ERROR(rc)) goto CLEANUP;
    } else {
        vboxlog_msg("Disabling VM Network Access.");
        rc = pNetworkAdapter->put_Enabled(FALSE);
        if (CHECK_ERROR(rc)) goto CLEANUP;
    }

    if (network_bridged_mode) {
        vboxlog_msg("Setting Network Configuration for Bridged Mode.");
        rc = pNetworkAdapter->put_AttachmentType(NetworkAttachmentType_Bridged);
        if (CHECK_ERROR(rc)) goto CLEANUP;

        get_default_network_interface(default_interface);

        vboxlog_msg("Setting Bridged Interface. (%s)", default_interface.c_str());
        rc = pNetworkAdapter->put_BridgedInterface(CComBSTR(CA2W(default_interface.c_str())));
        if (CHECK_ERROR(rc)) goto CLEANUP;
    } else {
        vboxlog_msg("Setting Network Configuration for NAT.");
        rc = pNetworkAdapter->put_AttachmentType(NetworkAttachmentType_NAT);
        if (CHECK_ERROR(rc)) goto CLEANUP;

        rc = pNATEngine->put_DNSProxy(TRUE);
        if (CHECK_ERROR(rc)) goto CLEANUP;
    }

    // Tweak the VM's USB Configuration
    //
    vboxlog_msg("Disabling USB Support for VM.");
#ifdef _VIRTUALBOX42_
    rc = pMachine->get_USBController(&pUSBContoller);
    if (SUCCEEDED(rc)) {
        pUSBContoller->put_Enabled(FALSE);
    }
#else
    ULONG lOHCICtrls = 0;
    rc = pMachine->GetUSBControllerCountByType(USBControllerType_OHCI, &lOHCICtrls);
    if (SUCCEEDED(rc) && lOHCICtrls) {
        pMachine->RemoveUSBController(CComBSTR("OHCI"));
    }
#endif

    // Tweak the VM's COM Port Support
    //
    vboxlog_msg("Disabling COM Port Support for VM.");
    rc = pMachine->GetSerialPort(0, &pSerialPort1);
    if (SUCCEEDED(rc)) {
        pSerialPort1->put_Enabled(FALSE);
    }
    rc = pMachine->GetSerialPort(1, &pSerialPort2);
    if (SUCCEEDED(rc)) {
        pSerialPort2->put_Enabled(FALSE);
    }

    // Tweak the VM's LPT Port Support
    //
    vboxlog_msg("Disabling LPT Port Support for VM.");
    rc = pMachine->GetParallelPort(0, &pParallelPort1);
    if (SUCCEEDED(rc)) {
        pParallelPort1->put_Enabled(FALSE);
    }
    rc = pMachine->GetParallelPort(1, &pParallelPort2);
    if (SUCCEEDED(rc)) {
        pParallelPort2->put_Enabled(FALSE);
    }

    // Tweak the VM's Audio Support
    //
    vboxlog_msg("Disabling Audio Support for VM.");
    pAudioAdapter->put_Enabled(FALSE);

    // Tweak the VM's Clipboard Support
    //
    vboxlog_msg("Disabling Clipboard Support for VM.");
    pMachine->put_ClipboardMode(ClipboardMode_Disabled);

    // Tweak the VM's Drag & Drop Support
    //
    vboxlog_msg("Disabling Drag and Drop Support for VM.");
#if defined(_VIRTUALBOX42_) || defined (_VIRTUALBOX43_)
    pMachine->put_DragAndDropMode(DragAndDropMode_Disabled);
#else
    pMachine->put_DnDMode(DnDMode_Disabled);
#endif

    // 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.");
            rc = pMachine->SetHWVirtExProperty(HWVirtExPropertyType_Enabled, FALSE);
            if (CHECK_ERROR(rc)) goto CLEANUP;
        }
    } 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.");
            retval = ERR_INVALID_PARAM;
            goto CLEANUP;
        }
    }

    // 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.");
    if (0 == stricmp(vm_disk_controller_type.c_str(), "ide")) {
        rc = pMachine->AddStorageController(CComBSTR("Hard Disk Controller"), StorageBus_IDE, &pDiskController);
        if (CHECK_ERROR(rc)) goto CLEANUP;
    }
    if (0 == stricmp(vm_disk_controller_type.c_str(), "sata")) {
        rc = pMachine->AddStorageController(CComBSTR("Hard Disk Controller"), StorageBus_SATA, &pDiskController);
        if (CHECK_ERROR(rc)) goto CLEANUP;

        pDiskController->put_UseHostIOCache(FALSE);
        pDiskController->put_PortCount(3);
    }
    if (0 == stricmp(vm_disk_controller_type.c_str(), "sas")) {
        rc = pMachine->AddStorageController(CComBSTR("Hard Disk Controller"), StorageBus_SAS, &pDiskController);
        if (CHECK_ERROR(rc)) goto CLEANUP;

        pDiskController->put_UseHostIOCache(FALSE);
    }
    if (0 == stricmp(vm_disk_controller_type.c_str(), "scsi")) {
        rc = pMachine->AddStorageController(CComBSTR("Hard Disk Controller"), StorageBus_SCSI, &pDiskController);
        if (CHECK_ERROR(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 = pMachine->AddStorageController(CComBSTR("Floppy Controller"), StorageBus_Floppy, &pFloppyController);
        if (CHECK_ERROR(rc)) goto CLEANUP;
    }

    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());
        CComPtr<IMedium> pISOImage;
        rc = m_pPrivate->m_pVirtualBox->OpenMedium(
            CComBSTR(string(virtual_machine_slot_directory + "\\" + iso_image_filename).c_str()),
            DeviceType_DVD,
            AccessMode_ReadOnly,
            TRUE,
            &pISOImage
        );
        if (CHECK_ERROR(rc)) goto CLEANUP;

        rc = pMachine->AttachDevice(
            CComBSTR("Hard Disk Controller"),
            0,
            0,
            DeviceType_DVD,
            pISOImage
        );
        if (CHECK_ERROR(rc)) goto CLEANUP;

        // Add guest additions to the VM
        //
        if (virtualbox_guest_additions.size()) {
            vboxlog_msg("Adding VirtualBox Guest Additions to VM.");
            CComPtr<IMedium> pGuestAdditionsImage;
            rc = m_pPrivate->m_pVirtualBox->OpenMedium(
                CComBSTR(virtualbox_guest_additions.c_str()),
                DeviceType_DVD,
                AccessMode_ReadOnly,
                FALSE,
                &pGuestAdditionsImage
            );
            if (CHECK_ERROR(rc)) goto CLEANUP;

            rc = pMachine->AttachDevice(
                CComBSTR("Hard Disk Controller"),
                2,
                0,
                DeviceType_DVD,
                pGuestAdditionsImage
            );
            if (CHECK_ERROR(rc)) goto CLEANUP;
        }

        // 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());
            CComPtr<IMedium> pCacheImage;
            rc = m_pPrivate->m_pVirtualBox->OpenMedium(
                CComBSTR(string(virtual_machine_slot_directory + "\\" + cache_disk_filename).c_str()),
                DeviceType_HardDisk,
                AccessMode_ReadWrite,
                TRUE,
                &pCacheImage
            );
            if (CHECK_ERROR(rc)) goto CLEANUP;

            rc = pMachine->AttachDevice(
                CComBSTR("Hard Disk Controller"),
                1,
                0,
                DeviceType_HardDisk,
                pCacheImage
            );
            if (CHECK_ERROR(rc)) goto CLEANUP;
        }
    } else {
        // Adding virtual hard drive to VM
        //
        vboxlog_msg("Adding virtual disk drive to VM. (%s)", image_filename.c_str());
        CComPtr<IMedium> pDiskImage;
        rc = m_pPrivate->m_pVirtualBox->OpenMedium(
            CComBSTR(string(virtual_machine_slot_directory + "\\" + image_filename).c_str()),
            DeviceType_HardDisk,
            AccessMode_ReadWrite,
            TRUE,
            &pDiskImage
        );
        if (CHECK_ERROR(rc)) goto CLEANUP;

        rc = pMachine->AttachDevice(
            CComBSTR("Hard Disk Controller"),
            0,
            0,
            DeviceType_HardDisk,
            pDiskImage
        );
        if (CHECK_ERROR(rc)) goto CLEANUP;

        // Add guest additions to the VM
        //
        if (virtualbox_guest_additions.size()) {
            vboxlog_msg("Adding VirtualBox Guest Additions to VM.");
            CComPtr<IMedium> pGuestAdditionsImage;
            rc = m_pPrivate->m_pVirtualBox->OpenMedium(
                CComBSTR(virtualbox_guest_additions.c_str()),
                DeviceType_DVD,
                AccessMode_ReadOnly,
                FALSE,
                &pGuestAdditionsImage
            );
            if (CHECK_ERROR(rc)) goto CLEANUP;

            rc = pMachine->AttachDevice(
                CComBSTR("Hard Disk Controller"),
                1,
                0,
                DeviceType_DVD,
                pGuestAdditionsImage
            );
            if (CHECK_ERROR(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 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());
            retval = ERR_FWRITE;
            goto CLEANUP;
        }

        vboxlog_msg("Adding virtual floppy disk drive to VM.");
        CComPtr<IMedium> pFloppyImage;
        rc = m_pPrivate->m_pVirtualBox->OpenMedium(
            CComBSTR(string(virtual_machine_slot_directory + "\\" + floppy_image_filename).c_str()),
            DeviceType_Floppy,
            AccessMode_ReadWrite,
            TRUE,
            &pFloppyImage
        );
        if (CHECK_ERROR(rc)) goto CLEANUP;

        rc = pMachine->AttachDevice(
            CComBSTR("Floppy Controller"),
            0,
            0,
            DeviceType_Floppy,
            pFloppyImage
        );
        if (CHECK_ERROR(rc)) goto CLEANUP;
    }

    // Add network bandwidth throttle group
    //
    vboxlog_msg("Adding network bandwidth throttle group to VM. (Defaulting to 1024GB)");
    rc = pBandwidthControl->CreateBandwidthGroup(
        CComBSTR(string(vm_name + "_net").c_str()),
        BandwidthGroupType_Network,
        (LONG64)1024*1024*1024*1024
    );
    if (CHECK_ERROR(rc)) goto CLEANUP;

    // Configure port forwarding
    //
    if (enable_network) {
        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
            //
            rc = pNATEngine->AddRedirect(
                CComBSTR(""),
                NATProtocol_TCP,
                pf.is_remote?CComBSTR(""):CComBSTR("127.0.0.1"),
                pf.host_port,
                CComBSTR(""),
                pf.guest_port
            );
            if (CHECK_ERROR(rc)) goto CLEANUP;
        }
    }

    // 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) goto CLEANUP;

            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 folders if a shared folder is specified.
    //
    if (enable_shared_directory) {
        vboxlog_msg("Enabling shared directory for VM.");
        rc = pMachine->CreateSharedFolder(
            CComBSTR("shared"),
            CComBSTR(string(virtual_machine_slot_directory + "\\shared").c_str()),
            TRUE,
            TRUE
        );
        if (CHECK_ERROR(rc)) goto CLEANUP;
    }

    // Enable the scratch folder if a scratch folder is specified.
    //
    if (enable_scratch_directory) {
        vboxlog_msg("Enabling scratch shared directory for VM.");
        rc = pMachine->CreateSharedFolder(
            CComBSTR("scratch"),
            CComBSTR(virtualbox_scratch_directory.c_str()),
            TRUE,
            TRUE
        );
        if (CHECK_ERROR(rc)) goto CLEANUP;
    }


CLEANUP:
    if (pMachine) {
        pMachine->SaveSettings();
    }
    if (pSession) {
        pSession->UnlockMachine();
    }

    return retval;
}

int VBOX_VM::register_vm() {
    int retval = ERR_EXEC;
    HRESULT rc;
    string virtual_machine_slot_directory;
    APP_INIT_DATA aid;
    CComPtr<IMachine> pMachine;

    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;

    rc = m_pPrivate->m_pVirtualBox->OpenMachine(
        CComBSTR(string(virtual_machine_slot_directory + "\\" + vm_name + "\\" + vm_name + ".vbox").c_str()),
        &pMachine
    );
    if (CHECK_ERROR(rc)) goto CLEANUP;

    rc = m_pPrivate->m_pVirtualBox->RegisterMachine(pMachine);
    if (CHECK_ERROR(rc)) goto CLEANUP;

CLEANUP:
    return retval;
}

int VBOX_VM::deregister_vm(bool delete_media) {
    int retval = ERR_EXEC;
    HRESULT rc;
    SAFEARRAY* pHardDisks = NULL;
    SAFEARRAY* pEmptyHardDisks = NULL;
    SAFEARRAY* pMediumAttachments = NULL;
    CComSafeArray<LPDISPATCH> aMediumAttachments;
    CComSafeArray<LPDISPATCH> aHardDisks;
    CComPtr<ISession> pSession;
    CComPtr<IConsole> pConsole;
    CComPtr<IMachine> pMachineRO;
    CComPtr<IMachine> pMachine;
    CComPtr<IProgress> pProgress;
    CComPtr<IBandwidthControl> pBandwidthControl;
    CComPtr<ISnapshot> pRootSnapshot;
    std::vector<CComPtr<IMedium>> mediums;
    std::vector<std::string> snapshots;
    DeviceType device_type; 
    LONG lDevice;
    LONG lPort;
    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);


    rc = m_pPrivate->m_pVirtualBox->FindMachine(CComBSTR(vm_name.c_str()), &pMachineRO);
    if (SUCCEEDED(rc)) {
        if (delete_media) {
            rc = pSession.CoCreateInstance(CLSID_Session);
            if (CHECK_ERROR(rc)) goto CLEANUP;

            rc = pMachineRO->LockMachine(pSession, LockType_Write);
            if (CHECK_ERROR(rc)) goto CLEANUP;

            rc = pSession->get_Console(&pConsole);
            if (CHECK_ERROR(rc)) goto CLEANUP;

            rc = pSession->get_Machine(&pMachine);
            if (CHECK_ERROR(rc)) goto CLEANUP;


            // Delete snapshots
            //
            rc = pMachine->FindSnapshot(CComBSTR(""), &pRootSnapshot);
            if (SUCCEEDED(rc) && pRootSnapshot) {
                string s = string("");
                TraverseSnapshots(s, snapshots, pRootSnapshot);
            }
            if (snapshots.size()) {
                for (size_t i = 0; i < snapshots.size(); i++) {
                    CComPtr<IProgress> pProgress;
#if defined(_VIRTUALBOX42_) || defined(_VIRTUALBOX43_)
                    rc = pConsole->DeleteSnapshot(CComBSTR(snapshots[i].c_str()), &pProgress);
#else
                    rc = pMachine->DeleteSnapshot(CComBSTR(snapshots[i].c_str()), &pProgress);
#endif
                    if (SUCCEEDED(rc)) {
                        pProgress->WaitForCompletion(-1);
                    } else {
                        CHECK_ERROR(rc);
                    }
                }
            }

            // Close Hard Disk, DVD, and floppy mediums
            //
           vboxlog_msg("Removing virtual disk drive(s) from VM.");
            rc = pMachine->get_MediumAttachments(&pMediumAttachments);
            if (SUCCEEDED(rc)) {
                aMediumAttachments.Attach(pMediumAttachments);
                for (int i = 0; i < (int)aMediumAttachments.GetCount(); i++) {
                    CComPtr<IMediumAttachment> pMediumAttachment((IMediumAttachment*)(LPDISPATCH)aMediumAttachments[i]);
                    rc = pMediumAttachment->get_Type(&device_type);
                    if (SUCCEEDED(rc) && 
                       ((DeviceType_HardDisk == device_type) ||
                        (DeviceType_DVD == device_type) ||
                        (DeviceType_Floppy == device_type))
                    ) {
                        CComPtr<IMedium> pMedium;
                        CComBSTR strController;

                        if ((DeviceType_HardDisk == device_type) || (DeviceType_DVD == device_type)) {
                            strController = "Hard Disk Controller";
                        } else {
                            strController = "Floppy Controller";
                        }

                        pMediumAttachment->get_Device(&lDevice);
                        pMediumAttachment->get_Port(&lPort);
                        pMediumAttachment->get_Medium(&pMedium);
                        
                        // If the device in question is a DVD/CD-ROM drive, the medium may have been ejected.
                        // If so, pMedium will be NULL.
                        if (pMedium) {
                            mediums.push_back(CComPtr<IMedium>(pMedium));
                        }
                        
                        rc = pMachine->DetachDevice(strController, lPort, lDevice);
                        CHECK_ERROR(rc);
                    }
                }
            }

            // Delete network bandwidth throttle group
            //
            vboxlog_msg("Removing network bandwidth throttle group from VM.");
            rc = pMachine->get_BandwidthControl(&pBandwidthControl);
            if (SUCCEEDED(rc)) {
                pBandwidthControl->DeleteBandwidthGroup(CComBSTR(string(vm_name + "_net").c_str()));
            }

            // Delete its storage controller(s)
            //
            vboxlog_msg("Removing storage controller(s) from VM.");
            pMachine->RemoveStorageController(CComBSTR("Hard Disk Controller"));
            if (enable_floppyio) {
                pMachine->RemoveStorageController(CComBSTR("Floppy Controller"));
            }

            // Save the VM Settings so the state is stored
            //
            rc = pMachine->SaveSettings();
            CHECK_ERROR(rc);

            // Now it should be safe to close down the mediums we detached.
            //
            for (int i = 0; i < (int)mediums.size(); i++) {
                mediums[i]->Close();
            }

            // Now free the session lock
            //
            rc = pSession->UnlockMachine();
            CHECK_ERROR(rc);
        }

        // Next, delete VM
        //
        vboxlog_msg("Removing VM from VirtualBox.");
        rc = pMachineRO->Unregister(CleanupMode_Full, &pHardDisks);
        if (SUCCEEDED(rc)) {

            // We only want to close(remove from media registry) the hard disks
            // instead of deleting them, order them by most recent image first
            // and then walk back to the root.
            //
            aHardDisks.Attach(pHardDisks);
            for (int i = 0; i < (int)aHardDisks.GetCount(); i++) {
                CComPtr<IMedium> pMedium((IMedium*)(LPDISPATCH)aHardDisks[i]);
                TraverseMediums(mediums, pMedium);
            }
            for (int i = 0; i < (int)mediums.size(); i++) {
                CComPtr<IMedium> pMedium(mediums[i]);
                pMedium->Close();
            }

#ifdef _VIRTUALBOX42_
            pMachineRO->Delete(pEmptyHardDisks, &pProgress);
            if (SUCCEEDED(rc)) {
                pProgress->WaitForCompletion(-1);
            } else {
                CHECK_ERROR(rc);
            }
#else
            pMachineRO->DeleteConfig(pEmptyHardDisks, &pProgress);
            if (SUCCEEDED(rc)) {
                pProgress->WaitForCompletion(-1);
            } else {
                CHECK_ERROR(rc);
            }
#endif

        } else {
            CHECK_ERROR(rc);
        }
    }

CLEANUP:
    return 0;
}

int VBOX_VM::deregister_stale_vm() {
    HRESULT rc;
    SAFEARRAY* pHardDisks = NULL;
    SAFEARRAY* pISOImages = NULL;
    SAFEARRAY* pCacheDisks = NULL;
    SAFEARRAY* pMachines = NULL;
    SAFEARRAY* pISOMachines = NULL;
    SAFEARRAY* pCacheMachines = NULL;
    CComSafeArray<LPDISPATCH> aHardDisks;
    CComSafeArray<LPDISPATCH> aISOImages;
    CComSafeArray<LPDISPATCH> aCacheDisks;
    CComSafeArray<BSTR> aMachines;
    CComSafeArray<BSTR> aISOMachines;
    CComSafeArray<BSTR> aCacheMachines;
    CComPtr<IMedium> pHardDisk;
    CComPtr<IMedium> pISOImage;
    CComPtr<IMedium> pCacheDisk;
    CComPtr<IMachine> pMachine;
    CComBSTR strLocation;
    CComBSTR strMachineId;
    string virtual_machine_root_dir;
    string hdd_image_location;
    string iso_image_location;
    string cache_image_location;

    get_slot_directory(virtual_machine_root_dir);

    hdd_image_location = string(virtual_machine_root_dir + "\\" + image_filename);
    rc = m_pPrivate->m_pVirtualBox->get_HardDisks(&pHardDisks);
    if (SUCCEEDED(rc)) {
        aHardDisks.Attach(pHardDisks);
        for (int i = 0; i < (int)aHardDisks.GetCount(); i++) {
            pHardDisk = aHardDisks[i];
            pHardDisk->get_Location(&strLocation);
            if (0 == stricmp(hdd_image_location.c_str(), CW2A(strLocation))) {

                // Disk found
                //
                rc = pHardDisk->get_MachineIds(&pMachines);
                if (SUCCEEDED(rc) && pMachines) {
                    aMachines.Attach(pMachines);
                    // Delete all registered VMs attached to this disk image
                    //
                    for (int j = 0; j < (int)aMachines.GetCount(); j++) {
                        strMachineId = aMachines[j];
                        vm_name = CW2A(strMachineId);
                        deregister_vm(false);
                    }
                } else {
                    // Disk is in the Media Registry but now currently attached
                    // to a VM.  Close it.
                    pHardDisk->Close();
                }
            }
        }
    }

    if (enable_isocontextualization) {
        iso_image_location = string(virtual_machine_root_dir + "\\" + iso_image_filename);
        rc = m_pPrivate->m_pVirtualBox->get_DVDImages(&pISOImages);
        if (SUCCEEDED(rc)) {
            aISOImages.Attach(pISOImages);
            for (int i = 0; i < (int)aISOImages.GetCount(); i++) {
                pISOImage = aISOImages[i];
                pISOImage->get_Location(&strLocation);
                if (0 == stricmp(iso_image_location.c_str(), CW2A(strLocation))) {

                    // Image found
                    //
                    rc = pISOImage->get_MachineIds(&pISOMachines);
                    if (SUCCEEDED(rc) && pISOMachines) {
                        aISOMachines.Attach(pISOMachines);

                        // Delete all registered VMs attached to this disk image
                        //
                        for (int j = 0; j < (int)aISOMachines.GetCount(); j++) {
                            strMachineId = aISOMachines[j];
                            vm_name = CW2A(strMachineId);
                            deregister_vm(false);
                        }
                    } else {
                        // Disk is in the Media Registry but now currently attached
                        // to a VM.  Close it.
                        pISOImage->Close();
                    }
                }
            }
        }
    }

    if (enable_isocontextualization && enable_cache_disk) {
        cache_image_location = string(virtual_machine_root_dir + "\\" + cache_disk_filename);
        rc = m_pPrivate->m_pVirtualBox->get_HardDisks(&pCacheDisks);
        if (SUCCEEDED(rc)) {
            aCacheDisks.Attach(pCacheDisks);
            for (int i = 0; i < (int)aCacheDisks.GetCount(); i++) {
                pCacheDisk = aCacheDisks[i];
                pCacheDisk->get_Location(&strLocation);
                if (0 == stricmp(cache_image_location.c_str(), CW2A(strLocation))) {

                    // Disk found
                    //
                    rc = pCacheDisk->get_MachineIds(&pCacheMachines);
                    if (SUCCEEDED(rc) && pCacheMachines) {
                        aCacheMachines.Attach(pCacheMachines);

                        // Delete all registered VMs attached to this disk image
                        //
                        for (int j = 0; j < (int)aCacheMachines.GetCount(); j++) {
                            strMachineId = aCacheMachines[j];
                            vm_name = CW2A(strMachineId);
                            deregister_vm(false);
                        }
                    } else {
                        // Disk is in the Media Registry but now currently attached
                        // to a VM.  Close it.
                        pCacheDisk->Close();
                    }
                }
            }
        }
    }

    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";
	//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?
	//
	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 == "saved") {
			online = false;
			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") {
			online = true;
			saving = false;
			restoring = false;
			suspended = false;
			crashed = false;
		}
		else if (vmstate == "deletingsnapshotpaused") {
			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;
    HRESULT rc;
    CComPtr<IMachine> pMachine;
    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)) {
        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.");
    }

    //
    // What state is the VM in?
    //
    rc = m_pPrivate->m_pVirtualBox->FindMachine(CComBSTR(vm_master_name.c_str()), &pMachine);
    if (SUCCEEDED(rc) && pMachine) {
        rc = 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;
                    if (log_state) {
                        vboxlog_msg("VM is no longer is a running state. It is in '%s'.", MachineStateToName(vmstate));
                    }
                    break;
            }
            if (log_state && (vmstate_old != vmstate)) {
                vboxlog_msg(
                    "VM state change detected. (old = '%s', new = '%s')",
                    MachineStateToName(vmstate_old),
                    MachineStateToName(vmstate)
                );
                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 = ERR_EXEC;
    HRESULT rc;
    CComBSTR session_type;
    CComPtr<IMachine> pMachineRO;
    CComPtr<IProgress> pProgress;
    APP_INIT_DATA aid;
    long bCompleted = 0;
    double timeout;

    boinc_get_init_data_p(&aid);


    vboxlog_msg("Starting VM. (%s, slot#%d)", vm_name.c_str(), aid.slot);


    if (!headless) {
        session_type = _T("gui");
    } else {
        session_type = _T("headless");
    }

    rc = m_pPrivate->m_pVirtualBox->FindMachine(CComBSTR(vm_master_name.c_str()), &pMachineRO);
    if (SUCCEEDED(rc)) {

        // Start a VM session
        rc = pMachineRO->LaunchVMProcess(m_pPrivate->m_pSession, session_type, NULL, &pProgress);
        if (CHECK_ERROR(rc)) goto CLEANUP;

        // Wait until VM is running.
        rc = pProgress->WaitForCompletion(-1);
        if (CHECK_ERROR(rc)) goto CLEANUP;

        pProgress->get_Completed(&bCompleted);
        if (bCompleted) {

            // We should now own what goes on with the VM.
            //
            pMachineRO->LockMachine(m_pPrivate->m_pSession, LockType_Write);
            m_pPrivate->m_pSession->get_Machine(&m_pPrivate->m_pMachine);

            rc = m_pPrivate->m_pMachine->get_SessionPID((ULONG*)&vm_pid);
            if (CHECK_ERROR(rc)) goto CLEANUP;

            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());

            vboxlog_msg("Successfully started VM. (PID = '%d')", vm_pid);
            started_successfully = true;
            retval = BOINC_SUCCESS;
        } else {
            vboxlog_msg("VM failed to start.");
        }
    }

CLEANUP:
    return retval;
}

int VBOX_VM::stop() {
    int retval = ERR_EXEC;
    HRESULT rc;
    double timeout;
    CComPtr<IProgress> pProgress;


    vboxlog_msg("Stopping VM.");
    if (online) {

#if defined(_VIRTUALBOX42_) || defined(_VIRTUALBOX43_)
        CComPtr<IConsole> pConsole;
        // Get console object. 
        rc = m_pPrivate->m_pSession->get_Console(&pConsole);
        if (CHECK_ERROR(rc)) goto CLEANUP;

        // Save the state of the machine.
        rc = pConsole->SaveState(&pProgress);
        if (CHECK_ERROR(rc)) goto CLEANUP;
#else
        rc = m_pPrivate->m_pMachine->SaveState(&pProgress);
        if (CHECK_ERROR(rc)) goto CLEANUP;
#endif

        // Wait until VM is powered down.
        rc = pProgress->WaitForCompletion(-1);
        if (CHECK_ERROR(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.
        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.");
            }
        }

        m_pPrivate->m_pSession->UnlockMachine();
        boinc_sleep(5.0);
    }

CLEANUP:
    return retval;
}

int VBOX_VM::poweroff() {
    int retval = ERR_EXEC;
    HRESULT rc;
    double timeout;
    CComPtr<IConsole> pConsole;
    CComPtr<IProgress> pProgress;


    vboxlog_msg("Powering off VM.");
    if (online) {
        // Get console object. 
        rc = m_pPrivate->m_pSession->get_Console(&pConsole);
        if (CHECK_ERROR(rc)) goto CLEANUP;

        // Power down the VM as quickly as possible.
        rc = pConsole->PowerDown(&pProgress);
        if (CHECK_ERROR(rc)) goto CLEANUP;

        // Wait until VM is powered down.
        rc = pProgress->WaitForCompletion(-1);
        if (CHECK_ERROR(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.
        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.");
            }
        }

        m_pPrivate->m_pSession->UnlockMachine();
        boinc_sleep(5.0);
    }

CLEANUP:
    return retval;
}

int VBOX_VM::pause() {
    int retval = ERR_EXEC;
    HRESULT rc;
    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_pPrivate->m_pSession->get_Console(&pConsole);
    if (CHECK_ERROR(rc)) goto CLEANUP;

    // Pause the machine.
    if (pConsole) {
        rc = pConsole->Pause();
        if (CHECK_ERROR(rc)) goto CLEANUP;

        retval = BOINC_SUCCESS;
    }

CLEANUP:
    return retval;
}


int VBOX_VM::resume() {
    int retval = ERR_EXEC;
    HRESULT rc;
    CComPtr<IConsole> pConsole;


    // Set the process priority back to the lowest level before resuming
    // execution
    //
    lower_vm_process_priority();


    // Get console object. 
    rc = m_pPrivate->m_pSession->get_Console(&pConsole);
    if (CHECK_ERROR(rc)) goto CLEANUP;

    // Resume the machine.
    if (pConsole) {
        rc = pConsole->Resume();
        if (CHECK_ERROR(rc)) goto CLEANUP;

        retval = BOINC_SUCCESS;
    }

CLEANUP:
    return retval;
}


int VBOX_VM::capture_screenshot() {
    if (enable_screenshots_on_error) {

#if defined(_VIRTUALBOX50_) || defined(_VIRTUALBOX51_)

        int retval = ERR_EXEC;
        ULONG width, height, bpp;
        LONG xOrigin, yOrigin;
	    GuestMonitorStatus monitorStatus;
        string virtual_machine_slot_directory;
	    string screenshot_location;
        HRESULT rc;
	    FILE* f = NULL;
        SAFEARRAY* pScreenshot = NULL;
        CComSafeArray<BYTE> aScreenshot;
        CComPtr<IConsole> pConsole;
        CComPtr<IDisplay> pDisplay;
        CComPtr<IKeyboard> pKeyboard;

        get_slot_directory(virtual_machine_slot_directory);

        vboxlog_msg("Capturing screenshot.");

        rc = m_pPrivate->m_pSession->get_Console(&pConsole);
        if (CHECK_ERROR(rc)) {
        } else {
            rc = pConsole->get_Display(&pDisplay);
            if (CHECK_ERROR(rc)) {
            } else {
                // Due to a recently fixed bug in VirtualBox we are going to attempt to prevent receiving garbage
                // by waking up the console.  We'll attempt to virtually tap the 'spacebar'.
                rc = pConsole->get_Keyboard(&pKeyboard);
                if (CHECK_ERROR(rc)) {
                } else {
                    pKeyboard->PutScancode(0x39);
                    boinc_sleep(1);
                }

			    rc = pDisplay->GetScreenResolution(0, &width, &height, &bpp, &xOrigin, &yOrigin, &monitorStatus);
			    if (CHECK_ERROR(rc)) {
			    } else {
                    vboxlog_msg("Retrieving screenshot from VirtualBox.");
				    rc = pDisplay->TakeScreenShotToArray(0, width, height, BitmapFormat_PNG, &pScreenshot);
				    if (SUCCEEDED(rc)) {
					    aScreenshot.Attach(pScreenshot);

                        vboxlog_msg("Writing screenshot to disk.");

					    screenshot_location = virtual_machine_slot_directory;
					    screenshot_location += "/";
					    screenshot_location += SCREENSHOT_FILENAME;

					    f = fopen(screenshot_location.c_str(), "wb");
					    if (f) {
						    fwrite(aScreenshot.GetSafeArrayPtr(), sizeof(BYTE), aScreenshot.GetCount(), f);
						    fclose(f);
					    } else {
                            vboxlog_msg("Failed to write screenshot to disk.");
					    }
				    } else {
                        vboxlog_msg("Failed to retrieving screenshot from VirtualBox.");
                    }
			    }

		    }
	    }

        vboxlog_msg("Screenshot completed.");

#endif

    }
	return 0;
}


int VBOX_VM::create_snapshot(double elapsed_time) {
    int retval = ERR_EXEC;
    char buf[256];
    HRESULT rc;
    CComPtr<IConsole> pConsole;
    CComPtr<IProgress> pProgress;

    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);

#if defined(_VIRTUALBOX42_) || defined(_VIRTUALBOX43_)
    rc = m_pPrivate->m_pSession->get_Console(&pConsole);
    if (CHECK_ERROR(rc)) {
    } else {
        rc = pConsole->TakeSnapshot(CComBSTR(string(string("boinc_") + buf).c_str()), CComBSTR(""), &pProgress);
        if (CHECK_ERROR(rc)) {
        } else {
            rc = pProgress->WaitForCompletion(-1);
            if (CHECK_ERROR(rc)) {
            }
        }
    }
#else
    CComBSTR strUUID;
    rc = m_pPrivate->m_pMachine->TakeSnapshot(CComBSTR(string(string("boinc_") + buf).c_str()), CComBSTR(""), true, &strUUID, &pProgress);
    if (CHECK_ERROR(rc)) {
    } else {
        rc = pProgress->WaitForCompletion(-1);
        if (CHECK_ERROR(rc)) {
        }
    }
#endif

    // 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);

    if (BOINC_SUCCESS == retval) {
        vboxlog_msg("Checkpoint completed.");
    }

    return retval;
}

int VBOX_VM::cleanup_snapshots(bool delete_active) {
    int retval = ERR_EXEC;
    HRESULT rc;
    CComPtr<IConsole> pConsole;
    CComPtr<ISnapshot> pCurrentSnapshot;
    CComPtr<ISnapshot> pRootSnapshot;
    CComBSTR tmp;
    std::string current_snapshot_id;
    std::vector<std::string> snapshots;

    rc = m_pPrivate->m_pSession->get_Console(&pConsole);
    if (CHECK_ERROR(rc)) goto CLEANUP;

    // Get the current snapshot, if we do not need to delete the active snapshot
    //
    if (!delete_active) {
        rc = m_pPrivate->m_pMachine->get_CurrentSnapshot(&pCurrentSnapshot);
        if (SUCCEEDED(rc) && pCurrentSnapshot) {
            rc = pCurrentSnapshot->get_Id(&tmp);
            if (SUCCEEDED(rc)) {
                current_snapshot_id = CW2A(tmp);
            }
        }
    }

    // Get the root snapshot and traverse the tree
    //
    rc = m_pPrivate->m_pMachine->FindSnapshot(CComBSTR(""), &pRootSnapshot);
    if (SUCCEEDED(rc) && pRootSnapshot) {
        TraverseSnapshots(current_snapshot_id, snapshots, pRootSnapshot);
    }

    // Delete stale snapshots
    //
    if (snapshots.size()) {
        for (size_t i = 0; i < snapshots.size(); i++) {
            CComPtr<IProgress> pProgress;

            vboxlog_msg("Deleting stale snapshot.");
#if defined(_VIRTUALBOX42_) || defined(_VIRTUALBOX43_)
            rc = pConsole->DeleteSnapshot(CComBSTR(snapshots[i].c_str()), &pProgress);
#else
            rc = m_pPrivate->m_pMachine->DeleteSnapshot(CComBSTR(snapshots[i].c_str()), &pProgress);
#endif
            if (SUCCEEDED(rc)) {
                pProgress->WaitForCompletion(-1);
            } else {
                CHECK_ERROR(rc);
            }
        }
    }

    retval = BOINC_SUCCESS;

CLEANUP:
    return retval;
}

int VBOX_VM::restore_snapshot() {
    int retval = ERR_EXEC;
    HRESULT rc;
    CComPtr<ISession> pSession;
    CComPtr<IMachine> pMachineRO;
    CComPtr<IMachine> pMachine;
    CComPtr<IConsole> pConsole;
    CComPtr<ISnapshot> pSnapshot;
    CComPtr<IProgress> pProgress;

    if (disable_automatic_checkpoints) return BOINC_SUCCESS;

    rc = m_pPrivate->m_pVirtualBox->FindMachine(CComBSTR(vm_name.c_str()), &pMachineRO);
    if (SUCCEEDED(rc)) {
        rc = pSession.CoCreateInstance(CLSID_Session);
        if (CHECK_ERROR(rc)) goto CLEANUP;

        rc = pMachineRO->LockMachine(pSession, LockType_Write);
        if (CHECK_ERROR(rc)) goto CLEANUP;

        rc = pSession->get_Machine(&pMachine);
        if (CHECK_ERROR(rc)) goto CLEANUP;

        rc = pSession->get_Console(&pConsole);
        if (CHECK_ERROR(rc)) goto CLEANUP;

        rc = pMachine->get_CurrentSnapshot(&pSnapshot);
        if (SUCCEEDED(rc)) {
            vboxlog_msg("Restore from previously saved snapshot.");
#if defined(_VIRTUALBOX42_) || defined(_VIRTUALBOX43_)
            rc = pConsole->RestoreSnapshot(pSnapshot, &pProgress);
#else
            rc = pMachine->RestoreSnapshot(pSnapshot, &pProgress);
#endif
            if (CHECK_ERROR(rc)) goto CLEANUP;

            rc = pProgress->WaitForCompletion(-1);
            if (CHECK_ERROR(rc)) goto CLEANUP;

            vboxlog_msg("Restore completed.");
        }

        retval = BOINC_SUCCESS;
    }

CLEANUP:
    if (pMachine) {
        pMachine->SaveSettings();
    }
    if (pSession) {
        pSession->UnlockMachine();
    }

    return retval;
}

void VBOX_VM::dump_hypervisor_status_reports() {
    SIZE_T ulMinimumWorkingSetSize;
    SIZE_T ulMaximumWorkingSetSize;

    if (
        GetProcessWorkingSetSize(
            vboxsvc_pid_handle,
            &ulMinimumWorkingSetSize,
            &ulMaximumWorkingSetSize)
    ) {
        vboxlog_msg(
            "Status Report (VirtualBox VboxSvc.exe): Minimum WSS: '%dKB', Maximum WSS: '%dKB'",
            ulMinimumWorkingSetSize/1024,
            ulMaximumWorkingSetSize/1024
        );
    }

    if (
        GetProcessWorkingSetSize(
            vm_pid_handle,
            &ulMinimumWorkingSetSize,
            &ulMaximumWorkingSetSize)
    ) {
        vboxlog_msg(
            "Status Report (VirtualBox Vboxheadless.exe/VirtualBox.exe): Minimum WSS: '%dKB', Maximum WSS: '%dKB'",
            ulMinimumWorkingSetSize/1024,
            ulMaximumWorkingSetSize/1024
        );
    }
}

int VBOX_VM::is_registered() {
    int retval = ERR_NOT_FOUND;
    HRESULT rc;
    CComPtr<IMachine> pMachine;

    rc = m_pPrivate->m_pVirtualBox->FindMachine(CComBSTR(vm_master_name.c_str()), &pMachine);
    if (VBOX_E_OBJECT_NOT_FOUND != rc) {
        retval = BOINC_SUCCESS;
    }

    return retval;
}

bool VBOX_VM::is_system_ready(std::string& ) {
    return true;
}

bool VBOX_VM::is_disk_image_registered() {
    HRESULT rc;
    SAFEARRAY* pHardDisks = NULL;
    SAFEARRAY* pISOImages = NULL;
    SAFEARRAY* pCacheDisks = NULL;
    CComSafeArray<LPDISPATCH> aHardDisks;
    CComSafeArray<LPDISPATCH> aISOImages;
    CComSafeArray<LPDISPATCH> aCacheDisks;
    CComBSTR tmp;
    IMedium* pHardDisk;
    IMedium* pISOImage;
    IMedium* pCacheDisk;
    string virtual_machine_root_dir;
    string hdd_image_location;
    string iso_image_location;
    string cache_image_location;

    get_slot_directory(virtual_machine_root_dir);

    hdd_image_location = string(virtual_machine_root_dir + "\\" + image_filename);
    rc = m_pPrivate->m_pVirtualBox->get_HardDisks(&pHardDisks);
    if (SUCCEEDED(rc)) {
        aHardDisks.Attach(pHardDisks);
        for (int i = 0; i < (int)aHardDisks.GetCount(); i++) {
            pHardDisk = (IMedium*)(LPDISPATCH)aHardDisks[i];
            pHardDisk->get_Location(&tmp);
            if (0 == stricmp(hdd_image_location.c_str(), CW2A(tmp))) {
                return true;
            }
        }
    }

    if (enable_isocontextualization) {
        iso_image_location = string(virtual_machine_root_dir + "\\" + iso_image_filename);
        rc = m_pPrivate->m_pVirtualBox->get_DVDImages(&pISOImages);
        if (SUCCEEDED(rc)) {
            aISOImages.Attach(pISOImages);
            for (int i = 0; i < (int)aISOImages.GetCount(); i++) {
                pISOImage = (IMedium*)(LPDISPATCH)aISOImages[i];
                pISOImage->get_Location(&tmp);
                if (0 == stricmp(iso_image_location.c_str(), CW2A(tmp))) {
                    return true;
                }
            }
        }
    }

    if (enable_isocontextualization && enable_cache_disk) {
        cache_image_location = string(virtual_machine_root_dir + "\\" + cache_disk_filename);
        rc = m_pPrivate->m_pVirtualBox->get_HardDisks(&pCacheDisks);
        if (SUCCEEDED(rc)) {
            aCacheDisks.Attach(pHardDisks);
            for (int i = 0; i < (int)aCacheDisks.GetCount(); i++) {
                pCacheDisk = (IMedium*)(LPDISPATCH)aCacheDisks[i];
                pCacheDisk->get_Location(&tmp);
                if (0 == stricmp(cache_image_location.c_str(), CW2A(tmp))) {
                    return true;
                }
            }
        }
    }

    return false;
}

bool VBOX_VM::is_extpack_installed() {
    CComPtr<IExtPackManager> pExtPackManager;
    CComPtr<IExtPack> pExtPack;
    long bUsable = 0;
    HRESULT rc;

    rc = m_pPrivate->m_pVirtualBox->get_ExtensionPackManager(&pExtPackManager);
    if (SUCCEEDED(rc)) {
        rc = pExtPackManager->IsExtPackUsable(CComBSTR("Oracle VM VirtualBox Extension Pack"), &bUsable);
        if (SUCCEEDED(rc)) {
            if (bUsable) {
                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 ERR_FREAD;
    }
    return BOINC_SUCCESS;
}

int VBOX_VM::get_version_information(string& version_raw, string& version_display) {
    LONG    lReturnValue;
    HKEY    hkSetupHive;
    LPSTR   lpszRegistryValue = NULL;
    DWORD   dwSize = 0;
    char    buf[256];

    // change the current directory to the boinc data directory if it exists
    lReturnValue = RegOpenKeyExA(
        HKEY_LOCAL_MACHINE, 
        "SOFTWARE\\Oracle\\VirtualBox",  
        0, 
        KEY_READ,
        &hkSetupHive
    );
    if (lReturnValue == ERROR_SUCCESS) {
        // How large does our buffer need to be?
        lReturnValue = RegQueryValueExA(
            hkSetupHive,
            "VersionExt",
            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 = RegQueryValueExA( 
                hkSetupHive,
                "VersionExt",
                NULL,
                NULL,
                (LPBYTE)lpszRegistryValue,
                &dwSize
            );
            version_raw = lpszRegistryValue;

			snprintf(
                buf, sizeof(buf),
                "VirtualBox COM Interface (Version: %s)",
                lpszRegistryValue
            );
            version_display = buf;
        }
    }

    if (hkSetupHive) RegCloseKey(hkSetupHive);
    if (lpszRegistryValue) free(lpszRegistryValue);
    if (version_raw.empty()) {
		version_raw = "Unknown";
        version_display = "VirtualBox COM Interface (Version: Unknown)";
    }
    return BOINC_SUCCESS;
}

int VBOX_VM::get_guest_additions(string& guest_additions) {
    int retval = ERR_NOT_FOUND;
    HRESULT rc;
    CComPtr<ISystemProperties> properties;
    CComBSTR tmp;

    rc = m_pPrivate->m_pVirtualBox->get_SystemProperties(&properties);
    if (SUCCEEDED(rc)) {
        rc = properties->get_DefaultAdditionsISO(&tmp);
        if (SUCCEEDED(rc)) {
            guest_additions = CW2A(tmp);
            if (!boinc_file_exists(guest_additions.c_str())) {
                guest_additions.clear();
            } else {
                retval = BOINC_SUCCESS;
            }
        }
    }

    return retval;
}

int VBOX_VM::get_default_network_interface(string& iface) {
    int retval = ERR_EXEC;
    HRESULT rc;
    SAFEARRAY* pNICS = NULL;
    CComSafeArray<LPDISPATCH> aNICS;
    CComPtr<IHost> pHost;
    CComBSTR tmp;
    IHostNetworkInterface* pNIC;

    rc = m_pPrivate->m_pVirtualBox->get_Host(&pHost);
    if (SUCCEEDED(rc)) {
        rc = pHost->FindHostNetworkInterfacesOfType(HostNetworkInterfaceType_Bridged, &pNICS);
        if (SUCCEEDED(rc)) {
            // Automatically clean up array after use
            aNICS.Attach(pNICS);

            // We only need the 'default' nic, which is usally the first one.
            pNIC = (IHostNetworkInterface*)((LPDISPATCH)aNICS[0]);

            // Get the name for future use
            rc = pNIC->get_Name(&tmp);
            if (SUCCEEDED(rc)) {
                iface = CW2A(tmp);
                retval = BOINC_SUCCESS;
            }
        }
    }

    return retval;
}

int VBOX_VM::get_vm_network_bytes_sent(double& sent) {
    int retval = ERR_EXEC;
    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_pPrivate->m_pSession->get_Console(&pConsole);
    if (CHECK_ERROR(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;
    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_pPrivate->m_pSession->get_Console(&pConsole);
    if (CHECK_ERROR(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;
}

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) {
    int retval = ERR_EXEC;
    HRESULT rc;
    CComPtr<INetworkAdapter> pNetworkAdapter;

    network_suspended = !enabled;

    if (enabled) {
        vboxlog_msg("Enabling network access for VM.");
        rc = m_pPrivate->m_pMachine->GetNetworkAdapter(0, &pNetworkAdapter);
        if (CHECK_ERROR(rc)) {
        } else {
            rc = pNetworkAdapter->put_Enabled(TRUE);
            if (SUCCEEDED(rc)) {
                retval = BOINC_SUCCESS;
            }
        }
    } else {
        vboxlog_msg("Disabling network access for VM.");
        rc = m_pPrivate->m_pMachine->GetNetworkAdapter(0, &pNetworkAdapter);
        if (CHECK_ERROR(rc)) {
        } else {
            rc = pNetworkAdapter->put_Enabled(FALSE);
            if (SUCCEEDED(rc)) {
                retval = BOINC_SUCCESS;
            }
        }
    }

    return retval;
}

int VBOX_VM::set_cpu_usage(int percentage) {
    vboxlog_msg("Setting CPU throttle for VM. (%d%%)", percentage);
    m_pPrivate->m_pMachine->put_CPUExecutionCap(percentage);
    return BOINC_SUCCESS;
}

int VBOX_VM::set_network_usage(int kilobytes) {
    int retval = ERR_EXEC;
    HRESULT rc;
    CComPtr<IBandwidthControl> pBandwidthControl;
    CComPtr<IBandwidthGroup> pBandwidthGroup;

    rc = m_pPrivate->m_pMachine->get_BandwidthControl(&pBandwidthControl);
    if (SUCCEEDED(rc)) {
        rc = pBandwidthControl->GetBandwidthGroup(CComBSTR(string(vm_name + "_net").c_str()), &pBandwidthGroup);
        if (SUCCEEDED(rc)) {
            if (kilobytes == 0) {
                vboxlog_msg("Setting network throttle for VM. (1024GB)");
                rc = pBandwidthGroup->put_MaxBytesPerSec((LONG64)1024*1024*1024*1024);
            } else {
                vboxlog_msg("Setting network throttle for VM. (%dKB)", kilobytes);
                rc = pBandwidthGroup->put_MaxBytesPerSec((LONG64)kilobytes*1024);
            }
            if (CHECK_ERROR(rc)) {
            } else {
                retval = BOINC_SUCCESS;
            }
        }
    }

    return retval;
}

void VBOX_VM::lower_vm_process_priority() {
    if (vm_pid_handle) {
        SetPriorityClass(vm_pid_handle, BELOW_NORMAL_PRIORITY_CLASS);
    }
}

void VBOX_VM::reset_vm_process_priority() {
    if (vm_pid_handle) {
        SetPriorityClass(vm_pid_handle, NORMAL_PRIORITY_CLASS);
    }
}

// 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) {
                vboxlog_msg("Status Report: Detected vboxsvc.exe. (PID = '%d')", 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";

                // Execute command
                if (!CreateProcess(
                    NULL,
                    (LPTSTR)command.c_str(),
                    NULL,
                    NULL,
                    TRUE,
                    CREATE_NO_WINDOW,
                    NULL,
                    virtualbox_home_directory.c_str(),
                    &si,
                    &pi
                )) {
                    vboxlog_msg(
                        "Status Report: Launching vboxsvc.exe failed!."
                    );
                    vboxlog_msg(
                        "        Error: %s (%d)",
                        windows_format_error_string(GetLastError(), buf, sizeof(buf)),
                        GetLastError()
                    );
                }

                if (pi.hThread) CloseHandle(pi.hThread);
                if (pi.hProcess) {
                    vboxlog_msg("Status Report: Launching vboxsvc.exe. (PID = '%d')", pi.dwProcessId);
                    vboxsvc_pid = pi.dwProcessId;
                    vboxsvc_pid_handle = pi.hProcess;
                    retval = BOINC_SUCCESS;
                }
            }
        }
    }

    return retval;
}

// Launch the VM.
int VBOX_VM::launch_vboxvm() {
    char cmdline[1024];
    char* argv[5];
    int argc;
    std::string output;
    int retval = ERR_EXEC;
    char buf[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)) {
        vboxlog_msg("CreatePipe failed! (%d).", 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
    )) {
        vboxlog_msg(
            "Status Report: Launching virtualbox.exe/vboxheadless.exe failed!."
        );
        vboxlog_msg(
            "        Error: %s (%d)",
            windows_format_error_string(GetLastError(), buf, sizeof(buf)),
            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) {
        vboxlog_msg(
            "Status Report: Virtualbox.exe/Vboxheadless.exe exited prematurely!."
        );
        vboxlog_msg(
            "    Exit Code: %d",
            ulExitCode
        );
        vboxlog_msg(
            "       Output: %s",
            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;
}

#endif
