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.cpp1702
1 files changed, 1702 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..20c83825
--- /dev/null
+++ b/src/VBox/Main/src-server/MachineImplMoveVM.cpp
@@ -0,0 +1,1702 @@
+/* $Id: MachineImplMoveVM.cpp $ */
+/** @file
+ * Implementation of MachineMoveVM
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#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 "SnapshotImpl.h"
+#include "MediumFormatImpl.h"
+#include "VirtualBoxImpl.h"
+#include "LoggingNew.h"
+
+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;
+
+};
+
+
+HRESULT MachineMoveVM::init()
+{
+ HRESULT hrc = S_OK;
+
+ Utf8Str strTargetFolder;
+ /* adding a trailing slash if it's needed */
+ {
+ size_t len = m_targetPath.length() + 2;
+ if (len >= RTPATH_MAX)
+ return m_pMachine->setError(VBOX_E_IPRT_ERROR, tr("The destination path exceeds the maximum value."));
+
+ /** @todo r=bird: I need to add a Utf8Str method or iprt/cxx/path.h thingy
+ * for doing this. We need this often and code like this doesn't
+ * need to be repeated and re-optimized in each instance... */
+ 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.
+ */
+
+ RTFOFF cbTotal = 0;
+ RTFOFF cbFree = 0;
+ uint32_t cbBlock = 0;
+ uint32_t cbSector = 0;
+
+
+ int vrc = RTFsQuerySizes(strTargetFolder.c_str(), &cbTotal, &cbFree, &cbBlock, &cbSector);
+ if (RT_FAILURE(vrc))
+ return m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Unable to determine free space at move destination ('%s'): %Rrc"),
+ strTargetFolder.c_str(), vrc);
+
+ RTDIR hDir;
+ vrc = RTDirOpen(&hDir, strTargetFolder.c_str());
+ if (RT_FAILURE(vrc))
+ return m_pMachine->setErrorVrc(vrc);
+
+ Utf8Str strTempFile = strTargetFolder + "test.txt";
+ RTFILE hFile;
+ vrc = RTFileOpen(&hFile, strTempFile.c_str(), RTFILE_O_OPEN_CREATE | RTFILE_O_READWRITE | RTFILE_O_DENY_NONE);
+ if (RT_FAILURE(vrc))
+ {
+ RTDirClose(hDir);
+ return m_pMachine->setErrorVrc(vrc,
+ tr("Can't create a test file test.txt in the %s. Check the access rights of the destination folder."),
+ strTargetFolder.c_str());
+ }
+
+ /** @todo r=vvp: Do we need to check each return result here? Looks excessively.
+ * And it's not so important for the test file.
+ * bird: I'd just do AssertRC on the same line, though the deletion
+ * of the test is a little important. */
+ vrc = RTFileClose(hFile); AssertRC(vrc);
+ RTFileDelete(strTempFile.c_str());
+ vrc = RTDirClose(hDir); AssertRC(vrc);
+
+ Log2(("blocks: total %RTfoff, free %RTfoff\n", cbTotal, cbFree));
+ Log2(("total space (Kb) %RTfoff (Mb) %RTfoff (Gb) %RTfoff\n", cbTotal/_1K, cbTotal/_1M, cbTotal/_1G));
+ Log2(("total free space (Kb) %RTfoff (Mb) %RTfoff (Gb) %RTfoff\n", cbFree/_1K, cbFree/_1M, cbFree/_1G));
+
+ RTFSPROPERTIES properties;
+ vrc = RTFsQueryProperties(strTargetFolder.c_str(), &properties);
+ if (RT_FAILURE(vrc))
+ return m_pMachine->setErrorVrc(vrc, "RTFsQueryProperties(%s): %Rrc", strTargetFolder.c_str(), vrc);
+
+ Log2(("disk properties: remote=%RTbool read only=%RTbool compressed=%RTbool\n",
+ properties.fRemote, properties.fReadOnly, properties.fCompressed));
+
+ /* Get the original VM path */
+ Utf8Str strSettingsFilePath;
+ Bstr bstr_settingsFilePath;
+ hrc = m_pMachine->COMGETTER(SettingsFilePath)(bstr_settingsFilePath.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+
+ strSettingsFilePath = bstr_settingsFilePath;
+ strSettingsFilePath.stripFilename();
+
+ m_vmFolders.insert(std::make_pair(VBox_SettingFolder, strSettingsFilePath));
+
+ /* Collect all files from the VM's folder */
+ fileList_t fullFileList;
+ hrc = getFilesList(strSettingsFilePath, fullFileList);
+ if (FAILED(hrc))
+ return hrc;
+
+ /*
+ * Collect all known folders used by the VM:
+ * - log folder;
+ * - state folder;
+ * - snapshot folder.
+ */
+ Utf8Str strLogFolder;
+ Bstr bstr_logFolder;
+ hrc = m_pMachine->COMGETTER(LogFolder)(bstr_logFolder.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+
+ strLogFolder = bstr_logFolder;
+ if ( m_type.equals("basic")
+ && RTPathStartsWith(strLogFolder.c_str(), strSettingsFilePath.c_str()))
+ m_vmFolders.insert(std::make_pair(VBox_LogFolder, strLogFolder));
+
+ Utf8Str strStateFilePath;
+ Bstr bstr_stateFilePath;
+ MachineState_T machineState;
+ hrc = m_pMachine->COMGETTER(State)(&machineState);
+ if (FAILED(hrc))
+ return hrc;
+
+ if (machineState == MachineState_Saved || machineState == MachineState_AbortedSaved)
+ {
+ m_pMachine->COMGETTER(StateFilePath)(bstr_stateFilePath.asOutParam());
+ strStateFilePath = bstr_stateFilePath;
+ strStateFilePath.stripFilename();
+ if ( m_type.equals("basic")
+ && RTPathStartsWith(strStateFilePath.c_str(), strSettingsFilePath.c_str()))
+ m_vmFolders.insert(std::make_pair(VBox_StateFolder, strStateFilePath));
+ }
+
+ Utf8Str strSnapshotFolder;
+ Bstr bstr_snapshotFolder;
+ hrc = m_pMachine->COMGETTER(SnapshotFolder)(bstr_snapshotFolder.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+
+ strSnapshotFolder = bstr_snapshotFolder;
+ if ( m_type.equals("basic")
+ && RTPathStartsWith(strSnapshotFolder.c_str(), strSettingsFilePath.c_str()))
+ m_vmFolders.insert(std::make_pair(VBox_SnapshotFolder, strSnapshotFolder));
+
+ if (m_pMachine->i_isSnapshotMachine())
+ {
+ Bstr bstrSrcMachineId;
+ hrc = m_pMachine->COMGETTER(Id)(bstrSrcMachineId.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+
+ ComPtr<IMachine> newSrcMachine;
+ hrc = m_pMachine->i_getVirtualBox()->FindMachine(bstrSrcMachineId.raw(), newSrcMachine.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+ }
+
+ /* Add the current machine and all snapshot machines below this machine
+ * in a list for further processing.
+ */
+
+ int64_t neededFreeSpace = 0;
+
+ /* Actual file list */
+ fileList_t actualFileList;
+ Utf8Str strTargetImageName;
+
+ machineList.push_back(m_pMachine);
+
+ {
+ ULONG cSnapshots = 0;
+ hrc = m_pMachine->COMGETTER(SnapshotCount)(&cSnapshots);
+ if (FAILED(hrc))
+ return hrc;
+
+ if (cSnapshots > 0)
+ {
+ Utf8Str id;
+ if (m_pMachine->i_isSnapshotMachine())
+ id = m_pMachine->i_getSnapshotId().toString();
+ ComPtr<ISnapshot> pSnapshot;
+ hrc = m_pMachine->FindSnapshot(Bstr(id).raw(), pSnapshot.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+ hrc = createMachineList(pSnapshot);
+ if (FAILED(hrc))
+ return hrc;
+ }
+ }
+
+ ULONG uCount = 1;//looks like it should be initialized by 1. See assertion in the Progress::setNextOperation()
+ ULONG uTotalWeight = 1;
+
+ /* The lists m_llMedias, m_llSaveStateFiles and m_llNVRAMFiles are filled in the queryMediasForAllStates() */
+ hrc = queryMediasForAllStates();
+ if (FAILED(hrc))
+ return hrc;
+
+ /* Calculate the total size of images. Fill m_finalMediumsMap */
+ { /** The scope here for better reading, apart from that the variables have limited scope too */
+ uint64_t totalMediumsSize = 0;
+
+ for (size_t i = 0; i < m_llMedias.size(); ++i)
+ {
+ MEDIUMTASKCHAINMOVE &mtc = m_llMedias.at(i);
+ for (size_t a = mtc.chain.size(); a > 0; --a)
+ {
+ Bstr bstrLocation;
+ Utf8Str name = mtc.chain[a - 1].strBaseName;
+ ComPtr<IMedium> plMedium = mtc.chain[a - 1].pMedium;
+ hrc = plMedium->COMGETTER(Location)(bstrLocation.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+
+ Utf8Str strLocation = bstrLocation;
+
+ /* if an image is located in the actual VM folder it will be added to the actual list */
+ if (strLocation.startsWith(strSettingsFilePath))
+ {
+ LONG64 cbSize = 0;
+ hrc = plMedium->COMGETTER(Size)(&cbSize);
+ if (FAILED(hrc))
+ return hrc;
+
+ std::pair<std::map<Utf8Str, MEDIUMTASKMOVE>::iterator,bool> ret;
+ ret = m_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 += (uint64_t)cbSize;
+ Log2(("Image %s was added into the moved list\n", name.c_str()));
+ }
+ }
+ }
+ }
+
+ Log2(("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 < m_llSaveStateFiles.size(); ++i)
+ {
+ uint64_t cbFile = 0;
+ SNAPFILETASKMOVE &sft = m_llSaveStateFiles.at(i);
+
+ Utf8Str name = sft.strFile;
+ /* if a state file is located in the actual VM folder it will be added to the actual list */
+ if (RTPathStartsWith(name.c_str(), strSettingsFilePath.c_str()))
+ {
+ vrc = RTFileQuerySizeByPath(name.c_str(), &cbFile);
+ if (RT_SUCCESS(vrc))
+ {
+ std::pair<std::map<Utf8Str, SNAPFILETASKMOVE>::iterator,bool> ret;
+ ret = m_finalSaveStateFilesMap.insert(std::make_pair(name, sft));
+ if (ret.second == true)
+ {
+ totalStateSize += cbFile;
+ ++uCount;
+ uTotalWeight += sft.uWeight;
+ Log2(("The state file %s was added into the moved list\n", name.c_str()));
+ }
+ }
+ else
+ {
+ Log2(("The state file %s wasn't added into the moved list. Couldn't get the file size.\n",
+ name.c_str()));
+ return m_pMachine->setErrorVrc(vrc,
+ tr("Failed to get file size for '%s': %Rrc"),
+ name.c_str(), vrc);
+ }
+ }
+ }
+
+ neededFreeSpace += totalStateSize;
+ }
+
+ /* Prepare data for moving ".nvram" files */
+ {
+ uint64_t totalNVRAMSize = 0;
+
+ for (size_t i = 0; i < m_llNVRAMFiles.size(); ++i)
+ {
+ uint64_t cbFile = 0;
+ SNAPFILETASKMOVE &sft = m_llNVRAMFiles.at(i);
+
+ Utf8Str name = sft.strFile;
+ /* if a NVRAM file is located in the actual VM folder it will be added to the actual list */
+ if (RTPathStartsWith(name.c_str(), strSettingsFilePath.c_str()))
+ {
+ vrc = RTFileQuerySizeByPath(name.c_str(), &cbFile);
+ if (RT_SUCCESS(vrc))
+ {
+ std::pair<std::map<Utf8Str, SNAPFILETASKMOVE>::iterator,bool> ret;
+ ret = m_finalNVRAMFilesMap.insert(std::make_pair(name, sft));
+ if (ret.second == true)
+ {
+ totalNVRAMSize += cbFile;
+ ++uCount;
+ uTotalWeight += sft.uWeight;
+ Log2(("The NVRAM file %s was added into the moved list\n", name.c_str()));
+ }
+ }
+ else
+ {
+ Log2(("The NVRAM file %s wasn't added into the moved list. Couldn't get the file size.\n",
+ name.c_str()));
+ return m_pMachine->setErrorVrc(vrc,
+ tr("Failed to get file size for '%s': %Rrc"),
+ name.c_str(), vrc);
+ }
+ }
+ }
+
+ neededFreeSpace += totalNVRAMSize;
+ }
+
+ /* Prepare data for moving the log files */
+ {
+ Utf8Str strFolder = m_vmFolders[VBox_LogFolder];
+
+ if (RTPathExists(strFolder.c_str()))
+ {
+ uint64_t totalLogSize = 0;
+ hrc = getFolderSize(strFolder, totalLogSize);
+ if (SUCCEEDED(hrc))
+ {
+ neededFreeSpace += totalLogSize;
+ if (cbFree - neededFreeSpace <= _1M)
+ return m_pMachine->setError(E_FAIL,
+ tr("Insufficient disk space available (%RTfoff needed, %RTfoff free)"),
+ neededFreeSpace, cbFree);
+
+ fileList_t filesList;
+ hrc = getFilesList(strFolder, filesList);
+ if (FAILED(hrc))
+ return hrc;
+
+ 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 = RTFileQuerySizeByPath(strFile.c_str(), &cbFile);
+ if (RT_SUCCESS(vrc))
+ {
+ uCount += 1;
+ uTotalWeight += (ULONG)((cbFile + _1M - 1) / _1M);
+ actualFileList.add(strFile);
+ Log2(("The log file %s added into the moved list\n", strFile.c_str()));
+ }
+ else
+ Log2(("The log file %s wasn't added into the moved list. Couldn't get the file size.\n", strFile.c_str()));
+ ++it;
+ }
+ }
+ else
+ return hrc;
+ }
+ else
+ {
+ Log2(("Information: The original log folder %s doesn't exist\n", strFolder.c_str()));
+ hrc = S_OK;//it's not error in this case if there isn't an original log folder
+ }
+ }
+
+ LogRel(("Total space needed is %lld bytes\n", neededFreeSpace));
+ /* Check a target location on enough room */
+ if (cbFree - neededFreeSpace <= _1M)
+ {
+ LogRel(("but free space on destination is %RTfoff\n", cbFree));
+ return m_pMachine->setError(VBOX_E_IPRT_ERROR,
+ tr("Insufficient disk space available (%RTfoff needed, %RTfoff free)"),
+ neededFreeSpace, cbFree);
+ }
+
+ /* 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 */
+ {
+ hrc = m_pProgress->init(m_pMachine->i_getVirtualBox(),
+ static_cast<IMachine *>(m_pMachine) /* aInitiator */,
+ Utf8Str(tr("Moving Machine")),
+ true /* fCancellable */,
+ uCount,
+ uTotalWeight,
+ Utf8Str(tr("Initialize Moving")),
+ 1);
+ if (FAILED(hrc))
+ return m_pMachine->setError(hrc,
+ tr("Couldn't correctly setup the progress object for moving VM operation"));
+ }
+
+ /* save all VM data */
+ m_pMachine->i_setModified(Machine::IsModified_MachineData);
+ hrc = m_pMachine->SaveSettings();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowFuncLeave();
+
+ return hrc;
+}
+
+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);
+ Log2(("snap.uuid = %s\n", snap.uuid.toStringCurly().c_str()));
+ Log2(("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 hrc = 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;
+ hrc = machine->COMGETTER(Name)(bstrMachineName.asOutParam());
+ if (FAILED(hrc))
+ {
+ taskMoveVM->m_result = hrc;
+ if (!taskMoveVM->m_pProgress.isNull())
+ taskMoveVM->m_pProgress->i_notifyComplete(taskMoveVM->m_result);
+ return;
+ }
+ 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 */
+ hrc = taskMoveVM->moveAllDisks(taskMoveVM->m_finalMediumsMap, strTargetFolder);
+ if (FAILED(hrc))
+ throw hrc;
+
+ /* 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->m_finalSaveStateFilesMap.size() > 0
+ || taskMoveVM->m_finalNVRAMFilesMap.size() > 1)
+ && !RTDirExists(strTrgSnapshotFolder.c_str()))
+ {
+ int vrc = RTDirCreateFullPath(strTrgSnapshotFolder.c_str(), 0700);
+ if (RT_FAILURE(vrc))
+ throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not create snapshots folder '%s' (%Rrc)"),
+ strTrgSnapshotFolder.c_str(), vrc);
+ }
+
+ std::map<Utf8Str, SNAPFILETASKMOVE>::iterator itState = taskMoveVM->m_finalSaveStateFilesMap.begin();
+ while (itState != taskMoveVM->m_finalSaveStateFilesMap.end())
+ {
+ const SNAPFILETASKMOVE &sft = itState->second;
+ const Utf8Str &strTrgSaveState = Utf8StrFmt("%s%c%s", strTrgSnapshotFolder.c_str(), RTPATH_DELIMITER,
+ RTPathFilename(sft.strFile.c_str()));
+
+ /* Move to next sub-operation. */
+ hrc = taskMoveVM->m_pProgress->SetNextOperation(BstrFmt(tr("Copy the save state file '%s' ..."),
+ RTPathFilename(sft.strFile.c_str())).raw(),
+ sft.uWeight);
+ if (FAILED(hrc))
+ throw hrc;
+
+ int vrc = RTFileCopyEx(sft.strFile.c_str(), strTrgSaveState.c_str(), 0,
+ MachineMoveVM::copyFileProgress, &taskMoveVM->m_pProgress);
+ if (RT_FAILURE(vrc))
+ throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not copy state file '%s' to '%s' (%Rrc)"),
+ sft.strFile.c_str(),
+ strTrgSaveState.c_str(),
+ vrc);
+
+ /* save new file in case of restoring */
+ newFiles.append(strTrgSaveState);
+ /* save original file for deletion in the end */
+ originalFiles.append(sft.strFile);
+ ++itState;
+ }
+
+ std::map<Utf8Str, SNAPFILETASKMOVE>::iterator itNVRAM = taskMoveVM->m_finalNVRAMFilesMap.begin();
+ while (itNVRAM != taskMoveVM->m_finalNVRAMFilesMap.end())
+ {
+ const SNAPFILETASKMOVE &sft = itNVRAM->second;
+ const Utf8Str &strTrgNVRAM = Utf8StrFmt("%s%c%s", sft.snapshotUuid.isZero() ? strTargetFolder.c_str() : strTrgSnapshotFolder.c_str(),
+ RTPATH_DELIMITER, RTPathFilename(sft.strFile.c_str()));
+
+ /* Move to next sub-operation. */
+ hrc = taskMoveVM->m_pProgress->SetNextOperation(BstrFmt(tr("Copy the NVRAM file '%s' ..."),
+ RTPathFilename(sft.strFile.c_str())).raw(),
+ sft.uWeight);
+ if (FAILED(hrc))
+ throw hrc;
+
+ int vrc = RTFileCopyEx(sft.strFile.c_str(), strTrgNVRAM.c_str(), 0,
+ MachineMoveVM::copyFileProgress, &taskMoveVM->m_pProgress);
+ if (RT_FAILURE(vrc))
+ throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not copy NVRAM file '%s' to '%s' (%Rrc)"),
+ sft.strFile.c_str(),
+ strTrgNVRAM.c_str(),
+ vrc);
+
+ /* save new file in case of restoring */
+ newFiles.append(strTrgNVRAM);
+ /* save original file for deletion in the end */
+ originalFiles.append(sft.strFile);
+ ++itNVRAM;
+ }
+ }
+
+ /*
+ * Update state file path
+ * very important step!
+ */
+ Log2(("Update state file path\n"));
+ /** @todo r=klaus: this update is not necessarily matching what the
+ * above code has set as the new folders, so it needs reimplementing */
+ taskMoveVM->updatePathsToStateFiles(taskMoveVM->m_vmFolders[VBox_SettingFolder],
+ strTargetFolder);
+
+ /*
+ * Update NVRAM file paths
+ * very important step!
+ */
+ Log2(("Update NVRAM paths\n"));
+ /** @todo r=klaus: this update is not necessarily matching what the
+ * above code has set as the new folders, so it needs reimplementing.
+ * What's good about this implementation: it does not look at the
+ * list of NVRAM files, because that only lists the existing ones,
+ * but all paths need fixing. */
+ taskMoveVM->updatePathsToNVRAMFiles(taskMoveVM->m_vmFolders[VBox_SettingFolder],
+ strTargetFolder);
+
+ /*
+ * 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.
+ */
+ {
+ Log2(("Copy Machine settings file\n"));
+
+ hrc = taskMoveVM->m_pProgress->SetNextOperation(BstrFmt(tr("Copy Machine settings file '%s' ..."),
+ machineConfFile->machineUserData.strName.c_str()).raw(),
+ 1);
+ if (FAILED(hrc))
+ throw hrc;
+
+ 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))
+ throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not create a home machine folder '%s' (%Rrc)"),
+ strTargetSettingsFilePath.c_str(), vrc);
+
+ Log2(("Created a home machine folder %s\n", strTargetSettingsFilePath.c_str()));
+ }
+
+ /* Create a full path */
+ Bstr bstrMachineName;
+ machine->COMGETTER(Name)(bstrMachineName.asOutParam());
+ if (FAILED(hrc))
+ throw hrc;
+ strTargetSettingsFilePath.append(RTPATH_DELIMITER).append(Utf8Str(bstrMachineName));
+ strTargetSettingsFilePath.append(".vbox");
+
+ Utf8Str strSettingsFilePath;
+ Bstr bstr_settingsFilePath;
+ machine->COMGETTER(SettingsFilePath)(bstr_settingsFilePath.asOutParam());
+ if (FAILED(hrc))
+ throw hrc;
+ strSettingsFilePath = bstr_settingsFilePath;
+
+ int vrc = RTFileCopyEx(strSettingsFilePath.c_str(), strTargetSettingsFilePath.c_str(), 0,
+ MachineMoveVM::copyFileProgress, &taskMoveVM->m_pProgress);
+ if (RT_FAILURE(vrc))
+ throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not copy the setting file '%s' to '%s' (%Rrc)"),
+ strSettingsFilePath.c_str(),
+ strTargetSettingsFilePath.stripFilename().c_str(),
+ vrc);
+
+ Log2(("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);
+
+ Utf8Str strPrevSettingsFilePath = strSettingsFilePath;
+ strPrevSettingsFilePath.append("-prev");
+ if (RTFileExists(strPrevSettingsFilePath.c_str()))
+ originalFiles.append(strPrevSettingsFilePath);
+ }
+
+ /* Moving Machine log files */
+ {
+ Log2(("Copy machine log files\n"));
+
+ if (taskMoveVM->m_vmFolders[VBox_LogFolder].isNotEmpty())
+ {
+ /* Check an original log folder existence */
+ if (RTDirExists(taskMoveVM->m_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))
+ throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not create log folder '%s' (%Rrc)"),
+ strTargetLogFolderPath.c_str(), vrc);
+
+ Log2(("Created a log machine folder %s\n", strTargetLogFolderPath.c_str()));
+ }
+
+ fileList_t filesList;
+ taskMoveVM->getFilesList(taskMoveVM->m_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. */
+ hrc = taskMoveVM->m_pProgress->SetNextOperation(BstrFmt(tr("Copying the log file '%s' ..."),
+ RTPathFilename(strFullSourceFilePath.c_str())).raw(),
+ 1);
+ if (FAILED(hrc))
+ throw hrc;
+
+ int vrc = RTFileCopyEx(strFullSourceFilePath.c_str(), strFullTargetFilePath.c_str(), 0,
+ MachineMoveVM::copyFileProgress, &taskMoveVM->m_pProgress);
+ if (RT_FAILURE(vrc))
+ throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not copy the log file '%s' to '%s' (%Rrc)"),
+ strFullSourceFilePath.c_str(),
+ strFullTargetFilePath.stripFilename().c_str(),
+ vrc);
+
+ Log2(("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 */
+ hrc = machine->SaveSettings();
+ if (FAILED(hrc))
+ throw hrc;
+
+ Log2(("Update path to XML setting file\n"));
+ Utf8Str strTargetSettingsFilePath = strTargetFolder;
+ Bstr bstrMachineName;
+ hrc = machine->COMGETTER(Name)(bstrMachineName.asOutParam());
+ if (FAILED(hrc))
+ throw hrc;
+ strTargetSettingsFilePath.append(RTPATH_DELIMITER).append(Utf8Str(bstrMachineName)).append(".vbox");
+ machineData->m_strConfigFileFull = strTargetSettingsFilePath;
+ machine->mParent->i_copyPathRelativeToConfig(strTargetSettingsFilePath, machineData->m_strConfigFile);
+
+ /* 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);
+
+ /* Save global settings in the VirtualBox.xml */
+ hrc = machine->mParent->i_saveSettings();
+ if (FAILED(hrc))
+ throw hrc;
+ }
+ catch(HRESULT aRc)
+ {
+ hrc = aRc;
+ taskMoveVM->m_result = hrc;
+ }
+ catch (...)
+ {
+ Log2(("Moving machine to a new destination was failed. Check original and destination places.\n"));
+ hrc = VirtualBoxBase::handleUnexpectedExceptions(machine, RT_SRC_POS);
+ taskMoveVM->m_result = hrc;
+ }
+
+ /* Cleanup on failure */
+ if (FAILED(hrc))
+ {
+ Machine::Data *machineData = machine->mData.data();
+
+ /* 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".
+ */
+
+ /** @todo r=vvp: Do we need to check each return result here? Looks excessively
+ * and what to do with any failure here? We are already in the rollback action.
+ * Throw only the important errors?
+ * We MUST finish this action anyway to avoid garbage and get the original VM state. */
+ /* ! Apparently we should update the Progress object !*/
+ ULONG operationCount = 0;
+ hrc = taskMoveVM->m_pProgress->COMGETTER(OperationCount)(&operationCount);
+ if (FAILED(hrc))
+ throw hrc;
+ ULONG operation = 0;
+ hrc = taskMoveVM->m_pProgress->COMGETTER(Operation)(&operation);
+ if (FAILED(hrc))
+ throw hrc;
+ Bstr bstrOperationDescription;
+ hrc = taskMoveVM->m_pProgress->COMGETTER(OperationDescription)(bstrOperationDescription.asOutParam());
+ if (FAILED(hrc))
+ throw hrc;
+ Utf8Str strOperationDescription = bstrOperationDescription;
+ ULONG operationPercent = 0;
+ hrc = taskMoveVM->m_pProgress->COMGETTER(OperationPercent)(&operationPercent);
+ if (FAILED(hrc))
+ throw hrc;
+ Bstr bstrMachineName;
+ hrc = machine->COMGETTER(Name)(bstrMachineName.asOutParam());
+ if (FAILED(hrc))
+ throw hrc;
+
+ Log2(("Moving machine %s was failed on operation %s\n",
+ Utf8Str(bstrMachineName.raw()).c_str(), Utf8Str(bstrOperationDescription.raw()).c_str()));
+
+ for (ULONG i = operation + 1; i < operationCount - operation; ++i)
+ taskMoveVM->m_pProgress->SetNextOperation(BstrFmt(tr("Skip the empty operation %d..."), i + 1).raw(), 1);
+
+ hrc = taskMoveVM->moveAllDisks(taskMoveVM->m_finalMediumsMap);
+ if (FAILED(hrc))
+ throw hrc;
+
+ /* Revert original paths to the state files */
+ taskMoveVM->updatePathsToStateFiles(strTargetFolder,
+ taskMoveVM->m_vmFolders[VBox_SettingFolder]);
+
+ /* Revert original paths to the NVRAM files */
+ taskMoveVM->updatePathsToNVRAMFiles(strTargetFolder,
+ taskMoveVM->m_vmFolders[VBox_SettingFolder]);
+
+ /* Delete all created files. Here we update progress object */
+ hrc = taskMoveVM->deleteFiles(newFiles);
+ if (FAILED(hrc))
+ {
+ Log2(("Rollback scenario: can't delete new created files. Check the destination folder.\n"));
+ throw hrc;
+ }
+
+ /* Delete destination folder */
+ int vrc = RTDirRemove(strTargetFolder.c_str());
+ if (RT_FAILURE(vrc))
+ {
+ Log2(("Rollback scenario: can't delete new destination folder.\n"));
+ throw machine->setErrorVrc(vrc, tr("Rollback scenario: can't delete new destination folder."));
+ }
+
+ /* save all VM data */
+ {
+ AutoWriteLock srcLock(machine COMMA_LOCKVAL_SRC_POS);
+ srcLock.release();
+ hrc = machine->SaveSettings();
+ if (FAILED(hrc))
+ {
+ Log2(("Rollback scenario: can't save machine settings.\n"));
+ throw hrc;
+ }
+ srcLock.acquire();
+ }
+
+ /* Restore an original path to XML setting file */
+ {
+ Log2(("Rollback scenario: restoration of the original path to XML setting file\n"));
+ Utf8Str strOriginalSettingsFilePath = taskMoveVM->m_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);
+ hrc = machine->mParent->i_saveSettings();
+ if (FAILED(hrc))
+ {
+ Log2(("Rollback scenario: can't save global settings.\n"));
+ throw hrc;
+ }
+ }
+ }
+ catch(HRESULT aRc)
+ {
+ hrc = aRc;
+ Log2(("Rollback scenario: restoration the original mediums were failed. Machine can be corrupted.\n"));
+ }
+ catch (...)
+ {
+ Log2(("Rollback scenario: restoration the original mediums were failed. Machine can be corrupted.\n"));
+ hrc = VirtualBoxBase::handleUnexpectedExceptions(machine, RT_SRC_POS);
+ }
+ /* 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->m_finalMediumsMap) to the current number of operation.
+ */
+
+ ULONG operationCount = 0;
+ hrc = taskMoveVM->m_pProgress->COMGETTER(OperationCount)(&operationCount);
+ ULONG operation = 0;
+ hrc = taskMoveVM->m_pProgress->COMGETTER(Operation)(&operation);
+
+ for (ULONG i = operation; i < operation + taskMoveVM->m_finalMediumsMap.size() - 1; ++i)
+ taskMoveVM->m_pProgress->SetNextOperation(BstrFmt(tr("Skip the empty operation %d..."), i).raw(), 1);
+
+ hrc = taskMoveVM->deleteFiles(originalFiles);
+ if (FAILED(hrc))
+ Log2(("Forward scenario: can't delete all original files.\n"));
+
+ /* delete no longer needed source directories */
+ if ( taskMoveVM->m_vmFolders[VBox_SnapshotFolder].isNotEmpty()
+ && RTDirExists(taskMoveVM->m_vmFolders[VBox_SnapshotFolder].c_str()))
+ RTDirRemove(taskMoveVM->m_vmFolders[VBox_SnapshotFolder].c_str());
+
+ if ( taskMoveVM->m_vmFolders[VBox_LogFolder].isNotEmpty()
+ && RTDirExists(taskMoveVM->m_vmFolders[VBox_LogFolder].c_str()))
+ RTDirRemove(taskMoveVM->m_vmFolders[VBox_LogFolder].c_str());
+
+ if ( taskMoveVM->m_vmFolders[VBox_SettingFolder].isNotEmpty()
+ && RTDirExists(taskMoveVM->m_vmFolders[VBox_SettingFolder].c_str()))
+ RTDirRemove(taskMoveVM->m_vmFolders[VBox_SettingFolder].c_str());
+ }
+
+ if (!taskMoveVM->m_pProgress.isNull())
+ taskMoveVM->m_pProgress->i_notifyComplete(taskMoveVM->m_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.isNotEmpty())
+ {
+ 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(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(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();
+
+ if (FAILED(rc)) throw rc;
+
+ Log2(("Moving %s has been finished\n", strTargetImageName.c_str()));
+
+ ++itMedium;
+ }
+
+ machineLock.release();
+ }
+ catch(HRESULT hrc)
+ {
+ Log2(("Exception during moving the disk %s\n", strLocation.c_str()));
+ rc = hrc;
+ machineLock.release();
+ }
+ catch (...)
+ {
+ Log2(("Exception during moving the disk %s\n", strLocation.c_str()));
+ rc = VirtualBoxBase::handleUnexpectedExceptions(m_pMachine, RT_SRC_POS);
+ machineLock.release();
+ }
+
+ return rc;
+}
+
+void MachineMoveVM::updatePathsToStateFiles(const Utf8Str &sourcePath, const Utf8Str &targetPath)
+{
+ ComObjPtr<Snapshot> pSnapshot;
+ HRESULT rc = m_pMachine->i_findSnapshotById(Guid() /* zero */, pSnapshot, true);
+ if (SUCCEEDED(rc) && !pSnapshot.isNull())
+ pSnapshot->i_updateSavedStatePaths(sourcePath.c_str(),
+ targetPath.c_str());
+ if (m_pMachine->mSSData->strStateFilePath.isNotEmpty())
+ {
+ if (RTPathStartsWith(m_pMachine->mSSData->strStateFilePath.c_str(), sourcePath.c_str()))
+ m_pMachine->mSSData->strStateFilePath = Utf8StrFmt("%s%s",
+ targetPath.c_str(),
+ m_pMachine->mSSData->strStateFilePath.c_str() + sourcePath.length());
+ else
+ m_pMachine->mSSData->strStateFilePath = Utf8StrFmt("%s%c%s",
+ targetPath.c_str(),
+ RTPATH_DELIMITER,
+ RTPathFilename(m_pMachine->mSSData->strStateFilePath.c_str()));
+ }
+}
+
+void MachineMoveVM::updatePathsToNVRAMFiles(const Utf8Str &sourcePath, const Utf8Str &targetPath)
+{
+ ComObjPtr<Snapshot> pSnapshot;
+ HRESULT rc = m_pMachine->i_findSnapshotById(Guid() /* zero */, pSnapshot, true);
+ if (SUCCEEDED(rc) && !pSnapshot.isNull())
+ pSnapshot->i_updateNVRAMPaths(sourcePath.c_str(),
+ targetPath.c_str());
+ ComObjPtr<NvramStore> pNvramStore(m_pMachine->mNvramStore);
+ const Utf8Str NVRAMFile(pNvramStore->i_getNonVolatileStorageFile());
+ if (NVRAMFile.isNotEmpty())
+ {
+ Utf8Str newNVRAMFile;
+ if (RTPathStartsWith(NVRAMFile.c_str(), sourcePath.c_str()))
+ newNVRAMFile = Utf8StrFmt("%s%s", targetPath.c_str(), NVRAMFile.c_str() + sourcePath.length());
+ else
+ newNVRAMFile = Utf8StrFmt("%s%c%s", targetPath.c_str(), RTPATH_DELIMITER, RTPathFilename(newNVRAMFile.c_str()));
+ pNvramStore->i_updateNonVolatileStorageFile(newNVRAMFile);
+ }
+}
+
+HRESULT MachineMoveVM::getFilesList(const Utf8Str &strRootFolder, fileList_t &filesList)
+{
+ RTDIR hDir;
+ HRESULT hrc = S_OK;
+ int vrc = RTDirOpen(&hDir, strRootFolder.c_str());
+ if (RT_SUCCESS(vrc))
+ {
+ /** @todo r=bird: RTDIRENTRY is big and this function is doing
+ * unrestrained recursion of arbritrary depth. Four things:
+ * - Add a depth counter parameter and refuse to go deeper than
+ * a certain reasonable limit.
+ * - Split this method into a main and a worker, placing
+ * RTDIRENTRY on the stack in the main and passing it onto to
+ * worker as a parameter.
+ * - RTDirRead may fail for reasons other than
+ * VERR_NO_MORE_FILES. For instance someone could create an
+ * entry with a name longer than RTDIRENTRY have space to
+ * store (windows host with UTF-16 encoding shorter than 255
+ * chars, but UTF-8 encoding longer than 260).
+ * - enmType can be RTDIRENTRYTYPE_UNKNOWN if the file system or
+ * the host doesn't return the information. See
+ * RTDIRENTRY::enmType. Use RTDirQueryUnknownType() to get the
+ * actual type. */
+ 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);
+ hrc = getFilesList(strNextFolder, filesList);
+ if (FAILED(hrc))
+ break;
+ }
+ }
+
+ vrc = RTDirClose(hDir);
+ AssertRC(vrc);
+ }
+ else if (vrc == VERR_FILE_NOT_FOUND)
+ hrc = m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Folder '%s' doesn't exist (%Rrc)"),
+ strRootFolder.c_str(), vrc);
+ else
+ hrc = m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not open folder '%s' (%Rrc)"),
+ strRootFolder.c_str(), vrc);
+
+ return hrc;
+}
+
+HRESULT MachineMoveVM::deleteFiles(const RTCList<Utf8Str> &listOfFiles)
+{
+ HRESULT hrc = S_OK;
+ /* Delete all created files. */
+ for (size_t i = 0; i < listOfFiles.size(); ++i)
+ {
+ Log2(("Deleting file %s ...\n", listOfFiles.at(i).c_str()));
+ hrc = m_pProgress->SetNextOperation(BstrFmt(tr("Deleting file %s..."), listOfFiles.at(i).c_str()).raw(), 1);
+ if (FAILED(hrc)) return hrc;
+
+ int vrc = RTFileDelete(listOfFiles.at(i).c_str());
+ if (RT_FAILURE(vrc))
+ return m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not delete file '%s' (%Rrc)"),
+ listOfFiles.at(i).c_str(), vrc);
+
+ else
+ Log2(("File %s has been deleted\n", listOfFiles.at(i).c_str()));
+ }
+
+ return hrc;
+}
+
+HRESULT MachineMoveVM::getFolderSize(const Utf8Str &strRootFolder, uint64_t &size)
+{
+ HRESULT hrc = S_OK;
+ int vrc = 0;
+ uint64_t totalFolderSize = 0;
+ fileList_t filesList;
+
+ bool ex = RTPathExists(strRootFolder.c_str());
+ if (ex == true)
+ {
+ hrc = getFilesList(strRootFolder, filesList);
+ if (SUCCEEDED(hrc))
+ {
+ 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 = RTFileQuerySizeByPath(fullPath.c_str(), &cbFile);
+ if (RT_SUCCESS(vrc))
+ {
+ totalFolderSize += cbFile;
+ }
+ else
+ return m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not get the size of file '%s': %Rrc"),
+ fullPath.c_str(),
+ vrc);
+
+ ++it;
+ }
+
+ size = totalFolderSize;
+ }
+ }
+ else
+ size = 0;
+
+ return hrc;
+}
+
+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)
+{
+ Bstr name;
+ HRESULT 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;
+ machineList.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]);
+ if (FAILED(rc)) return rc;
+ }
+
+ return rc;
+}
+
+HRESULT MachineMoveVM::queryMediasForAllStates()
+{
+ /* 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 < machineList.size(); ++i)
+ {
+ const ComObjPtr<Machine> &machine = machineList.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)) return 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 */
+ rc = isMediumTypeSupportedForMoving(pMedium);
+ if (FAILED(rc))
+ {
+ if (rc == S_FALSE)
+ {
+ Log2(("Skipping file %ls because of this medium type hasn't been supported for moving.\n",
+ bstrLocation.raw()));
+ continue;
+ }
+ else
+ return rc;
+ }
+
+ 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)) return rc;
+
+ rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
+ if (FAILED(rc)) return rc;
+
+ MEDIUMTASKMOVE mt;// = {false, "basename", NULL, 0, 0};
+ mt.strBaseName = bstrLocation;
+ Utf8Str const &strFolder = m_vmFolders[VBox_SnapshotFolder];
+
+ if (strFolder.isNotEmpty() && RTPathStartsWith(mt.strBaseName.c_str(), strFolder.c_str()))
+ 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;
+ }
+
+ m_llMedias.append(mtc);
+ }
+
+ /* Add the save state files of this machine if there is one. */
+ rc = addSaveState(machine);
+ if (FAILED(rc)) return rc;
+
+ /* Add the NVRAM files of this machine if there is one. */
+ rc = addNVRAM(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 < m_llMedias.size(); ++i)
+ {
+ uint32_t uIdx = 0;
+ MEDIUMTASKCHAINMOVE &mtc = m_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())
+ {
+ SNAPFILETASKMOVE sft;
+
+ sft.snapshotUuid = machine->i_getSnapshotId();
+ sft.strFile = bstrSrcSaveStatePath;
+ uint64_t cbSize;
+
+ int vrc = RTFileQuerySizeByPath(sft.strFile.c_str(), &cbSize);
+ if (RT_FAILURE(vrc))
+ return m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not get file size of '%s': %Rrc"),
+ sft.strFile.c_str(),
+ vrc);
+
+ /* same rule as above: count both the data which needs to
+ * be read and written */
+ sft.uWeight = (ULONG)(2 * (cbSize + _1M - 1) / _1M);
+ m_llSaveStateFiles.append(sft);
+ }
+ return S_OK;
+}
+
+HRESULT MachineMoveVM::addNVRAM(const ComObjPtr<Machine> &machine)
+{
+ ComPtr<INvramStore> pNvramStore;
+ HRESULT rc = machine->COMGETTER(NonVolatileStore)(pNvramStore.asOutParam());
+ if (FAILED(rc)) return rc;
+ Bstr bstrSrcNVRAMPath;
+ rc = pNvramStore->COMGETTER(NonVolatileStorageFile)(bstrSrcNVRAMPath.asOutParam());
+ if (FAILED(rc)) return rc;
+ Utf8Str strSrcNVRAMPath(bstrSrcNVRAMPath);
+ if (!strSrcNVRAMPath.isEmpty() && RTFileExists(strSrcNVRAMPath.c_str()))
+ {
+ SNAPFILETASKMOVE sft;
+
+ sft.snapshotUuid = machine->i_getSnapshotId();
+ sft.strFile = strSrcNVRAMPath;
+ uint64_t cbSize;
+
+ int vrc = RTFileQuerySizeByPath(sft.strFile.c_str(), &cbSize);
+ if (RT_FAILURE(vrc))
+ return m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not get file size of '%s': %Rrc"),
+ sft.strFile.c_str(),
+ vrc);
+
+ /* same rule as above: count both the data which needs to
+ * be read and written */
+ sft.uWeight = (ULONG)(2 * (cbSize + _1M - 1) / _1M);
+ m_llNVRAMFiles.append(sft);
+ }
+ 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);
+ }
+}
+
+HRESULT MachineMoveVM::isMediumTypeSupportedForMoving(const ComPtr<IMedium> &pMedium)
+{
+ Bstr bstrLocation;
+ HRESULT rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
+ if (FAILED(rc))
+ return rc;
+
+ DeviceType_T deviceType;
+ rc = pMedium->COMGETTER(DeviceType)(&deviceType);
+ if (FAILED(rc))
+ return rc;
+
+ ComPtr<IMediumFormat> mediumFormat;
+ rc = pMedium->COMGETTER(MediumFormat)(mediumFormat.asOutParam());
+ if (FAILED(rc))
+ return 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))
+ return rc;
+
+ Utf8Str formatName = Utf8Str(bstrFormatName);
+ if (formatName.compare("VHDX", Utf8Str::CaseInsensitive) == 0)
+ {
+ Log2(("Skipping medium %ls. VHDX format is supported in \"read-only\" mode only.\n", bstrLocation.raw()));
+ return S_FALSE;
+ }
+
+ /* Check whether medium is represented by file on the disk or not */
+ ComObjPtr<Medium> pObjMedium = (Medium *)(IMedium *)pMedium;
+ if (!pObjMedium->i_isMediumFormatFile())
+ {
+ Log2(("Skipping medium %ls because it's not a real file on the disk.\n", bstrLocation.raw()));
+ return S_FALSE;
+ }
+
+ /* some special checks for DVD */
+ if (deviceType == DeviceType_DVD)
+ {
+ Utf8Str ext = bstrLocation;
+ /* only ISO image is moved */
+ if (!ext.endsWith(".iso", Utf8Str::CaseInsensitive))
+ {
+ Log2(("Skipping file %ls. Only ISO images are supported for now.\n", bstrLocation.raw()));
+ return S_FALSE;
+ }
+ }
+
+ return S_OK;
+}