VirtualBox

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

Last change on this file was 108740, checked in by vboxsync, 7 weeks ago

Main/MachineImplMoveVM: Minor code cleanup: remove unused variables,
clarify the wording of some of the informational and error messages,
and rename a few things for improved readability such as changing
'VBox_SettingFolder' to 'VBox_MachineFolder' since the former is
ambiguous as it could refer to either the VirtualBox settings folder
or the Machine folder.

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

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