VirtualBox

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

Last change on this file since 95512 was 94598, checked in by vboxsync, 3 years ago

Main/Machine+Medium+Snapshot+VirtualBox: Recursion elimination to save stack space. Caused trouble with the current settings limits already in ASAN builds, now much higher limits would be possible, but that's not urgent. Also fix the behavior of forgetting medium objects when unregistering VMs (was previously not doing what the API documentation said in the CleanupMode_UnregisterOnly case). bugref:7717

Settings.cpp: Recursion elimination and make the handling of settings reading and writing more similar.

ValidationKit/tests/api/tdTreeDepth1.py: Improve testcase. Make sure that VM unregistering does the right thing with the associated medium objects.

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