VirtualBox

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

Last change on this file since 94173 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

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