VirtualBox

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

Last change on this file since 81397 was 81397, checked in by vboxsync, 5 years ago

Main/MachineImplMoveVM: fix error message typo

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