VirtualBox

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

Last change on this file since 76092 was 74804, checked in by vboxsync, 6 years ago

Main/Progress: Split into safe public interface and a private one which is used purely by API implementation code (for updating). Needed quite a few minor adjustments elsewhere.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 62.1 KB
Line 
1/* $Id: MachineImplMoveVM.cpp 74804 2018-10-12 15:09:44Z vboxsync $ */
2/** @file
3 * Implementation of MachineMoveVM
4 */
5
6/*
7 * Copyright (C) 2011-2017 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17#include <iprt/fs.h>
18#include <iprt/dir.h>
19#include <iprt/file.h>
20#include <iprt/path.h>
21#include <iprt/cpp/utils.h>
22#include <iprt/stream.h>
23#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::MoveTo needs to get the VirtualBox one. */
1106 machineLock.release();
1107
1108 ComPtr<IProgress> moveDiskProgress;
1109 rc = pMedium->MoveTo(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->WaitForOtherProgressCompletion(moveDiskProgress, 0 /* indefinite wait */);
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 mtc.fAttachLinked = false;
1429 mtc.fCreateDiffs = false;
1430
1431 while (!pMedium.isNull())
1432 {
1433 /* Refresh the state so that the file size get read. */
1434 MediumState_T e;
1435 rc = pMedium->RefreshState(&e);
1436 if (FAILED(rc)) return rc;
1437
1438 LONG64 lSize;
1439 rc = pMedium->COMGETTER(Size)(&lSize);
1440 if (FAILED(rc)) return rc;
1441
1442 MediumType_T mediumType;//immutable, shared, passthrough
1443 rc = pMedium->COMGETTER(Type)(&mediumType);
1444 if (FAILED(rc)) throw rc;
1445
1446 rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
1447 if (FAILED(rc)) throw rc;
1448
1449 MEDIUMTASKMOVE mt;// = {false, "basename", NULL, 0, 0};
1450 mt.strBaseName = bstrLocation;
1451 Utf8Str strFolder = vmFolders[VBox_SnapshotFolder];
1452 if (strFolder.isNotEmpty() && mt.strBaseName.contains(strFolder))
1453 {
1454 mt.fSnapshot = true;
1455 }
1456 else
1457 mt.fSnapshot = false;
1458
1459 mt.uIdx = UINT32_MAX;
1460 mt.pMedium = pMedium;
1461 mt.uWeight = (ULONG)((lSize + _1M - 1) / _1M);
1462 mtc.chain.append(mt);
1463
1464 /* Query next parent. */
1465 rc = pMedium->COMGETTER(Parent)(pMedium.asOutParam());
1466 if (FAILED(rc)) return rc;
1467 }
1468
1469 llMedias.append(mtc);
1470 }
1471 /* Add the save state files of this machine if there is one. */
1472 rc = addSaveState(machine);
1473 if (FAILED(rc)) return rc;
1474
1475 }
1476 /* Build up the index list of the image chain. Unfortunately we can't do
1477 * that in the previous loop, cause there we go from child -> parent and
1478 * didn't know how many are between. */
1479 for (size_t i = 0; i < llMedias.size(); ++i)
1480 {
1481 uint32_t uIdx = 0;
1482 MEDIUMTASKCHAINMOVE &mtc = llMedias.at(i);
1483 for (size_t a = mtc.chain.size(); a > 0; --a)
1484 mtc.chain[a - 1].uIdx = uIdx++;
1485 }
1486
1487 return rc;
1488}
1489
1490HRESULT MachineMoveVM::addSaveState(const ComObjPtr<Machine> &machine)
1491{
1492 Bstr bstrSrcSaveStatePath;
1493 HRESULT rc = machine->COMGETTER(StateFilePath)(bstrSrcSaveStatePath.asOutParam());
1494 if (FAILED(rc)) return rc;
1495 if (!bstrSrcSaveStatePath.isEmpty())
1496 {
1497 SAVESTATETASKMOVE sst;
1498
1499 sst.snapshotUuid = machine->i_getSnapshotId();
1500 sst.strSaveStateFile = bstrSrcSaveStatePath;
1501 uint64_t cbSize;
1502
1503 int vrc = RTFileQuerySize(sst.strSaveStateFile.c_str(), &cbSize);
1504 if (RT_FAILURE(vrc))
1505 {
1506 Utf8StrFmt errorDesc("Could not get file size of '%s' (%Rrc)", sst.strSaveStateFile.c_str(), vrc);
1507 errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
1508
1509 return m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, m_pMachine->tr(errorDesc.c_str()));
1510 }
1511 /* same rule as above: count both the data which needs to
1512 * be read and written */
1513 sst.uWeight = (ULONG)(2 * (cbSize + _1M - 1) / _1M);
1514 llSaveStateFiles.append(sst);
1515 }
1516 return S_OK;
1517}
1518
1519void MachineMoveVM::updateProgressStats(MEDIUMTASKCHAINMOVE &mtc, ULONG &uCount, ULONG &uTotalWeight) const
1520{
1521
1522 /* Currently the copying of diff images involves reading at least
1523 * the biggest parent in the previous chain. So even if the new
1524 * diff image is small in size, it could need some time to create
1525 * it. Adding the biggest size in the chain should balance this a
1526 * little bit more, i.e. the weight is the sum of the data which
1527 * needs to be read and written. */
1528 ULONG uMaxWeight = 0;
1529 for (size_t e = mtc.chain.size(); e > 0; --e)
1530 {
1531 MEDIUMTASKMOVE &mt = mtc.chain.at(e - 1);
1532 mt.uWeight += uMaxWeight;
1533
1534 /* Calculate progress data */
1535 ++uCount;
1536 uTotalWeight += mt.uWeight;
1537
1538 /* Save the max size for better weighting of diff image
1539 * creation. */
1540 uMaxWeight = RT_MAX(uMaxWeight, mt.uWeight);
1541 }
1542}
1543
1544bool MachineMoveVM::isMediumTypeSupportedForMoving(const ComPtr<IMedium> &pMedium)
1545{
1546 HRESULT rc = S_OK;
1547 bool fSupported = true;
1548 Bstr bstrLocation;
1549 rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
1550 if (FAILED(rc))
1551 {
1552 fSupported = false;
1553 throw rc;
1554 }
1555
1556 DeviceType_T deviceType;
1557 rc = pMedium->COMGETTER(DeviceType)(&deviceType);
1558 if (FAILED(rc))
1559 {
1560 fSupported = false;
1561 throw rc;
1562 }
1563
1564 ComPtr<IMediumFormat> mediumFormat;
1565 rc = pMedium->COMGETTER(MediumFormat)(mediumFormat.asOutParam());
1566 if (FAILED(rc))
1567 {
1568 fSupported = false;
1569 throw rc;
1570 }
1571
1572 /*Check whether VBox is able to create this medium format or not, i.e. medium can be "read-only" */
1573 Bstr bstrFormatName;
1574 rc = mediumFormat->COMGETTER(Name)(bstrFormatName.asOutParam());
1575 if (FAILED(rc))
1576 {
1577 fSupported = false;
1578 throw rc;
1579 }
1580
1581 Utf8Str formatName = Utf8Str(bstrFormatName);
1582 if (formatName.compare("VHDX", Utf8Str::CaseInsensitive) == 0)
1583 {
1584 LogRelFunc(("Skipping medium %s. VHDX format is supported in \"read-only\" mode only. \n",
1585 Utf8Str(bstrLocation.raw()).c_str()));
1586 fSupported = false;
1587 }
1588
1589 /* Check whether medium is represented by file on the disk or not */
1590 if (fSupported)
1591 {
1592 ComObjPtr<Medium> pObjMedium = (Medium *)(IMedium *)pMedium;
1593 fSupported = pObjMedium->i_isMediumFormatFile();
1594 if (!fSupported)
1595 {
1596 LogRelFunc(("Skipping medium %s because it's not a real file on the disk.\n",
1597 Utf8Str(bstrLocation.raw()).c_str()));
1598 }
1599 }
1600
1601 /* some special checks for DVD */
1602 if (fSupported && deviceType == DeviceType_DVD)
1603 {
1604 Utf8Str ext = bstrLocation;
1605 ext.assignEx(RTPathSuffix(ext.c_str()));//returns extension with dot (".iso")
1606
1607 //only ISO image is moved. Otherwise adding some information into log file
1608 int equality = ext.compare(".iso", Utf8Str::CaseInsensitive);
1609 if (equality != false)
1610 {
1611 LogRelFunc(("Skipping file %s. Only ISO images are supported for now.\n",
1612 Utf8Str(bstrLocation.raw()).c_str()));
1613 fSupported = false;
1614 }
1615 }
1616
1617 return fSupported;
1618}
Note: See TracBrowser for help on using the repository browser.

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