VirtualBox

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

Last change on this file since 79956 was 79956, checked in by vboxsync, 5 years ago

bugref:8345. small typo.

  • 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 79956 2019-07-24 12:18:40Z 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 hrc = aRc;
1013 Log2(("Rollback scenario: restoration the original mediums were failed. Machine can be corrupted.\n"));
1014 }
1015 catch (...)
1016 {
1017 Log2(("Rollback scenario: restoration the original mediums were failed. Machine can be corrupted.\n"));
1018 hrc = VirtualBoxBase::handleUnexpectedExceptions(machine, RT_SRC_POS);
1019 }
1020 /* In case of failure the progress object on the other side (user side) get notification about operation
1021 completion but the operation percentage may not be set to 100% */
1022 }
1023 else /*Operation was successful and now we can delete the original files like the state files, XML setting, log files */
1024 {
1025 /*
1026 * In case of success it's not urgent to update the progress object because we call i_notifyComplete() with
1027 * the success result. As result, the last number of progress operation can be not equal the number of operations
1028 * because we doubled the number of operations for rollback case.
1029 * But if we want to update the progress object corectly it's needed to add all medium moved by standard
1030 * "move medium" logic (for us it's taskMoveVM->m_finalMediumsMap) to the current number of operation.
1031 */
1032
1033 ULONG operationCount = 0;
1034 hrc = taskMoveVM->m_pProgress->COMGETTER(OperationCount)(&operationCount);
1035 ULONG operation = 0;
1036 hrc = taskMoveVM->m_pProgress->COMGETTER(Operation)(&operation);
1037
1038 for (ULONG i = operation; i < operation + taskMoveVM->m_finalMediumsMap.size() - 1; ++i)
1039 taskMoveVM->m_pProgress->SetNextOperation(BstrFmt("Skip the empty operation %d...", i).raw(), 1);
1040
1041 hrc = taskMoveVM->deleteFiles(originalFiles);
1042 if (FAILED(hrc))
1043 Log2(("Forward scenario: can't delete all original files.\n"));
1044 }
1045
1046 if (!taskMoveVM->m_pProgress.isNull())
1047 taskMoveVM->m_pProgress->i_notifyComplete(taskMoveVM->m_result);
1048
1049 LogFlowFuncLeave();
1050}
1051
1052HRESULT MachineMoveVM::moveAllDisks(const std::map<Utf8Str, MEDIUMTASKMOVE>& listOfDisks,
1053 const Utf8Str* strTargetFolder)
1054{
1055 HRESULT rc = S_OK;
1056 ComObjPtr<Machine> &machine = m_pMachine;
1057 Utf8Str strLocation;
1058
1059 AutoWriteLock machineLock(machine COMMA_LOCKVAL_SRC_POS);
1060
1061 try{
1062 std::map<Utf8Str, MEDIUMTASKMOVE>::const_iterator itMedium = listOfDisks.begin();
1063 while(itMedium != listOfDisks.end())
1064 {
1065 const MEDIUMTASKMOVE &mt = itMedium->second;
1066 ComPtr<IMedium> pMedium = mt.pMedium;
1067 Utf8Str strTargetImageName;
1068 Bstr bstrLocation;
1069 Bstr bstrSrcName;
1070
1071 rc = pMedium->COMGETTER(Name)(bstrSrcName.asOutParam());
1072 if (FAILED(rc)) throw rc;
1073
1074 if (strTargetFolder != NULL && !strTargetFolder->isEmpty())
1075 {
1076 strTargetImageName = *strTargetFolder;
1077 rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
1078 if (FAILED(rc)) throw rc;
1079 strLocation = bstrLocation;
1080
1081 if (mt.fSnapshot == true)
1082 strLocation.stripFilename().stripPath().append(RTPATH_DELIMITER).append(Utf8Str(bstrSrcName));
1083 else
1084 strLocation.stripPath();
1085
1086 strTargetImageName.append(RTPATH_DELIMITER).append(strLocation);
1087 rc = m_pProgress->SetNextOperation(BstrFmt(machine->tr("Moving medium '%ls' ..."),
1088 bstrSrcName.raw()).raw(),
1089 mt.uWeight);
1090 if (FAILED(rc)) throw rc;
1091 }
1092 else
1093 {
1094 strTargetImageName = mt.strBaseName;//Should contain full path to the image
1095 rc = m_pProgress->SetNextOperation(BstrFmt(machine->tr("Moving medium '%ls' back..."),
1096 bstrSrcName.raw()).raw(),
1097 mt.uWeight);
1098 if (FAILED(rc)) throw rc;
1099 }
1100
1101
1102
1103 /* consistency: use \ if appropriate on the platform */
1104 RTPathChangeToDosSlashes(strTargetImageName.mutableRaw(), false);
1105
1106 bstrLocation = strTargetImageName.c_str();
1107
1108 MediumType_T mediumType;//immutable, shared, passthrough
1109 rc = pMedium->COMGETTER(Type)(&mediumType);
1110 if (FAILED(rc)) throw rc;
1111
1112 DeviceType_T deviceType;//floppy, hard, DVD
1113 rc = pMedium->COMGETTER(DeviceType)(&deviceType);
1114 if (FAILED(rc)) throw rc;
1115
1116 /* Drop lock early because IMedium::MoveTo needs to get the VirtualBox one. */
1117 machineLock.release();
1118
1119 ComPtr<IProgress> moveDiskProgress;
1120 rc = pMedium->MoveTo(bstrLocation.raw(), moveDiskProgress.asOutParam());
1121 if (SUCCEEDED(rc))
1122 {
1123 /* In case of failure moveDiskProgress would be in the invalid state or not initialized at all
1124 * Call i_waitForOtherProgressCompletion only in success
1125 */
1126 /* Wait until the other process has finished. */
1127 rc = m_pProgress->WaitForOtherProgressCompletion(moveDiskProgress, 0 /* indefinite wait */);
1128 }
1129
1130 /*acquire the lock back*/
1131 machineLock.acquire();
1132
1133 if (FAILED(rc)) throw rc;
1134
1135 Log2(("Moving %s has been finished\n", strTargetImageName.c_str()));
1136
1137 ++itMedium;
1138 }
1139
1140 machineLock.release();
1141 }
1142 catch(HRESULT hrc)
1143 {
1144 Log2(("\nException during moving the disk %s\n", strLocation.c_str()));
1145 rc = hrc;
1146 machineLock.release();
1147 }
1148 catch (...)
1149 {
1150 Log2(("\nException during moving the disk %s\n", strLocation.c_str()));
1151 rc = VirtualBoxBase::handleUnexpectedExceptions(m_pMachine, RT_SRC_POS);
1152 machineLock.release();
1153 }
1154
1155 return rc;
1156}
1157
1158HRESULT MachineMoveVM::updatePathsToStateFiles(const std::map<Utf8Str, SAVESTATETASKMOVE>& listOfFiles,
1159 const Utf8Str& sourcePath, const Utf8Str& targetPath)
1160{
1161 HRESULT rc = S_OK;
1162
1163 std::map<Utf8Str, SAVESTATETASKMOVE>::const_iterator itState = listOfFiles.begin();
1164 while (itState != listOfFiles.end())
1165 {
1166 const SAVESTATETASKMOVE &sst = itState->second;
1167
1168 if (sst.snapshotUuid != Guid::Empty)
1169 {
1170 Utf8Str strGuidMachine = sst.snapshotUuid.toString();
1171 ComObjPtr<Snapshot> snapshotMachineObj;
1172
1173 rc = m_pMachine->i_findSnapshotById(sst.snapshotUuid, snapshotMachineObj, true);
1174 if (SUCCEEDED(rc) && !snapshotMachineObj.isNull())
1175 snapshotMachineObj->i_updateSavedStatePaths(sourcePath.c_str(),
1176 targetPath.c_str());
1177
1178 }
1179 else
1180 {
1181 const Utf8Str &path = m_pMachine->mSSData->strStateFilePath;
1182 /*
1183 * This check for the case when a new value is equal to the old one.
1184 * Maybe the more clever check is needed in the some corner cases.
1185 */
1186 if (!path.contains(targetPath))
1187 m_pMachine->mSSData->strStateFilePath = Utf8StrFmt("%s%s",
1188 targetPath.c_str(),
1189 path.c_str() + sourcePath.length());
1190 }
1191
1192 ++itState;
1193 }
1194
1195 return rc;
1196}
1197
1198HRESULT MachineMoveVM::getFilesList(const Utf8Str& strRootFolder, fileList_t &filesList)
1199{
1200 RTDIR hDir;
1201 HRESULT hrc = S_OK;
1202 int vrc = RTDirOpen(&hDir, strRootFolder.c_str());
1203 if (RT_SUCCESS(vrc))
1204 {
1205 /** @todo r=bird: RTDIRENTRY is big and this function is doing
1206 * unrestrained recursion of arbritrary depth. Four things:
1207 * - Add a depth counter parameter and refuse to go deeper than
1208 * a certain reasonable limit.
1209 * - Split this method into a main and a worker, placing
1210 * RTDIRENTRY on the stack in the main and passing it onto to
1211 * worker as a parameter.
1212 * - RTDirRead may fail for reasons other than
1213 * VERR_NO_MORE_FILES. For instance someone could create an
1214 * entry with a name longer than RTDIRENTRY have space to
1215 * store (windows host with UTF-16 encoding shorter than 255
1216 * chars, but UTF-8 encoding longer than 260).
1217 * - enmType can be RTDIRENTRYTYPE_UNKNOWN if the file system or
1218 * the host doesn't return the information. See
1219 * RTDIRENTRY::enmType. Use RTDirQueryUnknownType() to get the
1220 * actual type. */
1221 RTDIRENTRY DirEntry;
1222 while (RT_SUCCESS(RTDirRead(hDir, &DirEntry, NULL)))
1223 {
1224 if (RTDirEntryIsStdDotLink(&DirEntry))
1225 continue;
1226
1227 if (DirEntry.enmType == RTDIRENTRYTYPE_FILE)
1228 {
1229 Utf8Str fullPath(strRootFolder);
1230 filesList.add(strRootFolder, DirEntry.szName);
1231 }
1232 else if (DirEntry.enmType == RTDIRENTRYTYPE_DIRECTORY)
1233 {
1234 Utf8Str strNextFolder(strRootFolder);
1235 strNextFolder.append(RTPATH_DELIMITER).append(DirEntry.szName);
1236 hrc = getFilesList(strNextFolder, filesList);
1237 if (FAILED(hrc))
1238 break;
1239 }
1240 }
1241
1242 vrc = RTDirClose(hDir);
1243 AssertRC(vrc);
1244 }
1245 else if (vrc == VERR_FILE_NOT_FOUND)
1246 hrc = m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
1247 m_pMachine->tr("Folder '%s' doesn't exist (%Rrc)"),
1248 strRootFolder.c_str(), vrc);
1249 else
1250 hrc = m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
1251 m_pMachine->tr("Could not open folder '%s' (%Rrc)"),
1252 strRootFolder.c_str(), vrc);
1253
1254 return hrc;
1255}
1256
1257HRESULT MachineMoveVM::deleteFiles(const RTCList<Utf8Str>& listOfFiles)
1258{
1259 HRESULT hrc = S_OK;
1260 /* Delete all created files. */
1261 for (size_t i = 0; i < listOfFiles.size(); ++i)
1262 {
1263 Log2(("Deleting file %s ...\n", listOfFiles.at(i).c_str()));
1264 hrc = m_pProgress->SetNextOperation(BstrFmt("Deleting file %s...", listOfFiles.at(i).c_str()).raw(), 1);
1265 if (FAILED(hrc)) return hrc;
1266
1267 int vrc = RTFileDelete(listOfFiles.at(i).c_str());
1268 if (RT_FAILURE(vrc))
1269 return m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
1270 m_pMachine->tr("Could not delete file '%s' (%Rrc)"),
1271 listOfFiles.at(i).c_str(), vrc);
1272
1273 else
1274 Log2(("File %s has been deleted\n", listOfFiles.at(i).c_str()));
1275 }
1276
1277 return hrc;
1278}
1279
1280HRESULT MachineMoveVM::getFolderSize(const Utf8Str& strRootFolder, uint64_t& size)
1281{
1282 HRESULT hrc = S_OK;
1283 int vrc = 0;
1284 uint64_t totalFolderSize = 0;
1285 fileList_t filesList;
1286
1287 bool ex = RTPathExists(strRootFolder.c_str());
1288 if (ex == true)
1289 {
1290 hrc = getFilesList(strRootFolder, filesList);
1291 if (SUCCEEDED(hrc))
1292 {
1293 cit_t it = filesList.m_list.begin();
1294 while(it != filesList.m_list.end())
1295 {
1296 uint64_t cbFile = 0;
1297 Utf8Str fullPath = it->first;
1298 fullPath.append(RTPATH_DELIMITER).append(it->second);
1299 vrc = RTFileQuerySize(fullPath.c_str(), &cbFile);
1300 if (RT_SUCCESS(vrc))
1301 {
1302 totalFolderSize += cbFile;
1303 }
1304 else
1305 return m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
1306 m_pMachine->tr("Could not get the size of file '%s': %Rrc"),
1307 fullPath.c_str(),
1308 vrc);
1309
1310 ++it;
1311 }
1312
1313 size = totalFolderSize;
1314 }
1315 }
1316 else
1317 size = 0;
1318
1319 return hrc;
1320}
1321
1322HRESULT MachineMoveVM::queryBaseName(const ComPtr<IMedium> &pMedium, Utf8Str &strBaseName) const
1323{
1324 ComPtr<IMedium> pBaseMedium;
1325 HRESULT rc = pMedium->COMGETTER(Base)(pBaseMedium.asOutParam());
1326 if (FAILED(rc)) return rc;
1327 Bstr bstrBaseName;
1328 rc = pBaseMedium->COMGETTER(Name)(bstrBaseName.asOutParam());
1329 if (FAILED(rc)) return rc;
1330 strBaseName = bstrBaseName;
1331 return rc;
1332}
1333
1334HRESULT MachineMoveVM::createMachineList(const ComPtr<ISnapshot> &pSnapshot,
1335 std::vector< ComObjPtr<Machine> > &aMachineList) const
1336{
1337 Bstr name;
1338 HRESULT rc = pSnapshot->COMGETTER(Name)(name.asOutParam());
1339 if (FAILED(rc)) return rc;
1340
1341 ComPtr<IMachine> l_pMachine;
1342 rc = pSnapshot->COMGETTER(Machine)(l_pMachine.asOutParam());
1343 if (FAILED(rc)) return rc;
1344 aMachineList.push_back((Machine*)(IMachine*)l_pMachine);
1345
1346 SafeIfaceArray<ISnapshot> sfaChilds;
1347 rc = pSnapshot->COMGETTER(Children)(ComSafeArrayAsOutParam(sfaChilds));
1348 if (FAILED(rc)) return rc;
1349 for (size_t i = 0; i < sfaChilds.size(); ++i)
1350 {
1351 rc = createMachineList(sfaChilds[i], aMachineList);
1352 if (FAILED(rc)) return rc;
1353 }
1354
1355 return rc;
1356}
1357
1358HRESULT MachineMoveVM::queryMediasForAllStates(const std::vector<ComObjPtr<Machine> > &aMachineList)
1359{
1360 /* In this case we create a exact copy of the original VM. This means just
1361 * adding all directly and indirectly attached disk images to the worker
1362 * list. */
1363 HRESULT rc = S_OK;
1364 for (size_t i = 0; i < aMachineList.size(); ++i)
1365 {
1366 const ComObjPtr<Machine> &machine = aMachineList.at(i);
1367
1368 /* Add all attachments (and their parents) of the different
1369 * machines to a worker list. */
1370 SafeIfaceArray<IMediumAttachment> sfaAttachments;
1371 rc = machine->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(sfaAttachments));
1372 if (FAILED(rc)) return rc;
1373 for (size_t a = 0; a < sfaAttachments.size(); ++a)
1374 {
1375 const ComPtr<IMediumAttachment> &pAtt = sfaAttachments[a];
1376 DeviceType_T deviceType;//floppy, hard, DVD
1377 rc = pAtt->COMGETTER(Type)(&deviceType);
1378 if (FAILED(rc)) return rc;
1379
1380 /* Valid medium attached? */
1381 ComPtr<IMedium> pMedium;
1382 rc = pAtt->COMGETTER(Medium)(pMedium.asOutParam());
1383 if (FAILED(rc)) return rc;
1384
1385 if (pMedium.isNull())
1386 continue;
1387
1388 Bstr bstrLocation;
1389 rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
1390 if (FAILED(rc)) return rc;
1391
1392 /* Cast to ComObjPtr<Medium> */
1393 ComObjPtr<Medium> pObjMedium = (Medium *)(IMedium *)pMedium;
1394
1395 /*Check for "read-only" medium in terms that VBox can't create this one */
1396 rc = isMediumTypeSupportedForMoving(pMedium);
1397 if (FAILED(rc))
1398 {
1399 if (rc == S_FALSE)
1400 {
1401 Log2(("Skipping file %ls because of this medium type hasn't been supported for moving.\n",
1402 bstrLocation.raw()));
1403 continue;
1404 }
1405 else
1406 return rc;
1407 }
1408
1409 MEDIUMTASKCHAINMOVE mtc;
1410 mtc.devType = deviceType;
1411 mtc.fAttachLinked = false;
1412 mtc.fCreateDiffs = false;
1413
1414 while (!pMedium.isNull())
1415 {
1416 /* Refresh the state so that the file size get read. */
1417 MediumState_T e;
1418 rc = pMedium->RefreshState(&e);
1419 if (FAILED(rc)) return rc;
1420
1421 LONG64 lSize;
1422 rc = pMedium->COMGETTER(Size)(&lSize);
1423 if (FAILED(rc)) return rc;
1424
1425 MediumType_T mediumType;//immutable, shared, passthrough
1426 rc = pMedium->COMGETTER(Type)(&mediumType);
1427 if (FAILED(rc)) return rc;
1428
1429 rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
1430 if (FAILED(rc)) return rc;
1431
1432 MEDIUMTASKMOVE mt;// = {false, "basename", NULL, 0, 0};
1433 mt.strBaseName = bstrLocation;
1434 Utf8Str const &strFolder = m_vmFolders[VBox_SnapshotFolder];
1435 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? */
1436 mt.fSnapshot = true;
1437 else
1438 mt.fSnapshot = false;
1439
1440 mt.uIdx = UINT32_MAX;
1441 mt.pMedium = pMedium;
1442 mt.uWeight = (ULONG)((lSize + _1M - 1) / _1M);
1443 mtc.chain.append(mt);
1444
1445 /* Query next parent. */
1446 rc = pMedium->COMGETTER(Parent)(pMedium.asOutParam());
1447 if (FAILED(rc)) return rc;
1448 }
1449
1450 m_llMedias.append(mtc);
1451 }
1452 /* Add the save state files of this machine if there is one. */
1453 rc = addSaveState(machine);
1454 if (FAILED(rc)) return rc;
1455
1456 }
1457 /* Build up the index list of the image chain. Unfortunately we can't do
1458 * that in the previous loop, cause there we go from child -> parent and
1459 * didn't know how many are between. */
1460 for (size_t i = 0; i < m_llMedias.size(); ++i)
1461 {
1462 uint32_t uIdx = 0;
1463 MEDIUMTASKCHAINMOVE &mtc = m_llMedias.at(i);
1464 for (size_t a = mtc.chain.size(); a > 0; --a)
1465 mtc.chain[a - 1].uIdx = uIdx++;
1466 }
1467
1468 return rc;
1469}
1470
1471HRESULT MachineMoveVM::addSaveState(const ComObjPtr<Machine> &machine)
1472{
1473 Bstr bstrSrcSaveStatePath;
1474 HRESULT rc = machine->COMGETTER(StateFilePath)(bstrSrcSaveStatePath.asOutParam());
1475 if (FAILED(rc)) return rc;
1476 if (!bstrSrcSaveStatePath.isEmpty())
1477 {
1478 SAVESTATETASKMOVE sst;
1479
1480 sst.snapshotUuid = machine->i_getSnapshotId();
1481 sst.strSaveStateFile = bstrSrcSaveStatePath;
1482 uint64_t cbSize;
1483
1484 int vrc = RTFileQuerySize(sst.strSaveStateFile.c_str(), &cbSize);
1485 if (RT_FAILURE(vrc))
1486 return m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
1487 m_pMachine->tr("Could not get file size of '%s': %Rrc"),
1488 sst.strSaveStateFile.c_str(),
1489 vrc);
1490
1491 /* same rule as above: count both the data which needs to
1492 * be read and written */
1493 sst.uWeight = (ULONG)(2 * (cbSize + _1M - 1) / _1M);
1494 m_llSaveStateFiles.append(sst);
1495 }
1496 return S_OK;
1497}
1498
1499void MachineMoveVM::updateProgressStats(MEDIUMTASKCHAINMOVE &mtc, ULONG &uCount, ULONG &uTotalWeight) const
1500{
1501
1502 /* Currently the copying of diff images involves reading at least
1503 * the biggest parent in the previous chain. So even if the new
1504 * diff image is small in size, it could need some time to create
1505 * it. Adding the biggest size in the chain should balance this a
1506 * little bit more, i.e. the weight is the sum of the data which
1507 * needs to be read and written. */
1508 ULONG uMaxWeight = 0;
1509 for (size_t e = mtc.chain.size(); e > 0; --e)
1510 {
1511 MEDIUMTASKMOVE &mt = mtc.chain.at(e - 1);
1512 mt.uWeight += uMaxWeight;
1513
1514 /* Calculate progress data */
1515 ++uCount;
1516 uTotalWeight += mt.uWeight;
1517
1518 /* Save the max size for better weighting of diff image
1519 * creation. */
1520 uMaxWeight = RT_MAX(uMaxWeight, mt.uWeight);
1521 }
1522}
1523
1524HRESULT MachineMoveVM::isMediumTypeSupportedForMoving(const ComPtr<IMedium> &pMedium)
1525{
1526 Bstr bstrLocation;
1527 HRESULT rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
1528 if (FAILED(rc))
1529 return rc;
1530
1531 DeviceType_T deviceType;
1532 rc = pMedium->COMGETTER(DeviceType)(&deviceType);
1533 if (FAILED(rc))
1534 return rc;
1535
1536 ComPtr<IMediumFormat> mediumFormat;
1537 rc = pMedium->COMGETTER(MediumFormat)(mediumFormat.asOutParam());
1538 if (FAILED(rc))
1539 return rc;
1540
1541 /*Check whether VBox is able to create this medium format or not, i.e. medium can be "read-only" */
1542 Bstr bstrFormatName;
1543 rc = mediumFormat->COMGETTER(Name)(bstrFormatName.asOutParam());
1544 if (FAILED(rc))
1545 return rc;
1546
1547 Utf8Str formatName = Utf8Str(bstrFormatName);
1548 if (formatName.compare("VHDX", Utf8Str::CaseInsensitive) == 0)
1549 {
1550 Log2(("Skipping medium %ls. VHDX format is supported in \"read-only\" mode only. \n", bstrLocation.raw()));
1551 return S_FALSE;
1552 }
1553
1554 /* Check whether medium is represented by file on the disk or not */
1555 ComObjPtr<Medium> pObjMedium = (Medium *)(IMedium *)pMedium;
1556 if (!pObjMedium->i_isMediumFormatFile())
1557 {
1558 Log2(("Skipping medium %ls because it's not a real file on the disk.\n", bstrLocation.raw()));
1559 return S_FALSE;
1560 }
1561
1562 /* some special checks for DVD */
1563 if (deviceType == DeviceType_DVD)
1564 {
1565 Utf8Str ext = bstrLocation;
1566 /* only ISO image is moved */
1567 if (!ext.endsWith(".iso", Utf8Str::CaseInsensitive))
1568 {
1569 Log2(("Skipping file %ls. Only ISO images are supported for now.\n", bstrLocation.raw()));
1570 return S_FALSE;
1571 }
1572 }
1573
1574 return S_OK;
1575}
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