VirtualBox

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

Last change on this file since 78406 was 78404, checked in by vboxsync, 6 years ago

removed Utf8Str::assignEx() and replaced RTCString::compare() by RTCString::endsWith()

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