VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/MachineImplMoveVM.cpp@ 72899

Last change on this file since 72899 was 72841, checked in by vboxsync, 7 years ago

Main/MachineImplMoveVM: bugref:8345. Fixed wrong manipulation with iterator, now properly.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 61.6 KB
Line 
1/* $Id: MachineImplMoveVM.cpp 72841 2018-07-03 19:22:25Z vboxsync $ */
2/** @file
3 * Implementation of MachineMoveVM
4 */
5
6/*
7 * Copyright (C) 2011-2017 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17#include <iprt/fs.h>
18#include <iprt/dir.h>
19#include <iprt/file.h>
20#include <iprt/path.h>
21#include <iprt/cpp/utils.h>
22#include <iprt/stream.h>
23
24#include "MachineImplMoveVM.h"
25#include "MediumFormatImpl.h"
26#include "VirtualBoxImpl.h"
27#include "Logging.h"
28
29/* This variable is global and used in the different places so it must be cleared each time before usage to avoid failure */
30std::vector< ComObjPtr<Machine> > machineList;
31
32typedef std::multimap<Utf8Str, Utf8Str> list_t;
33typedef std::multimap<Utf8Str, Utf8Str>::const_iterator cit_t;
34typedef std::multimap<Utf8Str, Utf8Str>::iterator it_t;
35typedef std::pair <std::multimap<Utf8Str, Utf8Str>::iterator, std::multimap<Utf8Str, Utf8Str>::iterator> rangeRes_t;
36
37struct fileList_t
38{
39 HRESULT add(const Utf8Str& folder, const Utf8Str& file)
40 {
41 HRESULT rc = S_OK;
42 m_list.insert(std::make_pair(folder, file));
43 return rc;
44 }
45
46 HRESULT add(const Utf8Str& fullPath)
47 {
48 HRESULT rc = S_OK;
49 Utf8Str folder = fullPath;
50 folder.stripFilename();
51 Utf8Str filename = fullPath;
52 filename.stripPath();
53 m_list.insert(std::make_pair(folder, filename));
54 return rc;
55 }
56
57 HRESULT removeFileFromList(const Utf8Str& fullPath)
58 {
59 HRESULT rc = S_OK;
60 Utf8Str folder = fullPath;
61 folder.stripFilename();
62 Utf8Str filename = fullPath;
63 filename.stripPath();
64 rangeRes_t res = m_list.equal_range(folder);
65 for (it_t it=res.first; it!=res.second;)
66 {
67 if (it->second.equals(filename))
68 {
69 it_t it2 = it;
70 ++it;
71 m_list.erase(it2);
72 }
73 else
74 ++it;
75 }
76
77 return rc;
78 }
79
80 HRESULT removeFileFromList(const Utf8Str& path, const Utf8Str& fileName)
81 {
82 HRESULT rc = S_OK;
83 rangeRes_t res = m_list.equal_range(path);
84 for (it_t it=res.first; it!=res.second;)
85 {
86 if (it->second.equals(fileName))
87 {
88 it_t it2 = it;
89 ++it;
90 m_list.erase(it2);
91 }
92 else
93 ++it;
94 }
95 return rc;
96 }
97
98 HRESULT removeFolderFromList(const Utf8Str& path)
99 {
100 HRESULT rc = S_OK;
101 m_list.erase(path);
102 return rc;
103 }
104
105 rangeRes_t getFilesInRange(const Utf8Str& path)
106 {
107 rangeRes_t res;
108 res = m_list.equal_range(path);
109 return res;
110 }
111
112 std::list<Utf8Str> getFilesInList(const Utf8Str& path)
113 {
114 std::list<Utf8Str> list_;
115 rangeRes_t res = m_list.equal_range(path);
116 for (it_t it=res.first; it!=res.second; ++it)
117 list_.push_back(it->second);
118 return list_;
119 }
120
121
122 list_t m_list;
123
124};
125
126
127void MachineMoveVM::ErrorInfoItem::printItem(bool fLog) const
128{
129 if (fLog)
130 {
131 LogRelFunc(("(The error code is %Rrc): %s\n",m_code, m_description.c_str()));
132 }
133 else
134 RTPrintf("(The error code is %Rrc): %s\n",m_code, m_description.c_str());
135}
136
137HRESULT MachineMoveVM::init()
138{
139 HRESULT rc = S_OK;
140
141 errorsList.clear();
142
143 Utf8Str strTargetFolder;
144 /* adding a trailing slash if it's needed */
145 {
146 size_t len = m_targetPath.length() + 2;
147 if (len >=RTPATH_MAX)
148 {
149 Utf8Str errorDesc(" The destination path isn't correct. The length of path exceeded the maximum value.");
150 errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
151 throw m_pMachine->setError(VBOX_E_IPRT_ERROR, m_pMachine->tr(errorDesc.c_str()));
152 }
153
154 char* path = new char [len];
155 RTStrCopy(path, len, m_targetPath.c_str());
156 RTPathEnsureTrailingSeparator(path, len);
157 strTargetFolder = m_targetPath = path;
158 delete[] path;
159 }
160
161 /*
162 * We have a mode which user is able to request
163 * basic mode:
164 * - The images which are solely attached to the VM
165 * and located in the original VM folder will be moved.
166 *
167 * Comment: in the future some other modes can be added.
168 */
169
170 try
171 {
172 Utf8Str info;
173 int vrc = 0;
174
175 RTFOFF cbTotal = 0;
176 RTFOFF cbFree = 0;
177 uint32_t cbBlock = 0;
178 uint32_t cbSector = 0;
179
180 vrc = RTFsQuerySizes(strTargetFolder.c_str(), &cbTotal, &cbFree, &cbBlock, &cbSector);
181 if (FAILED(vrc))
182 {
183 RTPrintf("strTargetFolder is %s\n", strTargetFolder.c_str());
184 Utf8StrFmt errorDesc("Unable to move machine. Can't get the destination storage size (%s)",
185 strTargetFolder.c_str());
186 errorsList.push_back(ErrorInfoItem(E_FAIL, errorDesc.c_str()));
187 rc = m_pMachine->setError(E_FAIL, m_pMachine->tr(errorDesc.c_str()));
188 throw rc;
189 }
190
191 RTDIR hDir;
192 Utf8Str strTempFile = strTargetFolder + "test.txt";
193 vrc = RTDirOpen(&hDir, strTargetFolder.c_str());
194 if (FAILED(vrc))
195 throw rc = vrc;
196 else
197 {
198 RTFILE hFile;
199 vrc = RTFileOpen(&hFile, strTempFile.c_str(), RTFILE_O_OPEN_CREATE | RTFILE_O_READWRITE | RTFILE_O_DENY_NONE);
200 if (FAILED(vrc))
201 {
202 LogRelFunc(("Can't create a test file %s (The error is %Rrc)\n", strTempFile.c_str(), vrc));
203 Utf8StrFmt errorDesc("Can't create a test file test.txt in the %s. Check the access rights of "
204 "the destination folder.", strTargetFolder.c_str());
205 errorsList.push_back(ErrorInfoItem(HRESULT(vrc), errorDesc.c_str()));
206 rc = m_pMachine->setError(vrc, m_pMachine->tr(errorDesc.c_str()));
207
208 }
209
210 RTFileClose(hFile);
211 RTFileDelete(strTempFile.c_str());
212 RTDirClose(hDir);
213 }
214
215 long long totalFreeSpace = cbFree;
216 long long totalSpace = cbTotal;
217 info = Utf8StrFmt("blocks: total %lld, free %u ", cbTotal, cbFree);
218 LogRelFunc(("%s \n", info.c_str()));
219 LogRelFunc(("total space (Kb) %lld (Mb) %lld (Gb) %lld\n",
220 totalSpace/1024, totalSpace/(1024*1024), totalSpace/(1024*1024*1024)));
221 LogRelFunc(("total free space (Kb) %lld (Mb) %lld (Gb) %lld\n",
222 totalFreeSpace/1024, totalFreeSpace/(1024*1024), totalFreeSpace/(1024*1024*1024)));
223
224 RTFSPROPERTIES properties;
225 vrc = RTFsQueryProperties(strTargetFolder.c_str(), &properties);
226 if (FAILED(vrc)) throw vrc;
227 info = Utf8StrFmt("disk properties:\n"
228 "remote: %s \n"
229 "read only: %s \n"
230 "compressed: %s \n",
231 properties.fRemote == true ? "true":"false",
232 properties.fReadOnly == true ? "true":"false",
233 properties.fCompressed == true ? "true":"false");
234
235 LogRelFunc(("%s \n", info.c_str()));
236
237 /* Get the original VM path */
238 Utf8Str strSettingsFilePath;
239 Bstr bstr_settingsFilePath;
240 m_pMachine->COMGETTER(SettingsFilePath)(bstr_settingsFilePath.asOutParam());
241 strSettingsFilePath = bstr_settingsFilePath;
242 strSettingsFilePath.stripFilename();
243
244 vmFolders.insert(std::make_pair(VBox_SettingFolder, strSettingsFilePath));
245
246 /* Collect all files from the VM's folder */
247 fileList_t fullFileList;
248 rc = getFilesList(strSettingsFilePath, fullFileList);
249 if (FAILED(rc)) throw rc;
250
251 /*
252 * Collect all known folders used by the VM:
253 * - log folder;
254 * - state folder;
255 * - snapshot folder.
256 */
257 Utf8Str strLogFolder;
258 Bstr bstr_logFolder;
259 m_pMachine->COMGETTER(LogFolder)(bstr_logFolder.asOutParam());
260 strLogFolder = bstr_logFolder;
261 if ( m_type.equals("basic")
262 && strLogFolder.contains(strSettingsFilePath))
263 {
264 vmFolders.insert(std::make_pair(VBox_LogFolder, strLogFolder));
265 }
266
267 Utf8Str strStateFilePath;
268 Bstr bstr_stateFilePath;
269 MachineState_T machineState;
270 rc = m_pMachine->COMGETTER(State)(&machineState);
271 if (FAILED(rc)) throw rc;
272 else if (machineState == MachineState_Saved)
273 {
274 m_pMachine->COMGETTER(StateFilePath)(bstr_stateFilePath.asOutParam());
275 strStateFilePath = bstr_stateFilePath;
276 strStateFilePath.stripFilename();
277 if ( m_type.equals("basic")
278 && strStateFilePath.contains(strSettingsFilePath))
279 vmFolders.insert(std::make_pair(VBox_StateFolder, strStateFilePath));//Utf8Str(bstr_stateFilePath)));
280 }
281
282 Utf8Str strSnapshotFolder;
283 Bstr bstr_snapshotFolder;
284 m_pMachine->COMGETTER(SnapshotFolder)(bstr_snapshotFolder.asOutParam());
285 strSnapshotFolder = bstr_snapshotFolder;
286 if ( m_type.equals("basic")
287 && strSnapshotFolder.contains(strSettingsFilePath))
288 vmFolders.insert(std::make_pair(VBox_SnapshotFolder, strSnapshotFolder));
289
290 if (m_pMachine->i_isSnapshotMachine())
291 {
292 Bstr bstrSrcMachineId;
293 rc = m_pMachine->COMGETTER(Id)(bstrSrcMachineId.asOutParam());
294 if (FAILED(rc)) throw rc;
295 ComPtr<IMachine> newSrcMachine;
296 rc = m_pMachine->i_getVirtualBox()->FindMachine(bstrSrcMachineId.raw(), newSrcMachine.asOutParam());
297 if (FAILED(rc)) throw rc;
298 }
299
300 /* Add the current machine and all snapshot machines below this machine
301 * in a list for further processing.
302 */
303
304 long long neededFreeSpace = 0;
305
306 /* Actual file list */
307 fileList_t actualFileList;
308 Utf8Str strTargetImageName;
309
310 /* Global variable (defined at the beginning of file), so clear it before usage */
311 machineList.clear();
312 machineList.push_back(m_pMachine);
313
314 {
315 ULONG cSnapshots = 0;
316 rc = m_pMachine->COMGETTER(SnapshotCount)(&cSnapshots);
317 if (FAILED(rc)) throw rc;
318 if (cSnapshots > 0)
319 {
320 Utf8Str id;
321 if (m_pMachine->i_isSnapshotMachine())
322 id = m_pMachine->i_getSnapshotId().toString();
323 ComPtr<ISnapshot> pSnapshot;
324 rc = m_pMachine->FindSnapshot(Bstr(id).raw(), pSnapshot.asOutParam());
325 if (FAILED(rc)) throw rc;
326 rc = createMachineList(pSnapshot, machineList);
327 if (FAILED(rc)) throw rc;
328 }
329 }
330
331 ULONG uCount = 1;//looks like it should be initialized by 1. See assertion in the Progress::setNextOperation()
332 ULONG uTotalWeight = 1;
333
334 /* The lists llMedias and llSaveStateFiles are filled in the queryMediasForAllStates() */
335 queryMediasForAllStates(machineList);
336
337 {
338 uint64_t totalMediumsSize = 0;
339
340 for (size_t i = 0; i < llMedias.size(); ++i)
341 {
342 LONG64 cbSize = 0;
343 MEDIUMTASKCHAINMOVE &mtc = llMedias.at(i);
344 for (size_t a = mtc.chain.size(); a > 0; --a)
345 {
346 Bstr bstrLocation;
347 Utf8Str strLocation;
348 Utf8Str name = mtc.chain[a - 1].strBaseName;
349 ComPtr<IMedium> plMedium = mtc.chain[a - 1].pMedium;
350 rc = plMedium->COMGETTER(Location)(bstrLocation.asOutParam());
351 if (FAILED(rc)) throw rc;
352 strLocation = bstrLocation;
353
354 /*if an image is located in the actual VM folder it will be added to the actual list */
355 if (strLocation.contains(strSettingsFilePath))
356 {
357 rc = plMedium->COMGETTER(Size)(&cbSize);
358 if (FAILED(rc)) throw rc;
359
360 std::pair<std::map<Utf8Str, MEDIUMTASKMOVE>::iterator,bool> ret;
361 ret = finalMediumsMap.insert(std::make_pair(name, mtc.chain[a - 1]));
362 if (ret.second == true)
363 {
364 /* Calculate progress data */
365 ++uCount;
366 uTotalWeight += mtc.chain[a - 1].uWeight;
367 totalMediumsSize += cbSize;
368 LogRelFunc(("Image %s was added into the moved list\n", name.c_str()));
369 }
370 }
371 }
372 }
373
374 LogRelFunc(("Total Size of images is %lld bytes\n", totalMediumsSize));
375 neededFreeSpace += totalMediumsSize;
376 }
377
378 /* Prepare data for moving ".sav" files */
379 {
380 uint64_t totalStateSize = 0;
381
382 for (size_t i = 0; i < llSaveStateFiles.size(); ++i)
383 {
384 uint64_t cbFile = 0;
385 SAVESTATETASKMOVE &sst = llSaveStateFiles.at(i);
386
387 Utf8Str name = sst.strSaveStateFile;
388 /*if a state file is located in the actual VM folder it will be added to the actual list */
389 if (name.contains(strSettingsFilePath))
390 {
391 vrc = RTFileQuerySize(name.c_str(), &cbFile);
392 if (RT_SUCCESS(vrc))
393 {
394 std::pair<std::map<Utf8Str, SAVESTATETASKMOVE>::iterator,bool> ret;
395 ret = finalSaveStateFilesMap.insert(std::make_pair(name, sst));
396 if (ret.second == true)
397 {
398 totalStateSize += cbFile;
399 ++uCount;
400 uTotalWeight += sst.uWeight;
401 LogRelFunc(("The state file %s was added into the moved list\n", name.c_str()));
402 }
403 }
404 else
405 LogRelFunc(("The state file %s wasn't added into the moved list. Couldn't get the file size.\n",
406 name.c_str()));
407 }
408 }
409
410 neededFreeSpace += totalStateSize;
411 }
412
413 /* Prepare data for moving the log files */
414 {
415 Utf8Str strFolder = vmFolders[VBox_LogFolder];
416
417 if (RTPathExists(strFolder.c_str()))
418 {
419 uint64_t totalLogSize = 0;
420 rc = getFolderSize(strFolder, totalLogSize);
421 if (SUCCEEDED(rc))
422 {
423 neededFreeSpace += totalLogSize;
424 if (totalFreeSpace - neededFreeSpace <= 1024*1024)
425 {
426 throw VERR_OUT_OF_RESOURCES;//less than 1Mb free space on the target location
427 }
428
429 fileList_t filesList;
430 getFilesList(strFolder, filesList);
431 cit_t it = filesList.m_list.begin();
432 while(it != filesList.m_list.end())
433 {
434 Utf8Str strFile = it->first.c_str();
435 strFile.append(RTPATH_DELIMITER).append(it->second.c_str());
436
437 uint64_t cbFile = 0;
438 vrc = RTFileQuerySize(strFile.c_str(), &cbFile);
439 if (RT_SUCCESS(vrc))
440 {
441 uCount += 1;
442 uTotalWeight += (ULONG)((cbFile + _1M - 1) / _1M);
443 actualFileList.add(strFile);
444 LogRelFunc(("The log file %s added into the moved list\n", strFile.c_str()));
445 }
446 else
447 LogRelFunc(("The log file %s wasn't added into the moved list. Couldn't get the file size."
448 "\n", strFile.c_str()));
449 ++it;
450 }
451 }
452 }
453 else
454 {
455 LogRelFunc(("Information: The original log folder %s doesn't exist\n", strFolder.c_str()));
456 rc = S_OK;//it's not error in this case if there isn't an original log folder
457 }
458 }
459
460 LogRelFunc(("Total space needed is %lld bytes\n", neededFreeSpace));
461 /* Check a target location on enough room */
462 if (totalFreeSpace - neededFreeSpace <= 1024*1024)
463 {
464 LogRelFunc(("but free space on destination is %lld\n", totalFreeSpace));
465 throw VERR_OUT_OF_RESOURCES;//less than 1Mb free space on the target location
466 }
467
468 /* Add step for .vbox machine setting file */
469 {
470 ++uCount;
471 uTotalWeight += 1;
472 }
473
474 /* Reserve additional steps in case of failure and rollback all changes */
475 {
476 uTotalWeight += uCount;//just add 1 for each possible rollback operation
477 uCount += uCount;//and increase the steps twice
478 }
479
480 /* Init Progress instance */
481 {
482 rc = m_pProgress->init(m_pMachine->i_getVirtualBox(),
483 static_cast<IMachine*>(m_pMachine) /* aInitiator */,
484 Bstr(m_pMachine->tr("Moving Machine")).raw(),
485 true /* fCancellable */,
486 uCount,
487 uTotalWeight,
488 Bstr(m_pMachine->tr("Initialize Moving")).raw(),
489 1);
490 if (FAILED(rc))
491 {
492 Utf8StrFmt errorDesc("Couldn't correctly setup the progress object for moving VM operation (%Rrc)", rc);
493 errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
494
495 throw m_pMachine->setError(VBOX_E_IPRT_ERROR,m_pMachine->tr(errorDesc.c_str()));
496 }
497 }
498
499 /* save all VM data */
500 m_pMachine->i_setModified(Machine::IsModified_MachineData);
501 rc = m_pMachine->SaveSettings();
502 }
503 catch(HRESULT hrc)
504 {
505 rc = hrc;
506 }
507
508 LogFlowFuncLeave();
509
510 return rc;
511}
512
513void MachineMoveVM::printStateFile(settings::SnapshotsList &snl)
514{
515 settings::SnapshotsList::iterator it;
516 for (it = snl.begin(); it != snl.end(); ++it)
517 {
518 if (!it->strStateFile.isEmpty())
519 {
520 settings::Snapshot snap = (settings::Snapshot)(*it);
521 LogRelFunc(("snap.uuid = %s\n", snap.uuid.toStringCurly().c_str()));
522 LogRelFunc(("snap.strStateFile = %s\n", snap.strStateFile.c_str()));
523 }
524
525 if (!it->llChildSnapshots.empty())
526 printStateFile(it->llChildSnapshots);
527 }
528}
529
530/* static */
531DECLCALLBACK(int) MachineMoveVM::updateProgress(unsigned uPercent, void *pvUser)
532{
533 MachineMoveVM* pTask = *(MachineMoveVM**)pvUser;
534
535 if ( pTask
536 && !pTask->m_pProgress.isNull())
537 {
538 BOOL fCanceled;
539 pTask->m_pProgress->COMGETTER(Canceled)(&fCanceled);
540 if (fCanceled)
541 return -1;
542 pTask->m_pProgress->SetCurrentOperationProgress(uPercent);
543 }
544 return VINF_SUCCESS;
545}
546
547/* static */
548DECLCALLBACK(int) MachineMoveVM::copyFileProgress(unsigned uPercentage, void *pvUser)
549{
550 ComObjPtr<Progress> pProgress = *static_cast< ComObjPtr<Progress>* >(pvUser);
551
552 BOOL fCanceled = false;
553 HRESULT rc = pProgress->COMGETTER(Canceled)(&fCanceled);
554 if (FAILED(rc)) return VERR_GENERAL_FAILURE;
555 /* If canceled by the user tell it to the copy operation. */
556 if (fCanceled) return VERR_CANCELLED;
557 /* Set the new process. */
558 rc = pProgress->SetCurrentOperationProgress(uPercentage);
559 if (FAILED(rc)) return VERR_GENERAL_FAILURE;
560
561 return VINF_SUCCESS;
562}
563
564
565/* static */
566void MachineMoveVM::i_MoveVMThreadTask(MachineMoveVM* task)
567{
568 LogFlowFuncEnter();
569 HRESULT rc = S_OK;
570
571 MachineMoveVM* taskMoveVM = task;
572 ComObjPtr<Machine> &machine = taskMoveVM->m_pMachine;
573
574 AutoCaller autoCaller(machine);
575// if (FAILED(autoCaller.rc())) return;//Should we return something here?
576
577 Utf8Str strTargetFolder = taskMoveVM->m_targetPath;
578 {
579 Bstr bstrMachineName;
580 machine->COMGETTER(Name)(bstrMachineName.asOutParam());
581 strTargetFolder.append(Utf8Str(bstrMachineName));
582 }
583
584 RTCList<Utf8Str> newFiles; /* All extra created files (save states, ...) */
585 RTCList<Utf8Str> originalFiles; /* All original files except images */
586 typedef std::map<Utf8Str, ComObjPtr<Medium> > MediumMap;
587 MediumMap mapOriginalMedium;
588
589 /*
590 * We have the couple modes which user is able to request
591 * basic mode:
592 * - The images which are solely attached to the VM
593 * and located in the original VM folder will be moved.
594 * All subfolders related to the original VM are also moved from the original location
595 * (Standard - snapshots and logs folders).
596 *
597 * canonical mode:
598 * - All disks tied with the VM will be moved into a new location if it's possible.
599 * All folders related to the original VM are also moved.
600 * This mode is intended to collect all files/images/snapshots related to the VM in the one place.
601 *
602 */
603
604 /*
605 * A way to handle shareable disk:
606 * Collect the shareable disks attched to the VM.
607 * Get the machines whom the shareable disks attach to.
608 * Return an error if the state of any VM doesn't allow to move a shareable disk and
609 * this disk is located in the VM's folder (it means the disk is intended for "moving").
610 */
611
612
613 /*
614 * Check new destination whether enough room for the VM or not. if "not" return an error.
615 * Make a copy of VM settings and a list with all files which are moved. Save the list on the disk.
616 * Start "move" operation.
617 * Check the result of operation.
618 * if the operation was successful:
619 * - delete all files in the original VM folder;
620 * - update VM disks info with new location;
621 * - update all other VM if it's needed;
622 * - update global settings
623 */
624
625 try
626 {
627 /* Move all disks */
628 rc = taskMoveVM->moveAllDisks(taskMoveVM->finalMediumsMap, &strTargetFolder);
629 if (FAILED(rc))
630 throw rc;
631
632 /* Get Machine::Data here because moveAllDisks() change it */
633 Machine::Data *machineData = machine->mData.data();
634 settings::MachineConfigFile *machineConfFile = machineData->pMachineConfigFile;
635
636 /* Copy all save state files. */
637 Utf8Str strTrgSnapshotFolder;
638 {
639 /* When the current snapshot folder is absolute we reset it to the
640 * default relative folder. */
641 if (RTPathStartsWithRoot((*machineConfFile).machineUserData.strSnapshotFolder.c_str()))
642 (*machineConfFile).machineUserData.strSnapshotFolder = "Snapshots";
643 (*machineConfFile).strStateFile = "";
644
645 /* The absolute name of the snapshot folder. */
646 strTrgSnapshotFolder = Utf8StrFmt("%s%c%s", strTargetFolder.c_str(), RTPATH_DELIMITER,
647 (*machineConfFile).machineUserData.strSnapshotFolder.c_str());
648
649 /* Check if a snapshot folder is necessary and if so doesn't already
650 * exists. */
651 if ( taskMoveVM->finalSaveStateFilesMap.size() != 0
652 && !RTDirExists(strTrgSnapshotFolder.c_str()))
653 {
654 int vrc = RTDirCreateFullPath(strTrgSnapshotFolder.c_str(), 0700);
655 if (RT_FAILURE(vrc))
656 {
657 Utf8StrFmt errorDesc("Could not create snapshots folder '%s' (%Rrc)", strTrgSnapshotFolder.c_str(), vrc);
658 taskMoveVM->errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
659
660 throw machine->setError(VBOX_E_IPRT_ERROR,machine->tr(errorDesc.c_str()));
661 }
662 }
663
664 std::map<Utf8Str, SAVESTATETASKMOVE>::iterator itState = taskMoveVM->finalSaveStateFilesMap.begin();
665 while (itState != taskMoveVM->finalSaveStateFilesMap.end())
666 {
667 const SAVESTATETASKMOVE &sst = itState->second;
668 const Utf8Str &strTrgSaveState = Utf8StrFmt("%s%c%s", strTrgSnapshotFolder.c_str(), RTPATH_DELIMITER,
669 RTPathFilename(sst.strSaveStateFile.c_str()));
670
671 /* Move to next sub-operation. */
672 rc = taskMoveVM->m_pProgress->SetNextOperation(BstrFmt(machine->tr("Copy the save state file '%s' ..."),
673 RTPathFilename(sst.strSaveStateFile.c_str())).raw(), sst.uWeight);
674 if (FAILED(rc)) throw rc;
675
676 int vrc = RTFileCopyEx(sst.strSaveStateFile.c_str(), strTrgSaveState.c_str(), 0,
677 MachineMoveVM::copyFileProgress, &taskMoveVM->m_pProgress);
678 if (RT_FAILURE(vrc))
679 {
680 Utf8StrFmt errorDesc("Could not copy state file '%s' to '%s' (%Rrc)",
681 sst.strSaveStateFile.c_str(), strTrgSaveState.c_str(), vrc);
682 taskMoveVM->errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
683
684 throw machine->setError(VBOX_E_IPRT_ERROR,machine->tr(errorDesc.c_str()));
685 }
686
687 /* save new file in case of restoring */
688 newFiles.append(strTrgSaveState);
689 /* save original file for deletion in the end */
690 originalFiles.append(sst.strSaveStateFile);
691 ++itState;
692 }
693 }
694
695 /*
696 * Update state file path
697 * very important step!
698 * Not obvious how to do it correctly.
699 */
700 {
701 LogRelFunc(("Update state file path\n"));
702 rc = taskMoveVM->updatePathsToStateFiles(taskMoveVM->finalSaveStateFilesMap,
703 taskMoveVM->vmFolders[VBox_SettingFolder],
704 strTargetFolder);
705 if (FAILED(rc))
706 throw rc;
707 }
708
709 /*
710 * Moving Machine settings file
711 * The settings file are moved after all disks and snapshots because this file should be updated
712 * with actual information and only then should be moved.
713 */
714 {
715 LogRelFunc(("Copy Machine settings file \n"));
716
717 rc = taskMoveVM->m_pProgress->SetNextOperation(BstrFmt(machine->tr("Copy Machine settings file '%s' ..."),
718 (*machineConfFile).machineUserData.strName.c_str()).raw(), 1);
719 if (FAILED(rc)) throw rc;
720
721 Utf8Str strTargetSettingsFilePath = strTargetFolder;
722
723 /* Check a folder existing and create one if it's not */
724 if (!RTDirExists(strTargetSettingsFilePath.c_str()))
725 {
726 int vrc = RTDirCreateFullPath(strTargetSettingsFilePath.c_str(), 0700);
727 if (RT_FAILURE(vrc))
728 {
729 Utf8StrFmt errorDesc("Could not create a home machine folder '%s' (%Rrc)",
730 strTargetSettingsFilePath.c_str(), vrc);
731 taskMoveVM->errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
732
733 throw machine->setError(VBOX_E_IPRT_ERROR,machine->tr(errorDesc.c_str()));
734 }
735 LogRelFunc(("Created a home machine folder %s\n", strTargetSettingsFilePath.c_str()));
736 }
737
738 /* Create a full path */
739 Bstr bstrMachineName;
740 machine->COMGETTER(Name)(bstrMachineName.asOutParam());
741 strTargetSettingsFilePath.append(RTPATH_DELIMITER).append(Utf8Str(bstrMachineName));
742 strTargetSettingsFilePath.append(".vbox");
743
744 Utf8Str strSettingsFilePath;
745 Bstr bstr_settingsFilePath;
746 machine->COMGETTER(SettingsFilePath)(bstr_settingsFilePath.asOutParam());
747 strSettingsFilePath = bstr_settingsFilePath;
748
749 int vrc = RTFileCopyEx(strSettingsFilePath.c_str(), strTargetSettingsFilePath.c_str(), 0,
750 MachineMoveVM::copyFileProgress, &taskMoveVM->m_pProgress);
751 if (RT_FAILURE(vrc))
752 {
753 Utf8StrFmt errorDesc("Could not copy the setting file '%s' to '%s' (%Rrc)",
754 strSettingsFilePath.c_str(), strTargetSettingsFilePath.stripFilename().c_str(), vrc);
755 taskMoveVM->errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
756
757 throw machine->setError(VBOX_E_IPRT_ERROR, machine->tr(errorDesc.c_str()));
758 }
759
760 LogRelFunc(("The setting file %s has been copied into the folder %s\n", strSettingsFilePath.c_str(),
761 strTargetSettingsFilePath.stripFilename().c_str()));
762
763 /* save new file in case of restoring */
764 newFiles.append(strTargetSettingsFilePath);
765 /* save original file for deletion in the end */
766 originalFiles.append(strSettingsFilePath);
767 }
768
769 /* Moving Machine log files */
770 {
771 LogRelFunc(("Copy machine log files \n"));
772
773 if (taskMoveVM->vmFolders[VBox_LogFolder].isNotEmpty())
774 {
775 /* Check an original log folder existence */
776 if (RTDirExists(taskMoveVM->vmFolders[VBox_LogFolder].c_str()))
777 {
778 Utf8Str strTargetLogFolderPath = strTargetFolder;
779 strTargetLogFolderPath.append(RTPATH_DELIMITER).append("Logs");
780
781 /* Check a destination log folder existence and create one if it's not */
782 if (!RTDirExists(strTargetLogFolderPath.c_str()))
783 {
784 int vrc = RTDirCreateFullPath(strTargetLogFolderPath.c_str(), 0700);
785 if (RT_FAILURE(vrc))
786 {
787 Utf8StrFmt errorDesc("Could not create log folder '%s' (%Rrc)",
788 strTargetLogFolderPath.c_str(), vrc);
789 taskMoveVM->errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
790
791 throw machine->setError(VBOX_E_IPRT_ERROR,machine->tr(errorDesc.c_str()));
792 }
793 LogRelFunc(("Created a log machine folder %s\n", strTargetLogFolderPath.c_str()));
794 }
795
796 fileList_t filesList;
797 taskMoveVM->getFilesList(taskMoveVM->vmFolders[VBox_LogFolder], filesList);
798 cit_t it = filesList.m_list.begin();
799 while(it != filesList.m_list.end())
800 {
801 Utf8Str strFullSourceFilePath = it->first.c_str();
802 strFullSourceFilePath.append(RTPATH_DELIMITER).append(it->second.c_str());
803
804 Utf8Str strFullTargetFilePath = strTargetLogFolderPath;
805 strFullTargetFilePath.append(RTPATH_DELIMITER).append(it->second.c_str());
806
807 /* Move to next sub-operation. */
808 rc = taskMoveVM->m_pProgress->SetNextOperation(BstrFmt(machine->tr("Copying the log file '%s' ..."),
809 RTPathFilename(strFullSourceFilePath.c_str())).raw(), 1);
810 if (FAILED(rc)) throw rc;
811
812 int vrc = RTFileCopyEx(strFullSourceFilePath.c_str(), strFullTargetFilePath.c_str(), 0,
813 MachineMoveVM::copyFileProgress, &taskMoveVM->m_pProgress);
814 if (RT_FAILURE(vrc))
815 {
816 Utf8StrFmt errorDesc("Could not copy the log file '%s' to '%s' (%Rrc)",
817 strFullSourceFilePath.c_str(),
818 strFullTargetFilePath.stripFilename().c_str(),
819 vrc);
820 taskMoveVM->errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
821
822 throw machine->setError(VBOX_E_IPRT_ERROR,machine->tr(errorDesc.c_str()));
823 }
824
825 LogRelFunc(("The log file %s has been copied into the folder %s\n", strFullSourceFilePath.c_str(),
826 strFullTargetFilePath.stripFilename().c_str()));
827
828 /* save new file in case of restoring */
829 newFiles.append(strFullTargetFilePath);
830 /* save original file for deletion in the end */
831 originalFiles.append(strFullSourceFilePath);
832
833 ++it;
834 }
835 }
836 }
837 }
838
839 /* save all VM data */
840 {
841 rc = machine->SaveSettings();
842 }
843
844 {
845 LogRelFunc(("Update path to XML setting file\n"));
846 Utf8Str strTargetSettingsFilePath = strTargetFolder;
847 Bstr bstrMachineName;
848 machine->COMGETTER(Name)(bstrMachineName.asOutParam());
849 strTargetSettingsFilePath.append(RTPATH_DELIMITER).append(Utf8Str(bstrMachineName)).append(".vbox");
850 machineData->m_strConfigFileFull = strTargetSettingsFilePath;
851 machine->mParent->i_copyPathRelativeToConfig(strTargetSettingsFilePath, machineData->m_strConfigFile);
852 }
853
854 /* Save global settings in the VirtualBox.xml */
855 {
856 /* Marks the global registry for uuid as modified */
857 Guid uuid = machine->mData->mUuid;
858 machine->mParent->i_markRegistryModified(uuid);
859
860 /* for saving the global settings we should hold only the VirtualBox lock */
861 AutoWriteLock vboxLock(machine->mParent COMMA_LOCKVAL_SRC_POS);
862
863 rc = machine->mParent->i_saveSettings();
864 }
865 }
866 catch(HRESULT hrc)
867 {
868 LogRelFunc(("Moving machine to a new destination was failed. Check original and destination places.\n"));
869 rc = hrc;
870 taskMoveVM->result = rc;
871 }
872 catch (...)
873 {
874 LogRelFunc(("Moving machine to a new destination was failed. Check original and destination places.\n"));
875 rc = VirtualBoxBase::handleUnexpectedExceptions(machine, RT_SRC_POS);
876 taskMoveVM->result = rc;
877 }
878
879 /* Cleanup on failure */
880 if (FAILED(rc))
881 {
882 Machine::Data *machineData = machine->mData.data();
883
884 /* ! Apparently we should update the Progress object !*/
885 ULONG operationCount = 0;
886 rc = taskMoveVM->m_pProgress->COMGETTER(OperationCount)(&operationCount);
887 ULONG operation = 0;
888 rc = taskMoveVM->m_pProgress->COMGETTER(Operation)(&operation);
889 Bstr bstrOperationDescription;
890 rc = taskMoveVM->m_pProgress->COMGETTER(OperationDescription)(bstrOperationDescription.asOutParam());
891 Utf8Str strOperationDescription = bstrOperationDescription;
892 ULONG operationPercent = 0;
893 rc = taskMoveVM->m_pProgress->COMGETTER(OperationPercent)(&operationPercent);
894
895 Bstr bstrMachineName;
896 machine->COMGETTER(Name)(bstrMachineName.asOutParam());
897 LogRelFunc(("Moving machine %s was failed on operation %s\n",
898 Utf8Str(bstrMachineName.raw()).c_str(), Utf8Str(bstrOperationDescription.raw()).c_str()));
899
900 /* Restoring the original mediums */
901 try
902 {
903 /*
904 * Fix the progress counter
905 * In instance, the whole "move vm" operation is failed on 5th step. But total count is 20.
906 * Where 20 = 2 * 10 operations, where 10 is the real number of operations. And this value was doubled
907 * earlier in the init() exactly for one reason - rollback operation. Because in this case we must do
908 * the same operations but in backward direction.
909 * Thus now we want to correct the progress counter from 5 to 15. Why?
910 * Because we should have evaluated the counter as "20/2 + (20/2 - 5)" = 15 or just "20 - 5" = 15
911 * And because the 5th step failed it shouldn't be counted.
912 * As result, we need to rollback 4 operations.
913 * Thus we start from "operation + 1" and finish when "i < operationCount - operation".
914 */
915 for (ULONG i = operation + 1; i < operationCount - operation; ++i)
916 {
917 rc = taskMoveVM->m_pProgress->SetNextOperation(BstrFmt("Skip the empty operation %d...", i + 1).raw(), 1);
918 if (FAILED(rc)) throw rc;
919 }
920
921 rc = taskMoveVM->moveAllDisks(taskMoveVM->finalMediumsMap);
922 if (FAILED(rc))
923 throw rc;
924 }
925 catch(HRESULT hrc)
926 {
927 LogRelFunc(("Rollback scenario: restoration the original mediums were failed. Machine can be corrupted.\n"));
928 taskMoveVM->result = hrc;
929 }
930 catch (...)
931 {
932 LogRelFunc(("Rollback scenario: restoration the original mediums were failed. Machine can be corrupted.\n"));
933 rc = VirtualBoxBase::handleUnexpectedExceptions(machine, RT_SRC_POS);
934 taskMoveVM->result = rc;
935 }
936
937 /* Revert original paths to the state files */
938 rc = taskMoveVM->updatePathsToStateFiles(taskMoveVM->finalSaveStateFilesMap,
939 strTargetFolder,
940 taskMoveVM->vmFolders[VBox_SettingFolder]);
941 if (FAILED(rc))
942 {
943 LogRelFunc(("Rollback scenario: can't restore the original paths to the state files. "
944 "Machine settings %s can be corrupted.\n", machineData->m_strConfigFileFull.c_str()));
945 }
946
947 /* Delete all created files. Here we update progress object */
948 rc = taskMoveVM->deleteFiles(newFiles);
949 if (FAILED(rc))
950 LogRelFunc(("Rollback scenario: can't delete new created files. Check the destination folder."));
951
952 /* Delete destination folder */
953 RTDirRemove(strTargetFolder.c_str());
954
955 /* save all VM data */
956 {
957 AutoWriteLock srcLock(machine COMMA_LOCKVAL_SRC_POS);
958 srcLock.release();
959 rc = machine->SaveSettings();
960 srcLock.acquire();
961 }
962
963 /* Restore an original path to XML setting file */
964 {
965 LogRelFunc(("Rollback scenario: restoration of the original path to XML setting file\n"));
966 Utf8Str strOriginalSettingsFilePath = taskMoveVM->vmFolders[VBox_SettingFolder];
967 strOriginalSettingsFilePath.append(RTPATH_DELIMITER).append(Utf8Str(bstrMachineName)).append(".vbox");
968 machineData->m_strConfigFileFull = strOriginalSettingsFilePath;
969 machine->mParent->i_copyPathRelativeToConfig(strOriginalSettingsFilePath, machineData->m_strConfigFile);
970 }
971
972 /* Marks the global registry for uuid as modified */
973 {
974 AutoWriteLock srcLock(machine COMMA_LOCKVAL_SRC_POS);
975 srcLock.release();
976 Guid uuid = machine->mData->mUuid;
977 machine->mParent->i_markRegistryModified(uuid);
978 srcLock.acquire();
979 }
980
981 /* save the global settings; for that we should hold only the VirtualBox lock */
982 {
983 AutoWriteLock vboxLock(machine->mParent COMMA_LOCKVAL_SRC_POS);
984 rc = machine->mParent->i_saveSettings();
985 }
986
987 /* In case of failure the progress object on the other side (user side) get notification about operation
988 completion but the operation percentage may not be set to 100% */
989 }
990 else /*Operation was successful and now we can delete the original files like the state files, XML setting, log files */
991 {
992 /*
993 * In case of success it's not urgent to update the progress object because we call i_notifyComplete() with
994 * the success result. As result, the last number of progress operation can be not equal the number of operations
995 * because we doubled the number of operations for rollback case.
996 * But if we want to update the progress object corectly it's needed to add all medium moved by standard
997 * "move medium" logic (for us it's taskMoveVM->finalMediumsMap) to the current number of operation.
998 */
999
1000 ULONG operationCount = 0;
1001 rc = taskMoveVM->m_pProgress->COMGETTER(OperationCount)(&operationCount);
1002 ULONG operation = 0;
1003 rc = taskMoveVM->m_pProgress->COMGETTER(Operation)(&operation);
1004
1005 for (ULONG i = operation; i < operation + taskMoveVM->finalMediumsMap.size() - 1; ++i)
1006 {
1007 rc = taskMoveVM->m_pProgress->SetNextOperation(BstrFmt("Skip the empty operation %d...", i).raw(), 1);
1008 if (FAILED(rc)) throw rc;
1009 }
1010
1011 rc = taskMoveVM->deleteFiles(originalFiles);
1012 if (FAILED(rc))
1013 LogRelFunc(("Forward scenario: can't delete all original files.\n"));
1014 }
1015
1016 if (!taskMoveVM->m_pProgress.isNull())
1017 {
1018 /* Set the first error happened */
1019 if (!taskMoveVM->errorsList.empty())
1020 {
1021 ErrorInfoItem ei = taskMoveVM->errorsList.front();
1022 machine->setError(ei.m_code, machine->tr(ei.m_description.c_str()));
1023 }
1024
1025 taskMoveVM->m_pProgress->i_notifyComplete(taskMoveVM->result);
1026 }
1027
1028 LogFlowFuncLeave();
1029}
1030
1031HRESULT MachineMoveVM::moveAllDisks(const std::map<Utf8Str, MEDIUMTASKMOVE>& listOfDisks,
1032 const Utf8Str* strTargetFolder)
1033{
1034 HRESULT rc = S_OK;
1035 ComObjPtr<Machine> &machine = m_pMachine;
1036 Utf8Str strLocation;
1037
1038 AutoWriteLock machineLock(machine COMMA_LOCKVAL_SRC_POS);
1039
1040 try{
1041 std::map<Utf8Str, MEDIUMTASKMOVE>::const_iterator itMedium = listOfDisks.begin();
1042 while(itMedium != listOfDisks.end())
1043 {
1044 const MEDIUMTASKMOVE &mt = itMedium->second;
1045 ComPtr<IMedium> pMedium = mt.pMedium;
1046 Utf8Str strTargetImageName;
1047 Bstr bstrLocation;
1048 Bstr bstrSrcName;
1049
1050 rc = pMedium->COMGETTER(Name)(bstrSrcName.asOutParam());
1051 if (FAILED(rc)) throw rc;
1052
1053 if (strTargetFolder != NULL && !strTargetFolder->isEmpty())
1054 {
1055 strTargetImageName = *strTargetFolder;
1056 rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
1057 if (FAILED(rc)) throw rc;
1058 strLocation = bstrLocation;
1059
1060 if (mt.fSnapshot == true)
1061 {
1062 strLocation.stripFilename().stripPath().append(RTPATH_DELIMITER).append(Utf8Str(bstrSrcName));
1063 }
1064 else
1065 {
1066 strLocation.stripPath();
1067 }
1068
1069 strTargetImageName.append(RTPATH_DELIMITER).append(strLocation);
1070 rc = m_pProgress->SetNextOperation(BstrFmt(machine->tr("Moving medium '%ls' ..."),
1071 bstrSrcName.raw()).raw(),
1072 mt.uWeight);
1073 if (FAILED(rc)) throw rc;
1074 }
1075 else
1076 {
1077 strTargetImageName = mt.strBaseName;//Should contain full path to the image
1078 rc = m_pProgress->SetNextOperation(BstrFmt(machine->tr("Moving medium '%ls' back..."),
1079 bstrSrcName.raw()).raw(),
1080 mt.uWeight);
1081 if (FAILED(rc)) throw rc;
1082 }
1083
1084
1085
1086 /* consistency: use \ if appropriate on the platform */
1087 RTPathChangeToDosSlashes(strTargetImageName.mutableRaw(), false);
1088
1089 bstrLocation = strTargetImageName.c_str();
1090
1091 MediumType_T mediumType;//immutable, shared, passthrough
1092 rc = pMedium->COMGETTER(Type)(&mediumType);
1093 if (FAILED(rc)) throw rc;
1094
1095 DeviceType_T deviceType;//floppy, hard, DVD
1096 rc = pMedium->COMGETTER(DeviceType)(&deviceType);
1097 if (FAILED(rc)) throw rc;
1098
1099 /* Drop lock early because IMedium::SetLocation needs to get the VirtualBox one. */
1100 machineLock.release();
1101
1102 ComPtr<IProgress> moveDiskProgress;
1103 rc = pMedium->SetLocation(bstrLocation.raw(), moveDiskProgress.asOutParam());
1104 if (SUCCEEDED(rc))
1105 {
1106 /* In case of failure moveDiskProgress would be in the invalid state or not initialized at all
1107 * Call WaitForAsyncProgressCompletion only in success
1108 */
1109 /* Wait until the async process has finished. */
1110 rc = m_pProgress->WaitForAsyncProgressCompletion(moveDiskProgress);
1111 }
1112
1113 /*acquire the lock back*/
1114 machineLock.acquire();
1115
1116 if (FAILED(rc)) throw rc;
1117
1118 LogRelFunc(("Moving %s has been finished\n", strTargetImageName.c_str()));
1119
1120 /* Check the result of the async process. */
1121 LONG iRc;
1122 rc = moveDiskProgress->COMGETTER(ResultCode)(&iRc);
1123 if (FAILED(rc)) throw rc;
1124 /* If the thread of the progress object has an error, then
1125 * retrieve the error info from there, or it'll be lost. */
1126 if (FAILED(iRc))
1127 {
1128 ProgressErrorInfo pie(moveDiskProgress);
1129 Utf8Str errorDesc(pie.getText().raw());
1130 errorsList.push_back(ErrorInfoItem(pie.getResultCode(), errorDesc.c_str()));
1131
1132 throw machine->setError(ProgressErrorInfo(moveDiskProgress));
1133 }
1134
1135 ++itMedium;
1136 }
1137
1138 machineLock.release();
1139 }
1140 catch(HRESULT hrc)
1141 {
1142 LogRelFunc(("\nException during moving the disk %s\n", strLocation.c_str()));
1143 rc = hrc;
1144 machineLock.release();
1145 }
1146 catch (...)
1147 {
1148 LogRelFunc(("\nException during moving the disk %s\n", strLocation.c_str()));
1149 rc = VirtualBoxBase::handleUnexpectedExceptions(m_pMachine, RT_SRC_POS);
1150 machineLock.release();
1151 }
1152
1153 if (FAILED(rc))
1154 {
1155 Utf8StrFmt errorDesc("Exception during moving the disk %s\n", strLocation.c_str());
1156 errorsList.push_back(ErrorInfoItem(rc, errorDesc.c_str()));
1157 }
1158
1159 return rc;
1160}
1161
1162HRESULT MachineMoveVM::updatePathsToStateFiles(const std::map<Utf8Str, SAVESTATETASKMOVE>& listOfFiles,
1163 const Utf8Str& sourcePath, const Utf8Str& targetPath)
1164{
1165 HRESULT rc = S_OK;
1166
1167 std::map<Utf8Str, SAVESTATETASKMOVE>::const_iterator itState = listOfFiles.begin();
1168 while (itState != listOfFiles.end())
1169 {
1170 const SAVESTATETASKMOVE &sst = itState->second;
1171
1172 if (sst.snapshotUuid != Guid::Empty)
1173 {
1174 Utf8Str strGuidMachine = sst.snapshotUuid.toString();
1175 ComObjPtr<Snapshot> snapshotMachineObj;
1176
1177 rc = m_pMachine->i_findSnapshotById(sst.snapshotUuid, snapshotMachineObj, true);
1178 if (SUCCEEDED(rc) && !snapshotMachineObj.isNull())
1179 {
1180 snapshotMachineObj->i_updateSavedStatePaths(sourcePath.c_str(),
1181 targetPath.c_str());
1182 }
1183 }
1184 else
1185 {
1186 const Utf8Str &path = m_pMachine->mSSData->strStateFilePath;
1187 /*
1188 * This check for the case when a new value is equal to the old one.
1189 * Maybe the more clever check is needed in the some corner cases.
1190 */
1191 if (!path.contains(targetPath))
1192 {
1193 m_pMachine->mSSData->strStateFilePath = Utf8StrFmt("%s%s",
1194 targetPath.c_str(),
1195 path.c_str() + sourcePath.length());
1196 }
1197 }
1198
1199 ++itState;
1200 }
1201
1202 return rc;
1203}
1204
1205HRESULT MachineMoveVM::getFilesList(const Utf8Str& strRootFolder, fileList_t &filesList)
1206{
1207 RTDIR hDir;
1208 HRESULT rc = S_OK;
1209 int vrc = RTDirOpen(&hDir, strRootFolder.c_str());
1210 if (RT_SUCCESS(vrc))
1211 {
1212 RTDIRENTRY DirEntry;
1213 while (RT_SUCCESS(RTDirRead(hDir, &DirEntry, NULL)))
1214 {
1215 if (RTDirEntryIsStdDotLink(&DirEntry))
1216 continue;
1217
1218 if (DirEntry.enmType == RTDIRENTRYTYPE_FILE)
1219 {
1220 Utf8Str fullPath(strRootFolder);
1221 filesList.add(strRootFolder, DirEntry.szName);
1222 }
1223 else if (DirEntry.enmType == RTDIRENTRYTYPE_DIRECTORY)
1224 {
1225 Utf8Str strNextFolder(strRootFolder);
1226 strNextFolder.append(RTPATH_DELIMITER).append(DirEntry.szName);
1227 rc = getFilesList(strNextFolder, filesList);
1228 if (FAILED(rc))
1229 break;
1230 }
1231 }
1232
1233 vrc = RTDirClose(hDir);
1234 }
1235 else if (vrc == VERR_FILE_NOT_FOUND)
1236 {
1237 Utf8StrFmt errorDesc("Folder '%s' doesn't exist (%Rrc)", strRootFolder.c_str(), vrc);
1238 errorsList.push_back(ErrorInfoItem(VERR_FILE_NOT_FOUND, errorDesc.c_str()));
1239
1240 m_pMachine->setError(VBOX_E_IPRT_ERROR, m_pMachine->tr(errorDesc.c_str()));
1241 rc = vrc;
1242 }
1243 else
1244 {
1245 Utf8StrFmt errorDesc("Could not open folder '%s' (%Rrc)", strRootFolder.c_str(), vrc);
1246 errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
1247
1248 throw m_pMachine->setError(VBOX_E_IPRT_ERROR, m_pMachine->tr(errorDesc.c_str()));
1249 }
1250
1251 return rc;
1252}
1253
1254HRESULT MachineMoveVM::deleteFiles(const RTCList<Utf8Str>& listOfFiles)
1255{
1256 HRESULT rc = S_OK;
1257 /* Delete all created files. */
1258 try
1259 {
1260 for (size_t i = 0; i < listOfFiles.size(); ++i)
1261 {
1262 LogRelFunc(("Deleting file %s ...\n", listOfFiles.at(i).c_str()));
1263 rc = m_pProgress->SetNextOperation(BstrFmt("Deleting file %s...", listOfFiles.at(i).c_str()).raw(), 1);
1264 if (FAILED(rc)) throw rc;
1265
1266 int vrc = RTFileDelete(listOfFiles.at(i).c_str());
1267 if (RT_FAILURE(vrc))
1268 {
1269 Utf8StrFmt errorDesc("Could not delete file '%s' (%Rrc)", listOfFiles.at(i).c_str(), rc);
1270 errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
1271
1272 rc = m_pMachine->setError(VBOX_E_IPRT_ERROR, m_pMachine->tr(errorDesc.c_str()));
1273 }
1274 else
1275 LogRelFunc(("File %s has been deleted\n", listOfFiles.at(i).c_str()));
1276 }
1277 }
1278 catch(HRESULT hrc)
1279 {
1280 rc = hrc;
1281 }
1282 catch (...)
1283 {
1284 rc = VirtualBoxBase::handleUnexpectedExceptions(m_pMachine, RT_SRC_POS);
1285 }
1286
1287 return rc;
1288}
1289
1290HRESULT MachineMoveVM::getFolderSize(const Utf8Str& strRootFolder, uint64_t& size)
1291{
1292 HRESULT rc = S_OK;
1293 int vrc = 0;
1294 uint64_t totalFolderSize = 0;
1295 fileList_t filesList;
1296
1297 bool ex = RTPathExists(strRootFolder.c_str());
1298 if (ex == true)
1299 {
1300 rc = getFilesList(strRootFolder, filesList);
1301 if (SUCCEEDED(rc))
1302 {
1303 cit_t it = filesList.m_list.begin();
1304 while(it != filesList.m_list.end())
1305 {
1306 uint64_t cbFile = 0;
1307 Utf8Str fullPath = it->first;
1308 fullPath.append(RTPATH_DELIMITER).append(it->second);
1309 vrc = RTFileQuerySize(fullPath.c_str(), &cbFile);
1310 if (RT_SUCCESS(vrc))
1311 {
1312 totalFolderSize += cbFile;
1313 }
1314 else
1315 {
1316 Utf8StrFmt errorDesc("Could not get the size of file '%s' (%Rrc)", fullPath.c_str(), vrc);
1317 errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
1318
1319 throw m_pMachine->setError(VBOX_E_IPRT_ERROR, m_pMachine->tr(errorDesc.c_str()));
1320 }
1321 ++it;
1322 }
1323
1324 size = totalFolderSize;
1325 }
1326 else
1327 {
1328 Utf8StrFmt errorDesc("Could not calculate the size of folder '%s' (%Rrc)", strRootFolder.c_str(), vrc);
1329 errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
1330
1331 m_pMachine->setError(VBOX_E_IPRT_ERROR, m_pMachine->tr(errorDesc.c_str()));
1332 }
1333 }
1334 else
1335 size = 0;
1336
1337 return rc;
1338}
1339
1340HRESULT MachineMoveVM::queryBaseName(const ComPtr<IMedium> &pMedium, Utf8Str &strBaseName) const
1341{
1342 ComPtr<IMedium> pBaseMedium;
1343 HRESULT rc = pMedium->COMGETTER(Base)(pBaseMedium.asOutParam());
1344 if (FAILED(rc)) return rc;
1345 Bstr bstrBaseName;
1346 rc = pBaseMedium->COMGETTER(Name)(bstrBaseName.asOutParam());
1347 if (FAILED(rc)) return rc;
1348 strBaseName = bstrBaseName;
1349 return rc;
1350}
1351
1352HRESULT MachineMoveVM::createMachineList(const ComPtr<ISnapshot> &pSnapshot,
1353 std::vector< ComObjPtr<Machine> > &aMachineList) const
1354{
1355 HRESULT rc = S_OK;
1356 Bstr name;
1357 rc = pSnapshot->COMGETTER(Name)(name.asOutParam());
1358 if (FAILED(rc)) return rc;
1359
1360 ComPtr<IMachine> l_pMachine;
1361 rc = pSnapshot->COMGETTER(Machine)(l_pMachine.asOutParam());
1362 if (FAILED(rc)) return rc;
1363 aMachineList.push_back((Machine*)(IMachine*)l_pMachine);
1364
1365 SafeIfaceArray<ISnapshot> sfaChilds;
1366 rc = pSnapshot->COMGETTER(Children)(ComSafeArrayAsOutParam(sfaChilds));
1367 if (FAILED(rc)) return rc;
1368 for (size_t i = 0; i < sfaChilds.size(); ++i)
1369 {
1370 rc = createMachineList(sfaChilds[i], aMachineList);
1371 if (FAILED(rc)) return rc;
1372 }
1373
1374 return rc;
1375}
1376
1377HRESULT MachineMoveVM::queryMediasForAllStates(const std::vector<ComObjPtr<Machine> > &aMachineList)
1378{
1379 /* In this case we create a exact copy of the original VM. This means just
1380 * adding all directly and indirectly attached disk images to the worker
1381 * list. */
1382 HRESULT rc = S_OK;
1383 for (size_t i = 0; i < aMachineList.size(); ++i)
1384 {
1385 const ComObjPtr<Machine> &machine = aMachineList.at(i);
1386
1387 /* Add all attachments (and their parents) of the different
1388 * machines to a worker list. */
1389 SafeIfaceArray<IMediumAttachment> sfaAttachments;
1390 rc = machine->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(sfaAttachments));
1391 if (FAILED(rc)) return rc;
1392 for (size_t a = 0; a < sfaAttachments.size(); ++a)
1393 {
1394 const ComPtr<IMediumAttachment> &pAtt = sfaAttachments[a];
1395 DeviceType_T deviceType;//floppy, hard, DVD
1396 rc = pAtt->COMGETTER(Type)(&deviceType);
1397 if (FAILED(rc)) return rc;
1398
1399 /* Valid medium attached? */
1400 ComPtr<IMedium> pMedium;
1401 rc = pAtt->COMGETTER(Medium)(pMedium.asOutParam());
1402 if (FAILED(rc)) return rc;
1403
1404 if (pMedium.isNull())
1405 continue;
1406
1407 Bstr bstrLocation;
1408 rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
1409 if (FAILED(rc)) throw rc;
1410
1411 /* Cast to ComObjPtr<Medium> */
1412 ComObjPtr<Medium> pObjMedium = (Medium *)(IMedium *)pMedium;
1413
1414 /*Check for "read-only" medium in terms that VBox can't create this one */
1415 bool fPass = isMediumTypeSupportedForMoving(pMedium);
1416 if(!fPass)
1417 {
1418 LogRelFunc(("Skipping file %s because of this medium type hasn't been supported for moving.\n",
1419 Utf8Str(bstrLocation.raw()).c_str()));
1420 continue;
1421 }
1422
1423 MEDIUMTASKCHAINMOVE mtc;
1424 mtc.devType = deviceType;
1425
1426 while (!pMedium.isNull())
1427 {
1428 /* Refresh the state so that the file size get read. */
1429 MediumState_T e;
1430 rc = pMedium->RefreshState(&e);
1431 if (FAILED(rc)) return rc;
1432
1433 LONG64 lSize;
1434 rc = pMedium->COMGETTER(Size)(&lSize);
1435 if (FAILED(rc)) return rc;
1436
1437 MediumType_T mediumType;//immutable, shared, passthrough
1438 rc = pMedium->COMGETTER(Type)(&mediumType);
1439 if (FAILED(rc)) throw rc;
1440
1441 rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
1442 if (FAILED(rc)) throw rc;
1443
1444 MEDIUMTASKMOVE mt;// = {false, "basename", NULL, 0, 0};
1445 mt.strBaseName = bstrLocation;
1446 Utf8Str strFolder = vmFolders[VBox_SnapshotFolder];
1447 if (strFolder.isNotEmpty() && mt.strBaseName.contains(strFolder))
1448 {
1449 mt.fSnapshot = true;
1450 }
1451 else
1452 mt.fSnapshot = false;
1453
1454 mt.uIdx = UINT32_MAX;
1455 mt.pMedium = pMedium;
1456 mt.uWeight = (ULONG)((lSize + _1M - 1) / _1M);
1457 mtc.chain.append(mt);
1458
1459 /* Query next parent. */
1460 rc = pMedium->COMGETTER(Parent)(pMedium.asOutParam());
1461 if (FAILED(rc)) return rc;
1462 }
1463
1464 llMedias.append(mtc);
1465 }
1466 /* Add the save state files of this machine if there is one. */
1467 rc = addSaveState(machine);
1468 if (FAILED(rc)) return rc;
1469
1470 }
1471 /* Build up the index list of the image chain. Unfortunately we can't do
1472 * that in the previous loop, cause there we go from child -> parent and
1473 * didn't know how many are between. */
1474 for (size_t i = 0; i < llMedias.size(); ++i)
1475 {
1476 uint32_t uIdx = 0;
1477 MEDIUMTASKCHAINMOVE &mtc = llMedias.at(i);
1478 for (size_t a = mtc.chain.size(); a > 0; --a)
1479 mtc.chain[a - 1].uIdx = uIdx++;
1480 }
1481
1482 return rc;
1483}
1484
1485HRESULT MachineMoveVM::addSaveState(const ComObjPtr<Machine> &machine)
1486{
1487 Bstr bstrSrcSaveStatePath;
1488 HRESULT rc = machine->COMGETTER(StateFilePath)(bstrSrcSaveStatePath.asOutParam());
1489 if (FAILED(rc)) return rc;
1490 if (!bstrSrcSaveStatePath.isEmpty())
1491 {
1492 SAVESTATETASKMOVE sst;
1493
1494 sst.snapshotUuid = machine->i_getSnapshotId();
1495 sst.strSaveStateFile = bstrSrcSaveStatePath;
1496 uint64_t cbSize;
1497
1498 int vrc = RTFileQuerySize(sst.strSaveStateFile.c_str(), &cbSize);
1499 if (RT_FAILURE(vrc))
1500 {
1501 Utf8StrFmt errorDesc("Could not get file size of '%s' (%Rrc)", sst.strSaveStateFile.c_str(), vrc);
1502 errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
1503
1504 return m_pMachine->setError(VBOX_E_IPRT_ERROR, m_pMachine->tr(errorDesc.c_str()));
1505 }
1506 /* same rule as above: count both the data which needs to
1507 * be read and written */
1508 sst.uWeight = (ULONG)(2 * (cbSize + _1M - 1) / _1M);
1509 llSaveStateFiles.append(sst);
1510 }
1511 return S_OK;
1512}
1513
1514void MachineMoveVM::updateProgressStats(MEDIUMTASKCHAINMOVE &mtc, ULONG &uCount, ULONG &uTotalWeight) const
1515{
1516
1517 /* Currently the copying of diff images involves reading at least
1518 * the biggest parent in the previous chain. So even if the new
1519 * diff image is small in size, it could need some time to create
1520 * it. Adding the biggest size in the chain should balance this a
1521 * little bit more, i.e. the weight is the sum of the data which
1522 * needs to be read and written. */
1523 ULONG uMaxWeight = 0;
1524 for (size_t e = mtc.chain.size(); e > 0; --e)
1525 {
1526 MEDIUMTASKMOVE &mt = mtc.chain.at(e - 1);
1527 mt.uWeight += uMaxWeight;
1528
1529 /* Calculate progress data */
1530 ++uCount;
1531 uTotalWeight += mt.uWeight;
1532
1533 /* Save the max size for better weighting of diff image
1534 * creation. */
1535 uMaxWeight = RT_MAX(uMaxWeight, mt.uWeight);
1536 }
1537}
1538
1539bool MachineMoveVM::isMediumTypeSupportedForMoving(const ComPtr<IMedium> &pMedium)
1540{
1541 HRESULT rc = S_OK;
1542 bool fSupported = true;
1543 Bstr bstrLocation;
1544 rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
1545 if (FAILED(rc))
1546 {
1547 fSupported = false;
1548 throw rc;
1549 }
1550
1551 DeviceType_T deviceType;
1552 rc = pMedium->COMGETTER(DeviceType)(&deviceType);
1553 if (FAILED(rc))
1554 {
1555 fSupported = false;
1556 throw rc;
1557 }
1558
1559 ComPtr<IMediumFormat> mediumFormat;
1560 rc = pMedium->COMGETTER(MediumFormat)(mediumFormat.asOutParam());
1561 if (FAILED(rc))
1562 {
1563 fSupported = false;
1564 throw rc;
1565 }
1566
1567 /*Check whether VBox is able to create this medium format or not, i.e. medium can be "read-only" */
1568 Bstr bstrFormatName;
1569 rc = mediumFormat->COMGETTER(Name)(bstrFormatName.asOutParam());
1570 if (FAILED(rc))
1571 {
1572 fSupported = false;
1573 throw rc;
1574 }
1575
1576 Utf8Str formatName = Utf8Str(bstrFormatName);
1577 if (formatName.compare("VHDX", Utf8Str::CaseInsensitive) == 0)
1578 {
1579 LogRelFunc(("Skipping medium %s. VHDX format is supported in \"read-only\" mode only. \n",
1580 Utf8Str(bstrLocation.raw()).c_str()));
1581 fSupported = false;
1582 }
1583
1584 /* Check whether medium is represented by file on the disk or not */
1585 if (fSupported)
1586 {
1587 ComObjPtr<Medium> pObjMedium = (Medium *)(IMedium *)pMedium;
1588 fSupported = pObjMedium->i_isMediumFormatFile();
1589 if (!fSupported)
1590 {
1591 LogRelFunc(("Skipping medium %s because it's not a real file on the disk.\n",
1592 Utf8Str(bstrLocation.raw()).c_str()));
1593 }
1594 }
1595
1596 /* some special checks for DVD */
1597 if (fSupported && deviceType == DeviceType_DVD)
1598 {
1599 Utf8Str ext = bstrLocation;
1600 ext.assignEx(RTPathSuffix(ext.c_str()));//returns extension with dot (".iso")
1601
1602 //only ISO image is moved. Otherwise adding some information into log file
1603 int equality = ext.compare(".iso", Utf8Str::CaseInsensitive);
1604 if (equality != false)
1605 {
1606 LogRelFunc(("Skipping file %s. Only ISO images are supported for now.\n",
1607 Utf8Str(bstrLocation.raw()).c_str()));
1608 fSupported = false;
1609 }
1610 }
1611
1612 return fSupported;
1613}
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette