VirtualBox

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

Last change on this file was 98352, checked in by vboxsync, 16 months ago

Main: Fix identifiers containing an incorrect plural of "medium".

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

© 2023 Oracle
ContactPrivacy policyTerms of Use