VirtualBox

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

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

bugref:8345. Second part of refining and cleaning up the code.

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