// This file is part of BOINC. // http://boinc.berkeley.edu // Copyright (C) 2010 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 . #ifdef _WIN32 #include "boinc_win.h" #include "win_util.h" #else #include #include #include #include #include #include #include #include #endif using std::string; #if defined(_MSC_VER) || defined(__MINGW32__) #define popen _popen #define pclose _pclose #define getcwd _getcwd #define putenv _putenv #endif #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 "boinc_api.h" #include "vbox.h" #include "vm.h" // Execute the vbox manage application and copy the output to the // designated buffer. // int virtualbox_vbm_popen(string& arguments, string& output) { char buf[256]; string command; // Initialize command line command = "VBoxManage -q " + arguments; #ifdef _WIN32 STARTUPINFO si; PROCESS_INFORMATION pi; SECURITY_ATTRIBUTES sa; SECURITY_DESCRIPTOR sd; HANDLE hReadPipe, hWritePipe; void* pBuf; DWORD dwCount; unsigned long ulExitCode = 0; int retval = VBOX_POPEN_ERROR; memset(&si, 0, sizeof(si)); memset(&pi, 0, sizeof(pi)); memset(&sa, 0, sizeof(sa)); memset(&sd, 0, sizeof(sd)); 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, 0)) { fprintf( stderr, "%s CreatePipe failed! (%d).\n", boinc_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, (LPTSTR)command.c_str(), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { fprintf( stderr, "%s CreateProcess failed! (%d).\n", boinc_msg_prefix(buf, sizeof(buf)), GetLastError() ); goto CLEANUP; } // Wait until process has completed while(1) { GetExitCodeProcess(pi.hProcess, &ulExitCode); if (ulExitCode != STILL_ACTIVE) break; } // Copy stdout/stderr to output buffer if (!PeekNamedPipe(hReadPipe, NULL, NULL, NULL, &dwCount, NULL)) { fprintf( stderr, "%s PeekNamedPipe failed! (%d).\n", boinc_msg_prefix(buf, sizeof(buf)), GetLastError() ); } if (dwCount) { pBuf = malloc(dwCount+1); memset(pBuf, 0, dwCount+1); if (ReadFile(hReadPipe, pBuf, dwCount, &dwCount, NULL)) { output += (char*)pBuf; } free(pBuf); } CLEANUP: if (pi.hThread) CloseHandle(pi.hThread); if (pi.hProcess) CloseHandle(pi.hProcess); if (hReadPipe) CloseHandle(hReadPipe); if (hWritePipe) CloseHandle(hWritePipe); if ((ulExitCode == 0) && (pi.hProcess)) { retval = VBOX_SUCCESS; } return retval; #else FILE* fp; // Execute command fp = popen(command.c_str(), "r"); if (fp == NULL){ fprintf( stderr, "%s vbm_popen popen failed! errno = %d\n", boinc_msg_prefix(buf, sizeof(buf)), errno ); return VBOX_POPEN_ERROR; } // Copy output to buffer while (fgets(buf, 256, fp)) { output += buf; } // Close stream pclose(fp); #endif return VBOX_SUCCESS; } // Returns the current directory in which the executable resides. // int virtualbox_generate_vm_root_dir( string& dir ) { char root_dir[256]; getcwd(root_dir, (sizeof(root_dir)*sizeof(char))); dir = root_dir; if (!dir.empty()) { return 1; } return 0; } // Generate a unique virtual machine name for a given task instance // Rules: // 1. Must be unique // 2. Must identifity itself as being part of BOINC // 3. Must be file system compatible // int virtualbox_generate_vm_name( string& name ) { APP_INIT_DATA aid; boinc_get_init_data_p( &aid ); name.empty(); name = "boinc_"; if (boinc_is_standalone()) { name += "standalone"; } else { name += aid.wu_name; } if (!name.empty()) { return 1; } return 0; } bool virtualbox_vm_is_registered() { string command; string output; string virtual_machine_name; virtualbox_generate_vm_name(virtual_machine_name); command = "showvminfo " + virtual_machine_name; if (VBOX_SUCCESS == virtualbox_vbm_popen(command, output)) { if (output.find("VBOX_E_OBJECT_NOT_FOUND") != string::npos) { return true; } } return false; } bool virtualbox_vm_is_hdd_registered() { string command; string output; string virtual_machine_root_dir; virtualbox_generate_vm_root_dir(virtual_machine_root_dir); command = "showhdinfo \"" + virtual_machine_root_dir + "/" + vm.vm_disk_image_name + "\" "; if (VBOX_SUCCESS == virtualbox_vbm_popen(command, output)) { if (output.find("VBOX_E_FILE_ERROR") != string::npos) { return true; } } return false; } bool virtualbox_vm_is_running() { string command; string output; string virtual_machine_name; virtualbox_generate_vm_name(virtual_machine_name); command = "list runningvms"; if (VBOX_SUCCESS == virtualbox_vbm_popen(command, output)) { if (output.find(virtual_machine_name) != string::npos) { return true; } } return false; } int virtualbox_get_install_directory( string& virtualbox_install_directory ) { #ifdef _WIN32 LONG lReturnValue; HKEY hkSetupHive; LPTSTR lpszRegistryValue = NULL; DWORD dwSize = 0; // change the current directory to the boinc data directory if it exists lReturnValue = RegOpenKeyEx( HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Oracle\\VirtualBox"), 0, KEY_READ, &hkSetupHive ); if (lReturnValue == ERROR_SUCCESS) { // How large does our buffer need to be? lReturnValue = RegQueryValueEx( hkSetupHive, _T("InstallDir"), NULL, NULL, NULL, &dwSize ); if (lReturnValue != ERROR_FILE_NOT_FOUND) { // Allocate the buffer space. lpszRegistryValue = (LPTSTR) malloc(dwSize); (*lpszRegistryValue) = NULL; // Now get the data lReturnValue = RegQueryValueEx( hkSetupHive, _T("InstallDir"), NULL, NULL, (LPBYTE)lpszRegistryValue, &dwSize ); virtualbox_install_directory = lpszRegistryValue; } } if (hkSetupHive) RegCloseKey(hkSetupHive); if (lpszRegistryValue) free(lpszRegistryValue); #endif return VBOX_SUCCESS; } int virtualbox_initialize() { string virtualbox_install_directory; string old_path; string new_path; char buf[256]; virtualbox_get_install_directory(virtualbox_install_directory); // Prep the environment so we can execute the vboxmanage application if (!virtualbox_install_directory.empty()) { old_path = getenv("path"); new_path = "path="; new_path += virtualbox_install_directory; // Path environment variable seperator #ifdef _WIN32 new_path += ";"; #else new_path += ":"; #endif new_path += old_path; if (putenv((char*)new_path.c_str())) { fprintf( stderr, "%s Failed to modify the search path.\n", boinc_msg_prefix(buf, sizeof(buf)) ); } } return VBOX_SUCCESS; } int virtualbox_register_vm() { string command; string output; string virtual_machine_name; string virtual_machine_root_dir; char buf[256]; int retval; virtualbox_generate_vm_name(virtual_machine_name); virtualbox_generate_vm_root_dir(virtual_machine_root_dir); fprintf( stderr, "%s Registering virtual machine. (%s) \n", boinc_msg_prefix(buf, sizeof(buf)), virtual_machine_name.c_str() ); // Create and register the VM // command = "createvm "; command += "--name \"" + virtual_machine_name + "\" "; command += "--basefolder \"" + virtual_machine_root_dir + "\" "; command += "--ostype \"" + vm.vm_os_name + "\" "; command += "--register"; retval = virtualbox_vbm_popen(command, output); if (retval) { fprintf( stderr, "%s Error registering virtual machine! rc = 0x%x\nCommand:\n%s\nOutput:\n%s\n", boinc_msg_prefix(buf, sizeof(buf)), retval, command.c_str(), output.c_str() ); return retval; } // Tweak the VM from it's default configuration // command = "modifyvm \"" + virtual_machine_name + "\" "; command += "--memory " + vm.vm_memory_size + " "; command += "--acpi on "; command += "--ioapic on "; command += "--boot1 disk "; command += "--boot2 none "; command += "--boot3 none "; command += "--boot4 none "; command += "--nic1 nat "; command += "--natdnsproxy1 on "; command += "--cableconnected1 off "; retval = virtualbox_vbm_popen(command, output); if (retval) { fprintf( stderr, "%s Error modifing virtual machine! rc = 0x%x\nCommand:\n%s\nOutput:\n%s\n", boinc_msg_prefix(buf, sizeof(buf)), retval, command.c_str(), output.c_str() ); return retval; } // Add storage controller to VM // command = "storagectl \"" + virtual_machine_name + "\" "; command += "--name \"IDE Controller\" "; command += "--add ide "; command += "--controller PIIX4 "; retval = virtualbox_vbm_popen(command, output); if (retval) { fprintf( stderr, "%s Error adding storage controller to virtual machine! rc = 0x%x\nCommand:\n%s\nOutput:\n%s\n", boinc_msg_prefix(buf, sizeof(buf)), retval, command.c_str(), output.c_str() ); return retval; } // Adding virtual hard drive to VM // command = "storageattach \"" + virtual_machine_name + "\" "; command += "--storagectl \"IDE Controller\" "; command += "--port 0 "; command += "--device 0 "; command += "--type hdd "; command += "--medium \"" + virtual_machine_root_dir + "/" + vm.vm_disk_image_name + "\" "; retval = virtualbox_vbm_popen(command, output); if (retval) { fprintf( stderr, "%s Error adding virtual disk drive to virtual machine! rc = 0x%x\nCommand:\n%s\nOutput:\n%s\n", boinc_msg_prefix(buf, sizeof(buf)), retval, command.c_str(), output.c_str() ); return retval; } // 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 itself is NOT signed. Doing so opens up // the network behind the firewall to attack. // if (vm.enable_network) { command = "modifyvm \"" + virtual_machine_name + "\" "; command += "--cableconnected1 on "; retval = virtualbox_vbm_popen(command, output); if (retval) { fprintf( stderr, "%s Error enabling network access for virtual machine! rc = 0x%x\nCommand:\n%s\nOutput:\n%s\n", boinc_msg_prefix(buf, sizeof(buf)), retval, command.c_str(), output.c_str() ); return retval; } } // Enable the shared folder if a shared folder is specified. // if (vm.enable_shared_directory) { command = "sharedfolder add \"" + virtual_machine_name + "\" "; command += "--name \"" + vm.vm_shared_folder_name + "\" "; command += "--hostpath \"" + virtual_machine_root_dir + "/" + vm.vm_shared_folder_dir_name + "\" "; retval = virtualbox_vbm_popen(command, output); if (retval) { fprintf( stderr, "%s Error enabling shared directory for virtual machine! rc = 0x%x\nCommand:\n%s\nOutput:\n%s\n", boinc_msg_prefix(buf, sizeof(buf)), retval, command.c_str(), output.c_str() ); return retval; } } return VBOX_SUCCESS; } int virtualbox_deregister_vm_by_name( string& virtual_machine_name ) { string command; string output; string virtual_machine_root_dir; char buf[256]; int retval; virtualbox_generate_vm_root_dir(virtual_machine_root_dir); fprintf( stderr, "%s Deregistering virtual machine. (%s)\n", boinc_msg_prefix(buf, sizeof(buf)), virtual_machine_name.c_str() ); // First step in deregistering a VM is to delete its storage controller // command = "storagectl \"" + virtual_machine_name + "\" "; command += "--name \"IDE Controller\" "; command += "--remove "; retval = virtualbox_vbm_popen(command, output); if (retval) { fprintf( stderr, "%s Error removing storage controller from virtual machine! rc = 0x%x\nCommand:\n%s\nOutput:\n%s\n", boinc_msg_prefix(buf, sizeof(buf)), retval, command.c_str(), output.c_str() ); return retval; } // Next delete VM // command = "unregistervm \"" + virtual_machine_name + "\" "; command += "--delete "; retval = virtualbox_vbm_popen(command, output); if (retval) { fprintf( stderr, "%s Error removing virtual machine from virtual box! rc = 0x%x\nCommand:\n%s\nOutput:\n%s\n", boinc_msg_prefix(buf, sizeof(buf)), retval, command.c_str(), output.c_str() ); return retval; } // Lastly delete medium from Virtual Box Media Registry // command = "closemedium disk \"" + virtual_machine_root_dir + "/" + vm.vm_disk_image_name + "\" "; retval = virtualbox_vbm_popen(command, output); if (retval) { fprintf( stderr, "%s Error removing virtual hdd from virtual box! rc = 0x%x\nCommand:\n%s\nOutput:\n%s\n", boinc_msg_prefix(buf, sizeof(buf)), retval, command.c_str(), output.c_str() ); return retval; } return VBOX_SUCCESS; } int virtualbox_deregister_stale_vm() { string command; string output; string virtual_machine_root_dir; string virtual_machine_name; size_t uuid_location; size_t uuid_length; char buf[256]; int retval; virtualbox_generate_vm_root_dir(virtual_machine_root_dir); // We need to determine what the name or uuid is of the previous VM which owns // this virtual disk // command = "showhdinfo \"" + virtual_machine_root_dir + "/" + vm.vm_disk_image_name + "\" "; retval = virtualbox_vbm_popen(command, output); if (retval) { fprintf( stderr, "%s Error retrieving virtual hard disk information from virtual box! rc = 0x%x\nCommand:\n%s\nOutput:\n%s\n", boinc_msg_prefix(buf, sizeof(buf)), retval, command.c_str(), output.c_str() ); 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_location = output.find("(UUID: "); if (uuid_location != string::npos) { // We can parse the virtual machine ID from the output uuid_location += 7; uuid_length = output.find(")", uuid_location); virtual_machine_name = output.substr(uuid_location, uuid_length); // Deregister stale VM by UUID return virtualbox_deregister_vm_by_name(virtual_machine_name); } else { // Did the user delete the VM in VirtualBox and not the medium? If so, // just remove the medium. command = "closemedium \"" + virtual_machine_root_dir + "/" + vm.vm_disk_image_name + "\" "; retval = virtualbox_vbm_popen(command, output); if (retval) { fprintf( stderr, "%s Error removing virtual hdd from virtual box! rc = 0x%x\nCommand:\n%s\nOutput:\n%s\n", boinc_msg_prefix(buf, sizeof(buf)), retval, command.c_str(), output.c_str() ); return retval; } } return VBOX_SUCCESS; } int virtualbox_deregister_vm() { string virtual_machine_name; virtualbox_generate_vm_name(virtual_machine_name); return virtualbox_deregister_vm_by_name(virtual_machine_name); } int virtualbox_cleanup() { return VBOX_SUCCESS; } int virtualbox_startvm() { string command; string output; string virtual_machine_name; char buf[256]; int retval; virtualbox_generate_vm_name(virtual_machine_name); command = "startvm \"" + virtual_machine_name + "\" --type headless"; retval = virtualbox_vbm_popen(command, output); if (retval) { fprintf( stderr, "%s Error starting virtual machine! rc = 0x%x\nCommand:\n%s\nOutput:\n%s\n", boinc_msg_prefix(buf, sizeof(buf)), retval, command.c_str(), output.c_str() ); return retval; } return VBOX_SUCCESS; } int virtualbox_stopvm() { string command; string output; string virtual_machine_name; char buf[256]; int retval; virtualbox_generate_vm_name(virtual_machine_name); command = "controlvm \"" + virtual_machine_name + "\" savestate"; retval = virtualbox_vbm_popen(command, output); if (retval) { fprintf( stderr, "%s Error stopping virtual machine! rc = 0x%x\nCommand:\n%s\nOutput:\n%s\n", boinc_msg_prefix(buf, sizeof(buf)), retval, command.c_str(), output.c_str() ); return retval; } return VBOX_SUCCESS; } int virtualbox_pausevm() { string command; string output; string virtual_machine_name; char buf[256]; int retval; virtualbox_generate_vm_name(virtual_machine_name); command = "controlvm \"" + virtual_machine_name + "\" pause"; retval = virtualbox_vbm_popen(command, output); if (retval) { fprintf( stderr, "%s Error pausing virtual machine! rc = 0x%x\nCommand:\n%s\nOutput:\n%s\n", boinc_msg_prefix(buf, sizeof(buf)), retval, command.c_str(), output.c_str() ); return retval; } return VBOX_SUCCESS; } int virtualbox_resumevm() { string command; string output; string virtual_machine_name; char buf[256]; int retval; virtualbox_generate_vm_name(virtual_machine_name); command = "controlvm \"" + virtual_machine_name + "\" resume"; retval = virtualbox_vbm_popen(command, output); if (retval) { fprintf( stderr, "%s Error resuming virtual machine! rc = 0x%x\nCommand:\n%s\nOutput:\n%s\n", boinc_msg_prefix(buf, sizeof(buf)), retval, command.c_str(), output.c_str() ); return retval; } return VBOX_SUCCESS; } int virtualbox_monitor() { return VBOX_SUCCESS; }