VirtualBox

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

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

FE/VBoxSDL+VirtualBox,Main/Console+Machine+VirtualBox.xidl: VMs which
crash while restoring from the 'Saved' state shouldn't lose their saved
state file. bugref:1484

A new machine state named 'AbortedSaved' has been added which a VM will
enter if it crashes when restoring from the 'Saved' state before the
'Running' state has been reached. A VM in the 'AbortedSaved' machine
state will have its saved state file preserved so that the VM can still be
restored once the cause of the failure to powerUp() and reach the
'Running' state has been resolved.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 66.6 KB
Line 
1/* $Id: MachineImplMoveVM.cpp 91363 2021-09-24 13:08:32Z vboxsync $ */
2/** @file
3 * Implementation of MachineMoveVM
4 */
5
6/*
7 * Copyright (C) 2011-2020 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("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, "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("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("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