VirtualBox

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

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

Main/Progress+Appliance+Machine: Turn IProgress::waitForAsyncProgressCompletion into an internal method. It is not needed by any client code outside the API, it's simply is too special. Also include the error propagation which it originally skipped (leding to reduntant code in the calling code). Remove a replicated version of it from the Appliance code which seems to be the half-forgotten ancestor of the method in IProgress. Adapt the users of the method to the new internal method, which made the code easier to read.

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