diff --git a/clientgui/BOINCTaskCtrl.cpp b/clientgui/BOINCTaskCtrl.cpp index c9eac68fd7..d4aff24330 100644 --- a/clientgui/BOINCTaskCtrl.cpp +++ b/clientgui/BOINCTaskCtrl.cpp @@ -214,7 +214,9 @@ wxInt32 CBOINCTaskCtrl::UpdateControls() { // Force update layout and scrollbars, since nothing we do here // necessarily generates a size event which would do it for us. - FitInside(); + if (layoutChanged) { + Fit (); + } return layoutChanged; } diff --git a/clientgui/ProjectInfoPage.cpp b/clientgui/ProjectInfoPage.cpp index 9e9095dcca..5a133964ad 100644 --- a/clientgui/ProjectInfoPage.cpp +++ b/clientgui/ProjectInfoPage.cpp @@ -342,11 +342,9 @@ void CProjectInfoPage::CreateControls() itemFlexGridSizer33->Add(m_pProjectURLStaticCtrl, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 5); m_pProjectURLCtrl = new wxTextCtrl( itemWizardPage23, ID_PROJECTURLCTRL, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); - itemFlexGridSizer33->Add(m_pProjectURLCtrl, 1, wxGROW|wxALIGN_CENTER_VERTICAL|wxALL, 5); + itemFlexGridSizer33->Add(m_pProjectURLCtrl, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxALL, 5); -#ifdef __WXMAC__ - itemFlexGridSizer33->Add(0, 20, 0); -#endif + itemFlexGridSizer33->Add(0, 10, 0); // Set validators m_pProjectURLCtrl->SetValidator( CValidateURL( & m_strProjectURL ) ); diff --git a/html/inc/language_names.inc b/html/inc/language_names.inc index f383544292..22950920f9 100644 --- a/html/inc/language_names.inc +++ b/html/inc/language_names.inc @@ -56,7 +56,7 @@ $language_names = array( function language_select() { global $language_names; - $supported_languages = getSupportedLanguages(); + $supported_languages = get_supported_languages(); $supported_languages[] = "en"; $bd = tra("Browser default"); echo " diff --git a/html/inc/translation.inc b/html/inc/translation.inc index 6788c4386e..8e5427ff02 100644 --- a/html/inc/translation.inc +++ b/html/inc/translation.inc @@ -25,7 +25,7 @@ $lang_log_level = 1; // Get a list of compiled languages by scanning the compiled/ dir // @returns A list of languages that have been compiled // -function getSupportedLanguages(){ +function get_supported_languages() { global $lang_language_dir, $lang_compiled_dir; $list = array(); if (!is_dir($lang_language_dir.$lang_compiled_dir)) { @@ -43,15 +43,21 @@ function getSupportedLanguages(){ return $list; } -// Builds the lookup arrays from the -// language files found in the given directory tree. +// generate PHP files defining translation arrays. +// For example, the file "ca.po.inc" would contain entries of the form +// $language_lookup_array["ca"]["Default"] = "Defecte"; +// +// Append to these files if they already exist +// (this may get done for both generic and project-specific translations) +// // @param langdir The language base directory // @param transdir The location of the .po files to compile relative to langdir // @param compdir The output location relative to langdir // -function buildLanguages($langdir, $transdir, $compdir){ +function build_translation_array_files($langdir, $transdir, $compdir) { // Run through each language and compile their lookup arrays. + // if (!is_dir($langdir.$transdir)) { //debug("$info_dir not found or is not a directory"); } @@ -61,7 +67,7 @@ function buildLanguages($langdir, $transdir, $compdir){ if ($file==".." || $file==".") { continue; } - // and only do files ending in .po + // only do files ending in .po if (substr($file,-3) != ".po"){ //debug("File $file with unknown extension found in $info_dir"); continue; @@ -69,7 +75,7 @@ function buildLanguages($langdir, $transdir, $compdir){ language_log( "-------------Compiling $transdir$file------------", 0 ); - $language = parseLanguage($langdir.$transdir.$file); + $language = parse_po_file($langdir.$transdir.$file); if (!$language){ language_log( "WARNING: Could not parse language ".$file @@ -106,7 +112,7 @@ function buildLanguages($langdir, $transdir, $compdir){ // @param file The file to parse // checking for inconsistencies if needed. // -function parseLanguage($file){ +function parse_po_file($file) { $translation_file = file($file); $first_entry = true; $current_token_text=""; @@ -124,25 +130,26 @@ function parseLanguage($file){ //If this is not the first, save the previous entry $output[$current_token]=$current_token_text; } - $current_token = getPOLineContent($entry, $file); + $current_token = get_po_line($entry, $file); $current_token_text=""; $parsing_token = true; $parsing_text = false; $first_entry=false; } elseif (strpos($entry, "msgstr") !== false) { - $current_token_text = getPOLineContent($entry, $file); + $current_token_text = get_po_line($entry, $file); $parsing_token = false; $parsing_text = true; } elseif ($parsing_token) { - $current_token .= getPOLineContent($entry, $file); + $current_token .= get_po_line($entry, $file); } elseif ($parsing_text) { - $current_token_text.=getPOLineContent($entry, $file); + $current_token_text .= get_po_line($entry, $file); } } // Get the last token + // if ($current_token && $current_token_text){ - $output[$current_token]=$current_token_text; + $output[$current_token] = $current_token_text; } return $output; } @@ -150,7 +157,7 @@ function parseLanguage($file){ // Returns the contents of a line (ie removes "" from start and end) // -function getPOLineContent($line, $file){ +function get_po_line($line, $file) { $start = strpos($line, '"')+1; $stop = strrpos($line, '"'); $x = substr($line, $start, $stop-$start); @@ -162,9 +169,12 @@ function getPOLineContent($line, $file){ return $x; } +////////// EVERYTHING BEFORE HERE IS FOR ops/update_translations.php, +////////// AND SHOULD BE MOVED TO A SEPARATE FILE + // Translate string // -function tra($text /* ...arglist... */){ +function tra($text /* ...arglist... */) { global $language_lookup_array, $languages_in_use; // Find the string in the user's language @@ -190,7 +200,7 @@ function tra($text /* ...arglist... */){ return $text; } -function tr_specific($text, $language){ +function tr_specific($text, $language) { global $lang_language_dir, $lang_compiled_dir, $language_lookup_array; $file_name = $lang_language_dir.$lang_compiled_dir.$language.".po.inc"; if (file_exists($file_name)) { @@ -200,7 +210,7 @@ function tr_specific($text, $language){ return $text; } -function language_log($message, $loglevel=0){ +function language_log($message, $loglevel=0) { global $lang_log_level; if ($loglevel==0) $msg = "[ Debug ]"; if ($loglevel==1) $msg = "[ Warning ]"; @@ -232,7 +242,7 @@ if (isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])) { // [2] => en;q=0.3 // ) -$client_languages=explode(",",$language_string); +$client_languages = explode(",", $language_string); // A language is either defined as primary-secondary or primary. // It can also have a quality attribute set, @@ -247,21 +257,21 @@ $languages_in_use = array(); // for ($i=0; $i2) - && (substr($client_languages[$i],2,1)=="_" || substr($client_languages[$i],2,1)=="-")) - { + && (substr($client_languages[$i], 2, 1) == "_" || substr($client_languages[$i], 2, 1) == "-") + ){ // If this is defined as primary-secondary, represent it as xx_YY // $language = substr( - $client_languages[$i],0,2)."_".strtoupper(substr($client_languages[$i],3,2) + $client_languages[$i], 0, 2)."_".strtoupper(substr($client_languages[$i], 3, 2) ); // And also check for the primary language // - $language2 = substr($client_languages[$i],0,2); + $language2 = substr($client_languages[$i], 0, 2); } else { // else just use xx // - $language = substr($client_languages[$i],0,2); + $language = substr($client_languages[$i], 0, 2); $language2 = null; } @@ -272,6 +282,7 @@ for ($i=0; $i= 3 && $argv[1] == '-d') { // process the generic BOINC web site strings // -buildLanguages($lang_language_dir, $lang_translations_dir, $lang_compiled_dir); +build_translation_array_files( + $lang_language_dir, $lang_translations_dir, $lang_compiled_dir +); // process the project-specific strings // -buildLanguages($lang_language_dir, $lang_prj_translations_dir, $lang_compiled_dir); +build_translation_array_files( + $lang_language_dir, $lang_prj_translations_dir, $lang_compiled_dir +); echo "update_translations finished\n"; diff --git a/html/user/language_select.php b/html/user/language_select.php index 5e5c1b1791..ef96d78aeb 100644 --- a/html/user/language_select.php +++ b/html/user/language_select.php @@ -22,7 +22,7 @@ require_once("../inc/translation.inc"); check_get_args(array("set_lang")); -$languages = getSupportedLanguages(); +$languages = get_supported_languages(); if (!is_array($languages)) { error_page("Language selection not enabled. Project admins must run the update_translations.php script."); } diff --git a/samples/vboxwrapper/vbox.cpp b/samples/vboxwrapper/vbox.cpp index 54e420c9d4..f1e482e024 100644 --- a/samples/vboxwrapper/vbox.cpp +++ b/samples/vboxwrapper/vbox.cpp @@ -96,7 +96,7 @@ VBOX_VM::VBOX_VM() { headless = true; #ifdef _WIN32 vm_pid_handle = 0; - vboxsvc_handle = 0; + vboxsvc_pid_handle = 0; #else vm_pid = 0; #endif @@ -116,9 +116,9 @@ VBOX_VM::~VBOX_VM() { CloseHandle(vm_pid_handle); vm_pid_handle = NULL; } - if (vboxsvc_handle) { - CloseHandle(vboxsvc_handle); - vboxsvc_handle = NULL; + if (vboxsvc_pid_handle) { + CloseHandle(vboxsvc_pid_handle); + vboxsvc_pid_handle = NULL; } #endif } @@ -234,350 +234,9 @@ int VBOX_VM::initialize() { return rc; } -int VBOX_VM::run(double elapsed_time) { - int retval; - - if (!is_registered()) { - if (is_hdd_registered()) { - // Handle the case where a previous instance of the same projects VM - // was already initialized for the current slot directory but aborted - // while the task was suspended and unloaded from memory. - retval = deregister_stale_vm(); - if (retval) return retval; - } - retval = register_vm(); - if (retval) return retval; - } - - // The user has requested that we exit after registering the VM, so return an - // error to stop further processing. - if (register_only) return ERR_FOPEN; - - // If we are restarting an already registered VM, then the vm_name variable - // will be empty right now, so populate it with the master name so all of the - // various other functions will work. - vm_name = vm_master_name; - - // Check to see if the VM is already in a running state, if so, poweroff. - poll(false); - if (online) { - poweroff(); - } - - // If our last checkpoint time is greater than 0, restore from the previously - // saved snapshot - if (elapsed_time) { - restoresnapshot(); - } - - // Start the VM - retval = start(); - if (retval) return retval; - - return 0; -} - -int VBOX_VM::start() { - string command; - string output; - double timeout; - char buf[256]; - int retval; - - fprintf( - stderr, - "%s Starting VM.\n", - vboxwrapper_msg_prefix(buf, sizeof(buf)) - ); - command = "startvm \"" + vm_name + "\""; - if (headless) { - command += " --type headless"; - } - retval = vbm_popen(command, output, "start VM"); - if (retval) return retval; - - // 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 interation based. - timeout = dtime() + 300; - do { - poll(false); - if (online) break; - boinc_sleep(1.0); - } while (timeout >= dtime()); - - if (online) { - fprintf( - stderr, - "%s Successfully started VM.\n", - vboxwrapper_msg_prefix(buf, sizeof(buf)) - ); - retval = BOINC_SUCCESS; - } else { - fprintf( - stderr, - "%s VM did not start within 5 minutes, aborting job.\n", - vboxwrapper_msg_prefix(buf, sizeof(buf)) - ); - retval = ERR_EXEC; - } - - return retval; -} - -int VBOX_VM::stop() { - string command; - string output; - char buf[256]; - int retval = 0; - - fprintf( - stderr, - "%s Stopping VM.\n", - vboxwrapper_msg_prefix(buf, sizeof(buf)) - ); - if (online) { - command = "controlvm \"" + vm_name + "\" savestate"; - retval = vbm_popen(command, output, "stop VM", true, false); - - poll(false); - - if (!online) { - fprintf( - stderr, - "%s Successfully stopped VM.\n", - vboxwrapper_msg_prefix(buf, sizeof(buf)) - ); - retval = BOINC_SUCCESS; - } else { - fprintf( - stderr, - "%s VM did not stop when requested.\n", - vboxwrapper_msg_prefix(buf, sizeof(buf)) - ); - retval = ERR_EXEC; - } - } - - return retval; -} - -int VBOX_VM::poweroff() { - string command; - string output; - char buf[256]; - int retval = 0; - - fprintf( - stderr, - "%s Powering off VM.\n", - vboxwrapper_msg_prefix(buf, sizeof(buf)) - ); - if (online) { - command = "controlvm \"" + vm_name + "\" poweroff"; - retval = vbm_popen(command, output, "poweroff VM", true, false); - - poll(false); - - if (!online) { - fprintf( - stderr, - "%s Successfully powered off VM.\n", - vboxwrapper_msg_prefix(buf, sizeof(buf)) - ); - retval = BOINC_SUCCESS; - } else { - fprintf( - stderr, - "%s VM did not power off when requested.\n", - vboxwrapper_msg_prefix(buf, sizeof(buf)) - ); - retval = ERR_EXEC; - } - } - - return retval; -} - -int VBOX_VM::pause() { - string command; - string output; - int retval; - - // Restore the process priority back to the default process priority - // to speed up the last minute maintenance tasks before the VirtualBox - // VM goes to sleep - // - reset_vm_process_priority(); - - command = "controlvm \"" + vm_name + "\" pause"; - retval = vbm_popen(command, output, "pause VM"); - if (retval) return retval; - suspended = true; - return 0; -} - -int VBOX_VM::resume() { - string command; - string output; - int retval; - - // Set the process priority back to the lowest level before resuming - // execution - // - lower_vm_process_priority(); - - command = "controlvm \"" + vm_name + "\" resume"; - retval = vbm_popen(command, output, "resume VM"); - if (retval) return retval; - suspended = false; - return 0; -} - -int VBOX_VM::createsnapshot(double elapsed_time) { - string command; - string output; - char buf[256]; - int retval; - - fprintf( - stderr, - "%s Creating new snapshot for VM.\n", - vboxwrapper_msg_prefix(buf, sizeof(buf)) - ); - - // Pause VM - Try and avoid the live snapshot and trigger an online - // snapshot instead. - pause(); - - // Create new snapshot - sprintf(buf, "%d", (int)elapsed_time); - command = "snapshot \"" + vm_name + "\" "; - command += "take boinc_"; - command += buf; - retval = vbm_popen(command, output, "create new snapshot", true, true, 0); - if (retval) return retval; - - // Resume VM - resume(); - - // Set the suspended flag back to false before deleting the stale - // snapshot - poll(false); - - // Delete stale snapshot(s), if one exists - cleanupsnapshots(false); - - fprintf( - stderr, - "%s Checkpoint completed.\n", - vboxwrapper_msg_prefix(buf, sizeof(buf)) - ); - - return 0; -} - -int VBOX_VM::cleanupsnapshots(bool delete_active) { - string command; - string output; - string line; - string uuid; - size_t eol_pos; - size_t eol_prev_pos; - size_t uuid_start; - size_t uuid_end; - char buf[256]; - int retval; - - - // Enumerate snapshot(s) - command = "snapshot \"" + vm_name + "\" "; - command += "list "; - retval = vbm_popen(command, output, "enumerate snapshot(s)"); - if (retval) return retval; - - // Output should look a little like this: - // Name: Snapshot 2 (UUID: 1751e9a6-49e7-4dcc-ab23-08428b665ddf) - // Name: Snapshot 3 (UUID: 92fa8b35-873a-4197-9d54-7b6b746b2c58) - // Name: Snapshot 4 (UUID: c049023a-5132-45d5-987d-a9cfadb09664) * - // - eol_prev_pos = 0; - eol_pos = output.find("\n"); - while (eol_pos != string::npos) { - line = output.substr(eol_prev_pos, eol_pos - eol_prev_pos); - - // This VM does not yet have any snapshots - if (line.find("does not have any snapshots") != string::npos) break; - - // The * signifies that it is the active snapshot and one we do not want to delete - if (!delete_active && (line.find("*") != string::npos)) break; - - uuid_start = line.find("(UUID: "); - if (uuid_start != string::npos) { - // We can parse the virtual machine ID from the line - uuid_start += 7; - uuid_end = line.find(")", uuid_start); - uuid = line.substr(uuid_start, uuid_end - uuid_start); - - fprintf( - stderr, - "%s Deleting stale snapshot.\n", - vboxwrapper_msg_prefix(buf, sizeof(buf)) - ); - - // Delete stale snapshot, if one exists - command = "snapshot \"" + vm_name + "\" "; - command += "delete \""; - command += uuid; - command += "\" "; - - vbm_popen(command, output, "delete stale snapshot", true, false, 0); - } - - eol_prev_pos = eol_pos + 1; - eol_pos = output.find("\n", eol_prev_pos); - } - - return 0; -} - -int VBOX_VM::restoresnapshot() { - string command; - string output; - char buf[256]; - int retval; - - fprintf( - stderr, - "%s Restore from previously saved snapshot.\n", - vboxwrapper_msg_prefix(buf, sizeof(buf)) - ); - - command = "snapshot \"" + vm_name + "\" "; - command += "restorecurrent "; - retval = vbm_popen(command, output, "restore current snapshot"); - if (retval) return retval; - - fprintf( - stderr, - "%s Restore completed.\n", - vboxwrapper_msg_prefix(buf, sizeof(buf)) - ); - - return 0; -} - -void VBOX_VM::cleanup() { - poweroff(); - deregister_vm(true); - - // Give time enough for external processes to finish the cleanup process - boinc_sleep(5.0); -} - void VBOX_VM::poll(bool log_state) { char buf[256]; + APP_INIT_DATA aid; string command; string output; string::iterator iter; @@ -585,6 +244,36 @@ void VBOX_VM::poll(bool log_state) { 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)) { + fprintf( + stderr, + "%s Status Report: vboxsvc.exe is no longer running.\n", + vboxwrapper_msg_prefix(buf, sizeof(buf)) + ); + } + if (vm_pid_handle && !process_exists(vm_pid_handle)) { + fprintf( + stderr, + "%s Status Report: virtualbox.exe/vboxheadless.exe is no longer running.\n", + vboxwrapper_msg_prefix(buf, sizeof(buf)) + ); + } +#else + if (vm_pid && !process_exists(vm_pid)) { + fprintf( + stderr, + "%s Status Report: virtualbox/vboxheadless is no longer running.\n", + vboxwrapper_msg_prefix(buf, sizeof(buf)) + ); + } +#endif + // // What state is the VM in? // @@ -698,168 +387,6 @@ void VBOX_VM::poll(bool log_state) { } } -void VBOX_VM::dumphypervisorlogs() { - string system_log; - unsigned long vm_exit_code = 0; - - get_system_log(system_log); - get_vm_exit_code(vm_exit_code); - - if (vm_exit_code) { - fprintf( - stderr, - " Hypervisor System Log:\n\n" - "%s\n" - " VM Execution Log:\n\n" - "%s\n" - " VM Exit Code: %d (0x%x)\n\n", - system_log.c_str(), - vm_log.c_str(), - (unsigned int)vm_exit_code, - (unsigned int)vm_exit_code - ); - } else { - fprintf( - stderr, - " Hypervisor System Log:\n\n" - "%s\n" - " VM Execution Log:\n\n" - "%s\n", - system_log.c_str(), - vm_log.c_str() - ); - } -} - -// Attempt to detect any condition that would prevent VirtualBox from running a VM properly, like: -// 1. The DCOM service not being started on Windows -// 2. Vboxmanage not being able to communicate with vboxsvc for some reason -// 3. VirtualBox driver not loaded for the current Linux kernel. -// -// Luckly both of the above conditions can be detected by attempting to detect the host information -// via vboxmanage and it is cross platform. -// -bool VBOX_VM::is_system_ready(std::string& message) { - string command; - string output; - bool rc = true; - - command = "list hostinfo "; - if (vbm_popen(command, output, "host info") == 0) { - - if (output.find("Processor count:") == string::npos) { - message = "Communication with VM Hypervisor failed."; - rc = false; - } - - if (output.find("WARNING: The vboxdrv kernel module is not loaded.") != string::npos) { - message = "Please update/recompile VirtualBox kernel drivers."; - rc = false; - } - - } - - return rc; -} - -bool VBOX_VM::is_registered() { - string command; - string output; - - command = "showvminfo \"" + vm_master_name + "\" "; - command += "--machinereadable "; - - if (vbm_popen(command, output, "registration", false, false) == 0) { - if (output.find("VBOX_E_OBJECT_NOT_FOUND") == string::npos) { - // Error message not found in text - return true; - } - } - return false; -} - -bool VBOX_VM::is_hdd_registered() { - string command; - string output; - string virtual_machine_root_dir; - - get_slot_directory(virtual_machine_root_dir); - - command = "showhdinfo \"" + virtual_machine_root_dir + "/" + image_filename + "\" "; - - if (vbm_popen(command, output, "hdd registration", false, false) == 0) { - if ((output.find("VBOX_E_FILE_ERROR") == string::npos) && - (output.find("VBOX_E_OBJECT_NOT_FOUND") == string::npos) && - (output.find("does not match the value") == string::npos) - ) { - // Error message not found in text - return true; - } - } - return false; -} - -bool VBOX_VM::is_extpack_installed() { - string command; - string output; - - command = "list extpacks"; - - if (vbm_popen(command, output, "extpack detection", false, false) == 0) { - if ((output.find("Oracle VM VirtualBox Extension Pack") != string::npos) && (output.find("VBoxVRDP") != string::npos)) { - return true; - } - } - return false; -} - -bool VBOX_VM::is_logged_failure_vm_extensions_disabled() { - if (vm_log.find("VERR_VMX_MSR_LOCKED_OR_DISABLED") != string::npos) return true; - if (vm_log.find("VERR_SVM_DISABLED") != string::npos) return true; - return false; -} - -bool VBOX_VM::is_logged_failure_vm_extensions_in_use() { - if (vm_log.find("VERR_VMX_IN_VMX_ROOT_MODE") != string::npos) return true; - if (vm_log.find("VERR_SVM_IN_USE") != string::npos) return true; - return false; -} - -bool VBOX_VM::is_logged_failure_vm_extensions_not_supported() { - if (vm_log.find("VERR_VMX_NO_VMX") != string::npos) return true; - if (vm_log.find("VERR_SVM_NO_SVM") != string::npos) return true; - return false; -} - -bool VBOX_VM::is_logged_failure_host_out_of_memory() { - if (vm_log.find("VERR_EM_NO_MEMORY") != string::npos) return true; - if (vm_log.find("VERR_NO_MEMORY") != string::npos) return true; - return false; -} - -bool VBOX_VM::is_logged_failure_guest_job_out_of_memory() { - if (vm_log.find("EXIT_OUT_OF_MEMORY") != string::npos) return true; - return false; -} - -bool VBOX_VM::is_virtualbox_version_newer(int maj, int min, int rel) { - int vbox_major = 0, vbox_minor = 0, vbox_release = 0; - if (3 == sscanf(virtualbox_version.c_str(), "%d.%d.%d", &vbox_major, &vbox_minor, &vbox_release)) { - if (maj < vbox_major) return true; - if (maj > vbox_major) return false; - if (min < vbox_minor) return true; - if (min > vbox_minor) return false; - if (rel < vbox_release) return true; - } - return false; -} - -bool VBOX_VM::is_virtualbox_error_recoverable(int retval) { - // See comments for VBOX_VM::vbm_popen about session lock issues. - if (VBOX_E_INVALID_OBJECT_STATE == retval) return true; - return false; -} - int VBOX_VM::register_vm() { string command; string output; @@ -1450,6 +977,516 @@ int VBOX_VM::deregister_stale_vm() { return 0; } +int VBOX_VM::run(double elapsed_time) { + int retval; + + if (!is_registered()) { + if (is_hdd_registered()) { + // Handle the case where a previous instance of the same projects VM + // was already initialized for the current slot directory but aborted + // while the task was suspended and unloaded from memory. + retval = deregister_stale_vm(); + if (retval) return retval; + } + retval = register_vm(); + if (retval) return retval; + } + + // The user has requested that we exit after registering the VM, so return an + // error to stop further processing. + if (register_only) return ERR_FOPEN; + + // If we are restarting an already registered VM, then the vm_name variable + // will be empty right now, so populate it with the master name so all of the + // various other functions will work. + vm_name = vm_master_name; + + // Check to see if the VM is already in a running state, if so, poweroff. + poll(false); + if (online) { + retval = poweroff(); + if (retval) return ERR_NOT_EXITED; + } + + // If our last checkpoint time is greater than 0, restore from the previously + // saved snapshot + if (elapsed_time) { + restoresnapshot(); + } + + // Start the VM + retval = start(); + if (retval) return retval; + + return 0; +} + +void VBOX_VM::cleanup() { + poweroff(); + deregister_vm(true); + + // Give time enough for external processes to finish the cleanup process + boinc_sleep(5.0); +} + +int VBOX_VM::start() { + string command; + string output; + double timeout; + char buf[256]; + int retval; + + fprintf( + stderr, + "%s Starting VM.\n", + vboxwrapper_msg_prefix(buf, sizeof(buf)) + ); + command = "startvm \"" + vm_name + "\""; + if (headless) { + command += " --type headless"; + } + retval = vbm_popen(command, output, "start VM", true, false, 0); + if (retval) return retval; + + // 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 interation based. + timeout = dtime() + 300; + do { + poll(false); + if (online) break; + boinc_sleep(1.0); + } while (timeout >= dtime()); + + if (online) { + fprintf( + stderr, + "%s Successfully started VM.\n", + vboxwrapper_msg_prefix(buf, sizeof(buf)) + ); + retval = BOINC_SUCCESS; + } else { + fprintf( + stderr, + "%s VM did not start within 5 minutes, aborting job.\n", + vboxwrapper_msg_prefix(buf, sizeof(buf)) + ); + retval = ERR_EXEC; + } + + return retval; +} + +int VBOX_VM::stop() { + string command; + string output; + char buf[256]; + int retval = 0; + + fprintf( + stderr, + "%s Stopping VM.\n", + vboxwrapper_msg_prefix(buf, sizeof(buf)) + ); + if (online) { + command = "controlvm \"" + vm_name + "\" savestate"; + retval = vbm_popen(command, output, "stop VM", true, false); + + poll(false); + + if (!online) { + fprintf( + stderr, + "%s Successfully stopped VM.\n", + vboxwrapper_msg_prefix(buf, sizeof(buf)) + ); + retval = BOINC_SUCCESS; + } else { + fprintf( + stderr, + "%s VM did not stop when requested.\n", + vboxwrapper_msg_prefix(buf, sizeof(buf)) + ); + retval = ERR_EXEC; + } + } + + return retval; +} + +int VBOX_VM::poweroff() { + string command; + string output; + char buf[256]; + int retval = 0; + + fprintf( + stderr, + "%s Powering off VM.\n", + vboxwrapper_msg_prefix(buf, sizeof(buf)) + ); + if (online) { + command = "controlvm \"" + vm_name + "\" poweroff"; + retval = vbm_popen(command, output, "poweroff VM", true, false); + + poll(false); + + if (!online) { + fprintf( + stderr, + "%s Successfully powered off VM.\n", + vboxwrapper_msg_prefix(buf, sizeof(buf)) + ); + retval = BOINC_SUCCESS; + } else { + fprintf( + stderr, + "%s VM did not power off when requested.\n", + vboxwrapper_msg_prefix(buf, sizeof(buf)) + ); + retval = ERR_EXEC; + } + } + + return retval; +} + +int VBOX_VM::pause() { + string command; + string output; + int retval; + + // Restore the process priority back to the default process priority + // to speed up the last minute maintenance tasks before the VirtualBox + // VM goes to sleep + // + reset_vm_process_priority(); + + command = "controlvm \"" + vm_name + "\" pause"; + retval = vbm_popen(command, output, "pause VM"); + if (retval) return retval; + suspended = true; + return 0; +} + +int VBOX_VM::resume() { + string command; + string output; + int retval; + + // Set the process priority back to the lowest level before resuming + // execution + // + lower_vm_process_priority(); + + command = "controlvm \"" + vm_name + "\" resume"; + retval = vbm_popen(command, output, "resume VM"); + if (retval) return retval; + suspended = false; + return 0; +} + +int VBOX_VM::createsnapshot(double elapsed_time) { + string command; + string output; + char buf[256]; + int retval; + + fprintf( + stderr, + "%s Creating new snapshot for VM.\n", + vboxwrapper_msg_prefix(buf, sizeof(buf)) + ); + + // Pause VM - Try and avoid the live snapshot and trigger an online + // snapshot instead. + pause(); + + // Create new snapshot + sprintf(buf, "%d", (int)elapsed_time); + command = "snapshot \"" + vm_name + "\" "; + command += "take boinc_"; + command += buf; + retval = vbm_popen(command, output, "create new snapshot", true, true, 0); + if (retval) return retval; + + // Resume VM + resume(); + + // Set the suspended flag back to false before deleting the stale + // snapshot + poll(false); + + // Delete stale snapshot(s), if one exists + cleanupsnapshots(false); + + fprintf( + stderr, + "%s Checkpoint completed.\n", + vboxwrapper_msg_prefix(buf, sizeof(buf)) + ); + + return 0; +} + +int VBOX_VM::cleanupsnapshots(bool delete_active) { + string command; + string output; + string line; + string uuid; + size_t eol_pos; + size_t eol_prev_pos; + size_t uuid_start; + size_t uuid_end; + char buf[256]; + int retval; + + + // Enumerate snapshot(s) + command = "snapshot \"" + vm_name + "\" "; + command += "list "; + retval = vbm_popen(command, output, "enumerate snapshot(s)"); + if (retval) return retval; + + // Output should look a little like this: + // Name: Snapshot 2 (UUID: 1751e9a6-49e7-4dcc-ab23-08428b665ddf) + // Name: Snapshot 3 (UUID: 92fa8b35-873a-4197-9d54-7b6b746b2c58) + // Name: Snapshot 4 (UUID: c049023a-5132-45d5-987d-a9cfadb09664) * + // + eol_prev_pos = 0; + eol_pos = output.find("\n"); + while (eol_pos != string::npos) { + line = output.substr(eol_prev_pos, eol_pos - eol_prev_pos); + + // This VM does not yet have any snapshots + if (line.find("does not have any snapshots") != string::npos) break; + + // The * signifies that it is the active snapshot and one we do not want to delete + if (!delete_active && (line.find("*") != string::npos)) break; + + uuid_start = line.find("(UUID: "); + if (uuid_start != string::npos) { + // We can parse the virtual machine ID from the line + uuid_start += 7; + uuid_end = line.find(")", uuid_start); + uuid = line.substr(uuid_start, uuid_end - uuid_start); + + fprintf( + stderr, + "%s Deleting stale snapshot.\n", + vboxwrapper_msg_prefix(buf, sizeof(buf)) + ); + + // Delete stale snapshot, if one exists + command = "snapshot \"" + vm_name + "\" "; + command += "delete \""; + command += uuid; + command += "\" "; + + vbm_popen(command, output, "delete stale snapshot", true, false, 0); + } + + eol_prev_pos = eol_pos + 1; + eol_pos = output.find("\n", eol_prev_pos); + } + + return 0; +} + +int VBOX_VM::restoresnapshot() { + string command; + string output; + char buf[256]; + int retval; + + fprintf( + stderr, + "%s Restore from previously saved snapshot.\n", + vboxwrapper_msg_prefix(buf, sizeof(buf)) + ); + + command = "snapshot \"" + vm_name + "\" "; + command += "restorecurrent "; + retval = vbm_popen(command, output, "restore current snapshot", true, false, 0); + if (retval) return retval; + + fprintf( + stderr, + "%s Restore completed.\n", + vboxwrapper_msg_prefix(buf, sizeof(buf)) + ); + + return 0; +} + +void VBOX_VM::dumphypervisorlogs() { + string system_log; + unsigned long vm_exit_code = 0; + + get_system_log(system_log); + get_vm_exit_code(vm_exit_code); + + if (vm_exit_code) { + fprintf( + stderr, + " Hypervisor System Log:\n\n" + "%s\n" + " VM Execution Log:\n\n" + "%s\n" + " VM Exit Code: %d (0x%x)\n\n", + system_log.c_str(), + vm_log.c_str(), + (unsigned int)vm_exit_code, + (unsigned int)vm_exit_code + ); + } else { + fprintf( + stderr, + " Hypervisor System Log:\n\n" + "%s\n" + " VM Execution Log:\n\n" + "%s\n", + system_log.c_str(), + vm_log.c_str() + ); + } +} + +// Attempt to detect any condition that would prevent VirtualBox from running a VM properly, like: +// 1. The DCOM service not being started on Windows +// 2. Vboxmanage not being able to communicate with vboxsvc for some reason +// 3. VirtualBox driver not loaded for the current Linux kernel. +// +// Luckly both of the above conditions can be detected by attempting to detect the host information +// via vboxmanage and it is cross platform. +// +bool VBOX_VM::is_system_ready(std::string& message) { + string command; + string output; + bool rc = true; + + command = "list hostinfo "; + if (vbm_popen(command, output, "host info") == 0) { + + if (output.find("Processor count:") == string::npos) { + message = "Communication with VM Hypervisor failed."; + rc = false; + } + + if (output.find("WARNING: The vboxdrv kernel module is not loaded.") != string::npos) { + message = "Please update/recompile VirtualBox kernel drivers."; + rc = false; + } + + } + + return rc; +} + +bool VBOX_VM::is_registered() { + string command; + string output; + + command = "showvminfo \"" + vm_master_name + "\" "; + command += "--machinereadable "; + + if (vbm_popen(command, output, "registration", false, false) == 0) { + if (output.find("VBOX_E_OBJECT_NOT_FOUND") == string::npos) { + // Error message not found in text + return true; + } + } + return false; +} + +bool VBOX_VM::is_hdd_registered() { + string command; + string output; + string virtual_machine_root_dir; + + get_slot_directory(virtual_machine_root_dir); + + command = "showhdinfo \"" + virtual_machine_root_dir + "/" + image_filename + "\" "; + + if (vbm_popen(command, output, "hdd registration", false, false) == 0) { + if ((output.find("VBOX_E_FILE_ERROR") == string::npos) && + (output.find("VBOX_E_OBJECT_NOT_FOUND") == string::npos) && + (output.find("does not match the value") == string::npos) + ) { + // Error message not found in text + return true; + } + } + return false; +} + +bool VBOX_VM::is_extpack_installed() { + string command; + string output; + + command = "list extpacks"; + + if (vbm_popen(command, output, "extpack detection", false, false) == 0) { + if ((output.find("Oracle VM VirtualBox Extension Pack") != string::npos) && (output.find("VBoxVRDP") != string::npos)) { + return true; + } + } + return false; +} + +bool VBOX_VM::is_logged_failure_vm_extensions_disabled() { + if (vm_log.find("VERR_VMX_MSR_LOCKED_OR_DISABLED") != string::npos) return true; + if (vm_log.find("VERR_SVM_DISABLED") != string::npos) return true; + + // VirtualBox 4.3.x or better + if (vm_log.find("VERR_VMX_MSR_VMXON_DISABLED") != string::npos) return true; + if (vm_log.find("VERR_VMX_MSR_SMX_VMXON_DISABLED") != string::npos) return true; + + return false; +} + +bool VBOX_VM::is_logged_failure_vm_extensions_in_use() { + if (vm_log.find("VERR_VMX_IN_VMX_ROOT_MODE") != string::npos) return true; + if (vm_log.find("VERR_SVM_IN_USE") != string::npos) return true; + return false; +} + +bool VBOX_VM::is_logged_failure_vm_extensions_not_supported() { + if (vm_log.find("VERR_VMX_NO_VMX") != string::npos) return true; + if (vm_log.find("VERR_SVM_NO_SVM") != string::npos) return true; + return false; +} + +bool VBOX_VM::is_logged_failure_host_out_of_memory() { + if (vm_log.find("VERR_EM_NO_MEMORY") != string::npos) return true; + if (vm_log.find("VERR_NO_MEMORY") != string::npos) return true; + return false; +} + +bool VBOX_VM::is_logged_failure_guest_job_out_of_memory() { + if (vm_log.find("EXIT_OUT_OF_MEMORY") != string::npos) return true; + return false; +} + +bool VBOX_VM::is_virtualbox_version_newer(int maj, int min, int rel) { + int vbox_major = 0, vbox_minor = 0, vbox_release = 0; + if (3 == sscanf(virtualbox_version.c_str(), "%d.%d.%d", &vbox_major, &vbox_minor, &vbox_release)) { + if (maj < vbox_major) return true; + if (maj > vbox_major) return false; + if (min < vbox_minor) return true; + if (min > vbox_minor) return false; + if (rel < vbox_release) return true; + } + return false; +} + +bool VBOX_VM::is_virtualbox_error_recoverable(int retval) { + // See comments for VBOX_VM::vbm_popen about session lock issues. + if (VBOX_E_INVALID_OBJECT_STATE == retval) return true; + return false; +} + int VBOX_VM::get_install_directory(string& install_directory ) { #ifdef _WIN32 LONG lReturnValue; @@ -1650,17 +1687,6 @@ int VBOX_VM::get_system_log(string& log) { return retval; } -int VBOX_VM::get_vm_exit_code(unsigned long& exit_code) { -#ifndef _WIN32 - int ec = 0; - waitpid(vm_pid, &ec, WNOHANG); - exit_code = ec; -#else - GetExitCodeProcess(vm_pid_handle, &exit_code); -#endif - return 0; -} - int VBOX_VM::get_vm_process_id(int& process_id) { string command; string output; @@ -1713,6 +1739,17 @@ int VBOX_VM::get_vm_process_id(int& process_id) { return 0; } +int VBOX_VM::get_vm_exit_code(unsigned long& exit_code) { +#ifndef _WIN32 + int ec = 0; + waitpid(vm_pid, &ec, WNOHANG); + exit_code = ec; +#else + GetExitCodeProcess(vm_pid_handle, &exit_code); +#endif + return 0; +} + int VBOX_VM::get_port_forwarding_port() { sockaddr_in addr; BOINC_SOCKLEN_T addrsize; @@ -1963,6 +2000,7 @@ void VBOX_VM::reset_vm_process_priority() { // to VBOX_USER_HOME which is required for running in the BOINC account-based // sandbox on Windows. int VBOX_VM::launch_vboxsvc() { + int retval = BOINC_SUCCESS; #ifdef _WIN32 STARTUPINFO si; @@ -1975,9 +2013,9 @@ int VBOX_VM::launch_vboxsvc() { if (aid.using_sandbox) { - if ((vboxsvc_handle == NULL) || !process_exists(vboxsvc_handle)) { + if (!vboxsvc_pid_handle || !process_exists(vboxsvc_pid_handle)) { - if (vboxsvc_handle) CloseHandle(vboxsvc_handle); + if (vboxsvc_pid_handle) CloseHandle(vboxsvc_pid_handle); memset(&si, 0, sizeof(si)); memset(&pi, 0, sizeof(pi)); @@ -1988,23 +2026,32 @@ int VBOX_VM::launch_vboxsvc() { command = "\"" + virtualbox_install_directory + "\\VBoxSVC.exe\" --logrotate 1 --logsize 1024000"; - if (!CreateProcess(NULL, (LPTSTR)command.c_str(), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { - fprintf( - stderr, - "%s Creating VBoxSVC.exe failed! (%d).\n", - vboxwrapper_msg_prefix(buf, sizeof(buf)), - GetLastError() - ); - } - - vboxsvc_handle = pi.hProcess; + CreateProcess(NULL, (LPTSTR)command.c_str(), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi); if (pi.hThread) CloseHandle(pi.hThread); + if (pi.hProcess) { + fprintf( + stderr, + "%s Status Report: Launching vboxsvc.exe.\n", + vboxwrapper_msg_prefix(buf, sizeof(buf)) + ); + vboxsvc_pid_handle = pi.hProcess; + } else { + fprintf( + stderr, + "%s Status Report: Launching vBoxsvc.exe failed! (%d).\n" + " Error: %s", + vboxwrapper_msg_prefix(buf, sizeof(buf)), + GetLastError(), + windows_format_error_string(GetLastError(), buf, sizeof(buf)) + ); + retval = ERR_EXEC; + } } } #endif - return 0; + return retval; } // If there are errors we can recover from, process them here. diff --git a/samples/vboxwrapper/vbox.h b/samples/vboxwrapper/vbox.h index 95c9475035..d77105eb9e 100644 --- a/samples/vboxwrapper/vbox.h +++ b/samples/vboxwrapper/vbox.h @@ -96,6 +96,7 @@ struct VBOX_VM { int rd_host_port; // dynamically assigned bool headless; + #ifdef _WIN32 // the handle to the process for the VM // NOTE: we get a handle to the pid right after we parse it from the @@ -106,14 +107,22 @@ struct VBOX_VM { HANDLE vm_pid_handle; // the handle to the vboxsvc process created by us in the sandbox'ed environment - HANDLE vboxsvc_handle; + HANDLE vboxsvc_pid_handle; #else // the pid to the VM process int vm_pid; #endif int initialize(); + void poll(bool log_state = true); + + int register_vm(); + int deregister_vm(bool delete_media); + int deregister_stale_vm(); + int run(double elapsed_time); + void cleanup(); + int start(); int stop(); int poweroff(); @@ -122,8 +131,6 @@ struct VBOX_VM { int createsnapshot(double elapsed_time); int cleanupsnapshots(bool delete_active); int restoresnapshot(); - void cleanup(); - void poll(bool log_state = true); void dumphypervisorlogs(); bool is_system_ready(std::string& message); @@ -138,17 +145,13 @@ struct VBOX_VM { bool is_virtualbox_version_newer(int maj, int min, int rel); bool is_virtualbox_error_recoverable(int retval); - int register_vm(); - int deregister_vm(bool delete_media); - int deregister_stale_vm(); - int get_install_directory(std::string& dir); int get_slot_directory(std::string& dir); int get_network_bytes_sent(double& sent); int get_network_bytes_received(double& received); int get_system_log(std::string& log); - int get_vm_exit_code(unsigned long& exit_code); int get_vm_process_id(int& process_id); + int get_vm_exit_code(unsigned long& exit_code); int get_port_forwarding_port(); int get_remote_desktop_port(); diff --git a/samples/vboxwrapper/vboxwrapper.cpp b/samples/vboxwrapper/vboxwrapper.cpp index 9c499cda7c..f00b9f82da 100644 --- a/samples/vboxwrapper/vboxwrapper.cpp +++ b/samples/vboxwrapper/vboxwrapper.cpp @@ -656,10 +656,6 @@ int main(int argc, char** argv) { char* temp_reason = (char*)""; int temp_delay = 300; - // Attempt to cleanup the VM - vm.cleanup(); - write_checkpoint(elapsed_time, vm); - fprintf( stderr, "%s VM failed to start.\n", @@ -704,13 +700,55 @@ int main(int argc, char** argv) { ); unrecoverable_error = false; temp_reason = (char*)"VM Hypervisor was unable to allocate enough memory to start VM."; + } else if (ERR_NOT_EXITED == retval) { + fprintf( + stderr, + "%s NOTE: VM was already running.\n" + " BOINC will be notified that it needs to clean up the environment.\n" + " This might be a temporary problem and so this job will be rescheduled for another time.\n", + vboxwrapper_msg_prefix(buf, sizeof(buf)) + ); + unrecoverable_error = false; + temp_reason = (char*)"VM environment needed to be cleaned up."; + } else if (vm.is_virtualbox_error_recoverable(retval)) { + fprintf( + stderr, + "%s NOTE: VM session lock error encountered.\n" + " BOINC will be notified that it needs to clean up the environment.\n" + " This might be a temporary problem and so this job will be rescheduled for another time.\n", + vboxwrapper_msg_prefix(buf, sizeof(buf)) + ); + unrecoverable_error = false; + temp_reason = (char*)"VM environment needed to be cleaned up."; } else { vm.dumphypervisorlogs(); } if (unrecoverable_error) { + // Attempt to cleanup the VM and exit. + vm.cleanup(); + write_checkpoint(elapsed_time, vm); boinc_finish(retval); } else { + // if the VM is already running notify BOINC about the process ID so it can + // clean up the environment. We should be safe to run after that. + // + vm.get_vm_process_id(vm_pid); + if (vm_pid) { + retval = boinc_report_app_status_aux( + elapsed_time, + checkpoint_cpu_time, + fraction_done, + vm_pid, + bytes_sent, + bytes_received + ); + } + + // Give the BOINC API time to report the pid to BOINC. + boinc_sleep(5.0); + + // Exit and let BOINC clean up the rest. boinc_temporary_exit(temp_delay, temp_reason); } } diff --git a/win_build/vboxwrapper.vcxproj b/win_build/vboxwrapper.vcxproj index e3e618910c..7ccbff0eba 100644 --- a/win_build/vboxwrapper.vcxproj +++ b/win_build/vboxwrapper.vcxproj @@ -128,12 +128,12 @@ libcmt.lib;libcpmt.lib;kernel32.lib;user32.lib;gdi32.lib;ole32.lib;wsock32.lib;psapi.lib;%(AdditionalDependencies) - .\Build\$(Platform)\$(Configuration)\vboxwrapper_26045_windows_intelx86.exe + .\Build\$(Platform)\$(Configuration)\vboxwrapper_26050_windows_intelx86.exe true true %(DelayLoadDLLs) true - .\Build\$(Platform)\$(Configuration)\vboxwrapper_26045_windows_intelx86.pdb + .\Build\$(Platform)\$(Configuration)\vboxwrapper_26050_windows_intelx86.pdb Windows MachineX86 @@ -177,12 +177,12 @@ libcmt.lib;libcpmt.lib;kernel32.lib;user32.lib;gdi32.lib;ole32.lib;wsock32.lib;psapi.lib;%(AdditionalDependencies) - .\Build\$(Platform)\$(Configuration)\vboxwrapper_26045_windows_x86_64.exe + .\Build\$(Platform)\$(Configuration)\vboxwrapper_26050_windows_x86_64.exe true true %(DelayLoadDLLs) true - .\Build\$(Platform)\$(Configuration)\vboxwrapper_26045_windows_x86_64.pdb + .\Build\$(Platform)\$(Configuration)\vboxwrapper_26050_windows_x86_64.pdb Windows MachineX64