VirtualBox

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

Last change on this file since 98110 was 98103, checked in by vboxsync, 23 months ago

Copyright year updates by scm.

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

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