VirtualBox

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

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

Main/MachineImplMoveVM: many cleanups including removal of unused code

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

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