VirtualBox

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

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

Main/MachienImplMoveVM.cpp: Some more review comments and a couple of cleanups. bugref:8345

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