summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main/src-server/MachineImplMoveVM.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Main/src-server/MachineImplMoveVM.cpp')
-rw-r--r--src/VBox/Main/src-server/MachineImplMoveVM.cpp1620
1 files changed, 1620 insertions, 0 deletions
diff --git a/src/VBox/Main/src-server/MachineImplMoveVM.cpp b/src/VBox/Main/src-server/MachineImplMoveVM.cpp
new file mode 100644
index 00000000..30ae729b
--- /dev/null
+++ b/src/VBox/Main/src-server/MachineImplMoveVM.cpp
@@ -0,0 +1,1620 @@
+/* $Id: MachineImplMoveVM.cpp $ */
+/** @file
+ * Implementation of MachineMoveVM
+ */
+
+/*
+ * Copyright (C) 2011-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_MACHINE
+#include <iprt/fs.h>
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/path.h>
+#include <iprt/cpp/utils.h>
+#include <iprt/stream.h>
+#include <VBox/com/ErrorInfo.h>
+
+#include "MachineImplMoveVM.h"
+#include "MediumFormatImpl.h"
+#include "VirtualBoxImpl.h"
+#include "LoggingNew.h"
+
+/** @todo r=klaus this file is abusing the release log by putting pretty much
+ * everything there. Should be adjusted to use debug logging where appropriate
+ * and if really necessary some LogRel2 and the like. */
+
+
+/* This variable is global and used in the different places so it must be cleared each time before usage to avoid failure */
+std::vector< ComObjPtr<Machine> > machineList;
+
+typedef std::multimap<Utf8Str, Utf8Str> list_t;
+typedef std::multimap<Utf8Str, Utf8Str>::const_iterator cit_t;
+typedef std::multimap<Utf8Str, Utf8Str>::iterator it_t;
+typedef std::pair <std::multimap<Utf8Str, Utf8Str>::iterator, std::multimap<Utf8Str, Utf8Str>::iterator> rangeRes_t;
+
+struct fileList_t
+{
+ HRESULT add(const Utf8Str& folder, const Utf8Str& file)
+ {
+ HRESULT rc = S_OK;
+ m_list.insert(std::make_pair(folder, file));
+ return rc;
+ }
+
+ HRESULT add(const Utf8Str& fullPath)
+ {
+ HRESULT rc = S_OK;
+ Utf8Str folder = fullPath;
+ folder.stripFilename();
+ Utf8Str filename = fullPath;
+ filename.stripPath();
+ m_list.insert(std::make_pair(folder, filename));
+ return rc;
+ }
+
+ HRESULT removeFileFromList(const Utf8Str& fullPath)
+ {
+ HRESULT rc = S_OK;
+ Utf8Str folder = fullPath;
+ folder.stripFilename();
+ Utf8Str filename = fullPath;
+ filename.stripPath();
+ rangeRes_t res = m_list.equal_range(folder);
+ for (it_t it=res.first; it!=res.second;)
+ {
+ if (it->second.equals(filename))
+ {
+ it_t it2 = it;
+ ++it;
+ m_list.erase(it2);
+ }
+ else
+ ++it;
+ }
+
+ return rc;
+ }
+
+ HRESULT removeFileFromList(const Utf8Str& path, const Utf8Str& fileName)
+ {
+ HRESULT rc = S_OK;
+ rangeRes_t res = m_list.equal_range(path);
+ for (it_t it=res.first; it!=res.second;)
+ {
+ if (it->second.equals(fileName))
+ {
+ it_t it2 = it;
+ ++it;
+ m_list.erase(it2);
+ }
+ else
+ ++it;
+ }
+ return rc;
+ }
+
+ HRESULT removeFolderFromList(const Utf8Str& path)
+ {
+ HRESULT rc = S_OK;
+ m_list.erase(path);
+ return rc;
+ }
+
+ rangeRes_t getFilesInRange(const Utf8Str& path)
+ {
+ rangeRes_t res;
+ res = m_list.equal_range(path);
+ return res;
+ }
+
+ std::list<Utf8Str> getFilesInList(const Utf8Str& path)
+ {
+ std::list<Utf8Str> list_;
+ rangeRes_t res = m_list.equal_range(path);
+ for (it_t it=res.first; it!=res.second; ++it)
+ list_.push_back(it->second);
+ return list_;
+ }
+
+
+ list_t m_list;
+
+};
+
+
+void MachineMoveVM::ErrorInfoItem::printItem(bool fLog) const
+{
+ if (fLog)
+ {
+ LogRelFunc(("(The error code is %Rrc): %s\n",m_code, m_description.c_str()));
+ }
+ else
+ RTPrintf("(The error code is %Rrc): %s\n",m_code, m_description.c_str());
+}
+
+HRESULT MachineMoveVM::init()
+{
+ HRESULT rc = S_OK;
+
+ errorsList.clear();
+
+ Utf8Str strTargetFolder;
+ /* adding a trailing slash if it's needed */
+ {
+ size_t len = m_targetPath.length() + 2;
+ if (len >=RTPATH_MAX)
+ {
+ Utf8Str errorDesc(" The destination path isn't correct. The length of path exceeded the maximum value.");
+ errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
+ throw m_pMachine->setError(VBOX_E_IPRT_ERROR, m_pMachine->tr(errorDesc.c_str()));
+ }
+
+ char* path = new char [len];
+ RTStrCopy(path, len, m_targetPath.c_str());
+ RTPathEnsureTrailingSeparator(path, len);
+ strTargetFolder = m_targetPath = path;
+ delete[] path;
+ }
+
+ /*
+ * We have a mode which user is able to request
+ * basic mode:
+ * - The images which are solely attached to the VM
+ * and located in the original VM folder will be moved.
+ *
+ * Comment: in the future some other modes can be added.
+ */
+
+ try
+ {
+ Utf8Str info;
+ int vrc = 0;
+
+ RTFOFF cbTotal = 0;
+ RTFOFF cbFree = 0;
+ uint32_t cbBlock = 0;
+ uint32_t cbSector = 0;
+
+ vrc = RTFsQuerySizes(strTargetFolder.c_str(), &cbTotal, &cbFree, &cbBlock, &cbSector);
+ if (FAILED(vrc))
+ {
+ RTPrintf("strTargetFolder is %s\n", strTargetFolder.c_str());
+ Utf8StrFmt errorDesc("Unable to move machine. Can't get the destination storage size (%s)",
+ strTargetFolder.c_str());
+ errorsList.push_back(ErrorInfoItem(E_FAIL, errorDesc.c_str()));
+ rc = m_pMachine->setErrorBoth(E_FAIL, vrc, m_pMachine->tr(errorDesc.c_str()));
+ throw rc;
+ }
+
+ RTDIR hDir;
+ Utf8Str strTempFile = strTargetFolder + "test.txt";
+ vrc = RTDirOpen(&hDir, strTargetFolder.c_str());
+ if (FAILED(vrc))
+ throw rc = vrc;
+ else
+ {
+ RTFILE hFile;
+ vrc = RTFileOpen(&hFile, strTempFile.c_str(), RTFILE_O_OPEN_CREATE | RTFILE_O_READWRITE | RTFILE_O_DENY_NONE);
+ if (FAILED(vrc))
+ {
+ LogRelFunc(("Can't create a test file %s (The error is %Rrc)\n", strTempFile.c_str(), vrc));
+ Utf8StrFmt errorDesc("Can't create a test file test.txt in the %s. Check the access rights of "
+ "the destination folder.", strTargetFolder.c_str());
+ errorsList.push_back(ErrorInfoItem(HRESULT(vrc), errorDesc.c_str()));
+ rc = m_pMachine->setErrorBoth(VBOX_E_FILE_ERROR, vrc, m_pMachine->tr(errorDesc.c_str()));
+ }
+
+ RTFileClose(hFile);
+ RTFileDelete(strTempFile.c_str());
+ RTDirClose(hDir);
+ }
+
+ long long totalFreeSpace = cbFree;
+ long long totalSpace = cbTotal;
+ info = Utf8StrFmt("blocks: total %lld, free %u ", cbTotal, cbFree);
+ LogRelFunc(("%s \n", info.c_str()));
+ LogRelFunc(("total space (Kb) %lld (Mb) %lld (Gb) %lld\n",
+ totalSpace/1024, totalSpace/(1024*1024), totalSpace/(1024*1024*1024)));
+ LogRelFunc(("total free space (Kb) %lld (Mb) %lld (Gb) %lld\n",
+ totalFreeSpace/1024, totalFreeSpace/(1024*1024), totalFreeSpace/(1024*1024*1024)));
+
+ RTFSPROPERTIES properties;
+ vrc = RTFsQueryProperties(strTargetFolder.c_str(), &properties);
+ if (FAILED(vrc)) throw vrc;
+ info = Utf8StrFmt("disk properties:\n"
+ "remote: %s \n"
+ "read only: %s \n"
+ "compressed: %s \n",
+ properties.fRemote == true ? "true":"false",
+ properties.fReadOnly == true ? "true":"false",
+ properties.fCompressed == true ? "true":"false");
+
+ LogRelFunc(("%s \n", info.c_str()));
+
+ /* Get the original VM path */
+ Utf8Str strSettingsFilePath;
+ Bstr bstr_settingsFilePath;
+ m_pMachine->COMGETTER(SettingsFilePath)(bstr_settingsFilePath.asOutParam());
+ strSettingsFilePath = bstr_settingsFilePath;
+ strSettingsFilePath.stripFilename();
+
+ vmFolders.insert(std::make_pair(VBox_SettingFolder, strSettingsFilePath));
+
+ /* Collect all files from the VM's folder */
+ fileList_t fullFileList;
+ rc = getFilesList(strSettingsFilePath, fullFileList);
+ if (FAILED(rc)) throw rc;
+
+ /*
+ * Collect all known folders used by the VM:
+ * - log folder;
+ * - state folder;
+ * - snapshot folder.
+ */
+ Utf8Str strLogFolder;
+ Bstr bstr_logFolder;
+ m_pMachine->COMGETTER(LogFolder)(bstr_logFolder.asOutParam());
+ strLogFolder = bstr_logFolder;
+ if ( m_type.equals("basic")
+ && strLogFolder.contains(strSettingsFilePath))
+ {
+ vmFolders.insert(std::make_pair(VBox_LogFolder, strLogFolder));
+ }
+
+ Utf8Str strStateFilePath;
+ Bstr bstr_stateFilePath;
+ MachineState_T machineState;
+ rc = m_pMachine->COMGETTER(State)(&machineState);
+ if (FAILED(rc)) throw rc;
+ else if (machineState == MachineState_Saved)
+ {
+ m_pMachine->COMGETTER(StateFilePath)(bstr_stateFilePath.asOutParam());
+ strStateFilePath = bstr_stateFilePath;
+ strStateFilePath.stripFilename();
+ if ( m_type.equals("basic")
+ && strStateFilePath.contains(strSettingsFilePath))
+ vmFolders.insert(std::make_pair(VBox_StateFolder, strStateFilePath));//Utf8Str(bstr_stateFilePath)));
+ }
+
+ Utf8Str strSnapshotFolder;
+ Bstr bstr_snapshotFolder;
+ m_pMachine->COMGETTER(SnapshotFolder)(bstr_snapshotFolder.asOutParam());
+ strSnapshotFolder = bstr_snapshotFolder;
+ if ( m_type.equals("basic")
+ && strSnapshotFolder.contains(strSettingsFilePath))
+ vmFolders.insert(std::make_pair(VBox_SnapshotFolder, strSnapshotFolder));
+
+ if (m_pMachine->i_isSnapshotMachine())
+ {
+ Bstr bstrSrcMachineId;
+ rc = m_pMachine->COMGETTER(Id)(bstrSrcMachineId.asOutParam());
+ if (FAILED(rc)) throw rc;
+ ComPtr<IMachine> newSrcMachine;
+ rc = m_pMachine->i_getVirtualBox()->FindMachine(bstrSrcMachineId.raw(), newSrcMachine.asOutParam());
+ if (FAILED(rc)) throw rc;
+ }
+
+ /* Add the current machine and all snapshot machines below this machine
+ * in a list for further processing.
+ */
+
+ long long neededFreeSpace = 0;
+
+ /* Actual file list */
+ fileList_t actualFileList;
+ Utf8Str strTargetImageName;
+
+ /* Global variable (defined at the beginning of file), so clear it before usage */
+ machineList.clear();
+ machineList.push_back(m_pMachine);
+
+ {
+ ULONG cSnapshots = 0;
+ rc = m_pMachine->COMGETTER(SnapshotCount)(&cSnapshots);
+ if (FAILED(rc)) throw rc;
+ if (cSnapshots > 0)
+ {
+ Utf8Str id;
+ if (m_pMachine->i_isSnapshotMachine())
+ id = m_pMachine->i_getSnapshotId().toString();
+ ComPtr<ISnapshot> pSnapshot;
+ rc = m_pMachine->FindSnapshot(Bstr(id).raw(), pSnapshot.asOutParam());
+ if (FAILED(rc)) throw rc;
+ rc = createMachineList(pSnapshot, machineList);
+ if (FAILED(rc)) throw rc;
+ }
+ }
+
+ ULONG uCount = 1;//looks like it should be initialized by 1. See assertion in the Progress::setNextOperation()
+ ULONG uTotalWeight = 1;
+
+ /* The lists llMedias and llSaveStateFiles are filled in the queryMediasForAllStates() */
+ queryMediasForAllStates(machineList);
+
+ {
+ uint64_t totalMediumsSize = 0;
+
+ for (size_t i = 0; i < llMedias.size(); ++i)
+ {
+ LONG64 cbSize = 0;
+ MEDIUMTASKCHAINMOVE &mtc = llMedias.at(i);
+ for (size_t a = mtc.chain.size(); a > 0; --a)
+ {
+ Bstr bstrLocation;
+ Utf8Str strLocation;
+ Utf8Str name = mtc.chain[a - 1].strBaseName;
+ ComPtr<IMedium> plMedium = mtc.chain[a - 1].pMedium;
+ rc = plMedium->COMGETTER(Location)(bstrLocation.asOutParam());
+ if (FAILED(rc)) throw rc;
+ strLocation = bstrLocation;
+
+ /*if an image is located in the actual VM folder it will be added to the actual list */
+ if (strLocation.contains(strSettingsFilePath))
+ {
+ rc = plMedium->COMGETTER(Size)(&cbSize);
+ if (FAILED(rc)) throw rc;
+
+ std::pair<std::map<Utf8Str, MEDIUMTASKMOVE>::iterator,bool> ret;
+ ret = finalMediumsMap.insert(std::make_pair(name, mtc.chain[a - 1]));
+ if (ret.second == true)
+ {
+ /* Calculate progress data */
+ ++uCount;
+ uTotalWeight += mtc.chain[a - 1].uWeight;
+ totalMediumsSize += cbSize;
+ LogRelFunc(("Image %s was added into the moved list\n", name.c_str()));
+ }
+ }
+ }
+ }
+
+ LogRelFunc(("Total Size of images is %lld bytes\n", totalMediumsSize));
+ neededFreeSpace += totalMediumsSize;
+ }
+
+ /* Prepare data for moving ".sav" files */
+ {
+ uint64_t totalStateSize = 0;
+
+ for (size_t i = 0; i < llSaveStateFiles.size(); ++i)
+ {
+ uint64_t cbFile = 0;
+ SAVESTATETASKMOVE &sst = llSaveStateFiles.at(i);
+
+ Utf8Str name = sst.strSaveStateFile;
+ /*if a state file is located in the actual VM folder it will be added to the actual list */
+ if (name.contains(strSettingsFilePath))
+ {
+ vrc = RTFileQuerySize(name.c_str(), &cbFile);
+ if (RT_SUCCESS(vrc))
+ {
+ std::pair<std::map<Utf8Str, SAVESTATETASKMOVE>::iterator,bool> ret;
+ ret = finalSaveStateFilesMap.insert(std::make_pair(name, sst));
+ if (ret.second == true)
+ {
+ totalStateSize += cbFile;
+ ++uCount;
+ uTotalWeight += sst.uWeight;
+ LogRelFunc(("The state file %s was added into the moved list\n", name.c_str()));
+ }
+ }
+ else
+ LogRelFunc(("The state file %s wasn't added into the moved list. Couldn't get the file size.\n",
+ name.c_str()));
+ }
+ }
+
+ neededFreeSpace += totalStateSize;
+ }
+
+ /* Prepare data for moving the log files */
+ {
+ Utf8Str strFolder = vmFolders[VBox_LogFolder];
+
+ if (RTPathExists(strFolder.c_str()))
+ {
+ uint64_t totalLogSize = 0;
+ rc = getFolderSize(strFolder, totalLogSize);
+ if (SUCCEEDED(rc))
+ {
+ neededFreeSpace += totalLogSize;
+ if (totalFreeSpace - neededFreeSpace <= 1024*1024)
+ {
+ throw VERR_OUT_OF_RESOURCES;//less than 1Mb free space on the target location
+ }
+
+ fileList_t filesList;
+ getFilesList(strFolder, filesList);
+ cit_t it = filesList.m_list.begin();
+ while(it != filesList.m_list.end())
+ {
+ Utf8Str strFile = it->first.c_str();
+ strFile.append(RTPATH_DELIMITER).append(it->second.c_str());
+
+ uint64_t cbFile = 0;
+ vrc = RTFileQuerySize(strFile.c_str(), &cbFile);
+ if (RT_SUCCESS(vrc))
+ {
+ uCount += 1;
+ uTotalWeight += (ULONG)((cbFile + _1M - 1) / _1M);
+ actualFileList.add(strFile);
+ LogRelFunc(("The log file %s added into the moved list\n", strFile.c_str()));
+ }
+ else
+ LogRelFunc(("The log file %s wasn't added into the moved list. Couldn't get the file size."
+ "\n", strFile.c_str()));
+ ++it;
+ }
+ }
+ }
+ else
+ {
+ LogRelFunc(("Information: The original log folder %s doesn't exist\n", strFolder.c_str()));
+ rc = S_OK;//it's not error in this case if there isn't an original log folder
+ }
+ }
+
+ LogRelFunc(("Total space needed is %lld bytes\n", neededFreeSpace));
+ /* Check a target location on enough room */
+ if (totalFreeSpace - neededFreeSpace <= 1024*1024)
+ {
+ LogRelFunc(("but free space on destination is %lld\n", totalFreeSpace));
+ throw VERR_OUT_OF_RESOURCES;//less than 1Mb free space on the target location
+ }
+
+ /* Add step for .vbox machine setting file */
+ {
+ ++uCount;
+ uTotalWeight += 1;
+ }
+
+ /* Reserve additional steps in case of failure and rollback all changes */
+ {
+ uTotalWeight += uCount;//just add 1 for each possible rollback operation
+ uCount += uCount;//and increase the steps twice
+ }
+
+ /* Init Progress instance */
+ {
+ rc = m_pProgress->init(m_pMachine->i_getVirtualBox(),
+ static_cast<IMachine*>(m_pMachine) /* aInitiator */,
+ Bstr(m_pMachine->tr("Moving Machine")).raw(),
+ true /* fCancellable */,
+ uCount,
+ uTotalWeight,
+ Bstr(m_pMachine->tr("Initialize Moving")).raw(),
+ 1);
+ if (FAILED(rc))
+ {
+ Utf8StrFmt errorDesc("Couldn't correctly setup the progress object for moving VM operation (%Rrc)", rc);
+ errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
+
+ throw m_pMachine->setError(VBOX_E_IPRT_ERROR, m_pMachine->tr(errorDesc.c_str()));
+ }
+ }
+
+ /* save all VM data */
+ m_pMachine->i_setModified(Machine::IsModified_MachineData);
+ rc = m_pMachine->SaveSettings();
+ }
+ catch(HRESULT hrc)
+ {
+ rc = hrc;
+ }
+
+ LogFlowFuncLeave();
+
+ return rc;
+}
+
+void MachineMoveVM::printStateFile(settings::SnapshotsList &snl)
+{
+ settings::SnapshotsList::iterator it;
+ for (it = snl.begin(); it != snl.end(); ++it)
+ {
+ if (!it->strStateFile.isEmpty())
+ {
+ settings::Snapshot snap = (settings::Snapshot)(*it);
+ LogRelFunc(("snap.uuid = %s\n", snap.uuid.toStringCurly().c_str()));
+ LogRelFunc(("snap.strStateFile = %s\n", snap.strStateFile.c_str()));
+ }
+
+ if (!it->llChildSnapshots.empty())
+ printStateFile(it->llChildSnapshots);
+ }
+}
+
+/* static */
+DECLCALLBACK(int) MachineMoveVM::updateProgress(unsigned uPercent, void *pvUser)
+{
+ MachineMoveVM* pTask = *(MachineMoveVM**)pvUser;
+
+ if ( pTask
+ && !pTask->m_pProgress.isNull())
+ {
+ BOOL fCanceled;
+ pTask->m_pProgress->COMGETTER(Canceled)(&fCanceled);
+ if (fCanceled)
+ return -1;
+ pTask->m_pProgress->SetCurrentOperationProgress(uPercent);
+ }
+ return VINF_SUCCESS;
+}
+
+/* static */
+DECLCALLBACK(int) MachineMoveVM::copyFileProgress(unsigned uPercentage, void *pvUser)
+{
+ ComObjPtr<Progress> pProgress = *static_cast< ComObjPtr<Progress>* >(pvUser);
+
+ BOOL fCanceled = false;
+ HRESULT rc = pProgress->COMGETTER(Canceled)(&fCanceled);
+ if (FAILED(rc)) return VERR_GENERAL_FAILURE;
+ /* If canceled by the user tell it to the copy operation. */
+ if (fCanceled) return VERR_CANCELLED;
+ /* Set the new process. */
+ rc = pProgress->SetCurrentOperationProgress(uPercentage);
+ if (FAILED(rc)) return VERR_GENERAL_FAILURE;
+
+ return VINF_SUCCESS;
+}
+
+
+/* static */
+void MachineMoveVM::i_MoveVMThreadTask(MachineMoveVM* task)
+{
+ LogFlowFuncEnter();
+ HRESULT rc = S_OK;
+
+ MachineMoveVM* taskMoveVM = task;
+ ComObjPtr<Machine> &machine = taskMoveVM->m_pMachine;
+
+ AutoCaller autoCaller(machine);
+// if (FAILED(autoCaller.rc())) return;//Should we return something here?
+
+ Utf8Str strTargetFolder = taskMoveVM->m_targetPath;
+ {
+ Bstr bstrMachineName;
+ machine->COMGETTER(Name)(bstrMachineName.asOutParam());
+ strTargetFolder.append(Utf8Str(bstrMachineName));
+ }
+
+ RTCList<Utf8Str> newFiles; /* All extra created files (save states, ...) */
+ RTCList<Utf8Str> originalFiles; /* All original files except images */
+ typedef std::map<Utf8Str, ComObjPtr<Medium> > MediumMap;
+ MediumMap mapOriginalMedium;
+
+ /*
+ * We have the couple modes which user is able to request
+ * basic mode:
+ * - The images which are solely attached to the VM
+ * and located in the original VM folder will be moved.
+ * All subfolders related to the original VM are also moved from the original location
+ * (Standard - snapshots and logs folders).
+ *
+ * canonical mode:
+ * - All disks tied with the VM will be moved into a new location if it's possible.
+ * All folders related to the original VM are also moved.
+ * This mode is intended to collect all files/images/snapshots related to the VM in the one place.
+ *
+ */
+
+ /*
+ * A way to handle shareable disk:
+ * Collect the shareable disks attched to the VM.
+ * Get the machines whom the shareable disks attach to.
+ * Return an error if the state of any VM doesn't allow to move a shareable disk and
+ * this disk is located in the VM's folder (it means the disk is intended for "moving").
+ */
+
+
+ /*
+ * Check new destination whether enough room for the VM or not. if "not" return an error.
+ * Make a copy of VM settings and a list with all files which are moved. Save the list on the disk.
+ * Start "move" operation.
+ * Check the result of operation.
+ * if the operation was successful:
+ * - delete all files in the original VM folder;
+ * - update VM disks info with new location;
+ * - update all other VM if it's needed;
+ * - update global settings
+ */
+
+ try
+ {
+ /* Move all disks */
+ rc = taskMoveVM->moveAllDisks(taskMoveVM->finalMediumsMap, &strTargetFolder);
+ if (FAILED(rc))
+ throw rc;
+
+ /* Get Machine::Data here because moveAllDisks() change it */
+ Machine::Data *machineData = machine->mData.data();
+ settings::MachineConfigFile *machineConfFile = machineData->pMachineConfigFile;
+
+ /* Copy all save state files. */
+ Utf8Str strTrgSnapshotFolder;
+ {
+ /* When the current snapshot folder is absolute we reset it to the
+ * default relative folder. */
+ if (RTPathStartsWithRoot((*machineConfFile).machineUserData.strSnapshotFolder.c_str()))
+ (*machineConfFile).machineUserData.strSnapshotFolder = "Snapshots";
+ (*machineConfFile).strStateFile = "";
+
+ /* The absolute name of the snapshot folder. */
+ strTrgSnapshotFolder = Utf8StrFmt("%s%c%s", strTargetFolder.c_str(), RTPATH_DELIMITER,
+ (*machineConfFile).machineUserData.strSnapshotFolder.c_str());
+
+ /* Check if a snapshot folder is necessary and if so doesn't already
+ * exists. */
+ if ( taskMoveVM->finalSaveStateFilesMap.size() != 0
+ && !RTDirExists(strTrgSnapshotFolder.c_str()))
+ {
+ int vrc = RTDirCreateFullPath(strTrgSnapshotFolder.c_str(), 0700);
+ if (RT_FAILURE(vrc))
+ {
+ Utf8StrFmt errorDesc("Could not create snapshots folder '%s' (%Rrc)", strTrgSnapshotFolder.c_str(), vrc);
+ taskMoveVM->errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
+
+ throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, machine->tr(errorDesc.c_str()));
+ }
+ }
+
+ std::map<Utf8Str, SAVESTATETASKMOVE>::iterator itState = taskMoveVM->finalSaveStateFilesMap.begin();
+ while (itState != taskMoveVM->finalSaveStateFilesMap.end())
+ {
+ const SAVESTATETASKMOVE &sst = itState->second;
+ const Utf8Str &strTrgSaveState = Utf8StrFmt("%s%c%s", strTrgSnapshotFolder.c_str(), RTPATH_DELIMITER,
+ RTPathFilename(sst.strSaveStateFile.c_str()));
+
+ /* Move to next sub-operation. */
+ rc = taskMoveVM->m_pProgress->SetNextOperation(BstrFmt(machine->tr("Copy the save state file '%s' ..."),
+ RTPathFilename(sst.strSaveStateFile.c_str())).raw(), sst.uWeight);
+ if (FAILED(rc)) throw rc;
+
+ int vrc = RTFileCopyEx(sst.strSaveStateFile.c_str(), strTrgSaveState.c_str(), 0,
+ MachineMoveVM::copyFileProgress, &taskMoveVM->m_pProgress);
+ if (RT_FAILURE(vrc))
+ {
+ Utf8StrFmt errorDesc("Could not copy state file '%s' to '%s' (%Rrc)",
+ sst.strSaveStateFile.c_str(), strTrgSaveState.c_str(), vrc);
+ taskMoveVM->errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
+
+ throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, machine->tr(errorDesc.c_str()));
+ }
+
+ /* save new file in case of restoring */
+ newFiles.append(strTrgSaveState);
+ /* save original file for deletion in the end */
+ originalFiles.append(sst.strSaveStateFile);
+ ++itState;
+ }
+ }
+
+ /*
+ * Update state file path
+ * very important step!
+ * Not obvious how to do it correctly.
+ */
+ {
+ LogRelFunc(("Update state file path\n"));
+ rc = taskMoveVM->updatePathsToStateFiles(taskMoveVM->finalSaveStateFilesMap,
+ taskMoveVM->vmFolders[VBox_SettingFolder],
+ strTargetFolder);
+ if (FAILED(rc))
+ throw rc;
+ }
+
+ /*
+ * Moving Machine settings file
+ * The settings file are moved after all disks and snapshots because this file should be updated
+ * with actual information and only then should be moved.
+ */
+ {
+ LogRelFunc(("Copy Machine settings file \n"));
+
+ rc = taskMoveVM->m_pProgress->SetNextOperation(BstrFmt(machine->tr("Copy Machine settings file '%s' ..."),
+ (*machineConfFile).machineUserData.strName.c_str()).raw(), 1);
+ if (FAILED(rc)) throw rc;
+
+ Utf8Str strTargetSettingsFilePath = strTargetFolder;
+
+ /* Check a folder existing and create one if it's not */
+ if (!RTDirExists(strTargetSettingsFilePath.c_str()))
+ {
+ int vrc = RTDirCreateFullPath(strTargetSettingsFilePath.c_str(), 0700);
+ if (RT_FAILURE(vrc))
+ {
+ Utf8StrFmt errorDesc("Could not create a home machine folder '%s' (%Rrc)",
+ strTargetSettingsFilePath.c_str(), vrc);
+ taskMoveVM->errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
+
+ throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, machine->tr(errorDesc.c_str()));
+ }
+ LogRelFunc(("Created a home machine folder %s\n", strTargetSettingsFilePath.c_str()));
+ }
+
+ /* Create a full path */
+ Bstr bstrMachineName;
+ machine->COMGETTER(Name)(bstrMachineName.asOutParam());
+ strTargetSettingsFilePath.append(RTPATH_DELIMITER).append(Utf8Str(bstrMachineName));
+ strTargetSettingsFilePath.append(".vbox");
+
+ Utf8Str strSettingsFilePath;
+ Bstr bstr_settingsFilePath;
+ machine->COMGETTER(SettingsFilePath)(bstr_settingsFilePath.asOutParam());
+ strSettingsFilePath = bstr_settingsFilePath;
+
+ int vrc = RTFileCopyEx(strSettingsFilePath.c_str(), strTargetSettingsFilePath.c_str(), 0,
+ MachineMoveVM::copyFileProgress, &taskMoveVM->m_pProgress);
+ if (RT_FAILURE(vrc))
+ {
+ Utf8StrFmt errorDesc("Could not copy the setting file '%s' to '%s' (%Rrc)",
+ strSettingsFilePath.c_str(), strTargetSettingsFilePath.stripFilename().c_str(), vrc);
+ taskMoveVM->errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
+
+ throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, machine->tr(errorDesc.c_str()));
+ }
+
+ LogRelFunc(("The setting file %s has been copied into the folder %s\n", strSettingsFilePath.c_str(),
+ strTargetSettingsFilePath.stripFilename().c_str()));
+
+ /* save new file in case of restoring */
+ newFiles.append(strTargetSettingsFilePath);
+ /* save original file for deletion in the end */
+ originalFiles.append(strSettingsFilePath);
+ }
+
+ /* Moving Machine log files */
+ {
+ LogRelFunc(("Copy machine log files \n"));
+
+ if (taskMoveVM->vmFolders[VBox_LogFolder].isNotEmpty())
+ {
+ /* Check an original log folder existence */
+ if (RTDirExists(taskMoveVM->vmFolders[VBox_LogFolder].c_str()))
+ {
+ Utf8Str strTargetLogFolderPath = strTargetFolder;
+ strTargetLogFolderPath.append(RTPATH_DELIMITER).append("Logs");
+
+ /* Check a destination log folder existence and create one if it's not */
+ if (!RTDirExists(strTargetLogFolderPath.c_str()))
+ {
+ int vrc = RTDirCreateFullPath(strTargetLogFolderPath.c_str(), 0700);
+ if (RT_FAILURE(vrc))
+ {
+ Utf8StrFmt errorDesc("Could not create log folder '%s' (%Rrc)",
+ strTargetLogFolderPath.c_str(), vrc);
+ taskMoveVM->errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
+
+ throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, machine->tr(errorDesc.c_str()));
+ }
+ LogRelFunc(("Created a log machine folder %s\n", strTargetLogFolderPath.c_str()));
+ }
+
+ fileList_t filesList;
+ taskMoveVM->getFilesList(taskMoveVM->vmFolders[VBox_LogFolder], filesList);
+ cit_t it = filesList.m_list.begin();
+ while(it != filesList.m_list.end())
+ {
+ Utf8Str strFullSourceFilePath = it->first.c_str();
+ strFullSourceFilePath.append(RTPATH_DELIMITER).append(it->second.c_str());
+
+ Utf8Str strFullTargetFilePath = strTargetLogFolderPath;
+ strFullTargetFilePath.append(RTPATH_DELIMITER).append(it->second.c_str());
+
+ /* Move to next sub-operation. */
+ rc = taskMoveVM->m_pProgress->SetNextOperation(BstrFmt(machine->tr("Copying the log file '%s' ..."),
+ RTPathFilename(strFullSourceFilePath.c_str())).raw(),
+ 1);
+ if (FAILED(rc)) throw rc;
+
+ int vrc = RTFileCopyEx(strFullSourceFilePath.c_str(), strFullTargetFilePath.c_str(), 0,
+ MachineMoveVM::copyFileProgress, &taskMoveVM->m_pProgress);
+ if (RT_FAILURE(vrc))
+ {
+ Utf8StrFmt errorDesc("Could not copy the log file '%s' to '%s' (%Rrc)",
+ strFullSourceFilePath.c_str(),
+ strFullTargetFilePath.stripFilename().c_str(),
+ vrc);
+ taskMoveVM->errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
+
+ throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, machine->tr(errorDesc.c_str()));
+ }
+
+ LogRelFunc(("The log file %s has been copied into the folder %s\n", strFullSourceFilePath.c_str(),
+ strFullTargetFilePath.stripFilename().c_str()));
+
+ /* save new file in case of restoring */
+ newFiles.append(strFullTargetFilePath);
+ /* save original file for deletion in the end */
+ originalFiles.append(strFullSourceFilePath);
+
+ ++it;
+ }
+ }
+ }
+ }
+
+ /* save all VM data */
+ {
+ rc = machine->SaveSettings();
+ }
+
+ {
+ LogRelFunc(("Update path to XML setting file\n"));
+ Utf8Str strTargetSettingsFilePath = strTargetFolder;
+ Bstr bstrMachineName;
+ machine->COMGETTER(Name)(bstrMachineName.asOutParam());
+ strTargetSettingsFilePath.append(RTPATH_DELIMITER).append(Utf8Str(bstrMachineName)).append(".vbox");
+ machineData->m_strConfigFileFull = strTargetSettingsFilePath;
+ machine->mParent->i_copyPathRelativeToConfig(strTargetSettingsFilePath, machineData->m_strConfigFile);
+ }
+
+ /* Save global settings in the VirtualBox.xml */
+ {
+ /* Marks the global registry for uuid as modified */
+ Guid uuid = machine->mData->mUuid;
+ machine->mParent->i_markRegistryModified(uuid);
+
+ /* for saving the global settings we should hold only the VirtualBox lock */
+ AutoWriteLock vboxLock(machine->mParent COMMA_LOCKVAL_SRC_POS);
+
+ rc = machine->mParent->i_saveSettings();
+ }
+ }
+ catch(HRESULT hrc)
+ {
+ LogRelFunc(("Moving machine to a new destination was failed. Check original and destination places.\n"));
+ rc = hrc;
+ taskMoveVM->result = rc;
+ }
+ catch (...)
+ {
+ LogRelFunc(("Moving machine to a new destination was failed. Check original and destination places.\n"));
+ rc = VirtualBoxBase::handleUnexpectedExceptions(machine, RT_SRC_POS);
+ taskMoveVM->result = rc;
+ }
+
+ /* Cleanup on failure */
+ if (FAILED(rc))
+ {
+ Machine::Data *machineData = machine->mData.data();
+
+ /* ! Apparently we should update the Progress object !*/
+ ULONG operationCount = 0;
+ rc = taskMoveVM->m_pProgress->COMGETTER(OperationCount)(&operationCount);
+ ULONG operation = 0;
+ rc = taskMoveVM->m_pProgress->COMGETTER(Operation)(&operation);
+ Bstr bstrOperationDescription;
+ rc = taskMoveVM->m_pProgress->COMGETTER(OperationDescription)(bstrOperationDescription.asOutParam());
+ Utf8Str strOperationDescription = bstrOperationDescription;
+ ULONG operationPercent = 0;
+ rc = taskMoveVM->m_pProgress->COMGETTER(OperationPercent)(&operationPercent);
+
+ Bstr bstrMachineName;
+ machine->COMGETTER(Name)(bstrMachineName.asOutParam());
+ LogRelFunc(("Moving machine %s was failed on operation %s\n",
+ Utf8Str(bstrMachineName.raw()).c_str(), Utf8Str(bstrOperationDescription.raw()).c_str()));
+
+ /* Restoring the original mediums */
+ try
+ {
+ /*
+ * Fix the progress counter
+ * In instance, the whole "move vm" operation is failed on 5th step. But total count is 20.
+ * Where 20 = 2 * 10 operations, where 10 is the real number of operations. And this value was doubled
+ * earlier in the init() exactly for one reason - rollback operation. Because in this case we must do
+ * the same operations but in backward direction.
+ * Thus now we want to correct the progress counter from 5 to 15. Why?
+ * Because we should have evaluated the counter as "20/2 + (20/2 - 5)" = 15 or just "20 - 5" = 15
+ * And because the 5th step failed it shouldn't be counted.
+ * As result, we need to rollback 4 operations.
+ * Thus we start from "operation + 1" and finish when "i < operationCount - operation".
+ */
+ for (ULONG i = operation + 1; i < operationCount - operation; ++i)
+ {
+ rc = taskMoveVM->m_pProgress->SetNextOperation(BstrFmt("Skip the empty operation %d...", i + 1).raw(), 1);
+ if (FAILED(rc)) throw rc;
+ }
+
+ rc = taskMoveVM->moveAllDisks(taskMoveVM->finalMediumsMap);
+ if (FAILED(rc))
+ throw rc;
+ }
+ catch(HRESULT hrc)
+ {
+ LogRelFunc(("Rollback scenario: restoration the original mediums were failed. Machine can be corrupted.\n"));
+ taskMoveVM->result = hrc;
+ }
+ catch (...)
+ {
+ LogRelFunc(("Rollback scenario: restoration the original mediums were failed. Machine can be corrupted.\n"));
+ rc = VirtualBoxBase::handleUnexpectedExceptions(machine, RT_SRC_POS);
+ taskMoveVM->result = rc;
+ }
+
+ /* Revert original paths to the state files */
+ rc = taskMoveVM->updatePathsToStateFiles(taskMoveVM->finalSaveStateFilesMap,
+ strTargetFolder,
+ taskMoveVM->vmFolders[VBox_SettingFolder]);
+ if (FAILED(rc))
+ {
+ LogRelFunc(("Rollback scenario: can't restore the original paths to the state files. "
+ "Machine settings %s can be corrupted.\n", machineData->m_strConfigFileFull.c_str()));
+ }
+
+ /* Delete all created files. Here we update progress object */
+ rc = taskMoveVM->deleteFiles(newFiles);
+ if (FAILED(rc))
+ LogRelFunc(("Rollback scenario: can't delete new created files. Check the destination folder."));
+
+ /* Delete destination folder */
+ RTDirRemove(strTargetFolder.c_str());
+
+ /* save all VM data */
+ {
+ AutoWriteLock srcLock(machine COMMA_LOCKVAL_SRC_POS);
+ srcLock.release();
+ rc = machine->SaveSettings();
+ srcLock.acquire();
+ }
+
+ /* Restore an original path to XML setting file */
+ {
+ LogRelFunc(("Rollback scenario: restoration of the original path to XML setting file\n"));
+ Utf8Str strOriginalSettingsFilePath = taskMoveVM->vmFolders[VBox_SettingFolder];
+ strOriginalSettingsFilePath.append(RTPATH_DELIMITER).append(Utf8Str(bstrMachineName)).append(".vbox");
+ machineData->m_strConfigFileFull = strOriginalSettingsFilePath;
+ machine->mParent->i_copyPathRelativeToConfig(strOriginalSettingsFilePath, machineData->m_strConfigFile);
+ }
+
+ /* Marks the global registry for uuid as modified */
+ {
+ AutoWriteLock srcLock(machine COMMA_LOCKVAL_SRC_POS);
+ srcLock.release();
+ Guid uuid = machine->mData->mUuid;
+ machine->mParent->i_markRegistryModified(uuid);
+ srcLock.acquire();
+ }
+
+ /* save the global settings; for that we should hold only the VirtualBox lock */
+ {
+ AutoWriteLock vboxLock(machine->mParent COMMA_LOCKVAL_SRC_POS);
+ rc = machine->mParent->i_saveSettings();
+ }
+
+ /* In case of failure the progress object on the other side (user side) get notification about operation
+ completion but the operation percentage may not be set to 100% */
+ }
+ else /*Operation was successful and now we can delete the original files like the state files, XML setting, log files */
+ {
+ /*
+ * In case of success it's not urgent to update the progress object because we call i_notifyComplete() with
+ * the success result. As result, the last number of progress operation can be not equal the number of operations
+ * because we doubled the number of operations for rollback case.
+ * But if we want to update the progress object corectly it's needed to add all medium moved by standard
+ * "move medium" logic (for us it's taskMoveVM->finalMediumsMap) to the current number of operation.
+ */
+
+ ULONG operationCount = 0;
+ rc = taskMoveVM->m_pProgress->COMGETTER(OperationCount)(&operationCount);
+ ULONG operation = 0;
+ rc = taskMoveVM->m_pProgress->COMGETTER(Operation)(&operation);
+
+ for (ULONG i = operation; i < operation + taskMoveVM->finalMediumsMap.size() - 1; ++i)
+ {
+ rc = taskMoveVM->m_pProgress->SetNextOperation(BstrFmt("Skip the empty operation %d...", i).raw(), 1);
+ if (FAILED(rc)) throw rc;
+ }
+
+ rc = taskMoveVM->deleteFiles(originalFiles);
+ if (FAILED(rc))
+ LogRelFunc(("Forward scenario: can't delete all original files.\n"));
+ }
+
+ if (!taskMoveVM->m_pProgress.isNull())
+ {
+ /* Set the first error happened */
+ if (!taskMoveVM->errorsList.empty())
+ {
+ ErrorInfoItem ei = taskMoveVM->errorsList.front();
+ machine->setError(ei.m_code, machine->tr(ei.m_description.c_str()));
+ }
+
+ taskMoveVM->m_pProgress->i_notifyComplete(taskMoveVM->result);
+ }
+
+ LogFlowFuncLeave();
+}
+
+HRESULT MachineMoveVM::moveAllDisks(const std::map<Utf8Str, MEDIUMTASKMOVE>& listOfDisks,
+ const Utf8Str* strTargetFolder)
+{
+ HRESULT rc = S_OK;
+ ComObjPtr<Machine> &machine = m_pMachine;
+ Utf8Str strLocation;
+
+ AutoWriteLock machineLock(machine COMMA_LOCKVAL_SRC_POS);
+
+ try{
+ std::map<Utf8Str, MEDIUMTASKMOVE>::const_iterator itMedium = listOfDisks.begin();
+ while(itMedium != listOfDisks.end())
+ {
+ const MEDIUMTASKMOVE &mt = itMedium->second;
+ ComPtr<IMedium> pMedium = mt.pMedium;
+ Utf8Str strTargetImageName;
+ Bstr bstrLocation;
+ Bstr bstrSrcName;
+
+ rc = pMedium->COMGETTER(Name)(bstrSrcName.asOutParam());
+ if (FAILED(rc)) throw rc;
+
+ if (strTargetFolder != NULL && !strTargetFolder->isEmpty())
+ {
+ strTargetImageName = *strTargetFolder;
+ rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
+ if (FAILED(rc)) throw rc;
+ strLocation = bstrLocation;
+
+ if (mt.fSnapshot == true)
+ {
+ strLocation.stripFilename().stripPath().append(RTPATH_DELIMITER).append(Utf8Str(bstrSrcName));
+ }
+ else
+ {
+ strLocation.stripPath();
+ }
+
+ strTargetImageName.append(RTPATH_DELIMITER).append(strLocation);
+ rc = m_pProgress->SetNextOperation(BstrFmt(machine->tr("Moving medium '%ls' ..."),
+ bstrSrcName.raw()).raw(),
+ mt.uWeight);
+ if (FAILED(rc)) throw rc;
+ }
+ else
+ {
+ strTargetImageName = mt.strBaseName;//Should contain full path to the image
+ rc = m_pProgress->SetNextOperation(BstrFmt(machine->tr("Moving medium '%ls' back..."),
+ bstrSrcName.raw()).raw(),
+ mt.uWeight);
+ if (FAILED(rc)) throw rc;
+ }
+
+
+
+ /* consistency: use \ if appropriate on the platform */
+ RTPathChangeToDosSlashes(strTargetImageName.mutableRaw(), false);
+
+ bstrLocation = strTargetImageName.c_str();
+
+ MediumType_T mediumType;//immutable, shared, passthrough
+ rc = pMedium->COMGETTER(Type)(&mediumType);
+ if (FAILED(rc)) throw rc;
+
+ DeviceType_T deviceType;//floppy, hard, DVD
+ rc = pMedium->COMGETTER(DeviceType)(&deviceType);
+ if (FAILED(rc)) throw rc;
+
+ /* Drop lock early because IMedium::MoveTo needs to get the VirtualBox one. */
+ machineLock.release();
+
+ ComPtr<IProgress> moveDiskProgress;
+ rc = pMedium->MoveTo(bstrLocation.raw(), moveDiskProgress.asOutParam());
+ if (SUCCEEDED(rc))
+ {
+ /* In case of failure moveDiskProgress would be in the invalid state or not initialized at all
+ * Call i_waitForOtherProgressCompletion only in success
+ */
+ /* Wait until the other process has finished. */
+ rc = m_pProgress->WaitForOtherProgressCompletion(moveDiskProgress, 0 /* indefinite wait */);
+ }
+
+ /*acquire the lock back*/
+ machineLock.acquire();
+
+ /** @todo r=klaus get rid of custom error handling logic everywhere in this file */
+ if (FAILED(rc))
+ {
+ com::ErrorInfoKeeper eik;
+ Utf8Str errorDesc(eik.getText().raw());
+ errorsList.push_back(ErrorInfoItem(eik.getResultCode(), errorDesc.c_str()));
+ }
+
+ if (FAILED(rc)) throw rc;
+
+ LogRelFunc(("Moving %s has been finished\n", strTargetImageName.c_str()));
+
+ ++itMedium;
+ }
+
+ machineLock.release();
+ }
+ catch(HRESULT hrc)
+ {
+ LogRelFunc(("\nException during moving the disk %s\n", strLocation.c_str()));
+ rc = hrc;
+ machineLock.release();
+ }
+ catch (...)
+ {
+ LogRelFunc(("\nException during moving the disk %s\n", strLocation.c_str()));
+ rc = VirtualBoxBase::handleUnexpectedExceptions(m_pMachine, RT_SRC_POS);
+ machineLock.release();
+ }
+
+ if (FAILED(rc))
+ {
+ Utf8StrFmt errorDesc("Exception during moving the disk %s\n", strLocation.c_str());
+ errorsList.push_back(ErrorInfoItem(rc, errorDesc.c_str()));
+ }
+
+ return rc;
+}
+
+HRESULT MachineMoveVM::updatePathsToStateFiles(const std::map<Utf8Str, SAVESTATETASKMOVE>& listOfFiles,
+ const Utf8Str& sourcePath, const Utf8Str& targetPath)
+{
+ HRESULT rc = S_OK;
+
+ std::map<Utf8Str, SAVESTATETASKMOVE>::const_iterator itState = listOfFiles.begin();
+ while (itState != listOfFiles.end())
+ {
+ const SAVESTATETASKMOVE &sst = itState->second;
+
+ if (sst.snapshotUuid != Guid::Empty)
+ {
+ Utf8Str strGuidMachine = sst.snapshotUuid.toString();
+ ComObjPtr<Snapshot> snapshotMachineObj;
+
+ rc = m_pMachine->i_findSnapshotById(sst.snapshotUuid, snapshotMachineObj, true);
+ if (SUCCEEDED(rc) && !snapshotMachineObj.isNull())
+ {
+ snapshotMachineObj->i_updateSavedStatePaths(sourcePath.c_str(),
+ targetPath.c_str());
+ }
+ }
+ else
+ {
+ const Utf8Str &path = m_pMachine->mSSData->strStateFilePath;
+ /*
+ * This check for the case when a new value is equal to the old one.
+ * Maybe the more clever check is needed in the some corner cases.
+ */
+ if (!path.contains(targetPath))
+ {
+ m_pMachine->mSSData->strStateFilePath = Utf8StrFmt("%s%s",
+ targetPath.c_str(),
+ path.c_str() + sourcePath.length());
+ }
+ }
+
+ ++itState;
+ }
+
+ return rc;
+}
+
+HRESULT MachineMoveVM::getFilesList(const Utf8Str& strRootFolder, fileList_t &filesList)
+{
+ RTDIR hDir;
+ HRESULT rc = S_OK;
+ int vrc = RTDirOpen(&hDir, strRootFolder.c_str());
+ if (RT_SUCCESS(vrc))
+ {
+ RTDIRENTRY DirEntry;
+ while (RT_SUCCESS(RTDirRead(hDir, &DirEntry, NULL)))
+ {
+ if (RTDirEntryIsStdDotLink(&DirEntry))
+ continue;
+
+ if (DirEntry.enmType == RTDIRENTRYTYPE_FILE)
+ {
+ Utf8Str fullPath(strRootFolder);
+ filesList.add(strRootFolder, DirEntry.szName);
+ }
+ else if (DirEntry.enmType == RTDIRENTRYTYPE_DIRECTORY)
+ {
+ Utf8Str strNextFolder(strRootFolder);
+ strNextFolder.append(RTPATH_DELIMITER).append(DirEntry.szName);
+ rc = getFilesList(strNextFolder, filesList);
+ if (FAILED(rc))
+ break;
+ }
+ }
+
+ vrc = RTDirClose(hDir);
+ AssertRC(vrc);
+ }
+ else if (vrc == VERR_FILE_NOT_FOUND)
+ {
+ Utf8StrFmt errorDesc("Folder '%s' doesn't exist (%Rrc)", strRootFolder.c_str(), vrc);
+ errorsList.push_back(ErrorInfoItem(VERR_FILE_NOT_FOUND, errorDesc.c_str()));
+
+ rc = m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, m_pMachine->tr(errorDesc.c_str()));
+ }
+ else
+ {
+ Utf8StrFmt errorDesc("Could not open folder '%s' (%Rrc)", strRootFolder.c_str(), vrc);
+ errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
+
+/** @todo r=bird: document this throwing business, please! Error handling is
+ * a bit fishy here. See also */
+ throw m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, m_pMachine->tr(errorDesc.c_str()));
+ }
+
+ return rc;
+}
+
+HRESULT MachineMoveVM::deleteFiles(const RTCList<Utf8Str>& listOfFiles)
+{
+ HRESULT rc = S_OK;
+ /* Delete all created files. */
+ try
+ {
+ for (size_t i = 0; i < listOfFiles.size(); ++i)
+ {
+ LogRelFunc(("Deleting file %s ...\n", listOfFiles.at(i).c_str()));
+ rc = m_pProgress->SetNextOperation(BstrFmt("Deleting file %s...", listOfFiles.at(i).c_str()).raw(), 1);
+ if (FAILED(rc)) throw rc;
+
+ int vrc = RTFileDelete(listOfFiles.at(i).c_str());
+ if (RT_FAILURE(vrc))
+ {
+ Utf8StrFmt errorDesc("Could not delete file '%s' (%Rrc)", listOfFiles.at(i).c_str(), rc);
+ errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
+
+ rc = m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, m_pMachine->tr(errorDesc.c_str()));
+ }
+ else
+ LogRelFunc(("File %s has been deleted\n", listOfFiles.at(i).c_str()));
+ }
+ }
+ catch(HRESULT hrc)
+ {
+ rc = hrc;
+ }
+ catch (...)
+ {
+ rc = VirtualBoxBase::handleUnexpectedExceptions(m_pMachine, RT_SRC_POS);
+ }
+
+ return rc;
+}
+
+HRESULT MachineMoveVM::getFolderSize(const Utf8Str& strRootFolder, uint64_t& size)
+{
+ HRESULT rc = S_OK;
+ int vrc = 0;
+ uint64_t totalFolderSize = 0;
+ fileList_t filesList;
+
+ bool ex = RTPathExists(strRootFolder.c_str());
+ if (ex == true)
+ {
+ rc = getFilesList(strRootFolder, filesList);
+ if (SUCCEEDED(rc))
+ {
+ cit_t it = filesList.m_list.begin();
+ while(it != filesList.m_list.end())
+ {
+ uint64_t cbFile = 0;
+ Utf8Str fullPath = it->first;
+ fullPath.append(RTPATH_DELIMITER).append(it->second);
+ vrc = RTFileQuerySize(fullPath.c_str(), &cbFile);
+ if (RT_SUCCESS(vrc))
+ {
+ totalFolderSize += cbFile;
+ }
+ else
+ {
+ Utf8StrFmt errorDesc("Could not get the size of file '%s' (%Rrc)", fullPath.c_str(), vrc);
+ errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
+
+ throw m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, m_pMachine->tr(errorDesc.c_str()));
+ }
+ ++it;
+ }
+
+ size = totalFolderSize;
+ }
+ else
+ {
+/** @todo r=bird: This only happens if RTDirOpen fails. So, I'm not sure
+ * what you're going for here... See not about throwing in getFilesList(). */
+ Utf8StrFmt errorDesc("Could not calculate the size of folder '%s'", strRootFolder.c_str());
+ errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
+
+ m_pMachine->setError(VBOX_E_IPRT_ERROR, m_pMachine->tr(errorDesc.c_str()));
+ }
+ }
+ else
+ size = 0;
+
+ return rc;
+}
+
+HRESULT MachineMoveVM::queryBaseName(const ComPtr<IMedium> &pMedium, Utf8Str &strBaseName) const
+{
+ ComPtr<IMedium> pBaseMedium;
+ HRESULT rc = pMedium->COMGETTER(Base)(pBaseMedium.asOutParam());
+ if (FAILED(rc)) return rc;
+ Bstr bstrBaseName;
+ rc = pBaseMedium->COMGETTER(Name)(bstrBaseName.asOutParam());
+ if (FAILED(rc)) return rc;
+ strBaseName = bstrBaseName;
+ return rc;
+}
+
+HRESULT MachineMoveVM::createMachineList(const ComPtr<ISnapshot> &pSnapshot,
+ std::vector< ComObjPtr<Machine> > &aMachineList) const
+{
+ HRESULT rc = S_OK;
+ Bstr name;
+ rc = pSnapshot->COMGETTER(Name)(name.asOutParam());
+ if (FAILED(rc)) return rc;
+
+ ComPtr<IMachine> l_pMachine;
+ rc = pSnapshot->COMGETTER(Machine)(l_pMachine.asOutParam());
+ if (FAILED(rc)) return rc;
+ aMachineList.push_back((Machine*)(IMachine*)l_pMachine);
+
+ SafeIfaceArray<ISnapshot> sfaChilds;
+ rc = pSnapshot->COMGETTER(Children)(ComSafeArrayAsOutParam(sfaChilds));
+ if (FAILED(rc)) return rc;
+ for (size_t i = 0; i < sfaChilds.size(); ++i)
+ {
+ rc = createMachineList(sfaChilds[i], aMachineList);
+ if (FAILED(rc)) return rc;
+ }
+
+ return rc;
+}
+
+HRESULT MachineMoveVM::queryMediasForAllStates(const std::vector<ComObjPtr<Machine> > &aMachineList)
+{
+ /* In this case we create a exact copy of the original VM. This means just
+ * adding all directly and indirectly attached disk images to the worker
+ * list. */
+ HRESULT rc = S_OK;
+ for (size_t i = 0; i < aMachineList.size(); ++i)
+ {
+ const ComObjPtr<Machine> &machine = aMachineList.at(i);
+
+ /* Add all attachments (and their parents) of the different
+ * machines to a worker list. */
+ SafeIfaceArray<IMediumAttachment> sfaAttachments;
+ rc = machine->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(sfaAttachments));
+ if (FAILED(rc)) return rc;
+ for (size_t a = 0; a < sfaAttachments.size(); ++a)
+ {
+ const ComPtr<IMediumAttachment> &pAtt = sfaAttachments[a];
+ DeviceType_T deviceType;//floppy, hard, DVD
+ rc = pAtt->COMGETTER(Type)(&deviceType);
+ if (FAILED(rc)) return rc;
+
+ /* Valid medium attached? */
+ ComPtr<IMedium> pMedium;
+ rc = pAtt->COMGETTER(Medium)(pMedium.asOutParam());
+ if (FAILED(rc)) return rc;
+
+ if (pMedium.isNull())
+ continue;
+
+ Bstr bstrLocation;
+ rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
+ if (FAILED(rc)) throw rc;
+
+ /* Cast to ComObjPtr<Medium> */
+ ComObjPtr<Medium> pObjMedium = (Medium *)(IMedium *)pMedium;
+
+ /*Check for "read-only" medium in terms that VBox can't create this one */
+ bool fPass = isMediumTypeSupportedForMoving(pMedium);
+ if(!fPass)
+ {
+ LogRelFunc(("Skipping file %s because of this medium type hasn't been supported for moving.\n",
+ Utf8Str(bstrLocation.raw()).c_str()));
+ continue;
+ }
+
+ MEDIUMTASKCHAINMOVE mtc;
+ mtc.devType = deviceType;
+ mtc.fAttachLinked = false;
+ mtc.fCreateDiffs = false;
+
+ while (!pMedium.isNull())
+ {
+ /* Refresh the state so that the file size get read. */
+ MediumState_T e;
+ rc = pMedium->RefreshState(&e);
+ if (FAILED(rc)) return rc;
+
+ LONG64 lSize;
+ rc = pMedium->COMGETTER(Size)(&lSize);
+ if (FAILED(rc)) return rc;
+
+ MediumType_T mediumType;//immutable, shared, passthrough
+ rc = pMedium->COMGETTER(Type)(&mediumType);
+ if (FAILED(rc)) throw rc;
+
+ rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
+ if (FAILED(rc)) throw rc;
+
+ MEDIUMTASKMOVE mt;// = {false, "basename", NULL, 0, 0};
+ mt.strBaseName = bstrLocation;
+ Utf8Str strFolder = vmFolders[VBox_SnapshotFolder];
+ if (strFolder.isNotEmpty() && mt.strBaseName.contains(strFolder))
+ {
+ mt.fSnapshot = true;
+ }
+ else
+ mt.fSnapshot = false;
+
+ mt.uIdx = UINT32_MAX;
+ mt.pMedium = pMedium;
+ mt.uWeight = (ULONG)((lSize + _1M - 1) / _1M);
+ mtc.chain.append(mt);
+
+ /* Query next parent. */
+ rc = pMedium->COMGETTER(Parent)(pMedium.asOutParam());
+ if (FAILED(rc)) return rc;
+ }
+
+ llMedias.append(mtc);
+ }
+ /* Add the save state files of this machine if there is one. */
+ rc = addSaveState(machine);
+ if (FAILED(rc)) return rc;
+
+ }
+ /* Build up the index list of the image chain. Unfortunately we can't do
+ * that in the previous loop, cause there we go from child -> parent and
+ * didn't know how many are between. */
+ for (size_t i = 0; i < llMedias.size(); ++i)
+ {
+ uint32_t uIdx = 0;
+ MEDIUMTASKCHAINMOVE &mtc = llMedias.at(i);
+ for (size_t a = mtc.chain.size(); a > 0; --a)
+ mtc.chain[a - 1].uIdx = uIdx++;
+ }
+
+ return rc;
+}
+
+HRESULT MachineMoveVM::addSaveState(const ComObjPtr<Machine> &machine)
+{
+ Bstr bstrSrcSaveStatePath;
+ HRESULT rc = machine->COMGETTER(StateFilePath)(bstrSrcSaveStatePath.asOutParam());
+ if (FAILED(rc)) return rc;
+ if (!bstrSrcSaveStatePath.isEmpty())
+ {
+ SAVESTATETASKMOVE sst;
+
+ sst.snapshotUuid = machine->i_getSnapshotId();
+ sst.strSaveStateFile = bstrSrcSaveStatePath;
+ uint64_t cbSize;
+
+ int vrc = RTFileQuerySize(sst.strSaveStateFile.c_str(), &cbSize);
+ if (RT_FAILURE(vrc))
+ {
+ Utf8StrFmt errorDesc("Could not get file size of '%s' (%Rrc)", sst.strSaveStateFile.c_str(), vrc);
+ errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
+
+ return m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, m_pMachine->tr(errorDesc.c_str()));
+ }
+ /* same rule as above: count both the data which needs to
+ * be read and written */
+ sst.uWeight = (ULONG)(2 * (cbSize + _1M - 1) / _1M);
+ llSaveStateFiles.append(sst);
+ }
+ return S_OK;
+}
+
+void MachineMoveVM::updateProgressStats(MEDIUMTASKCHAINMOVE &mtc, ULONG &uCount, ULONG &uTotalWeight) const
+{
+
+ /* Currently the copying of diff images involves reading at least
+ * the biggest parent in the previous chain. So even if the new
+ * diff image is small in size, it could need some time to create
+ * it. Adding the biggest size in the chain should balance this a
+ * little bit more, i.e. the weight is the sum of the data which
+ * needs to be read and written. */
+ ULONG uMaxWeight = 0;
+ for (size_t e = mtc.chain.size(); e > 0; --e)
+ {
+ MEDIUMTASKMOVE &mt = mtc.chain.at(e - 1);
+ mt.uWeight += uMaxWeight;
+
+ /* Calculate progress data */
+ ++uCount;
+ uTotalWeight += mt.uWeight;
+
+ /* Save the max size for better weighting of diff image
+ * creation. */
+ uMaxWeight = RT_MAX(uMaxWeight, mt.uWeight);
+ }
+}
+
+bool MachineMoveVM::isMediumTypeSupportedForMoving(const ComPtr<IMedium> &pMedium)
+{
+ HRESULT rc = S_OK;
+ bool fSupported = true;
+ Bstr bstrLocation;
+ rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
+ if (FAILED(rc))
+ {
+ fSupported = false;
+ throw rc;
+ }
+
+ DeviceType_T deviceType;
+ rc = pMedium->COMGETTER(DeviceType)(&deviceType);
+ if (FAILED(rc))
+ {
+ fSupported = false;
+ throw rc;
+ }
+
+ ComPtr<IMediumFormat> mediumFormat;
+ rc = pMedium->COMGETTER(MediumFormat)(mediumFormat.asOutParam());
+ if (FAILED(rc))
+ {
+ fSupported = false;
+ throw rc;
+ }
+
+ /*Check whether VBox is able to create this medium format or not, i.e. medium can be "read-only" */
+ Bstr bstrFormatName;
+ rc = mediumFormat->COMGETTER(Name)(bstrFormatName.asOutParam());
+ if (FAILED(rc))
+ {
+ fSupported = false;
+ throw rc;
+ }
+
+ Utf8Str formatName = Utf8Str(bstrFormatName);
+ if (formatName.compare("VHDX", Utf8Str::CaseInsensitive) == 0)
+ {
+ LogRelFunc(("Skipping medium %s. VHDX format is supported in \"read-only\" mode only. \n",
+ Utf8Str(bstrLocation.raw()).c_str()));
+ fSupported = false;
+ }
+
+ /* Check whether medium is represented by file on the disk or not */
+ if (fSupported)
+ {
+ ComObjPtr<Medium> pObjMedium = (Medium *)(IMedium *)pMedium;
+ fSupported = pObjMedium->i_isMediumFormatFile();
+ if (!fSupported)
+ {
+ LogRelFunc(("Skipping medium %s because it's not a real file on the disk.\n",
+ Utf8Str(bstrLocation.raw()).c_str()));
+ }
+ }
+
+ /* some special checks for DVD */
+ if (fSupported && deviceType == DeviceType_DVD)
+ {
+ Utf8Str ext = bstrLocation;
+ ext.assignEx(RTPathSuffix(ext.c_str()));//returns extension with dot (".iso")
+
+ //only ISO image is moved. Otherwise adding some information into log file
+ int equality = ext.compare(".iso", Utf8Str::CaseInsensitive);
+ if (equality != false)
+ {
+ LogRelFunc(("Skipping file %s. Only ISO images are supported for now.\n",
+ Utf8Str(bstrLocation.raw()).c_str()));
+ fSupported = false;
+ }
+ }
+
+ return fSupported;
+}