VirtualBox

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

Last change on this file since 78952 was 78836, checked in by vboxsync, 6 years ago

bugref:8345. Refining and cleaning up the code.

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